Skip to content

Commit 26b9ef6

Browse files
committed
write schema annotation (#225)
1 parent e9eb82a commit 26b9ef6

File tree

9 files changed

+299
-0
lines changed

9 files changed

+299
-0
lines changed

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/converter/MappingFinder.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,28 @@ class MappingFinder(val options: ApiOptions) {
231231
return repository.findGlobalAnnotationTypeMapping(query, step)
232232
}
233233

234+
fun findAnnotationSchemaTypeMappings(sourceName: String): List<AnnotationTypeMapping> {
235+
val (type, format) = splitTypeName(sourceName)
236+
return findAnnotationSchemaTypeMappings(
237+
MappingFinderQuery(
238+
type = type,
239+
format = format)
240+
)
241+
}
242+
243+
private fun findAnnotationSchemaTypeMappings(query: MappingQuery): List<AnnotationTypeMapping> {
244+
val step = rootStep("looking for annotation schema type mapping of", query)
245+
try {
246+
return findAnnotationSchemaTypeMappings(query, step)
247+
} finally {
248+
step.log()
249+
}
250+
}
251+
252+
private fun findAnnotationSchemaTypeMappings(query: MappingQuery, step: MappingStep): List<AnnotationTypeMapping> {
253+
return repository.findGlobalAnnotationSchemaTypeMapping(query, step)
254+
}
255+
234256
fun findParameterTypeMapping(query: MappingQuery): TypeMapping? {
235257
val step = rootStep("looking for parameter type mapping of", query)
236258
try {

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/converter/mapping/MappingRepository.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ class MappingRepository(
4242
return globalMappings.findAnnotationTypeMapping(AnnotationTypeMatcher(query), step.add(GlobalsStep()))
4343
}
4444

45+
fun findGlobalAnnotationSchemaTypeMapping(query: MappingQuery, step: MappingStep): List<AnnotationTypeMapping> {
46+
return globalMappings.findAnnotationSchemaTypeMapping(AnnotationTypeMatcher(query), step.add(GlobalsStep()))
47+
}
48+
4549
fun findGlobalParameterTypeMapping(query: MappingQuery, step: MappingStep): TypeMapping? {
4650
return globalMappings.findParameterTypeMapping(TypeMatcher(query), step.add(GlobalsStep()))
4751
}

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/converter/mapping/Mappings.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Mappings(
1515
private val multiTypeMapping: TypeMapping? = null,
1616
private val nullTypeMapping: NullTypeMapping? = null,
1717
private val typeMappings: TypeMappings = TypeMappings(),
18+
private val schemaMappings: TypeMappings = TypeMappings(),
1819
private val parameterTypeMappings: TypeMappings = TypeMappings(),
1920
private val responseTypeMappings: TypeMappings = TypeMappings(),
2021
private val exclude: Boolean = false
@@ -76,6 +77,28 @@ class Mappings(
7677
return mappings.map { it as AnnotationTypeMapping }
7778
}
7879

80+
fun findSchemaTypeMapping(filter: MappingMatcher, step: MappingStep): TypeMapping? {
81+
val mappings = schemaMappings.filter(filter, step.add(SchemasStep()))
82+
if (mappings.isEmpty()) {
83+
return null
84+
}
85+
86+
if (mappings.size > 1) {
87+
throw AmbiguousTypeMappingException(mappings)
88+
}
89+
90+
return mappings.first() as TypeMapping
91+
}
92+
93+
fun findAnnotationSchemaTypeMapping(filter: MappingMatcher, step: MappingStep): List<AnnotationTypeMapping> {
94+
val mappings = schemaMappings.filter(filter, step.add(SchemasStep()))
95+
if (mappings.isEmpty()) {
96+
return emptyList()
97+
}
98+
99+
return mappings.map { it as AnnotationTypeMapping }
100+
}
101+
79102
fun findParameterTypeMapping(filter: MappingMatcher, step: MappingStep): TypeMapping? {
80103
val mappings = parameterTypeMappings.filter(filter, step.add(ParametersStep("type")))
81104
if (mappings.isEmpty()) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor-base/openapi-processor-core
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.converter.mapping.steps
7+
8+
class SchemasStep: ItemsStep() {
9+
10+
override fun isEqual(step: MappingStep): Boolean {
11+
return step is SchemasStep
12+
}
13+
14+
override fun log(indent: String) {
15+
if (!hasMappings()) {
16+
return
17+
}
18+
19+
val prefix = if (isMatch()) {
20+
"$indent$MATCH"
21+
} else {
22+
"$indent$NO_MATCH"
23+
}
24+
25+
log(prefix, "schemas")
26+
steps.forEach {
27+
it.log("$indent ")
28+
}
29+
}
30+
}

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/processor/mapping/v2/MappingConverter.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class MappingConverter(val mapping: MappingV2) {
3535
var singleTypeMapping: TypeMapping? = null
3636
var multiTypeMapping: TypeMapping? = null
3737
val typeMappings = mutableListOf<Mapping>()
38+
val schemaTypeMappings = mutableListOf<Mapping>()
3839
val parameterTypeMappings = mutableListOf<Mapping>()
3940
val responseTypeMappings = mutableListOf<Mapping>()
4041

@@ -58,6 +59,10 @@ class MappingConverter(val mapping: MappingV2) {
5859
typeMappings.add(convertType(it))
5960
}
6061

62+
map.schemas.forEach {
63+
schemaTypeMappings.add(convertType(it))
64+
}
65+
6166
map.parameters.forEach {
6267
parameterTypeMappings.add (convertParameter (it))
6368
}
@@ -73,6 +78,7 @@ class MappingConverter(val mapping: MappingV2) {
7378
multiTypeMapping,
7479
null,
7580
TypeMappings(typeMappings),
81+
TypeMappings(schemaTypeMappings),
7682
TypeMappings(parameterTypeMappings),
7783
TypeMappings(responseTypeMappings))
7884
}
@@ -317,6 +323,7 @@ class MappingConverter(val mapping: MappingV2) {
317323
var multiTypeMapping: TypeMapping? = null
318324
var nullTypeMapping: NullTypeMapping? = null
319325
val typeMappings = mutableListOf<Mapping>()
326+
val schemaTypeMappings = mutableListOf<Mapping>()
320327
val parameterTypeMappings = mutableListOf<Mapping>()
321328
val responseTypeMappings = mutableListOf<Mapping>()
322329

@@ -340,6 +347,10 @@ class MappingConverter(val mapping: MappingV2) {
340347
typeMappings.add(convertType(it))
341348
}
342349

350+
source.schemas.forEach {
351+
schemaTypeMappings.add(convertType(it))
352+
}
353+
343354
source.parameters.forEach {
344355
parameterTypeMappings.add (convertParameter (it))
345356
}
@@ -355,6 +366,7 @@ class MappingConverter(val mapping: MappingV2) {
355366
multiTypeMapping,
356367
nullTypeMapping,
357368
TypeMappings(typeMappings),
369+
TypeMappings(schemaTypeMappings),
358370
TypeMappings(parameterTypeMappings),
359371
TypeMappings(responseTypeMappings),
360372
source.exclude)
@@ -366,6 +378,7 @@ class MappingConverter(val mapping: MappingV2) {
366378
var multiTypeMapping: TypeMapping? = null
367379
var nullTypeMapping: NullTypeMapping? = null
368380
val typeMappings = mutableListOf<Mapping>()
381+
val schemaTypeMappings = mutableListOf<Mapping>()
369382
val parameterTypeMappings = mutableListOf<Mapping>()
370383
val responseTypeMappings = mutableListOf<Mapping>()
371384

@@ -393,6 +406,10 @@ class MappingConverter(val mapping: MappingV2) {
393406
typeMappings.add(convertType(it))
394407
}
395408

409+
source.schemas.forEach {
410+
schemaTypeMappings.add(convertType(it))
411+
}
412+
396413
source.parameters.forEach {
397414
parameterTypeMappings.add (convertParameter (it))
398415
}
@@ -408,6 +425,7 @@ class MappingConverter(val mapping: MappingV2) {
408425
multiTypeMapping,
409426
nullTypeMapping,
410427
TypeMappings(typeMappings),
428+
TypeMappings(schemaTypeMappings),
411429
TypeMappings(parameterTypeMappings),
412430
TypeMappings(responseTypeMappings),
413431
source.exclude

openapi-processor-core/src/main/kotlin/io/openapiprocessor/core/writer/java/DataTypeWriterBase.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ abstract class DataTypeWriterBase(
113113
result += builder.toString()
114114
}
115115

116+
val schemaBuilder = StringBuilder()
117+
writeAnnotations(schemaBuilder, collectSchemaAnnotations(propDataType.dataType.getSourceName()))
118+
result += schemaBuilder.toString()
119+
116120
val extBuilder = StringBuilder()
117121
writeAnnotations(extBuilder, collectExtensionAnnotations(propDataType.extensions))
118122
result += extBuilder.toString()
@@ -136,6 +140,12 @@ abstract class DataTypeWriterBase(
136140
.map { Annotation(it.annotation.type, it.annotation.parameters) }
137141
}
138142

143+
private fun collectSchemaAnnotations(sourceName: String): Collection<Annotation> {
144+
return MappingFinder(apiOptions)
145+
.findAnnotationSchemaTypeMappings(sourceName)
146+
.map { Annotation(it.annotation.type, it.annotation.parameters) }
147+
}
148+
139149
private fun collectExtensionAnnotations(extensions: Map<String, *>): Collection<Annotation> {
140150
val mappingFinder = MappingFinder(apiOptions)
141151

@@ -325,6 +335,17 @@ abstract class DataTypeWriterBase(
325335
}
326336
}
327337

338+
val schemaAnnotations = collectSchemaAnnotations(target.getSourceName())
339+
schemaAnnotations.forEach { annotation ->
340+
imports.addAll(annotation.imports)
341+
342+
annotation.parameters.forEach {
343+
val import = it.value.import
344+
if (import != null)
345+
imports.add(import)
346+
}
347+
}
348+
328349
val extAnnotations = collectExtensionAnnotations(propData.propDataType.extensions)
329350
extAnnotations.forEach { annotation ->
330351
imports.addAll(annotation.imports)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor/openapi-processor-base
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiprocessor.core.processor.mapping.v2
7+
8+
import io.kotest.core.spec.style.StringSpec
9+
import io.kotest.matchers.collections.shouldBeEmpty
10+
import io.kotest.matchers.collections.shouldHaveSize
11+
import io.kotest.matchers.maps.shouldBeEmpty
12+
import io.kotest.matchers.shouldBe
13+
import io.mockk.mockk
14+
import io.openapiprocessor.core.converter.mapping.steps.SchemasStep
15+
import io.openapiprocessor.core.processor.MappingReader
16+
import io.openapiprocessor.core.support.annotationTypeMatcher
17+
import io.openapiprocessor.core.support.typeMatcher
18+
import org.slf4j.Logger
19+
20+
class MappingConverterSchemaSpec: StringSpec({
21+
22+
val reader = MappingReader()
23+
reader.log = mockk<Logger>(relaxed = true)
24+
25+
"read global schema type mapping" {
26+
val yaml = """
27+
|openapi-processor-mapping: v11
28+
|map:
29+
| schemas:
30+
| - type: integer:year => java.time.Year
31+
""".trimMargin()
32+
33+
// when:
34+
val mapping = reader.read (yaml) as Mapping
35+
val mappings = MappingConverter(mapping).convert().globalMappings
36+
37+
// then:
38+
val typeMapping = mappings.findSchemaTypeMapping(
39+
typeMatcher(name = "integer", format = "year"), SchemasStep())!!
40+
41+
typeMapping.sourceTypeName shouldBe "integer"
42+
typeMapping.sourceTypeFormat shouldBe "year"
43+
typeMapping.targetTypeName shouldBe "java.time.Year"
44+
typeMapping.genericTypes.shouldBeEmpty()
45+
}
46+
47+
"read global annotation schema type mapping" {
48+
val yaml = """
49+
|openapi-processor-mapping: v11
50+
|
51+
|options:
52+
| package-name: io.openapiprocessor.somewhere
53+
|
54+
|map:
55+
| schemas:
56+
| - type: integer:year @ io.openapiprocessor.Annotation
57+
""".trimMargin()
58+
59+
val mapping = reader.read(yaml) as Mapping
60+
val mappings = MappingConverter(mapping).convert().globalMappings
61+
62+
val annotationMappings = mappings.findAnnotationSchemaTypeMapping(
63+
annotationTypeMatcher(type = "integer", format = "year"), SchemasStep())
64+
65+
annotationMappings shouldHaveSize 1
66+
val annotationMapping = annotationMappings.first()
67+
annotationMapping.sourceTypeName shouldBe "integer"
68+
annotationMapping.sourceTypeFormat shouldBe "year"
69+
annotationMapping.annotation.type shouldBe "io.openapiprocessor.Annotation"
70+
annotationMapping.annotation.parameters.shouldBeEmpty()
71+
}
72+
})

openapi-processor-core/src/test/kotlin/io/openapiprocessor/core/writer/java/DataTypeWriterPojoSpec.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,4 +686,56 @@ class DataTypeWriterPojoSpec: StringSpec({
686686
|
687687
""".trimMargin()
688688
}
689+
690+
"writes additional annotation from schema annotation mapping" {
691+
val opts = parseOptions(
692+
"""
693+
|openapi-processor-mapping: v11
694+
|options:
695+
| package-name: pkg
696+
|map:
697+
| types:
698+
| - type: integer:year => java.time.Year
699+
| schemas:
700+
| - type: integer:year @ foo.Bar
701+
""".trimMargin())
702+
703+
val dataType = ObjectDataType("Foo",
704+
"pkg", linkedMapOf(
705+
"foo" to propertyDataType(
706+
MappedDataType("Year", "java.time",
707+
sourceDataType = StringDataType("integer:year")))
708+
))
709+
710+
// when:
711+
writer(opts).write(target, dataType)
712+
713+
// then:
714+
target.toString() shouldBeEqual
715+
"""package pkg;
716+
|
717+
|import com.fasterxml.jackson.annotation.JsonProperty;
718+
|import foo.Bar;
719+
|import io.openapiprocessor.generated.support.Generated;
720+
|import java.time.Year;
721+
|
722+
|@Generated
723+
|public class Foo {
724+
|
725+
| @Bar
726+
| @JsonProperty("foo")
727+
| private Year foo;
728+
|
729+
| public Year getFoo() {
730+
| return foo;
731+
| }
732+
|
733+
| public void setFoo(Year foo) {
734+
| this.foo = foo;
735+
| }
736+
|
737+
|}
738+
|
739+
""".trimMargin()
740+
}
689741
})

0 commit comments

Comments
 (0)