From e26098c1cc5f9dcc2aae5131cf50b8f37b18f9b7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 4 Mar 2025 13:35:58 +0100 Subject: [PATCH] js: Update codegen templates to support struct enums --- javascript/src/models/endpointHeadersOut.ts | 1 + javascript/src/models/endpointMessageOut.ts | 1 + .../src/models/eventTypeImportOpenApiIn.ts | 1 + javascript/templates/component_type.ts.jinja | 2 + javascript/templates/types/macros.ts.jinja | 70 ++++++++++++++ javascript/templates/types/struct.ts.jinja | 91 ++----------------- .../templates/types/struct_enum.ts.jinja | 84 +++++++++++++++++ .../templates/types/struct_fields.ts.jinja | 10 ++ 8 files changed, 176 insertions(+), 84 deletions(-) create mode 100644 javascript/templates/types/macros.ts.jinja create mode 100644 javascript/templates/types/struct_enum.ts.jinja create mode 100644 javascript/templates/types/struct_fields.ts.jinja diff --git a/javascript/src/models/endpointHeadersOut.ts b/javascript/src/models/endpointHeadersOut.ts index f92c7dca2..c0032c325 100644 --- a/javascript/src/models/endpointHeadersOut.ts +++ b/javascript/src/models/endpointHeadersOut.ts @@ -1,5 +1,6 @@ // this file is @generated /* eslint @typescript-eslint/no-explicit-any: 0 */ + /** * The value of the headers is returned in the `headers` field. * diff --git a/javascript/src/models/endpointMessageOut.ts b/javascript/src/models/endpointMessageOut.ts index a08ff1096..8f7655a85 100644 --- a/javascript/src/models/endpointMessageOut.ts +++ b/javascript/src/models/endpointMessageOut.ts @@ -1,6 +1,7 @@ // this file is @generated /* eslint @typescript-eslint/no-explicit-any: 0 */ import { MessageStatus, MessageStatusSerializer } from "./messageStatus"; + /** A model containing information on a given message plus additional fields on the last attempt for that message. */ export interface EndpointMessageOut { /** List of free-form identifiers that endpoints can filter by */ diff --git a/javascript/src/models/eventTypeImportOpenApiIn.ts b/javascript/src/models/eventTypeImportOpenApiIn.ts index 73ed87cf5..7ae5032cb 100644 --- a/javascript/src/models/eventTypeImportOpenApiIn.ts +++ b/javascript/src/models/eventTypeImportOpenApiIn.ts @@ -1,5 +1,6 @@ // this file is @generated /* eslint @typescript-eslint/no-explicit-any: 0 */ + /** * Import a list of event types from webhooks defined in an OpenAPI spec. * diff --git a/javascript/templates/component_type.ts.jinja b/javascript/templates/component_type.ts.jinja index 5b6236f1f..8f3c80c93 100644 --- a/javascript/templates/component_type.ts.jinja +++ b/javascript/templates/component_type.ts.jinja @@ -10,4 +10,6 @@ {% include "types/string_enum.ts.jinja" -%} {% elif type.kind == "integer_enum" -%} {% include "types/integer_enum.ts.jinja" -%} +{% elif type.kind == "struct_enum" -%} + {% include "types/struct_enum.ts.jinja" -%} {% endif %} diff --git a/javascript/templates/types/macros.ts.jinja b/javascript/templates/types/macros.ts.jinja new file mode 100644 index 000000000..80cd29aaa --- /dev/null +++ b/javascript/templates/types/macros.ts.jinja @@ -0,0 +1,70 @@ +{% macro field_from_json(field_expr, type, field_required) %} + {%- if type.is_datetime() -%} + new Date({{ field_expr }}) + {%- elif type.is_schema_ref() -%} + {%- if not field_required -%} + {{ field_expr }} ? {{ type.to_js() }}Serializer._fromJsonObject({{ field_expr }}): undefined + {%- else -%} + {{ type.to_js() }}Serializer._fromJsonObject({{ field_expr }}) + {%- endif %} + {%- elif type.is_list() or type.is_set() -%} + {{ field_expr }} + {%- set inner_t = type.inner_type() -%} + {%- if inner_t.is_datetime() + or inner_t.is_schema_ref() + or inner_t.is_list() + or inner_t.is_set() + or inner_t.is_map() -%} + .map((item: {{ inner_t.to_js() }}) => {{ field_from_json("item", inner_t, true) }}) + {%- endif -%} + {%- elif type.is_map() -%} + {%- set value_t = type.value_type() -%} + {%- if value_t.is_datetime() + or value_t.is_schema_ref() + or value_t.is_list() + or value_t.is_set() + or value_t.is_map() -%} + Object.fromEntries(Object.entries({{ field_expr }}).map( + (item : {{ inner_t.to_js() }}) => [item[0], {{ field_from_json("item[1]", value_t, true) }}] + )) + {%- else -%} + {{ field_expr }} + {%- endif -%} + {%- else -%} + {{ field_expr }} + {%- endif -%} +{% endmacro -%} + +{% macro field_to_json(field_expr, type, field_required) %} + {%- if type.is_schema_ref() -%} + {%- if not field_required -%} + {{ field_expr }} ? {{ type.to_js() }}Serializer._toJsonObject({{ field_expr }}) : undefined + {%- else -%} + {{ type.to_js() }}Serializer._toJsonObject({{ field_expr }}) + {%- endif -%} + {%- elif type.is_list() or type.is_set() -%} + {{ field_expr }} + {%- set inner_t = type.inner_type() -%} + {%- if inner_t.is_schema_ref() + or inner_t.is_list() + or inner_t.is_set() + or inner_t.is_map() -%} + {%- if not field_required -%}?{% endif -%} + .map((item) => {{ field_to_json("item", inner_t, true) }}) + {%- endif -%} + {%- elif type.is_map() -%} + {%- set value_t = type.value_type() -%} + {%- if value_t.is_schema_ref() + or value_t.is_list() + or value_t.is_set() + or value_t.is_map() -%} + Object.fromEntries(Object.entries({{ field_expr }}).map( + (item) => [item[0], {{ field_to_json("item[1]", value_t, true) }}] + )) + {%- else -%} + {{ field_expr }} + {%- endif -%} + {%- else -%} + {{ field_expr }} + {%- endif -%} +{% endmacro -%} diff --git a/javascript/templates/types/struct.ts.jinja b/javascript/templates/types/struct.ts.jinja index 775621b24..25f99c2ed 100644 --- a/javascript/templates/types/struct.ts.jinja +++ b/javascript/templates/types/struct.ts.jinja @@ -1,3 +1,4 @@ +{% from "types/macros.ts.jinja" import field_from_json, field_to_json -%} {% for c in referenced_components -%} import { {{ c | to_upper_camel_case }}, @@ -5,93 +6,15 @@ import { } from './{{ c | to_lower_camel_case }}'; {% endfor -%} -{% macro field_from_json(field_expr, type, field_required) %} - {%- if type.is_datetime() -%} - new Date({{ field_expr }}) - {%- elif type.is_schema_ref() -%} - {%- if not field_required -%} - {{ field_expr }} ? {{ type.to_js() }}Serializer._fromJsonObject({{ field_expr }}): undefined - {%- else -%} - {{ type.to_js() }}Serializer._fromJsonObject({{ field_expr }}) - {%- endif %} - {%- elif type.is_list() or type.is_set() -%} - {{ field_expr }} - {%- set inner_t = type.inner_type() -%} - {%- if inner_t.is_datetime() - or inner_t.is_schema_ref() - or inner_t.is_list() - or inner_t.is_set() - or inner_t.is_map() -%} - .map((item: {{ inner_t.to_js() }}) => {{ field_from_json("item", inner_t, true) }}) - {%- endif -%} - {%- elif type.is_map() -%} - {%- set value_t = type.value_type() -%} - {%- if value_t.is_datetime() - or value_t.is_schema_ref() - or value_t.is_list() - or value_t.is_set() - or value_t.is_map() -%} - Object.fromEntries(Object.entries({{ field_expr }}).map( - (item : {{ inner_t.to_js() }}) => [item[0], {{ field_from_json("item[1]", value_t, true) }}] - )) - {%- else -%} - {{ field_expr }} - {%- endif -%} - {%- else -%} - {{ field_expr }} - {%- endif -%} -{% endmacro -%} - -{% macro field_to_json(field_expr, type, field_required) %} - {%- if type.is_schema_ref() -%} - {%- if not field_required -%} - {{ field_expr }} ? {{ type.to_js() }}Serializer._toJsonObject({{ field_expr }}) : undefined - {%- else -%} - {{ type.to_js() }}Serializer._toJsonObject({{ field_expr }}) - {%- endif -%} - {%- elif type.is_list() or type.is_set() -%} - {{ field_expr }} - {%- set inner_t = type.inner_type() -%} - {%- if inner_t.is_schema_ref() - or inner_t.is_list() - or inner_t.is_set() - or inner_t.is_map() -%} - {%- if not field_required -%}?{% endif -%} - .map((item) => {{ field_to_json("item", inner_t, true) }}) - {%- endif -%} - {%- elif type.is_map() -%} - {%- set value_t = type.value_type() -%} - {%- if value_t.is_schema_ref() - or value_t.is_list() - or value_t.is_set() - or value_t.is_map() -%} - Object.fromEntries(Object.entries({{ field_expr }}).map( - (item) => [item[0], {{ field_to_json("item[1]", value_t, true) }}] - )) - {%- else -%} - {{ field_expr }} - {%- endif -%} - {%- else -%} - {{ field_expr }} - {%- endif -%} -{% endmacro -%} +{% set type_name = type.name | to_upper_camel_case %} {{ doc_comment }} -export interface {{ type.name | to_upper_camel_case }} { - {% for field in type.fields -%} - {% if field.description is defined -%} - {{ field.description | with_javadoc_deprecation(field.deprecated) | to_doc_comment(style="js") }} - {% endif -%} - {% set field_lhs = field.name | to_lower_camel_case -%} - {% if not field.required %}{% set field_lhs %}{{ field_lhs }}?{% endset %}{% endif -%} - {% set ty = field.type.to_js() -%} - {% if field.nullable %}{% set ty %}{{ ty }} | null{% endset %}{% endif -%} - {{ field_lhs }}: {{ ty }}; - {% endfor -%} +export interface {{ type_name }} { + {% include "types/struct_fields.ts.jinja" -%} } -export const {{ type.name | to_upper_camel_case }}Serializer = { - _fromJsonObject(object: any): {{ type.name | to_upper_camel_case }} { +export const {{ type_name }}Serializer = { + _fromJsonObject(object: any): {{ type_name }} { return { {% for field in type.fields -%} {% set field_expr %}object['{{ field.name }}']{% endset -%} @@ -100,7 +23,7 @@ export const {{ type.name | to_upper_camel_case }}Serializer = { }; }, - _toJsonObject(self: {{ type.name | to_upper_camel_case }}): any { + _toJsonObject(self: {{ type_name }}): any { return { {% for field in type.fields -%} {% set field_expr %}self.{{ field.name | to_lower_camel_case }}{% endset -%} diff --git a/javascript/templates/types/struct_enum.ts.jinja b/javascript/templates/types/struct_enum.ts.jinja new file mode 100644 index 000000000..a7b8d7e41 --- /dev/null +++ b/javascript/templates/types/struct_enum.ts.jinja @@ -0,0 +1,84 @@ +{% from "types/macros.ts.jinja" import field_from_json, field_to_json -%} +{% for c in referenced_components -%} +import { + {{ c | to_upper_camel_case }}, + {{ c | to_upper_camel_case }}Serializer, +} from './{{ c | to_lower_camel_case }}'; +{% endfor -%} + +{% set type_name = type.name | to_upper_camel_case -%} +{% set disc_field_name = type.discriminator_field | to_lower_camel_case -%} +{% set content_field_name = type.content_field | to_lower_camel_case -%} + +interface _{{ type_name }}Fields { + {% include "types/struct_fields.ts.jinja" -%} +} + +{% for variant in type.variants %} +interface {{ type_name }}{{ variant.name | to_upper_camel_case }} { + {{ disc_field_name }}: '{{ variant.name }}'; + {% if variant.schema_ref is defined -%} + {{ content_field_name }}: {{ variant.schema_ref | to_upper_camel_case }}; + {%- endif %} +} +{% endfor %} + +{{ doc_comment }} +export type {{ type_name }} = _{{ type_name }}Fields & ( + {%- for variant in type.variants -%} + | {{ type_name }}{{ variant.name | to_upper_camel_case }} + {% endfor -%} +); + +export const {{ type.name | to_upper_camel_case }}Serializer = { + _fromJsonObject(object: any): {{ type.name | to_upper_camel_case }} { + const {{ disc_field_name }} = object['{{ type.discriminator_field }}']; + let {{ content_field_name }}; + switch ({{ disc_field_name }}) { + {%- for variant in type.variants %} + {% if variant.schema_ref is defined -%} + case '{{ variant.name }}': + {{ content_field_name }} = + {{ variant.schema_ref | to_upper_camel_case }}Serializer._fromJsonObject( + object['{{ type.content_field }}'] + ); + break; + {% endif -%} + {% endfor -%} + } + + return { + {{ disc_field_name }}, + {{ content_field_name }}, + {% for field in type.fields -%} + {% set field_expr %}object['{{ field.name }}']{% endset -%} + {{ field.name | to_lower_camel_case }}: {{ field_from_json(field_expr, field.type, field.required) }}, + {% endfor -%} + }; + }, + + _toJsonObject(self: {{ type.name | to_upper_camel_case }}): any { + let {{ content_field_name }}; + switch (self.{{ disc_field_name }}) { + {%- for variant in type.variants %} + {% if variant.schema_ref is defined %} + case '{{ variant.name }}': + {{ content_field_name }} = + {{ variant.schema_ref | to_upper_camel_case }}Serializer._toJsonObject( + self.{{ content_field_name }} + ); + break; + {%- endif %} + {% endfor -%} + } + + return { + '{{ type.discriminator_field }}': self.{{ disc_field_name }}, + '{{ type.content_field }}': {{ content_field_name }}, + {% for field in type.fields -%} + {% set field_expr %}self.{{ field.name | to_lower_camel_case }}{% endset -%} + '{{ field.name }}': {{ field_to_json(field_expr, field.type, field.required) }}, + {% endfor -%} + }; + } +} diff --git a/javascript/templates/types/struct_fields.ts.jinja b/javascript/templates/types/struct_fields.ts.jinja new file mode 100644 index 000000000..4a0b7ccde --- /dev/null +++ b/javascript/templates/types/struct_fields.ts.jinja @@ -0,0 +1,10 @@ +{% for field in type.fields -%} + {% if field.description is defined -%} + {{ field.description | with_javadoc_deprecation(field.deprecated) | to_doc_comment(style="js") }} + {% endif -%} + {% set field_lhs = field.name | to_lower_camel_case -%} + {% if not field.required %}{% set field_lhs %}{{ field_lhs }}?{% endset %}{% endif -%} + {% set ty = field.type.to_js() -%} + {% if field.nullable %}{% set ty %}{{ ty }} | null{% endset %}{% endif -%} + {{ field_lhs }}: {{ ty }}; +{% endfor -%}