Skip to content

Commit 5392595

Browse files
committed
feat: maps with nested models
1 parent 93a3335 commit 5392595

File tree

6 files changed

+101
-0
lines changed

6 files changed

+101
-0
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
360360
supportingFiles.add(new SupportingFile("request_builder.ex.mustache",
361361
sourceFolder(),
362362
"request_builder.ex"));
363+
364+
supportingFiles.add(new SupportingFile("ecto_utils.ex.mustache",
365+
sourceFolder(),
366+
"ecto_utils.ex"));
363367
}
364368

365369
@Override
@@ -454,6 +458,13 @@ public CodegenModel fromModel(String name, Schema model) {
454458
for (CodegenProperty field : ectoEnums) {
455459
ecm.ectoEnums.add(new ExtendedCodegenProperty(field));
456460
}
461+
462+
463+
List<CodegenProperty> ectoMaps = new ArrayList<>(ecm.ectoMaps);
464+
ecm.ectoMaps.clear();
465+
for (CodegenProperty field : ectoMaps) {
466+
ecm.ectoMaps.add(new ExtendedCodegenProperty(field));
467+
}
457468

458469
return ecm;
459470
}
@@ -939,6 +950,7 @@ class ExtendedCodegenModel extends CodegenModel {
939950
public List<CodegenProperty> ectoFields = new ArrayList<>();
940951
public List<CodegenProperty> ectoEmbeds = new ArrayList<>();
941952
public List<CodegenProperty> ectoEnums = new ArrayList<>();
953+
public List<CodegenProperty> ectoMaps = new ArrayList<>();
942954
public List<CodegenProperty> requiredEctoFields = new ArrayList<>();
943955

944956
public ExtendedCodegenModel(CodegenModel cm) {
@@ -1003,6 +1015,9 @@ public ExtendedCodegenModel(CodegenModel cm) {
10031015
if (var.isEnum || var.isEnumRef) {
10041016
this.ectoEnums.add(var);
10051017
}
1018+
if (var.isMap && !var.isFreeFormObject && var.additionalProperties != null && var.additionalProperties.isModel) {
1019+
this.ectoMaps.add(var);
1020+
}
10061021
} else {
10071022
this.ectoEmbeds.add(var);
10081023
}
@@ -1012,6 +1027,8 @@ public ExtendedCodegenModel(CodegenModel cm) {
10121027

10131028
class ExtendedCodegenProperty extends CodegenProperty {
10141029
public String enumBaseType;
1030+
public String mapValueType;
1031+
public boolean isMapWithSchema;
10151032

10161033
public ExtendedCodegenProperty(CodegenProperty cp) {
10171034
super();
@@ -1103,6 +1120,17 @@ public ExtendedCodegenProperty(CodegenProperty cp) {
11031120
this.enumBaseType = "String.t";
11041121
}
11051122
}
1123+
// For map properties, extract the model name from additionalProperties
1124+
if (cp.isMap && cp.additionalProperties != null) {
1125+
// Check if the map has a schema (model) or is a free-form map
1126+
if (cp.additionalProperties.isModel) {
1127+
// Extract just the model name without the full type declaration
1128+
this.mapValueType = cp.additionalProperties.complexType;
1129+
this.isMapWithSchema = true;
1130+
} else {
1131+
this.isMapWithSchema = false;
1132+
}
1133+
}
11061134
}
11071135

11081136
public String ectoType() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{{>licenseInfo}}
2+
defmodule {{moduleName}}.EctoUtils do
3+
def cast_nested_map(changeset, field, schema) do
4+
case Map.get(changeset.changes, field) do
5+
nil ->
6+
changeset
7+
8+
values ->
9+
{result_map, errors} =
10+
Enum.reduce(values, {%{}, []}, fn {key, params}, {acc, errs} ->
11+
changeset = schema.changeset(struct(schema), params)
12+
13+
case Ecto.Changeset.apply_action(changeset, changeset.action || :insert) do
14+
{:ok, struct} ->
15+
{Map.put(acc, key, struct), errs}
16+
17+
{:error, nested_cs} ->
18+
{Map.put(acc, key, nested_cs), [{key, nested_cs} | errs]}
19+
end
20+
end)
21+
22+
changeset =
23+
Ecto.Changeset.put_change(changeset, field, result_map)
24+
25+
if errors == [] do
26+
changeset
27+
else
28+
Ecto.Changeset.add_error(changeset, field, "contains invalid nested entries")
29+
|> Map.put(:valid?, false)
30+
end
31+
end
32+
end
33+
end

modules/openapi-generator/src/main/resources/elixir/model.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
{{#ectoEnums}}
3030
|> Ecto.Changeset.validate_inclusion({{#atom}}{{&baseName}}{{/atom}}, [{{#allowableValues}}{{#values}}{{#quoteIfString}}{{.}}{{/quoteIfString}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}])
3131
{{/ectoEnums}}
32+
{{#ectoMaps}}
33+
|> {{moduleName}}.EctoUtils.cast_nested_map({{#atom}}{{&baseName}}{{/atom}}, {{&moduleName}}.Model.{{{mapValueType}}})
34+
{{/ectoMaps}}
3235
{{#ectoEmbeds}}
3336
|> Ecto.Changeset.cast_embed({{#atom}}{{&baseName}}{{/atom}}{{#required}}, required: true{{/required}})
3437
{{/ectoEmbeds}}

samples/client/petstore/elixir/.openapi-generator/FILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ lib/openapi_petstore/api/pet.ex
1111
lib/openapi_petstore/api/store.ex
1212
lib/openapi_petstore/api/user.ex
1313
lib/openapi_petstore/connection.ex
14+
lib/openapi_petstore/ecto_utils.ex
1415
lib/openapi_petstore/model/_foo_get_default_response.ex
1516
lib/openapi_petstore/model/_special_model_name_.ex
1617
lib/openapi_petstore/model/additional_properties_class.ex
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# NOTE: This file is auto generated by OpenAPI Generator 7.14.0-SNAPSHOT (https://openapi-generator.tech).
2+
# Do not edit this file manually.
3+
4+
defmodule OpenapiPetstore.EctoUtils do
5+
def cast_nested_map(changeset, field, schema) do
6+
case Map.get(changeset.changes, field) do
7+
nil ->
8+
changeset
9+
10+
values ->
11+
{result_map, errors} =
12+
Enum.reduce(values, {%{}, []}, fn {key, params}, {acc, errs} ->
13+
changeset = schema.changeset(struct(schema), params)
14+
15+
case Ecto.Changeset.apply_action(changeset, changeset.action || :insert) do
16+
{:ok, struct} ->
17+
{Map.put(acc, key, struct), errs}
18+
19+
{:error, nested_cs} ->
20+
{Map.put(acc, key, nested_cs), [{key, nested_cs} | errs]}
21+
end
22+
end)
23+
24+
changeset =
25+
Ecto.Changeset.put_change(changeset, field, result_map)
26+
27+
if errors == [] do
28+
changeset
29+
else
30+
Ecto.Changeset.add_error(changeset, field, "contains invalid nested entries")
31+
|> Map.put(:valid?, false)
32+
end
33+
end
34+
end
35+
end

samples/client/petstore/elixir/lib/openapi_petstore/model/mixed_properties_and_additional_properties_class.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule OpenapiPetstore.Model.MixedPropertiesAndAdditionalPropertiesClass do
2626
struct
2727
|> Ecto.Changeset.cast(params, [:uuid, :dateTime, :map])
2828
|> Ecto.Changeset.validate_required([])
29+
|> OpenapiPetstore.EctoUtils.cast_nested_map(:map, OpenapiPetstore.Model.Animal)
2930
end
3031
end
3132

0 commit comments

Comments
 (0)