Merge branch 'explain-assert' into master
[nit.git] / src / doc / api / api_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 api_base
17
18 import popcorn
19 import popcorn::pop_config
20 import popcorn::pop_repos
21 import popcorn::pop_json
22
23 import commands::commands_http
24 import commands::commands_json
25 import commands::commands_html
26
27 # Nitweb config file.
28 class NitwebConfig
29 super AppConfig
30
31 redef fun default_db_name do return "nitweb"
32
33 # Model to use.
34 var model: Model
35
36 # MModule used to flatten model.
37 var mainmodule: MModule
38
39 # Modelbuilder used to access sources.
40 var modelbuilder: ModelBuilder
41
42 # The JSON API does not filter anything by default.
43 #
44 # So we can cache the model view.
45 var view: ModelView
46
47 # Catalog to pass to handlers.
48 var catalog: Catalog is noinit
49
50 # Build the catalog
51 #
52 # This method should be called at nitweb startup.
53 # TODO move to nitweb
54 fun build_catalog do
55 var catalog = new Catalog(modelbuilder)
56 # Compute the poset
57 for p in view.mpackages do
58 var g = p.root
59 assert g != null
60 modelbuilder.scan_group(g)
61
62 catalog.deps.add_node(p)
63 for gg in p.mgroups do for m in gg.mmodules do
64 for im in m.in_importation.direct_greaters do
65 var ip = im.mpackage
66 if ip == null or ip == p then continue
67 catalog.deps.add_edge(p, ip)
68 end
69 end
70 end
71 # Build the catalog
72 for mpackage in view.mpackages do
73 catalog.package_page(mpackage)
74 catalog.git_info(mpackage)
75 catalog.mpackage_stats(mpackage)
76 end
77 self.catalog = catalog
78 end
79 end
80
81 # Specific handler for the nitweb API.
82 abstract class APIHandler
83 super Handler
84
85 # App config.
86 var config: NitwebConfig
87
88 # Find the MEntity ` with `full_name`.
89 fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
90 if full_name == null then return null
91 var mentity = model.mentity_by_full_name(full_name.from_percent_encoding)
92 if mentity == null then return null
93 if config.view.accept_mentity(mentity) then return mentity
94 return null
95 end
96
97 # Try to load the mentity from uri with `/:id`.
98 #
99 # Send 400 if `:id` is null.
100 # Send 404 if no entity is found.
101 # Return null in both cases.
102 fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
103 var id = req.param("id")
104 if id == null then
105 res.api_error(400, "Expected mentity full name")
106 return null
107 end
108 var mentity = find_mentity(config.view, id)
109 if mentity == null then
110 res.api_error(404, "MEntity `{id}` not found")
111 end
112 return mentity
113 end
114
115 # Paginate a json array
116 #
117 # Returns only a subset of `results` depending on the current `page` and the
118 # number of elements to return set by `limit`.
119 #
120 # Transforms the json array into an object:
121 # ~~~json
122 # {
123 # "page": 2,
124 # "limit": 10,
125 # "results: [ ... ],
126 # "max": 5,
127 # "total": 49
128 # }
129 # ~~~
130 fun paginate(results: JsonArray, count: Int, page, limit: nullable Int): JsonObject do
131 if page == null or page <= 0 then page = 1
132 if limit == null or limit <= 0 then limit = 20
133
134 var max = count / limit
135 if max == 0 then
136 page = 1
137 max = 1
138 else if page > max then
139 page = max
140 end
141
142 var lstart = (page - 1) * limit
143 var lend = limit
144 if lstart + lend > count then lend = count - lstart
145
146 var res = new JsonObject
147 res["page"] = page
148 res["limit"] = limit
149 res["results"] = new JsonArray.from(results.subarray(lstart, lend))
150 res["max"] = max
151 res["total"] = count
152 return res
153 end
154 end
155
156 # A Rooter dedicated to APIHandlers.
157 class APIRouter
158 super Router
159
160 # App config
161 var config: NitwebConfig
162 end
163
164 redef class HttpResponse
165
166 # Return an HTTP error response with `status`
167 #
168 # Like the rest of the API, errors are formated as JSON:
169 # ~~~json
170 # { "status": 404, "message": "Not found" }
171 # ~~~
172 fun api_error(status: Int, message: String) do
173 json(new APIError(status, message), status)
174 end
175
176 # Return `serializable` as a json string
177 #
178 # Uses `req` to define serialization options.
179 fun api_json(req: HttpRequest, serializable: nullable Serializable, status: nullable Int, plain, pretty: nullable Bool) do
180 json(serializable, status, plain, req.bool_arg("pretty"))
181 end
182 end
183
184 # An error returned by the API.
185 #
186 # Can be serialized to json.
187 class APIError
188 serialize
189
190 # Reponse status
191 var status: Int
192
193 # Response error message
194 var message: String
195 end
196
197 redef class MEntity
198
199 # URL to `self` within the web interface.
200 fun web_url: String do return "/doc/" / full_name
201
202 # URL to `self` within the JSON api.
203 fun api_url: String do return "/api/entity/" / full_name
204
205 redef fun core_serialize_to(v) do
206 super
207 v.serialize_attribute("web_url", web_url)
208 v.serialize_attribute("api_url", api_url)
209 end
210 end
211
212 redef class MEntityRef
213 redef fun core_serialize_to(v) do
214 super
215 v.serialize_attribute("web_url", mentity.web_url)
216 v.serialize_attribute("api_url", mentity.api_url)
217 end
218 end
219
220 redef class MClassDef
221 redef fun web_url do return "{mclass.web_url}/lin#{full_name}"
222 end
223
224 redef class MPropDef
225 redef fun web_url do return "{mproperty.web_url}/lin#{full_name}"
226 end
227
228 redef class MType
229 redef fun core_serialize_to(v) do
230 super
231 v.serialize_attribute("web_url", web_url)
232 v.serialize_attribute("api_url", api_url)
233 end
234 end
235
236 redef class MClassType
237 redef var web_url = mclass.web_url is lazy
238 redef var api_url = mclass.api_url is lazy
239 end
240
241 redef class MNullableType
242 redef var web_url = mtype.web_url is lazy
243 redef var api_url = mtype.api_url is lazy
244 end
245
246 redef class MParameterType
247 redef var web_url = mclass.web_url is lazy
248 redef var api_url = mclass.api_url is lazy
249 end
250
251 redef class MVirtualType
252 redef var web_url = mproperty.web_url is lazy
253 redef var api_url = mproperty.api_url is lazy
254 end