Skip to content

Commit

Permalink
feat: add external reference resolution mode aggressive (#245)
Browse files Browse the repository at this point in the history
* fix: missing types from other spec file

* fix: test

* feat: add model name registry to prevent name collisions

* fix: correct response type in test schema

* chore: add inline models to external reference test

* chore: cleanup

* chore: cleanup some more

* chore: cleanup even more

* chore: use external reference schema in okhttpclient test suite only

* chore: valueClassName -> valueSuffix

* feat: add external ref resolution mode flag

* fix: add missing newline in snapshot file

* chore: remove unused arg

* chore: more representative test name
  • Loading branch information
dpnolte authored Oct 18, 2023
1 parent dfa30a2 commit a3a452d
Show file tree
Hide file tree
Showing 32 changed files with 974 additions and 112 deletions.
98 changes: 51 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 5 additions & 3 deletions src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ object CodeGen {
codeGenArgs.typeOverrides,
codeGenArgs.srcPath,
codeGenArgs.resourcesPath,
codeGenArgs.validationLibrary
codeGenArgs.validationLibrary,
codeGenArgs.externalRefResolutionMode
)
}

Expand All @@ -51,9 +52,10 @@ object CodeGen {
typeOverrides: Set<CodeGenTypeOverride>,
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
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenArgs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeGenerationType> {
Expand Down Expand Up @@ -176,6 +183,10 @@ class TypeCodeGenOptionsConverter: IStringConverter<CodeGenTypeOverride> {
override fun convert(value: String): CodeGenTypeOverride = convertToEnumValue(value)
}

class ExternalReferencesResolutionModeConverter: IStringConverter<ExternalReferencesResolutionMode> {
override fun convert(value: String): ExternalReferencesResolutionMode = convertToEnumValue(value)
}

class PackageNameValidator : IValueValidator<String> {
override fun validate(name: String, value: String) {
if (!value.isValidJavaPackage()) {
Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenOptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
9 changes: 4 additions & 5 deletions src/main/kotlin/com/cjbooms/fabrikt/cli/CodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<GeneratedFile> = MutableSettings.generationTypes().map(::generateCode).flatten()
Expand Down Expand Up @@ -63,7 +62,7 @@ class CodeGenerator(
private fun resourceSet(resFiles: Collection<ResourceFile>) = 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<ResourceFile> =
listOfNotNull(QuarkusReflectionModelGenerator(models, MutableSettings.generationTypes()).generate())
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ object MutableSettings {
private lateinit var clientTarget: ClientCodeGenTargetType
private lateinit var typeOverrides: MutableSet<CodeGenTypeOverride>
private lateinit var validationLibrary: ValidationLibrary
private lateinit var externalRefResolutionMode: ExternalReferencesResolutionMode

fun updateSettings(
genTypes: Set<CodeGenerationType> = emptySet(),
Expand All @@ -20,7 +21,8 @@ object MutableSettings {
clientOptions: Set<ClientCodeGenOptionType> = emptySet(),
clientTarget: ClientCodeGenTargetType = ClientCodeGenTargetType.OK_HTTP,
typeOverrides: Set<CodeGenTypeOverride> = emptySet(),
validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION
validationLibrary: ValidationLibrary = ValidationLibrary.JAVAX_VALIDATION,
externalRefResolutionMode: ExternalReferencesResolutionMode = ExternalReferencesResolutionMode.TARGETED,
) {
this.generationTypes = genTypes.toMutableSet()
this.controllerOptions = controllerOptions.toMutableSet()
Expand All @@ -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)
Expand All @@ -43,4 +46,5 @@ object MutableSettings {
fun clientTarget() = this.clientTarget
fun typeOverrides() = this.typeOverrides.toSet()
fun validationLibrary() = this.validationLibrary
fun externalRefResolutionMode() = this.externalRefResolutionMode
}
Loading

0 comments on commit a3a452d

Please sign in to comment.