85a2360c372be552fc8f1fef55f5f42fc5f79d80
[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 # Search mentities from a query string.
81 #
82 # Example: `GET /search?q=Arr`
83 class APISearch
84 super APIHandler
85
86 redef fun get(req, res) do
87 var q = req.string_arg("q")
88 if q == null then
89 res.error 400
90 return
91 end
92 var arr = new JsonArray
93 for mentity in view.mentities do
94 if mentity.name.has_prefix(q) then arr.add mentity
95 end
96 res.json arr
97 end
98 end
99
100 # List all mentities.
101 #
102 # MEntities can be filtered on their kind using the `k` parameter.
103 # Allowed kinds are `package`, `group`, `module`, `class`, `classdef`, `property`, `propdef`.
104 #
105 # List size can be limited with the `n` parameter.
106 #
107 # Example: `GET /list?k=module?n=10`
108 class APIList
109 super APIHandler
110
111 # List mentities depending on the `k` kind parameter.
112 fun list_mentities(req: HttpRequest): Array[MEntity] do
113 var k = req.string_arg("k")
114 var mentities = new Array[MEntity]
115 if k == "package" then
116 for mentity in view.mpackages do mentities.add mentity
117 else if k == "group" then
118 for mentity in view.mgroups do mentities.add mentity
119 else if k == "module" then
120 for mentity in view.mmodules do mentities.add mentity
121 else if k == "class" then
122 for mentity in view.mclasses do mentities.add mentity
123 else if k == "classdef" then
124 for mentity in view.mclassdefs do mentities.add mentity
125 else if k == "property" then
126 for mentity in view.mproperties do mentities.add mentity
127 else if k == "propdef" then
128 for mentity in view.mpropdefs do mentities.add mentity
129 else
130 for mentity in view.mentities do mentities.add mentity
131 end
132 return mentities
133 end
134
135 # Limit mentities depending on the `n` parameter.
136 fun limit_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
137 var n = req.int_arg("n")
138 if n != null then
139 return mentities.sub(0, n)
140 end
141 return mentities
142 end
143
144 redef fun get(req, res) do
145 var mentities = list_mentities(req)
146 mentities = limit_mentities(req, mentities)
147 var arr = new JsonArray
148 for mentity in mentities do arr.add mentity
149 res.json arr
150 end
151 end
152
153 # Return a random list of MEntities.
154 #
155 # Example: `GET /random?n=10&k=module`
156 class APIRandom
157 super APIList
158
159 # Randomize mentities order.
160 fun randomize_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
161 var res = mentities.to_a
162 res.shuffle
163 return res
164 end
165
166 redef fun get(req, res) do
167 var mentities = list_mentities(req)
168 mentities = limit_mentities(req, mentities)
169 mentities = randomize_mentities(req, mentities)
170 var arr = new JsonArray
171 for mentity in mentities do arr.add mentity
172 res.json arr
173 end
174 end
175
176 # Return the JSON representation of a MEntity.
177 #
178 # Example: `GET /entity/core::Array`
179 class APIEntity
180 super APIHandler
181
182 redef fun get(req, res) do
183 var mentity = mentity_from_uri(req, res)
184 if mentity == null then return
185 res.json mentity.api_json(self)
186 end
187 end
188
189
190 # Return a UML representation of MEntity.
191 #
192 # Example: `GET /entity/core::Array/uml`
193 class APIEntityUML
194 super APIHandler
195
196 redef fun get(req, res) do
197 var mentity = mentity_from_uri(req, res)
198 var dot
199 if mentity isa MClassDef then mentity = mentity.mclass
200 if mentity isa MClass then
201 var uml = new UMLModel(view, mainmodule)
202 dot = uml.generate_class_uml.write_to_string
203 else if mentity isa MModule then
204 var uml = new UMLModel(view, mentity)
205 dot = uml.generate_package_uml.write_to_string
206 else
207 res.error 404
208 return
209 end
210 res.send render_svg(dot)
211 end
212
213 # Render a `dot` string as a svg image.
214 fun render_svg(dot: String): String do
215 var proc = new ProcessDuplex("dot", "-Tsvg")
216 var svg = proc.write_and_read(dot)
217 proc.close
218 proc.wait
219 return svg
220 end
221 end
222
223 # Return the source code of MEntity.
224 #
225 # Example: `GET /entity/core::Array/code`
226 class APIEntityCode
227 super APIHandler
228
229 # Modelbuilder used to access sources.
230 var modelbuilder: ModelBuilder
231
232 redef fun get(req, res) do
233 var mentity = mentity_from_uri(req, res)
234 if mentity == null then return
235 var source = render_source(mentity)
236 if source == null then
237 res.error 404
238 return
239 end
240 res.send source
241 end
242
243 # Highlight `mentity` source code.
244 private fun render_source(mentity: MEntity): nullable HTMLTag do
245 var node = modelbuilder.mentity2node(mentity)
246 if node == null then return null
247 var hl = new HighlightVisitor
248 hl.enter_visit node
249 return hl.html
250 end
251 end