Generate types for the path string
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
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
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.