Skip to content

Commit 1c1ed86

Browse files
committed
move exchange/mapping annotation configuration to mapping file with spring specif mapping (#439)
1 parent 909ee6d commit 1c1ed86

9 files changed

Lines changed: 244 additions & 8 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright 2026 https://github.com/openapi-processor/openapi-processor-spring
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.spring.processor
7+
8+
import io.openapiprocessor.core.processor.JsonSchema
9+
import java.net.URI
10+
11+
const val SPRING_MAPPING_SCHEMA_VERSION = "v1"
12+
13+
val JSON_SCHEMA_SPRING = JsonSchema(
14+
URI("https://openapiprocessor.io/schemas/mapping/spring-mapping-${SPRING_MAPPING_SCHEMA_VERSION}.json"),
15+
"/mapping/${SPRING_MAPPING_SCHEMA_VERSION}/spring-mapping.yaml.json")
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright 2026 https://github.com/openapi-processor/openapi-processor-spring
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.spring.processor
7+
8+
import io.openapiprocessor.core.converter.ApiOptions
9+
10+
class SpringApiOptions: ApiOptions() {
11+
var springAnnotations: String = "mapping"
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2026 https://github.com/openapi-processor/openapi-processor-spring
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.spring.processor
7+
8+
class SpringMappingOptionsValues(val annotations: String = "mapping")
9+
class SpringMappingOptions(val spring: SpringMappingOptionsValues = SpringMappingOptionsValues())
10+
class SpringMapping(val options: SpringMappingOptions)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2026 https://github.com/openapi-processor/openapi-processor-spring
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.spring.processor
7+
8+
import com.fasterxml.jackson.databind.DeserializationFeature
9+
import com.fasterxml.jackson.databind.MapperFeature
10+
import com.fasterxml.jackson.databind.ObjectMapper
11+
import com.fasterxml.jackson.databind.PropertyNamingStrategies
12+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
13+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper
14+
import com.fasterxml.jackson.module.kotlin.KotlinFeature
15+
import com.fasterxml.jackson.module.kotlin.KotlinModule
16+
import io.openapiprocessor.core.processor.MappingValidator
17+
import org.slf4j.Logger
18+
import org.slf4j.LoggerFactory
19+
import java.io.File
20+
import java.net.MalformedURLException
21+
import java.net.URL
22+
23+
/**
24+
* Reader for mapping YAML.
25+
*/
26+
class SpringMappingReader(private val validator: MappingValidator = MappingValidator()) {
27+
var log: Logger = LoggerFactory.getLogger(this.javaClass.name)
28+
29+
fun read(mappings: String?): SpringMapping? {
30+
if (mappings.isNullOrEmpty()) {
31+
return null
32+
}
33+
34+
val mapping: String = when {
35+
isUrl (mappings) -> {
36+
URL (mappings).readText()
37+
}
38+
isFileName (mappings) -> {
39+
File (mappings).readText()
40+
}
41+
else -> {
42+
mappings
43+
}
44+
}
45+
46+
validate(mapping)
47+
48+
val mapper = createParser()
49+
return mapper.readValue (mapping, SpringMapping::class.java)
50+
}
51+
52+
private fun validate(mapping: String) {
53+
val output = validator.validate(mapping)
54+
if (output.isValid)
55+
return
56+
57+
log.warn("mapping is not valid!")
58+
val error = output.error
59+
if(error != null) {
60+
log.warn(error)
61+
}
62+
63+
output.errors?.forEach {
64+
log.warn("{} at {}", it.error, it.instanceLocation.ifEmpty { "/" })
65+
}
66+
}
67+
68+
private fun createParser(): ObjectMapper {
69+
val kotlinModule = KotlinModule.Builder()
70+
.configure(KotlinFeature.NullIsSameAsDefault, true)
71+
.build ()
72+
73+
return YAMLMapper.builder(YAMLFactory())
74+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
75+
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true)
76+
.build()
77+
.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE)
78+
.registerModules(kotlinModule)
79+
}
80+
81+
private fun isFileName(name: String): Boolean {
82+
return name.endsWith (".yaml") || name.endsWith (".yml")
83+
}
84+
85+
private fun isUrl (source: String): Boolean {
86+
return try {
87+
URL (source)
88+
true
89+
} catch (ignore: MalformedURLException) {
90+
false
91+
}
92+
}
93+
94+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2026 https://github.com/openapi-processor/openapi-processor-spring
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.spring.processor
7+
8+
import io.openapiprocessor.core.converter.InvalidMappingException
9+
import org.slf4j.Logger
10+
import org.slf4j.LoggerFactory
11+
12+
class SpringOptionsConverter(private val mappingReader: SpringMappingReader) {
13+
var log: Logger = LoggerFactory.getLogger(this.javaClass.name)
14+
15+
fun fillOptions(processorOptions: Map<String, Any>, options: SpringApiOptions) {
16+
if (processorOptions.containsKey("mapping")) {
17+
readMapping(processorOptions["mapping"].toString(), options)
18+
} else {
19+
log.warn("required option 'mapping' is missing!")
20+
}
21+
}
22+
23+
private fun readMapping(mappingSource: String, options: SpringApiOptions) {
24+
try {
25+
val mapping = mappingReader.read(mappingSource)
26+
if (mapping == null) {
27+
log.warn("missing 'mapping.yaml' configuration!")
28+
return
29+
}
30+
31+
options.springAnnotations = mapping.options.spring.annotations
32+
33+
} catch (t: Throwable) {
34+
throw InvalidMappingException("failed to parse 'mapping.yaml' configuration!", t)
35+
}
36+
}
37+
}

src/main/kotlin/io/openapiprocessor/spring/processor/SpringProcessor.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import io.openapiprocessor.core.converter.ApiConverter
99
import io.openapiprocessor.core.converter.ApiOptions
1010
import io.openapiprocessor.core.converter.OptionsConverter
1111
import io.openapiprocessor.core.parser.OpenApiParser
12+
import io.openapiprocessor.core.processor.JSON_SCHEMA_CORE
13+
import io.openapiprocessor.core.processor.MappingReader
14+
import io.openapiprocessor.core.processor.MappingValidator
1215
import io.openapiprocessor.core.writer.SourceFormatter
1316
import io.openapiprocessor.core.writer.java.*
1417
import io.openapiprocessor.spring.Versions
@@ -32,15 +35,17 @@ class SpringProcessor : OpenApiProcessorTest {
3235
private var sourceRoot: String? = null
3336
private var resourceRoot: String? = null
3437

35-
fun run(processorOptions: Map<String, *>) {
38+
fun run(processorOptions: Map<String, Any>) {
3639
try {
3740
val parser = OpenApiParser()
3841
val openapi = parser.parse(processorOptions)
3942
if (processorOptions["showWarnings"] != null) {
4043
openapi.printWarnings()
4144
}
4245

43-
val kind = SpringAnnotations.valueOf(processorOptions["annotations"]?.toString())
46+
val options = convertOptions(processorOptions)
47+
48+
val kind = SpringAnnotations.valueOf(options.springAnnotations)
4449

4550
val annotations = when (kind) {
4651
EXCHANGE -> SpringFrameworkExchange()
@@ -51,7 +56,6 @@ class SpringProcessor : OpenApiProcessorTest {
5156
else -> MappingAnnotationFactory(annotations)
5257
}
5358

54-
val options = convertOptions(processorOptions)
5559
val identifier = JavaIdentifier(IdentifierOptions(
5660
options.identifierWordBreakFromDigitToLetter,
5761
options.identifierPrefixInvalidEnumStart))
@@ -147,9 +151,11 @@ class SpringProcessor : OpenApiProcessorTest {
147151
testMode = true
148152
}
149153

150-
@Suppress("UNCHECKED_CAST")
151-
private fun convertOptions(processorOptions: Map<String, *>): ApiOptions {
152-
val options = OptionsConverter().convertOptions (processorOptions as Map<String, Any>)
154+
private fun convertOptions(processorOptions: Map<String, Any>): SpringApiOptions {
155+
val options = SpringApiOptions()
156+
157+
OptionsConverter(createCoreMappingReader()).fillOptions (processorOptions, options)
158+
SpringOptionsConverter(createSpringMappingReader()).fillOptions (processorOptions, options)
153159
options.validate ()
154160

155161
if (options.targetDirOptions.standardLayout) {
@@ -171,4 +177,13 @@ class SpringProcessor : OpenApiProcessorTest {
171177
private fun getFormatter(apiOptions: ApiOptions): SourceFormatter {
172178
return SourceFormatterFactory().getFormatter(apiOptions)
173179
}
180+
181+
private fun createCoreMappingReader(): MappingReader {
182+
return MappingReader(MappingValidator(JSON_SCHEMA_CORE))
183+
}
184+
185+
private fun createSpringMappingReader(): SpringMappingReader {
186+
return SpringMappingReader(MappingValidator(
187+
JSON_SCHEMA_SPRING, listOf(JSON_SCHEMA_CORE)))
188+
}
174189
}

src/main/kotlin/io/openapiprocessor/spring/processor/SpringService.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ class SpringService(private val testMode: Boolean = false):
2525
if (testMode) {
2626
processor.enableTestMode()
2727
}
28-
processor.run(processorOptions)
28+
29+
@Suppress("UNCHECKED_CAST")
30+
processor.run(processorOptions as Map<String, Any>)
2931

3032
} catch (ex: Exception) {
3133
throw ex

src/main/kotlin/io/openapiprocessor/spring/processor/SpringServiceV2.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ class SpringServiceV2(
3737
return "spring"
3838
}
3939

40+
@Suppress("UNCHECKED_CAST")
4041
override fun run(processorOptions: MutableMap<String, *>) {
4142
try {
4243
val processor = SpringProcessor()
4344
if (testMode) {
4445
processor.enableTestMode()
4546
}
46-
processor.run(processorOptions)
47+
processor.run(processorOptions as Map<String, Any>)
4748

4849
sourceRoot = processor.sourceRoot
4950
resourceRoot = processor.resourceRoot
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$id": "https://openapiprocessor.io/schemas/mapping/mapping-spring-v1.json",
3+
"$schema": "http://json-schema.org/draft-07/schema#",
4+
"title": "JSON Schema for openapi-processor-spring mapping.yaml v1",
5+
"description": "openapi-processor-spring configuration and type mappings",
6+
"allOf": [
7+
{ "$ref": "#/definitions/SpringMapping" },
8+
{ "$ref": "https://openapiprocessor.io/schemas/mapping/mapping-v18.json" }
9+
],
10+
"definitions": {
11+
"SpringMapping": {
12+
"type": "object",
13+
"properties": {
14+
"openapi-processor-spring": {
15+
"description": "version of the spring mapping format.",
16+
"enum": ["v1"]
17+
},
18+
"openapi-processor-mapping": {
19+
"description": "version of the mapping format.",
20+
"type": "string"
21+
},
22+
"options": {
23+
"$ref": "#/definitions/SpringOptions"
24+
}
25+
},
26+
"oneOf": [
27+
{ "required": ["openapi-processor-spring"] },
28+
{ "required": ["openapi-processor-mapping"] }
29+
]
30+
},
31+
"SpringOptions": {
32+
"description": "spring processor configuration options.",
33+
"type": "object",
34+
"properties": {
35+
"spring": {
36+
"type": "object",
37+
"properties": {
38+
"annotations": {
39+
"type": "string",
40+
"enum": [
41+
"mapping",
42+
"exchange"
43+
]
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)