apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.23k stars 89 forks source link

Support mixed path parameters (part constant, part variable) in client requests #490

Closed clearbrian closed 5 months ago

clearbrian commented 5 months ago

Description

If URL parameter is String + Int then throws Unprocessible Content error.

The Victoria & Albert museum in London has put their huge collection behind an openapi api.

https://api.vam.ac.uk/docs

Spec is at https://api.vam.ac.uk/openapi.json

Every object has an system id which starts with a O e.g. O81405 - Returns a painting

https://collections.vam.ac.uk/item/O81405/full-scale-study-for-ithe-oil-painting-constable-john-ra/

The most basic api to get a single object is defined as:

http://api.vam.ac.uk/v2/object/O81405?response_format=prettyjson

**"\\/v2\\/object\\/O{object_num}" : {**
      "get" : {
        "operationId" : "collection_object_v2_object_O_object_num__get",
        "parameters" : [
          {
            "in" : "path",
            **"name" : "object_num",**
            "required" : true,
            "schema" : {
              "title" : "The unique system number of the museum object",
              **"type" : "integer"**
            }
          },
...
        ],
...
      }
    },

Note the pre-filled O in the path so the param is a String but in the generated code you pass in an Int e.g. 81405 and its appended to the 'O' to get the final url param "O81405"


"\/v2\/object\/O{object_num}"

And the parameter type for object_num is an int

 "parameters" : [
          {
...
            "name" : "object_num",
 ...
           "schema" : {
              "title" : "The unique system number of the museum object",
              "type" : "integer"
            }
          },

When i generate a app with this spec.

And pass in an Int


let object_num: Int = 81405

let path = Operations.collection_object_v2_object_O_object_num__get.Input.Path.init(object_num: object_num)

let output = try await client.collection_object_v2_object_O_object_num__get(path:path)

print("output:\r\(output)")

At build time it returns a Unprocessable Content complaining “param is not an Int”

FIX - To get it to work I have to manually change the spec

"\/v2\/object\/**O{object_num}**" : {

"\/v2\/object\/**{object_num}**" : {
"\\/v2\\/object\\/{object_num}" : {
  "get" : {
    "parameters" : [
      {
        .....
        ....
        "schema" : {
          ...
          "type" : "string"
        },
        "name" : "object_num"
      },

I changed the Input to be a String and include the O in the object ID

//BEFORE
//let object_num: Int = 81405
//AFTER
let object_num: String = "O81405"

//And now api generated code works

let path = Operations.collection_object_v2_object_O_object_num__get.Input.Path.init(object_num: object_num)

let output = try await client.collection_object_v2_object_O_object_num__get(path:path)

print("output:\r\(output)")              

And code runs fine though I need to hack the spec so if would be better if the generator could handle this format.
"/O{object_num}"

Api generator should handle this valid api definition where the sub part of the url may be String and Int

http://api.vam.ac.uk/v2/object/O{object_num}?response_format=prettyjson

[http://api.vam.ac.uk/v2/object/O81405?response_format=prettyjson](http://api.vam.ac.uk/v2/object/O81405?response_format=prettyjson)

Reproduction

Reproduction

openapi.json

Get official spec from

https://api.vam.ac.uk/openapi.json

openapi-generator-config.yaml

generate:
  - types
  - client

v1 - spec unchanged

let object_num: Int = 81405

let path = Operations.collection_object_v2_object_O_object_num__get.Input.Path.init(object_num: object_num)

let output = try await client.collection_object_v2_object_O_object_num__get(path:path)

print("output:\r\(output)")

v2 - changed spec FROM

"\/v2\/object\/**O**{object_num}" : {
    "type" : "integer"
"\/v2\/object\/{object_num}" : {
    "type" : "string"

Change param from Int to String and include the prefixed ‘O’ in the string

//let object_num: Int = 81405

//AFTER
let object_num: String = "O81405"

//And now api generated code works

let path = Operations.collection_object_v2_object_O_object_num__get.Input.Path.init(object_num: object_num)

let output = try await client.collection_object_v2_object_O_object_num__get(path:path)

print("output:\r\(output)")

Unprocessible content error on build goes away and api runs ok

Package version(s)

latest - dec 2023

Expected behavior

should handle url param that may be string and int

Environment

swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5) Target: x86_64-apple-macosx13.0

Additional information

none

clearbrian commented 5 months ago

dependencies versions

czechboy0 commented 5 months ago

Thank you for the great issue, @clearbrian, I was able to reproduce the issue and have a fix coming shortly.

Note that the fact that the parameter is an integer is not the issue, it was the fact that the path component was a mix of a constant value "O" and a variable.

czechboy0 commented 5 months ago

Ok verified that #491 will fix your issue, thanks again for the report.

czechboy0 commented 5 months ago

Fixed in 1.1.0.