]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * This file is open source software, licensed to you under the terms | |
3 | * of the Apache License, Version 2.0 (the "License"). See the NOTICE file | |
4 | * distributed with this work for additional information regarding copyright | |
5 | * ownership. You may not use this file except in compliance with the License. | |
6 | * | |
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, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | /* | |
19 | * Copyright 2015 Cloudius Systems | |
20 | */ | |
21 | ||
22 | #pragma once | |
23 | #include <seastar/json/json_elements.hh> | |
24 | #include <seastar/json/formatter.hh> | |
25 | #include <seastar/http/routes.hh> | |
26 | #include <seastar/http/transformers.hh> | |
27 | #include <string> | |
28 | #include <seastar/util/noncopyable_function.hh> | |
29 | ||
30 | namespace seastar { | |
31 | ||
32 | namespace httpd { | |
33 | ||
34 | struct api_doc : public json::json_base { | |
35 | json::json_element<std::string> path; | |
36 | json::json_element<std::string> description; | |
37 | ||
38 | void register_params() { | |
39 | add(&path, "path"); | |
40 | add(&description, "description"); | |
41 | ||
42 | } | |
43 | api_doc() { | |
44 | register_params(); | |
45 | } | |
9f95a23c TL |
46 | api_doc(const api_doc & e) |
47 | : json::json_base() | |
48 | { | |
11fdf7f2 TL |
49 | register_params(); |
50 | path = e.path; | |
51 | description = e.description; | |
52 | } | |
53 | template<class T> | |
54 | api_doc& operator=(const T& e) { | |
55 | path = e.path; | |
56 | description = e.description; | |
57 | return *this; | |
58 | } | |
59 | api_doc& operator=(const api_doc& e) { | |
60 | path = e.path; | |
61 | description = e.description; | |
62 | return *this; | |
63 | } | |
64 | }; | |
65 | ||
66 | struct api_docs : public json::json_base { | |
67 | json::json_element<std::string> apiVersion; | |
68 | json::json_element<std::string> swaggerVersion; | |
69 | json::json_list<api_doc> apis; | |
70 | ||
71 | void register_params() { | |
72 | add(&apiVersion, "apiVersion"); | |
73 | add(&swaggerVersion, "swaggerVersion"); | |
74 | add(&apis, "apis"); | |
75 | ||
76 | } | |
77 | api_docs() { | |
78 | apiVersion = "0.0.1"; | |
79 | swaggerVersion = "1.2"; | |
80 | register_params(); | |
81 | } | |
9f95a23c TL |
82 | api_docs(const api_docs & e) |
83 | : json::json_base() | |
84 | { | |
11fdf7f2 TL |
85 | apiVersion = "0.0.1"; |
86 | swaggerVersion = "1.2"; | |
87 | register_params(); | |
88 | } | |
89 | template<class T> | |
90 | api_docs& operator=(const T& e) { | |
91 | apis = e.apis; | |
92 | return *this; | |
93 | } | |
94 | api_docs& operator=(const api_docs& e) { | |
95 | apis = e.apis; | |
96 | return *this; | |
97 | } | |
98 | }; | |
99 | ||
100 | class api_registry_base : public handler_base { | |
101 | protected: | |
102 | sstring _base_path; | |
103 | sstring _file_directory; | |
104 | routes& _routes; | |
105 | ||
106 | public: | |
107 | api_registry_base(routes& routes, const sstring& file_directory, | |
108 | const sstring& base_path) | |
109 | : _base_path(base_path), _file_directory(file_directory), _routes( | |
110 | routes) { | |
111 | } | |
112 | ||
113 | void set_route(handler_base* h) { | |
114 | _routes.put(GET, _base_path, h); | |
115 | } | |
116 | virtual ~api_registry_base() = default; | |
117 | }; | |
118 | ||
119 | class api_registry : public api_registry_base { | |
120 | api_docs _docs; | |
121 | public: | |
122 | api_registry(routes& routes, const sstring& file_directory, | |
123 | const sstring& base_path) | |
124 | : api_registry_base(routes, file_directory, base_path) { | |
125 | set_route(this); | |
126 | } | |
127 | ||
128 | future<std::unique_ptr<reply>> handle(const sstring& path, | |
129 | std::unique_ptr<request> req, std::unique_ptr<reply> rep) override { | |
130 | rep->_content = json::formatter::to_json(_docs); | |
131 | rep->done("json"); | |
132 | return make_ready_future<std::unique_ptr<reply>>(std::move(rep)); | |
133 | } | |
134 | ||
135 | void reg(const sstring& api, const sstring& description, | |
136 | const sstring& alternative_path = "") { | |
137 | api_doc doc; | |
138 | doc.description = description; | |
139 | doc.path = "/" + api; | |
140 | _docs.apis.push(doc); | |
141 | sstring path = | |
142 | (alternative_path == "") ? | |
143 | _file_directory + api + ".json" : alternative_path; | |
144 | file_handler* index = new file_handler(path, | |
145 | new content_replace("json")); | |
146 | _routes.put(GET, _base_path + "/" + api, index); | |
147 | } | |
148 | }; | |
149 | ||
150 | class api_registry_builder_base { | |
151 | protected: | |
152 | sstring _file_directory; | |
153 | sstring _base_path; | |
154 | static const sstring DEFAULT_DIR; | |
155 | static const sstring DEFAULT_PATH; | |
156 | public: | |
157 | api_registry_builder_base(const sstring& file_directory = DEFAULT_DIR, | |
158 | const sstring& base_path = DEFAULT_PATH) | |
159 | : _file_directory(file_directory), _base_path(base_path) { | |
160 | } | |
161 | }; | |
162 | ||
163 | class api_registry_builder : public api_registry_builder_base { | |
164 | public: | |
165 | api_registry_builder(const sstring& file_directory = DEFAULT_DIR, | |
166 | const sstring& base_path = DEFAULT_PATH) | |
167 | : api_registry_builder_base(file_directory, base_path) { | |
168 | } | |
169 | ||
170 | void set_api_doc(routes& r) { | |
171 | new api_registry(r, _file_directory, _base_path); | |
172 | } | |
173 | ||
174 | void register_function(routes& r, const sstring& api, | |
175 | const sstring& description, const sstring& alternative_path = "") { | |
176 | auto h = r.get_exact_match(GET, _base_path); | |
177 | if (h) { | |
178 | // if a handler is found, it was added there by the api_registry_builder | |
179 | // with the set_api_doc method, so we know it's the type | |
180 | static_cast<api_registry*>(h)->reg(api, description, alternative_path); | |
181 | }; | |
182 | } | |
183 | }; | |
184 | ||
185 | using doc_entry = noncopyable_function<future<>(output_stream<char>&)>; | |
186 | ||
187 | /*! | |
188 | * \brief a helper function that creates a reader from a file | |
189 | */ | |
190 | ||
191 | doc_entry get_file_reader(sstring file_name); | |
192 | ||
193 | /*! | |
194 | * \brief An api doc that support swagger version 2.0 | |
195 | * | |
196 | * The result is a unified JSON file with the swagger definitions. | |
197 | * | |
198 | * The file content is a concatenation of the doc_entry by the order of | |
199 | * their entry. | |
200 | * | |
201 | * Definitions will be added under the definition section | |
202 | * | |
203 | * typical usage: | |
204 | * | |
205 | * First entry: | |
206 | * | |
207 | { | |
208 | "swagger": "2.0", | |
209 | "host": "localhost:10000", | |
210 | "basePath": "/v2", | |
211 | "paths": { | |
212 | ||
213 | * entry: | |
214 | "/config/{id}": { | |
215 | "get": { | |
216 | "description": "Return a config value", | |
217 | "operationId": "findConfigId", | |
218 | "produces": [ | |
219 | "application/json" | |
220 | ], | |
221 | } | |
222 | } | |
223 | * | |
224 | * Closing the entries: | |
225 | }, | |
226 | ||
227 | "definitions": { | |
228 | ..... | |
229 | ||
230 | ..... | |
231 | } | |
232 | } | |
233 | * | |
234 | */ | |
235 | class api_docs_20 { | |
236 | std::vector<doc_entry> _apis; | |
237 | content_replace _transform; | |
238 | std::vector<doc_entry> _definitions; | |
239 | ||
240 | public: | |
241 | future<> write(output_stream<char>&&, std::unique_ptr<request> req); | |
242 | ||
243 | void add_api(doc_entry&& f) { | |
244 | _apis.emplace_back(std::move(f)); | |
245 | } | |
246 | ||
247 | void add_definition(doc_entry&& f) { | |
248 | _definitions.emplace_back(std::move(f)); | |
249 | } | |
250 | }; | |
251 | ||
252 | class api_registry_20 : public api_registry_base { | |
253 | api_docs_20 _docs; | |
254 | public: | |
255 | api_registry_20(routes& routes, const sstring& file_directory, | |
256 | const sstring& base_path) | |
257 | : api_registry_base(routes, file_directory, base_path) { | |
258 | set_route(this); | |
259 | } | |
260 | ||
261 | future<std::unique_ptr<reply>> handle(const sstring& path, | |
262 | std::unique_ptr<request> req, std::unique_ptr<reply> rep) override { | |
263 | rep->write_body("json", [this, req = std::move(req)] (output_stream<char>&& os) mutable { | |
264 | return _docs.write(std::move(os), std::move(req)); | |
265 | }); | |
266 | return make_ready_future<std::unique_ptr<reply>>(std::move(rep)); | |
267 | } | |
268 | ||
269 | virtual void reg(doc_entry&& f) { | |
270 | _docs.add_api(std::move(f)); | |
271 | } | |
272 | ||
273 | virtual void add_definition(doc_entry&& f) { | |
274 | _docs.add_definition(std::move(f)); | |
275 | } | |
276 | }; | |
277 | ||
278 | class api_registry_builder20 : public api_registry_builder_base { | |
279 | api_registry_20* get_register_base(routes& r) { | |
280 | auto h = r.get_exact_match(GET, _base_path); | |
281 | if (h) { | |
282 | // if a handler is found, it was added there by the api_registry_builder | |
283 | // with the set_api_doc method, so we know it's the type | |
284 | return static_cast<api_registry_20*>(h); | |
285 | } | |
286 | return nullptr; | |
287 | } | |
288 | ||
289 | public: | |
290 | api_registry_builder20(const sstring& file_directory = DEFAULT_DIR, | |
291 | const sstring& base_path = DEFAULT_PATH) | |
292 | : api_registry_builder_base(file_directory, base_path) { | |
293 | } | |
294 | ||
295 | void set_api_doc(routes& r) { | |
296 | new api_registry_20(r, _file_directory, _base_path); | |
297 | } | |
298 | ||
299 | /*! | |
300 | * \brief register a doc_entry | |
301 | * This doc_entry can be used to either take the definition from a file | |
302 | * or generate them dynamically. | |
303 | */ | |
304 | void register_function(routes& r, doc_entry&& f) { | |
305 | auto h = get_register_base(r); | |
306 | if (h) { | |
307 | h->reg(std::move(f)); | |
308 | } | |
309 | } | |
310 | /*! | |
311 | * \brief register an API | |
312 | */ | |
313 | void register_api_file(routes& r, const sstring& api) { | |
314 | register_function(r, get_file_reader(_file_directory + "/" + api + ".json")); | |
315 | } | |
316 | ||
317 | ||
318 | /*! | |
319 | * Add a footer doc_entry | |
320 | */ | |
321 | void add_definition(routes& r, doc_entry&& f) { | |
322 | auto h = get_register_base(r); | |
323 | if (h) { | |
324 | h->add_definition(std::move(f)); | |
325 | } | |
326 | ||
327 | } | |
328 | ||
329 | /*! | |
330 | * Add a definition file | |
331 | */ | |
332 | void add_definitions_file(routes& r, const sstring& file) { | |
333 | add_definition(r, get_file_reader(_file_directory + file + ".def.json" )); | |
334 | } | |
335 | ||
336 | }; | |
337 | ||
338 | } | |
339 | ||
340 | } |