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