diff --git a/modules/openapi-generator/src/main/resources/crystal/configuration.mustache b/modules/openapi-generator/src/main/resources/crystal/configuration.mustache index 21d61690bdda..ace95a8b28fe 100644 --- a/modules/openapi-generator/src/main/resources/crystal/configuration.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/configuration.mustache @@ -209,6 +209,7 @@ module {{moduleName}} # Returns Auth Settings hash for api client. def auth_settings +{{#authMethods.0}} Hash{ {{#authMethods}} {{#isApiKey}} @@ -250,6 +251,10 @@ module {{moduleName}} {{/isOAuth}} {{/authMethods}} } +{{/authMethods.0}} +{{^authMethods}} + {} of String => Hash(Symbol, String) +{{/authMethods}} end # Returns an array of Server setting diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/crystal/CrystalClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/crystal/CrystalClientCodegenTest.java index 2c4f9af953af..b1ca249b9c76 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/crystal/CrystalClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/crystal/CrystalClientCodegenTest.java @@ -133,6 +133,77 @@ public void testBooleanDefaultValue() throws Exception { } } + @Test + public void testAuthSettingsWithNoAuthMethodsEmitsValidCrystal() throws Exception { + // Regression test: when an OpenAPI spec has no recognized auth methods, + // the generated configuration.cr's auth_settings method must not emit + // a bare `Hash{}` literal, which is invalid Crystal. Crystal requires + // `{} of K => V` for empty hash literals. + final File output = Files.createTempDirectory("test").toFile(); + output.mkdirs(); + output.deleteOnExit(); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/pathWithHtmlEntity.yaml"); + CodegenConfig codegenConfig = new CrystalClientCodegen(); + codegenConfig.setOutputDir(output.getAbsolutePath()); + + ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegenConfig); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + boolean configFileGenerated = false; + for (File file : files) { + if (file.getName().equals("configuration.cr")) { + configFileGenerated = true; + String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + // Bug: the previous template emitted `Hash{\n }` for empty + // auth methods, which Crystal rejects with: + // Error: for empty hashes use '{} of KeyType => ValueType' + Assert.assertFalse(content.contains("Hash{\n }"), + "configuration.cr must not contain an empty `Hash{}` literal"); + Assert.assertFalse(content.contains("Hash{}"), + "configuration.cr must not contain an empty `Hash{}` literal"); + // Fix: emit a typed empty hash literal that compiles in Crystal. + assertTrue(content.contains("{} of String => Hash(Symbol, String)"), + "configuration.cr must emit a typed empty hash literal in auth_settings"); + } + } + if (!configFileGenerated) { + fail("configuration.cr file is not generated!"); + } + } + + @Test + public void testAuthSettingsWithAuthMethodsStillEmitsHashLiteral() throws Exception { + // Ensure the empty-auth fix did not regress the populated case. + final File output = Files.createTempDirectory("test").toFile(); + output.mkdirs(); + output.deleteOnExit(); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/crystal/petstore.yaml"); + CodegenConfig codegenConfig = new CrystalClientCodegen(); + codegenConfig.setOutputDir(output.getAbsolutePath()); + + ClientOptInput clientOptInput = new ClientOptInput().openAPI(openAPI).config(codegenConfig); + + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + boolean configFileGenerated = false; + for (File file : files) { + if (file.getName().equals("configuration.cr")) { + configFileGenerated = true; + String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + assertTrue(content.contains("Hash{"), + "configuration.cr should still emit `Hash{` for populated auth methods"); + Assert.assertFalse(content.contains("{} of String => Hash(Symbol, String)"), + "configuration.cr should not emit empty hash literal when auth methods exist"); + } + } + if (!configFileGenerated) { + fail("configuration.cr file is not generated!"); + } + } + @Test public void testSanitizeModelName() throws Exception { final CrystalClientCodegen codegen = new CrystalClientCodegen();