genskel: improvements and bug fixs
[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 #include <ctype.h>
48
49 #include <json-c/json.h>
50
51 #define oom(x) do{if(!(x)){fprintf(stderr,"out of memory\n");exit(1);}}while(0)
52
53 /**
54  * records path to the expanded node
55  */
56 struct path
57 {
58         struct json_object *object;     /**< node being expanded */
59         struct path *upper;             /**< link to upper expanded nodes */
60 };
61
62 /**
63  * root of the JSON being parsed
64  */
65 struct json_object *root = NULL;
66 struct json_object *d_perms = NULL;
67 struct json_object *a_perms = NULL;
68 const char *preinit = NULL;
69 const char *init = NULL;
70 const char *onevent = NULL;
71 const char *api = NULL;
72 const char *scope = NULL;
73 const char *prefix = NULL;
74 const char *postfix = NULL;
75 int priv = -1;
76 int noconc = -1;
77
78 /**
79  * Search for a reference of type "#/a/b/c" int the
80  * parsed JSON object
81  */
82 struct json_object *search(const char *path)
83 {
84         char *d;
85         struct json_object *i;
86
87         /* does it match #/ at the beginning? */
88         if (path[0] != '#' || (path[0] && path[1] != '/'))
89                 return NULL;
90
91         /* search from root to target */
92         i = root;
93         d = strdupa(path+2);
94         d = strtok(d, "/");
95         while(i && d) {
96                 if (!json_object_object_get_ex(i, d, &i))
97                         return NULL; 
98                 d = strtok(NULL, "/");
99         }
100         return i;
101 }
102
103 /**
104  * Expands the node designated by path and returns its expanded form
105  */
106 struct json_object *expand_$ref(struct path path)
107 {
108         struct path *p;
109         struct json_object *o, *x;
110         int n, i;
111         struct json_object_iterator ji, jn; 
112
113         /* expansion depends of the type of the node */
114         switch (json_object_get_type(path.object)) {
115         case json_type_object:
116                 /* for object, look if it contains a property "$ref" */
117                 if (json_object_object_get_ex(path.object, "$ref", &o)) {
118                         /* yes, reference, try to substitute its target */
119                         if (!json_object_is_type(o, json_type_string)) {
120                                 fprintf(stderr, "found a $ref not being string. Is: %s\n", json_object_get_string(o));
121                                 exit(1);
122                         }
123                         x = search(json_object_get_string(o));
124                         if (!x) {
125                                 fprintf(stderr, "$ref not found. Was: %s\n", json_object_get_string(o));
126                                 exit(1);
127                         }
128                         p = &path;
129                         while(p) {
130                                 if (x == p->object) {
131                                         fprintf(stderr, "$ref recursive. Was: %s\n", json_object_get_string(o));
132                                         exit(1);
133                                 }
134                                 p = p->upper;
135                         }
136                         /* cool found, return a new instance of the target */
137                         return json_object_get(x);
138                 }
139                 /* no, expand the values */
140                 ji = json_object_iter_begin(path.object);
141                 jn = json_object_iter_end(path.object);
142                 while (!json_object_iter_equal(&ji, &jn)) {
143                         o = json_object_iter_peek_value(&ji);
144                         x = expand_$ref((struct path){ .object = o, .upper = &path });
145                         if (x != o)
146                                 json_object_object_add(path.object, json_object_iter_peek_name(&ji), x);
147                         json_object_iter_next(&ji);
148                 }
149                 break;
150         case json_type_array:
151                 /* expand the values of arrays */
152                 i = 0;
153                 n = json_object_array_length(path.object);
154                 while (i != n) {
155                         o = json_object_array_get_idx(path.object, i);
156                         x = expand_$ref((struct path){ .object = o, .upper = &path });
157                         if (x != o)
158                                 json_object_array_put_idx(path.object, i, x);
159                         i++;
160                 }
161                 break;
162         default:
163                 /* otherwise no expansion */
164                 break;
165         }
166
167         /* return the given node */
168         return path.object;
169 }
170
171 char *cify(const char *str)
172 {
173         char *r = strdup(str);
174         int i = 0;
175         while (r && r[i]) {
176                 if (!isalnum(r[i]))
177                         r[i] = '_';
178                 i++;
179         }
180         return r;
181 }
182
183 char *make_info(const char *text, int split)
184 {
185         const char *a, *b;
186         char *desc, c, buf[3];
187         size_t len;
188         int i, pos, e;
189
190         /* estimated length */
191         a = b = text;
192         len = 1;
193         while((c = *b++)) {
194                 len += 1 + ('"' == c);
195         }
196
197         len += 7 * (1 + len / 72);
198         desc = malloc(len);
199         oom(desc);
200
201         len = pos = 0;
202         if (!split)
203                 desc[len++] = '"';
204         b = a;
205         while((c = *b++)) {
206                 if (c == '"') {
207                         buf[0] = '\\';
208                         buf[1] = '"';
209                         buf[2] = 0;
210                 }
211                 else if (c == '\\') {
212                         switch ((c = *b++)) {
213                         case 0:
214                                 b--;
215                                 break;
216                         case '/':
217                                 buf[0] = '/';
218                                 buf[1] = 0;
219                                 break;
220                         default:
221                                 buf[0] = '\\';
222                                 buf[1] = c;
223                                 buf[2] = 0;
224                                 break;
225                         }
226                 }
227                 else {
228                         buf[0] = c;
229                         buf[1] = 0;
230                 }
231                 i = e = 0;
232                 while (buf[i]) {
233                         if (split) {
234                                 if (pos >= 77 && !e) {
235                                         desc[len++] = '"';
236                                         desc[len++] = '\n';
237                                         pos = 0;
238                                 }
239                                 if (pos == 0) {
240                                         desc[len++] = ' ';
241                                         desc[len++] = ' ';
242                                         desc[len++] = ' ';
243                                         desc[len++] = ' ';
244                                         desc[len++] = '"';
245                                         pos = 5;
246                                 }
247                         }
248                         c = buf[i++];
249                         desc[len++] = c;
250                         e = !e && c == '\\';
251                         pos++;
252                 }
253         }
254         desc[len++] = '"';
255         if (split)
256                 desc[len++] = '\n';
257         desc[len] = 0;
258         return desc;
259 }
260
261 char *make_desc(struct json_object *o)
262 {
263         return make_info(json_object_to_json_string_ext(root, 0), 1);
264 }
265
266 struct json_object *permissions_of_verb(struct json_object *obj)
267 {
268         struct json_object *x, *y;
269
270         if (json_object_object_get_ex(obj, "x-permissions", &x))
271                 return x;
272
273         if (json_object_object_get_ex(obj, "get", &x))
274                 if (json_object_object_get_ex(x, "x-permissions", &y))
275                         return y;
276
277         return NULL;
278 }
279
280 void print_perms()
281 {
282         int i, n;
283
284         n = a_perms ? json_object_array_length(a_perms) : 0;
285         if (n) {
286                 printf("static const struct afb_auth _afb_auths_v2_%s[] = {\n" , api);
287                 i = 0;
288                 while (i < n) {
289                         printf("\t{ %s }", json_object_get_string(json_object_array_get_idx(a_perms, i)));
290                         printf(",\n"+(++i == n));
291                 }
292                 printf("};\n\n");
293         }
294 }
295
296 struct json_object *new_perm(struct json_object *obj, const char *desc)
297 {
298         const char *tag;
299         char *b;
300         struct json_object *x, *y;
301
302         tag = obj ? json_object_to_json_string_ext(obj, 0) : desc;
303         if (!json_object_object_get_ex(d_perms, tag, &y)) {
304                 if (!d_perms) {
305                         d_perms = json_object_new_object();
306                         a_perms = json_object_new_array();
307                 }       
308
309                 asprintf(&b, "&_afb_auths_v2_%s[%d]", api, json_object_array_length(a_perms));
310                 x = json_object_new_string(desc);
311                 y = json_object_new_string(b);
312                 json_object_array_add(a_perms, x);
313                 json_object_object_add(d_perms, tag, y);
314                 free(b);
315         }
316         return y;
317 }
318
319 struct json_object *decl_perm(struct json_object *obj);
320
321 struct json_object *decl_perm_a(const char *op, struct json_object *obj)
322 {
323         int i, n;
324         char *a;
325         struct json_object *x, *y;
326
327         x = NULL;
328         i = n = obj ? json_object_array_length(obj) : 0;
329         while (i) {
330                 y = decl_perm(json_object_array_get_idx(obj, --i));
331                 if (!y)
332                         ;
333                 else if (!x)
334                         x = y;
335                 else if (x != y) {
336                         asprintf(&a, ".type = afb_auth_%s, .first = %s, .next = %s",
337                                  op, json_object_get_string(y), json_object_get_string(x));
338                         x = new_perm(NULL, a);
339                         free(a);
340                 }
341         }
342         return x;
343 }
344
345 struct json_object *decl_perm(struct json_object *obj)
346 {
347         char *a;
348         struct json_object *x, *y;
349
350         if (json_object_object_get_ex(d_perms, json_object_to_json_string_ext(obj, 0), &x))
351                 return x;
352
353         if (json_object_object_get_ex(obj, "permission", &x)) {
354                 asprintf(&a, ".type = afb_auth_Permission, .text = \"%s\"", json_object_get_string(x));
355                 y = new_perm(obj, a);
356                 free(a);
357         }
358         else if (json_object_object_get_ex(obj, "anyOf", &x)) {
359                 y = decl_perm_a("Or", x);
360         }
361         else if (json_object_object_get_ex(obj, "allOf", &x)) {
362                 y = decl_perm_a("And", x);
363         }
364         else if (json_object_object_get_ex(obj, "not", &x)) {
365                 x = decl_perm(x);
366                 asprintf(&a, ".type = afb_auth_Not, .first = %s", json_object_get_string(x));
367                 y = new_perm(obj, a);
368                 free(a);
369         }
370         else if (json_object_object_get_ex(obj, "LOA", &x))
371                 y = NULL;
372         else if (json_object_object_get_ex(obj, "session", &x))
373                 y = NULL;
374         else
375                 y = NULL;
376
377         return y;
378 }
379
380 void declare_permissions(const char *name, struct json_object *obj)
381 {
382         struct json_object *p;
383
384         p = permissions_of_verb(obj);
385         if (p)
386                 decl_perm(p);
387 }
388
389
390 #define SESSION_CLOSE  0x000001
391 #define SESSION_RENEW  0x000010
392 #define SESSION_CHECK  0x000100
393 #define SESSION_LOA_1  0x001000
394 #define SESSION_LOA_2  0x011000
395 #define SESSION_LOA_3  0x111000
396 #define SESSION_MASK   0x111111
397
398
399 int get_session(struct json_object *obj);
400
401 int get_session_a(int and, struct json_object *obj)
402 {
403         int i, n, x, y;
404
405         n = obj ? json_object_array_length(obj) : 0;
406         if (n == 0)
407                 return 0;
408
409         i = n;
410         x = get_session(json_object_array_get_idx(obj, --i));
411         while (i) {
412                 y = get_session(json_object_array_get_idx(obj, --i));
413                 if (and)
414                         x &= y;
415                 else
416                         x |= y;
417         }
418         return x;
419 }
420
421 int get_session(struct json_object *obj)
422 {
423         int y;
424         const char *a;
425         struct json_object *x;
426
427         y = 0;
428         if (json_object_object_get_ex(obj, "anyOf", &x)) {
429                 y = get_session_a(1, x);
430         }
431         else if (json_object_object_get_ex(obj, "allOf", &x)) {
432                 y = get_session_a(0, x);
433         }
434         else if (json_object_object_get_ex(obj, "not", &x)) {
435                 y = ~get_session(x) & SESSION_MASK;
436         }
437         else if (json_object_object_get_ex(obj, "LOA", &x)) {
438                 switch (json_object_get_int(x)) {
439                 case 3: y = SESSION_LOA_3; break;
440                 case 2: y = SESSION_LOA_2; break;
441                 case 1: y = SESSION_LOA_1; break;
442                 default: break;
443                 }
444         }
445         else if (json_object_object_get_ex(obj, "session", &x)) {
446                 a = json_object_get_string(x);
447                 if (!strcmp(a, "check"))
448                         y = SESSION_CHECK;
449                 else if (!strcmp(a, "close"))
450                         y = SESSION_CLOSE;
451         }
452         else if (json_object_object_get_ex(obj, "token", &x)) {
453                 a = json_object_get_string(x);
454                 if (!strcmp(a, "refresh"))
455                         y = SESSION_RENEW;
456         }
457
458         return y;
459 }
460
461 void print_session(struct json_object *p)
462 {
463         int s, c, l;
464
465         s = p ? get_session(p) : 0;
466         c = 1;
467         if (s & SESSION_CHECK) {
468                 printf("%s", "|AFB_SESSION_CHECK_V2" + c);
469                 c = 0;
470         }
471         if (s & SESSION_LOA_3 & ~SESSION_LOA_2)
472                 l = 3;
473         else if (s & SESSION_LOA_2 & ~SESSION_LOA_1)
474                 l = 2;
475         else if (s & SESSION_LOA_1)
476                 l = 1;
477         else
478                 l = 0;
479         if (l) {
480                 printf("%s%d_V2", "|AFB_SESSION_LOA_" + c, l);
481                 c = 0;
482         }
483         if (s & SESSION_CLOSE) {
484                 printf("%s", "|AFB_SESSION_CLOSE_V2" + c);
485                 c = 0;
486         }
487         if (s & SESSION_RENEW) {
488                 printf("%s", "|AFB_SESSION_REFRESH_V2" + c);
489                 c = 0;
490         }
491         if (c)
492                 printf("AFB_SESSION_NONE_V2");
493 }
494
495 void print_verb(const char *name)
496 {
497         printf("%s%s%s" , prefix, name, postfix);
498 }
499
500 void print_declare_verb(const char *name, struct json_object *obj)
501 {
502         printf("%s void ", scope);
503         print_verb(name);
504         printf("(struct afb_req req);\n");
505 }
506
507 void print_struct_verb(const char *name, struct json_object *obj)
508 {
509         struct json_object *p, *i;
510         const char *info;
511
512         info = NULL;
513         if (json_object_object_get_ex(obj, "description", &i))
514                 info = json_object_get_string(i);
515
516         p = permissions_of_verb(obj);
517         printf(
518                 "    {\n"
519                 "        .verb = \"%s\",\n"
520                 "        .callback = "
521                 , name
522         );
523         print_verb(name);
524         printf(
525                 ",\n"
526                 "        .auth = %s,\n"
527                 "        .info = %s,\n"
528                 "        .session = "
529                 , p && decl_perm(p) ? json_object_get_string(decl_perm(p)) : "NULL"
530                 , info ? make_info(info, 0) : "NULL"
531         );
532         print_session(p);
533         printf(
534                 "\n"
535                 "    },\n"
536         );
537 }
538
539 void enum_verbs(void (*func)(const char *name, struct json_object *obj))
540 {
541         struct json_object_iterator ji, jn;
542         struct json_object *paths, *obj;
543         const char *name;
544
545         /* search the verbs */
546         paths = search("#/paths");
547         if (!paths)
548                 return;
549
550         /* list the verbs and sort it */
551         ji = json_object_iter_begin(paths);
552         jn = json_object_iter_end(paths);
553         while (!json_object_iter_equal(&ji, &jn)) {
554                 name = json_object_iter_peek_name(&ji);
555                 obj = json_object_iter_peek_value(&ji);
556                 name += (*name == '/');
557                 func(name, obj);
558                 json_object_iter_next(&ji);
559         }
560 }
561
562 void getvarbool(int *var, const char *path, int defval)
563 {
564         struct json_object *o;
565
566         if (*var != 0 && *var != 1) {
567                 o = search(path);
568                 if (o && json_object_is_type(o, json_type_boolean))
569                         *var = json_object_get_boolean(o);
570                 else
571                         *var = !!defval;
572         }
573 }
574
575 void getvar(const char **var, const char *path, const char *defval)
576 {
577         struct json_object *o;
578
579         if (!*var) {
580                 o = search(path);
581                 if (o && json_object_is_type(o, json_type_string))
582                         *var = json_object_get_string(o);
583                 else
584                         *var = defval;
585         }
586 }
587
588 /**
589  * process a file and prints its expansion on stdout
590  */
591 void process(char *filename)
592 {
593         char *desc;
594         const char *info;
595         char *capi;
596
597         /* translate - */
598         if (!strcmp(filename, "-"))
599                 filename = "/dev/stdin";
600
601         /* check access */
602         if (access(filename, R_OK)) {
603                 fprintf(stderr, "can't access file %s\n", filename);
604                 exit(1);
605         }
606
607         /* read the file */
608         root = json_object_from_file(filename);
609         if (!root) {
610                 fprintf(stderr, "reading file %s produced null\n", filename);
611                 exit(1);
612         }
613
614         /* create the description */
615         desc = make_desc(root);
616
617         /* expand references */
618         root = expand_$ref((struct path){ .object = root, .upper = NULL });
619
620         /* get some names */
621         getvar(&api, "#/info/x-binding-c-generator/api", NULL);
622         getvar(&preinit, "#/info/x-binding-c-generator/preinit", NULL);
623         getvar(&init, "#/info/x-binding-c-generator/init", NULL);
624         getvar(&onevent, "#/info/x-binding-c-generator/onevent", NULL);
625         getvar(&scope, "#/info/x-binding-c-generator/scope", "static");
626         getvar(&prefix, "#/info/x-binding-c-generator/prefix", "afb_verb_");
627         getvar(&postfix, "#/info/x-binding-c-generator/postfix", "_cb");
628         getvarbool(&priv, "#/info/x-binding-c-generator/private", 0);
629         getvarbool(&noconc, "#/info/x-binding-c-generator/noconcurrency", 0);
630         getvar(&api, "#/info/title", "?");
631         info = NULL;
632         getvar(&info, "#/info/description", NULL);
633         capi = cify(api);
634
635         /* get the API name */
636         printf(
637                 "\n"
638                 "static const char _afb_description_v2_%s[] =\n"
639                 "%s"
640                 ";\n"
641                 "\n"
642                 , capi, desc
643         );
644         enum_verbs(declare_permissions);
645         print_perms();
646         enum_verbs(print_declare_verb);
647         printf(
648                 "\n"
649                 "static const struct afb_verb_v2 _afb_verbs_v2_%s[] = {\n"
650                 , capi
651         );
652         enum_verbs(print_struct_verb);
653         printf(
654                 "    { .verb = NULL }\n"
655                 "};\n"
656         );
657         printf(
658                 "\n"
659                 "%sconst struct afb_binding_v2 %s%s = {\n"
660                 "    .api = \"%s\",\n"
661                 "    .specification = _afb_description_v2_%s,\n"
662                 "    .info = %s,\n"
663                 "    .verbs = _afb_verbs_v2_%s,\n"
664                 "    .preinit = %s,\n"
665                 "    .init = %s,\n"
666                 "    .onevent = %s,\n"
667                 "    .noconcurrency = %d\n"
668                 "};\n"
669                 "\n"
670                 , priv ? "static " : ""
671                 , priv ? "_afb_binding_v2_" : "afbBindingV2"
672                 , priv ? capi : ""
673                 , api
674                 , capi
675                 , info ? make_info(info, 0) : "NULL"
676                 , capi
677                 , preinit ?: "NULL"
678                 , init ?: "NULL"
679                 , onevent ?: "NULL"
680                 , !!noconc
681         );
682
683         /* clean up */
684         json_object_put(root);
685         free(desc);
686 }
687
688 /** process the list of files or stdin if none */
689 int main(int ac, char **av)
690 {
691         if (!*++av)
692                 process("-");
693         else {
694                 do { process(*av); } while(*++av);
695         }       
696         return 0;
697 }
698
699
700
701
702