Skip to content

Commit a148e43

Browse files
DDtKeyoscarr-reyes
authored andcommitted
fix(rust): oneOf generation for client (OpenAPITools#17915)
* fix(rust): discriminator mapping to serde rename Discriminator mapping has been ignored in some cases. Even existing samples had wrong definition in some cases This PR addresses this * fix(rust): `oneOf` generation for client Solves OpenAPITools#17869 and OpenAPITools#17896 and also includes unmerged $17898 Unfortunately it affects quite a lot of code, but we can see that only client-side models were affected by re-generation. I tried to split this PR to several, but they're really coupled and hard to create a chain of PRs. * fix: indentation in `impl Default` * missing fixes * fix: correct typeDeclaration with unaliased schema * style: improve indentation for models * fix: user toModelName for aliases of oneOf * refactor: unify `getTypeDeclaration` for rust * cover the case when `mapping` has the same `ref` for different mapping names * test: add test for previous change * style: remove extra qualified path to models * add some comments * fix(build): use method of `List` instead of specific for `LinkedList`
1 parent 47fa951 commit a148e43

File tree

335 files changed

+3663
-1099
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

335 files changed

+3663
-1099
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
generatorName: rust
2+
outputDir: samples/client/others/rust/hyper/oneOf-array-map
3+
library: hyper
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/oneOfArrayMapImport.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/rust
6+
additionalProperties:
7+
supportAsync: false
8+
packageName: oneof-array-map-hyper
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
generatorName: rust
2+
outputDir: samples/client/others/rust/hyper/oneOf-reuseRef
3+
library: hyper
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/oneOf_reuseRef.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/rust
6+
additionalProperties:
7+
supportAsync: false
8+
packageName: oneof-reuse-ref-hyper
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
generatorName: rust
2+
outputDir: samples/client/others/rust/reqwest/oneOf-array-map
3+
library: reqwest
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/oneOfArrayMapImport.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/rust
6+
additionalProperties:
7+
supportAsync: false
8+
packageName: oneof-array-map-reqwest
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
generatorName: rust
2+
outputDir: samples/client/others/rust/reqwest/oneOf-reuseRef
3+
library: reqwest
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/oneOf_reuseRef.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/rust
6+
additionalProperties:
7+
supportAsync: false
8+
packageName: oneof-reuse-ref-reqwest

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

+82-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
import com.google.common.annotations.VisibleForTesting;
44
import com.google.common.base.Strings;
5-
import org.openapitools.codegen.CodegenConfig;
6-
import org.openapitools.codegen.CodegenProperty;
7-
import org.openapitools.codegen.DefaultCodegen;
8-
import org.openapitools.codegen.GeneratorLanguage;
5+
import io.swagger.v3.oas.models.media.ArraySchema;
6+
import io.swagger.v3.oas.models.media.FileSchema;
7+
import io.swagger.v3.oas.models.media.Schema;
8+
import org.openapitools.codegen.*;
9+
import org.openapitools.codegen.utils.ModelUtils;
910
import org.openapitools.codegen.utils.StringUtils;
1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;
@@ -232,6 +233,83 @@ public String sanitizeIdentifier(String name, CasingType casingType, String esca
232233
return name;
233234
}
234235

236+
@Override
237+
public String getTypeDeclaration(Schema p) {
238+
if (ModelUtils.isArraySchema(p)) {
239+
ArraySchema ap = (ArraySchema) p;
240+
Schema inner = ap.getItems();
241+
String innerType = getTypeDeclaration(inner);
242+
return typeMapping.get("array") + "<" + innerType + ">";
243+
} else if (ModelUtils.isMapSchema(p)) {
244+
Schema inner = ModelUtils.getAdditionalProperties(p);
245+
String innerType = getTypeDeclaration(inner);
246+
StringBuilder typeDeclaration = new StringBuilder(typeMapping.get("map")).append("<").append(typeMapping.get("string")).append(", ");
247+
typeDeclaration.append(innerType).append(">");
248+
return typeDeclaration.toString();
249+
} else if (!org.apache.commons.lang3.StringUtils.isEmpty(p.get$ref())) {
250+
String datatype;
251+
try {
252+
datatype = toModelName(ModelUtils.getSimpleRef(p.get$ref()));
253+
datatype = "models::" + toModelName(datatype);
254+
} catch (Exception e) {
255+
LOGGER.warn("Error obtaining the datatype from schema (model):{}. Datatype default to Object", p);
256+
datatype = "Object";
257+
LOGGER.error(e.getMessage(), e);
258+
}
259+
return datatype;
260+
} else if (p instanceof FileSchema) {
261+
return typeMapping.get("file");
262+
}
263+
264+
String oasType = getSchemaType(p);
265+
if (typeMapping.containsKey(oasType)) {
266+
return typeMapping.get(oasType);
267+
}
268+
269+
if (typeMapping.containsValue(oasType)) {
270+
return oasType;
271+
}
272+
273+
if (languageSpecificPrimitives.contains(oasType)) {
274+
return oasType;
275+
}
276+
277+
return "models::" + toModelName(oasType);
278+
}
279+
280+
@Override
281+
public CodegenModel fromModel(String name, Schema model) {
282+
LOGGER.trace("Creating model from schema: {}", model);
283+
284+
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
285+
CodegenModel mdl = super.fromModel(name, model);
286+
287+
mdl.vendorExtensions.put("x-upper-case-name", name.toUpperCase(Locale.ROOT));
288+
if (!org.apache.commons.lang3.StringUtils.isEmpty(model.get$ref())) {
289+
Schema schema = allDefinitions.get(ModelUtils.getSimpleRef(model.get$ref()));
290+
mdl.dataType = typeMapping.get(schema.getType());
291+
}
292+
if (ModelUtils.isArraySchema(model)) {
293+
if (typeMapping.containsKey(mdl.arrayModelType)) {
294+
mdl.arrayModelType = typeMapping.get(mdl.arrayModelType);
295+
} else {
296+
mdl.arrayModelType = toModelName(mdl.arrayModelType);
297+
}
298+
} else if ((!mdl.anyOf.isEmpty()) || (!mdl.oneOf.isEmpty())) {
299+
mdl.dataType = getSchemaType(model);
300+
}
301+
302+
Schema additionalProperties = ModelUtils.getAdditionalProperties(model);
303+
304+
if (additionalProperties != null) {
305+
mdl.additionalPropertiesType = getTypeDeclaration(additionalProperties);
306+
}
307+
308+
LOGGER.trace("Created model: {}", mdl);
309+
310+
return mdl;
311+
}
312+
235313
@Override
236314
public String toVarName(String name) {
237315
// obtain the name from nameMapping directly if provided

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

-69
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import io.swagger.v3.oas.models.Operation;
2222
import io.swagger.v3.oas.models.info.Info;
2323
import io.swagger.v3.oas.models.media.ArraySchema;
24-
import io.swagger.v3.oas.models.media.FileSchema;
2524
import io.swagger.v3.oas.models.media.Schema;
2625
import io.swagger.v3.oas.models.parameters.Parameter;
2726
import io.swagger.v3.oas.models.parameters.RequestBody;
@@ -703,41 +702,6 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
703702
return codegenParameter;
704703
}
705704

706-
@Override
707-
public String getTypeDeclaration(Schema p) {
708-
if (ModelUtils.isArraySchema(p)) {
709-
ArraySchema ap = (ArraySchema) p;
710-
Schema inner = ap.getItems();
711-
String innerType = getTypeDeclaration(inner);
712-
return typeMapping.get("array") + "<" + innerType + ">";
713-
} else if (ModelUtils.isMapSchema(p)) {
714-
Schema inner = ModelUtils.getAdditionalProperties(p);
715-
String innerType = getTypeDeclaration(inner);
716-
StringBuilder typeDeclaration = new StringBuilder(typeMapping.get("map")).append("<").append(typeMapping.get("string")).append(", ");
717-
typeDeclaration.append(innerType).append(">");
718-
return typeDeclaration.toString();
719-
} else if (!StringUtils.isEmpty(p.get$ref())) {
720-
String datatype;
721-
try {
722-
datatype = p.get$ref();
723-
724-
if (datatype.indexOf("#/components/schemas/") == 0) {
725-
datatype = toModelName(datatype.substring("#/components/schemas/".length()));
726-
datatype = "models::" + datatype;
727-
}
728-
} catch (Exception e) {
729-
LOGGER.warn("Error obtaining the datatype from schema (model):{}. Datatype default to Object", p);
730-
datatype = "Object";
731-
LOGGER.error(e.getMessage(), e);
732-
}
733-
return datatype;
734-
} else if (p instanceof FileSchema) {
735-
return typeMapping.get("File");
736-
}
737-
738-
return super.getTypeDeclaration(p);
739-
}
740-
741705
@Override
742706
public String toInstantiationType(Schema p) {
743707
if (ModelUtils.isArraySchema(p)) {
@@ -752,39 +716,6 @@ public String toInstantiationType(Schema p) {
752716
}
753717
}
754718

755-
@Override
756-
public CodegenModel fromModel(String name, Schema model) {
757-
LOGGER.trace("Creating model from schema: {}", model);
758-
759-
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
760-
CodegenModel mdl = super.fromModel(name, model);
761-
762-
mdl.vendorExtensions.put("x-upper-case-name", name.toUpperCase(Locale.ROOT));
763-
if (!StringUtils.isEmpty(model.get$ref())) {
764-
Schema schema = allDefinitions.get(ModelUtils.getSimpleRef(model.get$ref()));
765-
mdl.dataType = typeMapping.get(schema.getType());
766-
}
767-
if (ModelUtils.isArraySchema(model)) {
768-
if (typeMapping.containsKey(mdl.arrayModelType)) {
769-
mdl.arrayModelType = typeMapping.get(mdl.arrayModelType);
770-
} else {
771-
mdl.arrayModelType = toModelName(mdl.arrayModelType);
772-
}
773-
} else if ((!mdl.anyOf.isEmpty()) || (!mdl.oneOf.isEmpty())) {
774-
mdl.dataType = getSchemaType(model);
775-
}
776-
777-
Schema additionalProperties = ModelUtils.getAdditionalProperties(model);
778-
779-
if (additionalProperties != null) {
780-
mdl.additionalPropertiesType = getTypeDeclaration(additionalProperties);
781-
}
782-
783-
LOGGER.trace("Created model: {}", mdl);
784-
785-
return mdl;
786-
}
787-
788719
@Override
789720
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> bundle) {
790721
generateYAMLSpecFile(bundle);

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

+68-42
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919

2020
import com.samskivert.mustache.Mustache;
2121
import com.samskivert.mustache.Template;
22-
import io.swagger.v3.oas.models.media.ArraySchema;
23-
import io.swagger.v3.oas.models.media.Schema;
24-
import io.swagger.v3.oas.models.media.StringSchema;
22+
import io.swagger.v3.oas.models.media.*;
2523
import io.swagger.v3.parser.util.SchemaTypeUtil;
2624
import joptsimple.internal.Strings;
2725
import org.openapitools.codegen.*;
@@ -41,8 +39,7 @@
4139
import java.math.BigDecimal;
4240
import java.math.BigInteger;
4341
import java.util.*;
44-
45-
import static org.openapitools.codegen.utils.StringUtils.camelize;
42+
import java.util.stream.Collectors;
4643

4744
public class RustClientCodegen extends AbstractRustCodegen implements CodegenConfig {
4845
private final Logger LOGGER = LoggerFactory.getLogger(RustClientCodegen.class);
@@ -149,11 +146,13 @@ public RustClientCodegen() {
149146
typeMapping.clear();
150147
typeMapping.put("integer", "i32");
151148
typeMapping.put("long", "i64");
152-
typeMapping.put("number", "f32");
149+
typeMapping.put("number", "f64");
153150
typeMapping.put("float", "f32");
154151
typeMapping.put("double", "f64");
155152
typeMapping.put("boolean", "bool");
156153
typeMapping.put("string", "String");
154+
typeMapping.put("array", "Vec");
155+
typeMapping.put("map", "std::collections::HashMap");
157156
typeMapping.put("UUID", "uuid::Uuid");
158157
typeMapping.put("URI", "String");
159158
typeMapping.put("date", "string");
@@ -205,11 +204,72 @@ public RustClientCodegen() {
205204
setLibrary(REQWEST_LIBRARY);
206205
}
207206

207+
@Override
208+
public CodegenModel fromModel(String name, Schema model) {
209+
CodegenModel mdl = super.fromModel(name, model);
210+
211+
// set correct names and baseNames to oneOf in composed-schema to use as enum variant names & mapping
212+
if (mdl.getComposedSchemas() != null && mdl.getComposedSchemas().getOneOf() != null
213+
&& !mdl.getComposedSchemas().getOneOf().isEmpty()) {
214+
215+
List<CodegenProperty> newOneOfs = mdl.getComposedSchemas().getOneOf().stream()
216+
.map(CodegenProperty::clone)
217+
.collect(Collectors.toList());
218+
List<Schema> schemas = ModelUtils.getInterfaces(model);
219+
if (newOneOfs.size() != schemas.size()) {
220+
// For safety reasons, this should never happen unless there is an error in the code
221+
throw new RuntimeException("oneOf size does not match the model");
222+
}
223+
224+
Map<String, String> refsMapping = Optional.ofNullable(model.getDiscriminator())
225+
.map(Discriminator::getMapping).orElse(Collections.emptyMap());
226+
227+
// Reverse mapped references to use as baseName for oneOF, but different keys may point to the same $ref.
228+
// Thus, we group them by the value
229+
Map<String, List<String>> mappedNamesByRef = refsMapping.entrySet().stream()
230+
.collect(Collectors.groupingBy(Map.Entry::getValue,
231+
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
232+
));
233+
234+
for (int i = 0; i < newOneOfs.size(); i++) {
235+
CodegenProperty oneOf = newOneOfs.get(i);
236+
Schema schema = schemas.get(i);
237+
238+
if (mappedNamesByRef.containsKey(schema.get$ref())) {
239+
// prefer mapped names if present
240+
// remove mapping not in order not to reuse for the next occurrence of the ref
241+
List<String> names = mappedNamesByRef.get(schema.get$ref());
242+
String mappedName = names.remove(0);
243+
oneOf.setBaseName(mappedName);
244+
oneOf.setName(toModelName(mappedName));
245+
} else if (!org.apache.commons.lang3.StringUtils.isEmpty(schema.get$ref())) {
246+
// use $ref if it's reference
247+
String refName = ModelUtils.getSimpleRef(schema.get$ref());
248+
if (refName != null) {
249+
String modelName = toModelName(refName);
250+
oneOf.setName(modelName);
251+
oneOf.setBaseName(refName);
252+
}
253+
} else {
254+
// In-placed type (primitive), because there is no mapping or ref for it.
255+
// use camelized `title` if present, otherwise use `type`
256+
String oneOfName = Optional.ofNullable(schema.getTitle()).orElseGet(schema::getType);
257+
oneOf.setName(toModelName(oneOfName));
258+
}
259+
}
260+
261+
mdl.getComposedSchemas().setOneOf(newOneOfs);
262+
}
263+
264+
return mdl;
265+
}
266+
208267
@Override
209268
public ModelsMap postProcessModels(ModelsMap objs) {
210269
// Remove the discriminator field from the model, serde will take care of this
211270
for (ModelMap model : objs.getModels()) {
212271
CodegenModel cm = model.getModel();
272+
213273
if (cm.discriminator != null) {
214274
String reserved_var_name = cm.discriminator.getPropertyBaseName();
215275

@@ -399,43 +459,9 @@ public String modelDocFileFolder() {
399459

400460
@Override
401461
public String getTypeDeclaration(Schema p) {
462+
// use unaliased schema for client-side
402463
Schema unaliasSchema = unaliasSchema(p);
403-
if (ModelUtils.isArraySchema(unaliasSchema)) {
404-
ArraySchema ap = (ArraySchema) unaliasSchema;
405-
Schema inner = ap.getItems();
406-
if (inner == null) {
407-
LOGGER.warn("{}(array property) does not have a proper inner type defined.Default to string",
408-
ap.getName());
409-
inner = new StringSchema().description("TODO default missing array inner type to string");
410-
}
411-
return "Vec<" + getTypeDeclaration(inner) + ">";
412-
} else if (ModelUtils.isMapSchema(unaliasSchema)) {
413-
Schema inner = ModelUtils.getAdditionalProperties(unaliasSchema);
414-
if (inner == null) {
415-
LOGGER.warn("{}(map property) does not have a proper inner type defined. Default to string", unaliasSchema.getName());
416-
inner = new StringSchema().description("TODO default missing map inner type to string");
417-
}
418-
return "::std::collections::HashMap<String, " + getTypeDeclaration(inner) + ">";
419-
}
420-
421-
// Not using the supertype invocation, because we want to UpperCamelize
422-
// the type.
423-
String schemaType = getSchemaType(unaliasSchema);
424-
if (typeMapping.containsKey(schemaType)) {
425-
return typeMapping.get(schemaType);
426-
}
427-
428-
if (typeMapping.containsValue(schemaType)) {
429-
return schemaType;
430-
}
431-
432-
if (languageSpecificPrimitives.contains(schemaType)) {
433-
return schemaType;
434-
}
435-
436-
// return fully-qualified model name
437-
// crate::models::{{classnameFile}}::{{classname}}
438-
return "crate::models::" + toModelName(schemaType);
464+
return super.getTypeDeclaration(unaliasSchema);
439465
}
440466

441467
@Override

0 commit comments

Comments
 (0)