Allow dynamic creation of APIs
[src/app-framework-binder.git] / src / afb-api-dyn.c
1 /*
2  * Copyright (C) 2016, 2017 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
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
18 #define _GNU_SOURCE
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <errno.h>
24
25 #include <json-c/json.h>
26
27 #define AFB_BINDING_VERSION 0
28 #include <afb/afb-binding.h>
29
30 #include "afb-api.h"
31 #include "afb-api-dyn.h"
32 #include "afb-apiset.h"
33 #include "afb-auth.h"
34 #include "afb-export.h"
35 #include "afb-xreq.h"
36 #include "verbose.h"
37
38 /*
39  * Description of a binding
40  */
41 struct afb_api_dyn {
42         int count;
43         struct afb_api_dyn_verb **verbs;
44         const struct afb_verb_v2 *verbsv2;
45         struct afb_export *export;
46         char info[1];
47 };
48
49 void afb_api_dyn_set_verbs_v2(
50                 struct afb_api_dyn *dynapi,
51                 const struct afb_verb_v2 *verbs)
52 {
53         dynapi->verbsv2 = verbs;
54 }
55
56 int afb_api_dyn_add_verb(
57                 struct afb_api_dyn *dynapi,
58                 const char *verb,
59                 const char *info,
60                 void (*callback)(struct afb_request *request),
61                 const struct afb_auth *auth,
62                 uint32_t session)
63 {
64         struct afb_api_dyn_verb *v, **vv;
65
66         vv = realloc(dynapi->verbs, (1 + dynapi->count) * sizeof *vv);
67         if (!vv)
68                 goto oom;
69         dynapi->verbs = vv;
70
71         v = malloc(sizeof *v + strlen(verb) + (info ? 1 + strlen(info) : 0));
72         if (!v)
73                 goto oom;
74
75         v->callback = callback;
76         v->auth = auth;
77         v->session = session;
78
79         v->info = 1 + stpcpy(v->verb, verb);
80         if (info)
81                 strcpy((char*)v->info, info);
82         else
83                 v->info = NULL;
84
85         dynapi->verbs[dynapi->count++] = v;
86         return 0;
87 oom:
88         errno = ENOMEM;
89         return -1;
90 }
91
92 int afb_api_dyn_sub_verb(
93                 struct afb_api_dyn *dynapi,
94                 const char *verb)
95 {
96         struct afb_api_dyn_verb *v;
97         int i;
98
99         /* look first in dyna mic verbs */
100         for (i = 0 ; i < dynapi->count ; i++) {
101                 v = dynapi->verbs[i];
102                 if (!strcasecmp(v->verb, verb)) {
103                         if (i != --dynapi->count)
104                                 dynapi->verbs[i] = dynapi->verbs[dynapi->count];
105                         free(v);
106                         return 0;
107                 }
108         }
109
110         errno = ENOENT;
111         return -1;
112 }
113
114 static void call_cb(void *closure, struct afb_xreq *xreq)
115 {
116         struct afb_api_dyn *dynapi = closure;
117         struct afb_api_dyn_verb **verbs;
118         const struct afb_verb_v2 *verbsv2;
119         int i;
120         const char *name;
121
122         name = xreq->verb;
123         xreq->request.dynapi = (void*)dynapi->export; /* hack: this avoids to export afb_export structure */
124
125         /* look first in dyna mic verbs */
126         verbs = dynapi->verbs;
127         i = dynapi->count;
128         while (i) {
129                 if (!strcasecmp(verbs[--i]->verb, name)) {
130                         afb_xreq_call_verb_vdyn(xreq, verbs[i]);
131                         return;
132                 }
133         }
134
135         verbsv2 = dynapi->verbsv2;
136         if (verbsv2) {
137                 while (verbsv2->verb) {
138                         if (strcasecmp(verbsv2->verb, name))
139                                 verbsv2++;
140                         else {
141                                 afb_xreq_call_verb_v2(xreq, verbsv2);
142                                 return;
143                         }
144                 }
145         }
146
147         afb_xreq_fail_unknown_verb(xreq);
148 }
149
150 static int service_start_cb(void *closure, int share_session, int onneed, struct afb_apiset *apiset)
151 {
152         struct afb_api_dyn *dynapi = closure;
153         return afb_export_start(dynapi->export, share_session, onneed, apiset);
154 }
155
156 static void update_hooks_cb(void *closure)
157 {
158         struct afb_api_dyn *dynapi = closure;
159         afb_export_update_hook(dynapi->export);
160 }
161
162 static int get_verbosity_cb(void *closure)
163 {
164         struct afb_api_dyn *dynapi = closure;
165         return afb_export_verbosity_get(dynapi->export);
166 }
167
168 static void set_verbosity_cb(void *closure, int level)
169 {
170         struct afb_api_dyn *dynapi = closure;
171         afb_export_verbosity_set(dynapi->export, level);
172 }
173
174 static struct json_object *make_description_openAPIv3(struct afb_api_dyn *dynapi)
175 {
176         char buffer[256];
177         struct afb_api_dyn_verb **iter, **end, *verb;
178         struct json_object *r, *f, *a, *i, *p, *g;
179
180         r = json_object_new_object();
181         json_object_object_add(r, "openapi", json_object_new_string("3.0.0"));
182
183         i = json_object_new_object();
184         json_object_object_add(r, "info", i);
185         json_object_object_add(i, "title", json_object_new_string(afb_export_apiname(dynapi->export)));
186         json_object_object_add(i, "version", json_object_new_string("0.0.0"));
187         json_object_object_add(i, "description", json_object_new_string(dynapi->info));
188
189         p = json_object_new_object();
190         json_object_object_add(r, "paths", p);
191         iter = dynapi->verbs;
192         end = iter + dynapi->count;
193         while (iter != end) {
194                 verb = *iter++;
195                 buffer[0] = '/';
196                 strncpy(buffer + 1, verb->verb, sizeof buffer - 1);
197                 buffer[sizeof buffer - 1] = 0;
198                 f = json_object_new_object();
199                 json_object_object_add(p, buffer, f);
200                 g = json_object_new_object();
201                 json_object_object_add(f, "get", g);
202
203                 a = afb_auth_json_v2(verb->auth, verb->session);
204                 if (a)
205                         json_object_object_add(g, "x-permissions", a);
206
207                 a = json_object_new_object();
208                 json_object_object_add(g, "responses", a);
209                 f = json_object_new_object();
210                 json_object_object_add(a, "200", f);
211                 json_object_object_add(f, "description", json_object_new_string(verb->info?:verb->verb));
212         }
213         return r;
214 }
215
216 static struct json_object *describe_cb(void *closure)
217 {
218         struct afb_api_dyn *dynapi = closure;
219         struct json_object *r = make_description_openAPIv3(dynapi);
220         return r;
221 }
222
223 static struct afb_api_itf dyn_api_itf = {
224         .call = call_cb,
225         .service_start = service_start_cb,
226         .update_hooks = update_hooks_cb,
227         .get_verbosity = get_verbosity_cb,
228         .set_verbosity = set_verbosity_cb,
229         .describe = describe_cb
230 };
231
232 int afb_api_dyn_add(struct afb_apiset *apiset, const char *name, const char *info, int (*preinit)(void*, struct afb_dynapi*), void *closure)
233 {
234         int rc;
235         struct afb_api_dyn *dynapi;
236         struct afb_api afb_api;
237         struct afb_export *export;
238
239         INFO("Starting creation of dynamic API %s", name);
240
241         /* allocates the description */
242         info = info ?: "";
243         dynapi = calloc(1, sizeof *dynapi + strlen(info));
244         export = afb_export_create_vdyn(apiset, name, dynapi);
245         if (!dynapi || !export) {
246                 ERROR("out of memory");
247                 goto error;
248         }
249         strcpy(dynapi->info, info);
250         dynapi->export = export;
251
252         /* preinit the api */
253         rc = afb_export_preinit_vdyn(export, preinit, closure);
254         if (rc < 0) {
255                 ERROR("dynamic api %s preinit function failed, ABORTING it!",
256                                 afb_export_apiname(dynapi->export));
257                 goto error;
258         }
259
260         /* records the binding */
261         afb_api.closure = dynapi;
262         afb_api.itf = &dyn_api_itf;
263         afb_api.group = NULL;
264         if (afb_apiset_add(apiset, afb_export_apiname(dynapi->export), afb_api) < 0) {
265                 ERROR("dynamic api %s can't be registered to set %s, ABORTING it!",
266                                 afb_export_apiname(dynapi->export),
267                                 afb_apiset_name(apiset));
268                 goto error;
269         }
270         INFO("binding %s added to set %s", afb_export_apiname(dynapi->export), afb_apiset_name(apiset));
271         return 1;
272
273 error:
274         afb_export_destroy(export);
275         free(dynapi);
276
277         return -1;
278 }
279