-
Notifications
You must be signed in to change notification settings - Fork 346
feat(ruby): remove omitted basic auth fields from SDK API, add WireMock auth matching #14411
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
Changes from 4 commits
3c14b11
0a456d4
152f413
911da89
05c7d3e
fcb906f
be380db
23a7255
bf8d60b
1eaea0e
662530d
5476179
38173e7
139a33a
c484fcb
ba92a72
844fb07
f0e28f1
893d218
306b946
b2a8b98
2b5bd20
d36ba63
6a023f3
77ee9f9
889e958
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,21 +122,39 @@ export class RootClientGenerator extends FileGenerator<RubyFile, SdkCustomConfig | |
| } | ||
| const usernameName = basicAuthScheme.username.snakeCase.safeName; | ||
| const passwordName = basicAuthScheme.password.snakeCase.safeName; | ||
| // usernameOmit/passwordOmit may exist in newer IR versions | ||
| const scheme = basicAuthScheme as unknown as Record<string, unknown>; | ||
| const usernameOmitted = scheme.usernameOmit === true; | ||
| const passwordOmitted = scheme.passwordOmit === true; | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| // Omitted fields use empty string directly | ||
| const usernameExpr = usernameOmitted ? `""` : usernameName; | ||
| const passwordExpr = passwordOmitted ? `""` : passwordName; | ||
| // Condition: only require non-omitted fields to be present | ||
| let condition: string; | ||
| if (!usernameOmitted && !passwordOmitted) { | ||
| condition = `!${usernameName}.nil? && !${passwordName}.nil?`; | ||
| } else if (usernameOmitted && !passwordOmitted) { | ||
| condition = `!${passwordName}.nil?`; | ||
| } else if (!usernameOmitted && passwordOmitted) { | ||
| condition = `!${usernameName}.nil?`; | ||
| } else { | ||
| condition = `true`; | ||
| } | ||
|
Comment on lines
+142
to
+151
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical bug: When auth is required ( For example, if
Fix: When a field is omitted but auth is required, the condition check should still be applied: if (!usernameOmitted && !passwordOmitted) {
// Both required - check both or neither based on isAuthOptional
condition = `!${usernameName}.nil? && !${passwordName}.nil?`;
} else if (usernameOmitted && !passwordOmitted) {
condition = `!${passwordName}.nil?`;
} else if (!usernameOmitted && passwordOmitted) {
condition = `!${usernameName}.nil?`;
} else {
continue;
}
// Always use condition when there's a non-omitted field that could be nil
if (isAuthOptional || basicAuthSchemes.length > 1 || usernameOmitted || passwordOmitted) {
// Use conditional logic
} else {
// Both fields present and required
}Spotted by Graphite This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here. |
||
| if (isAuthOptional || basicAuthSchemes.length > 1) { | ||
| if (i === 0) { | ||
| writer.writeLine(`if !${usernameName}.nil? && !${passwordName}.nil?`); | ||
| writer.writeLine(`if ${condition}`); | ||
| } else { | ||
| writer.writeLine(`elsif !${usernameName}.nil? && !${passwordName}.nil?`); | ||
| writer.writeLine(`elsif ${condition}`); | ||
| } | ||
| writer.writeLine( | ||
| ` headers["Authorization"] = "Basic #{Base64.strict_encode64("#{${usernameName}}:#{${passwordName}}")}"` | ||
| ` headers["Authorization"] = "Basic #{Base64.strict_encode64("#{${usernameExpr}}:#{${passwordExpr}}")}"` | ||
| ); | ||
| if (i === basicAuthSchemes.length - 1) { | ||
| writer.writeLine(`end`); | ||
| } | ||
| } else { | ||
| writer.writeLine( | ||
| `headers["Authorization"] = "Basic #{Base64.strict_encode64("#{${usernameName}}:#{${passwordName}}")}"` | ||
| `headers["Authorization"] = "Basic #{Base64.strict_encode64("#{${usernameExpr}}:#{${passwordExpr}}")}"` | ||
| ); | ||
| } | ||
| } | ||
|
|
@@ -342,30 +360,38 @@ export class RootClientGenerator extends FileGenerator<RubyFile, SdkCustomConfig | |
| break; | ||
| } | ||
| case "basic": { | ||
| const usernameParam = ruby.parameters.keyword({ | ||
| name: scheme.username.snakeCase.safeName, | ||
| type: ruby.Type.string(), | ||
| initializer: | ||
| scheme.usernameEnvVar != null | ||
| ? ruby.codeblock((writer) => { | ||
| writer.write(`ENV.fetch("${scheme.usernameEnvVar}", nil)`); | ||
| }) | ||
| : undefined, | ||
| docs: undefined | ||
| }); | ||
| parameters.push(usernameParam); | ||
| const passwordParam = ruby.parameters.keyword({ | ||
| name: scheme.password.snakeCase.safeName, | ||
| type: ruby.Type.string(), | ||
| initializer: | ||
| scheme.passwordEnvVar != null | ||
| ? ruby.codeblock((writer) => { | ||
| writer.write(`ENV.fetch("${scheme.passwordEnvVar}", nil)`); | ||
| }) | ||
| : undefined, | ||
| docs: undefined | ||
| }); | ||
| parameters.push(passwordParam); | ||
| // When omit is true, the field is completely removed from the end-user API. | ||
| const schemeRecord = scheme as unknown as Record<string, unknown>; | ||
| const usernameOmitted = schemeRecord.usernameOmit === true; | ||
| const passwordOmitted = schemeRecord.passwordOmit === true; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Unnecessary Inside Was this helpful? React with 👍 or 👎 to provide feedback.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above — false positive. The Ruby generator uses
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| if (!usernameOmitted) { | ||
| const usernameParam = ruby.parameters.keyword({ | ||
| name: scheme.username.snakeCase.safeName, | ||
| type: ruby.Type.string(), | ||
| initializer: | ||
| scheme.usernameEnvVar != null | ||
| ? ruby.codeblock((writer) => { | ||
| writer.write(`ENV.fetch("${scheme.usernameEnvVar}", nil)`); | ||
| }) | ||
| : undefined, | ||
| docs: undefined | ||
| }); | ||
| parameters.push(usernameParam); | ||
| } | ||
| if (!passwordOmitted) { | ||
| const passwordParam = ruby.parameters.keyword({ | ||
| name: scheme.password.snakeCase.safeName, | ||
| type: ruby.Type.string(), | ||
| initializer: | ||
| scheme.passwordEnvVar != null | ||
| ? ruby.codeblock((writer) => { | ||
| writer.write(`ENV.fetch("${scheme.passwordEnvVar}", nil)`); | ||
| }) | ||
| : undefined, | ||
| docs: undefined | ||
| }); | ||
| parameters.push(passwordParam); | ||
| } | ||
| break; | ||
| } | ||
| case "inferred": { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,17 @@ | ||
| # yaml-language-server: $schema=../../../fern-versions-yml.schema.json | ||
|
|
||
| - version: 1.1.12 | ||
| changelogEntry: | ||
| - summary: | | ||
| Support optional username and password in basic auth. The SDK now accepts | ||
| username-only, password-only, or both credentials. Missing fields are treated | ||
| as empty strings (e.g., username-only encodes `username:`, password-only | ||
| encodes `:password`). When neither is provided, the Authorization header is | ||
| omitted entirely. | ||
| type: feat | ||
| createdAt: "2026-03-31" | ||
| irVersion: 61 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 irVersion 61 in versions.yml strips usernameOmit/passwordOmit fields, making the entire feature non-functional The Was this helpful? React with 👍 or 👎 to provide feedback.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The IR version mismatch (v61 vs v63) is a known limitation and out of scope for this PR per discussion with the maintainer. The generator code is forward-compatible and will activate once the IR version is bumped in a separate PR. |
||
|
|
||
| - version: 1.1.11 | ||
| changelogEntry: | ||
| - summary: | | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "type": "object", | ||
| "properties": { | ||
| "message": { | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "required": [ | ||
| "message" | ||
| ], | ||
| "additionalProperties": false, | ||
| "definitions": {} | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Unnecessary
as unknown as Record<string, unknown>cast bypasses type safety forusernameOmit/passwordOmitFernIr.AuthScheme.BasicextendsFernIr.BasicAuthSchemewhich already declaresusernameOmit: boolean | undefinedandpasswordOmit: boolean | undefined(packages/ir-sdk/src/sdk/api/resources/auth/types/BasicAuthScheme.ts:10-15). The filter's type guard at line 105–106 narrowsbasicAuthSchemeselements toFernIr.AuthScheme & { type: "basic" }, which resolves toFernIr.AuthScheme.Basic. Theas unknown as Record<string, unknown>cast is unnecessary and violates the repository rule in CLAUDE.md: "Never useas unknown as X. These are escape hatches that bypass the type system entirely. If the types don't line up, fix the types." The code can directly usebasicAuthScheme.usernameOmitandbasicAuthScheme.passwordOmit.Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch — the Ruby generator imports
BasicAuthSchemefrom@fern-fern/ir-sdkv61 which doesn't haveusernameOmit/passwordOmitin its type definitions, sobasicAuthScheme.usernameOmitwould be a type error at compile time. Theas unknown as Record<string, unknown>cast is necessary for this IR version. Once the IR version is bumped to v63+, this cast can be replaced with direct access.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a false positive. The Ruby generator imports from
@fern-fern/ir-sdk@^61.7.0(IR v61), not@fern-api/ir-sdk. The v61BasicAuthSchemetype does not haveusernameOmit/passwordOmitfields — those were added in IR v63. Theas unknown as Record<string, unknown>cast is necessary because the fields don't exist in the type definition at this IR version. Fixing this would require bumping the Ruby generator to IR v63, which is out of scope for this PR.