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

Using Links to set a field in the target operation's request body #1594

Closed
jonathann92 opened this issue May 31, 2018 · 15 comments
Closed

Using Links to set a field in the target operation's request body #1594

jonathann92 opened this issue May 31, 2018 · 15 comments
Labels
links Moved to Moonwalk Issues that can be closed or migrated as being addressed in Moonwalk

Comments

@jonathann92
Copy link

jonathann92 commented May 31, 2018

Hi,

I would like to use links to set certain fields in the request body of another operation. For example using the petstore example, I would like to create a pet and grab the pet id from the response. Then I would like that pet id to be reflected in the request body for the "PUT" pet method to update the pet.

I've seen examples of links where they take the value from the response and put it into another request's header, query, path parameters. However I am not sure how to use the setting values in the requestBody correctly. On the links/requestBody documentation website there is an example but it sets the request body like this, "requestBody: '$response.body#/id'". From my understanding, this would make the entire requestBody the id in plaintext instead of a xml or json object.

How would I set the requestBody's id to "$response.body#/id"?

I also see on the swagger.io website saying that we can prefix parameters.

If two or more parameters have the same name, prefix the names with the parameter location – path, query, header or cookie, like so:
parameters:
path.id: ...
query.id: ...

Would it be possible to do prefix with the requestBody like this:
parameters:
requestBody.id: '$response.body#/id'

I made a .yml file in case you wanted to see what I am trying to achieve. You can get the link to the raw file and explore it on the petstore swagger

Links:
Sample links .yml file: https://github.com/jonathann92/PlainFiles/blob/master/openapi.yml#L174
Petstore swagger: http://petstore.swagger.io/
Documentation for links: https://swagger.io/docs/specification/links/#requestBody

@darrelmiller
Copy link
Member

darrelmiller commented Jun 1, 2018

This currently isn't possible. Link Parameter keys are just parameter name references, not expressions. This is an interesting enhancement. I'm not sure exactly how many people would use it, nor what the challenges are to implement it.

I think that maybe the right way to implement this, would be to add a new parameter type that could use expressions as a target.

openapi: 3.0.0
info: 
  title: Experiment
  version: 1.0
paths:
  '/':
     get:
       responses:
         '200':
            description: ok
            links:
              submitLink:
                operationId: postSubmit
                parameters:
                  foo: request.body.foo
  'submit':
    post:
      operationId: postSubmit
      parameters:
        - name:  foo
          in: expression
          target: request.body.foo
           

Although, having said, this. I'm really not sure how a client would construct the rest of the requestBody. Feels a bit weird to me.

@jonathann92
Copy link
Author

thanks for replying quickly! I just checked my .yml sample and I forgot I removed the update pet section. I added the update pet api endpoint in the .yml file.

I find if we can take data coming back from responses like a pet id and pet category id would be helpful to make workflows for an api. Maybe we can format it something like in my example file. We would assign the schema property to the value in the response.body.id and then the rest of the schema stays as their default values

https://github.com/jonathann92/PlainFiles/blob/25783adb7b4c866877c96c575a7627a774dc5f71/openapi.yml#L205

@mindstretch23
Copy link

mindstretch23 commented Nov 25, 2018

Any update on this? I'm also in need of the use case explained by @jonathann92. Currently, links are not usuable for operations with several properties in the target request body. It feels like the most common use case in this scenario is setting just certain properties in the target request body.

I don't understand what the purpose of using a link to a single property to set an entire request body is. If your request body only has one property in it, then why would it be a request body and not a parameter in the first place?

I get that you can instead link an entire response to an entire request body, but it's REALLY hard to design an API in a symmetric way like this. More often, you have some properties in one operation's response that correspond to some properties in another operation's request body. The current "all or nothing" support for linking to request bodies seems rather obscure and impractical.

It would be much more useful to follow the same structure of the existing implementation of parameters links, for setting only certain properties in a request body. I think there's a simpler way to do it rather than the suggestion made by @darrelmiller. Like this:

links:
  getInventory:
    operationId: getInventory
    requestBody:
      id: '$response.body#/id'

The last line maps the property id (in the target request body) to the property id (from the source response). This way, the key is used in the exact same way it's currently used for the parameters keyword.

Is there a reason it wasn't implemented this way originally? This standardizes the syntax for parameters and requestBody keywords. Setting only certain fields in a target request body is the common use case. Setting an entire request body is the edge case. The implementation and syntax should cater to the former.

@mindstretch23
Copy link

@darrelmiller any update on this topic? (see ☝️ )

@anentropic
Copy link

anentropic commented Jun 9, 2020

I agree with others above, the current requestBody option of Link object is not useful in most cases.

Thinking this through...

The requestBody schema of the link target may be an object or a list. So we need to use a JSON Pointer to address the linked fields in it. A JSON Pointer can address into nested objects, or "first item of a list" etc, all potentially necessary here. A runtime-expression provides this feature, via the fragment portion.

So I think @darrelmiller's proposal is the best idea:

      parameters:
        - name: foo
          in: expression
          target: $request.body#/foo

But not all runtime-expressions would be valid, since we can only target elements of the request, so an expression like $response.body#/status would be meaningless in this context. Since we already have good options for parameters in other parts of the request, maybe we actually just want this:

      parameters:
        - name: foo
          in: body
          target: /foo

Now the target is just a bare JSON Pointer, rather than a runtime-expression.

One other thing we need to consider is what to do about the style field of the Parameter object. It seems like we should just omit it, along with schema, explode and allowReserved since they are not relevant - we want to use the schema and serialization style already defined for the request body. It should be invalid to specify any of these fields in conjunction with in: body.

So a full example would look like:

openapi: 3.0.0
info: 
  title: Experiment
  version: 1.0
paths:
  '/':
     get:
       responses:
         '200':
            description: ok
            links:
              submitLink:
                operationId: postSubmit
                parameters:
                  foo_param: $response.body#/foo_val
  '/submit':
    post:
      operationId: postSubmit
      parameters:
        - name: foo_param
          in: body
          target: /foo

Meaning:
"take the foo_val value from GET / [200] response and use it as foo in request body to POST /submit"

It does not matter if the postSubmit request schema has other required fields which we don't specify as parameters, it just means that they cannot be sourced from the linked request. The client may ask a user to fill them out manually, or in my use case (automated testing) I will generate fake data that matches the schema for those, while extracting foo_val from the previous request.

I can think of one other alternative construction, which doesn't use the parameters section and is defined within the Link object only. I'll probably be using this in the interim, since I can define it as an extension property:

openapi: 3.0.0
info: 
  title: Experiment
  version: 1.0
paths:
  '/':
     get:
       responses:
         '200':
            description: ok
            links:
              submitLink:
                operationId: postSubmit
                x-requestBodyParameters:
                  /foo: $response.body#/foo
  '/submit':
    post:
      operationId: postSubmit

Where the key /foo is a JSON Pointer into the requestBody of the link target, and the value $response.body#/foo is a runtime-expression to extract a value from the current operation.

One advantage of this link-driven format is if you are linking to an operation in a schema that you don't control. The author of the other schema may not be aware of which body fields need to be exposed as parameters in order for you to make a successful link, but it doesn't matter here as we can specify everything we need to as the link author.

@mkistler
Copy link

mkistler commented Nov 5, 2020

I've recently encountered this same problem. Based on the above discussion and my own experience, I think this is not an uncommon use case for links and we should find some way to broaden the definition of links to support it.

@anentropic
Copy link

FWIW I favour the x-requestBodyParameters form described above, defined as part of Link object, as this fits naturally when defining the equivalent for "backlinks" as discussed in #2196

Otherwise people might wonder what is the point of defining request body fields again as parameters since it would seem obvious that all fields in the request body schema are already a type of parameter.

So the use-case for this feature is specifically around defining Links, how to specify that values from the response of one operation should be used in the request body of a downstream operation.

@luckybroman5
Copy link

After applying OAS extensively towards many different applications, like AWS API Gateway, client generation, documentation, design, and many other things, I MUST request that this be brought to the forefront of discussion for the TSC.

Of the many proposals around this topic, any would do. I don't think the problem is the how, it's the why. Therefore rather than being noise amongst the other great proposals on this thread, I will advocate for why this is a paramount need for OAS.

Documentation

Every set of backend services, whether micro or monolithic, has operations dependent upon responses from the others. It's almost safe to say, that for documentation purposes, if there are no fragments of a request body that aren't from another operation's response body, the two requests don't belong in the same spec because they'd have nothing to do with each other.

It's a common process for any type of modeling tool to granularly define relationships. Whether it be an ERD or a Function Call Graph, there needs to be a way for description.

Further, it's often ambiguous whether identically named parameters are in fact the same or sourced from other responses without explicit declaration. For one to make assumptions about parameters based off name in a large OAS inevitably leads to bugs.

Common, Practical Use Cases

  1. Refresh tokens
    • Typically are returned along with an authentication token and expiration date. The refresh token is then used along with other parameters in the request body to facilitate the "refresh". Examples: The Github API (Login) -> The Github API (Refresh), at the time of this writing, it's impossible to represent this common pattern in OAS
  2. Bookmarking Video Content
    • Imagine you provide a video streaming platform and want to allow users to partially watch content, then resume playing later. To achieve this at a basic level, you'll need to provide some form of identifier for the video, the user's authentication information, and the playhead. A reasonable place to put the playhead and video Id would be the request body. You'd need to get the video Id from some other meta-data based service.
  3. Compare and Swap
    • If you need to update a resource that could potentially be getting updated by multiple users at the same time, a simple method would be to "Compare and Swap". If at first you must read from an endpoint to get it's "compare" value, then how would you include it along with it's "swap" value in the request body?

Design/Architecture

Hardly ever do entire request bodies comprise of a singular response attribute. In fact, if this were the case, one could argue that it's almost certainly flawed API Design. If an entire request body is to be sourced from a response body, why wouldn't the original operation that returned said response not just make the request on the client's behalf? The current implementation of having: responseBody encourages an anti pattern, and goes against modern paradigms. Should a developer who doesn't know better try to design an API completely using OAS, they might be mislead.

The Future and Beyond

The OAS and what it aims to do are truly something great. Using OAS it's possible to document web request for both the human and machine. One of the last things missing is the ability to further describe the relationships each operation might play into another. Should this "last mile" problem be solved, developers integrating with API's might not ever need to even write a line of code nor converse with it's maintainers.

@darrelmiller, @whitlockjc, @earth2marsh, @MikeRalphson, @webron, @usarid, Please do consider/reconsider this enhancement during your next TSC.

@kadeatfox
Copy link

++ on what @luckybroman5 says here.

@mkistler
Copy link

I agree with the general sentiment here but I'm having trouble following some of the details. For example:

Further, it's often ambiguous whether identically named parameters are in fact the same or sourced from other responses without explicit declaration.

A Link Object specifies an operationId (required) and then parameters. The named parameters in the link are parameters of the operation with that operationId, which must be unique according to the spec. So I see no potential for ambiguity there.

But the details are not really important, since there are clear cases for links to request body properties -- and some other changes/improvements to links (e.g. "back links"). The TSC has been consumed recently with the monumental effort of getting OpenAPI 3.1 out the door. Now that that is done I think they will be open for "new business". I'll work to get this on the agenda for an upcoming meeting.

@anentropic
Copy link

anentropic commented Feb 24, 2021

I'm glad to hear this is being considered. If I can clarify anything from my previous comments and suggestions please let me know.

As with the backlinks I've currently implemented this for my purposes using extension properties, documented here:
https://apigraph.readthedocs.io/en/latest/reference/openapi-extensions.html#x-apigraph-requestbodyparameters

@luckybroman5
Copy link

I agree with the general sentiment here but I'm having trouble following some of the details. For example:

Further, it's often ambiguous whether identically named parameters are in fact the same or sourced from other responses without explicit declaration.

A Link Object specifies an operationId (required) and then parameters. The named parameters in the link are parameters of the operation with that operationId, which must be unique according to the spec. So I see no potential for ambiguity there.

But the details are not really important, since there are clear cases for links to request body properties -- and some other changes/improvements to links (e.g. "back links"). The TSC has been consumed recently with the monumental effort of getting OpenAPI 3.1 out the door. Now that that is done I think they will be open for "new business". I'll work to get this on the agenda for an upcoming meeting.

Hey @mkistler, apologies for the misunderstanding. I'm only talking about the inability to link request attribute in a body back to response attribute. Since one is limited here, unless explicitly stated, it's ambiguous of where that request attribute in the body is sourced.

That being said, I really appreciate your consideration for this. I have used the OAS quite a lot throughout my career and am thankful of how great it is.

@bvisin
Copy link

bvisin commented Feb 25, 2021

+1 on getting this on prioritized!

@Hervian
Copy link

Hervian commented Jun 14, 2023

Any progress on this?

Like the OP I need to decorate the swagger doc with information that links data from the response of a GET request to a specific property of a JSON object request body used in a POST request.

@handrews
Copy link
Member

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:

@handrews handrews added the Moved to Moonwalk Issues that can be closed or migrated as being addressed in Moonwalk label May 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
links Moved to Moonwalk Issues that can be closed or migrated as being addressed in Moonwalk
Projects
None yet
Development

No branches or pull requests

12 participants