feat(swift): support server URL variable templating - #17354
feat(swift): support server URL variable templating#17354devin-ai-integration[bot] wants to merge 3 commits into
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| const interpolationsById = new Map( | ||
| variables.map(({ variable, name }) => [ | ||
| variable.id, | ||
| `\\(${name} ?? "${escapeSwiftStringLiteralContent(variable.default ?? "")}")` | ||
| ]) | ||
| ); |
There was a problem hiding this comment.
🟡 Generated Swift SDK fails to build when a server URL placeholder is named like a Swift keyword
The placeholder's name is inserted verbatim into the generated Swift text (${name} at generators/swift/sdk/src/generators/environment/serverUrlVariables.ts:63) without the keyword protection every other generated reference gets, so a URL placeholder called e.g. protocol or default produces source code that will not build.
Impact: Customers whose API URLs use a variable whose name matches a Swift language keyword get an SDK that fails to compile.
Missing reserved-keyword escaping in the generated string interpolation
getServerUrlVariables derives names with caseConverter.camelUnsafe (generators/swift/sdk/src/generators/environment/serverUrlVariables.ts:46,49), i.e. names that are not keyword-escaped; the codegen layer escapes them at write time (Expression.reference → escapeReservedKeyword, generators/swift/codegen/src/ast/Expression.ts:222-223). urlTemplateToStringLiteral bypasses that by building the interpolation as a raw string, so for a variable named protocol the environment extension emits return "https://\(protocol ?? "https")..." instead of the required \(`protocol` ?? "https"), which is not valid Swift. escapeReservedKeyword is exported from @fern-api/swift-codegen (generators/swift/codegen/src/syntax/index.ts) and can be applied here.
Note the same names are also used as parameters in SingleUrlEnvironmentGenerator.generateUrlVariablesExtension and RootClientGenerator.serverUrlVariableParams, where Expression.reference already escapes them — so only the manual interpolation is inconsistent.
| const interpolationsById = new Map( | |
| variables.map(({ variable, name }) => [ | |
| variable.id, | |
| `\\(${name} ?? "${escapeSwiftStringLiteralContent(variable.default ?? "")}")` | |
| ]) | |
| ); | |
| const interpolationsById = new Map( | |
| variables.map(({ variable, name }) => [ | |
| variable.id, | |
| `\\(${escapeReservedKeyword(name)} ?? "${escapeSwiftStringLiteralContent(variable.default ?? "")}")` | |
| ]) | |
| ); |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Confirmed and fixed in 4d93a03. The interpolation was the one place the name reached generated source without going through Expression.reference's escapeReservedKeyword, so a variable named protocol would have emitted \(protocol ?? "https") — invalid Swift, while the matching parameter declaration was correctly backticked. Now wrapped in escapeReservedKeyword, with a unit test asserting "https://\(protocol ?? "https").example.com". No seed output changes (no fixture uses a keyword-named variable).
…tion Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Description
The Swift SDK generator ignored server URL variables: an environment declared as
https://api.{region}.example.com/v1was emitted as the frozen default URL, so{region}could never be changed without passing a fullbaseURL. Swift and Rust were the last two generators missing this (Rust: #17353); TS/Python/Java/Go/C#/PHP/Ruby already support it.Templated environments now expose a
url(...)method, and the root client takes each variable as an initializer parameter:An explicit
baseURLstill wins, omitted variables fall back to their IR defaults, and APIs without templates are untouched (baseURLkeeps its non-optional, defaulted form). Multiple-base-URL environments are unaffected — Swift doesn't generate environments for them at all yet (TODO(kafkas)), so templating them is a follow-up.Notable details:
urlVariablesentry doesn't leak a parameter.timeout,token, …) is exposed asserverUrlTimeoutrather than shadowing it.Changes Made
serverUrlVariables.ts: variable extraction (dedupe, template filtering, collision-safe naming) and URL-template → Swift string-literal rendering.SdkGeneratorContextand generated theApiEnvironment.url(...)extension fromSingleUrlEnvironmentGenerator.RootClientGenerator: added the variable parameters, madebaseURLoptional when templating applies, and resolved it viaresolvedBaseURL; factored the default-environment lookup out ofgetDefaultBaseUrl.server-url-templating-single-urlseed fixture.Testing
serverUrlVariables.test.ts(7 cases: dedupe, casing, unused variables, reserved-name collisions, interpolation with defaults, literal/unterminated placeholders).pnpm turbo run compile test --filter @fern-api/swift-sdk→ 38 passed.seed test --generator swift-sdkrun: 140/140 fixtures pass, and the only functional diff is the templating fixture (all others byte-identical). The templating fixture also passes with scripts enabled, i.e.swift test -c releaseinswift:6.1.2.Link to Devin session: https://app.devin.ai/sessions/ffc45071747143ae905306ba18077836