apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.21k stars 87 forks source link

Questions re: anyOf/allOf/oneOf, and one more thing #534

Open edonv opened 2 months ago

edonv commented 2 months ago

Question

I'm sure there are details I'm unaware of that impact these things, so forgive me if this has been discussed before. I have a few questions/thoughts regarding anyOf/allOf/oneOf:

Separately, is it possible to set the configuration so the generated types from components/schemas are public, but the initializers and underlying stuff is internal? Or would I just have to do 2 separate targets that generate the types and clients separately, then I combine them for public-facing stuff in a 3rd target?

czechboy0 commented 2 months ago

Hi @edonv,

yeah this is a large topic, so let me answer the individual points inline.

Regarding nullability, check out this maintainer doc (https://swiftpackageindex.com/apple/swift-openapi-generator/1.2.1/documentation/swift-openapi-generator/handling-nullable-schemas), which goes into detail of some of the decisions we made and hopefully answers your question.

At a higher level - we try to apply the smallest amount of post-processing to the OpenAPI doc, we don't flatten various schemas, because we want to stay faithful to the doc (rationale explained here: https://swiftpackageindex.com/apple/swift-openapi-generator/1.2.1/documentation/swift-openapi-generator/project-scope-and-goals).

That means that if you write something like:

Foo:
  type: object
  properties:
    bar:
      anyOf:
        - type: null
        - type: string

we will generate the complicated structure, even though you could have spelled the same thing as:

Foo:
  type: object
  properties:
    bar:
      type: string

The idea is that the OpenAPI document is the source of truth, and we follow that as closely as possible. If your OpenAPI document is written in an inefficient or verbose way, so will your generated Swift code.

Could oneOf be translated to an enum with associated values of the possible options?

That's already the case today. If you're not seeing that, please provide an example of the input OpenAPI snippet and generated code.

For anyOf/oneOf where the only options are "null" and some $ref, could it be translated to an optional instance of that type?

Not really, for the reasons explained earlier - we don't try to optimize your OpenAPI doc, that's up to the OpenAPI doc author. We just generate the most faithful Swift representation of the OpenAPI doc, without first pre-processing it.

If you're looking for ways to find and fix inefficiencies, there are many OSS tools that take an OpenAPI document as input and suggest improvements. But that functionality is out of scope of our Swift code generator, we try to be good OpenAPI community citizens and not needlessly include too many features that are already covered by other tools, as it both increases complexity of the generator and removes flexibility from our adopters.

edonv commented 2 months ago

Thanks for the detailed answer. I'm still relatively new to OpenAPI as it is, so it's likely just that the doc I'm working on isn't as efficient as it could be, leading the generated Swift code to be the same.

But in that vein, in OpenAPI, what would you say would be the best way to represent a property in an object that must be present but can be null? It sounds like there should be a better way than what I example above.

edonv commented 2 months ago

And any suggestions regarding my last question?

Separately, is it possible to set the configuration so the generated types from components/schemas are public, but the initializers and underlying stuff is internal? Or would I just have to do 2 separate targets that generate the types and clients separately, then I combine them for public-facing stuff in a 3rd target?

simonjbeaumont commented 2 months ago

And any suggestions regarding my last question?

Separately, is it possible to set the configuration so the generated types from components/schemas are public, but the initializers and underlying stuff is internal? Or would I just have to do 2 separate targets that generate the types and clients separately, then I combine them for public-facing stuff in a 3rd target?

That's currently the only supported way, yes. Our integration test package has an example of using the generator in multiple targets to separate the generation of types, client, and server, and sharing the generated types in the other targets: https://github.com/apple/swift-openapi-generator/tree/main/IntegrationTest.

czechboy0 commented 2 months ago

But in that vein, in OpenAPI, what would you say would be the best way to represent a property in an object that must be present but can be null?

That's not easy to do for the reasons outlined in: https://github.com/apple/swift-openapi-generator/issues/513#issuecomment-1911980259

I'd recommend to try to avoid needing to make a distinction between a present null value and an absent value, if at all possible.