1b7974e291698b14292abf1d9b2433e13b6949db
[src/app-framework-binder.git] / src / genskel / genskel.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  * This simple program expands the object { "$ref": "#/path/to/a/target" }
19  *
20  * For example:
21  *
22  *  {
23  *    "type":{
24  *      "a": "int",
25  *      "b": { "$ref": "#/type/a" }
26  *    }
27  *  }
28  *
29  * will be exapanded to
30  *
31  *  {
32  *    "type":{
33  *      "a": "int",
34  *      "b": "int"
35  *    }
36  *  }
37  * 
38  * Invocation:   program  [file|-]...
39  *
40  * without arguments, it reads the input.
41  */
42
43 #define _GNU_SOURCE
44 #include <string.h>
45 #include <stdio.h>
46 #include <unistd.h>
47
48 #include <json-c/json.h>
49
50 #define oom(x) do{if(!(x)){fprintf(stderr,"out of memory\n");exit(1);}}while(0)
51
52 /**
53  * records path to the expanded node
54  */
55 struct path
56 {
57         struct json_object *object;     /**< node being expanded */
58         struct path *upper;             /**< link to upper expanded nodes */
59 };
60
61 /**
62  * root of the JSON being parsed
63  */
64 struct json_object *root;
65 const char *init = NULL;
66 const char *start = NULL;
67 const char *onevent = NULL;
68 const char *api = NULL;
69 const char *scope = NULL;
70 const char *prefix = NULL;
71 const char *postfix = NULL;
72 int priv = -1;
73
74 /**
75  * Search for a reference of type "#/a/b/c" int the
76  * parsed JSON object
77  */
78 struct json_object *search(const char *path)
79 {
80         char *d;
81         struct json_object *i;
82
83         /* does it match #/ at the beginning? */
84         if (path[0] != '#' || (path[0] && path[1] != '/'))
85                 return NULL;
86
87         /* search from root to target */
88         i = root;
89         d = strdupa(path+2);
90         d = strtok(d, "/");
91         while(i && d) {
92                 if (!json_object_object_get_ex(i, d, &i))
93                         return NULL; 
94                 d = strtok(NULL, "/");
95         }
96         return i;
97 }
98
99 /**
100  * Expands the node designated by path and returns its expanded form
101  */
102 struct json_object *expand_$ref(struct path path)
103 {
104         struct path *p;
105         struct json_object *o, *x;
106         int n, i;
107         struct json_object_iterator ji, jn; 
108
109         /* expansion depends of the type of the node */
110         switch (json_object_get_type(path.object)) {
111         case json_type_object:
112                 /* for object, look if it contains a property "$ref" */
113                 if (json_object_object_get_ex(path.object, "$ref", &o)) {
114                         /* yes, reference, try to substitute its target */
115                         if (!json_object_is_type(o, json_type_string)) {
116                                 fprintf(stderr, "found a $ref not being string. Is: %s\n", json_object_get_string(o));
117                                 exit(1);
118                         }
119                         x = search(json_object_get_string(o));
120                         if (!x) {
121                                 fprintf(stderr, "$ref not found. Was: %s\n", json_object_get_string(o));
122                                 exit(1);
123                         }
124                         p = &path;
125                         while(p) {
126                                 if (x == p->object) {
127                                         fprintf(stderr, "$ref recursive. Was: %s\n", json_object_get_string(o));
128                                         exit(1);
129                                 }
130                                 p = p->upper;
131                         }
132                         /* cool found, return a new instance of the target */
133                         return json_object_get(x);
134                 }
135                 /* no, expand the values */
136                 ji = json_object_iter_begin(path.object);
137                 jn = json_object_iter_end(path.object);
138                 while (!json_object_iter_equal(&ji, &jn)) {
139                         o = json_object_iter_peek_value(&ji);
140                         x = expand_$ref((struct path){ .object = o, .upper = &path });
141                         if (x != o)
142                                 json_object_object_add(path.object, json_object_iter_peek_name(&ji), x);
143                         json_object_iter_next(&ji);
144                 }
145                 break;
146         case json_type_array:
147                 /* expand the values of arrays */
148                 i = 0;
149                 n = json_object_array_length(path.object);
150                 while (i != n) {
151                         o = json_object_array_get_idx(path.object, i);
152                         x = expand_$ref((struct path){ .object = o, .upper = &path });
153                         if (x != o)
154                                 json_object_array_put_idx(path.object, i, x);
155                         i++;
156                 }
157                 break;
158         default:
159                 /* otherwise no expansion */
160                 break;
161         }
162
163         /* return the given node */
164         return path.object;
165 }
166
167 char *get_perm_string(struct json_object *o, int *pl)
168 {
169         struct json_object *x, *y;
170         char *a, *b, *c;
171         int l, n, i, L;
172
173         /* value for permission */
174         if (json_object_object_get_ex(o, "permission", &x)) {
175                 if (!json_object_is_type(x, json_type_string)) {
176                         fprintf(stderr, "permission must be a string. Was: %s\n", json_object_get_string(o));
177                         exit(1);
178                 }
179                 a = strdup(json_object_get_string(x));
180                 oom(a);
181                 *pl = 0;
182                 return a;
183         }
184
185         /* value for not */
186         if (json_object_object_get_ex(o, "not", &x)) {
187                 a = get_perm_string(x, &l);
188                 b = malloc(6 + strlen(a));
189                 oom(b);
190                 if (l)
191                         stpcpy(stpcpy(stpcpy(b,"not("),a),")");
192                 else
193                         stpcpy(stpcpy(b,"not "),a);
194                 free(a);
195                 *pl = 0;
196                 return b;
197         }
198
199         /* value for and or or */
200         if (json_object_object_get_ex(o, "allOf", &x))
201                 L = 1;
202         else if (json_object_object_get_ex(o, "anyOf", &x))
203                 L = 2;
204         else {
205                 fprintf(stderr, "unrecognized permission. Was: %s\n", json_object_get_string(o));
206                 exit(1);
207         }
208
209         /* check the array */
210         if (!json_object_is_type(x, json_type_array)) {
211                 fprintf(stderr, "sequence must be an array. Was: %s\n", json_object_get_string(o));
212                 exit(1);
213         }
214         n = json_object_array_length(x);
215         if (n == 0) {
216                 fprintf(stderr, "invalid empty sequence. Was: %s\n", json_object_get_string(o));
217                 exit(1);
218         }
219
220         /* process the array */
221         if (n == 1)
222                 return get_perm_string(json_object_array_get_idx(x, 0), pl);
223         b = NULL;
224         i = 0;
225         while (i != n) {
226                 y = json_object_array_get_idx(x, i);
227                 a = get_perm_string(y, &l);
228                 if (l > L) {
229                         c = malloc(3 + strlen(a));
230                         oom(c);
231                         stpcpy(stpcpy(stpcpy(c,"("),a),")");
232                         free(a);
233                         a = c;
234                 }
235                 if (!b)
236                         b = a;
237                 else {
238                         c = malloc(6 + strlen(a) + strlen(b));
239                         oom(c);
240                         stpcpy(stpcpy(stpcpy(c,b),L==2?" or ":" and "),a);
241                         free(a);
242                         free(b);
243                         b = c;
244                 }
245                 i++;
246         }
247         *pl = L;
248         return b;
249 }
250
251 struct json_object *make_perm(struct json_object *o)
252 {
253         int l;
254         char *permstr = get_perm_string(o, &l);
255         return json_object_new_string(permstr);
256 }
257
258 void make_permissions(struct path path)
259 {
260         struct json_object *o, *x, *y;
261         struct json_object_iterator ji, jn; 
262
263         if (json_object_object_get_ex(path.object, "permissions", &o)) {
264
265                 /* expand $refs of permissions */
266                 x = expand_$ref((struct path){ .object = o, .upper = &path });
267                 if (x != o)
268                         json_object_object_add(path.object, "permissions", x);
269
270                 /* makes the permissions */
271                 ji = json_object_iter_begin(o);
272                 jn = json_object_iter_end(o);
273                 while (!json_object_iter_equal(&ji, &jn)) {
274                         x = json_object_iter_peek_value(&ji);
275                         y = make_perm(x);
276                         if (x != y)
277                                 json_object_object_add(o, json_object_iter_peek_name(&ji), y);
278                         json_object_iter_next(&ji);
279                 }
280         }
281 }
282
283 char *make_desc(struct json_object *o)
284 {
285         const char *a, *b;
286         char *desc, c, buf[3];
287         size_t len;
288         int i, pos, e;
289
290         a = b = json_object_to_json_string_ext(root, 0);
291         len = 1;
292         while((c = *b++)) {
293                 len += 1 + ('"' == c);
294         }
295
296         len += 7 * (1 + len / 72);
297         desc = malloc(len);
298         oom(desc);
299
300         len = pos = 0;
301         b = a;
302         while((c = *b++)) {
303                 if (c == '"') {
304                         buf[0] = '\\';
305                         buf[1] = '"';
306                         buf[2] = 0;
307                 }
308                 else if (c == '\\') {
309                         switch ((c = *b++)) {
310                         case 0:
311                                 b--;
312                                 break;
313                         case '/':
314                                 buf[0] = '/';
315                                 buf[1] = 0;
316                                 break;
317                         default:
318                                 buf[0] = '\\';
319                                 buf[1] = c;
320                                 buf[2] = 0;
321                                 break;
322                         }
323                 }
324                 else {
325                         buf[0] = c;
326                         buf[1] = 0;
327                 }
328                 i = e = 0;
329                 while (buf[i]) {
330                         if (pos >= 77 && !e) {
331                                 desc[len++] = '"';
332                                 desc[len++] = '\n';
333                                 pos = 0;
334                         }
335                         if (pos == 0) {
336                                 desc[len++] = ' ';
337                                 desc[len++] = ' ';
338                                 desc[len++] = ' ';
339                                 desc[len++] = ' ';
340                                 desc[len++] = '"';
341                                 pos = 5;
342                         }
343                         c = buf[i++];
344                         desc[len++] = c;
345                         e = !e && c == '\\';
346                         pos++;
347                 }
348         }
349         desc[len++] = '"';
350         desc[len++] = '\n';
351         desc[len] = 0;
352         return desc;
353 }
354
355 void print_verbs(int real)
356 {
357         struct json_object_iterator ji, jn;
358         struct json_object *o, *v, *x, *y;
359         const char *verb, *perm, *loa;
360         int i, n, l;
361         const char **verbs;
362
363         /* search the verbs */
364         o = search("#/verbs");
365         if (!o)
366                 return;
367
368         /* list the verbs and sort it */
369         n = json_object_object_length(o);
370         verbs = malloc(n * sizeof *verbs);
371         oom(verbs);
372         n = 0;
373         ji = json_object_iter_begin(o);
374         jn = json_object_iter_end(o);
375         while (!json_object_iter_equal(&ji, &jn)) {
376                 verb = json_object_iter_peek_name(&ji);
377                 i = 0;
378                 while (i < n && strcasecmp(verb, verbs[i]) > 0)
379                         i++;
380                 if (i < n)
381                         memmove(verbs + i + 1, verbs + i, (n - i) * sizeof *verbs);
382                 verbs[i] = verb;
383                 n++;
384                 json_object_iter_next(&ji);
385         }
386
387         /* emit the verbs */
388         for (i = 0 ; i < n ; i++) {
389                 verb = verbs[i];
390                 json_object_object_get_ex(o, verb, &v);
391
392                 if (real) {
393                         if (!json_object_object_get_ex(v, "permissions", &x))
394                                 perm = "NULL";
395                         else {
396                                 perm = json_object_to_json_string(x);
397                         }
398                         l = 0;
399                         loa = "";
400                         if (json_object_object_get_ex(v, "LOA", &x)) {
401                                 if (json_object_is_type(x, json_type_int)) {
402                                         loa = "AFB_SESSION_LOA_EQ_";
403                                         y = x;
404                                 } else {
405                                         if (json_object_object_get_ex(x, "minimum", &y))
406                                                 loa = "AFB_SESSION_LOA_GE_";
407                                         else if (json_object_object_get_ex(x, "maximum", &y))
408                                                 loa = "AFB_SESSION_LOA_LE_";
409                                         else
410                                                 y = NULL;
411                                         if (y && !json_object_is_type(y, json_type_int))
412                                                 y = NULL;
413                                 }
414                                 l = json_object_get_int(y);
415                                 if (y == NULL || l < 0 || l > 3) {
416                                         fprintf(stderr, "invalid LOA spec. Was: %s",  json_object_get_string(x));
417                                         exit(1);
418                                 }
419                         }
420
421                         printf(
422                                 "    {\n"
423                                 "        .verb = \"%s\",\n"
424                                 "        .callback = %s%s%s,\n"
425                                 "        .permissions = %s,\n"
426                                 "        .session = %s%d,\n"
427                                 "    },\n"
428                                 , verb, prefix, verb, postfix, perm, loa, l
429                         );
430                 } else {
431                         printf(
432                                 "%s void %s%s%s(struct afb_req req);\n"
433                                 , scope, prefix, verb, postfix
434                         );
435                 }
436         }
437
438         free(verbs);
439 }
440
441 void getvarbool(int *var, const char *path, int defval)
442 {
443         struct json_object *o;
444
445         if (*var != 0 && *var != 1) {
446                 o = search(path);
447                 if (o && json_object_is_type(o, json_type_boolean))
448                         *var = json_object_get_boolean(o);
449                 else
450                         *var = !!defval;
451         }
452 }
453
454 void getvar(const char **var, const char *path, const char *defval)
455 {
456         struct json_object *o;
457
458         if (!*var) {
459                 o = search(path);
460                 if (o && json_object_is_type(o, json_type_string))
461                         *var = json_object_get_string(o);
462                 else
463                         *var = defval;
464         }
465 }
466
467 /**
468  * process a file and prints its expansion on stdout
469  */
470 void process(char *filename)
471 {
472         char *desc;
473
474         /* translate - */
475         if (!strcmp(filename, "-"))
476                 filename = "/dev/stdin";
477
478         /* check access */
479         if (access(filename, R_OK)) {
480                 fprintf(stderr, "can't access file %s\n", filename);
481                 exit(1);
482         }
483
484         /* read the file */
485         root = json_object_from_file(filename);
486         if (!root) {
487                 fprintf(stderr, "reading file %s produced null\n", filename);
488                 exit(1);
489         }
490
491         /* create the description */
492         desc = make_desc(root);
493
494         /* generate permissions strings */
495         make_permissions((struct path){ .object = root, .upper = NULL });
496
497         /* expand references */
498         root = expand_$ref((struct path){ .object = root, .upper = NULL });
499
500         /* get some names */
501         getvar(&api, "#/api", "?");
502         getvar(&init, "#/meta-binding/init", NULL);
503         getvar(&start, "#/meta-binding/start", NULL);
504         getvar(&onevent, "#/meta-binding/onevent", NULL);
505         getvar(&scope, "#/meta-binding/scope", "static");
506         getvar(&prefix, "#/meta-binding/prefix", "afb_verb_");
507         getvar(&postfix, "#/meta-binding/postfix", "_cb");
508         getvarbool(&priv, "#/meta-binding/private", 0);
509
510         /* get the API name */
511         printf(
512                 "\n"
513                 "static const char _afb_description_v2_[] =\n"
514                 "%s"
515                 ";\n"
516                 "\n"
517                 , desc
518         );
519         print_verbs(0);
520         printf(
521                 "\n"
522                 "static const struct afb_verb_v2 _afb_verbs_v2_[] = {\n"
523         );
524         print_verbs(1);
525         printf(
526                 "    { .verb = NULL }\n"
527                 "};\n"
528                 "\n"
529                 "%sconst struct afb_binding_v2 %s = {\n"
530                 "    .api = \"%s\",\n"
531                 "    .specification = _afb_description_v2_,\n"
532                 "    .verbs = _afb_verbs_v2_,\n"
533                 "    .init = %s,\n"
534                 "    .start = %s,\n"
535                 "    .onevent = %s,\n"
536                 "};\n"
537                 "\n"
538                 , priv?"static ":"", priv?"_afb_binding_v2_":"afbBindingV2"
539                 , api?:"?", init?:"NULL", start?:"NULL", onevent?:"NULL"
540         );
541
542         /* clean up */
543         json_object_put(root);
544         free(desc);
545 }
546
547 /** process the list of files or stdin if none */
548 int main(int ac, char **av)
549 {
550         if (!*++av)
551                 process("-");
552         else {
553                 do { process(*av); } while(*++av);
554         }       
555         return 0;
556 }
557