Skip to content

#84 #86

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

Merged
merged 2 commits into from
Feb 29, 2020
Merged

#84 #86

Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -42,6 +42,7 @@ import com.github.hauner.openapi.spring.model.datatypes.IntegerDataType
import com.github.hauner.openapi.spring.model.datatypes.LongDataType
import com.github.hauner.openapi.spring.model.datatypes.NoneDataType
import com.github.hauner.openapi.spring.model.datatypes.OffsetDateTimeDataType
import com.github.hauner.openapi.spring.model.datatypes.LazyDataType
import com.github.hauner.openapi.spring.model.datatypes.SetDataType
import com.github.hauner.openapi.spring.model.datatypes.StringDataType
import com.github.hauner.openapi.spring.model.datatypes.StringEnumDataType
Expand All @@ -55,9 +56,12 @@ import com.github.hauner.openapi.spring.model.datatypes.StringEnumDataType
class DataTypeConverter {

private ApiOptions options
private List<SchemaInfo> current


DataTypeConverter(ApiOptions options) {
this.options = options
this.current = []
}

DataType none() {
Expand All @@ -74,19 +78,28 @@ class DataTypeConverter {
* @return the resulting java data type
*/
DataType convert (SchemaInfo dataTypeInfo, DataTypes dataTypes) {
if (isLoop (dataTypeInfo)) {
return new LazyDataType (info: dataTypeInfo, dataTypes: dataTypes)
}

push (dataTypeInfo)

DataType result
if (dataTypeInfo.isRefObject ()) {
createRefDataType(dataTypeInfo, dataTypes)
result = createRefDataType(dataTypeInfo, dataTypes)

} else if (dataTypeInfo.isArray ()) {
createArrayDataType (dataTypeInfo, dataTypes)
result = createArrayDataType (dataTypeInfo, dataTypes)

} else if (dataTypeInfo.isObject ()) {
createObjectDataType (dataTypeInfo, dataTypes)
result = createObjectDataType (dataTypeInfo, dataTypes)

} else {
createSimpleDataType (dataTypeInfo, dataTypes)
result = createSimpleDataType (dataTypeInfo, dataTypes)
}

pop ()
result
}

private DataType createArrayDataType (SchemaInfo schemaInfo, DataTypes dataTypes) {
Expand Down Expand Up @@ -250,7 +263,7 @@ class DataTypeConverter {
enumType
}

TargetType getMappedDataType (SchemaType schemaType) {
private TargetType getMappedDataType (SchemaType schemaType) {
// check endpoint mappings
List<Mapping> endpointMatches = schemaType.matchEndpointMapping (options.typeMappings)
if (!endpointMatches.empty) {
Expand Down Expand Up @@ -293,4 +306,40 @@ class DataTypeConverter {
return match.targetType
}

/**
* push the current schema info.
*
* Pushes the given {@code info} onto the in-progress data type stack. It is used to detect
* $ref loops.
*
* @param info the schema info that is currently processed
*/
private void push (SchemaInfo info) {
current.push (info)
}

/**
* pop the current schema info.
*
*/
private void pop () {
current.pop ()
}

/**
* detect $ref loop.
*
* returns true if the given {@code info} is currently processed, false otherwise. True indicates
* a $ref loop.
*
* @param info the schema info that is currently processed
* @return
*/
private boolean isLoop (SchemaInfo info) {
def found = current.find {
it.name == info.name
}
found != null
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2019 the original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.hauner.openapi.spring.model.datatypes

import com.github.hauner.openapi.spring.converter.schema.SchemaInfo
import com.github.hauner.openapi.spring.model.DataTypes

/**
* OpenAPI $ref type that is lazily evaluated. It is used to break loops in the schema definitions.
*
* @autor Martin Hauner
*/
class LazyDataType implements DataType {

private DataTypes dataTypes
private SchemaInfo info

@Override
String getName () {
dataType.name
}

@Override
String getPackageName () {
dataType.packageName
}

@Override
Set<String> getImports () {
dataType.imports
}

@Override
Set<String> getReferencedImports () {
dataType.referencedImports
}

private DataType getDataType () {
dataTypes.find (info.name)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2020 the original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.hauner.openapi.spring.converter

import com.github.hauner.openapi.spring.model.datatypes.LazyDataType
import com.github.hauner.openapi.spring.model.datatypes.ObjectDataType
import spock.lang.Specification

import static com.github.hauner.openapi.spring.support.OpenApiParser.parse


class DataTypeConverterLoopSpec extends Specification {

void "handles \$ref loops"() {
def openApi = parse (
"""\
openapi: 3.0.2
info:
title: test \$ref loop
version: 1.0.0

paths:

/self-reference:
get:
responses:
'200':
description: none
content:
application/json:
schema:
\$ref: '#/components/schemas/Self'

components:
schemas:

Self:
type: object
properties:
self:
\$ref: '#/components/schemas/Self'
""")

when:
def api = new ApiConverter (new ApiOptions()).convert (openApi)

then:
def itf = api.interfaces.first ()
def ep = itf.endpoints.first ()
def rp = ep.getFirstResponse ('200')
def rt = rp.responseType
def sf = rt.objectProperties.self
rt instanceof ObjectDataType
sf instanceof LazyDataType
sf.name == 'Self'
sf.packageName == 'generatr.model'
sf.imports == ['generatr.model.Self'] as Set
sf.referencedImports == ['generatr.model.Self'] as Set
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ import org.junit.Ignore
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@Ignore
//@Ignore
@RunWith(Parameterized)
class ProcessorPendingTest extends ProcessorTestBase {

@Parameterized.Parameters(name = "{0}")
static Collection<TestSet> sources () {
return [
new TestSet(name: 'response-content-multiple')
new TestSet(name: 'ref-loop')
]
}

Expand Down
25 changes: 25 additions & 0 deletions src/testInt/resources/ref-loop/generated/api/Api.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
* DO NOT EDIT.
*/

package generated.api;

import generated.model.Foo;
import generated.model.Self;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;

public interface Api {

@GetMapping(
path = "/self-reference",
produces = {"application/json"})
ResponseEntity<Self> getSelfReference();

@GetMapping(
path = "/nested-self-reference",
produces = {"application/json"})
ResponseEntity<Foo> getNestedSelfReference();

}
23 changes: 23 additions & 0 deletions src/testInt/resources/ref-loop/generated/model/Bar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
* DO NOT EDIT.
*/

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Bar {

@JsonProperty("parent")
private Foo parent;

public Foo getParent() {
return parent;
}

public void setParent(Foo parent) {
this.parent = parent;
}

}
23 changes: 23 additions & 0 deletions src/testInt/resources/ref-loop/generated/model/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
* DO NOT EDIT.
*/

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Foo {

@JsonProperty("child")
private Bar child;

public Bar getChild() {
return child;
}

public void setChild(Bar child) {
this.child = child;
}

}
23 changes: 23 additions & 0 deletions src/testInt/resources/ref-loop/generated/model/Self.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* This class is auto generated by https://github.com/hauner/openapi-generatr-spring.
* DO NOT EDIT.
*/

package generated.model;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Self {

@JsonProperty("self")
private Self self;

public Self getSelf() {
return self;
}

public void setSelf(Self self) {
this.self = self;
}

}
47 changes: 47 additions & 0 deletions src/testInt/resources/ref-loop/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
openapi: 3.0.2
info:
title: test $ref loop
version: 1.0.0

paths:

/self-reference:
get:
responses:
'200':
description: none
content:
application/json:
schema:
$ref: '#/components/schemas/Self'

/nested-self-reference:
get:
responses:
'200':
description: none
content:
application/json:
schema:
$ref: '#/components/schemas/Foo'

components:
schemas:

Self:
type: object
properties:
self:
$ref: '#/components/schemas/Self'

Foo:
type: object
properties:
child:
$ref: '#/components/schemas/Bar'

Bar:
type: object
properties:
parent:
$ref: '#/components/schemas/Foo'