glademiller / openapiv3

Rust Open API v3 Structs and Enums for easy deserialization with serde
Apache License 2.0
173 stars 46 forks source link

Support for OpenAPI v3.1.x #50

Open JamesHinshelwood opened 3 years ago

JamesHinshelwood commented 3 years ago

OpenAPI v3.1.x has some features that would be desirable for us. So it would be neat if this crate could parse OpenAPI v3.1.x documents.

I'm not sure what the best approach for this would be though, particularly if we want to maintain support for OpenAPI v3.0.x in some form. Perhaps a separate variant of openapiv3::OpenAPI (maybe behind a feature flag) used for representing v3.1 documents instead of v3.0 ones?

Would v3.1.x support be something you'd consider adding to this crate at all? I'd be willing to put in the work if so :)

blazeworx commented 2 years ago

Also very interested in 3.1.x support. @JamesHinshelwood I'd be down to help out if you start working on this 😎

ahl commented 2 years ago

Support for 3.1 is going to be great. It would necessitate a pretty significant change to how this crate functions. In particular, this crate just defines some structs (and enums) and lets you serde_json your data into them. (Oddly, given the numbering) 3.1 was an incompatible change. For example in 3.0.x the exclusiveMaximum was of type boolean; in 3.1 it's a number. There are some other differences: https://github.com/OAI/OpenAPI-Specification/releases/tag/3.1.0-rc0

The key benefit of 3.1 (IMO) is that it brings OpenAPI into closer alignment with JSON Schema. As such, I'd like to see an OpenAPI 3.1 crate that uses a JSON Schema crate as part of its implementation (whereas this crate currently defines its own schema type). Unfortunately, the best JSON Schema crate I've found (https://github.com/GREsau/schemars/) is only sporadically maintained.

There are some key questions:

  1. Do we want a crate that supports deserialization from either 3.1.x and 3.0.x? I think the answer is yes.
  2. Do we want a crate that supports serialization into either 3.x.x or 3.0.x? That seems most useful since much of the world is going to stick with 3.0.x for a while. It raises the question: how would this crate handle a request to serialize into 3.0.x if there are structures that can only be represented in 3.1.x? For example, 3.1 supports the following type schema: { "type": "null" } while there's no way to represent this in 3.0.x. Presumably serialization could fail with some error type...
  3. Given the answers to 1 and 2, what would a good interface look like?

On all three of those questions I'd love your input: @JamesHinshelwood and @blazeworx

And with respect to @glademiller, I think that if I were to invest time into a 3.1.x crate I'd ask for your blessing to fork this crate into my employer's org and publish under a different name.

blazeworx commented 2 years ago

With OAS 3.1 bringing OAS schema closer to JSON schema, I too think that building something that uses schemars might make sense.

One thing to consider is that schemars itself has "generating OAS 3.1 schema" support on its Road map to 1.0. Not sure how that will end up playing out and what that will look like.

As far as your questions go, I think the answer for all of those depends on wether or not this should be a standalone 3.1.x only crate or not?

ahl commented 2 years ago

As far as your questions go, I think the answer for all of those depends on wether or not this should be a standalone 3.1.x only crate or not?

Yeah! For my recent uses, I'd prefer to have a crate that could deserialize from either 3.1 or 3.0. I'm only really interested in writing out 3.0.x now because 1. there isn't that much value in 3.1 and 2. the tooling for 3.1 is anemic right now.

What would you like to see?

JamesHinshelwood commented 2 years ago

To answer what would be desired from my side... All I would want is to serialise and deserialise 3.1.x OpenAPI documents. The only other bit of tooling we're using is Redoc, which already appears to be updated to 3.1.x.

However, I obviously wouldn't object to this crate supporting some combination of both 3.0.x and 3.1.x if that's helpful for others.

@ahl For your use case (deserialising from either 3.0.x or 3.1.x) do you think it would be sufficient to write a mapping to convert between v3.0 and v3.1, then write something like

let api: OpenAPIv31 = serde_json::from_str(api).or_else(|_| convert_to_3_1(serde_json::from_str::<OpenAPIv30>(api)));

(or vice-versa if you want to end up with a 3.0 document of course)

This would let us keep the v3.0 and v3.1 types separate while still providing some level of interchangeability?

In terms of borrowing an existing JSON schema implementation, I think it would make sense to do so if an appropriate one is available. If not, it probably makes sense for us to create one separately from this crate? What were your objections to https://github.com/GREsau/schemars/ ? It looks reasonably well maintained to me?

JamesHinshelwood commented 2 years ago

I had a go at adding support for 3.1.x in a branch here - https://github.com/JamesHinshelwood/openapiv3/tree/openapiv3.1

The basic approach is that we have entirely distinct OpenAPI structs for 3.0 and 3.1, plus an enum at the root of the crate to differentiate them. I'm not entirely happy with the approach, as it can be slightly annoying when importing stuff to have to chose between v3_0 and v3_1 but maybe we can solve that by enabling each with a feature flag, so only people using both will be bothered?

For schemas, I'm using Schema from schemars which seems to work pretty well.

Let me know what you think! I have no attachment to the approach, just wanted to see what it would look like in practice.

I'll try and find some 3.1.x test cases to validate this. I had a go at the 3.0.x fixtures and all seemed to pass with the 3.1.x deserializer, except for authentiq.yaml - I didn't yet investigate why.

EdorianDark commented 2 years ago

I like the implemention of OpenAPI 3.1. But is it really a common use case to use both 3.0 and 3.1 at the same time? I would guess not, so I want to suggest to release 1.0 with support for 3.0 and 2.0 with support for 3.1

msrd0 commented 2 years ago

But is it really a common use case to use both 3.0 and 3.1 at the same time?

From a server's perspective? No

But from a client's / tooling's perspective that needs to deal with whatever version the server returns, I'd guess having support for as many versions as possible at the same time is desired.

JamesHinshelwood commented 2 years ago

Thanks for the positive comments. I will try and find some time in the next week to clean up my branch and submit it as a PR here so we can get some proper feedback.

rrichardson commented 2 years ago

I just found this and, I too, am very interested in 3.1. Is there anything that I can do to help move this along?

rrichardson commented 2 years ago

The basic approach is that we have entirely distinct OpenAPI structs for 3.0 and 3.1, plus an enum at the root of the crate to differentiate them. I'm not entirely happy with the approach, as it can be slightly annoying when importing stuff to have to chose between v3_0 and v3_1 but maybe we can solve that by enabling each with a feature flag, so only people using both will be bothered?

After attempting to create a code-generator using the result, I am having serious second thoughts about the approach.

Switching between the two versions at the top of the document isn't the problem. It's the fact that I have to implement two distinct, but nearly identical code generators for the v3.0 and v3.1 versions. Every single type is different.

We need to unify these somehow. I'm not sure if traits are the way to go, or if there is something more clever here.

So far, I think the best approach would be to provide a way to "pull" the 3.0 implementation "through" the 3.1 implementation.

This could be done with a trait. I am thinking an As<T> trait might be the most flexible. E.g. all of the v3_0:: and v3_1:: types, T, would implement As. This way we can offer a unified interface. It might still a bit awkward when traversing the data structures, if you had to call them out by name.

I've already implemented From<T> for most of the implementation, so if this is the preferred direction, I will convert the From's to As's to provide a reference based approach for both structs.

ahl commented 2 years ago

I haven't prototyped it, but my plan with https://github.com/oxidecomputer/progenitor is to convert the 3.0.x import into 3.1 at the root and use that. I'm already converting from openapiv3::Schema into schemars::Schema (in order to use https://github.com/oxidecomputer/typify) so I'd get to just delete all that code.

rrichardson commented 2 years ago

The more I think about it, maybe the From<T> is the best approach. So, as you say, just convert at the beginning. With this in mind, we should probably borrow your v3.0 -> schemars::Schema to correctly implement the From<v3_0::Schema>, since it is rather involved.

ahl commented 1 year ago

FYI I intend to make a concrete proposal year in January at the latest. Agreed that 3.1 support would be great. I'm increasingly convinced that relying on schemars is the wrong approach due to its lack of support for the relevant JSON Schema draft and sporadic maintenance of the repo. Moving this repo may also be worth consideration.

kurtbuilds commented 1 year ago

I have a fork of this crate at https://github.com/kurtbuilds/openapiv3 , and recently added support for V2 and transforming (1 way) from V2 to V3.0. That structure might be good inspiration for eventual V3.1 support.

doylemark commented 6 months ago

If I can help pull things through I'm happy to lend a hand here. There are some great crates that currently can't support 3.1 because of the lack of support here.

@ahl Did a proposal ever end up being put forward?

florianmartens commented 2 months ago

Would be amazing to see progress on this.

robjtede commented 4 weeks ago

FWIW to readers of this issue, the oas3 crate is specifically an OpenAPI v3.1 crate.