geniespec 0.1.1.dev7__tar.gz → 0.1.2.dev9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/PKG-INFO +20 -4
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/README.md +19 -3
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/pyproject.toml +1 -1
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/pyproject.toml.orig +1 -1
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/api_schema.yaml +25 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/genie.py +38 -2
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/languages.py +21 -16
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/renderable_types.py +13 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/v{version}/resources.py.j2 +7 -1
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/typescript-client/{service}/v{version}/resources.ts.j2 +17 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/types.py +7 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/__init__.py +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/cli.py +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/__init__.py.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/v{version}/__init__.py.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/v{version}/api.py.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/v{version}/endpoints.py.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/v{version}/exceptions.py.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/python-server/{service}/v{version}/internal.py.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/typescript-client/{service}/v{version}/client.ts.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/typescript-client/{service}/v{version}/endpoints.ts.j2 +0 -0
- {geniespec-0.1.1.dev7 → geniespec-0.1.2.dev9}/src/geniespec/templates/typescript-client/{service}/v{version}/index.ts.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geniespec
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2.dev9
|
|
4
4
|
Summary: Generate API code from an api.yaml spec
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Dist: jinja2>=3.1.6
|
|
@@ -27,7 +27,7 @@ geniespec watch --spec api.yaml --target <python-server | typescript-client> [--
|
|
|
27
27
|
- `watch` builds, then rebuilds whenever the spec changes. If the spec is invalid it logs the error and keeps the previous output. Given a command after `--`, it runs it once the first build is done, keeps rebuilding while it runs, exits with its exit code and stops it when watch itself is stopped, e.g. `geniespec watch --spec api.yaml --target python-server -- uvicorn app:app --reload`.
|
|
28
28
|
- The bundled templates are examples of a kibalabs-style target.
|
|
29
29
|
|
|
30
|
-
## Enums and files
|
|
30
|
+
## Enums, oneOfs and files
|
|
31
31
|
|
|
32
32
|
Enums are string values limited to a list. Name them at the top level to reuse them, or give a field its own:
|
|
33
33
|
|
|
@@ -50,6 +50,24 @@ resources:
|
|
|
50
50
|
|
|
51
51
|
`type: File` request fields upload a file. They are only allowed on POST transitions, and the transition's other fields must be single primitive or enum values, as the request is sent as a multipart form. Internal methods receive kiba-core's `UploadedFile` (`filename`, `contentType`, `content`); clients pass a `File`.
|
|
52
52
|
|
|
53
|
+
A oneOf is a value that is one of several resources, told apart by a discriminator attribute. Each member resource needs that attribute as a non-nullable enum with a single value:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
oneOfs:
|
|
57
|
+
- name: ViewContent
|
|
58
|
+
discriminator: kind
|
|
59
|
+
types: [ChartContent, TableContent]
|
|
60
|
+
resources:
|
|
61
|
+
- singleName: ChartContent
|
|
62
|
+
collectionName: ChartContents
|
|
63
|
+
attributes:
|
|
64
|
+
- name: kind
|
|
65
|
+
type: Enum
|
|
66
|
+
enumValues: [chart]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Fields use it like any other type (`type: ViewContent`, also inside `List` and `Mapping`). `python-server` generates a `ViewContentV1` pydantic discriminated union in `resources`; `typescript-client` generates a `ViewContentV1` union type and a `viewContentV1FromObject` function that builds the member class named by the discriminator (and throws for unknown values).
|
|
70
|
+
|
|
53
71
|
# Development
|
|
54
72
|
|
|
55
73
|
```
|
|
@@ -70,8 +88,6 @@ Merges to `main` publish a dev version to PyPI. To release, bump `version` in `p
|
|
|
70
88
|
|
|
71
89
|
## Typing of params and fields in general
|
|
72
90
|
|
|
73
|
-
OneOf not supported yet
|
|
74
|
-
|
|
75
91
|
I think we need to create an object for Type. what would it look like?
|
|
76
92
|
|
|
77
93
|
|
|
@@ -13,7 +13,7 @@ geniespec watch --spec api.yaml --target <python-server | typescript-client> [--
|
|
|
13
13
|
- `watch` builds, then rebuilds whenever the spec changes. If the spec is invalid it logs the error and keeps the previous output. Given a command after `--`, it runs it once the first build is done, keeps rebuilding while it runs, exits with its exit code and stops it when watch itself is stopped, e.g. `geniespec watch --spec api.yaml --target python-server -- uvicorn app:app --reload`.
|
|
14
14
|
- The bundled templates are examples of a kibalabs-style target.
|
|
15
15
|
|
|
16
|
-
## Enums and files
|
|
16
|
+
## Enums, oneOfs and files
|
|
17
17
|
|
|
18
18
|
Enums are string values limited to a list. Name them at the top level to reuse them, or give a field its own:
|
|
19
19
|
|
|
@@ -36,6 +36,24 @@ resources:
|
|
|
36
36
|
|
|
37
37
|
`type: File` request fields upload a file. They are only allowed on POST transitions, and the transition's other fields must be single primitive or enum values, as the request is sent as a multipart form. Internal methods receive kiba-core's `UploadedFile` (`filename`, `contentType`, `content`); clients pass a `File`.
|
|
38
38
|
|
|
39
|
+
A oneOf is a value that is one of several resources, told apart by a discriminator attribute. Each member resource needs that attribute as a non-nullable enum with a single value:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
oneOfs:
|
|
43
|
+
- name: ViewContent
|
|
44
|
+
discriminator: kind
|
|
45
|
+
types: [ChartContent, TableContent]
|
|
46
|
+
resources:
|
|
47
|
+
- singleName: ChartContent
|
|
48
|
+
collectionName: ChartContents
|
|
49
|
+
attributes:
|
|
50
|
+
- name: kind
|
|
51
|
+
type: Enum
|
|
52
|
+
enumValues: [chart]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Fields use it like any other type (`type: ViewContent`, also inside `List` and `Mapping`). `python-server` generates a `ViewContentV1` pydantic discriminated union in `resources`; `typescript-client` generates a `ViewContentV1` union type and a `viewContentV1FromObject` function that builds the member class named by the discriminator (and throws for unknown values).
|
|
56
|
+
|
|
39
57
|
# Development
|
|
40
58
|
|
|
41
59
|
```
|
|
@@ -56,8 +74,6 @@ Merges to `main` publish a dev version to PyPI. To release, bump `version` in `p
|
|
|
56
74
|
|
|
57
75
|
## Typing of params and fields in general
|
|
58
76
|
|
|
59
|
-
OneOf not supported yet
|
|
60
|
-
|
|
61
77
|
I think we need to create an object for Type. what would it look like?
|
|
62
78
|
|
|
63
79
|
|
|
@@ -36,6 +36,10 @@ properties:
|
|
|
36
36
|
type: array
|
|
37
37
|
items:
|
|
38
38
|
$ref: '#/definitions/Enum'
|
|
39
|
+
oneOfs:
|
|
40
|
+
type: array
|
|
41
|
+
items:
|
|
42
|
+
$ref: '#/definitions/OneOf'
|
|
39
43
|
imports:
|
|
40
44
|
type: array
|
|
41
45
|
items:
|
|
@@ -65,6 +69,27 @@ definitions:
|
|
|
65
69
|
pattern: '^[A-Z][A-Za-z]*$'
|
|
66
70
|
values:
|
|
67
71
|
$ref: '#/definitions/EnumValues'
|
|
72
|
+
OneOf:
|
|
73
|
+
type: object
|
|
74
|
+
additionalProperties: false
|
|
75
|
+
required:
|
|
76
|
+
- name
|
|
77
|
+
- discriminator
|
|
78
|
+
- types
|
|
79
|
+
properties:
|
|
80
|
+
name:
|
|
81
|
+
type: string
|
|
82
|
+
pattern: '^[A-Z][A-Za-z]*$'
|
|
83
|
+
discriminator:
|
|
84
|
+
type: string
|
|
85
|
+
pattern: '^[a-z][A-Za-z0-9-]*$'
|
|
86
|
+
types:
|
|
87
|
+
type: array
|
|
88
|
+
minItems: 2
|
|
89
|
+
uniqueItems: true
|
|
90
|
+
items:
|
|
91
|
+
type: string
|
|
92
|
+
pattern: '^[A-Z][A-Za-z-]*$'
|
|
68
93
|
Attribute:
|
|
69
94
|
type: object
|
|
70
95
|
additionalProperties: false
|
|
@@ -28,6 +28,8 @@ from geniespec.renderable_types import RenderableEnum
|
|
|
28
28
|
from geniespec.renderable_types import RenderableException
|
|
29
29
|
from geniespec.renderable_types import RenderableField
|
|
30
30
|
from geniespec.renderable_types import RenderableMethod
|
|
31
|
+
from geniespec.renderable_types import RenderableOneOf
|
|
32
|
+
from geniespec.renderable_types import RenderableOneOfMember
|
|
31
33
|
from geniespec.renderable_types import RenderableOperation
|
|
32
34
|
from geniespec.renderable_types import RenderableParameter
|
|
33
35
|
from geniespec.renderable_types import RenderableRawTypes
|
|
@@ -36,6 +38,8 @@ from geniespec.renderable_types import RenderableString
|
|
|
36
38
|
from geniespec.renderable_types import RenderableType
|
|
37
39
|
from geniespec.renderable_types import ResourceEndpointComponent
|
|
38
40
|
from geniespec.renderable_types import SingleResourceEndpointComponent
|
|
41
|
+
from geniespec.types import ApiEnum
|
|
42
|
+
from geniespec.types import ApiOneOf
|
|
39
43
|
from geniespec.types import Attribute
|
|
40
44
|
from geniespec.types import BaseTransition
|
|
41
45
|
from geniespec.types import ComplexTypes
|
|
@@ -378,13 +382,44 @@ class Renderer:
|
|
|
378
382
|
errors += [f'Field {field.name.snakeCaseString} of {exception.name.pascalCaseString} cannot be a File' for exception in exceptions for field in exception.fields if field.fieldType.rawType == RenderableRawTypes.FILE]
|
|
379
383
|
return errors
|
|
380
384
|
|
|
385
|
+
@staticmethod
|
|
386
|
+
def _one_of_members(oneOf: ApiOneOf, resourcesByName: dict[str, Resource], enumsByName: dict[str, ApiEnum]) -> tuple[list[RenderableOneOfMember], list[str]]:
|
|
387
|
+
members: list[RenderableOneOfMember] = []
|
|
388
|
+
errors: list[str] = []
|
|
389
|
+
for typeName in oneOf.types:
|
|
390
|
+
resource = resourcesByName.get(typeName)
|
|
391
|
+
if resource is None:
|
|
392
|
+
errors.append(f'Unknown resource {typeName} in {oneOf.name}')
|
|
393
|
+
continue
|
|
394
|
+
attribute = next((attribute for attribute in resource.attributes if attribute.name == oneOf.discriminator), None)
|
|
395
|
+
if attribute is None:
|
|
396
|
+
errors.append(f'{typeName} needs a {oneOf.discriminator} attribute to be part of {oneOf.name}')
|
|
397
|
+
continue
|
|
398
|
+
values = attribute.enumValues if attribute.fieldType == SpecialTypes.ENUM else enumsByName[attribute.fieldType].values if attribute.fieldType in enumsByName else []
|
|
399
|
+
if len(values) != 1 or attribute.isNullable:
|
|
400
|
+
errors.append(f'{oneOf.discriminator} of {typeName} must be a non-nullable Enum with one value to be part of {oneOf.name}')
|
|
401
|
+
continue
|
|
402
|
+
members.append(RenderableOneOfMember(className=RenderableString.from_pascal_case_string(string=typeName), value=values[0]))
|
|
403
|
+
errors += [f'{oneOf.name} has more than one {oneOf.discriminator} {value}' for value, count in collections.Counter(member.value for member in members).items() if count > 1]
|
|
404
|
+
return members, errors
|
|
405
|
+
|
|
381
406
|
def build_renderable_api(self, api: KibaApi) -> RenderableApi:
|
|
382
407
|
fullResourceNames = [self._resource_full_name(resource=resource, name=resource.singleName) for resource in api.resources]
|
|
383
408
|
enumNames = [enum.name for enum in api.enums]
|
|
384
|
-
|
|
385
|
-
|
|
409
|
+
oneOfNames = [oneOf.name for oneOf in api.oneOfs]
|
|
410
|
+
knownTypeNames = {
|
|
411
|
+
**dict.fromkeys(fullResourceNames, RenderableRawTypes.CLASS),
|
|
412
|
+
**dict.fromkeys(enumNames, RenderableRawTypes.ENUM),
|
|
413
|
+
**dict.fromkeys(oneOfNames, RenderableRawTypes.ONE_OF),
|
|
414
|
+
}
|
|
415
|
+
resourceErrors = [f'Duplicate type name: {name}' for name, count in collections.Counter(fullResourceNames + enumNames + oneOfNames).items() if count > 1]
|
|
386
416
|
resourceErrors += [f'Unknown parent resource {resource.parentResource} for {resource.singleName}' for resource in api.resources if resource.parentResource is not None and resource.parentResource not in fullResourceNames]
|
|
387
417
|
resourcesByName = dict(zip(fullResourceNames, api.resources, strict=False))
|
|
418
|
+
renderableOneOfs: list[RenderableOneOf] = []
|
|
419
|
+
for oneOf in api.oneOfs:
|
|
420
|
+
members, oneOfErrors = self._one_of_members(oneOf=oneOf, resourcesByName=resourcesByName, enumsByName={enum.name: enum for enum in api.enums})
|
|
421
|
+
resourceErrors += oneOfErrors
|
|
422
|
+
renderableOneOfs.append(RenderableOneOf(name=RenderableString.from_pascal_case_string(string=oneOf.name), discriminator=RenderableString.from_kebab_case_string(string=oneOf.discriminator), members=members))
|
|
388
423
|
for resource, fullName in zip(api.resources, fullResourceNames, strict=True):
|
|
389
424
|
ancestorNames: list[str] = []
|
|
390
425
|
ancestor = resourcesByName.get(resource.parentResource) if resource.parentResource is not None else None
|
|
@@ -439,6 +474,7 @@ class Renderer:
|
|
|
439
474
|
operations=renderableOperations,
|
|
440
475
|
exceptions=renderableExceptions,
|
|
441
476
|
enums=[RenderableEnum(name=RenderableString.from_pascal_case_string(string=enum.name), values=enum.values) for enum in api.enums],
|
|
477
|
+
oneOfs=renderableOneOfs,
|
|
442
478
|
)
|
|
443
479
|
renderableResources: list[RenderableResource] = [self._resource_to_renderable_resource(resource=resource, knownTypeNames=knownTypeNames) for resource in api.resources]
|
|
444
480
|
uncoveredTransitions: list[str] = []
|
|
@@ -12,6 +12,7 @@ from geniespec.renderable_types import RenderableEndpointComponent
|
|
|
12
12
|
from geniespec.renderable_types import RenderableEnum
|
|
13
13
|
from geniespec.renderable_types import RenderableException
|
|
14
14
|
from geniespec.renderable_types import RenderableField
|
|
15
|
+
from geniespec.renderable_types import RenderableOneOf
|
|
15
16
|
from geniespec.renderable_types import RenderableOperation
|
|
16
17
|
from geniespec.renderable_types import RenderableParameter
|
|
17
18
|
from geniespec.renderable_types import RenderableRawTypes
|
|
@@ -68,9 +69,6 @@ class BaseLanguageDefinition:
|
|
|
68
69
|
def object_type(self, isNullable: bool, className: RenderableString, apiVersion: Version, isLocalReference: bool) -> str:
|
|
69
70
|
raise NotImplementedError
|
|
70
71
|
|
|
71
|
-
def one_of_type(self, isNullable: bool, subTypes: list[str]) -> str:
|
|
72
|
-
raise NotImplementedError
|
|
73
|
-
|
|
74
72
|
def string_type(self, isNullable: bool) -> str:
|
|
75
73
|
raise NotImplementedError
|
|
76
74
|
|
|
@@ -92,10 +90,6 @@ class BaseLanguageDefinition:
|
|
|
92
90
|
def is_valid_name(self, name: str) -> bool:
|
|
93
91
|
raise NotImplementedError
|
|
94
92
|
|
|
95
|
-
def _render_one_of_type_string(self, oneOfTypeString: str, isNullable: bool, apiVersion: Version) -> list[str]:
|
|
96
|
-
oneOfTypes = ''.join(oneOfTypeString.replace('OneOf[', '', 1).rsplit(']', 1)).split(', ')
|
|
97
|
-
return [self.render_type(renderableType=RenderableType(rawType=oneOfType, isNullable=isNullable), apiVersion=apiVersion) for oneOfType in oneOfTypes]
|
|
98
|
-
|
|
99
93
|
def _render_list_type_string(self, listTypeString: str, apiVersion: Version) -> str:
|
|
100
94
|
listItemType = ''.join(listTypeString.replace('List[', '', 1).rsplit(']', 1))
|
|
101
95
|
return self.render_type(renderableType=RenderableType(rawType=listItemType, isNullable=False), apiVersion=apiVersion)
|
|
@@ -131,7 +125,7 @@ class BaseLanguageDefinition:
|
|
|
131
125
|
keyType = self.render_type(renderableType=renderableType.keyType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
132
126
|
valueType = self.render_type(renderableType=renderableType.valueType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
133
127
|
return self.mapping_type(isNullable=isNullable, keyType=keyType, valueType=valueType)
|
|
134
|
-
if yamlType
|
|
128
|
+
if yamlType in (RenderableRawTypes.CLASS, RenderableRawTypes.ONE_OF) and renderableType.className is not None:
|
|
135
129
|
return self.object_type(isNullable=isNullable, className=renderableType.className, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
136
130
|
raise KibaException(f'Failed to render: {type(renderableType)} {renderableType}')
|
|
137
131
|
|
|
@@ -175,6 +169,12 @@ class BaseLanguageDefinition:
|
|
|
175
169
|
def enum_class_name(self, enum: RenderableEnum, apiVersion: Version) -> str:
|
|
176
170
|
return f'{self.pascal_name(name=enum.name)}V{apiVersion.major}'
|
|
177
171
|
|
|
172
|
+
def one_of_class_name(self, oneOf: RenderableOneOf, apiVersion: Version) -> str:
|
|
173
|
+
return f'{self.pascal_name(name=oneOf.name)}V{apiVersion.major}'
|
|
174
|
+
|
|
175
|
+
def one_of_member_class_names(self, oneOf: RenderableOneOf, apiVersion: Version) -> list[str]:
|
|
176
|
+
return [f'{self.pascal_name(name=member.className)}V{apiVersion.major}' for member in oneOf.members]
|
|
177
|
+
|
|
178
178
|
@staticmethod
|
|
179
179
|
def is_inline_enum(renderableType: RenderableType) -> bool:
|
|
180
180
|
return renderableType.rawType == RenderableRawTypes.ENUM and renderableType.className is None
|
|
@@ -339,10 +339,6 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
339
339
|
typeName = f'{className.pascalCaseString}V{apiVersion.major}' if isLocalReference else f'resources.{className.pascalCaseString}V{apiVersion.major}'
|
|
340
340
|
return f'{typeName} | None' if isNullable else typeName
|
|
341
341
|
|
|
342
|
-
def one_of_type(self, isNullable: bool, subTypes: list[str]) -> str:
|
|
343
|
-
fieldTypes = ' | '.join(subTypes)
|
|
344
|
-
return f'{fieldTypes} | None' if isNullable else f'Union[{fieldTypes} | None'
|
|
345
|
-
|
|
346
342
|
def string_type(self, isNullable: bool) -> str:
|
|
347
343
|
return 'str | None' if isNullable else 'str'
|
|
348
344
|
|
|
@@ -416,7 +412,10 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
416
412
|
return any(self._contains_json(renderableType=attribute.fieldType) for resource in api.resources for attribute in resource.attributes)
|
|
417
413
|
|
|
418
414
|
def resources_require_typing(self, api: RenderableApi) -> bool:
|
|
419
|
-
return len(api.enums) > 0 or any(self.is_inline_enum(renderableType=attribute.fieldType) for resource in api.resources for attribute in resource.attributes)
|
|
415
|
+
return len(api.enums) > 0 or len(api.oneOfs) > 0 or any(self.is_inline_enum(renderableType=attribute.fieldType) for resource in api.resources for attribute in resource.attributes)
|
|
416
|
+
|
|
417
|
+
def one_of_alias(self, oneOf: RenderableOneOf, apiVersion: Version) -> str:
|
|
418
|
+
return f'typing.Annotated[{" | ".join(self.one_of_member_class_names(oneOf=oneOf, apiVersion=apiVersion))}, pydantic.Field(discriminator={self.identifier(name=oneOf.discriminator)!r})]'
|
|
420
419
|
|
|
421
420
|
def operations_require_typing(self, api: RenderableApi) -> bool:
|
|
422
421
|
fieldTypes = [parameter.fieldType for operation in api.operations for parameter in operation.parameters] + [field.fieldType for operation in api.operations for field in operation.responseFields or []]
|
|
@@ -612,7 +611,10 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
612
611
|
|
|
613
612
|
def references_resources(self, renderableTypes: list[RenderableType]) -> bool:
|
|
614
613
|
valueTypes = [self._value_type(renderableType=renderableType) for renderableType in renderableTypes]
|
|
615
|
-
return any(valueType.rawType in ('class', PrimitiveTypes.JSON) or (valueType.rawType == RenderableRawTypes.ENUM and valueType.className is not None) for valueType in valueTypes)
|
|
614
|
+
return any(valueType.rawType in ('class', PrimitiveTypes.JSON, RenderableRawTypes.ONE_OF) or (valueType.rawType == RenderableRawTypes.ENUM and valueType.className is not None) for valueType in valueTypes)
|
|
615
|
+
|
|
616
|
+
def one_of_parser_name(self, name: RenderableString, apiVersion: Version) -> str:
|
|
617
|
+
return f'{self._normalized_name(name=name).camelCaseString}V{apiVersion.major}FromObject'
|
|
616
618
|
|
|
617
619
|
def _value_type(self, renderableType: RenderableType) -> RenderableType:
|
|
618
620
|
return self._value_type(renderableType=renderableType.valueType) if isinstance(renderableType, MappingRenderableType) else renderableType
|
|
@@ -658,6 +660,9 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
658
660
|
valueTypeName = self.type(renderableType=renderableType.valueType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
659
661
|
convertedEntry = self.from_object(renderableType=renderableType.valueType, value='entry', apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
660
662
|
return f'Object.fromEntries(Object.entries({value} as RawObject).map(([key, entry]: [string, unknown]): [string, {valueTypeName}] => [key, {convertedEntry}]))'
|
|
663
|
+
if renderableType.rawType == RenderableRawTypes.ONE_OF and renderableType.className is not None:
|
|
664
|
+
parserName = self.one_of_parser_name(name=renderableType.className, apiVersion=apiVersion)
|
|
665
|
+
return f'{parserName if isLocalReference else f"Resources.{parserName}"}({value} as RawObject)'
|
|
661
666
|
if renderableType.rawType == 'class' and renderableType.className is not None:
|
|
662
667
|
return f'{self.type(renderableType=renderableType, apiVersion=apiVersion, isLocalReference=isLocalReference)}.fromObject({value} as RawObject)'
|
|
663
668
|
if renderableType.rawType == PrimitiveTypes.STRING:
|
|
@@ -677,7 +682,7 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
677
682
|
def _is_serialized_as_is(self, renderableType: RenderableType) -> bool:
|
|
678
683
|
if isinstance(renderableType, MappingRenderableType):
|
|
679
684
|
return self._is_serialized_as_is(renderableType=renderableType.valueType)
|
|
680
|
-
return renderableType.rawType not in ('class', PrimitiveTypes.DATE, PrimitiveTypes.DATETIME)
|
|
685
|
+
return renderableType.rawType not in ('class', RenderableRawTypes.ONE_OF, PrimitiveTypes.DATE, PrimitiveTypes.DATETIME)
|
|
681
686
|
|
|
682
687
|
def to_object(self, renderableType: RenderableType, value: str, apiVersion: Version, isOptional: bool = False, isLocalReference: bool = False) -> str:
|
|
683
688
|
if self._is_serialized_as_is(renderableType=renderableType):
|
|
@@ -693,7 +698,7 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
693
698
|
valueTypeName = self.type(renderableType=renderableType.valueType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
694
699
|
convertedEntry = self.to_object(renderableType=renderableType.valueType, value='entry', apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
695
700
|
return f'Object.fromEntries(Object.entries({value}).map(([key, entry]: [string, {valueTypeName}]): [string, unknown] => [key, {convertedEntry}]))'
|
|
696
|
-
if renderableType.rawType
|
|
701
|
+
if renderableType.rawType in ('class', RenderableRawTypes.ONE_OF):
|
|
697
702
|
return f'{value}.toObject()'
|
|
698
703
|
if renderableType.rawType == PrimitiveTypes.DATETIME:
|
|
699
704
|
return f'dateToString({value})'
|
|
@@ -91,6 +91,7 @@ class RenderableRawTypes:
|
|
|
91
91
|
CLASS = 'class'
|
|
92
92
|
ENUM = 'enum'
|
|
93
93
|
FILE = 'file'
|
|
94
|
+
ONE_OF = 'oneOf'
|
|
94
95
|
|
|
95
96
|
|
|
96
97
|
class RenderableType(BaseModel):
|
|
@@ -106,6 +107,17 @@ class RenderableEnum(BaseModel):
|
|
|
106
107
|
values: list[str]
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
class RenderableOneOfMember(BaseModel):
|
|
111
|
+
className: RenderableString
|
|
112
|
+
value: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class RenderableOneOf(BaseModel):
|
|
116
|
+
name: RenderableString
|
|
117
|
+
discriminator: RenderableString
|
|
118
|
+
members: list[RenderableOneOfMember]
|
|
119
|
+
|
|
120
|
+
|
|
109
121
|
class MappingRenderableType(RenderableType):
|
|
110
122
|
keyType: RenderableType
|
|
111
123
|
valueType: RenderableType
|
|
@@ -238,6 +250,7 @@ class RenderableApi(BaseModel):
|
|
|
238
250
|
operations: list[RenderableOperation]
|
|
239
251
|
exceptions: list[RenderableException]
|
|
240
252
|
enums: list[RenderableEnum] = []
|
|
253
|
+
oneOfs: list[RenderableOneOf] = []
|
|
241
254
|
|
|
242
255
|
|
|
243
256
|
class ProcessableResource(BaseModel):
|
|
@@ -7,7 +7,7 @@ import datetime
|
|
|
7
7
|
import typing
|
|
8
8
|
{% endif %}
|
|
9
9
|
|
|
10
|
-
{% if target.resources_require_json(api=api) %}
|
|
10
|
+
{% if target.resources_require_json(api=api) or api.oneOfs %}
|
|
11
11
|
import pydantic
|
|
12
12
|
{% endif %}
|
|
13
13
|
from pydantic import BaseModel
|
|
@@ -29,3 +29,9 @@ class {{ target.resource_class_name(resource=resource, apiVersion=api.version) }
|
|
|
29
29
|
{% endfor %}
|
|
30
30
|
|
|
31
31
|
{% endfor %}
|
|
32
|
+
{% for oneOf in api.oneOfs %}
|
|
33
|
+
{% if loop.first %}
|
|
34
|
+
|
|
35
|
+
{% endif %}
|
|
36
|
+
{{ target.one_of_class_name(oneOf=oneOf, apiVersion=api.version) }} = {{ target.one_of_alias(oneOf=oneOf, apiVersion=api.version) }}
|
|
37
|
+
{% endfor %}
|
|
@@ -47,3 +47,20 @@ export class {{ className }} {
|
|
|
47
47
|
{% endif %}
|
|
48
48
|
}
|
|
49
49
|
{% endfor %}
|
|
50
|
+
{% for oneOf in api.oneOfs %}
|
|
51
|
+
{% set typeName = target.one_of_class_name(oneOf=oneOf, apiVersion=api.version) %}
|
|
52
|
+
{% set discriminator = target.identifier(name=oneOf.discriminator) %}
|
|
53
|
+
|
|
54
|
+
export type {{ typeName }} = {{ target.one_of_member_class_names(oneOf=oneOf, apiVersion=api.version) | join(' | ') }};
|
|
55
|
+
|
|
56
|
+
export const {{ target.one_of_parser_name(name=oneOf.name, apiVersion=api.version) }} = (obj: RawObject): {{ typeName }} => {
|
|
57
|
+
switch (obj.{{ discriminator }}) {
|
|
58
|
+
{% for member in oneOf.members %}
|
|
59
|
+
case '{{ member.value }}':
|
|
60
|
+
return {{ target.one_of_member_class_names(oneOf=oneOf, apiVersion=api.version)[loop.index0] }}.fromObject(obj);
|
|
61
|
+
{% endfor %}
|
|
62
|
+
default:
|
|
63
|
+
throw new Error(`Unknown {{ typeName }} {{ discriminator }}: ${String(obj.{{ discriminator }})}`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
{% endfor %}
|
|
@@ -47,6 +47,12 @@ class ApiEnum(BaseModel):
|
|
|
47
47
|
values: list[str]
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
class ApiOneOf(BaseModel):
|
|
51
|
+
name: str
|
|
52
|
+
discriminator: str
|
|
53
|
+
types: list[str]
|
|
54
|
+
|
|
55
|
+
|
|
50
56
|
class Attribute(BaseModel):
|
|
51
57
|
name: str
|
|
52
58
|
fieldType: str = pydantic.Field(alias='type')
|
|
@@ -155,6 +161,7 @@ class KibaApi(BaseModel):
|
|
|
155
161
|
transitions: list[RootTransition] = []
|
|
156
162
|
exceptions: list[ApiException] = []
|
|
157
163
|
enums: list[ApiEnum] = []
|
|
164
|
+
oneOfs: list[ApiOneOf] = []
|
|
158
165
|
defaultAuthentication: str | None = None
|
|
159
166
|
|
|
160
167
|
@property
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|