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

Question on nested oneOf definition #2260

Closed
sonic-kaka opened this issue Jun 10, 2020 · 16 comments
Closed

Question on nested oneOf definition #2260

sonic-kaka opened this issue Jun 10, 2020 · 16 comments
Labels

Comments

@sonic-kaka
Copy link

sonic-kaka commented Jun 10, 2020

Using the following nested oneOf definition as an example

mammal:
  oneOf:
    - $ref: '#/components/schemas/whale'
    - $ref: '#/components/schemas/zebra'
    - $ref: '#/components/schemas/pig'
whale:
  type: object
  properties:
    hasBaleen:
      type: boolean
    hasTeeth:
      type: boolean
  required:
    - hasBaleen
    - hasTeeth
pig:
  oneOf:
    - $ref: '#/components/schemas/basque_pig'
    - $ref: '#/components/schemas/danish_pig'
basque_pig:
  type: object
  properties:
    color:
      type: string
  required:
    - color
danish_pig:
  type: object
  properties:
    weight:
      type: double
  required:
    - weight

UPDATE (JUN 12): I've removed the classname property to avoid confusion on the correctness of the object.

The API server's endpoint returns mammal (not provided in the spec above). If let's say the API client (in Java for example) submits an HTTP request to the endpoint and the server returns a danish_pig, e.g.

{
  "weight": 32.1
}

One can further process the object returned by the server and send it back (via another endpoint expecting mammal in the payload). For example, switch the payload from danish_pig to basque_pig and send it to the server, which should accepts it without issue. Switching the payload from danish_pig to something else (e.g. bird, snake) should result in an error in the client side if the validation is performed or the server should return an error as bird, snake do not conform to the mammal definition.

Here is the question: should the client obtain the danish_pig object directly? Or should it obtain pig object directly? Or actually the return object should be mammal object instead?

Thank you for your time.

@sebastien-rosset
Copy link
Contributor

sebastien-rosset commented Jun 10, 2020

Here is the question: should the client obtain the danish_pig object directly? Or should it obtain pig object directly? Or actually the return object should be mammal object instead?

What do you mean when you use the term "object"? Do you mean the "object" as defined in RFC 8259? I.e An object structure is represented as a pair of curly brackets surrounding zero or more name/value pairs (or members) Or are you are referring to an implementation object (such as an instance of a Java class or C#), as implemented by particular client in a given programming language? Or something else?

If you meant the implementation object, IMO, what matters from the perspective of OAI is how the data is serialized on the wire. The client may be implemented using any number of data structures and programming constructs, which may or may not available across programming languages. E.g. class, struct, set, list... but this internal representation is not relevant to OAI.

@sonic-kaka
Copy link
Author

Good question. I used the term object as it's how the spec describes the schema: type: object. I mentioned using Java as an example but I don't think the programming language matters here as the question has to do with the definition of nested oneOf schemas.

@sebastien-rosset
Copy link
Contributor

Good question. I used the term object as it's how the spec describes the schema: type: object. I mentioned using Java as an example but I don't think the programming language matters here as the question has to do with the definition of nested oneOf schemas.

ok, then what do you mean by the client obtain the danish_pig object directly? Or should it obtain pig object directly?
You provided the following payload as an example. Are you saying the payload on the wire would be different if the object is a Pig or DanishPig?

{
  "className": "danish_pig",
  "weight": 32.1
}

@sonic-kaka
Copy link
Author

sonic-kaka commented Jun 10, 2020

The endpoint returns a mammal. Given the payload (JSON in this case), should the payload directly match "danish_pig" (nested oneOf schema) and return the representation of "danish_pig" in the respective programming language? or it should return "pig" or "mammal" in the respective programming language?

Given the payload below:

{
  "className": "danish_pig",
  "weight": 32.1
}

If the object is a Pig (oneOf schema with danish_pig, basque_pig), then the above payload is valid with respect to Pig if I understand oneOf definition correctly.

If someone defines another endpoint that returns "danish_pig", then the payload above is also valid.

@sebastien-rosset
Copy link
Contributor

The endpoint returns a mammal. Given the payload (JSON in this case), should the payload directly match "danish_pig" (nested oneOf schema) and return the representation of "danish_pig" in the respective programming language? or it should return "pig" or "mammal" in the respective programming language?

Given the payload below:

{
  "className": "danish_pig",
  "weight": 32.1
}

If the object is a Pig (oneOf schema with danish_pig, basque_pig), then the above payload is valid with respect to Pig if I understand oneOf definition correctly.

If someone defines another endpoint that returns "danish_pig", then the payload above is also valid.

From a OAI validation perspective, a compliant validator (the client) validates whether the payload is valid or not. The validation process does not necessarily involve creating a representation of the payload in the programming language. To validate the payload, the client has to assert all JSON schema constraints, which can be done without creating "objects" in the programming language. For example this could be done using a streaming validator.

Besides validation, the client may optionally decide to create a data structure in a programming language, such that the data can be manipulated by the client (and possibly sent back to the server). IMO this is completely up to the client to decide which data structure to use.

return the representation of "danish_pig" in the respective programming language"

This part is an internal decision to the client. It's not visible outside the client. A client may not even have the concept of a unique per schema representation of the objects. The client could use a generic JSON document data structure.

Suppose the payload below is retrieved from the server:

{
  "className": "danish_pig",
  "weight": 32.1
}

The client modifies and sends back the following payload:

{
  "className": "danish_pig",
  "weight": 37.2
}

What the client does in between to make that modification is completely up to the client. It could be as ugly as doing string replacement manipulations of the JSON payload without creating any object. It could involve parsing the payload into a JsonDocument object. Or (more sensibly) a class/struct could be instantiated. I don't think how the client represents the data in a programming language is relevant to the OAI spec.

@sonic-kaka
Copy link
Author

I think one thing I didn't make it clear in my issue description is that both API client and server are in compliance with the OAI specification (the contract between the client and server).

Using danish_pig as an example:

danish_pig:
  type: object
  properties:
    className:
      type: string
    weight:
      type: double
  required:
    - className
    - weight

All fields are required which means the value of weight and className must be present no matter how it's represented in the programming language (e.g. class, HashMap, JSON object, etc). If the representation fulfills such requirement that both weight and className are required, then the representation conforms/complies with the OpenAPI specification.

My understanding is that the same needs to be applied to oneOf (or nested oneOf or other constraints specified in the specification) meaning that it's another requirement that the representation needs to conform/comply to. For example, mammal is returned by the server and before sending it out, the server must validate the payload (JSON, XML, etc) to ensure that it cannot be snake, bird or something else - it must be oneOf whale, zebra or pig. The same validation can also happen in the client side any time after the payload is received.

I agree with you that OpenAPI specification does not mandate how the implementation should be done in the programming languages but still the output (implementation) needs to comply with the OpenAPI specification.

Back to my original question:

Here is the question: should the client obtain the danish_pig object directly? Or should it obtain pig object directly? Or actually the return object should be mammal object instead?

If client (implementation) obtains danish_pig or pig directly, will that result in information loss as certain constraints (e.g. can't be snake, bird) are dropped?

If these constraints/validations are dropped, does the client (implementation) still comply with the OpenAPI specification?

@sebastien-rosset
Copy link
Contributor

sebastien-rosset commented Jun 10, 2020

Please provide the exact definition of "obtains" and "directly". Concretely, what does the client do to "obtain" something? Please reformulate your question to avoid using vague and ambiguous terms.

Above I pointed out the client may provide one or more of the following capabilities:

  1. Validate the received payload against the OAI/JSON schema. This does not necessarily involve creating objects, for example it could be a streaming validator. In that scenario, there is no "obtaining" a danish_pig or pig "directly". The client has to validate the payload against the schema.
  2. Deserialize the received payload into a language-specific representation
  3. Provide accessors such that the application code can manipulate the object, possibly for the purpose of invoking another API.

@sonic-kaka
Copy link
Author

Please provide the exact definition of "obtains" and "directly". Concretely, what does the client do to "obtain" something? Please reformulate your question to avoid using vague and ambiguous terms.

Let's consider the following in the famous Petstore OpenAPI specification:

https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml#L57-L79

The client sends the NewPet directly to the server and then obtains Pet from the server (assuming the operation is successful). All validations (e.g. required fields) still apply.

Now consider replacing both NewPet and Pet with Mammal (oneOf schema). Would that be easier to understand? Does all the validation (including oneOf) still apply (independent of how the client is implemented)?

Above I pointed out the client may provide one or more of the following capabilities:

Validate the received payload against the OAI/JSON schema. This does not necessarily involve
creating objects, for example it could be a streaming validator. In that scenario, there is no
"obtaining" a danish_pig or pig "directly". The client has to validate the payload against the
schema.

If the client doesn't validate the payload as you mentioned "client may provide one or more of the following capabilities", then the client doesn't comply with the OpenAPI specification.

Now consider this particular line:

https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml#L66

which is the payload in the request body and it has certain properties defined.

If a client (implementation) may send NewPet or something else such as NewDog, NewPig or other values to the server, then the client (implementation) doesn't comply to the specification as only NewPet is defined in the requestBody.

Are we on the same page that all the validations (including oneOf) in the requestBody and responses must be complied with respect to the OpenAPI specification regardless of how the API clients and servers are implemented?

@sebastien-rosset
Copy link
Contributor

Are we on the same page that all the validations (including oneOf) in the requestBody and responses must be complied with respect to the OpenAPI specification regardless of how the API clients and servers are implemented?

Yes I agree a compliant validator must validate whatever is specified in a OAI document.

Here is the question: should the client obtain the danish_pig object directly? Or should it obtain pig object directly? Or actually the return object should be mammal object instead?

I still don't see how we can answer your question without reformulation. It would help if you reformulate by using words that are meaningful in the OAI spec. For example, did you mean "should the client validate the payload against the danish_pig first? Or should it validate against the pig object first?" Or something else? You should be able to reformulate the question.

@spacether
Copy link

spacether commented Jun 11, 2020

I have a couple of questions

  1. Is sending this payload valid with the spec describe in this issue?
    {"className": "pig", "color": "red"}
    In my opinion, yes, we validate against basque_pig
  2. What if classsName in basque_pig is limited to an enum with only the option "basque_pig" and className in danish_pig is limited to only the option "danish_pig" then is sending this payload valid?
    {"className": "pig", "color": "red"}
    In my opinion no, because our className is not a valid choice.

@spacether
Copy link

spacether commented Jun 11, 2020

If a client (implementation) may send NewPet or something else such as NewDog, NewPig or other values to the server, then the client (implementation) doesn't comply to the specification as only NewPet is defined in the requestBody.

Assuming the endpoint must receive NewPet:
In my opinion as long as the json serialization of that NewDog or NewPig complies with the OAI requirements of the NewPet schema then yes we can return NewDog or NewPig.

@sebastien-rosset
Copy link
Contributor

I have a couple of questions

  1. Is sending this payload valid with the spec describe in this issue?
    {"className": "pig", "color": "red"}
    In my opinion, yes, we validate against basque_pig
  2. What if classsName in basque_pig is limited to an enum with only the option "basque_pig" and className in danish_pig is limited to only the option "danish_pig" then is sending this payload valid?
    {"className": "pig", "color": "red"}
    In my opinion no, because our className is not a valid choice.

Yes, I agree with you on both.
For 1), I would say it's a semantic mistake that the OAS doc does not include enum values for the className property. Most likely in practice the API server implementation would have well-defined values for the className property; it wouldn't return or accept random values, therefore the OAS doc should capture these constraints as specific enum values. This would make it pretty clear what are the acceptable values for className.

@spacether
Copy link

spacether commented Jun 11, 2020

Yes, I agree with you on both.
For 1), I would say it's a semantic mistake that the OAS doc does not include enum values for the className property. Most likely in practice the API server implementation would have well-defined values for the className property; it wouldn't return or accept random values, therefore the OAS doc should capture these constraints as specific enum values. This would make it pretty clear what are the acceptable values for className.

It would have helped to clarify that with an enum. For 1, even:
{"className": "bird", "color": "red"} would match danish_pig

@sonic-kaka
Copy link
Author

First of all, I've removed the classname property from the spec in my original question to avoid confusion on the correctness of the object.

Yes I agree a compliant validator must validate whatever is specified in a OAI document.

OK. I think I got the answer I need.

For 1), I would say it's a semantic mistake that the OAS doc does not include enum values for the className property.

From what I understand, OpenAPI specification is for describing RESTful interfaces. I think your suggestion to include enum values for the className property is more like API best practice as not all data exchange between existing REST APIs has the property "className" defined. Even if it's defined, its name can be something else such as "class_type", "object_type", etc.

@sebastien-rosset
Copy link
Contributor

sebastien-rosset commented Jun 12, 2020

First of all, I've removed the classname property from the spec in my original question to avoid confusion on the correctness of the object.

Yes I agree a compliant validator must validate whatever is specified in a OAI document.

OK. I think I got the answer I need.

For 1), I would say it's a semantic mistake that the OAS doc does not include enum values for the className property.

From what I understand, OpenAPI specification is for describing RESTful interfaces. I think your suggestion to include enum values for the className property is more like API best practice as not all data exchange between existing REST APIs has the property "className" defined. Even if it's defined, its name can be something else such as "class_type", "object_type", etc.

Oh yes, sure, "classname" is just one possible name for the property. Like you said it could be "class_type", "object_type", "type", or any other meaningful name for REST. Even the author uses "classname", the value of the "classname" should not be leaking programming details, for example, it would be a bad idea to set the value of classname to a Java classname, because the REST API is supposed to be independent of the programming language. Each client should have its own serialization to class mapping.

@handrews
Copy link
Member

OP commented that they got the answer they needed, so I'm closing this as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants