ac63f5379caed62ec9d7281996300b7d1510b364
[nit.git] / src / web / web_base.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 # Base classes used by `nitweb`.
16 module web_base
17
18 import model::model_views
19 import model::model_json
20 import doc_down
21 import popcorn
22 import popcorn::pop_config
23 import popcorn::pop_repos
24 import popcorn::pop_json
25
26 # Nitweb config file.
27 class NitwebConfig
28 super AppConfig
29
30 redef fun default_db_name do return "nitweb"
31
32 # Model to use.
33 var model: Model
34
35 # MModule used to flatten model.
36 var mainmodule: MModule
37
38 # Modelbuilder used to access sources.
39 var modelbuilder: ModelBuilder
40
41 # The JSON API does not filter anything by default.
42 #
43 # So we can cache the model view.
44 var view: ModelView is lazy do
45 var view = new ModelView(model)
46 view.min_visibility = private_visibility
47 view.include_fictive = true
48 view.include_empty_doc = true
49 view.include_attribute = true
50 view.include_test = true
51 return view
52 end
53 end
54
55 # Specific handler for the nitweb API.
56 abstract class APIHandler
57 super Handler
58
59 # App config.
60 var config: NitwebConfig
61
62 # Find the MEntity ` with `full_name`.
63 fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
64 if full_name == null then return null
65 return model.mentity_by_full_name(full_name.from_percent_encoding)
66 end
67
68 # Try to load the mentity from uri with `/:id`.
69 #
70 # Send 400 if `:id` is null.
71 # Send 404 if no entity is found.
72 # Return null in both cases.
73 fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
74 var id = req.param("id")
75 if id == null then
76 res.api_error(400, "Expected mentity full name")
77 return null
78 end
79 var mentity = find_mentity(config.view, id)
80 if mentity == null then
81 res.api_error(404, "MEntity `{id}` not found")
82 end
83 return mentity
84 end
85
86 # Paginate a json array
87 #
88 # Returns only a subset of `results` depending on the current `page` and the
89 # number of elements to return set by `limit`.
90 #
91 # Transforms the json array into an object:
92 # ~~~json
93 # {
94 # "page": 2,
95 # "limit": 10,
96 # "results: [ ... ],
97 # "max": 5,
98 # "total": 49
99 # }
100 # ~~~
101 fun paginate(results: JsonArray, count: Int, page, limit: nullable Int): JsonObject do
102 if page == null or page <= 0 then page = 1
103 if limit == null or limit <= 0 then limit = 20
104
105 var max = count / limit
106 if max == 0 then
107 page = 1
108 max = 1
109 else if page > max then
110 page = max
111 end
112
113 var lstart = (page - 1) * limit
114 var lend = limit
115 if lstart + lend > count then lend = count - lstart
116
117 var res = new JsonObject
118 res["page"] = page
119 res["limit"] = limit
120 res["results"] = new JsonArray.from(results.subarray(lstart, lend))
121 res["max"] = max
122 res["total"] = count
123 return res
124 end
125 end
126
127 # A Rooter dedicated to APIHandlers.
128 class APIRouter
129 super Router
130
131 # App config
132 var config: NitwebConfig
133 end
134
135 redef class HttpResponse
136
137 # Return an HTTP error response with `status`
138 #
139 # Like the rest of the API, errors are formated as JSON:
140 # ~~~json
141 # { "status": 404, "message": "Not found" }
142 # ~~~
143 fun api_error(status: Int, message: String) do
144 json(new APIError(status, message), status)
145 end
146
147 # Write data as JSON and set the right content type header.
148 fun raw_json(json: nullable String, status: nullable Int) do
149 header["Content-Type"] = media_types["json"].as(not null)
150 if json == null then
151 send(null, status)
152 else
153 send(json, status)
154 end
155 end
156 end
157
158 # An error returned by the API.
159 #
160 # Can be serialized to json.
161 class APIError
162 serialize
163
164 # Reponse status
165 var status: Int
166
167 # Response error message
168 var message: String
169 end
170
171 # Fullname representation that can be used to build decorated links
172 #
173 # * MPackage: `mpackage_name`
174 # * MGroup: `(mpackage_name::)mgroup_name`
175 class Namespace
176 super Array[nullable NSEntity]
177 super NSEntity
178 serialize
179
180 redef fun to_s do return self.join("")
181 redef fun serialize_to(v) do to_a.serialize_to(v)
182 end
183
184 # Something that goes in a Namespace
185 #
186 # Can be either:
187 # * a `NSToken` for tokens like `::`, `>` and `$`
188 # * a `MSRef` for references to mentities
189 interface NSEntity
190 super Serializable
191 end
192
193 # A reference to a MEntity that can be rendered as a link.
194 #
195 # We do not reuse `MEntityRef` ref since NSRef can be found in a ref and create
196 # an infinite loop.
197 class NSRef
198 super NSEntity
199 serialize
200
201 # The mentity to link to/
202 var mentity: MEntity
203
204 redef fun core_serialize_to(v) do
205 v.serialize_attribute("web_url", mentity.web_url)
206 v.serialize_attribute("api_url", mentity.api_url)
207 v.serialize_attribute("name", mentity.name)
208 end
209 end
210
211 # A namespace token representation
212 #
213 # Used for namespace tokens like `::`, `>` and `$`
214 redef class String
215 super NSEntity
216 end
217
218 redef class MEntity
219
220 # URL to `self` within the web interface.
221 fun web_url: String do return "/doc/" / full_name
222
223 # URL to `self` within the JSON api.
224 fun api_url: String do return "/api/entity/" / full_name
225
226 redef fun core_serialize_to(v) do
227 super
228 v.serialize_attribute("namespace", namespace)
229 v.serialize_attribute("web_url", web_url)
230 v.serialize_attribute("api_url", api_url)
231 end
232
233 # Return `self.full_name` as an object that can be serialized to json.
234 fun namespace: nullable Namespace do return null
235
236 # Return a new NSRef to `self`.
237 fun to_ns_ref: NSRef do return new NSRef(self)
238 end
239
240 redef class MEntityRef
241 redef fun core_serialize_to(v) do
242 super
243 v.serialize_attribute("namespace", mentity.namespace)
244 v.serialize_attribute("web_url", mentity.web_url)
245 v.serialize_attribute("api_url", mentity.api_url)
246 v.serialize_attribute("name", mentity.name)
247 v.serialize_attribute("mdoc", mentity.mdoc_or_fallback)
248 v.serialize_attribute("visibility", mentity.visibility.to_s)
249 v.serialize_attribute("modifiers", mentity.collect_modifiers)
250 v.serialize_attribute("class_name", mentity.class_name)
251 var mentity = self.mentity
252 if mentity isa MMethod then
253 v.serialize_attribute("msignature", mentity.intro.msignature)
254 else if mentity isa MMethodDef then
255 v.serialize_attribute("msignature", mentity.msignature)
256 else if mentity isa MVirtualTypeProp then
257 v.serialize_attribute("bound", to_mentity_ref(mentity.intro.bound))
258 else if mentity isa MVirtualTypeDef then
259 v.serialize_attribute("bound", to_mentity_ref(mentity.bound))
260 end
261 v.serialize_attribute("location", mentity.location)
262 end
263 end
264
265 redef class MDoc
266
267 # Add doc down processing
268 redef fun core_serialize_to(v) do
269 v.serialize_attribute("html_synopsis", html_synopsis.write_to_string)
270 end
271 end
272
273 redef class MPackage
274 redef fun namespace do return new Namespace.from([to_ns_ref])
275 end
276
277 redef class MGroup
278 redef fun namespace do
279 var p = parent
280 if p == null then
281 return new Namespace.from([to_ns_ref, ">": nullable NSEntity])
282 end
283 return new Namespace.from([p.namespace, to_ns_ref, ">": nullable NSEntity])
284 end
285 end
286
287 redef class MModule
288 redef fun namespace do
289 var mgroup = self.mgroup
290 if mgroup == null then
291 return new Namespace.from([to_ns_ref])
292 end
293 return new Namespace.from([mgroup.mpackage.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
294 end
295
296 private fun ns_for(visibility: MVisibility): nullable Namespace do
297 if visibility <= private_visibility then return namespace
298 var mgroup = self.mgroup
299 if mgroup == null then return namespace
300 return mgroup.mpackage.namespace
301 end
302 end
303
304 redef class MClass
305 redef fun namespace do
306 return new Namespace.from([intro_mmodule.ns_for(visibility), "::", to_ns_ref: nullable NSEntity])
307 end
308 end
309
310 redef class MClassDef
311 redef fun namespace do
312 if is_intro then
313 return new Namespace.from([mmodule.ns_for(mclass.visibility), "$", to_ns_ref: nullable NSEntity])
314 else if mclass.intro_mmodule.mpackage != mmodule.mpackage then
315 return new Namespace.from([mmodule.namespace, "$", mclass.namespace: nullable NSEntity])
316 else if mclass.visibility > private_visibility then
317 return new Namespace.from([mmodule.namespace, "$", mclass.to_ns_ref: nullable NSEntity])
318 end
319 return new Namespace.from([mmodule.full_name, "$::", mclass.intro_mmodule.to_ns_ref: nullable NSEntity])
320 end
321
322 redef fun web_url do return "{mclass.web_url}/lin#{full_name}"
323 end
324
325 redef class MProperty
326 redef fun namespace do
327 if intro_mclassdef.is_intro then
328 return new Namespace.from([intro_mclassdef.mmodule.ns_for(visibility), "::", intro_mclassdef.mclass.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
329 else
330 return new Namespace.from([intro_mclassdef.mmodule.namespace, "::", intro_mclassdef.mclass.to_ns_ref, "::", to_ns_ref: nullable NSEntity])
331 end
332 end
333 end
334
335 redef class MPropDef
336 redef fun namespace do
337 var res = new Namespace
338 res.add mclassdef.namespace
339 res.add "$"
340
341 if mclassdef.mclass == mproperty.intro_mclassdef.mclass then
342 res.add to_ns_ref
343 else
344 if mclassdef.mmodule.mpackage != mproperty.intro_mclassdef.mmodule.mpackage then
345 res.add mproperty.intro_mclassdef.mmodule.ns_for(mproperty.visibility)
346 res.add "::"
347 else if mproperty.visibility <= private_visibility then
348 if mclassdef.mmodule.namespace_for(mclassdef.mclass.visibility) != mproperty.intro_mclassdef.mmodule.mpackage then
349 res.add "::"
350 res.add mproperty.intro_mclassdef.mmodule.to_ns_ref
351 res.add "::"
352 end
353 end
354 if mclassdef.mclass != mproperty.intro_mclassdef.mclass then
355 res.add mproperty.intro_mclassdef.to_ns_ref
356 res.add "::"
357 end
358 res.add to_ns_ref
359 end
360 return res
361 end
362
363 redef fun web_url do return "{mproperty.web_url}/lin#{full_name}"
364 end
365
366 redef class MClassType
367 redef var web_url = mclass.web_url is lazy
368 end
369
370 redef class MNullableType
371 redef var web_url = mtype.web_url is lazy
372 end
373
374 redef class MParameterType
375 redef var web_url = mclass.web_url is lazy
376 end
377
378 redef class MVirtualType
379 redef var web_url = mproperty.web_url is lazy
380 end
381
382 redef class POSetElement[E]
383 super Serializable
384
385 redef fun core_serialize_to(v) do
386 assert self isa POSetElement[MEntity]
387 v.serialize_attribute("direct_greaters", to_mentity_refs(direct_greaters))
388 v.serialize_attribute("direct_smallers", to_mentity_refs(direct_smallers))
389 end
390 end