Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spring][server] oneOf Interface for Spring Boot #8091

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6a8769e
Fix OpenAPITools#5381
alexsuperdev Mar 21, 2020
049359c
Fix OpenAPITools#5381
alexsuperdev Mar 22, 2020
49f13eb
Fix OpenAPITools#5381
alexsuperdev Mar 22, 2020
1a5a484
Fix OpenAPITools#5381
alexsuperdev Mar 22, 2020
66a09d2
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
alexsuperdev Mar 22, 2020
48a1a4a
Fix OpenAPITools#5381
alexsuperdev Mar 24, 2020
a811330
Fix OpenAPITools#5381
alexsuperdev Mar 24, 2020
299cdb3
Fix OpenAPITools#5381
alexsuperdev Mar 24, 2020
461cbc8
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
alexsuperdev Mar 24, 2020
dd0118d
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
alexsuperdev Mar 25, 2020
26a95f6
Fix OpenAPITools#5381
alexsuperdev Mar 25, 2020
9af7db6
Fix OpenAPITools#5381
alexsuperdev Mar 30, 2020
fbfd57d
Fix OpenAPITools#5381
alexsuperdev Mar 21, 2020
bfab304
Fix OpenAPITools#5381
alexsuperdev Mar 22, 2020
29003d8
Fix OpenAPITools#5381
alexsuperdev Apr 1, 2020
d864831
Fix OpenAPITools#5381
alexsuperdev Apr 1, 2020
6d54799
Fix OpenAPITools#5381
alexsuperdev Apr 2, 2020
c6a5c6b
Merge remote-tracking branch 'super/master' into spring_fix_5381_jb
jburgess Dec 1, 2020
d96cd2e
Merge remote-tracking branch 'super/master' into spring_fix_5381_jb
jburgess Dec 3, 2020
8f09f52
Initial merge of 5.0
jburgess Dec 3, 2020
0d31df4
Aligned with master formatting
jburgess Dec 3, 2020
397e711
Corrected spacing for class names to align with samples.
jburgess Dec 4, 2020
a922217
Merge remote-tracking branch 'source/master' into spring_fix_5381_jb
jburgess Dec 5, 2020
21a98e5
Merge remote-tracking branch 'source/master' into spring_fix_5381_jb
jburgess Aug 11, 2021
1b5964e
Merge remote-tracking branch 'source/master' into spring_fix_5381_jb
jburgess Aug 11, 2021
05105fa
Merged master
jburgess Aug 11, 2021
c89a28b
Updated samples
jburgess Aug 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ public void postProcessParameter(CodegenParameter parameter) {
@Override
@SuppressWarnings("unused")
public void preprocessOpenAPI(OpenAPI openAPI) {
if (useOneOfInterfaces) {
if (useOneOfInterfaces && openAPI.getComponents() != null) {
// we process the openapi schema here to find oneOf schemas and create interface models for them
Map<String, Schema> schemas = new HashMap<String, Schema>(openAPI.getComponents().getSchemas());
if (schemas == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,16 @@ public Map<String, Object> postProcessModels(Map<String, Object> objs) {
}
}

// add implements for serializable/parcelable to all models
List<Map<String, Object>> models = (List<Map<String, Object>>) objs.get("models");
for (Map<String, Object> mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");
if (this.serializableModel) {
cm.getVendorExtensions().putIfAbsent("x-implements", new ArrayList<String>());
((ArrayList<String>) cm.getVendorExtensions().get("x-implements")).add("Serializable");
}
}

return postProcessModelsEnum(objs);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public void processOpts() {
}

super.processOpts();

useOneOfInterfaces = true;
// clear model and api doc template as this codegen
// does not support auto-generated markdown doc at the moment
//TODO: add doc templates
Expand Down Expand Up @@ -874,4 +874,37 @@ public void setPerformBeanValidation(boolean performBeanValidation) {
public void setUseOptional(boolean useOptional) {
this.useOptional = useOptional;
}
}

@Override
public void postProcessParameter(CodegenParameter p) {
// we use a custom version of this function to remove the l, d, and f suffixes from Long/Double/Float
// defaultValues
// remove the l because our users will use Long.parseLong(String defaultValue)
// remove the d because our users will use Double.parseDouble(String defaultValue)
// remove the f because our users will use Float.parseFloat(String defaultValue)
// NOTE: for CodegenParameters we DO need these suffixes because those defaultValues are used as java value
// literals assigned to Long/Double/Float
if (p.defaultValue == null) {
return;
}
Boolean fixLong = (p.isLong && "l".equals(p.defaultValue.substring(p.defaultValue.length()-1)));
Boolean fixDouble = (p.isDouble && "d".equals(p.defaultValue.substring(p.defaultValue.length()-1)));
Boolean fixFloat = (p.isFloat && "f".equals(p.defaultValue.substring(p.defaultValue.length()-1)));
if (fixLong || fixDouble || fixFloat) {
p.defaultValue = p.defaultValue.substring(0, p.defaultValue.length()-1);
}
}

@Override
public void addImportsToOneOfInterface(List<Map<String, String>> imports) {
for (String i : Arrays.asList("JsonSubTypes", "JsonTypeInfo")) {
Map<String, String> oneImport = new HashMap<String, String>() {{
put("import", importMapping.get(i));
}};
if (!imports.contains(oneImport)) {
imports.add(oneImport);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.springframework.hateoas.RepresentationModel;
{{>enumOuterClass}}
{{/isEnum}}
{{^isEnum}}
{{>pojo}}
{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}}
{{/isEnum}}
{{/model}}
{{/models}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}}
public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/{{#description}}
@ApiModel(description = "{{{description}}}"){{/description}}
{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}{{>additionalModelTypeAnnotations}}
public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}} {{#serializableModel}}implements Serializable{{/serializableModel}} {
public class {{classname}} {{#parent}}extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}}extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}} {{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}} {
{{#serializableModel}}
private static final long serialVersionUID = 1L;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,4 +653,43 @@ public void reactiveMapTypeRequestMonoTest() throws IOException {
assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApi.java"), "Mono<DummyRequest>");
assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApiDelegate.java"), "Mono<DummyRequest>");
}

@Test
public void oneOf_5381() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');
OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/issue_5381.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.setUseOneOfInterfaces(true);

ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
codegen.setHateoas(true);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
//generator.setGeneratorPropertyDefault(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false");

codegen.setUseOneOfInterfaces(true);
codegen.setJava8(true);
codegen.setLegacyDiscriminatorBehavior(false);

generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");

generator.opts(input).generate();

assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public class Foo implements FooRefOrValue");
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public class FooRef implements FooRefOrValue");
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue");
}
}
134 changes: 134 additions & 0 deletions modules/openapi-generator/src/test/resources/3_0/issue_5381.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
openapi: 3.0.1
info:
title: ByRefOrValue
description: >
This tests for a oneOf interface representation
version: 0.0.1
servers:
- url: "http://localhost:8080"
tags:
- name: Foo
paths:
/foo:
get:
tags:
- Foo
summary: GET all Foos
operationId: getAllFoos
responses:
'200':
$ref: '#/components/responses/200FooArray'
post:
tags:
- Foo
summary: Create a Foo
operationId: createFoo
requestBody:
$ref: '#/components/requestBodies/Foo'
responses:
'201':
$ref: '#/components/responses/201Foo'

components:
schemas:
Entity:
type: object
allOf:
- "$ref": "#/components/schemas/Addressable"
- "$ref": "#/components/schemas/Extensible"

EntityRef:
description: Entity reference schema to be use for all entityRef class.
type: object
properties:
name:
type: string
description: Name of the related entity.
'@referredType':
type: string
description: The actual type of the target instance when needed for disambiguation.
allOf:
- $ref: '#/components/schemas/Addressable'
- "$ref": "#/components/schemas/Extensible"


Addressable:
type: object
properties:
href:
type: string
description: Hyperlink reference
id:
type: string
description: unique identifier
description: Base schema for adressable entities
Extensible:
type: object
properties:
"@schemaLocation":
type: string
description: A URI to a JSON-Schema file that defines additional attributes
and relationships
"@baseType":
type: string
description: When sub-classing, this defines the super-class
"@type":
type: string
description: When sub-classing, this defines the sub-class Extensible name
required:
- '@type'

FooRefOrValue:
type: object
oneOf:
- $ref: "#/components/schemas/Foo"
- $ref: "#/components/schemas/FooRef"
discriminator:
propertyName: "@type"
mapping:
Foo: "#/components/schemas/Foo"
FooRef: "#/components/schemas/FooRef"

Foo:
type: object
properties:
fooPropA:
type: string
fooPropB:
type: string
allOf:
- $ref: '#/components/schemas/Entity'

FooRef:
type: object
properties:
foorefPropA:
type: string
allOf:
- $ref: '#/components/schemas/EntityRef'

requestBodies:
Foo:
description: The Foo to be created
content:
application/json;charset=utf-8:
schema:
$ref: '#/components/schemas/Foo'
responses:
'204':
description: Deleted
content: { }
201Foo:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/FooRefOrValue'
200FooArray:
description: Success
content:
application/json;charset=utf-8:
schema:
type: array
items:
$ref: '#/components/schemas/FooRefOrValue'