9bbe5ad986a7ce01742465506d839720f3ac6f2b
[nit.git] / src / web / model_api.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 module model_api
16
17 import web_base
18 import highlight
19 import uml
20
21 # Specific handler for nitweb API.
22 abstract class APIHandler
23 super ModelHandler
24
25 # The JSON API does not filter anything by default.
26 #
27 # So we can cache the model view.
28 var view: ModelView is lazy do
29 var view = new ModelView(model)
30 view.min_visibility = private_visibility
31 view.include_fictive = true
32 view.include_empty_doc = true
33 view.include_attribute = true
34 view.include_test_suite = true
35 return view
36 end
37
38 # Try to load the mentity from uri with `/:id`.
39 #
40 # Send 400 if `:id` is null.
41 # Send 404 if no entity is found.
42 # Return null in both cases.
43 fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
44 var id = req.param("id")
45 if id == null then
46 res.error 400
47 return null
48 end
49 var mentity = find_mentity(view, id)
50 if mentity == null then
51 res.error 404
52 end
53 return mentity
54 end
55 end
56
57 # Group all api handlers in one router.
58 class APIRouter
59 super Router
60
61 # Model to pass to handlers.
62 var model: Model
63
64 # ModelBuilder to pass to handlers.
65 var modelbuilder: ModelBuilder
66
67 # Mainmodule to pass to handlers.
68 var mainmodule: MModule
69
70 init do
71 use("/list", new APIList(model, mainmodule))
72 use("/search", new APISearch(model, mainmodule))
73 use("/random", new APIRandom(model, mainmodule))
74 use("/entity/:id", new APIEntity(model, mainmodule))
75 use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
76 use("/uml/:id", new APIEntityUML(model, mainmodule))
77 end
78 end
79
80 # List all mentities.
81 #
82 # MEntities can be filtered on their kind using the `k` parameter.
83 # Allowed kinds are `package`, `group`, `module`, `class`, `classdef`, `property`, `propdef`.
84 #
85 # List size can be limited with the `n` parameter.
86 #
87 # Example: `GET /list?k=module?n=10`
88 class APIList
89 super APIHandler
90
91 # List mentities depending on the `k` kind parameter.
92 fun list_mentities(req: HttpRequest): Array[MEntity] do
93 var k = req.string_arg("k")
94 var mentities = new Array[MEntity]
95 if k == "package" then
96 for mentity in view.mpackages do mentities.add mentity
97 else if k == "group" then
98 for mentity in view.mgroups do mentities.add mentity
99 else if k == "module" then
100 for mentity in view.mmodules do mentities.add mentity
101 else if k == "class" then
102 for mentity in view.mclasses do mentities.add mentity
103 else if k == "classdef" then
104 for mentity in view.mclassdefs do mentities.add mentity
105 else if k == "property" then
106 for mentity in view.mproperties do mentities.add mentity
107 else if k == "propdef" then
108 for mentity in view.mpropdefs do mentities.add mentity
109 else
110 for mentity in view.mentities do mentities.add mentity
111 end
112 return mentities
113 end
114
115 # Limit mentities depending on the `n` parameter.
116 fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
117 var n = req.int_arg("n")
118 if n != null then
119 return mentities.sub(0, n)
120 end
121 return mentities
122 end
123
124 redef fun get(req, res) do
125 var mentities = list_mentities(req)
126 mentities = limit_mentities(req, mentities)
127 res.json new JsonArray.from(mentities)
128 end
129 end
130
131 # Search mentities from a query string.
132 #
133 # Example: `GET /search?q=Arr`
134 class APISearch
135 super APIList
136
137 redef fun list_mentities(req) do
138 var q = req.string_arg("q")
139 var mentities = new Array[MEntity]
140 if q == null then return mentities
141 for mentity in view.mentities do
142 if mentity.name.has_prefix(q) then mentities.add mentity
143 end
144 return mentities
145 end
146 end
147
148 # Return a random list of MEntities.
149 #
150 # Example: `GET /random?n=10&k=module`
151 class APIRandom
152 super APIList
153
154 # Randomize mentities order.
155 fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
156 var res = mentities.to_a
157 res.shuffle
158 return res
159 end
160
161 redef fun get(req, res) do
162 var mentities = list_mentities(req)
163 mentities = limit_mentities(req, mentities)
164 mentities = randomize_mentities(req, mentities)
165 res.json new JsonArray.from(mentities)
166 end
167 end
168
169 # Return the JSON representation of a MEntity.
170 #
171 # Example: `GET /entity/core::Array`
172 class APIEntity
173 super APIHandler
174
175 redef fun get(req, res) do
176 var mentity = mentity_from_uri(req, res)
177 if mentity == null then return
178 res.json mentity.api_json(self)
179 end
180 end
181
182
183 # Return a UML representation of MEntity.
184 #
185 # Example: `GET /entity/core::Array/uml`
186 class APIEntityUML
187 super APIHandler
188
189 redef fun get(req, res) do
190 var mentity = mentity_from_uri(req, res)
191 var dot
192 if mentity isa MClassDef then mentity = mentity.mclass
193 if mentity isa MClass then
194 var uml = new UMLModel(view, mainmodule)
195 dot = uml.generate_class_uml.write_to_string
196 else if mentity isa MModule then
197 var uml = new UMLModel(view, mentity)
198 dot = uml.generate_package_uml.write_to_string
199 else
200 res.error 404
201 return
202 end
203 res.send render_svg(dot)
204 end
205
206 # Render a `dot` string as a svg image.
207 fun render_svg(dot: String): String do
208 var proc = new ProcessDuplex("dot", "-Tsvg")
209 var svg = proc.write_and_read(dot)
210 proc.close
211 proc.wait
212 return svg
213 end
214 end
215
216 # Return the source code of MEntity.
217 #
218 # Example: `GET /entity/core::Array/code`
219 class APIEntityCode
220 super APIHandler
221
222 # Modelbuilder used to access sources.
223 var modelbuilder: ModelBuilder
224
225 redef fun get(req, res) do
226 var mentity = mentity_from_uri(req, res)
227 if mentity == null then return
228 var source = render_source(mentity)
229 if source == null then
230 res.error 404
231 return
232 end
233 res.send source
234 end
235
236 # Highlight `mentity` source code.
237 private fun render_source(mentity: MEntity): nullable HTMLTag do
238 var node = modelbuilder.mentity2node(mentity)
239 if node == null then return null
240 var hl = new HighlightVisitor
241 hl.enter_visit node
242 return hl.html
243 end
244 end