9e1cec2a50e97a2421e813daf8f44c7ca571bf14
[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 templates::html_commands
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 var filter: nullable ModelFilter
44
45 # Catalog to pass to handlers.
46 var catalog: Catalog
47 end
48
49 # Specific handler for the nitweb API.
50 abstract class APIHandler
51 super Handler
52
53 # App config.
54 var config: NitwebConfig
55
56 # Find the MEntity ` with `full_name`.
57 fun find_mentity(full_name: nullable String): nullable MEntity do
58 if full_name == null then return null
59 var mentity = config.model.mentity_by_full_name(full_name.from_percent_encoding, config.filter)
60 if mentity == null then return null
61
62 var filter = config.filter
63 if filter == null or filter.accept_mentity(mentity) then return mentity
64 return null
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(id)
79 if mentity == null then
80 res.api_error(404, "MEntity `{id}` not found")
81 end
82 return mentity
83 end
84
85 # Paginate a json array
86 #
87 # Returns only a subset of `results` depending on the current `page` and the
88 # number of elements to return set by `limit`.
89 #
90 # Transforms the json array into an object:
91 # ~~~json
92 # {
93 # "page": 2,
94 # "limit": 10,
95 # "results: [ ... ],
96 # "max": 5,
97 # "total": 49
98 # }
99 # ~~~
100 fun paginate(results: JsonArray, count: Int, page, limit: nullable Int): JsonObject do
101 if page == null or page <= 0 then page = 1
102 if limit == null or limit <= 0 then limit = 20
103
104 var max = count / limit
105 if max == 0 then
106 page = 1
107 max = 1
108 else if page > max then
109 page = max
110 end
111
112 var lstart = (page - 1) * limit
113 var lend = limit
114 if lstart + lend > count then lend = count - lstart
115
116 var res = new JsonObject
117 res["page"] = page
118 res["limit"] = limit
119 res["results"] = new JsonArray.from(results.subarray(lstart, lend))
120 res["max"] = max
121 res["total"] = count
122 return res
123 end
124 end
125
126 # A Rooter dedicated to APIHandlers.
127 class APIRouter
128 super Router
129
130 # App config
131 var config: NitwebConfig
132 end
133
134 redef class HttpResponse
135
136 # Return an HTTP error response with `status`
137 #
138 # Like the rest of the API, errors are formated as JSON:
139 # ~~~json
140 # { "status": 404, "message": "Not found" }
141 # ~~~
142 fun api_error(status: Int, message: String) do
143 json(new APIError(status, message), status)
144 end
145
146 # Return `serializable` as a json string
147 #
148 # Uses `req` to define serialization options.
149 fun api_json(req: HttpRequest, serializable: nullable Serializable, status: nullable Int, plain, pretty: nullable Bool) do
150 json(serializable, status, plain, req.bool_arg("pretty"))
151 end
152 end
153
154 # An error returned by the API.
155 #
156 # Can be serialized to json.
157 class APIError
158 serialize
159
160 # Reponse status
161 var status: Int
162
163 # Response error message
164 var message: String
165 end
166
167 redef class MEntity
168
169 # URL to `self` within the web interface.
170 fun web_url: String do return "/doc/" / full_name
171
172 # URL to `self` within the JSON api.
173 fun api_url: String do return "/api/entity/" / full_name
174
175 redef fun html_url do return web_url
176
177 redef fun core_serialize_to(v) do
178 super
179 v.serialize_attribute("web_url", web_url)
180 v.serialize_attribute("api_url", api_url)
181 end
182 end
183
184 redef class MEntityRef
185 redef fun core_serialize_to(v) do
186 super
187 v.serialize_attribute("web_url", mentity.web_url)
188 v.serialize_attribute("api_url", mentity.api_url)
189 end
190 end
191
192 redef class MClassDef
193 redef fun web_url do return "{mclass.web_url}/lin#{full_name}"
194 end
195
196 redef class MPropDef
197 redef fun web_url do return "{mproperty.web_url}/lin#{full_name}"
198 end
199
200 redef class MType
201 redef fun core_serialize_to(v) do
202 super
203 v.serialize_attribute("web_url", web_url)
204 v.serialize_attribute("api_url", api_url)
205 end
206 end
207
208 redef class MClassType
209 redef var web_url = mclass.web_url is lazy
210 redef var api_url = mclass.api_url is lazy
211 end
212
213 redef class MNullableType
214 redef var web_url = mtype.web_url is lazy
215 redef var api_url = mtype.api_url is lazy
216 end
217
218 redef class MParameterType
219 redef var web_url = mclass.web_url is lazy
220 redef var api_url = mclass.api_url is lazy
221 end
222
223 redef class MVirtualType
224 redef var web_url = mproperty.web_url is lazy
225 redef var api_url = mproperty.api_url is lazy
226 end
227
228 redef class HtmlightVisitor
229 redef fun hrefto(mentity) do return mentity.html_url
230 end
231
232 redef class CmdLicenseFile
233 redef var file_url is lazy do
234 var mentity = self.mentity
235 if mentity == null then return super
236 return "{mentity.web_url}/license"
237 end
238 end
239
240 redef class CmdContribFile
241 redef var file_url is lazy do
242 var mentity = self.mentity
243 if mentity == null then return super
244 return "{mentity.web_url}/contrib"
245 end
246 end