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

Generate types for the path string #668

Open
atacan opened this issue Nov 10, 2024 · 2 comments
Open

Generate types for the path string #668

atacan opened this issue Nov 10, 2024 · 2 comments
Labels
kind/feature New feature. status/triage Collecting information required to triage the issue.

Comments

@atacan
Copy link

atacan commented Nov 10, 2024

Motivation

paths:
  /greet:
    get:
      operationId: getGreeting
  /count:
    get:
      operationId: getCount

The actual path of the endpoints that we define is only used in the comment or documentation of the operations. It would be useful to have the string of the path, /greet and /count in the above example, available in the Swift code as well.

One use case that I encountered was when I was making a request to a third-party API. I provide a webhook URL, meaning when the third party finishes, they will call back my endpoint that I defined in my OpenAPI YAML file for the webhook requests. In this case, I need to pass this path to this third-party provider. It would be nice to have it type-safe from changes in the OpenAPI path namings.

Proposed solution

For the simple paths this can be straightforward a constant in the Operations enum. But for the paths that have parameters in them, we might use a simple function with path parameters as its arguments.

public enum Operations {
    public enum greet {
       public static let path = "/greet"
// ...
    }
}

If we have a yaml like

paths:
  /users/{userId}:
    get:
      operationId: getUser

then

public enum Operations {
    public enum getUser {
       public static let path: @Sendable (String) -> String = { "/users/\($0)" }
// ...
    }
}

we can make use of the type of the path parameter

paths:
  /users/{id}:
    get:
      operationId: getUser
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
public enum Operations {
    public enum getUser {
       public static let path: @Sendable (Int) -> String = { "/users/\($0)" }
// ...
    }
}

Alternatives considered

No response

Additional information

No response

@atacan atacan added kind/feature New feature. status/triage Collecting information required to triage the issue. labels Nov 10, 2024
@czechboy0
Copy link
Contributor

Hi @atacan,

this is an interesting use case, I agree that for webhooks, this could be useful.

I wonder if a function wouldn't work better here, as it'd allow us to express all the parameters to the path, including future defaulted ones, consistently:

public enum Operations {
    public enum getUser {
        public static func renderedPath(userId: String) -> String { 
            "/users/\(userId)" 
        }
    }
}

Now, the problem with the above is that it won't work correctly for all strings and other types, we have this method to correctly render paths from parameters: https://github.com/apple/swift-openapi-runtime/blob/daa2fb54fe4a7f5187d7286047d5144c8cb97477/Sources/OpenAPIRuntime/Conversion/Converter%2BClient.swift#L37

But to call to that method, we'd need an instance of the Converter, which we only have in the generated client and server code.

So maybe we could constrain this feature to the server, and instead of on Types.swift, it'd be somewhere in Server.swift? Not sure, do you have ideas? https://github.com/apple/swift-openapi-generator/blob/main/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift

@atacan
Copy link
Author

atacan commented Nov 12, 2024

If the converter is needed, and it's a stored property of UniversalServer struct, that rules out to have a static function, right?

This came to my mind:

fileprivate extension UniversalServer where APIHandler: APIProtocol {

    enum Path {
        case getUser(id: Int)
    }

    func path(_ path: Path) throws -> String {
        switch path {
        case .getUser(id: let id):
            return try converter.renderedPath(template: "/users/{id}", parameters: [id])
        }
    }

// ...
}

but it is fileprivate and UniversalServer is not directly used. We simply conform to APIProtocol, and registerHandlers of APIProtocol has an instance of UniversalServer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature New feature. status/triage Collecting information required to triage the issue.
Projects
None yet
Development

No branches or pull requests

2 participants