Skip to content

Commit

Permalink
rust: Update codegen templates to support struct enums (#1754)
Browse files Browse the repository at this point in the history
  • Loading branch information
svix-jplatte authored Feb 26, 2025
2 parents 7afde6b + 4c7ac25 commit f8a5cb7
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 53 deletions.
7 changes: 1 addition & 6 deletions regen_openapi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -eo pipefail

OPENAPI_GIT_REV='98dbc5b090a5c8d72fe50962ee04b46fb9d7db20'
OPENAPI_GIT_REV='272125558d6ac4718bdc87b1652e5d4122b69f19'

if [ -n "$1" ]; then
curl "$1" | python -m json.tool > lib-openapi.json
Expand Down Expand Up @@ -34,11 +34,6 @@ fi

# Remove APIs we may not (yet) want to expose
rm rust/src/api/{environment,health}.rs

# Remove .codegen.json files, its purpose is fulfilled already:
# - The expected git rev of the tool is encoded at the top of this file.
# - The lib-openapi.json used as input is also committed to this repo.
rm rust/src/{api,models}/.codegen.json
)

cd $(dirname "$0")
Expand Down
2 changes: 2 additions & 0 deletions rust/templates/component_type.rs.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
{% include "types/string_enum.rs.jinja" -%}
{% elif type.kind == "integer_enum" -%}
{% include "types/integer_enum.rs.jinja" -%}
{% elif type.kind == "struct_enum" -%}
{% include "types/struct_enum.rs.jinja" -%}
{% else -%}
compile_error!("{{ type.kind }} types are not supported by this codegen template");
{% endif %}
48 changes: 1 addition & 47 deletions rust/templates/types/struct.rs.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,7 @@ use super::{
{{ doc_comment }}
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct {{ type.name | to_upper_camel_case }} {
{% for field in type.fields %}
{% if field.description is defined -%}
{{ field.description | to_doc_comment(style="rust") }}
{# we currently use String for date-time params, for backwards compat -#}
{# document the format so it's not _that_ awkward -#}
{% if field.type.is_datetime() -%}
///
/// RFC3339 date string.
{% endif -%}
{% endif -%}

{% if field.deprecated -%}
#[deprecated]
{% endif -%}

{% if field.name != field.name | to_snake_case -%}
#[serde(rename = "{{ field.name }}")]
{% endif -%}

{#
we only have defaults on optional fields now, and the old codegen
was not doing anything with them, so leave them alone here as well,
at least for now
-#}
{# {% if field.default is defined and field.default is not none -%}
#[serde(default = "{{ field.name | to_snake_case }}_default")]
{% endif -%} -#}
{% if field.type.is_datetime() -%}
{% set field_ty = "String" -%}
{% else -%}
{% set field_ty = field.type.to_rust() -%}
{% endif -%}

{% if not field.required or field.nullable -%}
{# only for patch requests, if the field is both non-required
and nullable, use JsOption -#}
{% if type.name is endingwith "Patch" and field.nullable -%}
{% set field_ty %}JsOption<{{ field_ty }}>{% endset -%}
#[serde(default, skip_serializing_if = "JsOption::is_undefined")]
{% else -%}
{% set field_ty %}Option<{{ field_ty }}>{% endset -%}
#[serde(skip_serializing_if = "Option::is_none")]
{% endif -%}
{% endif -%}

pub {{ field.name | to_snake_case }}: {{ field_ty }},
{% endfor %}
{% include "types/struct_fields.rs.jinja" -%}
}

impl {{ type.name | to_upper_camel_case }} {
Expand Down
38 changes: 38 additions & 0 deletions rust/templates/types/struct_enum.rs.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use serde::{Deserialize, Serialize};

use super::{
{% for c in referenced_components -%}
{{ c | to_snake_case }}::{{ c | to_upper_camel_case }},
{% endfor -%}
};

{% set type_name = type.name | to_upper_camel_case -%}
{% if type.fields | length > 0 -%}
{% set enum_type_name %}{{ type_name }}{{ type.content_field | to_upper_camel_case }}{% endset -%}

{{ doc_comment }}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct {{ type_name }} {
{% include "types/struct_fields.rs.jinja" %}

#[serde(flatten)]
{% set enum_field_name = type.content_field | to_snake_case %}
{% if type.content_field != enum_field_name -%}
#[serde(rename = "{{ type.content_field }}")]
{% endif -%}
pub {{ enum_field_name }}: {{ enum_type_name }},
}
{% else -%}
{% set enum_type_name = type_name %}
{% endif %}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "{{ type.discriminator_field }}", content = "{{ type.content_field }}")]
pub enum {{ enum_type_name }} {
{% for variant in type.variants -%}
{{ variant.name | to_upper_camel_case -}}
{% if variant.schema_ref is defined -%}
({{ variant.schema_ref | to_upper_camel_case }})
{%- endif %},
{% endfor -%}
}
47 changes: 47 additions & 0 deletions rust/templates/types/struct_fields.rs.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{% for field in type.fields %}
{% if field.description is defined -%}
{{ field.description | to_doc_comment(style="rust") }}
{# we currently use String for date-time params, for backwards compat -#}
{# document the format so it's not _that_ awkward -#}
{% if field.type.is_datetime() -%}
///
/// RFC3339 date string.
{% endif -%}
{% endif -%}

{% if field.deprecated -%}
#[deprecated]
{% endif -%}

{% if field.name != field.name | to_snake_case -%}
#[serde(rename = "{{ field.name }}")]
{% endif -%}

{#
we only have defaults on optional fields now, and the old codegen
was not doing anything with them, so leave them alone here as well,
at least for now
-#}
{# {% if field.default is defined and field.default is not none -%}
#[serde(default = "{{ field.name | to_snake_case }}_default")]
{% endif -%} -#}
{% if field.type.is_datetime() -%}
{% set field_ty = "String" -%}
{% else -%}
{% set field_ty = field.type.to_rust() -%}
{% endif -%}

{% if not field.required or field.nullable -%}
{# only for patch requests, if the field is both non-required
and nullable, use JsOption -#}
{% if type.name is endingwith "Patch" and field.nullable -%}
{% set field_ty %}JsOption<{{ field_ty }}>{% endset -%}
#[serde(default, skip_serializing_if = "JsOption::is_undefined")]
{% else -%}
{% set field_ty %}Option<{{ field_ty }}>{% endset -%}
#[serde(skip_serializing_if = "Option::is_none")]
{% endif -%}
{% endif -%}

pub {{ field.name | to_snake_case }}: {{ field_ty }},
{% endfor %}

0 comments on commit f8a5cb7

Please sign in to comment.