diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java index 207eb2872e9b..a4460cd03c46 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java @@ -1560,6 +1560,9 @@ public CodegenModel fromModel(String name, Schema schema) { if (model != null) { model.name = toModelName(modelName); model.classname = model.name; + if (model.isEnum) { + model.vendorExtensions.put("isStringEnum", ModelUtils.isStringSchema(schema)); + } if (ModelUtils.hasAllOf(schema) && this.openAPI != null) { int refCount = 0; diff --git a/modules/openapi-generator/src/main/resources/cpp-httplib-server/README.mustache b/modules/openapi-generator/src/main/resources/cpp-httplib-server/README.mustache index 979519ce5c71..a7ab3632f456 100644 --- a/modules/openapi-generator/src/main/resources/cpp-httplib-server/README.mustache +++ b/modules/openapi-generator/src/main/resources/cpp-httplib-server/README.mustache @@ -97,6 +97,18 @@ The server will start on `http://0.0.0.0:8080` with: #### {{vendorExtensions.modelNamespace}}::{{vendorExtensions.modelClassName}} ```cpp +{{#isEnum}} +// Select an enum value +auto model = {{vendorExtensions.modelNamespace}}::{{vendorExtensions.modelClassName}}::{{#allowableValues}}{{#enumVars}}{{#-first}}{{name}}{{/-first}}{{/enumVars}}{{/allowableValues}}; + +// Serialize to JSON via the generated to_json free function +nlohmann::json json = model; +std::string jsonString = json.dump(); + +// Deserialize from JSON via the generated from_json free function +auto parsedModel = nlohmann::json::parse(jsonString).get<{{vendorExtensions.modelNamespace}}::{{vendorExtensions.modelClassName}}>(); +{{/isEnum}} +{{^isEnum}} // Create a model auto model = {{vendorExtensions.modelNamespace}}::{{vendorExtensions.modelClassName}}(); {{#vars}} @@ -109,6 +121,7 @@ std::string jsonString = json.dump(); // Deserialize from JSON auto parsedModel = {{vendorExtensions.modelNamespace}}::{{vendorExtensions.modelClassName}}::fromJson(nlohmann::json::parse(jsonString)); +{{/isEnum}} ``` {{/model}} {{/models}} diff --git a/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-header.mustache b/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-header.mustache index fa0176786b67..7aa82b2613ab 100644 --- a/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-header.mustache +++ b/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-header.mustache @@ -16,6 +16,37 @@ namespace {{vendorExtensions.modelNamespace}} { +{{#isEnum}} +enum class {{vendorExtensions.modelClassName}} { + {{#allowableValues}}{{#enumVars}} + {{name}}{{^-last}},{{/-last}} + {{/enumVars}}{{/allowableValues}} +}; + +inline void to_json(nlohmann::json& j, const {{vendorExtensions.modelClassName}}& value) +{ + switch (value) + { + {{#allowableValues}}{{#enumVars}} + case {{vendorExtensions.modelClassName}}::{{name}}: j = {{#vendorExtensions.isStringEnum}}"{{{value}}}"{{/vendorExtensions.isStringEnum}}{{^vendorExtensions.isStringEnum}}{{value}}{{/vendorExtensions.isStringEnum}}; break; + {{/enumVars}}{{/allowableValues}} + } +} + +inline void from_json(const nlohmann::json& j, {{vendorExtensions.modelClassName}}& value) +{ + {{#allowableValues}}{{#enumVars}} + if (j == {{#vendorExtensions.isStringEnum}}"{{{value}}}"{{/vendorExtensions.isStringEnum}}{{^vendorExtensions.isStringEnum}}{{value}}{{/vendorExtensions.isStringEnum}}) + { + value = {{vendorExtensions.modelClassName}}::{{name}}; + return; + } + {{/enumVars}}{{/allowableValues}} + throw nlohmann::json::type_error::create(302, "Invalid value for {{vendorExtensions.modelClassName}}", &j); +} + +{{/isEnum}} +{{^isEnum}} {{#vendorExtensions.isOneOfSchema}} // oneOf schema - type alias for std::variant using {{vendorExtensions.modelClassName}} = std::variant< @@ -252,5 +283,6 @@ private: } // namespace {{vendorExtensions.modelNamespace}} +{{/isEnum}} {{/model}} {{/models}} diff --git a/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-source.mustache b/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-source.mustache index 414becd4a421..eabdc902a7d8 100644 --- a/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-source.mustache +++ b/modules/openapi-generator/src/main/resources/cpp-httplib-server/model-source.mustache @@ -3,6 +3,11 @@ {{#models}} {{#model}} +{{#isEnum}} +#include "{{vendorExtensions.modelClassName}}.h" + +{{/isEnum}} +{{^isEnum}} {{^vendorExtensions.isOneOfSchema}} {{^vendorExtensions.isAnyOfSchema}} #include "{{vendorExtensions.modelClassName}}.h" @@ -79,5 +84,6 @@ std::string {{classname}}::{{enumName}}ToString({{classname}}::{{enumName}} valu {{^vendorExtensions.isOneOfSchema}}{{^vendorExtensions.isAnyOfSchema}} } // namespace {{vendorExtensions.modelNamespace}} {{/vendorExtensions.isAnyOfSchema}}{{/vendorExtensions.isOneOfSchema}} +{{/isEnum}} {{/model}} {{/models}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/cpphttplibserver/CppHttplibServerCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/cpphttplibserver/CppHttplibServerCodegenModelTest.java index 0044e8b167a9..51d929804a1e 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/cpphttplibserver/CppHttplibServerCodegenModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/cpphttplibserver/CppHttplibServerCodegenModelTest.java @@ -35,13 +35,17 @@ public class CppHttplibServerCodegenModelTest { * Wraps a single model the way {@link org.openapitools.codegen.DefaultGenerator} * does before calling {@code postProcessAllModels}, so tests can exercise the full * enum vendor-extension pipeline (identifier + original-value derivation), not just - * the intermediate state produced by {@code fromModel}. + * the intermediate state produced by {@code fromModel}. This mirrors + * {@code DefaultGenerator}'s {@code processModels}, which runs {@code postProcessModels} + * (and, for C++, {@code postProcessModelsEnum} with it) before {@code postProcessAllModels} + * is ever invoked. */ - private Map wrapForPostProcessAllModels(String name, CodegenModel model) { + private Map wrapForPostProcessAllModels(CppHttplibServerCodegen codegen, String name, CodegenModel model) { final ModelMap modelMap = new ModelMap(); modelMap.setModel(model); final ModelsMap modelsMap = new ModelsMap(); modelsMap.setModels(Collections.singletonList(modelMap)); + codegen.postProcessModels(modelsMap); final HashMap allModels = new HashMap<>(); allModels.put(name, modelsMap); return allModels; @@ -173,7 +177,7 @@ public void arrayOfEnumsDeclaresValidUpperCaseIdentifiersTest() { final CodegenModel model = codegen.fromModel("ModelWithColorArray", schema); final CodegenModel processedModel = codegen.postProcessAllModels( - wrapForPostProcessAllModels("ModelWithColorArray", model)) + wrapForPostProcessAllModels(codegen, "ModelWithColorArray", model)) .get("ModelWithColorArray").getModels().get(0).getModel(); CodegenProperty arrayProp = processedModel.vars.get(0); @@ -221,7 +225,7 @@ public void numericEnumPropertyTest() { // postProcessAllModels, since that's the single place both the identifier and the // original spec value are derived together (see enumSerializationUsesOriginalSpecValueTest). final CodegenModel processedModel = codegen.postProcessAllModels( - wrapForPostProcessAllModels("UserStatusModel", model)) + wrapForPostProcessAllModels(codegen, "UserStatusModel", model)) .get("UserStatusModel").getModels().get(0).getModel(); CodegenProperty statusProp = processedModel.vars.get(0); Assert.assertTrue((boolean) statusProp.vendorExtensions.getOrDefault("isEnum", false)); @@ -244,7 +248,7 @@ public void enumSerializationUsesOriginalSpecValueTest() { final CodegenModel model = codegen.fromModel("Pet", schema); final CodegenModel processedModel = codegen.postProcessAllModels( - wrapForPostProcessAllModels("Pet", model)) + wrapForPostProcessAllModels(codegen, "Pet", model)) .get("Pet").getModels().get(0).getModel(); CodegenProperty statusProp = processedModel.vars.get(0); @@ -274,15 +278,20 @@ public void enumModelTest() { final CodegenModel model = codegen.fromModel("Status", enumSchema); - // Note: The C++ httplib server generator may not process enum-only models - // in the same way as regular object models. The model might be null or empty. - if (model != null) { - Assert.assertEquals(model.name, "Status"); - // Check if it's marked as an enum in vendor extensions - if (model.vendorExtensions.containsKey("x-is-enum")) { - Assert.assertEquals(model.vendorExtensions.get("x-is-enum"), true); - } - } + Assert.assertNotNull(model); + Assert.assertEquals(model.name, "Status"); + Assert.assertTrue(model.isEnum, "top-level enum schemas must remain enum models"); + Assert.assertEquals(model.vendorExtensions.get("isStringEnum"), true, + "string-backed top-level enums must serialize their values as JSON strings"); + Assert.assertNotNull(model.allowableValues); + Assert.assertEquals(model.allowableValues.get("values"), + java.util.Arrays.asList("ACTIVE", "INACTIVE", "PENDING")); + + final CodegenModel processedModel = codegen.postProcessAllModels( + wrapForPostProcessAllModels(codegen, "Status", model)) + .get("Status").getModels().get(0).getModel(); + Assert.assertNotNull(processedModel.vendorExtensions.get("modelClassName")); + Assert.assertEquals(((List) processedModel.allowableValues.get("enumVars")).size(), 3); } @Test(description = "convert model with nullable property") diff --git a/modules/openapi-generator/src/test/resources/3_0/cpp-httplib-server/feature-test.json b/modules/openapi-generator/src/test/resources/3_0/cpp-httplib-server/feature-test.json index 2ffb304ceb53..7f61b18fe837 100644 --- a/modules/openapi-generator/src/test/resources/3_0/cpp-httplib-server/feature-test.json +++ b/modules/openapi-generator/src/test/resources/3_0/cpp-httplib-server/feature-test.json @@ -1006,6 +1006,10 @@ } } }, + "TopLevelStatus": { + "type": "string", + "enum": ["active", "inactive", "pending"] + }, "SimpleObject": { "type": "object", "required": ["id", "name"], diff --git a/samples/server/petstore/cpp-httplib-server/feature-test/.openapi-generator/FILES b/samples/server/petstore/cpp-httplib-server/feature-test/.openapi-generator/FILES index 05151c84f82a..01f3994a50bd 100644 --- a/samples/server/petstore/cpp-httplib-server/feature-test/.openapi-generator/FILES +++ b/samples/server/petstore/cpp-httplib-server/feature-test/.openapi-generator/FILES @@ -68,3 +68,5 @@ models/TestQueryParameters200Response.cpp models/TestQueryParameters200Response.h models/TestQueryParametersDeepObjectParameter.cpp models/TestQueryParametersDeepObjectParameter.h +models/TopLevelStatus.cpp +models/TopLevelStatus.h diff --git a/samples/server/petstore/cpp-httplib-server/feature-test/README.md b/samples/server/petstore/cpp-httplib-server/feature-test/README.md index 8889a4d4cb6f..82b04b186bbf 100644 --- a/samples/server/petstore/cpp-httplib-server/feature-test/README.md +++ b/samples/server/petstore/cpp-httplib-server/feature-test/README.md @@ -520,6 +520,19 @@ std::string jsonString = json.dump(); // Deserialize from JSON auto parsedModel = models::TestQueryParametersDeepObjectParameter::fromJson(nlohmann::json::parse(jsonString)); ``` +#### models::TopLevelStatus + +```cpp +// Select an enum value +auto model = models::TopLevelStatus::ACTIVE; + +// Serialize to JSON via the generated to_json free function +nlohmann::json json = model; +std::string jsonString = json.dump(); + +// Deserialize from JSON via the generated from_json free function +auto parsedModel = nlohmann::json::parse(jsonString).get(); +``` ## Implementing API Handlers diff --git a/samples/server/petstore/cpp-httplib-server/feature-test/models/TopLevelStatus.cpp b/samples/server/petstore/cpp-httplib-server/feature-test/models/TopLevelStatus.cpp new file mode 100644 index 000000000000..96ebc0c7a965 --- /dev/null +++ b/samples/server/petstore/cpp-httplib-server/feature-test/models/TopLevelStatus.cpp @@ -0,0 +1,8 @@ + +/** +* This file is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +#include "TopLevelStatus.h" + diff --git a/samples/server/petstore/cpp-httplib-server/feature-test/models/TopLevelStatus.h b/samples/server/petstore/cpp-httplib-server/feature-test/models/TopLevelStatus.h new file mode 100644 index 000000000000..c5d9807210a0 --- /dev/null +++ b/samples/server/petstore/cpp-httplib-server/feature-test/models/TopLevelStatus.h @@ -0,0 +1,62 @@ +/** +* This file is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ + +#pragma once +// System headers +#include + + + +namespace models { + +enum class TopLevelStatus { + + ACTIVE, + + INACTIVE, + + PENDING + +}; + +inline void to_json(nlohmann::json& j, const TopLevelStatus& value) +{ + switch (value) + { + + case TopLevelStatus::ACTIVE: j = "active"; break; + + case TopLevelStatus::INACTIVE: j = "inactive"; break; + + case TopLevelStatus::PENDING: j = "pending"; break; + + } +} + +inline void from_json(const nlohmann::json& j, TopLevelStatus& value) +{ + + if (j == "active") + { + value = TopLevelStatus::ACTIVE; + return; + } + + if (j == "inactive") + { + value = TopLevelStatus::INACTIVE; + return; + } + + if (j == "pending") + { + value = TopLevelStatus::PENDING; + return; + } + + throw nlohmann::json::type_error::create(302, "Invalid value for TopLevelStatus", &j); +} +