diff --git a/README.md b/README.md index cb9b9713..63cd75cd 100644 --- a/README.md +++ b/README.md @@ -186,53 +186,57 @@ data class Responses( This section documents the available CLI parameters for controlling what gets generated. This documentation is generated using: `./gradlew printCodeGenUsage` -| Parameter | Description | -| ---------------------------- | ---------------------------- | -| `--api-file` | This must be a valid Open API v3 spec. All code generation will be based off this input. | -| `--api-fragment` | A partial Open API v3 fragment, to be combined with the primary API for code generation purposes. | -| * `--base-package` | The base package which all code will be generated under. | -| `--http-client-opts` | Select the options for the http client code that you want to be generated. | -| | CHOOSE ANY OF: | -| | `RESILIENCE4J` - Generates a fault tolerance service for the client using the following library "io.github.resilience4j:resilience4j-all:+" (only for OkHttp clients) | -| | `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated client functions (only for OpenFeign clients) | -| `--http-client-target` | Optionally select the target client that you want to be generated. Defaults to OK_HTTP | -| | CHOOSE ONE OF: | -| | `OK_HTTP` - Generate OkHttp client. | -| | `OPEN_FEIGN` - Generate OpenFeign client. | -| `--http-controller-opts` | Select the options for the controllers that you want to be generated. | -| | CHOOSE ANY OF: | -| | `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated controller functions | -| | `AUTHENTICATION` - This option adds the authentication parameter to the generated controller functions | -| `--http-controller-target` | Optionally select the target framework for the controllers that you want to be generated. Defaults to Spring Controllers | -| | CHOOSE ONE OF: | -| | `SPRING` - Generate for Spring framework. | -| | `MICRONAUT` - Generate for Micronaut framework. | -| `--http-model-opts` | Select the options for the http models that you want to be generated. | -| | CHOOSE ANY OF: | -| | `X_EXTENSIBLE_ENUMS` - This option treats x-extensible-enums as enums | -| | `JAVA_SERIALIZATION` - This option adds Java Serializable interface to the generated models | -| | `QUARKUS_REFLECTION` - This option adds @RegisterForReflection to the generated models. Requires dependency "'io.quarkus:quarkus-core:+" | -| | `MICRONAUT_INTROSPECTION` - This option adds @Introspected to the generated models. Requires dependency "'io.micronaut:micronaut-core:+" | -| | `MICRONAUT_REFLECTION` - This option adds @ReflectiveAccess to the generated models. Requires dependency "'io.micronaut:micronaut-core:+" | -| | `INCLUDE_COMPANION_OBJECT` - This option adds a companion object to the generated models. | -| | `SEALED_INTERFACES_FOR_ONE_OF` - This option enables the generation of interfaces for discriminated oneOf types | -| `--output-directory` | Allows the generation dir to be overridden. Defaults to current dir | -| `--resources-path` | Allows the path for generated resources to be overridden. Defaults to `src/main/resources` | -| `--src-path` | Allows the path for generated source files to be overridden. Defaults to `src/main/kotlin` | -| `--targets` | Targets are the parts of the application that you want to be generated. | -| | CHOOSE ANY OF: | -| | `HTTP_MODELS` - Jackson annotated data classes to represent the schema objects defined in the input. | -| | `CONTROLLERS` - Spring / Micronaut annotated HTTP controllers for each of the endpoints defined in the input. | -| | `CLIENT` - Simple http rest client. | -| | `QUARKUS_REFLECTION_CONFIG` - This options generates the reflection-config.json file for quarkus integration projects | -| `--type-overrides` | Specify non-default kotlin types for certain OAS types. For example, generate `Instant` instead of `OffsetDateTime` | -| | CHOOSE ANY OF: | -| | `DATETIME_AS_INSTANT` - Use `Instant` as the datetime type. Defaults to `OffsetDateTime` | -| | `DATETIME_AS_LOCALDATETIME` - Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime` | -| `--validation-library` | Specify which validation library to use for annotations in generated model classes. Default: JAVAX_VALIDATION | -| | CHOOSE ONE OF: | -| | `JAVAX_VALIDATION` - Use `javax.validation` annotations in generated model classes (default) | -| | `JAKARTA_VALIDATION` - Use `jakarta.validation` annotations in generated model classes | +| Parameter | Description | +| ----------------------------- | ----------------------------- | +| `--api-file` | This must be a valid Open API v3 spec. All code generation will be based off this input. | +| `--api-fragment` | A partial Open API v3 fragment, to be combined with the primary API for code generation purposes. | +| * `--base-package` | The base package which all code will be generated under. | +| `--external-ref-resolution` | Specify to which degree referenced schemas from external files are included in model generation. Default: TARGETED | +| | CHOOSE ONE OF: | +| | `TARGETED` - Generate models only for directly referenced schemas in external API files. | +| | `AGGRESSIVE` - Referencing any schema in an external API file triggers generation of every external schema in that file. | +| `--http-client-opts` | Select the options for the http client code that you want to be generated. | +| | CHOOSE ANY OF: | +| | `RESILIENCE4J` - Generates a fault tolerance service for the client using the following library "io.github.resilience4j:resilience4j-all:+" (only for OkHttp clients) | +| | `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated client functions (only for OpenFeign clients) | +| `--http-client-target` | Optionally select the target client that you want to be generated. Defaults to OK_HTTP | +| | CHOOSE ONE OF: | +| | `OK_HTTP` - Generate OkHttp client. | +| | `OPEN_FEIGN` - Generate OpenFeign client. | +| `--http-controller-opts` | Select the options for the controllers that you want to be generated. | +| | CHOOSE ANY OF: | +| | `SUSPEND_MODIFIER` - This option adds the suspend modifier to the generated controller functions | +| | `AUTHENTICATION` - This option adds the authentication parameter to the generated controller functions | +| `--http-controller-target` | Optionally select the target framework for the controllers that you want to be generated. Defaults to Spring Controllers | +| | CHOOSE ONE OF: | +| | `SPRING` - Generate for Spring framework. | +| | `MICRONAUT` - Generate for Micronaut framework. | +| `--http-model-opts` | Select the options for the http models that you want to be generated. | +| | CHOOSE ANY OF: | +| | `X_EXTENSIBLE_ENUMS` - This option treats x-extensible-enums as enums | +| | `JAVA_SERIALIZATION` - This option adds Java Serializable interface to the generated models | +| | `QUARKUS_REFLECTION` - This option adds @RegisterForReflection to the generated models. Requires dependency "'io.quarkus:quarkus-core:+" | +| | `MICRONAUT_INTROSPECTION` - This option adds @Introspected to the generated models. Requires dependency "'io.micronaut:micronaut-core:+" | +| | `MICRONAUT_REFLECTION` - This option adds @ReflectiveAccess to the generated models. Requires dependency "'io.micronaut:micronaut-core:+" | +| | `INCLUDE_COMPANION_OBJECT` - This option adds a companion object to the generated models. | +| | `SEALED_INTERFACES_FOR_ONE_OF` - This option enables the generation of interfaces for discriminated oneOf types | +| `--output-directory` | Allows the generation dir to be overridden. Defaults to current dir | +| `--resources-path` | Allows the path for generated resources to be overridden. Defaults to `src/main/resources` | +| `--src-path` | Allows the path for generated source files to be overridden. Defaults to `src/main/kotlin` | +| `--targets` | Targets are the parts of the application that you want to be generated. | +| | CHOOSE ANY OF: | +| | `HTTP_MODELS` - Jackson annotated data classes to represent the schema objects defined in the input. | +| | `CONTROLLERS` - Spring / Micronaut annotated HTTP controllers for each of the endpoints defined in the input. | +| | `CLIENT` - Simple http rest client. | +| | `QUARKUS_REFLECTION_CONFIG` - This options generates the reflection-config.json file for quarkus integration projects | +| `--type-overrides` | Specify non-default kotlin types for certain OAS types. For example, generate `Instant` instead of `OffsetDateTime` | +| | CHOOSE ANY OF: | +| | `DATETIME_AS_INSTANT` - Use `Instant` as the datetime type. Defaults to `OffsetDateTime` | +| | `DATETIME_AS_LOCALDATETIME` - Use `LocalDateTime` as the datetime type. Defaults to `OffsetDateTime` | +| `--validation-library` | Specify which validation library to use for annotations in generated model classes. Default: JAVAX_VALIDATION | +| | CHOOSE ONE OF: | +| | `JAVAX_VALIDATION` - Use `javax.validation` annotations in generated model classes (default) | +| | `JAKARTA_VALIDATION` - Use `jakarta.validation` annotations in generated model classes | ### Command Line Fabrikt is packaged as an executable jar, allowing it to be integrated into any build tool. The CLI can be invoked as follows: diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt index f34424bf..ddd1f39b 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt @@ -33,7 +33,8 @@ object CodeGen { codeGenArgs.typeOverrides, codeGenArgs.srcPath, codeGenArgs.resourcesPath, - codeGenArgs.validationLibrary + codeGenArgs.validationLibrary, + codeGenArgs.externalRefResolutionMode ) } @@ -51,9 +52,10 @@ object CodeGen { typeOverrides: Set, srcPath: Path, resourcesPath: Path, - validationLibrary: ValidationLibrary + validationLibrary: ValidationLibrary, + externalRefResolutionMode: ExternalReferencesResolutionMode ) { - MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, clientTarget, typeOverrides, validationLibrary) + MutableSettings.updateSettings(codeGenTypes, controllerOptions, controllerTarget, modelOptions, clientOptions, clientTarget, typeOverrides, validationLibrary, externalRefResolutionMode) val suppliedApi = pathToApi.toFile().readText() val baseDir = pathToApi.parent diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt index b4f30f8e..4f38b042 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt @@ -140,6 +140,13 @@ class CodeGenArgs { converter = ValidationLibraryOptionConverter::class ) var validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION + + @Parameter( + names = ["--external-ref-resolution"], + description = "Specify to which degree referenced schemas from external files are included in model generation. Default: TARGETED", + converter = ExternalReferencesResolutionModeConverter::class + ) + var externalRefResolutionMode: ExternalReferencesResolutionMode = ExternalReferencesResolutionMode.TARGETED } class CodeGenerationTypesConverter : IStringConverter { @@ -176,6 +183,10 @@ class TypeCodeGenOptionsConverter: IStringConverter { override fun convert(value: String): CodeGenTypeOverride = convertToEnumValue(value) } +class ExternalReferencesResolutionModeConverter: IStringConverter { + override fun convert(value: String): ExternalReferencesResolutionMode = convertToEnumValue(value) +} + class PackageNameValidator : IValueValidator { override fun validate(name: String, value: String) { if (!value.isValidJavaPackage()) { diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt index 0bdf7ea7..11541c17 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt @@ -71,5 +71,12 @@ enum class CodeGenTypeOverride(val description: String) { enum class ValidationLibrary(val description: String, val annotations: ValidationAnnotations) { JAVAX_VALIDATION("Use `javax.validation` annotations in generated model classes (default)", JavaxValidationAnnotations), JAKARTA_VALIDATION("Use `jakarta.validation` annotations in generated model classes", JakartaAnnotations); + override fun toString() = "`${super.toString()}` - $description" +} + +enum class ExternalReferencesResolutionMode(val description: String) { + TARGETED("Generate models only for directly referenced schemas in external API files."), + AGGRESSIVE("Referencing any schema in an external API file triggers generation of every external schema in that file."); + override fun toString() = "`${super.toString()}` - $description" } \ No newline at end of file diff --git a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt index a29de27a..6fba3c47 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt @@ -12,7 +12,6 @@ import com.cjbooms.fabrikt.generators.controller.MicronautControllerInterfaceGen import com.cjbooms.fabrikt.generators.controller.SpringControllerInterfaceGenerator import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator import com.cjbooms.fabrikt.generators.model.QuarkusReflectionModelGenerator -import com.cjbooms.fabrikt.model.Clients import com.cjbooms.fabrikt.model.GeneratedFile import com.cjbooms.fabrikt.model.KotlinSourceSet import com.cjbooms.fabrikt.model.KotlinTypes @@ -27,7 +26,7 @@ class CodeGenerator( private val packages: Packages, private val sourceApi: SourceApi, private val srcPath: Path, - private val resourcesPath: Path + private val resourcesPath: Path, ) { fun generate(): Collection = MutableSettings.generationTypes().map(::generateCode).flatten() @@ -63,7 +62,7 @@ class CodeGenerator( private fun resourceSet(resFiles: Collection) = setOf(ResourceSourceSet(resFiles, resourcesPath)) private fun models(): Models = - JacksonModelGenerator(packages, sourceApi, MutableSettings.modelOptions(), MutableSettings.validationLibrary().annotations).generate() + JacksonModelGenerator(packages, sourceApi, MutableSettings.modelOptions(), MutableSettings.validationLibrary().annotations, MutableSettings.externalRefResolutionMode()).generate() private fun resources(models: Models): List = listOfNotNull(QuarkusReflectionModelGenerator(models, MutableSettings.generationTypes()).generate()) @@ -75,14 +74,14 @@ class CodeGenerator( packages, sourceApi, MutableSettings.validationLibrary().annotations, - MutableSettings.controllerOptions() + MutableSettings.controllerOptions(), ) ControllerCodeGenTargetType.MICRONAUT -> MicronautControllerInterfaceGenerator( packages, sourceApi, MutableSettings.validationLibrary().annotations, - MutableSettings.controllerOptions() + MutableSettings.controllerOptions(), ) } return generator.generate() diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt index f3b85e2e..af496682 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/MutableSettings.kt @@ -11,6 +11,7 @@ object MutableSettings { private lateinit var clientTarget: ClientCodeGenTargetType private lateinit var typeOverrides: MutableSet private lateinit var validationLibrary: ValidationLibrary + private lateinit var externalRefResolutionMode: ExternalReferencesResolutionMode fun updateSettings( genTypes: Set = emptySet(), @@ -20,7 +21,8 @@ object MutableSettings { clientOptions: Set = emptySet(), clientTarget: ClientCodeGenTargetType = ClientCodeGenTargetType.OK_HTTP, typeOverrides: Set = emptySet(), - validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION + validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION, + externalRefResolutionMode: ExternalReferencesResolutionMode = ExternalReferencesResolutionMode.TARGETED, ) { this.generationTypes = genTypes.toMutableSet() this.controllerOptions = controllerOptions.toMutableSet() @@ -30,6 +32,7 @@ object MutableSettings { this.clientTarget = clientTarget this.typeOverrides = typeOverrides.toMutableSet() this.validationLibrary = validationLibrary + this.externalRefResolutionMode = externalRefResolutionMode } fun addOption(option: ModelCodeGenOptionType) = modelOptions.add(option) @@ -43,4 +46,5 @@ object MutableSettings { fun clientTarget() = this.clientTarget fun typeOverrides() = this.typeOverrides.toSet() fun validationLibrary() = this.validationLibrary + fun externalRefResolutionMode() = this.externalRefResolutionMode } diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt index 880a6795..07d7ae25 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt @@ -1,5 +1,6 @@ package com.cjbooms.fabrikt.generators.model +import com.cjbooms.fabrikt.cli.ExternalReferencesResolutionMode import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType.SEALED_INTERFACES_FOR_ONE_OF import com.cjbooms.fabrikt.configurations.Packages @@ -27,6 +28,7 @@ import com.cjbooms.fabrikt.model.PropertyInfo.Companion.HTTP_SETTINGS import com.cjbooms.fabrikt.model.PropertyInfo.Companion.topLevelProperties import com.cjbooms.fabrikt.model.SchemaInfo import com.cjbooms.fabrikt.model.SourceApi +import com.cjbooms.fabrikt.model.toEnclosingSchemaInfo import com.cjbooms.fabrikt.util.KaizenParserExtensions.getDiscriminatorForInLinedObjectUnderAllOf import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSchemaRefName import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSuperType @@ -41,8 +43,7 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isPolymorphicSuperType import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleType import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKeys import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName -import com.cjbooms.fabrikt.util.KaizenParserExtensions.toMapValueClassName -import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.NormalisedString.toEnumName import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName import com.reprezen.jsonoverlay.Overlay @@ -69,6 +70,7 @@ class JacksonModelGenerator( private val sourceApi: SourceApi, private val options: Set = emptySet(), private val validationAnnotations: ValidationAnnotations = JavaxValidationAnnotations, + private val externalRefResolutionMode: ExternalReferencesResolutionMode = ExternalReferencesResolutionMode.TARGETED, ) { companion object { fun toModelType(basePackage: String, typeInfo: KotlinTypeInfo, isNullable: Boolean = false): TypeName { @@ -148,9 +150,8 @@ class JacksonModelGenerator( val models: MutableSet = createModels(sourceApi.openApi3, sourceApi.allSchemas) externalApiSchemas.forEach { externalReferences -> val api = OpenApi3Parser().parse(externalReferences.key) - val schemas = api.schemas.entries.map { it.key to it.value } - .map { (key, schema) -> SchemaInfo(key, schema) } - .filter { apiSchema -> externalReferences.value.contains(apiSchema.name) } + val schemas = api.schemas.entries.map { (key, schema) -> SchemaInfo(key, schema) } + .filterByExternalRefResolutionMode(externalReferences) val externalModels = createModels(api, schemas) externalModels.forEach { additionalModel -> if (models.none { it.name == additionalModel.name }) models.add(additionalModel) @@ -174,7 +175,7 @@ class JacksonModelGenerator( schemaName = it.schema.safeName(), enclosingSchema = it.schema, apiDocUrl = it.schema.getDocumentUrl(), - enclosingModelName = it.name, + enclosingSchemaInfoName = it.name, ) } else { emptyList() @@ -187,7 +188,7 @@ class JacksonModelGenerator( properties: Collection, allSchemas: List, ): TypeSpec { - val modelName = schemaInfo.name.toModelClassName() + val modelName = ModelNameRegistry.getOrRegister(schemaInfo) val schemaName = schemaInfo.schema.getSchemaRefName() return when { schemaInfo.schema.isOneOfSuperInterface() && SEALED_INTERFACES_FOR_ONE_OF in options -> oneOfSuperInterface( @@ -275,7 +276,6 @@ class JacksonModelGenerator( enclosingSchema: Schema, apiDocUrl: String, ): List = topLevelProperties.flatMap { - val enclosingModelName = enclosingSchema.toModelClassName() if (it.schema.isInExternalDocument(apiDocUrl)) { it.schema.captureMissingExternalSchemas(apiDocUrl) emptySet() @@ -284,7 +284,7 @@ class JacksonModelGenerator( is PropertyInfo.ObjectInlinedField -> { val props = it.schema.topLevelProperties(HTTP_SETTINGS, enclosingSchema) val currentModel = standardDataClass( - it.name.toModelClassName(enclosingModelName), + ModelNameRegistry.getOrRegister(it.schema, enclosingSchema.toEnclosingSchemaInfo()), it.name, props, it.schema.extensions, @@ -302,7 +302,7 @@ class JacksonModelGenerator( if (it.schema.isComplexTypedAdditionalProperties("additionalProperties")) { setOf( standardDataClass( - modelName = if (it.schema.isInlinedTypedAdditionalProperties()) it.schema.toMapValueClassName() else it.schema.toModelClassName(), + modelName = ModelNameRegistry.getOrRegister(it.schema, valueSuffix = it.schema.isInlinedTypedAdditionalProperties()), schemaName = it.name, properties = it.schema.topLevelProperties(HTTP_SETTINGS, enclosingSchema), extensions = it.schema.extensions, @@ -321,7 +321,7 @@ class JacksonModelGenerator( } is PropertyInfo.ListField -> - buildInlinedListDefinition(it.schema, it.name, enclosingSchema, apiDocUrl, enclosingModelName) + buildInlinedListDefinition(it.schema, it.name, enclosingSchema, apiDocUrl) is PropertyInfo.OneOfAny -> emptySet() } @@ -333,9 +333,11 @@ class JacksonModelGenerator( schemaName: String, enclosingSchema: Schema, apiDocUrl: String, - enclosingModelName: String, + enclosingSchemaInfoName: String? = null, ): Collection = schema.itemsSchema.let { items -> + val enclosingSchemaInfo = enclosingSchemaInfoName?.toEnclosingSchemaInfo() + ?: enclosingSchema.toEnclosingSchemaInfo() when { items.isInlinedObjectDefinition() -> items.topLevelProperties(HTTP_SETTINGS, enclosingSchema).let { props -> @@ -344,7 +346,7 @@ class JacksonModelGenerator( enclosingSchema = enclosingSchema, apiDocUrl = apiDocUrl, ) + standardDataClass( - modelName = schema.toModelClassName(enclosingModelName), + modelName = ModelNameRegistry.getOrRegister(schema, enclosingSchemaInfo), schemaName = schemaName, properties = props, extensions = schema.extensions, @@ -355,7 +357,7 @@ class JacksonModelGenerator( items.isInlinedEnumDefinition() -> setOf( buildEnumClass( - KotlinTypeInfo.from(items, "items", enclosingModelName) as KotlinTypeInfo.Enum, + KotlinTypeInfo.from(items, "items", enclosingSchemaInfo) as KotlinTypeInfo.Enum, ), ) @@ -443,7 +445,7 @@ class JacksonModelGenerator( if (mapField.schema.additionalPropertiesSchema.isComplexTypedAdditionalProperties("additionalProperties")) { val schema = mapField.schema.additionalPropertiesSchema standardDataClass( - modelName = if (schema.isInlinedTypedAdditionalProperties()) schema.toMapValueClassName() else schema.toModelClassName(), + modelName = ModelNameRegistry.getOrRegister(schema, valueSuffix = schema.isInlinedTypedAdditionalProperties()), schemaName = schema.safeName(), properties = mapField.schema.additionalPropertiesSchema.topLevelProperties(HTTP_SETTINGS), extensions = mapField.schema.extensions, @@ -490,7 +492,7 @@ class JacksonModelGenerator( .addCompanionObject() for (oneOfInterface in oneOfInterfaces) { classBuilder - .addSuperinterface(generatedType(packages.base, oneOfInterface.schema.toModelClassName())) + .addSuperinterface(generatedType(packages.base, ModelNameRegistry.getOrRegister(oneOfInterface.schema))) } if (!generateObject) { @@ -796,6 +798,13 @@ class JacksonModelGenerator( } return this } + + private fun List.filterByExternalRefResolutionMode( + externalReferences: Map.Entry>, + ) = when (externalRefResolutionMode) { + ExternalReferencesResolutionMode.TARGETED -> this.filter { apiSchema -> externalReferences.value.contains(apiSchema.name) } + else -> this + } } private val Map.hasJsonMergePatchExtension diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt index 965f6423..47bb7aa4 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/model/KotlinTypeInfo.kt @@ -7,9 +7,7 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.getEnumValues import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedTypedAdditionalProperties import com.cjbooms.fabrikt.util.KaizenParserExtensions.isNotDefined import com.cjbooms.fabrikt.util.KaizenParserExtensions.isOneOfSuperInterface -import com.cjbooms.fabrikt.util.KaizenParserExtensions.toMapValueClassName -import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName -import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.reprezen.kaizen.oasparser.model3.Schema import java.math.BigDecimal import java.net.URI @@ -57,13 +55,13 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN } companion object { - fun from(schema: Schema, oasKey: String = "", enclosingName: String = ""): KotlinTypeInfo = + fun from(schema: Schema, oasKey: String = "", enclosingSchema: EnclosingSchemaInfo? = null): KotlinTypeInfo = when (schema.toOasType(oasKey)) { OasType.Date -> Date OasType.DateTime -> getOverridableDateTimeType() OasType.Text -> Text OasType.Enum -> - Enum(schema.getEnumValues(), schema.toModelClassName(enclosingName.toModelClassName())) + Enum(schema.getEnumValues(), ModelNameRegistry.getOrRegister(schema, enclosingSchema)) OasType.Uuid -> Uuid OasType.Uri -> Uri OasType.Base64String -> ByteArray @@ -78,24 +76,23 @@ sealed class KotlinTypeInfo(val modelKClass: KClass<*>, val generatedModelClassN OasType.Array -> if (schema.itemsSchema.isNotDefined()) throw IllegalArgumentException("Property ${schema.name} cannot be parsed to a Schema. Check your input") - else Array(from(schema.itemsSchema, oasKey, enclosingName)) - OasType.Object -> Object(schema.toModelClassName(enclosingName.toModelClassName())) + else Array(from(schema.itemsSchema, oasKey, enclosingSchema)) + OasType.Object -> Object(ModelNameRegistry.getOrRegister(schema, enclosingSchema)) OasType.Map -> - Map(from(schema.additionalPropertiesSchema, "", enclosingName)) + Map(from(schema.additionalPropertiesSchema, "", enclosingSchema)) OasType.TypedObjectAdditionalProperties -> GeneratedTypedAdditionalProperties( - if (schema.isInlinedTypedAdditionalProperties()) schema.toMapValueClassName() - else schema.toModelClassName() + ModelNameRegistry.getOrRegister(schema, valueSuffix = schema.isInlinedTypedAdditionalProperties()) ) OasType.UntypedObjectAdditionalProperties -> UntypedObjectAdditionalProperties OasType.UntypedObject -> UntypedObject OasType.UnknownAdditionalProperties -> UnknownAdditionalProperties OasType.TypedMapAdditionalProperties -> MapTypeAdditionalProperties( - from(schema.additionalPropertiesSchema, "", enclosingName) + from(schema.additionalPropertiesSchema, "", enclosingSchema) ) OasType.Any -> AnyType OasType.OneOfAny -> - if (schema.isOneOfSuperInterface()) Object(schema.toModelClassName(enclosingName.toModelClassName())) + if (schema.isOneOfSuperInterface()) Object(ModelNameRegistry.getOrRegister(schema, enclosingSchema)) else AnyType } diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt index 93547108..75842a31 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt @@ -13,7 +13,6 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSchemaLess import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleMapDefinition import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeType -import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName import com.cjbooms.fabrikt.util.NormalisedString.camelCase import com.cjbooms.fabrikt.util.NormalisedString.toEnumName import com.reprezen.kaizen.oasparser.model3.Schema @@ -161,7 +160,7 @@ sealed class PropertyInfo { val enclosingSchema: Schema? = null ) : PropertyInfo() { override val typeInfo: KotlinTypeInfo = - KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toModelClassName() ?: "") + KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toEnclosingSchemaInfo()) val pattern: String? = schema.safeField(Schema::getPattern) val maxLength: Int? = schema.safeField(Schema::getMaxLength) val minLength: Int? = schema.safeField(Schema::getMinLength) @@ -187,7 +186,7 @@ sealed class PropertyInfo { val enclosingSchema: Schema? ) : PropertyInfo(), CollectionValidation { override val typeInfo: KotlinTypeInfo = - KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toModelClassName() ?: "") + KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toEnclosingSchemaInfo()) override val minItems: Int? = schema.minItems override val maxItems: Int? = schema.maxItems } @@ -221,7 +220,7 @@ sealed class PropertyInfo { val enclosingSchema: Schema? ) : PropertyInfo() { override val typeInfo: KotlinTypeInfo = - KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toModelClassName() ?: "") + KotlinTypeInfo.from(schema, oasKey, enclosingSchema?.toEnclosingSchemaInfo()) } data class AdditionalProperties( diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt index a949e473..c276c635 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt @@ -83,3 +83,29 @@ data class SourceApi( } } } + +/** + * Enclosing schema can either be provided as: + * - a schema from Kaizen as OAS3 model. + * - provided as schema name based on names provided in spec file from SchemaInfo + */ +enum class EnclosingSchemaInfoType { + NAME, + OAS_MODEL, +} + +interface EnclosingSchemaInfo { + val type: EnclosingSchemaInfoType +} + +data class EnclosingSchemaInfoName(val name: String) : EnclosingSchemaInfo { + override val type = EnclosingSchemaInfoType.NAME +} + +data class EnclosingSchemaInfoOasModel(val schema: Schema) : EnclosingSchemaInfo { + override val type = EnclosingSchemaInfoType.OAS_MODEL +} + +fun Schema.toEnclosingSchemaInfo() = EnclosingSchemaInfoOasModel(this) + +fun String.toEnclosingSchemaInfo() = EnclosingSchemaInfoName(this) diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt index ca97c3be..93904468 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt @@ -57,10 +57,6 @@ object KaizenParserExtensions { fun Schema.isInlinedArrayDefinition() = isArrayType() && !isSchemaLess() && this.itemsSchema.isInlinedObjectDefinition() - fun Schema.toModelClassName(enclosingClassName: String = "") = enclosingClassName + safeName().toModelClassName() - - fun Schema.toMapValueClassName() = safeName().toMapValueClassName() - fun Schema.isSchemaLess() = isObjectType() && properties?.isEmpty() == true && ( oneOfSchemas?.isNotEmpty() != true && allOfSchemas?.isNotEmpty() != true && diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/ModelNameRegistry.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/ModelNameRegistry.kt new file mode 100644 index 00000000..b0b44512 --- /dev/null +++ b/src/main/kotlin/com/cjbooms/fabrikt/util/ModelNameRegistry.kt @@ -0,0 +1,107 @@ +package com.cjbooms.fabrikt.util + +import com.cjbooms.fabrikt.model.EnclosingSchemaInfo +import com.cjbooms.fabrikt.model.EnclosingSchemaInfoName +import com.cjbooms.fabrikt.model.EnclosingSchemaInfoOasModel +import com.cjbooms.fabrikt.model.SchemaInfo +import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName +import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName +import com.reprezen.jsonoverlay.Overlay +import com.reprezen.kaizen.oasparser.model3.Schema +import java.net.URL + +/** + * Model name registry to avoid name collisions + */ +object ModelNameRegistry { + private val allocatedNames: MutableSet = mutableSetOf() + private val tagToName: MutableMap = mutableMapOf() + private const val SUFFIX = "Extra" + + /** + * Registers a new model class name using `schema` and if it is inlined type also based on enclosed schema. + * The returned value can be queried multiple times by passing `tag` to + * [ModelNameRegistry.get]. + */ + private fun register( + schema: Schema, + enclosingSchema: EnclosingSchemaInfo? = null, + valueSuffix: Boolean = false, + schemaInfoName: String? = null, + ): String { + val modelClassName = schema.toModelClassName(schemaInfoName, enclosingSchema?.toModelClassName(), valueSuffix) + var suggestion = modelClassName + while (allocatedNames.contains(suggestion)) { + suggestion += SUFFIX + } + allocatedNames.add(suggestion) + + val tag = resolveTag(schema, modelClassName) + val replaced = tagToName.put(tag, suggestion) + if (replaced != null) { + // Only allow unique tags to be registered + throw IllegalArgumentException("tag $tag cannot be used for both '$replaced' and '$suggestion'") + } + + return suggestion + } + + private fun Schema.toModelClassName( + schemaInfoName: String? = null, + enclosingClassName: String? = null, + valueSuffix: Boolean = false, + ): String = buildString { + if (enclosingClassName != null) { + append(enclosingClassName) + } + val modelClassName = schemaInfoName?.toModelClassName() ?: safeName().toModelClassName() + append(modelClassName) + if (valueSuffix) { + append("Value") + } + } + + private fun EnclosingSchemaInfo.toModelClassName() = + when (this) { + is EnclosingSchemaInfoName -> this.name + is EnclosingSchemaInfoOasModel -> this.schema.toModelClassName() + else -> "" + } + + private fun resolveTag( + schema: Schema, + enclosingSchema: EnclosingSchemaInfo? = null, + valueSuffix: Boolean = false, + schemaInfoName: String? = null, + ): String = + resolveTag(schema, schema.toModelClassName(schemaInfoName, enclosingSchema?.toModelClassName(), valueSuffix)) + + private fun resolveTag(schema: Schema, modelClassName: String): String { + val overlay = Overlay.of(schema) + val uri = URL(overlay.jsonReference) + return "file:${uri.file}#$modelClassName" + } + + /** Retrieve a model class name created with [ModelNameRegistry.register]. */ + operator fun get(tag: String): Result = runCatching { + requireNotNull(tagToName[tag]) { "unknown tag: $tag" } + } + + fun getOrRegister( + schema: Schema, + enclosingSchema: EnclosingSchemaInfo? = null, + valueSuffix: Boolean = false, + ) = this[resolveTag(schema, enclosingSchema, valueSuffix)] + .getOrElse { register(schema, enclosingSchema, valueSuffix) } + + fun getOrRegister( + schemaInfo: SchemaInfo, + ) = + this[resolveTag(schemaInfo.schema, schemaInfoName = schemaInfo.name)] + .getOrElse { register(schemaInfo.schema, schemaInfoName = schemaInfo.name) } + + fun clear() { + allocatedNames.clear() + tagToName.clear() + } +} diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt index a6823041..06072511 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautAuthenticationTest.kt @@ -6,6 +6,7 @@ import com.cjbooms.fabrikt.cli.ControllerCodeGenTargetType import com.cjbooms.fabrikt.configurations.Packages import com.cjbooms.fabrikt.generators.controller.MicronautControllerInterfaceGenerator import com.cjbooms.fabrikt.model.SourceApi +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.TypeSpec @@ -45,6 +46,7 @@ class MicronautAuthenticationTest { controllerTarget = ControllerCodeGenTargetType.MICRONAUT, controllerOptions = setOf(ControllerCodeGenOptionType.AUTHENTICATION), ) + ModelNameRegistry.clear() } // global authentication tests diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt index 3818200b..28fbb0bf 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/MicronautControllerGeneratorTest.kt @@ -11,6 +11,7 @@ import com.cjbooms.fabrikt.generators.controller.metadata.MicronautImports import com.cjbooms.fabrikt.model.Destinations.controllersPackage import com.cjbooms.fabrikt.model.SourceApi import com.cjbooms.fabrikt.util.Linter +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -49,6 +50,7 @@ class MicronautControllerGeneratorTest { genTypes = setOf(CodeGenerationType.CONTROLLERS), controllerTarget = ControllerCodeGenTargetType.MICRONAUT, ) + ModelNameRegistry.clear() } // @Test diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt index 553fbd74..1e239b19 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt @@ -10,6 +10,7 @@ import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator import com.cjbooms.fabrikt.model.Models import com.cjbooms.fabrikt.model.SourceApi import com.cjbooms.fabrikt.util.Linter +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec import org.assertj.core.api.Assertions.assertThat @@ -34,7 +35,7 @@ class ModelGeneratorTest { "duplicatePropertyHandling", "enumExamples", "enumPolymorphicDiscriminator", - "externalReferences", + "externalReferences/targeted", "githubApi", "inLinedObject", "jsonMergePatch", @@ -60,6 +61,7 @@ class ModelGeneratorTest { MutableSettings.updateSettings( genTypes = setOf(CodeGenerationType.HTTP_MODELS), ) + ModelNameRegistry.clear() } // @Test @@ -73,7 +75,7 @@ class ModelGeneratorTest { if (testCaseName == "instantDateTime") { MutableSettings.addOption(CodeGenTypeOverride.DATETIME_AS_INSTANT) } - val basePackage = "examples.$testCaseName" + val basePackage = "examples.${testCaseName.replace("/", ".")}" val apiLocation = javaClass.getResource("/examples/$testCaseName/api.yaml")!! val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI())) val expectedModels = readTextResource("/examples/$testCaseName/models/Models.kt") diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt index de4b1a27..6ce75af5 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt @@ -3,6 +3,7 @@ package com.cjbooms.fabrikt.generators import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType import com.cjbooms.fabrikt.cli.ClientCodeGenTargetType import com.cjbooms.fabrikt.cli.CodeGenerationType +import com.cjbooms.fabrikt.cli.ExternalReferencesResolutionMode import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType import com.cjbooms.fabrikt.configurations.Packages import com.cjbooms.fabrikt.generators.client.OkHttpEnhancedClientGenerator @@ -14,14 +15,17 @@ import com.cjbooms.fabrikt.model.Models import com.cjbooms.fabrikt.model.SimpleFile import com.cjbooms.fabrikt.model.SourceApi import com.cjbooms.fabrikt.util.Linter +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec +import java.nio.file.Paths import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInstance import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource import java.util.stream.Stream +import org.junit.jupiter.api.Test @TestInstance(TestInstance.Lifecycle.PER_CLASS) class OkHttpClientGeneratorTest { @@ -32,7 +36,7 @@ class OkHttpClientGeneratorTest { "multiMediaType", "okHttpClientPostWithoutRequestBody", "pathLevelParameters", - "parameterNameClash", + "parameterNameClash" ) @BeforeEach @@ -42,13 +46,15 @@ class OkHttpClientGeneratorTest { clientTarget = ClientCodeGenTargetType.OK_HTTP, modelOptions = setOf(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS), ) + ModelNameRegistry.clear() } @ParameterizedTest @MethodSource("fullApiTestCases") fun `correct api simple client is generated from a full API definition`(testCaseName: String) { val packages = Packages("examples.$testCaseName") - val sourceApi = SourceApi(readTextResource("/examples/$testCaseName/api.yaml")) + val apiLocation = javaClass.getResource("/examples/$testCaseName/api.yaml")!! + val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI())) val expectedModel = readTextResource("/examples/$testCaseName/models/Models.kt") val expectedClient = readTextResource("/examples/$testCaseName/client/ApiClient.kt") @@ -72,7 +78,8 @@ class OkHttpClientGeneratorTest { @MethodSource("fullApiTestCases") fun `correct api fault-tolerant service client is generated when the resilience4j option is set`(testCaseName: String) { val packages = Packages("examples.$testCaseName") - val sourceApi = SourceApi(readTextResource("/examples/$testCaseName/api.yaml")) + val apiLocation = javaClass.getResource("/examples/$testCaseName/api.yaml")!! + val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI())) val expectedLibUtil = readTextResource("/examples/$testCaseName/client/HttpResilience4jUtil.kt") val expectedClientCode = readTextResource("/examples/$testCaseName/client/ApiService.kt") @@ -92,7 +99,8 @@ class OkHttpClientGeneratorTest { @MethodSource("fullApiTestCases") fun `the enhanced client is not generated when no specific options are provided`(testCaseName: String) { val packages = Packages("examples.$testCaseName") - val sourceApi = SourceApi(readTextResource("/examples/$testCaseName/api.yaml")) + val apiLocation = javaClass.getResource("/examples/$testCaseName/api.yaml")!! + val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI())) val enhancedClientCode = OkHttpEnhancedClientGenerator( packages, @@ -107,7 +115,8 @@ class OkHttpClientGeneratorTest { @MethodSource("fullApiTestCases") fun `correct http utility libraries are generated`(testCaseName: String) { val packages = Packages("examples.$testCaseName") - val sourceApi = SourceApi(readTextResource("/examples/$testCaseName/api.yaml")) + val apiLocation = javaClass.getResource("/examples/$testCaseName/api.yaml")!! + val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI())) val expectedHttpUtils = readTextResource("/examples/$testCaseName/client/HttpUtil.kt") @@ -120,6 +129,37 @@ class OkHttpClientGeneratorTest { assertThat(generatedHttpUtils.content).isEqualTo(expectedHttpUtils) } + @Test + fun `correct api client and models are generated with external reference solution mode AGGRESSIVE`() { + val packages = Packages("examples.externalReferences.aggressive") + val apiLocation = javaClass.getResource("/examples/externalReferences/aggressive/api.yaml")!! + val sourceApi = SourceApi(apiLocation.readText(), baseDir = Paths.get(apiLocation.toURI())) + + val expectedModel = readTextResource("/examples/externalReferences/aggressive/models/Models.kt") + val expectedClient = readTextResource("/examples/externalReferences/aggressive/client/ApiClient.kt") + val expectedClientCode = readTextResource("/examples/externalReferences/aggressive/client/ApiService.kt") + + val models = JacksonModelGenerator( + packages, + sourceApi, + externalRefResolutionMode = ExternalReferencesResolutionMode.AGGRESSIVE + ).generate().toSingleFile() + val generator = + OkHttpEnhancedClientGenerator(packages, sourceApi) + val simpleClientCode = OkHttpSimpleClientGenerator( + packages, + sourceApi + ) + .generateDynamicClientCode() + .toSingleFile() + val enhancedClientCode = generator.generateDynamicClientCode(setOf(ClientCodeGenOptionType.RESILIENCE4J)) + .toSingleFile() + + assertThat(models).isEqualTo(expectedModel) + assertThat(simpleClientCode).isEqualTo(expectedClient) + assertThat(enhancedClientCode).isEqualTo(expectedClientCode) + } + private fun Collection.toSingleFile(): String { val destPackage = if (this.isNotEmpty()) first().destinationPackage else "" val singleFileBuilder = FileSpec.builder(destPackage, "dummyFilename") diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt index e5c3dff6..ba4b6375 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/OpenFeignClientGeneratorTest.kt @@ -11,6 +11,7 @@ import com.cjbooms.fabrikt.model.ClientType import com.cjbooms.fabrikt.model.Models import com.cjbooms.fabrikt.model.SourceApi import com.cjbooms.fabrikt.util.Linter +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec import org.assertj.core.api.Assertions.assertThat @@ -39,6 +40,7 @@ class OpenFeignClientGeneratorTest { clientTarget = ClientCodeGenTargetType.OPEN_FEIGN, modelOptions = setOf(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS), ) + ModelNameRegistry.clear() } @ParameterizedTest diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ResourceGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ResourceGeneratorTest.kt index 85ff7a36..d108c6ec 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ResourceGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ResourceGeneratorTest.kt @@ -6,6 +6,7 @@ import com.cjbooms.fabrikt.generators.model.JacksonModelGenerator import com.cjbooms.fabrikt.generators.model.QuarkusReflectionModelGenerator import com.cjbooms.fabrikt.model.ResourceFile import com.cjbooms.fabrikt.model.SourceApi +import com.cjbooms.fabrikt.util.ModelNameRegistry import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInstance @@ -26,6 +27,7 @@ class ResourceGeneratorTest { MutableSettings.updateSettings( genTypes = setOf(CodeGenerationType.HTTP_MODELS), ) + ModelNameRegistry.clear() } @ParameterizedTest diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt index 8ec0a957..07f2b754 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringAuthenticationTest.kt @@ -6,6 +6,7 @@ import com.cjbooms.fabrikt.cli.ControllerCodeGenTargetType import com.cjbooms.fabrikt.configurations.Packages import com.cjbooms.fabrikt.generators.controller.SpringControllerInterfaceGenerator import com.cjbooms.fabrikt.model.SourceApi +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.TypeSpec @@ -43,6 +44,7 @@ class SpringAuthenticationTest { genTypes = setOf(CodeGenerationType.CONTROLLERS), controllerTarget = ControllerCodeGenTargetType.SPRING, ) + ModelNameRegistry.clear() } // global authentication tests diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt index 181760bf..6655e88b 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/SpringControllerGeneratorTest.kt @@ -10,6 +10,7 @@ import com.cjbooms.fabrikt.generators.controller.metadata.SpringImports import com.cjbooms.fabrikt.model.Destinations.controllersPackage import com.cjbooms.fabrikt.model.SourceApi import com.cjbooms.fabrikt.util.Linter +import com.cjbooms.fabrikt.util.ModelNameRegistry import com.cjbooms.fabrikt.util.ResourceHelper.readTextResource import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -45,6 +46,7 @@ class SpringControllerGeneratorTest { @BeforeEach fun init() { MutableSettings.updateSettings(genTypes = setOf(CodeGenerationType.CONTROLLERS)) + ModelNameRegistry.clear() } // @Test diff --git a/src/test/resources/examples/externalReferences/aggressive/api.yaml b/src/test/resources/examples/externalReferences/aggressive/api.yaml new file mode 100644 index 00000000..54fa85fc --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/api.yaml @@ -0,0 +1,62 @@ +openapi: 3.0.0 +info: + title: "" + version: "" +paths: + /hello: + get: + operationId: helloWorld + parameters: + - name: parameter + in: path + required: true + schema: + $ref: './external-models.yaml#/components/schemas/ExternalParameter' + responses: + 200: + description: "world" + content: + application/json: + schema: + $ref: '#/components/schemas/ContainingExternalReference' +components: + schemas: + ContainingExternalReference: + type: object + properties: + some-external-reference: + $ref: './external-models.yaml#/components/schemas/ExternalObject' + inlineObject: + $ref: '#/components/schemas/FirstInlineObject' + ConflictingSchemaName: + type: object + required: + - conflictsInMainSpecFile + properties: + conflictsInMainSpecFile: + type: string + FirstInlineObject: + type: object + properties: + generation: + type: object + properties: + call_home: + type: object + required: + - url + properties: + url: + type: string + database_view: + type: object + required: + - view_name + properties: + view_name: + type: string + direct: + type: string + externalInlineObject: + $ref: './external-models.yaml#/components/schemas/SecondInlineObject' + diff --git a/src/test/resources/examples/externalReferences/aggressive/client/ApiClient.kt b/src/test/resources/examples/externalReferences/aggressive/client/ApiClient.kt new file mode 100644 index 00000000..90985239 --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/client/ApiClient.kt @@ -0,0 +1,52 @@ +package examples.externalReferences.aggressive.client + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import examples.externalReferences.aggressive.models.ContainingExternalReference +import examples.externalReferences.aggressive.models.ExternalParameter +import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import kotlin.String +import kotlin.Suppress +import kotlin.collections.Map +import kotlin.jvm.Throws + +@Suppress("unused") +public class HelloClient( + private val objectMapper: ObjectMapper, + private val baseUrl: String, + private val client: OkHttpClient, +) { + /** + * + * + * @param parameter + */ + @Throws(ApiException::class) + public fun helloWorld( + parameter: ExternalParameter, + additionalHeaders: Map = + emptyMap(), + ): ApiResponse { + val httpUrl: HttpUrl = "$baseUrl/hello" + .pathParam("{parameter}" to parameter) + .toHttpUrl() + .newBuilder() + .build() + + val headerBuilder = Headers.Builder() + additionalHeaders.forEach { headerBuilder.header(it.key, it.value) } + val httpHeaders: Headers = headerBuilder.build() + + val request: Request = Request.Builder() + .url(httpUrl) + .headers(httpHeaders) + .get() + .build() + + return request.execute(client, objectMapper, jacksonTypeRef()) + } +} diff --git a/src/test/resources/examples/externalReferences/aggressive/client/ApiModels.kt b/src/test/resources/examples/externalReferences/aggressive/client/ApiModels.kt new file mode 100644 index 00000000..9702733f --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/client/ApiModels.kt @@ -0,0 +1,26 @@ +package examples.externalReferences.aggressive.client + +import okhttp3.Headers + +/** + * API 2xx success response returned by API call. + * + * @param The type of data that is deserialized from response body + */ +data class ApiResponse(val statusCode: Int, val headers: Headers, val data: T? = null) + +/** + * API non-2xx failure responses returned by API call. + */ +open class ApiException(override val message: String) : RuntimeException(message) + +/** + * API 4xx failure responses returned by API call. + */ +data class ApiClientException(val statusCode: Int, val headers: Headers, override val message: String) : ApiException(message) + +/** + * API 5xx failure responses returned by API call. + */ +data class ApiServerException(val statusCode: Int, val headers: Headers, override val message: String) : ApiException(message) + diff --git a/src/test/resources/examples/externalReferences/aggressive/client/ApiService.kt b/src/test/resources/examples/externalReferences/aggressive/client/ApiService.kt new file mode 100644 index 00000000..4958364c --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/client/ApiService.kt @@ -0,0 +1,41 @@ +package examples.externalReferences.aggressive.client + +import com.fasterxml.jackson.databind.ObjectMapper +import examples.externalReferences.aggressive.models.ContainingExternalReference +import examples.externalReferences.aggressive.models.ExternalParameter +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry +import okhttp3.OkHttpClient +import kotlin.String +import kotlin.Suppress +import kotlin.collections.Map +import kotlin.jvm.Throws + +/** + * The circuit breaker registry should have the proper configuration to correctly action on circuit + * breaker transitions based on the client exceptions [ApiClientException], [ApiServerException] and + * [IOException]. + * + * @see ApiClientException + * @see ApiServerException + */ +@Suppress("unused") +public class HelloService( + private val circuitBreakerRegistry: CircuitBreakerRegistry, + objectMapper: ObjectMapper, + baseUrl: String, + client: OkHttpClient, +) { + public var circuitBreakerName: String = "helloClient" + + private val apiClient: HelloClient = HelloClient(objectMapper, baseUrl, client) + + @Throws(ApiException::class) + public fun helloWorld( + parameter: ExternalParameter, + additionalHeaders: Map = + emptyMap(), + ): ApiResponse = + withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) { + apiClient.helloWorld(parameter, additionalHeaders) + } +} diff --git a/src/test/resources/examples/externalReferences/aggressive/client/HttpResilience4jUtil.kt b/src/test/resources/examples/externalReferences/aggressive/client/HttpResilience4jUtil.kt new file mode 100644 index 00000000..10085eae --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/client/HttpResilience4jUtil.kt @@ -0,0 +1,13 @@ +package examples.externalReferences.aggressive.client + +import io.github.resilience4j.circuitbreaker.CircuitBreaker +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry + +fun withCircuitBreaker( + circuitBreakerRegistry: CircuitBreakerRegistry, + apiClientName: String, + apiCall: () -> ApiResponse +): ApiResponse { + val circuitBreaker = circuitBreakerRegistry.circuitBreaker(apiClientName) + return CircuitBreaker.decorateSupplier(circuitBreaker, apiCall).get() +} diff --git a/src/test/resources/examples/externalReferences/aggressive/client/HttpUtil.kt b/src/test/resources/examples/externalReferences/aggressive/client/HttpUtil.kt new file mode 100644 index 00000000..525f4d19 --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/client/HttpUtil.kt @@ -0,0 +1,65 @@ +package examples.externalReferences.aggressive.client + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import okhttp3.FormBody +import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody + +@Suppress("unused") +fun HttpUrl.Builder.queryParam(key: String, value: T?): HttpUrl.Builder = this.apply { + if (value != null) this.addQueryParameter(key, value.toString()) +} + +@Suppress("unused") +fun FormBody.Builder.formParam(key: String, value: T?): FormBody.Builder = this.apply { + if (value != null) this.add(key, value.toString()) +} + +@Suppress("unused") +fun HttpUrl.Builder.queryParam(key: String, values: List?, explode: Boolean = true) = this.apply { + if (values != null) { + if (explode) values.forEach { addQueryParameter(key, it) } + else addQueryParameter(key, values.joinToString(",")) + } +} + +@Suppress("unused") +fun Headers.Builder.header(key: String, value: String?): Headers.Builder = this.apply { + if (value != null) this.add(key, value) +} + +@Throws(ApiException::class) +fun Request.execute(client: OkHttpClient, objectMapper: ObjectMapper, typeRef: TypeReference): ApiResponse = + client.newCall(this).execute().use { response -> + when { + response.isSuccessful -> + ApiResponse(response.code, response.headers, response.body?.deserialize(objectMapper, typeRef)) + response.isBadRequest() -> + throw ApiClientException(response.code, response.headers, response.errorMessage()) + response.isServerError() -> + throw ApiServerException(response.code, response.headers, response.errorMessage()) + else -> throw ApiException("[${response.code}]: ${response.errorMessage()}") + } + } + +@Suppress("unused") +fun String.pathParam(vararg params: Pair): String = params.asSequence() + .joinToString { param -> + this.replace(param.first, param.second.toString()) + } + +fun ResponseBody.deserialize(objectMapper: ObjectMapper, typeRef: TypeReference): T? = + this.string().isNotBlankOrNull()?.let { objectMapper.readValue(it, typeRef) } + +fun String?.isNotBlankOrNull() = if (this.isNullOrBlank()) null else this + +private fun Response.errorMessage(): String = this.body?.string() ?: this.message + +private fun Response.isBadRequest(): Boolean = this.code in 400..499 + +private fun Response.isServerError(): Boolean = this.code in 500..599 diff --git a/src/test/resources/examples/externalReferences/aggressive/external-models-2.yaml b/src/test/resources/examples/externalReferences/aggressive/external-models-2.yaml new file mode 100644 index 00000000..c4cfdb71 --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/external-models-2.yaml @@ -0,0 +1,12 @@ +openapi: 3.0.0 +paths: { } +info: + title: "" + version: "" +components: + schemas: + ReferencedFromOtherExternalFile: + type: object + properties: + another: + type: string diff --git a/src/test/resources/examples/externalReferences/aggressive/external-models.yaml b/src/test/resources/examples/externalReferences/aggressive/external-models.yaml new file mode 100644 index 00000000..dbf56be4 --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/external-models.yaml @@ -0,0 +1,127 @@ +openapi: 3.0.0 +paths: { } +info: + title: "" + version: "" +components: + schemas: + ExternalObject: + type: object + properties: + another: + $ref: "#/components/schemas/ExternalObjectTwo" + one_of: + $ref: "#/components/schemas/ExternalOneOf" + anotherExternal: + $ref: './external-models-2.yaml#/components/schemas/ReferencedFromOtherExternalFile' + conflicting: + $ref: '#/components/schemas/ConflictingSchemaName' + + ExternalObjectTwo: + type: object + required: + - errors + properties: + list-others: + type: array + items: + $ref: '#/components/schemas/ExternalObjectThree' + additionalProperties: + type: object + minProperties: 1 + additionalProperties: + $ref: '#/components/schemas/ExternalObjectFour' + + ExternalObjectThree: + type: object + required: + - enum + - description + properties: + enum: + type: string + enum: + - one + - two + - three + description: + type: string + + ExternalObjectFour: + type: object + properties: + blah: + type: string + + UnreferencedExternalObjectFive: + type: object + properties: + blah: + type: string + + ExternalOneOf: + oneOf: + - $ref: '#/components/schemas/OneOfOne' + - $ref: '#/components/schemas/OneOfTwo' + + ParentOneOf: + type: object + discriminator: + propertyName: discriminator + properties: + discriminator: + type: string + + OneOfOne: + allOf: + - $ref: '#/components/schemas/ParentOneOf' + - type: object + properties: + oneOfOne: + type: string + + OneOfTwo: + allOf: + - $ref: '#/components/schemas/ParentOneOf' + - type: object + properties: + oneOfTwo: + type: string + + ExternalParameter: + type: string + enum: + - ONE + - TWO + - THREE + + ConflictingSchemaName: + type: object + required: + - conflictsInExternalSpecFile + properties: + conflictsInExternalSpecFile: + type: string + SecondInlineObject: + type: object + properties: + generation: + type: object + properties: + call_home: + type: object + required: + - url + properties: + url: + type: string + database_view: + type: object + required: + - view_name + properties: + view_name: + type: string + direct: + type: string + diff --git a/src/test/resources/examples/externalReferences/aggressive/models/Models.kt b/src/test/resources/examples/externalReferences/aggressive/models/Models.kt new file mode 100644 index 00000000..d20cc700 --- /dev/null +++ b/src/test/resources/examples/externalReferences/aggressive/models/Models.kt @@ -0,0 +1,251 @@ +package examples.externalReferences.aggressive.models + +import com.fasterxml.jackson.`annotation`.JsonAnyGetter +import com.fasterxml.jackson.`annotation`.JsonAnySetter +import com.fasterxml.jackson.`annotation`.JsonIgnore +import com.fasterxml.jackson.`annotation`.JsonProperty +import com.fasterxml.jackson.`annotation`.JsonSubTypes +import com.fasterxml.jackson.`annotation`.JsonTypeInfo +import com.fasterxml.jackson.`annotation`.JsonValue +import javax.validation.Valid +import javax.validation.constraints.NotNull +import kotlin.String +import kotlin.collections.List +import kotlin.collections.Map +import kotlin.collections.MutableMap + +public data class ConflictingSchemaName( + @param:JsonProperty("conflictsInMainSpecFile") + @get:JsonProperty("conflictsInMainSpecFile") + @get:NotNull + public val conflictsInMainSpecFile: String, +) + +public data class ConflictingSchemaNameExtra( + @param:JsonProperty("conflictsInExternalSpecFile") + @get:JsonProperty("conflictsInExternalSpecFile") + @get:NotNull + public val conflictsInExternalSpecFile: String, +) + +public data class ContainingExternalReference( + @param:JsonProperty("some-external-reference") + @get:JsonProperty("some-external-reference") + @get:Valid + public val someExternalReference: ExternalObject? = null, + @param:JsonProperty("inlineObject") + @get:JsonProperty("inlineObject") + @get:Valid + public val inlineObject: FirstInlineObject? = null, +) + +public data class ExternalObject( + @param:JsonProperty("another") + @get:JsonProperty("another") + @get:Valid + public val another: ExternalObjectTwo? = null, + @param:JsonProperty("one_of") + @get:JsonProperty("one_of") + @get:Valid + public val oneOf: ParentOneOf? = null, + @param:JsonProperty("anotherExternal") + @get:JsonProperty("anotherExternal") + @get:Valid + public val anotherExternal: ReferencedFromOtherExternalFile? = null, + @param:JsonProperty("conflicting") + @get:JsonProperty("conflicting") + @get:Valid + public val conflicting: ConflictingSchemaNameExtra? = null, +) + +public data class ExternalObjectFour( + @param:JsonProperty("blah") + @get:JsonProperty("blah") + public val blah: String? = null, +) + +public data class ExternalObjectThree( + @param:JsonProperty("enum") + @get:JsonProperty("enum") + @get:NotNull + public val `enum`: ExternalObjectThreeEnum, + @param:JsonProperty("description") + @get:JsonProperty("description") + @get:NotNull + public val description: String, +) + +public enum class ExternalObjectThreeEnum( + @JsonValue + public val `value`: String, +) { + ONE("one"), + TWO("two"), + THREE("three"), + ; + + public companion object { + private val mapping: Map = + values().associateBy(ExternalObjectThreeEnum::value) + + public fun fromValue(`value`: String): ExternalObjectThreeEnum? = mapping[value] + } +} + +public data class ExternalObjectTwo( + @param:JsonProperty("list-others") + @get:JsonProperty("list-others") + @get:Valid + public val listOthers: List? = null, + @get:JsonIgnore + public val properties: MutableMap> = mutableMapOf(), +) { + @JsonAnyGetter + public fun `get`(): Map> = properties + + @JsonAnySetter + public fun `set`(name: String, `value`: Map) { + properties[name] = value + } +} + +public enum class ExternalParameter( + @JsonValue + public val `value`: String, +) { + ONE("ONE"), + TWO("TWO"), + THREE("THREE"), + ; + + public companion object { + private val mapping: Map = + values().associateBy(ExternalParameter::value) + + public fun fromValue(`value`: String): ExternalParameter? = mapping[value] + } +} + +public data class FirstInlineObject( + @param:JsonProperty("generation") + @get:JsonProperty("generation") + @get:Valid + public val generation: FirstInlineObjectGeneration? = null, +) + +public data class FirstInlineObjectCallHome( + @param:JsonProperty("url") + @get:JsonProperty("url") + @get:NotNull + public val url: String, +) + +public data class FirstInlineObjectDatabaseView( + @param:JsonProperty("view_name") + @get:JsonProperty("view_name") + @get:NotNull + public val viewName: String, +) + +public data class FirstInlineObjectGeneration( + @param:JsonProperty("call_home") + @get:JsonProperty("call_home") + @get:Valid + public val callHome: FirstInlineObjectCallHome? = null, + @param:JsonProperty("database_view") + @get:JsonProperty("database_view") + @get:Valid + public val databaseView: FirstInlineObjectDatabaseView? = null, + @param:JsonProperty("direct") + @get:JsonProperty("direct") + public val direct: String? = null, + @param:JsonProperty("externalInlineObject") + @get:JsonProperty("externalInlineObject") + @get:Valid + public val externalInlineObject: SecondInlineObject? = null, +) + +public data class OneOfOne( + @param:JsonProperty("oneOfOne") + @get:JsonProperty("oneOfOne") + public val oneOfOne: String? = null, + @get:JsonProperty("discriminator") + @get:NotNull + @param:JsonProperty("discriminator") + override val discriminator: String = "OneOfOne", +) : ParentOneOf() + +public data class OneOfTwo( + @param:JsonProperty("oneOfTwo") + @get:JsonProperty("oneOfTwo") + public val oneOfTwo: String? = null, + @get:JsonProperty("discriminator") + @get:NotNull + @param:JsonProperty("discriminator") + override val discriminator: String = "OneOfTwo", +) : ParentOneOf() + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "discriminator", + visible = true, +) +@JsonSubTypes( + JsonSubTypes.Type(value = OneOfOne::class, name = "OneOfOne"), + JsonSubTypes.Type( + value = + OneOfTwo::class, + name = "OneOfTwo", + ), +) +public sealed class ParentOneOf() { + public abstract val discriminator: String +} + +public data class ReferencedFromOtherExternalFile( + @param:JsonProperty("another") + @get:JsonProperty("another") + public val another: String? = null, +) + +public data class SecondInlineObject( + @param:JsonProperty("generation") + @get:JsonProperty("generation") + @get:Valid + public val generation: SecondInlineObjectGeneration? = null, +) + +public data class SecondInlineObjectCallHome( + @param:JsonProperty("url") + @get:JsonProperty("url") + @get:NotNull + public val url: String, +) + +public data class SecondInlineObjectDatabaseView( + @param:JsonProperty("view_name") + @get:JsonProperty("view_name") + @get:NotNull + public val viewName: String, +) + +public data class SecondInlineObjectGeneration( + @param:JsonProperty("call_home") + @get:JsonProperty("call_home") + @get:Valid + public val callHome: SecondInlineObjectCallHome? = null, + @param:JsonProperty("database_view") + @get:JsonProperty("database_view") + @get:Valid + public val databaseView: SecondInlineObjectDatabaseView? = null, + @param:JsonProperty("direct") + @get:JsonProperty("direct") + public val direct: String? = null, +) + +public data class UnreferencedExternalObjectFive( + @param:JsonProperty("blah") + @get:JsonProperty("blah") + public val blah: String? = null, +) diff --git a/src/test/resources/examples/externalReferences/api.yaml b/src/test/resources/examples/externalReferences/targeted/api.yaml similarity index 94% rename from src/test/resources/examples/externalReferences/api.yaml rename to src/test/resources/examples/externalReferences/targeted/api.yaml index 16eba688..101cb31d 100644 --- a/src/test/resources/examples/externalReferences/api.yaml +++ b/src/test/resources/examples/externalReferences/targeted/api.yaml @@ -9,6 +9,4 @@ components: type: object properties: some-external-reference: - $ref: './external-models.yaml#/components/schemas/ExternalObject' - - + $ref: './external-models.yaml#/components/schemas/ExternalObject' \ No newline at end of file diff --git a/src/test/resources/examples/externalReferences/external-models.yaml b/src/test/resources/examples/externalReferences/targeted/external-models.yaml similarity index 90% rename from src/test/resources/examples/externalReferences/external-models.yaml rename to src/test/resources/examples/externalReferences/targeted/external-models.yaml index 39af0dd9..04c4946d 100644 --- a/src/test/resources/examples/externalReferences/external-models.yaml +++ b/src/test/resources/examples/externalReferences/targeted/external-models.yaml @@ -9,10 +9,10 @@ components: type: object properties: another: - $ref: "#/components/schemas/ExternalObjectTwo" + $ref: "#/components/schemas/ExternalObjectTwo" one_of: $ref: "#/components/schemas/ExternalOneOf" - + ExternalObjectTwo: type: object required: @@ -27,7 +27,7 @@ components: minProperties: 1 additionalProperties: $ref: '#/components/schemas/ExternalObjectFour' - + ExternalObjectThree: type: object required: @@ -41,20 +41,20 @@ components: - two - three description: - type: string - + type: string + ExternalObjectFour: type: object properties: blah: - type: string - + type: string + IgnoredExternalObjectFive: type: object properties: blah: type: string - + ExternalOneOf: oneOf: - $ref: '#/components/schemas/OneOfOne' @@ -67,7 +67,7 @@ components: properties: discriminator: type: string - + OneOfOne: allOf: - $ref: '#/components/schemas/ParentOneOf' diff --git a/src/test/resources/examples/externalReferences/models/Models.kt b/src/test/resources/examples/externalReferences/targeted/models/Models.kt similarity index 98% rename from src/test/resources/examples/externalReferences/models/Models.kt rename to src/test/resources/examples/externalReferences/targeted/models/Models.kt index d7fb6873..fb994ab4 100644 --- a/src/test/resources/examples/externalReferences/models/Models.kt +++ b/src/test/resources/examples/externalReferences/targeted/models/Models.kt @@ -1,4 +1,4 @@ -package examples.externalReferences.models +package examples.externalReferences.targeted.models import com.fasterxml.jackson.`annotation`.JsonAnyGetter import com.fasterxml.jackson.`annotation`.JsonAnySetter