Skip to content

Commit 231436c

Browse files
usiegj00Jonathan Siegel
andauthored
[crystal] Fix empty auth_settings Hash literal (#23625)
The Crystal client codegen template emits `Hash{...}` for the `auth_settings` method. When an OpenAPI spec has no auth methods recognized by the template (e.g. a non-standard OAuth2 flow URN like `urn:ietf:params:oauth:grant-type:device_code`), the loop emits no entries and the result is a bare `Hash{}` -- which Crystal rejects: Error: for empty hashes use '{} of KeyType => ValueType' This wraps the populated branch in `{{#authMethods.0}}...{{/authMethods.0}}` and adds an empty branch via `{{^authMethods}}` that emits a typed empty hash literal `{} of String => Hash(Symbol, String)`, which compiles cleanly. Adds two regression tests in CrystalClientCodegenTest: - testAuthSettingsWithNoAuthMethodsEmitsValidCrystal: asserts the no-auth case emits `{} of String => Hash(Symbol, String)` and not `Hash{}`. - testAuthSettingsWithAuthMethodsStillEmitsHashLiteral: asserts the populated case still emits `Hash{` (no regression). Co-authored-by: Jonathan Siegel <jonathan@aluminum.io>
1 parent b3629fc commit 231436c

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

modules/openapi-generator/src/main/resources/crystal/configuration.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ module {{moduleName}}
209209

210210
# Returns Auth Settings hash for api client.
211211
def auth_settings
212+
{{#authMethods.0}}
212213
Hash{
213214
{{#authMethods}}
214215
{{#isApiKey}}
@@ -250,6 +251,10 @@ module {{moduleName}}
250251
{{/isOAuth}}
251252
{{/authMethods}}
252253
}
254+
{{/authMethods.0}}
255+
{{^authMethods}}
256+
{} of String => Hash(Symbol, String)
257+
{{/authMethods}}
253258
end
254259

255260
# Returns an array of Server setting

modules/openapi-generator/src/test/java/org/openapitools/codegen/crystal/CrystalClientCodegenTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,77 @@ public void testBooleanDefaultValue() throws Exception {
133133
}
134134
}
135135

136+
@Test
137+
public void testAuthSettingsWithNoAuthMethodsEmitsValidCrystal() throws Exception {
138+
// Regression test: when an OpenAPI spec has no recognized auth methods,
139+
// the generated configuration.cr's auth_settings method must not emit
140+
// a bare `Hash{}` literal, which is invalid Crystal. Crystal requires
141+
// `{} of K => V` for empty hash literals.
142+
final File output = Files.createTempDirectory("test").toFile();
143+
output.mkdirs();
144+
output.deleteOnExit();
145+
146+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/pathWithHtmlEntity.yaml");
147+
CodegenConfig codegenConfig = new CrystalClientCodegen();
148+
codegenConfig.setOutputDir(output.getAbsolutePath());
149+
150+
ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegenConfig);
151+
152+
DefaultGenerator generator = new DefaultGenerator();
153+
List<File> files = generator.opts(clientOptInput).generate();
154+
boolean configFileGenerated = false;
155+
for (File file : files) {
156+
if (file.getName().equals("configuration.cr")) {
157+
configFileGenerated = true;
158+
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
159+
// Bug: the previous template emitted `Hash{\n }` for empty
160+
// auth methods, which Crystal rejects with:
161+
// Error: for empty hashes use '{} of KeyType => ValueType'
162+
Assert.assertFalse(content.contains("Hash{\n }"),
163+
"configuration.cr must not contain an empty `Hash{}` literal");
164+
Assert.assertFalse(content.contains("Hash{}"),
165+
"configuration.cr must not contain an empty `Hash{}` literal");
166+
// Fix: emit a typed empty hash literal that compiles in Crystal.
167+
assertTrue(content.contains("{} of String => Hash(Symbol, String)"),
168+
"configuration.cr must emit a typed empty hash literal in auth_settings");
169+
}
170+
}
171+
if (!configFileGenerated) {
172+
fail("configuration.cr file is not generated!");
173+
}
174+
}
175+
176+
@Test
177+
public void testAuthSettingsWithAuthMethodsStillEmitsHashLiteral() throws Exception {
178+
// Ensure the empty-auth fix did not regress the populated case.
179+
final File output = Files.createTempDirectory("test").toFile();
180+
output.mkdirs();
181+
output.deleteOnExit();
182+
183+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/crystal/petstore.yaml");
184+
CodegenConfig codegenConfig = new CrystalClientCodegen();
185+
codegenConfig.setOutputDir(output.getAbsolutePath());
186+
187+
ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegenConfig);
188+
189+
DefaultGenerator generator = new DefaultGenerator();
190+
List<File> files = generator.opts(clientOptInput).generate();
191+
boolean configFileGenerated = false;
192+
for (File file : files) {
193+
if (file.getName().equals("configuration.cr")) {
194+
configFileGenerated = true;
195+
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
196+
assertTrue(content.contains("Hash{"),
197+
"configuration.cr should still emit `Hash{` for populated auth methods");
198+
Assert.assertFalse(content.contains("{} of String => Hash(Symbol, String)"),
199+
"configuration.cr should not emit empty hash literal when auth methods exist");
200+
}
201+
}
202+
if (!configFileGenerated) {
203+
fail("configuration.cr file is not generated!");
204+
}
205+
}
206+
136207
@Test
137208
public void testSanitizeModelName() throws Exception {
138209
final CrystalClientCodegen codegen = new CrystalClientCodegen();

0 commit comments

Comments
 (0)