Browse Source

openapi: rework the allowedValues to allow for imported variables

Previously I was assuming all variables were declared in the same file.
Turns out that imports exists too, and we need to recurse one more time
in those imports./

Also "clean" up a bit the function to do the parsing in 2 steps:
- first find out the actual matching node in js
- then convert it based on its type
rinse wash repeat.

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@gmail.com>
Benjamin Tissoires 4 years ago
parent
commit
39f8a138d7
2 changed files with 99 additions and 34 deletions
  1. 76 33
      openapi/generate_openapi.py
  2. 23 1
      public/api/wekan.yml

+ 76 - 33
openapi/generate_openapi.py

@@ -466,6 +466,7 @@ class SchemaProperty(object):
         self.type = 'object'
         self.blackbox = False
         self.required = True
+        imports = {}
         for p in statement.value.properties:
             try:
                 if p.key.name == 'type':
@@ -477,41 +478,79 @@ class SchemaProperty(object):
 
                 elif p.key.name == 'allowedValues':
                     self.type = 'enum'
-                    if p.value.type == 'ArrayExpression':
-                        self.enum = [e.value.lower() for e in p.value.elements]
-                    elif p.value.type == 'Identifier':
-                        # tree wide lookout for the identifier
-                        def find_variable(elem, match):
-                            if isinstance(elem, list):
-                                for value in elem:
-                                    ret = find_variable(value, match)
+                    self.enum = []
+
+                    def parse_enum(value, enum):
+                        if value.type == 'ArrayExpression':
+                            for e in value.elements:
+                                parse_enum(e, enum)
+                        elif value.type == 'Literal':
+                            enum.append(value.value.lower())
+                            return
+                        elif value.type == 'Identifier':
+                            # tree wide lookout for the identifier
+                            def find_variable(elem, match):
+                                if isinstance(elem, list):
+                                    for value in elem:
+                                        ret = find_variable(value, match)
+                                        if ret is not None:
+                                            return ret
+
+                                try:
+                                    items = elem.items()
+                                except AttributeError:
+                                    return None
+                                except TypeError:
+                                    return None
+
+                                if (elem.type == 'VariableDeclarator' and
+                                   elem.id.name == match):
+                                    return elem
+                                elif (elem.type == 'ImportSpecifier' and
+                                     elem.local.name == match):
+                                    # we have to treat that case in the caller, because we lack
+                                    # information of the source of the import at that point
+                                    return elem
+                                elif (elem.type == 'ExportNamedDeclaration' and
+                                     elem.declaration.type == 'VariableDeclaration'):
+                                    ret = find_variable(elem.declaration.declarations, match)
                                     if ret is not None:
                                         return ret
 
-                            try:
-                                items = elem.items()
-                            except AttributeError:
-                                return None
-                            except TypeError:
-                                return None
+                                for type, value in items:
+                                    ret = find_variable(value, match)
+                                    if ret is not None:
+                                        if ret.type == 'ImportSpecifier':
+                                            # first open and read the import source, if
+                                            # we haven't already done so
+                                            path = elem.source.value
+                                            if elem.source.value.startswith('/'):
+                                                script_dir = os.path.dirname(os.path.realpath(__file__))
+                                                path = os.path.abspath(os.path.join('{}/..'.format(script_dir), elem.source.value.lstrip('/')))
+                                            else:
+                                                path = os.path.abspath(os.path.join(os.path.dirname(context.path), elem.source.value))
+                                            path += '.js'
+
+                                            if path not in imports:
+                                                imported_context = parse_file(path)
+                                                imports[path] = imported_context
+                                            imported_context = imports[path]
+
+                                            # and then re-run the find in the imported file
+                                            return find_variable(imported_context.program.body, match)
 
-                            if (elem.type == 'VariableDeclarator' and
-                               elem.id.name == match):
-                                return elem
+                                        return ret
 
-                            for type, value in items:
-                                ret = find_variable(value, match)
-                                if ret is not None:
-                                    return ret
+                                return None
 
-                            return None
+                            elem = find_variable(context.program.body, value.name)
 
-                        elem = find_variable(context.program.body, p.value.name)
+                            if elem is None:
+                                raise TypeError('can not find "{}"'.format(value.name))
 
-                        if elem.init.type != 'ArrayExpression':
-                            raise TypeError('can not find "{}"'.format(p.value.name))
+                            return parse_enum(elem.init, enum)
 
-                        self.enum = [e.value.lower() for e in elem.init.elements]
+                    parse_enum(p.value, self.enum)
 
                 elif p.key.name == 'blackbox':
                     self.blackbox = True
@@ -757,6 +796,15 @@ class Context(object):
         return ''.join(self._txt[begin - 1:end])
 
 
+def parse_file(path):
+    try:
+        # if the file failed, it's likely it doesn't contain a schema
+        context = Context(path)
+    except:
+        return
+
+    return context
+
 def parse_schemas(schemas_dir):
 
     schemas = {}
@@ -766,12 +814,7 @@ def parse_schemas(schemas_dir):
         files.sort()
         for filename in files:
             path = os.path.join(root, filename)
-            try:
-                # if the file failed, it's likely it doesn't contain a schema
-                context = Context(path)
-            except:
-                continue
-
+            context = parse_file(path)
             program = context.program
 
             current_schema = None
@@ -1012,7 +1055,7 @@ def main():
     script_dir = os.path.dirname(os.path.realpath(__file__))
     parser.add_argument('--release', default='git-master', nargs=1,
                         help='the current version of the API, can be retrieved by running `git describe --tags --abbrev=0`')
-    parser.add_argument('dir', default='{}/../models'.format(script_dir), nargs='?',
+    parser.add_argument('dir', default=os.path.abspath('{}/../models'.format(script_dir)), nargs='?',
                         help='the directory where to look for schemas')
 
     args = parser.parse_args()

+ 23 - 1
public/api/wekan.yml

@@ -1,7 +1,7 @@
 swagger: '2.0'
 info:
   title: Wekan REST API
-  version: v5.17
+  version: v5.19
   description: |
     The REST API allows you to control and extend Wekan with ease.
 
@@ -2757,6 +2757,10 @@ definitions:
         description: |
            Does the board allows labels?
         type: boolean
+      allowsCreator:
+        description: |
+           Does the board allow creator?
+        type: boolean
       allowsAssignee:
         description: |
            Does the board allows assignee?
@@ -2834,7 +2838,12 @@ definitions:
       type:
         description: |
            The type of board
+           possible values: board, template-board, template-container
         type: string
+        enum:
+          - board
+          - template-board
+          - template-container
       sort:
         description: |
            Sort value
@@ -2857,6 +2866,7 @@ definitions:
       - allowsDescriptionText
       - allowsActivities
       - allowsLabels
+      - allowsCreator
       - allowsAssignee
       - allowsMembers
       - allowsRequestedBy
@@ -2889,6 +2899,7 @@ definitions:
            `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`
         type: string
         enum:
+          - white
           - green
           - yellow
           - orange
@@ -3154,6 +3165,10 @@ definitions:
         description: |
            type of the card
         type: string
+        enum:
+          - cardtype-card
+          - cardtype-linkedcard
+          - cardtype-linkedboard
       linkedId:
         description: |
            ID of the linked card
@@ -3298,6 +3313,7 @@ definitions:
           - dropdown
           - checkbox
           - currency
+          - stringtemplate
       settings:
         description: |
            settings of the custom field
@@ -3344,6 +3360,10 @@ definitions:
         type: array
         items:
           $ref: "#/definitions/CustomFieldsSettingsDropdownitems"
+      stringtemplateFormat:
+        type: string
+      stringtemplateSeparator:
+        type: string
   CustomFieldsSettingsDropdownitems:
     type: object
   Integrations:
@@ -3467,6 +3487,7 @@ definitions:
           - lime
           - pink
           - black
+          - silver
           - peachpuff
           - crimson
           - plum
@@ -3558,6 +3579,7 @@ definitions:
           - lime
           - pink
           - black
+          - silver
           - peachpuff
           - crimson
           - plum