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