geniespec 0.1.1.dev6__tar.gz → 0.1.1.dev8__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.dev6 → geniespec-0.1.1.dev8}/PKG-INFO +44 -5
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/README.md +43 -4
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/pyproject.toml +2 -2
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/pyproject.toml.orig +2 -2
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/api_schema.yaml +54 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/genie.py +106 -19
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/languages.py +94 -26
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/renderable_types.py +26 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/v{version}/endpoints.py.j2 +4 -1
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/v{version}/internal.py.j2 +6 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/v{version}/resources.py.j2 +16 -1
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/typescript-client/{service}/v{version}/client.ts.j2 +8 -1
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/typescript-client/{service}/v{version}/endpoints.ts.j2 +25 -1
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/typescript-client/{service}/v{version}/resources.ts.j2 +21 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/types.py +23 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/__init__.py +0 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/cli.py +0 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/__init__.py.j2 +0 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/v{version}/__init__.py.j2 +0 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/v{version}/api.py.j2 +0 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/src/geniespec/templates/python-server/{service}/v{version}/exceptions.py.j2 +0 -0
- {geniespec-0.1.1.dev6 → geniespec-0.1.1.dev8}/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.1.
|
|
3
|
+
Version: 0.1.1.dev8
|
|
4
4
|
Summary: Generate API code from an api.yaml spec
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Dist: jinja2>=3.1.6
|
|
@@ -22,11 +22,52 @@ geniespec watch --spec api.yaml --target <python-server | typescript-client> [--
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
- `build` writes the target's package into the output directory. Only files in that package are touched: changed files are rewritten and files it no longer generates are removed.
|
|
25
|
-
- `python-server`: `./<service>_api/v<major>/` (`api`, `endpoints`, `resources`, `internal`, `exceptions`). Code run from the output directory imports it directly, e.g. `from my_service_api.v1 import api`. Needs `kiba-core` (`core.api`, `core.util.typing_util`), `pydantic` and `starlette`.
|
|
26
|
-
- `typescript-client`: `./<service>-api/v<major>/` (`client`, `endpoints`, `resources`, `index`) with a `<Service>ApiV<major>Client` that extends `@kibalabs/core`'s `ServiceClient`. Construct it with the url the API version is mounted at, e.g. `new MyServiceApiV1Client(requester, 'https://api.example.com/v1')`. Methods take path parameters, then a params object for the request fields, and return the response field; stream transitions take a callback for each item. Transitions with `responseType: redirect` or a `query-parameters` field are left out, as browsers open redirects directly and query-parameters fields receive third-party callbacks. Needs `@kibalabs/core`.
|
|
25
|
+
- `python-server`: `./<service>_api/v<major>/` (`api`, `endpoints`, `resources`, `internal`, `exceptions`). Code run from the output directory imports it directly, e.g. `from my_service_api.v1 import api`. Needs `kiba-core` (`core.api`, `core.util.typing_util`; 0.5.3.dev87 or later for `File` fields), `pydantic` and `starlette`.
|
|
26
|
+
- `typescript-client`: `./<service>-api/v<major>/` (`client`, `endpoints`, `resources`, `index`) with a `<Service>ApiV<major>Client` that extends `@kibalabs/core`'s `ServiceClient`. Construct it with the url the API version is mounted at, e.g. `new MyServiceApiV1Client(requester, 'https://api.example.com/v1')`. Methods take path parameters, then a params object for the request fields, and return the response field; stream transitions take a callback for each item; transitions with `File` fields send a multipart form. Transitions with `responseType: redirect` or a `query-parameters` field are left out, as browsers open redirects directly and query-parameters fields receive third-party callbacks. Needs `@kibalabs/core`.
|
|
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, oneOfs and files
|
|
31
|
+
|
|
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
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
enums:
|
|
36
|
+
- name: ViewKind
|
|
37
|
+
values: [member, cycle-start]
|
|
38
|
+
resources:
|
|
39
|
+
- singleName: View
|
|
40
|
+
collectionName: Views
|
|
41
|
+
attributes:
|
|
42
|
+
- name: kind
|
|
43
|
+
type: ViewKind
|
|
44
|
+
- name: size
|
|
45
|
+
type: Enum
|
|
46
|
+
enumValues: [small, large]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`python-server` types them as `typing.Literal[...]` (named ones as `ViewKindV1` in `resources`) and rejects requests with other values; `typescript-client` types them as `'member' | 'cycle-start'` (named ones as an exported `ViewKindV1`).
|
|
50
|
+
|
|
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
|
+
|
|
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
|
+
|
|
30
71
|
# Development
|
|
31
72
|
|
|
32
73
|
```
|
|
@@ -47,8 +88,6 @@ Merges to `main` publish a dev version to PyPI. To release, bump `version` in `p
|
|
|
47
88
|
|
|
48
89
|
## Typing of params and fields in general
|
|
49
90
|
|
|
50
|
-
Enum and OneOf not supported yet
|
|
51
|
-
|
|
52
91
|
I think we need to create an object for Type. what would it look like?
|
|
53
92
|
|
|
54
93
|
|
|
@@ -8,11 +8,52 @@ geniespec watch --spec api.yaml --target <python-server | typescript-client> [--
|
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
- `build` writes the target's package into the output directory. Only files in that package are touched: changed files are rewritten and files it no longer generates are removed.
|
|
11
|
-
- `python-server`: `./<service>_api/v<major>/` (`api`, `endpoints`, `resources`, `internal`, `exceptions`). Code run from the output directory imports it directly, e.g. `from my_service_api.v1 import api`. Needs `kiba-core` (`core.api`, `core.util.typing_util`), `pydantic` and `starlette`.
|
|
12
|
-
- `typescript-client`: `./<service>-api/v<major>/` (`client`, `endpoints`, `resources`, `index`) with a `<Service>ApiV<major>Client` that extends `@kibalabs/core`'s `ServiceClient`. Construct it with the url the API version is mounted at, e.g. `new MyServiceApiV1Client(requester, 'https://api.example.com/v1')`. Methods take path parameters, then a params object for the request fields, and return the response field; stream transitions take a callback for each item. Transitions with `responseType: redirect` or a `query-parameters` field are left out, as browsers open redirects directly and query-parameters fields receive third-party callbacks. Needs `@kibalabs/core`.
|
|
11
|
+
- `python-server`: `./<service>_api/v<major>/` (`api`, `endpoints`, `resources`, `internal`, `exceptions`). Code run from the output directory imports it directly, e.g. `from my_service_api.v1 import api`. Needs `kiba-core` (`core.api`, `core.util.typing_util`; 0.5.3.dev87 or later for `File` fields), `pydantic` and `starlette`.
|
|
12
|
+
- `typescript-client`: `./<service>-api/v<major>/` (`client`, `endpoints`, `resources`, `index`) with a `<Service>ApiV<major>Client` that extends `@kibalabs/core`'s `ServiceClient`. Construct it with the url the API version is mounted at, e.g. `new MyServiceApiV1Client(requester, 'https://api.example.com/v1')`. Methods take path parameters, then a params object for the request fields, and return the response field; stream transitions take a callback for each item; transitions with `File` fields send a multipart form. Transitions with `responseType: redirect` or a `query-parameters` field are left out, as browsers open redirects directly and query-parameters fields receive third-party callbacks. Needs `@kibalabs/core`.
|
|
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, oneOfs and files
|
|
17
|
+
|
|
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
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
enums:
|
|
22
|
+
- name: ViewKind
|
|
23
|
+
values: [member, cycle-start]
|
|
24
|
+
resources:
|
|
25
|
+
- singleName: View
|
|
26
|
+
collectionName: Views
|
|
27
|
+
attributes:
|
|
28
|
+
- name: kind
|
|
29
|
+
type: ViewKind
|
|
30
|
+
- name: size
|
|
31
|
+
type: Enum
|
|
32
|
+
enumValues: [small, large]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`python-server` types them as `typing.Literal[...]` (named ones as `ViewKindV1` in `resources`) and rejects requests with other values; `typescript-client` types them as `'member' | 'cycle-start'` (named ones as an exported `ViewKindV1`).
|
|
36
|
+
|
|
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
|
+
|
|
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
|
+
|
|
16
57
|
# Development
|
|
17
58
|
|
|
18
59
|
```
|
|
@@ -33,8 +74,6 @@ Merges to `main` publish a dev version to PyPI. To release, bump `version` in `p
|
|
|
33
74
|
|
|
34
75
|
## Typing of params and fields in general
|
|
35
76
|
|
|
36
|
-
Enum and OneOf not supported yet
|
|
37
|
-
|
|
38
77
|
I think we need to create an object for Type. what would it look like?
|
|
39
78
|
|
|
40
79
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "geniespec"
|
|
3
|
-
version = "0.1.1.
|
|
3
|
+
version = "0.1.1.dev8"
|
|
4
4
|
description = "Generate API code from an api.yaml spec"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -22,7 +22,7 @@ repository = "https://github.com/kibalabs/genie"
|
|
|
22
22
|
[dependency-groups]
|
|
23
23
|
dev = [
|
|
24
24
|
"kiba-build==0.1.11.dev15",
|
|
25
|
-
"kiba-core[core-api,requester]>=0.5.3.
|
|
25
|
+
"kiba-core[core-api,requester]>=0.5.3.dev87,<0.5.4",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[build-system]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "geniespec"
|
|
3
|
-
version = "0.1.1.
|
|
3
|
+
version = "0.1.1.dev8"
|
|
4
4
|
description = "Generate API code from an api.yaml spec"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -22,7 +22,7 @@ repository = "https://github.com/kibalabs/genie"
|
|
|
22
22
|
[dependency-groups]
|
|
23
23
|
dev = [
|
|
24
24
|
"kiba-build==0.1.11.dev15",
|
|
25
|
-
"kiba-core[core-api,requester]>=0.5.3.
|
|
25
|
+
"kiba-core[core-api,requester]>=0.5.3.dev87,<0.5.4",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[build-system]
|
|
@@ -32,6 +32,14 @@ properties:
|
|
|
32
32
|
type: array
|
|
33
33
|
items:
|
|
34
34
|
$ref: '#/definitions/Exception'
|
|
35
|
+
enums:
|
|
36
|
+
type: array
|
|
37
|
+
items:
|
|
38
|
+
$ref: '#/definitions/Enum'
|
|
39
|
+
oneOfs:
|
|
40
|
+
type: array
|
|
41
|
+
items:
|
|
42
|
+
$ref: '#/definitions/OneOf'
|
|
35
43
|
imports:
|
|
36
44
|
type: array
|
|
37
45
|
items:
|
|
@@ -42,6 +50,46 @@ properties:
|
|
|
42
50
|
- string
|
|
43
51
|
pattern: '^[a-z][a-z0-9-]*$'
|
|
44
52
|
definitions:
|
|
53
|
+
EnumValues:
|
|
54
|
+
type: array
|
|
55
|
+
minItems: 1
|
|
56
|
+
uniqueItems: true
|
|
57
|
+
items:
|
|
58
|
+
type: string
|
|
59
|
+
pattern: '^[A-Za-z0-9][A-Za-z0-9_-]*$'
|
|
60
|
+
Enum:
|
|
61
|
+
type: object
|
|
62
|
+
additionalProperties: false
|
|
63
|
+
required:
|
|
64
|
+
- name
|
|
65
|
+
- values
|
|
66
|
+
properties:
|
|
67
|
+
name:
|
|
68
|
+
type: string
|
|
69
|
+
pattern: '^[A-Z][A-Za-z]*$'
|
|
70
|
+
values:
|
|
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-]*$'
|
|
45
93
|
Attribute:
|
|
46
94
|
type: object
|
|
47
95
|
additionalProperties: false
|
|
@@ -93,6 +141,8 @@ definitions:
|
|
|
93
141
|
items:
|
|
94
142
|
type: string
|
|
95
143
|
pattern: '^[A-Z][A-Za-z-\[\]_, ]*$'
|
|
144
|
+
enumValues:
|
|
145
|
+
$ref: '#/definitions/EnumValues'
|
|
96
146
|
Resource:
|
|
97
147
|
type: object
|
|
98
148
|
additionalProperties: false
|
|
@@ -221,6 +271,8 @@ definitions:
|
|
|
221
271
|
items:
|
|
222
272
|
type: string
|
|
223
273
|
pattern: '^[A-Z][A-Za-z-\[\]_, ]*$'
|
|
274
|
+
enumValues:
|
|
275
|
+
$ref: '#/definitions/EnumValues'
|
|
224
276
|
isRequired:
|
|
225
277
|
type: boolean
|
|
226
278
|
defaultValue:
|
|
@@ -271,6 +323,8 @@ definitions:
|
|
|
271
323
|
items:
|
|
272
324
|
type: string
|
|
273
325
|
pattern: '^[A-Z][A-Za-z-\[\]_, ]*$'
|
|
326
|
+
enumValues:
|
|
327
|
+
$ref: '#/definitions/EnumValues'
|
|
274
328
|
isNullable:
|
|
275
329
|
type: boolean
|
|
276
330
|
isList:
|
|
@@ -24,16 +24,22 @@ from geniespec.renderable_types import ProcessableResource
|
|
|
24
24
|
from geniespec.renderable_types import RenderableApi
|
|
25
25
|
from geniespec.renderable_types import RenderableAttribute
|
|
26
26
|
from geniespec.renderable_types import RenderableEndpoint
|
|
27
|
+
from geniespec.renderable_types import RenderableEnum
|
|
27
28
|
from geniespec.renderable_types import RenderableException
|
|
28
29
|
from geniespec.renderable_types import RenderableField
|
|
29
30
|
from geniespec.renderable_types import RenderableMethod
|
|
31
|
+
from geniespec.renderable_types import RenderableOneOf
|
|
32
|
+
from geniespec.renderable_types import RenderableOneOfMember
|
|
30
33
|
from geniespec.renderable_types import RenderableOperation
|
|
31
34
|
from geniespec.renderable_types import RenderableParameter
|
|
35
|
+
from geniespec.renderable_types import RenderableRawTypes
|
|
32
36
|
from geniespec.renderable_types import RenderableResource
|
|
33
37
|
from geniespec.renderable_types import RenderableString
|
|
34
38
|
from geniespec.renderable_types import RenderableType
|
|
35
39
|
from geniespec.renderable_types import ResourceEndpointComponent
|
|
36
40
|
from geniespec.renderable_types import SingleResourceEndpointComponent
|
|
41
|
+
from geniespec.types import ApiEnum
|
|
42
|
+
from geniespec.types import ApiOneOf
|
|
37
43
|
from geniespec.types import Attribute
|
|
38
44
|
from geniespec.types import BaseTransition
|
|
39
45
|
from geniespec.types import ComplexTypes
|
|
@@ -44,10 +50,12 @@ from geniespec.types import RequestField
|
|
|
44
50
|
from geniespec.types import Resource
|
|
45
51
|
from geniespec.types import ResponseField
|
|
46
52
|
from geniespec.types import ResponseTypes
|
|
53
|
+
from geniespec.types import SpecialTypes
|
|
47
54
|
|
|
48
55
|
SCHEMA_FILE_PATH = pathlib.Path(__file__).parent / 'api_schema.yaml'
|
|
49
56
|
TEMPLATES_DIRECTORY_PATH = pathlib.Path(__file__).parent / 'templates'
|
|
50
57
|
TARGET_LANGUAGE_CODES = {'python-server': 'python', 'typescript-client': 'typescript'}
|
|
58
|
+
FORM_FIELD_RAW_TYPES = frozenset({*PrimitiveTypes.ALL, RenderableRawTypes.ENUM, RenderableRawTypes.FILE} - {PrimitiveTypes.JSON})
|
|
51
59
|
|
|
52
60
|
|
|
53
61
|
class SyntacticValidationException(BadRequestException):
|
|
@@ -106,7 +114,7 @@ class Renderer:
|
|
|
106
114
|
method = transition.method or defaultMethods.get(transition.action.upper(), 'POST')
|
|
107
115
|
return RenderableMethod(method=RenderableString.from_upper_case_string(string=method))
|
|
108
116
|
|
|
109
|
-
def _action_operation(self, transition: BaseTransition, endpoint: RenderableEndpoint, knownTypeNames:
|
|
117
|
+
def _action_operation(self, transition: BaseTransition, endpoint: RenderableEndpoint, knownTypeNames: dict[str, str], defaultAuthentication: str | None, isIndirectAction: bool = False) -> RenderableOperation:
|
|
110
118
|
method = self._transition_to_renderable_method(transition=transition)
|
|
111
119
|
fields = [self._request_field_to_renderable_parameter(requestField=requestField, knownTypeNames=knownTypeNames) for requestField in transition.requestFields]
|
|
112
120
|
isQuery = method.method.upperCaseString == 'GET'
|
|
@@ -126,38 +134,50 @@ class Renderer:
|
|
|
126
134
|
rawType: str,
|
|
127
135
|
isNullable: bool,
|
|
128
136
|
isList: bool,
|
|
129
|
-
knownTypeNames:
|
|
137
|
+
knownTypeNames: dict[str, str],
|
|
130
138
|
listValueTypes: list[str] | None = None,
|
|
131
139
|
mappingKeyType: str | None = None,
|
|
132
140
|
mappingValueTypes: list[str] | None = None,
|
|
141
|
+
enumValues: list[str] | None = None,
|
|
133
142
|
) -> RenderableType:
|
|
134
143
|
listValueTypes = listValueTypes or []
|
|
135
144
|
mappingValueTypes = mappingValueTypes or []
|
|
145
|
+
enumValues = enumValues or []
|
|
136
146
|
if rawType == ComplexTypes.MAPPING:
|
|
137
147
|
if mappingKeyType is None or len(mappingValueTypes) == 0:
|
|
138
148
|
raise KibaException(message='Mapping types require mappingKeyType and mappingValueTypes')
|
|
149
|
+
if enumValues:
|
|
150
|
+
raise KibaException(message='Mapping types cannot define enumValues, use a named enum as the value type')
|
|
139
151
|
keyType = self._type_to_renderable_type(rawType=mappingKeyType, isNullable=False, isList=False, knownTypeNames=knownTypeNames)
|
|
140
|
-
nonNullValueTypes = [valueType for valueType in mappingValueTypes if valueType !=
|
|
152
|
+
nonNullValueTypes = [valueType for valueType in mappingValueTypes if valueType != SpecialTypes.NULL]
|
|
141
153
|
if len(nonNullValueTypes) != 1 or len(nonNullValueTypes) - len(mappingValueTypes) not in (0, -1):
|
|
142
154
|
raise KibaException(message=f'Mapping types currently support one value type with an optional Null: {mappingValueTypes}')
|
|
143
|
-
valueType = self._type_to_renderable_type(rawType=nonNullValueTypes[0], isNullable=
|
|
155
|
+
valueType = self._type_to_renderable_type(rawType=nonNullValueTypes[0], isNullable=SpecialTypes.NULL in mappingValueTypes, isList=False, knownTypeNames=knownTypeNames)
|
|
144
156
|
return MappingRenderableType(isNullable=isNullable, isList=isList, keyType=keyType, valueType=valueType)
|
|
145
|
-
if rawType ==
|
|
157
|
+
if rawType == SpecialTypes.LIST:
|
|
146
158
|
if isList or len(listValueTypes) != 1:
|
|
147
159
|
raise KibaException(message='List types require exactly one listValueTypes entry')
|
|
148
|
-
itemType = self._type_to_renderable_type(rawType=listValueTypes[0], isNullable=False, isList=False, knownTypeNames=knownTypeNames)
|
|
160
|
+
itemType = self._type_to_renderable_type(rawType=listValueTypes[0], isNullable=False, isList=False, knownTypeNames=knownTypeNames, enumValues=enumValues)
|
|
149
161
|
if itemType.isList:
|
|
150
162
|
raise KibaException(message='Nested list types are not supported')
|
|
151
|
-
return
|
|
163
|
+
return itemType.model_copy(update={'isNullable': isNullable, 'isList': True})
|
|
152
164
|
if listValueTypes:
|
|
153
165
|
raise KibaException(message=f'Only List types may define listValueTypes: {rawType}')
|
|
166
|
+
if rawType == SpecialTypes.ENUM:
|
|
167
|
+
if not enumValues:
|
|
168
|
+
raise KibaException(message='Enum types require enumValues')
|
|
169
|
+
return RenderableType(rawType=RenderableRawTypes.ENUM, isNullable=isNullable, isList=isList, enumValues=enumValues)
|
|
170
|
+
if enumValues:
|
|
171
|
+
raise KibaException(message=f'Only Enum types may define enumValues: {rawType}')
|
|
172
|
+
if rawType == SpecialTypes.FILE:
|
|
173
|
+
return RenderableType(rawType=RenderableRawTypes.FILE, isNullable=isNullable, isList=isList)
|
|
154
174
|
if rawType in PrimitiveTypes.ALL:
|
|
155
175
|
return RenderableType(rawType=rawType, isNullable=isNullable, isList=isList)
|
|
156
176
|
if rawType in knownTypeNames:
|
|
157
|
-
return RenderableType(rawType=
|
|
177
|
+
return RenderableType(rawType=knownTypeNames[rawType], isNullable=isNullable, className=RenderableString.from_pascal_case_string(string=rawType), isList=isList)
|
|
158
178
|
raise KibaException(message=f'Unknown type: {rawType}')
|
|
159
179
|
|
|
160
|
-
def _request_field_to_renderable_parameter(self, requestField: RequestField, knownTypeNames:
|
|
180
|
+
def _request_field_to_renderable_parameter(self, requestField: RequestField, knownTypeNames: dict[str, str]) -> RenderableParameter:
|
|
161
181
|
return RenderableParameter(
|
|
162
182
|
name=RenderableString.from_kebab_case_string(string=requestField.name),
|
|
163
183
|
fieldType=self._type_to_renderable_type(
|
|
@@ -168,6 +188,7 @@ class Renderer:
|
|
|
168
188
|
listValueTypes=requestField.listValueTypes,
|
|
169
189
|
mappingKeyType=requestField.mappingKeyType,
|
|
170
190
|
mappingValueTypes=requestField.mappingValueTypes,
|
|
191
|
+
enumValues=requestField.enumValues,
|
|
171
192
|
),
|
|
172
193
|
defaultValue=requestField.defaultValue,
|
|
173
194
|
maxValue=requestField.maxValue,
|
|
@@ -177,7 +198,7 @@ class Renderer:
|
|
|
177
198
|
source=requestField.source,
|
|
178
199
|
)
|
|
179
200
|
|
|
180
|
-
def _response_field_to_renderable_field(self, responseField: ResponseField, knownTypeNames:
|
|
201
|
+
def _response_field_to_renderable_field(self, responseField: ResponseField, knownTypeNames: dict[str, str]) -> RenderableField:
|
|
181
202
|
return RenderableField(
|
|
182
203
|
name=RenderableString.from_kebab_case_string(string=responseField.name),
|
|
183
204
|
fieldType=self._type_to_renderable_type(
|
|
@@ -188,10 +209,11 @@ class Renderer:
|
|
|
188
209
|
listValueTypes=responseField.listValueTypes,
|
|
189
210
|
mappingKeyType=responseField.mappingKeyType,
|
|
190
211
|
mappingValueTypes=responseField.mappingValueTypes,
|
|
212
|
+
enumValues=responseField.enumValues,
|
|
191
213
|
),
|
|
192
214
|
)
|
|
193
215
|
|
|
194
|
-
def _attribute_to_renderable_attribute(self, attribute: Attribute, knownTypeNames:
|
|
216
|
+
def _attribute_to_renderable_attribute(self, attribute: Attribute, knownTypeNames: dict[str, str], isNullable: bool | None = None) -> RenderableAttribute:
|
|
195
217
|
return RenderableAttribute(
|
|
196
218
|
name=RenderableString.from_kebab_case_string(string=attribute.name),
|
|
197
219
|
fieldType=self._type_to_renderable_type(
|
|
@@ -202,6 +224,7 @@ class Renderer:
|
|
|
202
224
|
listValueTypes=attribute.listValueTypes,
|
|
203
225
|
mappingKeyType=attribute.mappingKeyType,
|
|
204
226
|
mappingValueTypes=attribute.mappingValueTypes,
|
|
227
|
+
enumValues=attribute.enumValues,
|
|
205
228
|
),
|
|
206
229
|
defaultValue=attribute.defaultValue,
|
|
207
230
|
isUnique=attribute.isUnique,
|
|
@@ -212,11 +235,18 @@ class Renderer:
|
|
|
212
235
|
isNullable=attribute.isNullable,
|
|
213
236
|
)
|
|
214
237
|
|
|
215
|
-
def _attribute_to_renderable_parameter(self, attribute: Attribute, isRequired: bool, knownTypeNames:
|
|
238
|
+
def _attribute_to_renderable_parameter(self, attribute: Attribute, isRequired: bool, knownTypeNames: dict[str, str], shouldUseDefault: bool = True) -> RenderableParameter:
|
|
216
239
|
return RenderableParameter(
|
|
217
240
|
name=RenderableString.from_kebab_case_string(string=attribute.name),
|
|
218
241
|
fieldType=self._type_to_renderable_type(
|
|
219
|
-
rawType=attribute.fieldType,
|
|
242
|
+
rawType=attribute.fieldType,
|
|
243
|
+
isNullable=attribute.isNullable,
|
|
244
|
+
isList=False,
|
|
245
|
+
knownTypeNames=knownTypeNames,
|
|
246
|
+
listValueTypes=attribute.listValueTypes,
|
|
247
|
+
mappingKeyType=attribute.mappingKeyType,
|
|
248
|
+
mappingValueTypes=attribute.mappingValueTypes,
|
|
249
|
+
enumValues=attribute.enumValues,
|
|
220
250
|
),
|
|
221
251
|
defaultValue=attribute.defaultValue if shouldUseDefault else None,
|
|
222
252
|
isRequired=isRequired,
|
|
@@ -227,7 +257,7 @@ class Renderer:
|
|
|
227
257
|
def _resource_full_name(resource: Resource, name: str) -> str:
|
|
228
258
|
return f'{resource.parentResource or ""}{name}'
|
|
229
259
|
|
|
230
|
-
def _resource_to_renderable_resource(self, resource: Resource, knownTypeNames:
|
|
260
|
+
def _resource_to_renderable_resource(self, resource: Resource, knownTypeNames: dict[str, str]) -> RenderableResource:
|
|
231
261
|
attributes = [self._attribute_to_renderable_attribute(attribute=attribute, knownTypeNames=knownTypeNames) for attribute in resource.attributes]
|
|
232
262
|
return RenderableResource(
|
|
233
263
|
singleName=RenderableString.from_pascal_case_string(string=self._resource_full_name(resource=resource, name=resource.singleName)),
|
|
@@ -239,7 +269,7 @@ class Renderer:
|
|
|
239
269
|
accessibleFrom=resource.accessibleFrom,
|
|
240
270
|
)
|
|
241
271
|
|
|
242
|
-
def _get_processable_resource(self, renderableResource: RenderableResource, allResources: list[RenderableResource], knownTypeNames:
|
|
272
|
+
def _get_processable_resource(self, renderableResource: RenderableResource, allResources: list[RenderableResource], knownTypeNames: dict[str, str]) -> ProcessableResource:
|
|
243
273
|
parent: ProcessableResource.Parent | None = None
|
|
244
274
|
attributeOccurrences: list[ProcessableResource.IndependentAttributeOccurrence] = []
|
|
245
275
|
parentResource = next(iter([otherResource for otherResource in allResources if renderableResource.parentResource and otherResource.singleName.pascalCaseString == renderableResource.parentResource]), None)
|
|
@@ -249,7 +279,7 @@ class Renderer:
|
|
|
249
279
|
parent = ProcessableResource.Parent(processableResource=parentProcessableResource, isOneToOne=isOneToOne)
|
|
250
280
|
for otherResource in allResources:
|
|
251
281
|
for attribute in otherResource.independentAttributes:
|
|
252
|
-
if attribute.fieldType.rawType ==
|
|
282
|
+
if attribute.fieldType.rawType == RenderableRawTypes.CLASS and attribute.fieldType.className == renderableResource.singleName:
|
|
253
283
|
occursInResource = self._get_processable_resource(renderableResource=otherResource, allResources=allResources, knownTypeNames=knownTypeNames)
|
|
254
284
|
occursInAttribute = attribute
|
|
255
285
|
attributeOccurrences.append(ProcessableResource.IndependentAttributeOccurrence(processableResource=occursInResource, attribute=occursInAttribute))
|
|
@@ -328,14 +358,68 @@ class Renderer:
|
|
|
328
358
|
errors.append(f'{name}: stream transitions need a responseField')
|
|
329
359
|
if operation.responseType == ResponseTypes.REDIRECT and operation.responseFields:
|
|
330
360
|
errors.append(f'{name}: redirect transitions cannot have a responseField')
|
|
361
|
+
errors += [f'{name}: response field {field.name.kebabCaseString} cannot be a File' for field in operation.responseFields or [] if field.fieldType.rawType == RenderableRawTypes.FILE]
|
|
362
|
+
if any(parameter.fieldType.rawType == RenderableRawTypes.FILE for parameter in operation.parameters):
|
|
363
|
+
if operation.method.method.upperCaseString != 'POST':
|
|
364
|
+
errors.append(f'{name}: only POST transitions can have File fields')
|
|
365
|
+
if operation.responseType == ResponseTypes.STREAM:
|
|
366
|
+
errors.append(f'{name}: stream transitions cannot have File fields')
|
|
367
|
+
errors += [
|
|
368
|
+
f'{name}: field {parameter.name.kebabCaseString} cannot be sent with File fields, only single primitive, enum and File values can'
|
|
369
|
+
for parameter in operation.parameters
|
|
370
|
+
if parameter.source == FieldSources.REQUEST and (parameter.fieldType.isList or isinstance(parameter.fieldType, MappingRenderableType) or parameter.fieldType.rawType not in FORM_FIELD_RAW_TYPES)
|
|
371
|
+
]
|
|
331
372
|
return errors
|
|
332
373
|
|
|
374
|
+
@staticmethod
|
|
375
|
+
def _file_errors(resources: list[RenderableResource], exceptions: list[RenderableException]) -> list[str]:
|
|
376
|
+
errors = [
|
|
377
|
+
f'Attribute {attribute.name.kebabCaseString} of {resource.singleName.pascalCaseString} cannot be a File'
|
|
378
|
+
for resource in resources
|
|
379
|
+
for attribute in [*resource.attributes, *resource.independentAttributes]
|
|
380
|
+
if attribute.fieldType.rawType == RenderableRawTypes.FILE
|
|
381
|
+
]
|
|
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]
|
|
383
|
+
return errors
|
|
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
|
+
|
|
333
406
|
def build_renderable_api(self, api: KibaApi) -> RenderableApi:
|
|
334
407
|
fullResourceNames = [self._resource_full_name(resource=resource, name=resource.singleName) for resource in api.resources]
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
408
|
+
enumNames = [enum.name for enum in api.enums]
|
|
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]
|
|
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]
|
|
338
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))
|
|
339
423
|
for resource, fullName in zip(api.resources, fullResourceNames, strict=True):
|
|
340
424
|
ancestorNames: list[str] = []
|
|
341
425
|
ancestor = resourcesByName.get(resource.parentResource) if resource.parentResource is not None else None
|
|
@@ -389,6 +473,8 @@ class Renderer:
|
|
|
389
473
|
resources=[],
|
|
390
474
|
operations=renderableOperations,
|
|
391
475
|
exceptions=renderableExceptions,
|
|
476
|
+
enums=[RenderableEnum(name=RenderableString.from_pascal_case_string(string=enum.name), values=enum.values) for enum in api.enums],
|
|
477
|
+
oneOfs=renderableOneOfs,
|
|
392
478
|
)
|
|
393
479
|
renderableResources: list[RenderableResource] = [self._resource_to_renderable_resource(resource=resource, knownTypeNames=knownTypeNames) for resource in api.resources]
|
|
394
480
|
uncoveredTransitions: list[str] = []
|
|
@@ -466,6 +552,7 @@ class Renderer:
|
|
|
466
552
|
message=f'Transitions with no endpoint: {", ".join(uncoveredTransitions)}. Item transitions need a primary attribute, collection transitions need direct access or a parent resource, and CREATE is not supported on one-to-one child resources.'
|
|
467
553
|
)
|
|
468
554
|
operationErrors = [error for operation in renderableApi.operations for error in self._operation_errors(operation=operation)]
|
|
555
|
+
operationErrors += self._file_errors(resources=renderableResources, exceptions=renderableExceptions)
|
|
469
556
|
if operationErrors:
|
|
470
557
|
raise SemanticValidationException(message='\n'.join(operationErrors))
|
|
471
558
|
renderableApi.resources.extend(renderableResources)
|
|
@@ -9,10 +9,13 @@ from geniespec.renderable_types import AttributeEndpointComponent
|
|
|
9
9
|
from geniespec.renderable_types import MappingRenderableType
|
|
10
10
|
from geniespec.renderable_types import RenderableApi
|
|
11
11
|
from geniespec.renderable_types import RenderableEndpointComponent
|
|
12
|
+
from geniespec.renderable_types import RenderableEnum
|
|
12
13
|
from geniespec.renderable_types import RenderableException
|
|
13
14
|
from geniespec.renderable_types import RenderableField
|
|
15
|
+
from geniespec.renderable_types import RenderableOneOf
|
|
14
16
|
from geniespec.renderable_types import RenderableOperation
|
|
15
17
|
from geniespec.renderable_types import RenderableParameter
|
|
18
|
+
from geniespec.renderable_types import RenderableRawTypes
|
|
16
19
|
from geniespec.renderable_types import RenderableResource
|
|
17
20
|
from geniespec.renderable_types import RenderableString
|
|
18
21
|
from geniespec.renderable_types import RenderableType
|
|
@@ -66,13 +69,13 @@ class BaseLanguageDefinition:
|
|
|
66
69
|
def object_type(self, isNullable: bool, className: RenderableString, apiVersion: Version, isLocalReference: bool) -> str:
|
|
67
70
|
raise NotImplementedError
|
|
68
71
|
|
|
69
|
-
def
|
|
72
|
+
def string_type(self, isNullable: bool) -> str:
|
|
70
73
|
raise NotImplementedError
|
|
71
74
|
|
|
72
|
-
def
|
|
75
|
+
def enum_type(self, isNullable: bool, renderableType: RenderableType, apiVersion: Version, isLocalReference: bool) -> str:
|
|
73
76
|
raise NotImplementedError
|
|
74
77
|
|
|
75
|
-
def
|
|
78
|
+
def file_type(self, isNullable: bool) -> str:
|
|
76
79
|
raise NotImplementedError
|
|
77
80
|
|
|
78
81
|
def package_name(self, api: RenderableApi) -> str:
|
|
@@ -87,10 +90,6 @@ class BaseLanguageDefinition:
|
|
|
87
90
|
def is_valid_name(self, name: str) -> bool:
|
|
88
91
|
raise NotImplementedError
|
|
89
92
|
|
|
90
|
-
def _render_one_of_type_string(self, oneOfTypeString: str, isNullable: bool, apiVersion: Version) -> list[str]:
|
|
91
|
-
oneOfTypes = ''.join(oneOfTypeString.replace('OneOf[', '', 1).rsplit(']', 1)).split(', ')
|
|
92
|
-
return [self.render_type(renderableType=RenderableType(rawType=oneOfType, isNullable=isNullable), apiVersion=apiVersion) for oneOfType in oneOfTypes]
|
|
93
|
-
|
|
94
93
|
def _render_list_type_string(self, listTypeString: str, apiVersion: Version) -> str:
|
|
95
94
|
listItemType = ''.join(listTypeString.replace('List[', '', 1).rsplit(']', 1))
|
|
96
95
|
return self.render_type(renderableType=RenderableType(rawType=listItemType, isNullable=False), apiVersion=apiVersion)
|
|
@@ -118,19 +117,15 @@ class BaseLanguageDefinition:
|
|
|
118
117
|
return self.date_type(isNullable=isNullable)
|
|
119
118
|
if yamlType == PrimitiveTypes.JSON:
|
|
120
119
|
return self.json_type(isNullable=isNullable)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# return self.enum_type(isNullable=isNullable, values=[RenderableString.from_kebab_case_string(string=value) for value in values])
|
|
126
|
-
# if yamlType.startswith(PrimitiveTypes.ONE_OF):
|
|
127
|
-
# subTypes = self._render_one_of_type_string(oneOfTypeString=yamlType, isNullable=isNullable, apiVersion=apiVersion)
|
|
128
|
-
# return self.one_of_type(isNullable=isNullable, subTypes=subTypes)
|
|
120
|
+
if yamlType == RenderableRawTypes.ENUM:
|
|
121
|
+
return self.enum_type(isNullable=isNullable, renderableType=renderableType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
122
|
+
if yamlType == RenderableRawTypes.FILE:
|
|
123
|
+
return self.file_type(isNullable=isNullable)
|
|
129
124
|
if yamlType == ComplexTypes.MAPPING and isinstance(renderableType, MappingRenderableType):
|
|
130
125
|
keyType = self.render_type(renderableType=renderableType.keyType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
131
126
|
valueType = self.render_type(renderableType=renderableType.valueType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
132
127
|
return self.mapping_type(isNullable=isNullable, keyType=keyType, valueType=valueType)
|
|
133
|
-
if yamlType
|
|
128
|
+
if yamlType in (RenderableRawTypes.CLASS, RenderableRawTypes.ONE_OF) and renderableType.className is not None:
|
|
134
129
|
return self.object_type(isNullable=isNullable, className=renderableType.className, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
135
130
|
raise KibaException(f'Failed to render: {type(renderableType)} {renderableType}')
|
|
136
131
|
|
|
@@ -171,6 +166,23 @@ class BaseLanguageDefinition:
|
|
|
171
166
|
def exception_class_name(self, exception: RenderableException, apiVersion: Version) -> str:
|
|
172
167
|
return f'{self.pascal_name(name=exception.name)}V{apiVersion.major}'
|
|
173
168
|
|
|
169
|
+
def enum_class_name(self, enum: RenderableEnum, apiVersion: Version) -> str:
|
|
170
|
+
return f'{self.pascal_name(name=enum.name)}V{apiVersion.major}'
|
|
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
|
+
@staticmethod
|
|
179
|
+
def is_inline_enum(renderableType: RenderableType) -> bool:
|
|
180
|
+
return renderableType.rawType == RenderableRawTypes.ENUM and renderableType.className is None
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def is_file_operation(operation: RenderableOperation) -> bool:
|
|
184
|
+
return any(parameter.fieldType.rawType == RenderableRawTypes.FILE for parameter in operation.parameters)
|
|
185
|
+
|
|
174
186
|
def type(self, renderableType: RenderableType, apiVersion: Version, isLocalReference: bool = False) -> str:
|
|
175
187
|
return self.render_type(renderableType=renderableType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
176
188
|
|
|
@@ -327,16 +339,22 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
327
339
|
typeName = f'{className.pascalCaseString}V{apiVersion.major}' if isLocalReference else f'resources.{className.pascalCaseString}V{apiVersion.major}'
|
|
328
340
|
return f'{typeName} | None' if isNullable else typeName
|
|
329
341
|
|
|
330
|
-
def one_of_type(self, isNullable: bool, subTypes: list[str]) -> str:
|
|
331
|
-
fieldTypes = ' | '.join(subTypes)
|
|
332
|
-
return f'{fieldTypes} | None' if isNullable else f'Union[{fieldTypes} | None'
|
|
333
|
-
|
|
334
342
|
def string_type(self, isNullable: bool) -> str:
|
|
335
343
|
return 'str | None' if isNullable else 'str'
|
|
336
344
|
|
|
337
|
-
def enum_type(self, isNullable: bool,
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
def enum_type(self, isNullable: bool, renderableType: RenderableType, apiVersion: Version, isLocalReference: bool) -> str:
|
|
346
|
+
if renderableType.className is None:
|
|
347
|
+
typeName = self.enum_literal(values=renderableType.enumValues)
|
|
348
|
+
else:
|
|
349
|
+
typeName = f'{renderableType.className.pascalCaseString}V{apiVersion.major}' if isLocalReference else f'resources.{renderableType.className.pascalCaseString}V{apiVersion.major}'
|
|
350
|
+
return f'{typeName} | None' if isNullable else typeName
|
|
351
|
+
|
|
352
|
+
@staticmethod
|
|
353
|
+
def enum_literal(values: list[str]) -> str:
|
|
354
|
+
return f'typing.Literal[{", ".join(repr(value) for value in values)}]'
|
|
355
|
+
|
|
356
|
+
def file_type(self, isNullable: bool) -> str:
|
|
357
|
+
return 'UploadedFile | None' if isNullable else 'UploadedFile'
|
|
340
358
|
|
|
341
359
|
def package_name(self, api: RenderableApi) -> str:
|
|
342
360
|
return f'{self.snake_name(name=api.serviceName)}_api'
|
|
@@ -393,6 +411,19 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
393
411
|
def resources_require_json(self, api: RenderableApi) -> bool:
|
|
394
412
|
return any(self._contains_json(renderableType=attribute.fieldType) for resource in api.resources for attribute in resource.attributes)
|
|
395
413
|
|
|
414
|
+
def resources_require_typing(self, api: RenderableApi) -> bool:
|
|
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})]'
|
|
419
|
+
|
|
420
|
+
def operations_require_typing(self, api: RenderableApi) -> bool:
|
|
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 []]
|
|
422
|
+
return any(self.is_inline_enum(renderableType=fieldType) for fieldType in fieldTypes)
|
|
423
|
+
|
|
424
|
+
def requires_uploaded_file(self, api: RenderableApi) -> bool:
|
|
425
|
+
return any(self.is_file_operation(operation=operation) for operation in api.operations)
|
|
426
|
+
|
|
396
427
|
def operations_require_json(self, api: RenderableApi) -> bool:
|
|
397
428
|
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 []]
|
|
398
429
|
return any(self._contains_json(renderableType=fieldType) for fieldType in fieldTypes)
|
|
@@ -516,6 +547,19 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
516
547
|
def string_type(self, isNullable: bool) -> str:
|
|
517
548
|
return self._nullable(typeName='string', isNullable=isNullable)
|
|
518
549
|
|
|
550
|
+
def enum_type(self, isNullable: bool, renderableType: RenderableType, apiVersion: Version, isLocalReference: bool) -> str:
|
|
551
|
+
if renderableType.className is None:
|
|
552
|
+
return self._nullable(typeName=f'({self.enum_union(values=renderableType.enumValues)})', isNullable=isNullable)
|
|
553
|
+
typeName = f'{renderableType.className.pascalCaseString}V{apiVersion.major}'
|
|
554
|
+
return self._nullable(typeName=typeName if isLocalReference else f'Resources.{typeName}', isNullable=isNullable)
|
|
555
|
+
|
|
556
|
+
@staticmethod
|
|
557
|
+
def enum_union(values: list[str]) -> str:
|
|
558
|
+
return ' | '.join(f"'{value}'" for value in values)
|
|
559
|
+
|
|
560
|
+
def file_type(self, isNullable: bool) -> str:
|
|
561
|
+
return self._nullable(typeName='File', isNullable=isNullable)
|
|
562
|
+
|
|
519
563
|
def package_name(self, api: RenderableApi) -> str:
|
|
520
564
|
return f'{self.kebab_name(name=api.serviceName)}-api'
|
|
521
565
|
|
|
@@ -566,7 +610,14 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
566
610
|
return f'on{self.pascal_name(name=responseField.name)}'
|
|
567
611
|
|
|
568
612
|
def references_resources(self, renderableTypes: list[RenderableType]) -> bool:
|
|
569
|
-
|
|
613
|
+
valueTypes = [self._value_type(renderableType=renderableType) for renderableType in renderableTypes]
|
|
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'
|
|
618
|
+
|
|
619
|
+
def _value_type(self, renderableType: RenderableType) -> RenderableType:
|
|
620
|
+
return self._value_type(renderableType=renderableType.valueType) if isinstance(renderableType, MappingRenderableType) else renderableType
|
|
570
621
|
|
|
571
622
|
def requires_date_parsing(self, renderableTypes: list[RenderableType]) -> bool:
|
|
572
623
|
return any(self._contains_raw_type(renderableType=renderableType, rawType=rawType) for renderableType in renderableTypes for rawType in (PrimitiveTypes.DATE, PrimitiveTypes.DATETIME))
|
|
@@ -580,6 +631,18 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
580
631
|
def body_parameter_types(self, api: RenderableApi) -> list[RenderableType]:
|
|
581
632
|
return [parameter.fieldType for operation in self.client_operations(api=api) for parameter in self.body_parameters(operation=operation)]
|
|
582
633
|
|
|
634
|
+
def requires_request_data(self, api: RenderableApi) -> bool:
|
|
635
|
+
return any(self.body_parameters(operation=operation) and not self.is_file_operation(operation=operation) for operation in self.client_operations(api=api))
|
|
636
|
+
|
|
637
|
+
def form_value(self, renderableType: RenderableType, value: str) -> str:
|
|
638
|
+
if renderableType.rawType == RenderableRawTypes.FILE:
|
|
639
|
+
return value
|
|
640
|
+
if renderableType.rawType == PrimitiveTypes.DATETIME:
|
|
641
|
+
return f'dateToString({value})'
|
|
642
|
+
if renderableType.rawType == PrimitiveTypes.DATE:
|
|
643
|
+
return f"dateToString({value}, 'yyyy-MM-dd')"
|
|
644
|
+
return f'String({value})'
|
|
645
|
+
|
|
583
646
|
def _contains_raw_type(self, renderableType: RenderableType, rawType: str) -> bool:
|
|
584
647
|
if isinstance(renderableType, MappingRenderableType):
|
|
585
648
|
return self._contains_raw_type(renderableType=renderableType.valueType, rawType=rawType)
|
|
@@ -597,6 +660,9 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
597
660
|
valueTypeName = self.type(renderableType=renderableType.valueType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
598
661
|
convertedEntry = self.from_object(renderableType=renderableType.valueType, value='entry', apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
599
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)'
|
|
600
666
|
if renderableType.rawType == 'class' and renderableType.className is not None:
|
|
601
667
|
return f'{self.type(renderableType=renderableType, apiVersion=apiVersion, isLocalReference=isLocalReference)}.fromObject({value} as RawObject)'
|
|
602
668
|
if renderableType.rawType == PrimitiveTypes.STRING:
|
|
@@ -609,12 +675,14 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
609
675
|
return f'dateFromString(String({value}))'
|
|
610
676
|
if renderableType.rawType == PrimitiveTypes.JSON:
|
|
611
677
|
return f'{value} as {self.type(renderableType=renderableType, apiVersion=apiVersion, isLocalReference=isLocalReference)}'
|
|
678
|
+
if renderableType.rawType == RenderableRawTypes.ENUM:
|
|
679
|
+
return f'String({value}) as {self.type(renderableType=renderableType, apiVersion=apiVersion, isLocalReference=isLocalReference)}'
|
|
612
680
|
raise KibaException(f'Failed to render conversion from object for: {renderableType}')
|
|
613
681
|
|
|
614
682
|
def _is_serialized_as_is(self, renderableType: RenderableType) -> bool:
|
|
615
683
|
if isinstance(renderableType, MappingRenderableType):
|
|
616
684
|
return self._is_serialized_as_is(renderableType=renderableType.valueType)
|
|
617
|
-
return renderableType.rawType not in ('class', PrimitiveTypes.DATE, PrimitiveTypes.DATETIME)
|
|
685
|
+
return renderableType.rawType not in ('class', RenderableRawTypes.ONE_OF, PrimitiveTypes.DATE, PrimitiveTypes.DATETIME)
|
|
618
686
|
|
|
619
687
|
def to_object(self, renderableType: RenderableType, value: str, apiVersion: Version, isOptional: bool = False, isLocalReference: bool = False) -> str:
|
|
620
688
|
if self._is_serialized_as_is(renderableType=renderableType):
|
|
@@ -630,7 +698,7 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
630
698
|
valueTypeName = self.type(renderableType=renderableType.valueType, apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
631
699
|
convertedEntry = self.to_object(renderableType=renderableType.valueType, value='entry', apiVersion=apiVersion, isLocalReference=isLocalReference)
|
|
632
700
|
return f'Object.fromEntries(Object.entries({value}).map(([key, entry]: [string, {valueTypeName}]): [string, unknown] => [key, {convertedEntry}]))'
|
|
633
|
-
if renderableType.rawType
|
|
701
|
+
if renderableType.rawType in ('class', RenderableRawTypes.ONE_OF):
|
|
634
702
|
return f'{value}.toObject()'
|
|
635
703
|
if renderableType.rawType == PrimitiveTypes.DATETIME:
|
|
636
704
|
return f'dateToString({value})'
|
|
@@ -87,11 +87,35 @@ class RenderableString(BaseModel):
|
|
|
87
87
|
return cls(string=string.split(separator))
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
class RenderableRawTypes:
|
|
91
|
+
CLASS = 'class'
|
|
92
|
+
ENUM = 'enum'
|
|
93
|
+
FILE = 'file'
|
|
94
|
+
ONE_OF = 'oneOf'
|
|
95
|
+
|
|
96
|
+
|
|
90
97
|
class RenderableType(BaseModel):
|
|
91
98
|
rawType: str
|
|
92
99
|
isNullable: bool
|
|
93
100
|
isList: bool = False
|
|
94
101
|
className: RenderableString | None = None
|
|
102
|
+
enumValues: list[str] = []
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RenderableEnum(BaseModel):
|
|
106
|
+
name: RenderableString
|
|
107
|
+
values: list[str]
|
|
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]
|
|
95
119
|
|
|
96
120
|
|
|
97
121
|
class MappingRenderableType(RenderableType):
|
|
@@ -225,6 +249,8 @@ class RenderableApi(BaseModel):
|
|
|
225
249
|
resources: list[RenderableResource]
|
|
226
250
|
operations: list[RenderableOperation]
|
|
227
251
|
exceptions: list[RenderableException]
|
|
252
|
+
enums: list[RenderableEnum] = []
|
|
253
|
+
oneOfs: list[RenderableOneOf] = []
|
|
228
254
|
|
|
229
255
|
|
|
230
256
|
class ProcessableResource(BaseModel):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
2
|
import collections.abc
|
|
3
3
|
import datetime
|
|
4
|
-
{% if target.requires_optional_field_default(api=api) %}
|
|
4
|
+
{% if target.requires_optional_field_default(api=api) or target.operations_require_typing(api=api) %}
|
|
5
5
|
import typing
|
|
6
6
|
{% endif %}
|
|
7
7
|
|
|
@@ -16,6 +16,9 @@ from pydantic import BaseModel
|
|
|
16
16
|
{% if target.requires_undefined(api=api) %}
|
|
17
17
|
from core.util.typing_util import UNDEFINED, UpdateValue
|
|
18
18
|
{% endif %}
|
|
19
|
+
{% if target.requires_uploaded_file(api=api) %}
|
|
20
|
+
from core.api.uploaded_file import UploadedFile
|
|
21
|
+
{% endif %}
|
|
19
22
|
|
|
20
23
|
from . import resources
|
|
21
24
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import abc
|
|
3
3
|
import datetime
|
|
4
4
|
import collections.abc
|
|
5
|
+
{% if target.operations_require_typing(api=api) %}
|
|
6
|
+
import typing
|
|
7
|
+
{% endif %}
|
|
5
8
|
|
|
6
9
|
{% if target.operations_require_json(api=api) %}
|
|
7
10
|
import pydantic
|
|
@@ -11,6 +14,9 @@ from . import resources
|
|
|
11
14
|
{% if target.requires_undefined(api=api) %}
|
|
12
15
|
from core.util.typing_util import UNDEFINED, UpdateValue
|
|
13
16
|
{% endif %}
|
|
17
|
+
{% if target.requires_uploaded_file(api=api) %}
|
|
18
|
+
from core.api.uploaded_file import UploadedFile
|
|
19
|
+
{% endif %}
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
class {{ target.internal_class_name(api=api) }}(abc.ABC):
|
|
@@ -3,12 +3,21 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import collections.abc
|
|
5
5
|
import datetime
|
|
6
|
+
{% if target.resources_require_typing(api=api) %}
|
|
7
|
+
import typing
|
|
8
|
+
{% endif %}
|
|
6
9
|
|
|
7
|
-
{% if target.resources_require_json(api=api) %}
|
|
10
|
+
{% if target.resources_require_json(api=api) or api.oneOfs %}
|
|
8
11
|
import pydantic
|
|
9
12
|
{% endif %}
|
|
10
13
|
from pydantic import BaseModel
|
|
11
14
|
|
|
15
|
+
{% for enum in api.enums %}
|
|
16
|
+
{{ target.enum_class_name(enum=enum, apiVersion=api.version) }} = {{ target.enum_literal(values=enum.values) }}
|
|
17
|
+
{% if loop.last %}
|
|
18
|
+
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% endfor %}
|
|
12
21
|
{% for resource in api.resources %}
|
|
13
22
|
|
|
14
23
|
class {{ target.resource_class_name(resource=resource, apiVersion=api.version) }}(BaseModel):
|
|
@@ -20,3 +29,9 @@ class {{ target.resource_class_name(resource=resource, apiVersion=api.version) }
|
|
|
20
29
|
{% endfor %}
|
|
21
30
|
|
|
22
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 %}
|
|
@@ -21,7 +21,14 @@ export class {{ target.client_class_name(api=api) }} extends ServiceClient {
|
|
|
21
21
|
{% if not loop.first %}
|
|
22
22
|
|
|
23
23
|
{% endif %}
|
|
24
|
-
{% if target.
|
|
24
|
+
{% if target.is_file_operation(operation=operation) %}
|
|
25
|
+
public {{ target.operation_name(operation=operation) }} = async ({{ arguments | join(', ') }}): Promise<{{ target.type(renderableType=responseField.fieldType, apiVersion=api.version) if responseField else 'void' }}> => {
|
|
26
|
+
{% if responseField %}const response = {% endif %}await this.requester.makeFormRequest(`${this.baseUrl}/{{ path }}`, {{ request }}.toFormData());
|
|
27
|
+
{% if responseField %}
|
|
28
|
+
return Endpoints.{{ className }}Response.fromObject(JSON.parse(response.content)).{{ target.identifier(name=responseField.name) }};
|
|
29
|
+
{% endif %}
|
|
30
|
+
};
|
|
31
|
+
{% elif target.is_streaming(operation=operation) %}
|
|
25
32
|
public {{ target.operation_name(operation=operation) }} = async ({{ arguments | join(', ') }}): Promise<void> => {
|
|
26
33
|
await this.makeStreamingRequest(RestMethod.{{ target.http_method(operation=operation) }}, {{ pathLiteral }}, {{ request }}, Endpoints.{{ className }}Response, (response: Endpoints.{{ className }}Response): void => {
|
|
27
34
|
{{ target.stream_callback_name(operation=operation) }}(response.{{ target.identifier(name=responseField.name) }});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{% if target.requires_date_parsing(renderableTypes=responseTypes) %}{% set _ = coreImports.append('dateFromString') %}{% endif %}
|
|
6
6
|
{% if target.requires_date_parsing(renderableTypes=bodyTypes) %}{% set _ = coreImports.append('dateToString') %}{% endif %}
|
|
7
7
|
{% set _ = coreImports.append('RawObject') %}
|
|
8
|
-
{% if
|
|
8
|
+
{% if target.requires_request_data(api=api) %}{% set _ = coreImports.append('RequestData') %}{% endif %}
|
|
9
9
|
{% if responseTypes %}{% set _ = coreImports.append('ResponseData') %}{% endif %}
|
|
10
10
|
import { {{ coreImports | join(', ') }} } from '@kibalabs/core';
|
|
11
11
|
{% if target.references_resources(renderableTypes=responseTypes + bodyTypes) %}
|
|
@@ -24,6 +24,29 @@ export interface {{ className }}Params {
|
|
|
24
24
|
{% endfor %}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
{% if target.is_file_operation(operation=operation) %}
|
|
28
|
+
export class {{ className }}Request {
|
|
29
|
+
public constructor(
|
|
30
|
+
readonly params: {{ className }}Params,
|
|
31
|
+
) { }
|
|
32
|
+
|
|
33
|
+
public toFormData = (): FormData => {
|
|
34
|
+
const formData = new FormData();
|
|
35
|
+
{% for parameter in bodyParameters %}
|
|
36
|
+
{% set parameterName = target.identifier(name=parameter.name) %}
|
|
37
|
+
{% set value = 'this.params.' ~ parameterName %}
|
|
38
|
+
{% if target.is_parameter_required(operation=operation, parameter=parameter) and not target.parameter_field_type(operation=operation, parameter=parameter).isNullable %}
|
|
39
|
+
formData.append('{{ parameterName }}', {{ target.form_value(renderableType=parameter.fieldType, value=value) }});
|
|
40
|
+
{% else %}
|
|
41
|
+
if ({{ value }} != null) {
|
|
42
|
+
formData.append('{{ parameterName }}', {{ target.form_value(renderableType=parameter.fieldType, value=value) }});
|
|
43
|
+
}
|
|
44
|
+
{% endif %}
|
|
45
|
+
{% endfor %}
|
|
46
|
+
return formData;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
{% else %}
|
|
27
50
|
export class {{ className }}Request extends RequestData {
|
|
28
51
|
public constructor(
|
|
29
52
|
readonly params: {{ className }}Params,
|
|
@@ -40,6 +63,7 @@ export class {{ className }}Request extends RequestData {
|
|
|
40
63
|
};
|
|
41
64
|
}
|
|
42
65
|
{% endif %}
|
|
66
|
+
{% endif %}
|
|
43
67
|
{% if responseField %}
|
|
44
68
|
|
|
45
69
|
export class {{ className }}Response extends ResponseData {
|
|
@@ -6,6 +6,10 @@ import { RawObject } from '@kibalabs/core';
|
|
|
6
6
|
{% endif %}
|
|
7
7
|
|
|
8
8
|
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
9
|
+
{% for enum in api.enums %}
|
|
10
|
+
|
|
11
|
+
export type {{ target.enum_class_name(enum=enum, apiVersion=api.version) }} = {{ target.enum_union(values=enum.values) }};
|
|
12
|
+
{% endfor %}
|
|
9
13
|
{% for resource in api.resources %}
|
|
10
14
|
{% set className = target.resource_class_name(resource=resource, apiVersion=api.version) %}
|
|
11
15
|
|
|
@@ -43,3 +47,20 @@ export class {{ className }} {
|
|
|
43
47
|
{% endif %}
|
|
44
48
|
}
|
|
45
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 %}
|
|
@@ -35,12 +35,31 @@ class ComplexTypes:
|
|
|
35
35
|
ALL: ClassVar[list[str]] = [MAPPING]
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
class SpecialTypes:
|
|
39
|
+
LIST = 'List'
|
|
40
|
+
ENUM = 'Enum'
|
|
41
|
+
FILE = 'File'
|
|
42
|
+
NULL = 'Null'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ApiEnum(BaseModel):
|
|
46
|
+
name: str
|
|
47
|
+
values: list[str]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ApiOneOf(BaseModel):
|
|
51
|
+
name: str
|
|
52
|
+
discriminator: str
|
|
53
|
+
types: list[str]
|
|
54
|
+
|
|
55
|
+
|
|
38
56
|
class Attribute(BaseModel):
|
|
39
57
|
name: str
|
|
40
58
|
fieldType: str = pydantic.Field(alias='type')
|
|
41
59
|
listValueTypes: list[str] = []
|
|
42
60
|
mappingKeyType: str | None = None
|
|
43
61
|
mappingValueTypes: list[str] = []
|
|
62
|
+
enumValues: list[str] = []
|
|
44
63
|
isPrimary: bool = False
|
|
45
64
|
isUnique: bool = False
|
|
46
65
|
shouldDisableGetEndpoint: bool = False
|
|
@@ -59,6 +78,7 @@ class ResponseField(BaseModel):
|
|
|
59
78
|
listValueTypes: list[str] = []
|
|
60
79
|
mappingKeyType: str | None = None
|
|
61
80
|
mappingValueTypes: list[str] = []
|
|
81
|
+
enumValues: list[str] = []
|
|
62
82
|
isNullable: bool = False
|
|
63
83
|
isList: bool = False
|
|
64
84
|
|
|
@@ -69,6 +89,7 @@ class RequestField(BaseModel):
|
|
|
69
89
|
listValueTypes: list[str] = []
|
|
70
90
|
mappingKeyType: str | None = None
|
|
71
91
|
mappingValueTypes: list[str] = []
|
|
92
|
+
enumValues: list[str] = []
|
|
72
93
|
isRequired: bool = False
|
|
73
94
|
isDeprecated: bool = False
|
|
74
95
|
defaultValue: str | int | float | bool | None = None
|
|
@@ -139,6 +160,8 @@ class KibaApi(BaseModel):
|
|
|
139
160
|
resources: list[Resource] = []
|
|
140
161
|
transitions: list[RootTransition] = []
|
|
141
162
|
exceptions: list[ApiException] = []
|
|
163
|
+
enums: list[ApiEnum] = []
|
|
164
|
+
oneOfs: list[ApiOneOf] = []
|
|
142
165
|
defaultAuthentication: str | None = None
|
|
143
166
|
|
|
144
167
|
@property
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|