From 59faed4820f164024d37ee2bd9ea3e5a1d476561 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sun, 1 Dec 2019 09:36:09 +0100 Subject: [PATCH 1/6] small enough to inline --- .../spring/converter/DataTypeConverter.groovy | 45 ++++++++++-- .../spring/converter/DataTypeMapper.groovy | 72 ------------------- 2 files changed, 39 insertions(+), 78 deletions(-) delete mode 100644 src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeMapper.groovy diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy index b73e0933..1c57a552 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy @@ -19,9 +19,11 @@ package com.github.hauner.openapi.spring.converter import com.github.hauner.openapi.spring.converter.mapping.AmbiguousTypeMappingException import com.github.hauner.openapi.spring.converter.mapping.TargetType import com.github.hauner.openapi.spring.converter.mapping.TypeMapping +import com.github.hauner.openapi.spring.converter.mapping.TypeMappingX import com.github.hauner.openapi.spring.converter.schema.ArraySchemaType import com.github.hauner.openapi.spring.converter.schema.ObjectSchemaType import com.github.hauner.openapi.spring.converter.schema.SchemaInfo +import com.github.hauner.openapi.spring.converter.schema.SchemaType import com.github.hauner.openapi.spring.model.DataTypes import com.github.hauner.openapi.spring.model.datatypes.ArrayDataType import com.github.hauner.openapi.spring.model.datatypes.BooleanDataType @@ -49,11 +51,9 @@ import com.github.hauner.openapi.spring.model.datatypes.StringDataType class DataTypeConverter { private ApiOptions options - private DataTypeMapper mapper DataTypeConverter(ApiOptions options) { this.options = options - this.mapper = new DataTypeMapper(options.typeMappings) } DataType none() { @@ -96,7 +96,7 @@ class DataTypeConverter { DataType item = convert (itemSchemaInfo, dataTypes) def arrayType - TargetType targetType = mapper.getMappedDataType (new ArraySchemaType (schemaInfo)) + TargetType targetType = getMappedDataType (new ArraySchemaType (schemaInfo)) switch (targetType?.typeName) { case Collection.name: arrayType = new CollectionDataType (item: item) @@ -117,7 +117,7 @@ class DataTypeConverter { private DataType createObjectDataType (SchemaInfo schemaInfo, DataTypes dataTypes) { def objectType - TargetType targetType = mapper.getMappedDataType (new ObjectSchemaType (schemaInfo)) + TargetType targetType = getMappedDataType (new ObjectSchemaType (schemaInfo)) if (targetType) { objectType = new MappedDataType ( type: targetType.name, @@ -204,11 +204,44 @@ class DataTypeConverter { simpleType } + TargetType getMappedDataType (SchemaType schemaType) { + // check endpoint mappings + List endpointMatches = schemaType.matchEndpointMapping (options.typeMappings) + if (!endpointMatches.empty) { + TargetType target = endpointMatches.first().targetType + if (target) { + return target + } + } + + // check global parameter & response mappings + List ioMatches = schemaType.matchIoMapping (options.typeMappings) + if (!ioMatches.empty) { + TargetType target = ioMatches.first().targetType + if (target) { + return target + } + } + + // check global type mapping + List typeMatches = schemaType.matchTypeMapping (options.typeMappings) + if (typeMatches.isEmpty ()) { + return null + } + + if (typeMatches.size () != 1) { + throw new AmbiguousTypeMappingException (typeMatches) + } + + TypeMapping match = typeMatches.first () as TypeMapping + return match.targetType + } + private TargetType getSimpleDataType (SchemaInfo schemaInfo) { if (options.typeMappings) { // check global mapping - List mappings = getTypeMappings () + List mappings = getTypeMappingsY () List matches = mappings.findAll { it.sourceTypeName == schemaInfo.type && it.sourceTypeFormat == schemaInfo.format } @@ -231,7 +264,7 @@ class DataTypeConverter { null } - private List getTypeMappings () { + private List getTypeMappingsY () { options.typeMappings.findResults { it instanceof TypeMapping ? it : null } diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeMapper.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeMapper.groovy deleted file mode 100644 index 9e9e9dbe..00000000 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeMapper.groovy +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.converter - -import com.github.hauner.openapi.spring.converter.mapping.AmbiguousTypeMappingException -import com.github.hauner.openapi.spring.converter.mapping.TargetType -import com.github.hauner.openapi.spring.converter.mapping.TypeMapping -import com.github.hauner.openapi.spring.converter.mapping.TypeMappingX -import com.github.hauner.openapi.spring.converter.schema.SchemaType - -/** - * Checks if there is a mapping for a given type. Used by DataTypeConverter. - * - * @author Martin Hauner - */ -class DataTypeMapper { - - private List typeMappings - - DataTypeMapper(List typeMappings) { - this.typeMappings = (typeMappings ?: []) as List - } - - TargetType getMappedDataType (SchemaType schemaType) { - - // check endpoint mappings - List endpointMatches = schemaType.matchEndpointMapping (typeMappings) - if (!endpointMatches.empty) { - TargetType target = endpointMatches.first().targetType - if (target) { - return target - } - } - - // check global parameter & response mappings - List ioMatches = schemaType.matchIoMapping (typeMappings) - if (!ioMatches.empty) { - TargetType target = ioMatches.first().targetType - if (target) { - return target - } - } - - // check global type mapping - List typeMatches = schemaType.matchTypeMapping (typeMappings) - if (typeMatches.isEmpty ()) { - return null - } - - if (typeMatches.size () != 1) { - throw new AmbiguousTypeMappingException (typeMatches) - } - - TypeMapping match = typeMatches.first () as TypeMapping - return match.targetType - } - -} From 923dc26708c2af54ad269f36ded538fe9fd62f87 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sun, 1 Dec 2019 09:40:32 +0100 Subject: [PATCH 2/6] cleanup --- .../converter/mapping/AmbiguousTypeMappingException.groovy | 2 -- .../hauner/openapi/spring/converter/mapping/TypeMappingX.groovy | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/AmbiguousTypeMappingException.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/AmbiguousTypeMappingException.groovy index ff131836..5ec5f86d 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/AmbiguousTypeMappingException.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/AmbiguousTypeMappingException.groovy @@ -16,8 +16,6 @@ package com.github.hauner.openapi.spring.converter.mapping -import com.github.hauner.openapi.spring.converter.mapping.TypeMapping - /** * thrown when the DataTypeConverter finds an ambiguous data type mapping. * diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/TypeMappingX.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/TypeMappingX.groovy index 29d61935..bfc6da9a 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/TypeMappingX.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/mapping/TypeMappingX.groovy @@ -26,7 +26,7 @@ enum MappingLevel { } /** - * + * Common interface for type mappings. */ interface TypeMappingX { boolean matches (SchemaInfo info) From 158c69ec4780639db488b8a4aec8f8ec036df684 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sun, 1 Dec 2019 09:41:12 +0100 Subject: [PATCH 3/6] missing syntax highlighting --- docs/generatr/identifier.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generatr/identifier.md b/docs/generatr/identifier.md index d5462337..d6b5a43c 100644 --- a/docs/generatr/identifier.md +++ b/docs/generatr/identifier.md @@ -29,6 +29,7 @@ first letter on the next word. The special characters are: For properties of model classes, the properties will be annotated with `@JsonProperty` to provide the mapping from the OpenAPI identifier to the Java identifier. +```java class Example { @JsonProperty("foo-bar") @@ -36,8 +37,7 @@ the mapping from the OpenAPI identifier to the Java identifier. // ... } - - +``` [java-char-start]: https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Character.html#isJavaIdentifierStart(char) [java-char-part]: https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Character.html#isJavaIdentifierPart(char) From 4930d7ab8d664d84a5a1f643837fc5442f5beaff Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sun, 1 Dec 2019 09:43:02 +0100 Subject: [PATCH 4/6] improved typing --- .../github/hauner/openapi/spring/converter/ApiOptions.groovy | 4 +++- .../hauner/openapi/spring/generatr/TypeMappingReader.groovy | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiOptions.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiOptions.groovy index 2bdb8ae9..9af0cf89 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiOptions.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/ApiOptions.groovy @@ -16,6 +16,8 @@ package com.github.hauner.openapi.spring.converter +import com.github.hauner.openapi.spring.converter.mapping.TypeMappingX + /** * Options of the generatr. * @@ -53,6 +55,6 @@ class ApiOptions { * {@link com.github.hauner.openapi.spring.converter.mapping.EndpointTypeMapping}: used to override * parameter, response type mappings or to add additional parameters on a single endpoint. */ - List typeMappings + List typeMappings } diff --git a/src/main/groovy/com/github/hauner/openapi/spring/generatr/TypeMappingReader.groovy b/src/main/groovy/com/github/hauner/openapi/spring/generatr/TypeMappingReader.groovy index 4ba02724..7ae705b5 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/generatr/TypeMappingReader.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/generatr/TypeMappingReader.groovy @@ -20,6 +20,7 @@ import com.github.hauner.openapi.spring.converter.mapping.EndpointTypeMapping import com.github.hauner.openapi.spring.converter.mapping.ParameterTypeMapping import com.github.hauner.openapi.spring.converter.mapping.ResponseTypeMapping import com.github.hauner.openapi.spring.converter.mapping.TypeMapping +import com.github.hauner.openapi.spring.converter.mapping.TypeMappingX import org.yaml.snakeyaml.Yaml import java.util.regex.Matcher @@ -33,7 +34,7 @@ import java.util.regex.Pattern class TypeMappingReader { private Pattern GENERIC_INLINE = ~/(.+?)<(.+?)>/ - List read (String typeMappings) { + List read (String typeMappings) { if (typeMappings == null) { return [] } From ca157b4c9a840578a8581df217ed73a0c04b7be9 Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sun, 1 Dec 2019 09:45:11 +0100 Subject: [PATCH 5/6] improved naming --- .../openapi/spring/converter/schema/SchemaType.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy index 79b9c750..539edc9e 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy @@ -28,11 +28,11 @@ interface SchemaType { } -abstract class SchemaTypeBase implements SchemaType { +abstract class BaseSchemaType implements SchemaType { protected SchemaInfo info - SchemaTypeBase(SchemaInfo info) { + BaseSchemaType (SchemaInfo info) { this.info = info } @@ -75,7 +75,7 @@ abstract class SchemaTypeBase implements SchemaType { } -class ObjectSchemaType extends SchemaTypeBase { +class ObjectSchemaType extends BaseSchemaType { ObjectSchemaType (SchemaInfo info) { super (info) @@ -90,7 +90,7 @@ class ObjectSchemaType extends SchemaTypeBase { } -class ArraySchemaType extends SchemaTypeBase { +class ArraySchemaType extends BaseSchemaType { ArraySchemaType (SchemaInfo info) { super (info) From 1709d4e8a1d6ff5260e61f3a0ca0020dc19799ff Mon Sep 17 00:00:00 2001 From: Martin Hauner Date: Sun, 1 Dec 2019 15:34:59 +0100 Subject: [PATCH 6/6] re-use object mapping --- .../spring/converter/DataTypeConverter.groovy | 2 +- .../spring/converter/schema/SchemaType.groovy | 9 +- ...taTypeConverterArrayTypeMappingSpec.groovy | 170 ++++++++++++++---- ...aTypeConverterObjectTypeMappingSpec.groovy | 2 - 4 files changed, 143 insertions(+), 40 deletions(-) diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy index 1c57a552..18f8e407 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverter.groovy @@ -214,7 +214,7 @@ class DataTypeConverter { } } - // check global parameter & response mappings + // check global io (parameter & response) mappings List ioMatches = schemaType.matchIoMapping (options.typeMappings) if (!ioMatches.empty) { TargetType target = ioMatches.first().targetType diff --git a/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy b/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy index 539edc9e..34129cd6 100644 --- a/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy +++ b/src/main/groovy/com/github/hauner/openapi/spring/converter/schema/SchemaType.groovy @@ -57,11 +57,7 @@ abstract class BaseSchemaType implements SchemaType { } // type mappings - endpoint.findAll { - it.isLevel (MappingLevel.TYPE) && it.matches (info) - }.collect { - it.childMappings - }.flatten () as List + matchTypeMapping (endpoint) } List matchIoMapping (List typeMappings) { @@ -98,8 +94,9 @@ class ArraySchemaType extends BaseSchemaType { @Override List matchTypeMapping (List typeMappings) { + def array = new SchemaInfo (null, null,'array') typeMappings.findAll () { - it.isLevel (MappingLevel.TYPE) && it.matches (new SchemaInfo (null, null,'array')) + it.isLevel (MappingLevel.TYPE) && it.matches (array) } } diff --git a/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterArrayTypeMappingSpec.groovy b/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterArrayTypeMappingSpec.groovy index d4ba56e4..a9d802fe 100644 --- a/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterArrayTypeMappingSpec.groovy +++ b/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterArrayTypeMappingSpec.groovy @@ -18,6 +18,7 @@ package com.github.hauner.openapi.spring.converter import com.github.hauner.openapi.spring.converter.mapping.AmbiguousTypeMappingException import com.github.hauner.openapi.spring.converter.mapping.EndpointTypeMapping +import com.github.hauner.openapi.spring.converter.mapping.ParameterTypeMapping import com.github.hauner.openapi.spring.converter.mapping.ResponseTypeMapping import com.github.hauner.openapi.spring.converter.mapping.TypeMapping import com.github.hauner.openapi.spring.model.Api @@ -29,7 +30,7 @@ import static com.github.hauner.openapi.spring.support.OpenApiParser.parse class DataTypeConverterArrayTypeMappingSpec extends Specification { @Unroll - void "maps simple array schema to #responseTypeName via global array mapping" () { + void "maps array schema to #responseTypeName via global type mapping" () { def openApi = parse ("""\ openapi: 3.0.2 info: @@ -108,7 +109,7 @@ paths: e.typeMappings == options.typeMappings } - void "converts simple array response schema to Collection<> via endpoint response type mapping" () { + void "converts array response schema to #responseTypeName via endpoint type mapping" () { def openApi = parse ("""\ openapi: 3.0.2 info: @@ -116,7 +117,7 @@ info: version: 1.0.0 paths: - /array-string: + /foo: get: responses: '200': @@ -126,36 +127,98 @@ paths: type: array items: type: string - description: none + description: none """) when: - def options = new ApiOptions( - packageName: 'pkg', - typeMappings: [ - new EndpointTypeMapping (path: '/array-string', - typeMappings: [ -// new ResponseTypeMapping ( -// contentType: 'application/vnd.any', -// mapping: new TypeMapping ( -// targetTypeName: 'pkg.TargetClass') -// ), - new ResponseTypeMapping ( - contentType: 'application/vnd.any', - mapping: new TypeMapping ( - targetTypeName: 'java.util.Collection') - ) + def options = new ApiOptions(packageName: 'pkg', typeMappings: [ + new EndpointTypeMapping (path: '/foo', + typeMappings: [ + new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: targetTypeName) ]) - ]) + ]) Api api = new ApiConverter (options).convert (openApi) then: def itf = api.interfaces.first () def ep = itf.endpoints.first () - ep.response.responseType.name == 'Collection' + ep.response.responseType.name == responseTypeName + ep.response.responseType.packageName == 'java.util' + + where: + targetTypeName | responseTypeName + 'java.util.Collection' | 'Collection' + 'java.util.List' | 'List' + 'java.util.Set' | 'Set' + } + + @Unroll + void "converts array parameter schema to java type via #type" () { + def openApi = parse ("""\ +openapi: 3.0.2 +info: + title: API + version: 1.0.0 + +paths: + /foobar: + get: + parameters: + - in: query + name: foobar + required: false + schema: + type: array + items: + type: string + responses: + '204': + description: empty +""") + + when: + def options = new ApiOptions(packageName: 'pkg', typeMappings: mappings) + Api api = new ApiConverter (options).convert (openApi) + + then: + def itf = api.interfaces.first () + def ep = itf.endpoints.first () + def p = ep.parameters.first () + p.dataType.name == 'Collection' + p.dataType.packageName == 'java.util' + + where: + type << [ + 'endpoint parameter mapping', + 'global parameter mapping' + ] + + mappings << [ + [ + new EndpointTypeMapping (path: '/foobar', + typeMappings: [ + new ParameterTypeMapping ( + parameterName: 'foobar', + mapping: new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ) + ]) + ], [ + new ParameterTypeMapping ( + parameterName: 'foobar', + mapping: new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ) + ] + ] } - void "converts simple array response schema to Collection<> via global response type array mapping" () { + @Unroll + void "converts array response schema to Collection<> via type" () { def openApi = parse ("""\ openapi: 3.0.2 info: @@ -177,21 +240,66 @@ paths: """) when: - def options = new ApiOptions( - packageName: 'pkg', - typeMappings: [ - new ResponseTypeMapping ( - contentType: 'application/vnd.any', - mapping: new TypeMapping( - targetTypeName: 'java.util.Collection') - ) - ]) + def options = new ApiOptions(packageName: 'pkg', typeMappings: mappings) Api api = new ApiConverter (options).convert (openApi) then: def itf = api.interfaces.first () def ep = itf.endpoints.first () ep.response.responseType.name == 'Collection' + ep.response.responseType.imports == ['java.util.Collection', 'java.lang.String'] as Set + + where: + type << [ + 'endpoint response mapping', + 'global response mapping', + 'endpoint response mapping over endpoint type mapping', + 'endpoint type mapping' + ] + + mappings << [ + [ + new EndpointTypeMapping (path: '/array-string', + typeMappings: [ + new ResponseTypeMapping ( + contentType: 'application/vnd.any', + mapping: new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ) + ] + ) + ], [ + new ResponseTypeMapping ( + contentType: 'application/vnd.any', + mapping: new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ) + ], [ + new EndpointTypeMapping (path: '/array-string', + typeMappings: [ + new ResponseTypeMapping ( + contentType: 'application/vnd.any', + mapping: new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ), + new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ] + ) + ], [ + new EndpointTypeMapping (path: '/array-string', + typeMappings: [ + new TypeMapping ( + sourceTypeName: 'array', + targetTypeName: 'java.util.Collection') + ] + ) + ] + ] } } diff --git a/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterObjectTypeMappingSpec.groovy b/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterObjectTypeMappingSpec.groovy index dd25663c..401a4d8c 100644 --- a/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterObjectTypeMappingSpec.groovy +++ b/src/test/groovy/com/github/hauner/openapi/spring/converter/DataTypeConverterObjectTypeMappingSpec.groovy @@ -285,7 +285,6 @@ paths: new ParameterTypeMapping ( parameterName: 'foobar', mapping: new TypeMapping ( - sourceTypeName: 'object', targetTypeName: 'pkg.TargetClass') ) ]) @@ -293,7 +292,6 @@ paths: new ParameterTypeMapping ( parameterName: 'foobar', mapping: new TypeMapping ( - sourceTypeName: 'object', targetTypeName: 'pkg.TargetClass') ) ]