OAI / OpenAPI-Specification

The OpenAPI Specification Repository
https://openapis.org
Apache License 2.0
28.6k stars 9.05k forks source link

Variables/Parameters in Links #1327

Closed LukasRos closed 1 month ago

LukasRos commented 6 years ago

The OpenAPI v3 links feature allows specifying related operations. To me, however, the spec was not clear on whether it's possible and if, how to define links that require an additional variable for the call.

To give a practical example, assume there's a GET /users operation to list all users and a GET /users/{id} operation to get details about an individual user. I could add the latter as a link to the former but this link requires an additional piece of information, specifically which user from the list I want to receive details for.

{
 "links" : {
  "details" : {
   "operationId" : "getUserDetails",
    "parameters": {
     "id" : "$response.body#/*/id"
    }
   }
  }
}

Is that possible or even desired to express with the links feature? If yes, is * the right syntax for a placeholder in JSON pointer? If not, is there another way?

darrelmiller commented 6 years ago

From a spec perspective, the JSON pointer with a * is not valid. While defining the links capability there was no attempt to define a mechanism to that would declare a set of links based on the assumption that a response contains a set of similarly structured objects and a client is likely to select one of those objects.

This scenario is definitely something that I have done using explicit hypermedia, but I'm not sure how feasible it would be the static links design in OpenApi V3. JSON pointer doesn't support returning a set of nodes. But JSONPath does. However, even once we are able to reference the set of potential parameter values, we still need a way to parameterize it to be able to select a particular parameter instance.

It's an interesting idea and one worth exploring more, but we need a way to indicate that a link parameter requires further client input before being resolvable.

LukasRos commented 6 years ago

Ok, thanks for your quick reply and the clarification, so I've understood links are always 1:1 pointers to other operations, never 1:n. I think that 1:n links with variables are a really interesting feature and would love to see them in a future version of the OAS.

ralfhandl commented 6 years ago

We also have the "get (filtered) collection/get single item within collection" pattern in almost all of our APIs, and it would be helpful to link the operations in that pair in a machine-readable way beyond naming or URL conventions.

handrews commented 6 years ago

I may annoy @darrelmiller by jumping in with this, but JSON Hyper-Schema can express this sort of thing as of draft-06, and better in the soon-to-be-published draft-07.

Of course, nothing implements recent hyper-schema drafts, it will be some time before I put out a reference implementation, and it doesn't have anything remotely resembling the ecosystem of OpenAPI. But... if anyone's looking for ideas, we've at least thought about this issue a lot.

tgockel commented 6 years ago

In #1452, I suggested JSONPath, which solves the same "identify pattern of things" problem. The advantage is that it should feel familiar to XPath users, so it should be too offensive to those dealing with XML data.

darrelmiller commented 6 years ago

The challenge with JSONPath is there is no detailed specification to ensure consistent implementations.

handrews commented 6 years ago

@tgockel @darrelmiller in Hyper-Schema, we avoided JSON Path by attaching links at specific locations within the schema (mapping to corresponding instance locations). So attaching the link under an items makes for a link attached to each element of the instance array that items describes.

Then we used Relative JSON Pointer to adjust variable resolution upwards or downwards. This is simple and unambiguous.

With JSONPath, in addition to the lack of specification, wildcarding really returns sets of data, and then you have to correlate your "current" location with the location within that set. Which is solvable but increasingly complex the more deeply you nest things.

Relative JSON Pointer solves this by starting from the most specifically nested thing, so that going "down" from that point does not require wildcarding, while going "up" keeps you in the correct subtree without having to calculate things.

It's a really complicated problem involving being able to set the attachment point of the link and independently set its context to potentially a different location than the attachment point, and independently set the resolution starting point of every template variable. And handle cases where the location you want to resolve from doesn't exist. We basically spent a year on this spread out across draft-06 and draft-07.

An example of different attachment point vs context is the "item" link relation, where it needs to be attached to the individual array items, but the context is supposed to be the entire collection, not the single array element within the collection.

MikeRalphson commented 6 years ago

I was idly wondering if we could resolve one simple case of this issue with some guidance in the specification:

If the link parameter expression resolves to an array, and the target parameter is itself not an array, then any item(s) from the source array MAY be used to provide the target operation parameter value.

This would handle the case of a simple array of resource ids, but not an array of resources containing ids (this could be accommodated by providing an additional itemExpression - but that's getting messy) . I appreciate that a more generic solution would be infinitely preferable.

The wording above is just to get a feel for the idea.

darrelmiller commented 6 years ago

@MikeRalphson That is a decent solution for expanding an array of ids into an array of links. I fear the much more common case is the array of objects where each object has an Id.

It will be interesting to see if we get alternateSchema working if a hyper-schema could be used to achieve this goal.

handrews commented 6 years ago

@darrelmiller

It will be interesting to see if we get alternateSchema working if a hyper-schema could be used to achieve this goal.

I think this will be possible. I'll keep this use case in mind.

chatelao commented 5 years ago

Any progress welcome. Here a 1:1 pet sample as a first contribution:

      # Link pets -> pets{petId}
      # -----------------------------------------------------
      links:
        ShowPetByPetId:   # <---- arbitrary name for the link
          operationId: showPetById
          # or
          # operationRef: '#/paths/~1pets~1{petId}/get'
          parameters:
            petId: '$response.body#/0/id'
          description: >
            The `id` value returned in the response can be used as
            the `petId` parameter in `GET /pets/{petId}`.
xorcus commented 5 years ago

Currently, the spec defines links property only in Response Object (links in Components Object do not help with this issue).

Would it be possible to add links property to Schema Object as well? The $response.body#<non-negative-integer><JSON pointer> in this context would be resolved relative to each schema object instance in the response, yielding a link.

This way, getUserDetails operation simply returns an array of User schema objects which define the links derived from each instance. The client gets the array of links resolving the User schema object links relative to each instance received in the response.

My understanding is that x-links can already be used with this semantics in Schema Object because it allows for Specification Extensions.

darrelmiller commented 5 years ago

JSON pointers in this context would be resolved relative to each schema object

The JSON Pointer spec says,

Evaluation of a JSON Pointer begins with a reference to the root value of a JSON document

https://tools.ietf.org/html/rfc6901#section-4

We would have to redefine JSON pointers and the way fragment identifiers work to make this happen. I would recommend using JSON Hyperschema if you need to do this.... or actually do hypermedia.

xorcus commented 5 years ago

I guess the use of hypermedia is not in line with OAS design-time Link Object as it requires link information in the runtime response. What I had in mind is something similar to JSON Hyper-Schema and relative JSON pointers, as described by @handrews in his comment on Jan 5, 2018, but without needing separate hyper-schema resources. Ideally, all links would be included in the OAS API spec.

As explained in Relative JSON Pointer, section 6, the fragment identifiers do not apply to relative pointers.

Would it be possible to have links in OAS Schema Object, allowing for the <non-negative-integer> prefix of JSON pointers (i.e. using Relative JSON pointers) as part of the standard OAS spec?

As far as I am aware, the only changes needed in the OAS are: a) Add links as a regular property of Schema Object (same as in Response Object) b) In Runtime Expressions, redefine the fragment:

fragment = a JSON Pointer [RFC 6901](https://tools.ietf.org/html/rfc6901)  

by allowing for the optional non-negative-integer prefix, having semantics explained in Relative JSON Pointer:

fragment = [ <non-negative-integer> ] <a JSON Pointer [RFC 6901](https://tools.ietf.org/html/rfc6901)>

This is a backward-compatible change.

If <non-negative-integer> is not there, JSON Pointer is absolute. If present, JSON pointer is relative. In majority of use cases, the evaluation of links does not depend on context in which a Schema Object is found, but rather on a Schema Object property (e.g. the unique id). Therefore, in a simplified client implementation, it should be sufficient to support evaluating only relative JSON pointers starting with zero, i.e. having the <non-negative-integer> set to zero.

markthepage commented 4 years ago

Has this been considered further since last year?

I have a model whereby an entity "A" that is related to one or more entities of type "B".

The entity of type "A" has a response body that returns an array of ID strings for entities of type "B".

For HATEOAS, I need to provide links to the "get entity B" operation by each individual ID from this array. I cannot find anyway to define this in OAS3 ... but then again I am a newbie :-)

@xorcus stated:

I guess the use of hypermedia is not in line with OAS design-time Link Object as it requires link information in the runtime response.

Does this mean that we cannot model what I think is pretty standard HATEOAS using the links object?

hrougier commented 3 years ago

Hi everyone!

Any progress on this? Considering OpenAPI schemas can be used by tools like graphql-mesh to generate & run GraphQL APIs, links missing this feature is an obstacle to automatically handle nested resolvers. Let's say you have a query like:

query {
  pets {
    name
    owner {
      name
    }
}

In the GET /pets response, you can't describe the pet => owner relation with a link like:

links:
  getOwnerById:
    description: Returns the owner of the pet
    operation: getUser
    parameters:

      userId: '$response.body#/{index}/ownerId’

This would be a huge and welcomed improvement!

AP-G commented 3 years ago

Sorry for the duplicate post. But at least there is an additional example of what is needed here: Example for link with parameter serialization needed #2539

So what could be done and who could do anything to get a step further to a solution as proposed by xorcus or hrougier? I am asking as e.g. UN/CEFACT found OpenAPI to be very relevant and so a lot of EDI-minded organizations currently start looking into the OAS. And here a link feature like this is really needed...

MikeRalphson commented 3 years ago

@AP-G Interesting to have the connection with the EDI community!

handrews commented 1 month ago

This scale of change is best discussed in Moonwalk (and can be backported if relevant and we decide to keep the 3.x line going). Closing in favor of: