Merge: doc: fixed some typos and other misc. corrections
[nit.git] / lib / popcorn / pop_json.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2017 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Introduce useful services for JSON REST API handlers.
18 #
19 # Validation and Deserialization of request bodies:
20 #
21 # ~~~nit
22 # class MyJsonHandler
23 # super Handler
24 #
25 # # Validator used do validate the body
26 # redef var validator = new MyFormValidator
27 #
28 # # Define the kind of objects expected by the deserialization process
29 # redef type BODY: MyForm
30 #
31 # redef fun post(req, res) do
32 # var post = validate_body(req, res)
33 # if post == null then return # Validation error: let popcorn return a HTTP 400
34 # var form = deserialize_body(req, res)
35 # if form == null then return # Deserialization error: let popcorn return a HTTP 400
36 #
37 # # TODO do something with the input
38 # print form.name
39 # end
40 # end
41 #
42 # class MyForm
43 # serialize
44 #
45 # var name: String
46 # end
47 #
48 # class MyFormValidator
49 # super ObjectValidator
50 #
51 # init do
52 # add new StringField("name", min_size=1, max_size=255)
53 # end
54 # end
55 # ~~~
56 module pop_json
57
58 import json
59 import meta
60 import pop_handlers
61 import pop_validation
62
63 redef class Handler
64
65 # Validator used to check body input
66 #
67 # Here we use the `pop_validation` module to validate JSON document from the request body.
68 var validator: nullable DocumentValidator = null
69
70 # Validate body input with `validator`
71 #
72 # Try to validate the request body as a json document using `validator`:
73 # * Returns the validated string input if the result of the validation is ok.
74 # * Answers a json error and returns `null` if something went wrong.
75 # * If no `validator` is set, returns the body without validation.
76 #
77 # Example:
78 #
79 # ~~~nit
80 # class ValidatedHandler
81 # super Handler
82 #
83 # redef var validator = new MyObjectValidator
84 #
85 # redef fun post(req, res) do
86 # var body = validate_body(req, res)
87 # if body == null then return # Validation error
88 # # At this point popcorn returned a HTTP 400 code with the validation error
89 # # if the validation failed.
90 #
91 # # TODO do something with the input
92 # print body
93 # end
94 # end
95 #
96 # class MyObjectValidator
97 # super ObjectValidator
98 #
99 # init do
100 # add new StringField("name", min_size=1, max_size=255)
101 # end
102 # end
103 # ~~~
104 fun validate_body(req: HttpRequest, res: HttpResponse): nullable String do
105 var body = req.body
106
107 var validator = self.validator
108 if validator == null then return body
109
110 if not validator.validate(body) then
111 res.json(validator.validation, 400)
112 return null
113 end
114 return body
115 end
116
117 # Deserialize the request body
118 #
119 # Returns the deserialized request body body or `null` if something went wrong.
120 # If the object cannot be deserialized, answers with a HTTP 400.
121 #
122 # See `BODY` and `new_body_object`.
123 #
124 # Example:
125 # ~~~nit
126 # class MyDeserializedHandler
127 # super Handler
128 #
129 # redef type BODY: MyObject
130 #
131 # redef fun post(req, res) do
132 # var form = deserialize_body(req, res)
133 # if form == null then return # Deserialization error
134 # # At this point popcorn returned a HTTP 400 code if something was wrong with
135 # # the deserialization process
136 #
137 # # TODO do something with the input
138 # print form.name
139 # end
140 # end
141 #
142 # class MyObject
143 # serialize
144 #
145 # var name: String
146 # end
147 # ~~~
148 fun deserialize_body(req: HttpRequest, res: HttpResponse): nullable BODY do
149 var body = req.body
150 var deserializer = new JsonDeserializer(body)
151 var form = deserializer.deserialize(body_type)
152 if not form isa BODY or deserializer.errors.not_empty then
153 res.json_error("Bad input", 400)
154 return null
155 end
156 return form
157 end
158
159 # Kind of objects returned by `deserialize_body`
160 #
161 # Define it in each sub handlers depending on the kind of objects sent in request bodies.
162 type BODY: Serializable
163
164 private var body_type: String is lazy do return (new GetName[BODY]).to_s
165 end
166
167 redef class HttpResponse
168
169 # Write data as JSON and set the right content type header.
170 fun json(json: nullable Serializable, status: nullable Int, plain, pretty: nullable Bool) do
171 header["Content-Type"] = media_types["json"].as(not null)
172 if json == null then
173 send(null, status)
174 else
175 send(json.serialize_to_json(plain or else true, pretty or else false), status)
176 end
177 end
178
179 # Write error as JSON.
180 #
181 # Format: `{"message": message, "status": status}`
182 fun json_error(message: String, status: Int) do
183 var obj = new JsonObject
184 obj["status"] = status
185 obj["message"] = message
186 json(obj, status)
187 end
188 end