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

How to specify an array of objects as a query parameter? #1265

Open
andyredhead opened this issue Jan 26, 2023 · 3 comments
Open

How to specify an array of objects as a query parameter? #1265

andyredhead opened this issue Jan 26, 2023 · 3 comments

Comments

@andyredhead
Copy link

We have a REST endpoint based on SOFA wrapping a graphql endpoint.

The query exposed by the REST endpoint can take an array of "objects" as one of the parameters.

If we provide a single object, everything works ok, however we are struggling to figure out how to specify more than one object in our query request... any guidance will be greatly appreciated

The endpoint lets us ask when "Articles" will be available to collect from a "Branch", where articles consist of a SKU and quantity while a branch just has a simple string id.

The graphql endpoint looks like:

query OrderDeliveryPromises($locale: String!, $articles: [Article!]!, $fulfilmentDetails: FulfilmentDetails!) {
  orderDeliveryPromises(locale: $locale, articles: $articles, fulfilmentDetails: $fulfilmentDetails) {
    articleId
    badge
    message
    status
    statusCode
    date
    quantity
    addToCartEnabled
    backOrderAllowed
    promiseSchedule {
      message
      date
      quantity
    }
  }
}

Typical variables for the query would look like (note the repeated entries in "articles"):

{  
  "locale": "uk",
  "articles": [
    {"articleId": "6086476", "quantity": 1} ,
    {"articleId": "6880401", "quantity": 10}
    ],
  "fulfilmentDetails": { 
    "type": "COLLECTION", 
    "collectionBranchId": "GB51"
  }
}

The sofa generated swagger.json for the query endpoint is (note articles is of type array with items "$ref": "#/components/schemas/Article"):

    "/api/order-delivery-promises": {
      "get": {
        "tags": [],
        "description": "",
        "summary": "",
        "operationId": "orderDeliveryPromises_query",
        "parameters": [
          {
            "in": "query",
            "name": "locale",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "articles",
            "required": true,
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Article"
              }
            }
          },
          {
            "in": "query",
            "name": "fulfilmentDetails",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/FulfilmentDetails"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DeliveryPromise"
                  }
                }
              }
            }
          }
        }
      }
    }

The Article type is defined as:

      "Article": {
        "type": "object",
        "required": [
          "articleId",
          "quantity"
        ],
        "properties": {
          "articleId": {
            "type": "string"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          }
        }
      } 

To request details for a single article we can make a request of the form:

https://our-sofa-server/api/order-delivery-promises?locale=uk&articles={
  "articleId": "444444",
  "quantity": 1
}&fulfilmentDetails={"type": "DELIVERY", "collectionBranchId": "GB51"}

Using curl (with url encoding added) with a command as shown below works as expected:

curl -H  'accept: application/json' \
-X 'GET' \
'https://our-sofa-server/api/order-delivery-promises?locale=uk&articles=%7B%0A%20%20%22articleId%22%3A%20%22444444%22%2C%0A%20%20%22quantity%22%3A%201%0A%7D&fulfilmentDetails=%7B%22type%22%3A%20%22DELIVERY%22%2C%20%22collectionBranchId%22%3A%20%22GB51%22%7D'

We had expected something like articles=[{"articleId": "44444", "quantity": 1},{"articleId": "555555", "quantity": 1}] to work but it doesn't (Bad Gateway error).

We cannot find a way to express more than one article in the query :(

Is there something we've overlooked?

Also, a follow-up question (assuming that supplying an array of object is possible), sending the query as a query string may not scale well if the query wants to include "lots" of articles... is there a way of sending the query as the request body instead?

@amitlicht
Copy link
Contributor

amitlicht commented Feb 5, 2023

Having investigated a similar issue recently, I came to conclusion that this is actually not a SOFA issue, but rather an OpenAPI issue - openAPI does not support lists of objects in query parameters. You can see the still-open 5yo discussion here: OAI/OpenAPI-Specification#1706

The workaround I've found is exposing the list argument in the request body rather than in the query arguments.

To support this over SOFA (without patching or modifying the resulting openapi schema), you may initiate SOFA with a custom route for this query, exposing it as HTTP POST with query body. This would look somewhere in the line of the following code:

const routes = {
    'Query.orderDeliveryPromises': { method: 'POST' , path: '/order-delivery-promises?locale=:locale' },
}

useSofa({
   ...,
   routes,
})

For the generated openapi schema, you will see that the locale parameter appears as a query argument, while articles & fulfilmentDetails appear as part of the request body.

This is a controversial yet acceptable compromise for passing complex query arguments in HTTP request body. You could technically also do this with HTTP GET, but not all client libraries support request body in HTTP GET (as it's not part of HTTP specs), and what's worse - SOFA does not currently support it.

PS: supporting query params in HTTP POST requires SOFA version 0.15.5 or later. See #1255.

@andyredhead
Copy link
Author

Thanks for your response :)

I've also come to the conclusion that OpenAPI does not support lists of objects in query parameters.

We started working around this by doing direct HTTP connections to the GraphQL endpoint.

The suggestion of using a combination of URL parameters and a POST query body looks useful :)

@tkosminov
Copy link

Having investigated a similar issue recently, I came to conclusion that this is actually not a SOFA issue, but rather an OpenAPI issue - openAPI does not support lists of objects in query parameters. You can see the still-open 5yo discussion here: OAI/OpenAPI-Specification#1706

The workaround I've found is exposing the list argument in the request body rather than in the query arguments.

To support this over SOFA (without patching or modifying the resulting openapi schema), you may initiate SOFA with a custom route for this query, exposing it as HTTP POST with query body. This would look somewhere in the line of the following code:

const routes = {
    'Query.orderDeliveryPromises': { method: 'POST' , path: '/order-delivery-promises?locale=:locale' },
}

useSofa({
   ...,
   routes,
})

For the generated openapi schema, you will see that the locale parameter appears as a query argument, while articles & fulfilmentDetails appear as part of the request body.

This is a controversial yet acceptable compromise for passing complex query arguments in HTTP request body. You could technically also do this with HTTP GET, but not all client libraries support request body in HTTP GET (as it's not part of HTTP specs), and what's worse - SOFA does not currently support it.

PS: supporting query params in HTTP POST requires SOFA version 0.15.5 or later. See #1255.

Is there a way to specify that all queries by default be via POST?

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

No branches or pull requests

3 participants