data extraction from openapi defintion

This commit is contained in:
Wolfgang Ludger Hottgenroth 2021-11-19 12:39:46 +01:00
parent e616a7cb60
commit 590e9da904
Signed by: wn
GPG Key ID: 6C1E5E531E0D5D7F
4 changed files with 176 additions and 32 deletions

View File

@ -3,12 +3,13 @@ from Cheetah.Template import Template
import glob import glob
import argparse import argparse
import os import os
import re
from yaml.loader import SafeLoader from yaml.loader import SafeLoader
from generateHelper import CsOperationNameConverter, OpenApiExtractRefType from generateHelper import CsOperationNameConverter, OpenApiExtractRefType
parser = argparse.ArgumentParser(description="generate.py") parser = argparse.ArgumentParser(description="generate.py")
parser.add_argument('--api', '-a', parser.add_argument('--apiDefinitionFile', '-a',
help='API definition file. Default: openapi.yaml in the current folder.', help='API definition file. Default: openapi.yaml in the current folder.',
required=False, required=False,
default='./openapi.yaml') default='./openapi.yaml')
@ -20,35 +21,35 @@ with an additional .tmpl extension. Default: all template files recursively from
args = parser.parse_args() args = parser.parse_args()
with open(args.api) as schemaFile: with open(args.apiDefinitionFile) as apiDefinitionFile:
schema = yaml.load(schemaFile, Loader=SafeLoader) apiDefinition = yaml.load(apiDefinitionFile, Loader=SafeLoader)
schema["GENERATED_SQL_COMMENT"] = """ apiDefinition["GENERATED_SQL_COMMENT"] = """
-- ---------------------------------------- -- ----------------------------------------
-- THIS FILE HAS BEEN GENERATED -- THIS FILE HAS BEEN GENERATED
-- DO NOT EDIT MANUALLY -- DO NOT EDIT MANUALLY
-- ---------------------------------------- -- ----------------------------------------
""" """
schema["GENERATED_PYTHON_COMMENT"] = """ apiDefinition["GENERATED_PYTHON_COMMENT"] = """
# ----------------------------------------- # -----------------------------------------
# THIS FILE HAS BEEN GENERATED # THIS FILE HAS BEEN GENERATED
# DO NOT EDIT MANUALLY # DO NOT EDIT MANUALLY
# ----------------------------------------- # -----------------------------------------
""" """
schema["GENERATED_YAML_COMMENT"] = """ apiDefinition["GENERATED_YAML_COMMENT"] = """
# ----------------------------------------- # -----------------------------------------
# THIS FILE HAS BEEN GENERATED # THIS FILE HAS BEEN GENERATED
# DO NOT EDIT MANUALLY # DO NOT EDIT MANUALLY
# ----------------------------------------- # -----------------------------------------
""" """
schema["GENERATED_TS_COMMENT"] = """ apiDefinition["GENERATED_TS_COMMENT"] = """
// ----------------------------------------- // -----------------------------------------
// THIS FILE HAS BEEN GENERATED // THIS FILE HAS BEEN GENERATED
// DO NOT EDIT MANUALLY // DO NOT EDIT MANUALLY
// ----------------------------------------- // -----------------------------------------
""" """
schema["GENERATED_CS_COMMENT"] = """ apiDefinition["GENERATED_CS_COMMENT"] = """
// ----------------------------------------- // -----------------------------------------
// THIS FILE HAS BEEN GENERATED // THIS FILE HAS BEEN GENERATED
// DO NOT EDIT MANUALLY // DO NOT EDIT MANUALLY
@ -56,34 +57,78 @@ schema["GENERATED_CS_COMMENT"] = """
""" """
packageName = os.environ["PACKAGE_NAME"] packageName = os.environ["PACKAGE_NAME"]
schema["env"] = { "packagename": packageName } apiDefinition["env"] = { "packagename": packageName }
statementFinder = re.compile('STATEMENTBEGIN (.*) STATEMENTEND')
statementChecker = {
'get': re.compile('^select', re.IGNORECASE),
'put': re.compile('^update', re.IGNORECASE),
'delete': re.compile('^delete', re.IGNORECASE),
'post': re.compile('^insert', re.IGNORECASE)
}
operations = [] operations = []
for path in schema['paths'].values(): for path in apiDefinition['paths'].values():
for (method, operation) in path.items(): for (method, operation) in path.items():
#print(f"{method=}") #print(f"{method=}")
#print(f"{CsOperationNameConverter(operation['operationId'])=}") #print(f"{CsOperationNameConverter(operation['operationId'])=}")
# if 200 in
content = operation['responses'][200]['content']['application/json']['schema'] content = operation['responses'][200]['content']['application/json']['schema']
if ('type' in content) and (content['type'] == 'array'): if ('type' in content) and (content['type'] == 'array'):
isList = True isList = True
typ = OpenApiExtractRefType(content['items']['$ref']) resultType = OpenApiExtractRefType(content['items']['$ref'])
else: else:
isList = False isList = False
typ = OpenApiExtractRefType(content['$ref']) resultType = OpenApiExtractRefType(content['$ref'])
#print(f"{content=}") #print(f"{content=}")
#print(f"{typ=}") #print(f"{resultType=}")
description = None
statement = None
if 'description' in operation:
description = operation['description']
statementFinderResult = statementFinder.search(description)
if statementFinderResult:
statement = statementFinderResult.group(1)
if not statementChecker[method].match(statement):
raise Exception(f"Invalid statement {statement} for method {method}")
inputType = None
if 'requestBody' in operation:
inputType = OpenApiExtractRefType(operation['requestBody']['content']['application/json']['schema']['$ref'])
operations.append({ operations.append({
'description': description,
'method':method, 'method':method,
'func':CsOperationNameConverter(operation['operationId']), 'func':CsOperationNameConverter(operation['operationId']),
'isList': isList, 'isList': isList,
'type': typ 'resultType': { 'apiName': resultType, 'csName': CsOperationNameConverter(resultType) } if resultType else {},
'allSelector': operation['operationId'].endswith('all'),
'byIdSelector': operation['operationId'].endswith('byid'),
'statement': statement,
'inputType': { 'apiName': inputType, 'csName': CsOperationNameConverter(inputType) } if inputType else {}
}) })
#print(f"{operations=}") print(f"{operations=}")
schema["operations"] = operations apiDefinition["operations"] = operations
types = {}
for (typeName, typeDefinition) in apiDefinition['components']['schemas'].items():
#print(f"{typeName=}")
typeProperties = []
for itemName in typeDefinition['properties']:
#print(f"{itemName=}")
typeProperties.append({
'sqlName': itemName,
'csName': itemName.capitalize()
})
types[typeName] = {
'sqlName': typeName,
'csName': typeName.capitalize(),
'properties': typeProperties
}
print(f"{types=}")
apiDefinition['types'] = types
for f in glob.glob(args.template, recursive=True): for f in glob.glob(args.template, recursive=True):
print(f"process {f}") print(f"process {f}")
tmpl = Template(file=f, searchList=[schema]) tmpl = Template(file=f, searchList=[apiDefinition])
with open(f[:-5], 'w') as outFile: with open(f[:-5], 'w') as outFile:
outFile.write(str(tmpl)) outFile.write(str(tmpl))

View File

@ -1,8 +1,11 @@
def capitalizeOnlyFirstLetter(i):
return i[0].upper() + i[1:]
def JsNameConverter(i): def JsNameConverter(i):
return ''.join([x.capitalize() for x in i.split('_')]) return ''.join([x.capitalize() for x in i.split('_')]) if i else i
def CsOperationNameConverter(i): def CsOperationNameConverter(i):
return ''.join([x.capitalize() for x in i.split('.')]) return ''.join([capitalizeOnlyFirstLetter(x) for x in i.split('.')]) if i else i
def OpenApiExtractRefType(i): def OpenApiExtractRefType(i):
e = i.split('/') e = i.split('/')

View File

@ -18,13 +18,26 @@ paths:
schema: schema:
type: array type: array
items: items:
$ref: "#/components/schemas/Test1" $ref: "#/components/schemas/test1"
404: 404:
description: No test1 items available description: No test1 items available
# post:
# tags: [ "Regular" ]
# operationId: Regular.test1insert
# summary: Inserts an item into table test1
# requestBody:
# description: test1
# content:
# application/json:
# schema:
# $ref: "#/components/schemas/test1"
# responses:
# 201:
# description: Your items has been inserted
/pdb/v2/test1/{id}: /pdb/v2/test1/{id}:
get: get:
tags: [ "Regular" ] tags: [ "Regular" ]
operationId: Regular.test2byid operationId: Regular.test1byid
summary: Returns one entry from table test1 by id summary: Returns one entry from table test1 by id
parameters: parameters:
- name: id - name: id
@ -38,13 +51,49 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Test1" $ref: "#/components/schemas/test1"
404:
description: No such test1 item available
/pdb/v2/test1/specificSelectName:
get:
tags: [ "Regular" ]
operationId: Regular.test1specificSelectName
summary: Returns entries from table test1 using a dedicated select statement
description:
STATEMENTBEGIN
select txt
from test1
where nr = ?
STATEMENTEND
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem
ipsum dolor sit amet.
requestBody:
description: specificSelectNameType
content:
application/json:
schema:
$ref: "#/components/schemas/specificSelectNameType"
responses:
200:
description: Here are your test1 items
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/specificResultType"
404: 404:
description: No such test1 item available description: No such test1 item available
components: components:
schemas: schemas:
Test1: test1:
description: A test1 item description: A test1 item
type: object type: object
required: required:
@ -56,3 +105,17 @@ components:
type: string type: string
nr: nr:
type: integer type: integer
specificSelectNameType:
description: Specific type to defintion select condition
type: object
required:
- nr
properties:
nr:
type: integer
specificResultType:
description: Specific result type to defintion select clause
type: object
properties:
txt:
type: string

View File

@ -17,18 +17,51 @@ namespace ${env['packagename']}.Implementations
public class RegularApiImplementation : RegularApiController public class RegularApiImplementation : RegularApiController
{ {
#for $operation in $operations #for $operation in $operations
public override IActionResult ${operation['func']}() { #if $operation['method'] == 'get'
return new ObjectResult(); public override IActionResult ${operation['func']}( #slurp
#if $operation['byIdSelector']
INSERT CODE TO GET ID #slurp
#elif $operation['inputType']
[FromBody]$operation['inputType']['csName'] inputItem #slurp
#end if
) {
// Statement:
#if not $operation['statement']
// SELECT
#set $sep = ""
#for $property in $types[$operation['resultType']['apiName']]['properties']
// $sep$property['sqlName']
#set $sep = ","
#end for
// FROM $types[$operation['resultType']['apiName']]['sqlName'];
#else
// $operation['statement']
#end if
#if $operation['inputType']
// Input type mapping: $operation['inputType']['apiName'] -> $operation['inputType']['csName']
#for $property in $types[$operation['inputType']['apiName']]['properties']
// $property['sqlName'] -> $property['csName']
#end for
#end if
// Model object to use: $operation['resultType']['apiName'] -> $operation['resultType']['csName']
// Properties to map result:
#for $property in $types[$operation['resultType']['apiName']]['properties']
// $property['sqlName'] -> $property['csName']
#end for
#if $operation['isList']
// result must be mapped in list
#end if
return new ObjectResult();
} }
#elif $operation['method'] == 'post'
bla
#else
#raise Exception('invalid method')
#end if
#end for #end for
public override IActionResult CyclesStartCycle([FromBody]MCycle mCycle) {
return StatusCode(200, default(MCycle));
}
public override IActionResult CyclesStopCycle([FromBody]CycleId cycleId) {
throw new NotImplementedException();
}
} }
} }