Skip to content

Commit c6eacb3

Browse files
committed
handle maps with nested models
1 parent 3e4c87e commit c6eacb3

File tree

6 files changed

+93
-0
lines changed

6 files changed

+93
-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
}
@@ -945,6 +956,7 @@ class ExtendedCodegenModel extends CodegenModel {
945956
public List<CodegenProperty> ectoFields = new ArrayList<>();
946957
public List<CodegenProperty> ectoEmbeds = new ArrayList<>();
947958
public List<CodegenProperty> ectoEnums = new ArrayList<>();
959+
public List<CodegenProperty> ectoMaps = new ArrayList<>();
948960
public List<CodegenProperty> requiredEctoFields = new ArrayList<>();
949961

950962
public ExtendedCodegenModel(CodegenModel cm) {
@@ -1009,6 +1021,9 @@ public ExtendedCodegenModel(CodegenModel cm) {
10091021
if (var.isEnum || var.isEnumRef) {
10101022
this.ectoEnums.add(var);
10111023
}
1024+
if (var.isMap && !var.isFreeFormObject && var.additionalProperties != null && var.additionalProperties.isModel) {
1025+
this.ectoMaps.add(var);
1026+
}
10121027
} else {
10131028
this.ectoEmbeds.add(var);
10141029
}
@@ -1018,6 +1033,8 @@ public ExtendedCodegenModel(CodegenModel cm) {
10181033

10191034
class ExtendedCodegenProperty extends CodegenProperty {
10201035
public String enumBaseType;
1036+
public String mapValueType;
1037+
public boolean isMapWithSchema;
10211038

10221039
public ExtendedCodegenProperty(CodegenProperty cp) {
10231040
super();
@@ -1110,6 +1127,17 @@ public ExtendedCodegenProperty(CodegenProperty cp) {
11101127
this.enumBaseType = "String.t";
11111128
}
11121129
}
1130+
// For map properties, extract the model name from additionalProperties
1131+
if (cp.isMap && cp.additionalProperties != null) {
1132+
// Check if the map has a schema (model) or is a free-form map
1133+
if (cp.additionalProperties.isModel) {
1134+
// Extract just the model name without the full type declaration
1135+
this.mapValueType = cp.additionalProperties.complexType;
1136+
this.isMapWithSchema = true;
1137+
} else {
1138+
this.isMapWithSchema = false;
1139+
}
1140+
}
11131141
}
11141142

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