Code Monkey home page Code Monkey logo

fabrikt's People

Contributors

acanda avatar ascheja avatar atollk avatar averabaq avatar beiertu-mms avatar cjbooms avatar detouched avatar dpnolte avatar gama11 avatar george-walker avatar herojan avatar lagarino avatar pschichtel avatar rajki avatar reallyliri avatar tanoshkin-cian avatar ttobollik avatar ulrikandersen avatar vasuman avatar y-marion avatar zapodot avatar zettelmj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fabrikt's Issues

Inlined object definitions should be Prefixed with their parent object name

Inlined objects that are generated often share the same name as top level objects, or other inlined objects. This causes models to overwrite each other at generation time with unexpected results

openapi: 3.0.0
components:
  schemas:
    ContainsInLinedObject:
      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

Should produce:

data class ContainsInLinedObject(
    @param:JsonProperty("generation")
    @get:JsonProperty("generation")
    @get:Valid
    val generation: ContainsInLinedObjectGeneration? = null
)

data class ContainsInLinedObjectGenerationCallHome(
    @param:JsonProperty("url")
    @get:JsonProperty("url")
    @get:NotNull
    val url: String
)

data class ContainsInLinedObjectGenerationDatabaseView(
    @param:JsonProperty("view_name")
    @get:JsonProperty("view_name")
    @get:NotNull
    val viewName: String
)

data class ContainsInLinedObjectGeneration(
    @param:JsonProperty("call_home")
    @get:JsonProperty("call_home")
    @get:Valid
    val callHome: ContainsInLinedObjectGenerationCallHome? = null,
    @param:JsonProperty("database_view")
    @get:JsonProperty("database_view")
    @get:Valid
    val databaseView: ContainsInLinedObjectGenerationDatabaseView? = null,
    @param:JsonProperty("direct")
    @get:JsonProperty("direct")
    val direct: String? = null
)



Client return type is always nullable

I noticed that the return type of clients is always nullable:

.parameterizedBy(returnType.copy(nullable = true))

Is this simply a result of OkHttp's response.body being nullable? For endpoints that return something, it might be more sensible to throw an ApiException instead if the status code was 2xx but there was no body. Otherwise user code always needs a try-catch and null handling, which seems annoying.

Add Java Serialization Option to Models

Add support for generating Java Serializable models via an optional flag to the CLI for supporting this new feature:

---http-models-option 'java_serialisation'

Add a configuration option to treat x-extensible-enum as an enum

Zalando's string format x-extensible-enum could be generated as an enum if requested by users.

The default should be not to treat these strings as enums as it could cause clients to break as new enum values are added. But for server-side development, it may be nicer to generate these definitions as enums

Generated Spring Controller DateTimeFormat parameter annnotations are missing

Spring Controller method parameters need @DateTimeFormat annotations to correctly parse date / date-time values

For example

     fun get(
         @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
         @RequestParam(value = "aDate", required = true)
         aDate: LocalDate,
         @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
         @RequestParam(value = "bDateTime", required = true)
         bDateTime: OffsetDateTime
     ): ResponseEntity<Unit>

This is described in https://www.baeldung.com/spring-date-parameters

Happy to contribute a PR to fix this.

Provide minimal gradle plugin with task, and fix caching in example

The gradle task example in the readme does not play nice with Gradle's caching at all. The worst case scenario is that the cache key is not dependent on the input files, so even after changing the spec a stale cache entry will be used. This seems to be the case.

At a minimum, the example should be updated to use inputs.file("/path-to-api/open-api.yaml").withPathSensitivity(PathSensitivity.NONE), but ideally you could provide a small gradle plugin with a task type that sets up proper caching and provides setters/configuration methods for the most common CLI options.

sealed class generation breaks when referenced from another schema

It looks like polymorphic class generation breaks as soon as that class is referenced from another schema. Here's an example that adds a Wrapper schema to the yml from the readme:

openapi: 3.0.0
components:
  schemas:
+    Wrapper:
+      type: object
+      properties:
+        polymorph:
+          $ref: '#/components/schemas/PolymorphicEnumDiscriminator'
    PolymorphicEnumDiscriminator:
      type: object
      discriminator:
        propertyName: some_enum
        mapping:
          obj_one: '#/components/schemas/ConcreteImplOne'
          obj_two: '#/components/schemas/ConcreteImplTwo'
      properties:
        some_enum:
          $ref: '#/components/schemas/EnumDiscriminator'
    ConcreteImplOne:
      allOf:
        - $ref: '#/components/schemas/PolymorphicEnumDiscriminator'
        - type: object
          properties:
            some_prop:
              type: string
    ConcreteImplTwo:
      allOf:
        - $ref: '#/components/schemas/PolymorphicEnumDiscriminator'
        - type: object
          properties:
            some_prop:
              type: string
    EnumDiscriminator:
      type: string
      enum:
        - obj_one
        - obj_two

In the generated code, PolymorphicEnumDiscriminator is generated as a regular class and the subtypes are missing:

data class Wrapper(
  @param:JsonProperty("polymorph")
  @get:JsonProperty("polymorph")
  @get:Valid
  val polymorph: PolymorphicEnumDiscriminator? = null
)

class PolymorphicEnumDiscriminator() {
  @param:JsonProperty("some_enum")
  @get:JsonProperty("some_enum")
  @get:NotNull
  val someEnum: EnumDiscriminator
}

enum class EnumDiscriminator(
  @JsonValue
  val value: String
) {
  OBJ_ONE("obj_one"),

  OBJ_TWO("obj_two");

  companion object {
    private val mapping: Map<String, EnumDiscriminator> =
        values().associateBy(EnumDiscriminator::value)

    fun fromValue(value: String): EnumDiscriminator? = mapping[value]
  }
}

Support naming controller methods after the operationId

In openapi-generator, controller methods are named after the operationId in the spec file. fabrikt just seems to name the methods after the HTTP method (post() etc). This makes it impossible to implement multiple controllers with overlapping HTTP methods in the same class.

The simplest solution would be to use the operationId like openapi-generator does - perhaps as an option as it would be breaking otherwise?

I could also see wanting to have this feature even apart from the mentioned multiple-controllers-in-one-class use case, simply because the operationId, if present, is probably carries more information than the generated one.

support kotlinx.serialization

This library looks great, are you planning to add support for kotlinx serialization, or would you accept a pr that adds it?

Models created from inline object fields overwrite models created from schema definitions

Below, the inline object in ConnectorResource has a model named Metadata created for it, overriding the one made for the top level Metadata schema. It's not clear how to handle this situation atm, possibly an error should be thrown or some naming strategy should be developed for the models created from inline objects, such as parent_field like we do in the sql.

Metadata:
  type: object
  properties:
    labels:
      type: string
    annotations:
      type: string

ConnectorResource:
  type: object
  required:
    - metadata
  properties:
    metadata:
      type: object
      required:
        - name
      properties:
        name:
          type: string
data class Metadata(
    @JsonProperty("name")
    @get:NotNull
    val name: String
)

Wrong Service interface return type generated for GET on resource collections

The following operation:

  /unsent-events:
    get:
      summary: "Page through all the UnsentEvent resources matching the query filters"
      tags:
        - "unsentevent"
      parameters:
        - $ref: "#/components/parameters/FlowId"
        - $ref: "#/components/parameters/TeamIdsQueryParam"
        - $ref: "#/components/parameters/AppIdsQueryParam"
        - $ref: "#/components/parameters/IncludeInactive"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/TokenInfo"
      responses:
        200:
          description: "successful operation"
          headers:
            Cache-Control:
              $ref: "#/components/headers/CacheControl"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnsentEventQueryResult"
        default:
          description: "error occurred - see status code and problem object for more\
            \ information."
          content:
            application/problem+json:
              schema:
                $ref: "https://zalando.github.io/problem/schema.yaml#/Problem"
      security:
        - oauth2:
            - "uid"
            - "fabric-event-scheduler.read"

is producing a Service interface with the following signature:

  fun query(
    limit: Int,
    xTokeninfoForward: TokenInfo,
    xFlowId: String?,
    teamIds: List<String>?,
    appIds: List<String>?,
    includeInactive: Boolean?,
    cursor: String?
  ): List<UnsentEvent>

But it should be producing this:

  fun query(
    limit: Int,
    xTokeninfoForward: TokenInfo,
    xFlowId: String?,
    teamIds: List<String>?,
    appIds: List<String>?,
    includeInactive: Boolean?,
    cursor: String?
  ): UnsentEventQueryResult

OneOf + discriminator should be supported

Using oneOf unions all fields, but with a discriminator it should create data classes with jackson subtypes. Currently oneOf + discriminator unions fields as usual and adds an empty subtypes list.

Generation:
  type: object
  description: |
    This section configures how Fabric Event Scheduler will produce an event for a given table's row change.
  oneOf:
    - $ref: '#/components/schemas/CallHome'
    - $ref: '#/components/schemas/DatabaseView'
    - $ref: '#/components/schemas/DirectGeneration'
  discriminator:
    propertyName: generation_type
CallHome:
  type: object
  description: |
    Use the Call-Home style to generate an event for a table row. In this style, when data is changed in a table row
    Fabric Event Scheduler will call your endpoint with the name of the table and the value from the id column, configured
    in the database section. The response will contain the event to be sent to Nakadi.
  required:
    - url
  properties:
    generation_type:
      type: string
    url:
      type: string
      description: |
        Url to call to get the latest event state for a given table row.
DatabaseView:
  type: object
  description: |
    Use the Database-View style to generate an event for a table row. In this style, when data is changed in a table row
    Fabric Event Scheduler will take the value of this view as the event to be sent to Nakadi.
  required:
    - view_name
  properties:
    generation_type:
      type: string
    view_name:
      type: string
      description: |
        Name of the view which will provide the latest state of a table row when queried.
DirectGeneration:
  type: object
  description: |
    This section configures direct event generation. In this style, the nakadi event is produced by converting the updated
    row to json.
  properties:
    generation_type:
      type: string
@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "generation_type",
    visible = true
)
@JsonSubTypes()
sealed class Generation(
    open val generationType: String?,
    open val url: String?,
    open val viewName: String?
)

Fabrikt misleadingly reports "Failed to resolve references when parsing API...", masking the actual exception

I have an OpenAPI specification that cannot be parsed for some reason. When fabrikt tries to convert it I get an error with the message Failed to resolve references when parsing API. External Schema references require internet connection even though the specification contains no external references. My first hunch was that it is actually a relative path problem, but catching the exception that triggers this message (in YamlUtils.kt:41) I am inclined to think the root NullPointerException has nothing to do with the message and is actually a bug in the underlying library. That same specification can certainly be parsed with openapi-generator or SwaggerUI.

I can try to replicate the example as I cannot (yet) share the actual OpenAPI specification but the stacktrace of the triggering exception looks like this:

java.lang.NullPointerException
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.getAllowedJsonTypes(ValidatorBase.java:419)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateField(ValidatorBase.java:222)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateBooleanField(ValidatorBase.java:71)
	at com.reprezen.kaizen.oasparser.val3.EncodingPropertyValidator.runObjectValidations(EncodingPropertyValidator.java:38)
	at com.reprezen.kaizen.oasparser.val.ObjectValidatorBase.runValidations(ObjectValidatorBase.java:18)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.MapValidator.runValidations(MapValidator.java:31)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMap(ValidatorBase.java:285)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMapField(ValidatorBase.java:279)
	at com.reprezen.kaizen.oasparser.val3.MediaTypeValidator.runObjectValidations(MediaTypeValidator.java:38)
	at com.reprezen.kaizen.oasparser.val.ObjectValidatorBase.runValidations(ObjectValidatorBase.java:18)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.MapValidator.runValidations(MapValidator.java:31)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMap(ValidatorBase.java:285)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMapField(ValidatorBase.java:279)
	at com.reprezen.kaizen.oasparser.val3.RequestBodyValidator.runObjectValidations(RequestBodyValidator.java:28)
	at com.reprezen.kaizen.oasparser.val.ObjectValidatorBase.runValidations(ObjectValidatorBase.java:18)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateField(ValidatorBase.java:225)
	at com.reprezen.kaizen.oasparser.val3.OperationValidator.runObjectValidations(OperationValidator.java:48)
	at com.reprezen.kaizen.oasparser.val.ObjectValidatorBase.runValidations(ObjectValidatorBase.java:18)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.MapValidator.runValidations(MapValidator.java:31)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMap(ValidatorBase.java:285)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMapField(ValidatorBase.java:279)
	at com.reprezen.kaizen.oasparser.val3.PathValidator.runObjectValidations(PathValidator.java:32)
	at com.reprezen.kaizen.oasparser.val.ObjectValidatorBase.runValidations(ObjectValidatorBase.java:18)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.MapValidator.runValidations(MapValidator.java:31)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMap(ValidatorBase.java:285)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validateMapField(ValidatorBase.java:279)
	at com.reprezen.kaizen.oasparser.val3.OpenApi3Validator.runObjectValidations(OpenApi3Validator.java:56)
	at com.reprezen.kaizen.oasparser.val.ObjectValidatorBase.runValidations(ObjectValidatorBase.java:18)
	at com.reprezen.kaizen.oasparser.val.ValidatorBase.validate(ValidatorBase.java:65)
	at com.reprezen.kaizen.oasparser.ovl3.OpenApi3Impl.validate(OpenApi3Impl.java:60)
	at com.reprezen.kaizen.oasparser.OpenApiParser.parse(OpenApiParser.java:96)
	at com.reprezen.kaizen.oasparser.OpenApiParser.parse(OpenApiParser.java:85)
	at com.reprezen.kaizen.oasparser.OpenApiParser.parse(OpenApiParser.java:35)
	at com.reprezen.kaizen.oasparser.OpenApi3Parser.parse(OpenApi3Parser.java:29)
	at com.reprezen.kaizen.oasparser.OpenApi3Parser.parse(OpenApi3Parser.java:20)
	at com.reprezen.kaizen.oasparser.OpenApiParser.parse(OpenApiParser.java:28)
	at com.reprezen.kaizen.oasparser.OpenApi3Parser.parse(OpenApi3Parser.java:24)
	at com.cjbooms.fabrikt.util.YamlUtils.parseOpenApi(YamlUtils.kt:39)
	at com.cjbooms.fabrikt.model.SourceApi.<init>(SourceApi.kt:32)
	at com.cjbooms.fabrikt.model.SourceApi$Companion.create(SourceApi.kt:28)
	at com.cjbooms.fabrikt.cli.CodeGen.generate(CodeGen.kt:50)
	at com.cjbooms.fabrikt.cli.CodeGen.main(CodeGen.kt:22)

oneOf schema for additionalProperties throws exceptions

If you have an additionalProperties schema described as per below, there is a stack trace thrown during model generation.

additionalProperties:
  oneOf:
     - $ref: '#/component/schema/1'
     - $ref: '#/component/schema/2'
Exception in thread "main" java.lang.IllegalStateException: Unknown OAS type: object and format: null
        at ie.zalando.fabric.model.OasType$Companion.toOasType(OasType.kt:45)
        at ie.zalando.fabric.model.PolyglotTypeInfo$Companion.from(PolyglotTypeInfo.kt:60)
        at ie.zalando.fabric.model.PolyglotTypeInfo$Companion.from(PolyglotTypeInfo.kt:77)
        at ie.zalando.fabric.model.PolyglotTypeInfo$Companion.from(PolyglotTypeInfo.kt:77)
        at ie.zalando.fabric.model.PropertyInfo$MapField.<init>(PropertyInfo.kt:206)
        at ie.zalando.fabric.model.PropertyInfo$Companion.getInLinedProperties(PropertyInfo.kt:89)
        at ie.zalando.fabric.model.PropertyInfo$Companion.topLevelProperties(PropertyInfo.kt:68)
        at ie.zalando.fabric.model.PropertyInfo$Companion.topLevelProperties(PropertyInfo.kt:59)
        at ie.zalando.fabric.model.ModelInfo.<init>(ModelInfo.kt:57)
        at ie.zalando.fabric.model.ModelInfo.<init>(ModelInfo.kt:32)
        at ie.zalando.fabric.model.ModelInfo$Companion.modelInfosFromApi$fabric_generation_station(ModelInfo.kt:109)
        at ie.zalando.fabric.model.ApiSeed.<init>(ApiSeed.kt:39)
        at ie.zalando.fabric.model.ApiSeed$Companion.create(ApiSeed.kt:24)
        at ie.zalando.fabric.cli.CodeGen.generate(CodeGen.kt:41)
        at ie.zalando.fabric.cli.CodeGen.main(CodeGen.kt:22)

Generated class names are inconsistent for inline array objects

Generated class names are inconsistent between the variable data type and its related class. This only happens when the inline object subtype is an array. The generated class name is prefixed correctly, but the variable data type of the parent class is not.

openapi: 3.0.0
components:
  schemas:
    EnrichmentValue:
      type: object
      required:
        - versioned
        - owner_id
      properties:
        versioned:
          type: array
          items:
            type: object
            properties:
              version:
                type: string

should generate:

data class EnrichmentValue(
  @param:JsonProperty("versioned")
  @get:JsonProperty("versioned")
  @get:NotNull
  @get:Valid
  val versioned: List<EnrichmentValueVersioned>
) : Serializable

but it's generating instead:

data class EnrichmentValue(
  @param:JsonProperty("versioned")
  @get:JsonProperty("versioned")
  @get:NotNull
  @get:Valid
  val versioned: List<Versioned>
) : Serializable

Add Support for @ReflectiveAccess Micronaut annotations

In Micronaut 3, the @introspected annotation is no longer used for adding a class to the reflect-config.json file, which has been replaced by the @ReflectiveAccess annotation. Please, add a new cli option to support this new annotation like micronaut_reflection for the generated models

INTROSPECTIONS AND GRAALVM REFLECTION
In previous versions of the Micronaut framework, adding @introspected to a class also added the configuration for GraalVM to allow for reflective usage of the class. This was the right choice to make prior to advancements made within the Framework, specifically in regards to validation and JSON encoding/decoding. The vast majority of cases should not require any reflection for introspected classes, and thus reflective metadata for GraalVM is no longer applied automatically.
To restore this behavior for an individual class, add the @ReflectiveAccess annotation to the class.

Support generation of models from requestBodies section

Currently we only generate models from the schemas and parameters sections

Consider expanding generation to support generating models from definitions within requestBodies also

This could be as simple as this:

SchemaInfo( ...  ) {
...
        allSchemas = openApi3.schemas.entries.map { it.key to it.value }
            .plus(openApi3.parameters.entries.map { it.key to it.value.schema })
            .plus(
                openApi3.requestBodies.entries.flatMap { requestBody ->
                    requestBody.value.contentMediaTypes.map { requestBody.key to it.value.schema }
                }
            )
            .map { (key, schema) -> SchemaInfo(key, schema) }

But lots test cases are need to validate this

allOf with single $ref leads to Any

Code generation for the following example is correct (return types correctly typed as Result):

openapi: 3.0.0
paths:
  /test:
    get:
      operationId: test
      responses:
        '200':
          description: Operation successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Result'
components:
  schemas:
    Result:
      allOf:
        - $ref: '#/components/schemas/Base'
        - type: object
          properties:
            bar:
              type: string
    Base:
      properties:
        foo:
          type: string

However, when Result is reduced to just this, the return type in both the client and the controller is suddenly Any:

    Result:
      allOf:
        - $ref: '#/components/schemas/Base'

I think an allOf with a single item should be treated the same as a direct $ref.

Spring Transactions not rolling back on generated API exceptions

The exception classes generated by Fabrikt for the client are not triggering Spring's @Transactional to rollback transactions.

This is because the Fabrikt generated exceptions extend Exception instead of RuntimeException.

In its default configuration, the Spring Frameworkโ€™s transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException.

Add Gradle Task to generate Usage Documentation

It should be possible to generate usage instuctions in mardown via a Gradle task that looks like:

    val printCodeGenUsage by creating(JavaExec::class) {
        dependsOn(fatJar)
        classpath = project.files("./build/libs/$executableName.jar")
        main = "ie.zalando.fabric.cli.CodeGen"
        args = listOf("--help")
    }

Neither fragments from external files nor inline responses are generated

I have this spec (given below). When running openapi-generator on it, I get 3 classes: Foo, Bar and Baz. Fabrikt only gives me Bar. I've derived this from a bigger example and the issue seems to be that only models included via #/components/schemas are processed.

openapi: 3.0.3
info:
    title: title
    description: description
    version: 1.0.0
paths:
    /foo:
        get:
            responses:
                "200":
                    description: ok
                    content:
                        application/json:
                            schema:
                                type: object
                                title: Foo
                                properties:
                                    fifo:
                                        type: string
                                required:
                                    - fifo
    /bar:
        get:
            responses:
                "200":
                    description: ok
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/Bar'
    /baz:
        get:
            responses:
                "200":
                    description: ok
                    content:
                        application/json:
                            schema:
                                $ref: ./Baz.yaml
components:
    schemas:
        Bar:
            type: object
            properties:
                baz:
                    type: string

Baz.yaml, for completness sake:

properties:
    baz:
        type: string

Add support for arbitrary `oneOf` types

Currently, Fabrikt only supports the use of oneOf on object types. By OAS 3.0, it is allowed to use pretty much any type as well, like so:

oneOf:
  - type: integer

At the moment, this causes an error:

Exception in thread "main" java.lang.IllegalStateException: Unknown OAS type: integer and format: null and specialization: ONE_OF_ANY
	at com.cjbooms.fabrikt.model.OasType$Companion.toOasType(OasType.kt:62)
	at com.cjbooms.fabrikt.model.KotlinTypeInfo$Companion.from(KotlinTypeInfo.kt:55)
	at com.cjbooms.fabrikt.model.PropertyInfo$Field.<init>(PropertyInfo.kt:155)
	at com.cjbooms.fabrikt.model.PropertyInfo$Companion.getInLinedProperties(PropertyInfo.kt:114)
	at com.cjbooms.fabrikt.model.PropertyInfo$Companion.topLevelProperties(PropertyInfo.kt:54)
	at com.cjbooms.fabrikt.generators.model.JacksonModelGenerator.createModels(JacksonModelGenerator.kt:152)
	at com.cjbooms.fabrikt.generators.model.JacksonModelGenerator.generate(JacksonModelGenerator.kt:134)
	at com.cjbooms.fabrikt.cli.CodeGenerator.models(CodeGenerator.kt:58)
	at com.cjbooms.fabrikt.cli.CodeGenerator.generateControllerInterfaces(CodeGenerator.kt:44)
	at com.cjbooms.fabrikt.cli.CodeGenerator.generateCode(CodeGenerator.kt:36)
	at com.cjbooms.fabrikt.cli.CodeGenerator.generate(CodeGenerator.kt:31)
	at com.cjbooms.fabrikt.cli.CodeGen.generate(CodeGen.kt:58)
	at com.cjbooms.fabrikt.cli.CodeGen.main(CodeGen.kt:22)

Not all required considered in allOf

Not all fields that should be non-null are in a spec like this for object B:

components:
  schemas:
    A:
      type: object
      required:
        - a
      properties:
        a:
          type: string
        b:
          type: string
    B:
      allOf:
        - $ref: '#/components/schemas/A'
        - type: object
          required:
            - b

I'd expect code something like this:

data class A(val a: String, val b: String?)
data class B(val a: String, val b: String)

Instead I get:

data class A(val a: String, val b: String?)
data class B(val a: String, val b: String?)

OpenAPI 3.1

The most recent OpenAPI standard is version 3.1. Fabrikt currently uses KaiZen-OpenApi-Parser, which only supports 3.0 and is unlikely to be updated further, since the last commit on that project happened three years ago.

It would be nice if Fabrikt could support OpenAPI 3.1 as well, using something like swagger-parser instead.

AnyType for oneOf

This is a followup for #106.

openapi-generator considers foo to be of type "any" in the following example using oneOf:

openapi: 3.0.0
components:
  schemas:
    Foo:
      type: object
      properties:
        foo:
          oneOf:
            - type: string
            - type: object
              properties:
                a:
                  type: string
                b:
                  type: string

It looks like right now fabrikt just uses the first schema here:

data class Foo(
  @param:JsonProperty("foo")
  @get:JsonProperty("foo")
  val foo: String? = null
)

AnyType support

openapi-generator and swagger-codegen support "any" types via typeless schemas:

https://swagger.io/docs/specification/data-models/data-types/#any

It seems right now fabrikt doesn't support this:

openapi: 3.0.0
components:
  schemas:
    Foo:
      type: object
      properties:
        bar:
          $ref: '#/components/schemas/AnyValue'
    AnyValue:
      description: Can be any value - string, number, boolean, array or object.
Exception in thread "main" com.beust.jcommander.ParameterException: Invalid models or api file:
ValidationError(reason=Property 'bar' cannot be parsed to a Schema. Check your input)
        at com.cjbooms.fabrikt.model.SourceApi.<init>(SourceApi.kt:37)
        at com.cjbooms.fabrikt.model.SourceApi$Companion.create(SourceApi.kt:28)
        at com.cjbooms.fabrikt.cli.CodeGen.generate(CodeGen.kt:50)
        at com.cjbooms.fabrikt.cli.CodeGen.main(CodeGen.kt:22)

By default openapi-generator uses kotlin.Any: OpenAPITools/openapi-generator#10070

In our project we map AnyType to Jackson's JsonNode since that seemed a bit more convenient to work with.

Add support for nested polymorphic models

It should be possible to generate nested polymorphic models. A schema could be both, SuperType and SubType, however only one is actual resolving

Example:

Given the API definition below

  schemas:
    RootDiscriminatorType:
      type: string
      enum:
        - firstLevelChild

    FirstLevelDiscriminatorType:
      type: string
      enum:
        - secondLevelChild1
        - secondLevelChild2

    RootType:
      type: object
      required:
        - rootDiscriminator
      properties:
        rootDiscriminator:
          $ref: "#/components/schemas/RootDiscriminatorType"
        rootField1:
          type: string
        rootField2:
          type: boolean
      discriminator:
        propertyName: rootDiscriminator
        mapping:
          firstLevelChild: '#/components/schemas/FirstLevelChild'

    FirstLevelChild:
      allOf:
        - $ref: '#/components/schemas/RootType'
        - type: object
          required:
            - firstLevelDiscriminator
          properties:
            firstLevelDiscriminator:
              $ref: '#/components/schemas/FirstLevelDiscriminatorType'
            firstLevelField1:
              type: string
            firstLevelField2:
              type: integer
          discriminator:
            propertyName: firstLevelDiscriminator
            mapping:
              secondLevelChild1: '#/components/schemas/SecondLevelChild1'

    SecondLevelChild1:
      allOf:
        - $ref: '#/components/schemas/FirstLevelChild'
        - type: object
          required:
            - metadata
          properties:
            metadata:
              $ref: '#/components/schemas/SecondLevelMetadata'

I would expect the model hierarchy to be

RootType->FirstLevelChild -> SecondLevelChild

Actual generation

RootType -> FirstLevelChild
SecondLevelChild

Support generating controller methods with `suspend` (for spring-webflux)

spring-webflux supports Kotlin suspend methods in controllers. Seems like this would be simple for fabrikt to support, though it would have to be optional.

The suspend modifier should be the only change that's needed for webflux support, otherwise the controllers generated by fabrikt look pretty much identical to the ones we use right now generated by openapi-generator.

External model reference doesn't generate JsonSubtypes or actual subtypes

So given two files:

api.yaml:

openapi: 3.0.0
paths: {}
info:
  title: ""
  version: ""
components:
  schemas:
    Wrapper:
      type: object
      properties:
        polymorph:
          $ref: 'external-models.yaml#/components/schemas/PolymorphicEnumDiscriminator'

and
external-models.yaml:

openapi: 3.0.0
paths: {}
info:
  title: ""
  version: ""
components:
  schemas:
    PolymorphicEnumDiscriminator:
      type: object
      discriminator:
        propertyName: some_enum
        mapping:
          obj_one_only: '#/components/schemas/ConcreteImplOne'
          obj_two_first: '#/components/schemas/ConcreteImplTwo'
          obj_two_second: '#/components/schemas/ConcreteImplTwo'
          obj_three: '#/components/schemas/ConcreteImplThree'
      properties:
        some_enum:
          $ref: '#/components/schemas/EnumDiscriminator'

    ConcreteImplOne:
      allOf:
        - $ref: '#/components/schemas/PolymorphicEnumDiscriminator'
        - type: object
          properties:
            some_prop:
              type: string

    ConcreteImplTwo:
      allOf:
        - $ref: '#/components/schemas/PolymorphicEnumDiscriminator'
        - type: object
          properties:
            some_prop:
              type: string
              
    ConcreteImplThree:
      allOf:
        - $ref: '#/components/schemas/PolymorphicEnumDiscriminator'

    EnumDiscriminator:
      type: string
      enum:
        - obj_one_only
        - obj_two_first
        - obj_two_second
        - obj_three

Generated source file PolymorphicEnumDiscriminator.kt is:

package com.example.models

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.EXISTING_PROPERTY,
  property = "some_enum",
  visible = true
)
@JsonSubTypes()
sealed class PolymorphicEnumDiscriminator() {
  abstract val someEnum: EnumDiscriminator
}

I would expect it to work just as it does if the schemas are defined in the api.yaml file.

Provide the complete url path to the http client generator

If the servers.url section is provided in the spec, the full path should be appended to the baseUrl for every route. It should just use the root "/" otherwise.

e.g.
Spec:

servers:
  - url: https://myapp.mydomain.com/path1/path2
...
paths:
  /auth:

should generate on the client:

"$baseUrl/path1/path2/auth"

Polymorphic generation creates empty JsonSubtypes

If your schema casing isn't consistent with the class name casing, the JsonSubtypes value of polymorphic discriminator objects is empty.

e.g., given this (where only the case of polymorphicEnumDiscriminator is modified from the example in the project:

openapi: 3.0.0
paths: {}
info:
  title: ""
  version: ""
components:
  schemas:
    Wrapper:
      type: object
      properties:
        polymorph:
          $ref: '#/components/schemas/polymorphicEnumDiscriminator'
    polymorphicEnumDiscriminator:
      type: object
      discriminator:
        propertyName: some_enum
        mapping:
          obj_one_only: '#/components/schemas/ConcreteImplOne'
          obj_two_first: '#/components/schemas/ConcreteImplTwo'
          obj_two_second: '#/components/schemas/ConcreteImplTwo'
          obj_three: '#/components/schemas/ConcreteImplThree'
      properties:
        some_enum:
          $ref: '#/components/schemas/EnumDiscriminator'

    ConcreteImplOne:
      allOf:
        - $ref: '#/components/schemas/polymorphicEnumDiscriminator'
        - type: object
          properties:
            some_prop:
              type: string

    ConcreteImplTwo:
      allOf:
        - $ref: '#/components/schemas/polymorphicEnumDiscriminator'
        - type: object
          properties:
            some_prop:
              type: string
              
    ConcreteImplThree:
      allOf:
        - $ref: '#/components/schemas/polymorphicEnumDiscriminator'

    EnumDiscriminator:
      type: string
      enum:
        - obj_one_only
        - obj_two_first
        - obj_two_second
        - obj_three

you get:

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.EXISTING_PROPERTY,
  property = "some_enum",
  visible = true
)
@JsonSubTypes()
sealed class PolymorphicEnumDiscriminator() {
  abstract val someEnum: EnumDiscriminator
}

It doesn't seem like the case of anything else matters, just the discriminator object.

202 Accepted Responses should not require Location Header

For this API operation:

  /refeed:
    post:
      summary: "Schedule a list of events which are to be resent to Nakadi in their current state"
      parameters:
        - $ref: "#/components/parameters/FlowId"
        - $ref: "#/components/parameters/TokenInfo"
      requestBody:
        $ref: "#/components/requestBodies/RefeedBody"
      responses:
        202:
          description: "The events were successfully rescheduled"
        default:
          description: "error occurred - see status code and problem object for more\
            \ information."
          content:
            application/problem+json:
              schema:
                $ref: "https://zalando.github.io/problem/schema.yaml#/Problem"
      security:
        - oauth2:
            - "uid"

The Service Interface generated for a POST with 202 response body has the following signature:

  fun create(
    refeedBody: RefeedBody,
    xTokeninfoForward: TokenInfo,
    xFlowId: String?
  ): Pair<URI, RefeedBody?>
}

This is fine for a 201 response, as the Location header is mandatory according to the RFC and is used in the generated Controller code.

For 202 and other responses, the URI should probably be nullable in the service interface signature:

  fun create(
    refeedBody: RefeedBody,
    xTokeninfoForward: TokenInfo,
    xFlowId: String?
  ): Pair<URI?, RefeedBody?>
}

Or possibly even be changed to:

  fun create(
    refeedBody: RefeedBody,
    xTokeninfoForward: TokenInfo,
    xFlowId: String?
  ): Unit
}

Otherwise service implementations are forced to create a dummy URI that is discarded in the generated controller code

Incomplete generation for Models in externally referenced APIs

The support for external referenced schemas is incomplete. When a schema in an external API spec makes use of aggregators anyOf oneOf or allOf fabrikt fails to find the additional schemas.

For example in this test scenario, if we modify the external API spec to include the following oneOf and allOf combinations:
Primary API spec

openapi: 3.0.0
components:
  schemas:
    ContainingExternalReference:
      type: object
      properties:
        some-external-reference:
          $ref: './external-models.yaml#/components/schemas/ExternalObject'

Externally Referenced Spec

openapi: 3.0.0
components:
  schemas:
    ExternalObject:
      type: object
      properties:
        another:
          $ref: "#/components/schemas/ExternalObjectTwo"        
        one_of:
          $ref: "#/components/schemas/ExternalOneOf"
          
    ExternalObjectTwo:
      type: object
      required:
        - errors
      properties:
        list-others:
          type: array
          items:
            $ref: '#/components/schemas/ExternalObjectThree'
   
    ExternalObjectThree:
      type: object
      required:
        - enum
        - description
      properties:
        enum:
          type: string
          enum:
            - one
            - two
            - three
        description:
          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

Then the generated models do not contain data classes for
ExternalOneOf ParentOneOf OneOfOne or OneOfTwo

Duplicated discriminator names

If the same field name is used in separate objects for their individual discriminators then generation station is adding all possible classes as JSON deserialization sub classes.
Please see below example:

openapi: 3.0.0
components:
  schemas:
    PolymorphicSuperTypeOne:
      x-fabric-resource-definition: true
      type: object
      discriminator:
        propertyName: shared
      required:
        - shared
      properties:
        shared:
          type: string
    PolymorphicTypeOneA:
      allOf:
        - $ref: "#/components/schemas/PolymorphicSuperTypeOne"
        - type: object
          properties:
            whateverA:
              type: string
    PolymorphicTypeOneB:
      allOf:
        - $ref: "#/components/schemas/PolymorphicSuperTypeOne"
        - type: object
          properties:
            whateverB:
              type: integer
              format: int32
    PolymorphicSuperTypeTwo:
      x-fabric-resource-definition: true
      type: object
      discriminator:
        propertyName: shared
      required:
        - shared
      properties:
        shared:
          type: string
    PolymorphicTypeTwoA:
      allOf:
        - $ref: "#/components/schemas/PolymorphicSuperTypeTwo"
        - type: object
          properties:
            whateverC:
              type: string
    PolymorphicTypeTwoB:
      allOf:
        - $ref: "#/components/schemas/PolymorphicSuperTypeTwo"
        - type: object
          properties:
            whateverD:
              type: integer
              format: int32

Generates something like:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "shared",
    visible = true
)
@JsonSubTypes(
    JsonSubTypes.Type(
        value = PolymorphicTypeOneA::class,
        name =
            "PolymorphicTypeOneA"
    ),
    JsonSubTypes.Type(
        value = PolymorphicTypeOneB::class,
        name =
            "PolymorphicTypeOneB"
    ),
    JsonSubTypes.Type(
        value = PolymorphicTypeTwoA::class,
        name =
            "PolymorphicTypeTwoA"
    ),
    JsonSubTypes.Type(
        value = PolymorphicTypeTwoB::class,
        name =
            "PolymorphicTypeTwoB"
    )
)
sealed class PolymorphicSuperTypeOne(
    open val shared: String
)

Add support for Spring service implementation templates

Add a cli option for generating the Spring service implementations with empty body templates. Something like:

@Service
class MyResourceServiceImpl() : MyResourceService {
    override fun read(param1: String, param2: String?): Customer {
        TODO("Not Implemented Yet!")
    }
}

Use operationId in clients as well

Follow-up for #111: for the sake of consistency, it seems sensible to use the operationId in clients as well, not just controllers. It looks like right now, the name is generated based on the path (/example-path-1 -> getExamplePath1).

Downloading a release and building it does not work -> missing .git folder

I tried downloading the latest release from the Releases tab. When I tried building it, I got the following error:

* Where:
Build file '/home/gregor/src/fabrikt-2.1.1/build.gradle.kts' line: 4

* What went wrong:
An exception occurred applying plugin request [id: 'com.palantir.git-version', version: '0.12.3']
> Failed to apply plugin 'com.palantir.git-version'.
   > Cannot find '.git' directory

Running git init . helped and I could then build the project.

Client generation failing when model is untyped

Client generation is using the following as input to Jackson methods:

Map<String, Any>::class.java

This is not valid when using parameterised types. It should be migrated over to use Jackson's TypeReference object:

typeRef: TypeReference<T>

Support String format UUID

A string with format type UUID should generate a data class using UUID instead of String.

   properties:
        stock_location_id:
          description: >
            The ID of the Stock Location from which this reservation should be fulfilled.
          type: string
          format: uuid

Should result in:
val stockLocationId: UUID

Wrapper classes for primitive types

In our codebase, we have a lot of single-argument data classes to wrap raw strings / integers / UUIDs / etc. This improves type safety (mixing up different things that happen to have the same type now causes a compiler error) and readability. With openapi-generator, we were able to get the generated models to use these (hand-written) wrappers by using their type and import mapping features.

However, I'm thinking that ideally, the wrappers would be generated as well. Basically, the idea would be that if there is an explicit named schema, it is always generated. Right now, the following simply "inlines" the AccountToken schema, so Account has a token: UUID argument:

openapi: 3.0.0
components:
  schemas:
    Account:
      type: object
      required:
        - token
      properties:
        token:
          $ref: '#/components/schemas/AccountToken'
    AccountToken:
      type: string
      format: uuid

With a potential new CLI flag, it could instead generate something like this instead:

data class Account(
  @param:JsonProperty("token")
  @get:JsonProperty("token")
  @get:NotNull
  val token: AccountToken
)

data class AccountToken(
  @get:JsonValue
  @get:NotNull
  val value: UUID
)

The @get:JsonValue ensures that Jackson (de)serialization works as expected, i.e. treating it as a JSON string rather than a JSON object. In the future, it might even make sense for the wrappers to be @JvmInline value classes, right now it seems Jackson doesn't like those.


The other complication here (and technically a separate problem) is that we have a lot of these wrapper schemas in shared spec files (think $ref: 'shared/tokens.yml#/components/schemas/AccountToken') used by many different APIs, and we generate each API into a separate Gradle module. By default that would lead to a lot of duplicated types being generated that are not compatible with each other. To avoid that we would need some mechanism to say "if the schema comes from a separate yml file, don't generate a type and instead assume it can be found in this package" (so perhaps a map from .yml file path to package paths).


These two things seem to be the biggest remaining blockers for us to be able to switch from openapi-generator to fabrikt.

Would you be interested in supporting these use cases in fabrikt? If desired I could have a stab at implementing this in the form of a pull request sometime. I'm open to discussing alternative ideas as well. :)

Adding the ability to generate reflection-config.json

Add a cli option that will generate the reflection-config.json out of the schema definitions. Such like this:

[
  {
    "name" : "package.GeneratedModel",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  },
  ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.