Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OpenAPI 3.1 #39

Closed
Tracked by #1
czechboy0 opened this issue May 25, 2023 · 17 comments · Fixed by #219
Closed
Tracked by #1

Support OpenAPI 3.1 #39

czechboy0 opened this issue May 25, 2023 · 17 comments · Fixed by #219
Assignees
Labels
area/openapi Adding/updating a feature defined in OpenAPI. kind/feature New feature. size/S Small task. (A couple of hours of work.)
Milestone

Comments

@czechboy0
Copy link
Contributor

Today, we assume OpenAPI 3.0.x, but we should explore how we can also support 3.1, and continue to support both in parallel for some time, as 3.0.x is still the most adopted one out in the wild.

@czechboy0 czechboy0 added area/openapi Adding/updating a feature defined in OpenAPI. status/needs-design Needs further discussion and a concrete proposal. kind/feature New feature. size/L Large task. (A couple of weeks of work.) labels May 25, 2023
@mattpolzin
Copy link

mattpolzin commented Jun 15, 2023

I'd be curious to hear your thoughts on how close to the mark OpenAPIKit's new compatibility layer comes for this use-case.

The idea (described in the release notes for Alpha 6 is that you would ideally have just one file that imported all of OpenAPI 3.0.x support, OpenAPI 3.1.x support, and OpenAPIKit compatibility layer -- that file would attempt to parse an OpenAPI Document as 3.0.x and convert the result to 3.1.x but then if it failed it would attempt to parse the same document as OpenAPI 3.1.x. In the release notes I give a low fidelity example w/r/t error handling because you'd probably want to give a better error if the document was supposed to be 3.0.x but failed to parse than just attempting the guaranteed failed parsing as 3.1.x.

The big caveat to this approach: you need to migrate this codebase to use the OpenAPIKit module everywhere (except for in that location that handles attempting to parse as 3.0.x). Although most differences between the OpenAPIKit30 and OpenAPIKit modules are minor, this could easily become a bit of a chore.

Aside from that caveat, I think this approach would be well suited especially to this use-case where you are taking OpenAPI in but not then re-serializing a new OpenAPI Document because you won't be hurt by "losing" the information that the original document was imported as 3.0.x.

[EDIT] RE handling of the parsing of the document as one version or another, I now see your code already takes a preemptive step of determining the document spec version prior to having OpenAPIKit parse the document, so that code would already be well equipped to decide whether to use OpenAPIKit's 3.0.x or 3.1.x module for parsing.

@czechboy0
Copy link
Contributor Author

Yeah I'd like us to take that approach, but I haven't played with the conversion enough yet to be confident that we won't lose fidelity in some cases, and wouldn't want to handle 3.0 and 3.1 differently in code generation. It'd be good for someone to prototype this approach and see what issues are hit. Contributions are always very welcome 🙂

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Jun 18, 2023

Use case about OpenAPI 3.1: discourse_api_docs/openapi.json

More info can be found at #72.

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Jun 20, 2023

For anyone eager to try OpenAPI 3.1 with swift-openapi-generator, you can give https://github.com/Kyle-Ye/swift-openapi-generator/tree/openapi_31x a try.

Disclaimer: This branch only fixes the compilation problem for testing openapi 3.1.0. It has not been fully tested. Please do not use it for production purposes.

@czechboy0
Copy link
Contributor Author

Looking at @Kyle-Ye's experimental converter branch, I'm concerned that we'd have to make such a large change atomically, in one go.

The phases I'd like us to be able to add OpenAPI 3.1 support (while also continuing to support 3.0) in would instead look like:

  1. Start with the leaves: convert Swift OpenAPI Generator logic that operates just on e.g. parameters from OpenAPIKit30 to OpenAPIKit. This wouldn't yet add support for parsing OpenAPI 3.1 documents, but it'd update one part of our codebase to use the latest currency types from OpenAPIKit, and we can discuss any 3.0 -> 3.1 changes in smaller, focused PRs, especially topics around the changes to JSON schema will warrant thoughtful discussion.
  2. Until all of the Swift OpenAPI Generator codebase is migrated, migrate small pieces of the codebase in individual PRs.
  3. Only once the whole codebase works with OpenAPIKit (and not OpenAPIKit30) currency types we can update our document parsing logic and for 3.0 convert the whole document, passing it into the generator logic, and for 3.1 pass it as-is, as it doesn't require conversion. Only at this point would the generator accept 3.1 documents, in addition to 3.0.

But for this piecemeal approach to be possible, we'd need OpenAPIKit's OpenAPIKitCompat to make it possible to convert just bits of the document, instead of the whole document, here: https://github.com/mattpolzin/OpenAPIKit/blob/3.0.0-alpha.7/Sources/OpenAPIKitCompat/Compat30To31.swift The conversion functions are currently fileprivate, would you be open to making them public, @mattpolzin? It'd go a long way for projects that deeply integrate with OpenAPIKit 🙂

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Jun 20, 2023

Looking at @Kyle-Ye's experimental converter branch, I'm concerned that we'd have to make such a large change atomically, in one go.

The phases I'd like us to be able to add OpenAPI 3.1 support (while also continuing to support 3.0) in would instead look like:

  1. Start with the leaves: convert Swift OpenAPI Generator logic that operates just on e.g. parameters from OpenAPIKit30 to OpenAPIKit. This wouldn't yet add support for parsing OpenAPI 3.1 documents, but it'd update one part of our codebase to use the latest currency types from OpenAPIKit, and we can discuss any 3.0 -> 3.1 changes in smaller, focused PRs, especially topics around the changes to JSON schema will warrant thoughtful discussion.
  2. Until all of the Swift OpenAPI Generator codebase is migrated, migrate small pieces of the codebase in individual PRs.
  3. Only once the whole codebase works with OpenAPIKit (and not OpenAPIKit30) currency types we can update our document parsing logic and for 3.0 convert the whole document, passing it into the generator logic, and for 3.1 pass it as-is, as it doesn't require conversion. Only at this point would the generator accept 3.1 documents, in addition to 3.0.

But for this piecemeal approach to be possible, we'd need OpenAPIKit's OpenAPIKitCompat to make it possible to convert just bits of the document, instead of the whole document, here: https://github.com/mattpolzin/OpenAPIKit/blob/3.0.0-alpha.7/Sources/OpenAPIKitCompat/Compat30To31.swift The conversion functions are currently fileprivate, would you be open to making them public, @mattpolzin? It'd go a long way for projects that deeply integrate with OpenAPIKit 🙂

Can we do the same like the upstream OpenAPIKit? Provide _OpenAPIGeneratorCore with dependency of OpenAPIKit and _OpenAPIGeneratorCore30 with dependency of OpenAPIKit30. Or even maintain 2 branch one for 3.0.x and one for 3.1.x.

This way we'll make the implementation simpler but make the maintain harder.

@Kyle-Ye
Copy link
Contributor

Kyle-Ye commented Jun 20, 2023

Looking at @Kyle-Ye's experimental converter branch, I'm concerned that we'd have to make such a large change atomically, in one go.

The phases I'd like us to be able to add OpenAPI 3.1 support (while also continuing to support 3.0) in would instead look like:

  1. Start with the leaves: convert Swift OpenAPI Generator logic that operates just on e.g. parameters from OpenAPIKit30 to OpenAPIKit. This wouldn't yet add support for parsing OpenAPI 3.1 documents, but it'd update one part of our codebase to use the latest currency types from OpenAPIKit, and we can discuss any 3.0 -> 3.1 changes in smaller, focused PRs, especially topics around the changes to JSON schema will warrant thoughtful discussion.
  2. Until all of the Swift OpenAPI Generator codebase is migrated, migrate small pieces of the codebase in individual PRs.
  3. Only once the whole codebase works with OpenAPIKit (and not OpenAPIKit30) currency types we can update our document parsing logic and for 3.0 convert the whole document, passing it into the generator logic, and for 3.1 pass it as-is, as it doesn't require conversion. Only at this point would the generator accept 3.1 documents, in addition to 3.0.

But for this piecemeal approach to be possible, we'd need OpenAPIKit's OpenAPIKitCompat to make it possible to convert just bits of the document, instead of the whole document, here: https://github.com/mattpolzin/OpenAPIKit/blob/3.0.0-alpha.7/Sources/OpenAPIKitCompat/Compat30To31.swift The conversion functions are currently fileprivate, would you be open to making them public, @mattpolzin? It'd go a long way for projects that deeply integrate with OpenAPIKit 🙂

One reason of the extra adaptive code is due to the change between OpenAPI.reference and JSONReference. OpenAPIKit repo updates some in OpenAPIKit module but not OpenAPIKit30 module. And according to the author, this is the expected behavior.

See mattpolzin/OpenAPIKit#276

@czechboy0
Copy link
Contributor Author

Can we do the same like the upstream OpenAPIKit? Provide _OpenAPIGeneratorCore with dependency of OpenAPIKit and _OpenAPIGeneratorCore30 with dependency of OpenAPIKit30. Or even maintain 2 branch one for 3.0.x and one for 3.1.x.

I'd like to avoid duplicating the whole generator logic module, as we want to fully migrate over to the OpenAPIKit module from OpenAPIKit30, we just don't want to do so in a single PR.

@mattpolzin
Copy link

I'm open to making the conversion logic public. I don't think I want to properly support them as public API, but this feels like a pretty reasonable request all things considered so I'll just think on how best to express that. It may just be documentation that indicates this as a valid piecemeal approach for now but I might make them private again in version 4.x of OpenAPIKit perhaps since that's a release I tentatively plan for code cleanup and minimum swift version bump anyway.

I would advise against the multiple module strategy (which was already sounding like an unlikely option in comments above). OpenAPIKits logic and/or code structure would have been drastically more complicated if I had tried to make the same code handle both OpenAPI 3.0 and 3.1, but two modules has definitely had a high cost as well. It still might have been the right move for me, but it would be very unfortunate if that move resulted in downstream projects bifurcating their codebases as well.

@czechboy0
Copy link
Contributor Author

Thanks @mattpolzin, totally understood. Yeah, we would use the piecemeal compat APIs only during the transition period, so you could even consider only making them public for a single alpha release, and remove them before 3.0 goes out of alpha.

@mattpolzin
Copy link

mattpolzin commented Jun 20, 2023

OpenAPIKit 3.0.0-alpha.8 exposes most of the to31() methods publicly. Hopefully your efforts won't suffer for not having access to the remaining functions (e.g. converting an JSONReference from one module to the other in isolation) because those operations are probably more granular than makes sense for a migration plan, but let me know if I am wrong and I will expose more of the API.

https://github.com/mattpolzin/OpenAPIKit/releases/tag/3.0.0-alpha.8

@czechboy0
Copy link
Contributor Author

So I have a PR ready, took only about 30mins to migrate the codebase to use the OpenAPIKit module instead of OpenAPIKit30, and now we can load either! Thanks so much for the seamless transition, @mattpolzin!

I do have one question, @mattpolzin though - one of the drivers of us adding 3.1 support is the ability to have description on next to $refs - but they don't seem to be getting propagated by OpenAPIKit today. Is that expected or am I holding it wrong? Hopefully won't be a large amount of work, and it's my only remaining blocker of calling initial 3.1 support done once #219 lands. Thanks 🙏

@czechboy0
Copy link
Contributor Author

Specifically, I'm looking for this to work (properties in object schemas):

Foo:
  type: object
  properties:
    bar:
      description: This is a bar.
      $ref: '#/components/schemas/Baz`

I need to get This is a bar generated on the property, but right now the JSONSchema's description is empty. Might it be because properties are String -> JSONSchema instead of String -> OpenAPI.Reference?

@mattpolzin
Copy link

Interesting. It turns out that when I first wrote my JSONSchema implementation it was not permitted to specify annotations like description next to $refs (well, it was stated that any annotations would be ignored) but now the newer versions do allow it. Even more interestingly, even the newer versions say that any given application can choose to use those annotations however it wants (it may choose to ignore them in favor of annotations within the referenced schema). However, given that OpenAPI is explicit about its support for overriding description and title alongside references, I think it is certainly the right move to carry that behavior forward into JSONSchema.

Hopefully doing so will be as simple as swapping the JSONReference out for an OpenAPI.Reference (as you suggested above). I'll look into that soon. If you get a chance to throw a ticket on OpenAPIKit that would help me remember, but I think I'll get to it soon enough.

@czechboy0 czechboy0 added this to the 0.1.next milestone Aug 25, 2023
@czechboy0 czechboy0 added size/S Small task. (A couple of hours of work.) and removed status/needs-design Needs further discussion and a concrete proposal. size/L Large task. (A couple of weeks of work.) labels Aug 25, 2023
@mattpolzin
Copy link

Tracking need for overriding reference descriptions within schemas here: mattpolzin/OpenAPIKit#298

@mattpolzin
Copy link

Ok, a new release has been cut with a few bug fixes and improvements including the ability to hang properties like description next to $refs.

I'm happy to answer questions or field suggestions regarding the changes: https://github.com/mattpolzin/OpenAPIKit/releases/tag/3.0.0-beta.2

@czechboy0
Copy link
Contributor Author

@mattpolzin I just tried it out and it works great, thank you for the quick turnaround! 🙏

czechboy0 added a commit that referenced this issue Aug 29, 2023
Support OpenAPI 3.1 in addition to 3.0

### Motivation

Fixes #39. We need to start accepting 3.1 documents in addition to 3.0.

It also has useful features, like being able to add descriptions to references.

### Modifications

Migrated the codebase to use the `OpenAPIKit` instead of the `OpenAPIKit30` module, and updated the parser to handle both 3.0 and 3.1, and convert 3.0 to 3.1 documents in memory transparently.

### Result

OpenAPI 3.1 documents are now accepted, instead of rejected. Any 3.1 specific features can be addressed separately, this is the MVP support.

### Test Plan

Updated the file-based reference test to use version 3.1.0, and the parser tests that 3.0.x documents are loaded and converted successfully. So no need to duplicate our reference tests or anything.


Reviewed by: Kyle-Ye, glbrntt

Builds:
     ✔︎ pull request validation (5.8) - Build finished. 
     ✔︎ pull request validation (5.9) - Build finished. 
     ✔︎ pull request validation (docc test) - Build finished. 
     ✔︎ pull request validation (integration test) - Build finished. 
     ✔︎ pull request validation (nightly) - Build finished. 
     ✔︎ pull request validation (soundness) - Build finished. 

#219
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/openapi Adding/updating a feature defined in OpenAPI. kind/feature New feature. size/S Small task. (A couple of hours of work.)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants