From c84768b639f1b2f95bf95e31207cb32a9e98f70b Mon Sep 17 00:00:00 2001 From: winrid Date: Tue, 18 Nov 2025 21:46:53 -0800 Subject: [PATCH 1/6] Nim Generator Fixes --- .../codegen/languages/NimClientCodegen.java | 127 +++++++++++++++- .../main/resources/nim-client/api.mustache | 12 +- .../main/resources/nim-client/model.mustache | 23 ++- .../nim-client/model_any_type.mustache | 6 + .../nim-client/model_object.mustache | 7 + .../codegen/nim/NimClientCodegenTest.java | 140 ++++++++++++++++++ .../src/test/resources/3_0/nim/petstore.yaml | 87 +++++++++++ .../petstore/nim/.openapi-generator/FILES | 9 ++ samples/client/petstore/nim/README.md | 2 + samples/client/petstore/nim/petstore.nim | 14 ++ samples/client/petstore/nim/petstore.out | Bin 0 -> 242888 bytes .../petstore/nim/petstore/apis/api_pet.nim | 18 +++ .../petstore/nim/petstore/apis/api_store.nim | 1 + .../petstore/nim/petstore/apis/api_user.nim | 6 + .../nim/petstore/models/model_any_type.nim | 14 ++ .../petstore/models/model_api_response.nim | 1 + .../nim/petstore/models/model_category.nim | 1 + .../models/model_get_pet_stats200response.nim | 18 +++ .../nim/petstore/models/model_object.nim | 15 ++ .../nim/petstore/models/model_order.nim | 17 +-- .../nim/petstore/models/model_pet.nim | 17 +-- .../models/model_pet_config_any_of1.nim | 17 +++ .../petstore/models/model_pet_metadata.nim | 17 +++ .../petstore/models/model_pet_positions.nim | 18 +++ .../petstore/models/model_pet_priority.nim | 30 ++++ ...ing_or_null_after_string_or_null_value.nim | 18 +++ .../nim/petstore/models/model_tag.nim | 1 + .../models/model_unfavorite_pet_request.nim | 17 +++ .../nim/petstore/models/model_user.nim | 1 + 29 files changed, 619 insertions(+), 35 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/nim-client/model_any_type.mustache create mode 100644 modules/openapi-generator/src/main/resources/nim-client/model_object.mustache create mode 100755 samples/client/petstore/nim/petstore.out create mode 100644 samples/client/petstore/nim/petstore/models/model_any_type.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_object.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_metadata.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_positions.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_priority.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java index 25501b96122f..a7117d9336d9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java @@ -34,6 +34,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -167,11 +168,63 @@ public NimClientCodegen() { typeMapping.put("DateTime", "string"); typeMapping.put("password", "string"); typeMapping.put("file", "string"); + typeMapping.put("object", "JsonNode"); + typeMapping.put("AnyType", "JsonNode"); } @Override public ModelsMap postProcessModels(ModelsMap objs) { - return postProcessModelsEnum(objs); + objs = postProcessModelsEnum(objs); + + // Mark top-level string enums for proper enum generation in template + for (ModelMap mo : objs.getModels()) { + CodegenModel cm = mo.getModel(); + if (cm.isEnum && cm.allowableValues != null && cm.allowableValues.containsKey("enumVars")) { + cm.vendorExtensions.put("x-is-top-level-enum", true); + } + + // Fix dataType fields that contain underscored type names + // This handles cases like Table[string, Record_string__foo__value] + for (CodegenProperty var : cm.vars) { + if (var.dataType != null && var.dataType.contains("Record_")) { + var.dataType = fixRecordTypeReferences(var.dataType); + } + if (var.datatypeWithEnum != null && var.datatypeWithEnum.contains("Record_")) { + var.datatypeWithEnum = fixRecordTypeReferences(var.datatypeWithEnum); + } + } + } + + return objs; + } + + /** + * Fix underscored Record type references in dataType strings. + * Converts Record_string__foo___value to RecordStringFooValue. + */ + private String fixRecordTypeReferences(String typeString) { + if (typeString == null || !typeString.contains("Record_")) { + return typeString; + } + + // Pattern to match Record_string_... type names with underscores + // These are embedded in strings like: Table[string, Record_string__foo__value] + String result = typeString; + + // Match Record_ followed by any characters until end or comma/bracket + Pattern pattern = Pattern.compile("Record_[a-z_]+"); + java.util.regex.Matcher matcher = pattern.matcher(result); + + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String matched = matcher.group(); + // Camelize the matched Record type name + String camelized = camelize(matched); + matcher.appendReplacement(sb, camelized); + } + matcher.appendTail(sb); + + return sb.toString(); } @Override @@ -192,6 +245,8 @@ public void processOpts() { apiPackage = File.separator + packageName + File.separator + "apis"; modelPackage = File.separator + packageName + File.separator + "models"; supportingFiles.add(new SupportingFile("lib.mustache", "", packageName + ".nim")); + supportingFiles.add(new SupportingFile("model_any_type.mustache", packageName + File.separator + "models", "model_any_type.nim")); + supportingFiles.add(new SupportingFile("model_object.mustache", packageName + File.separator + "models", "model_object.nim")); } @Override @@ -215,11 +270,13 @@ public String escapeUnsafeCharacters(String input) { @Override public String toModelImport(String name) { + name = normalizeSchemaName(name); name = name.replaceAll("-", "_"); + if (importMapping.containsKey(name)) { - return "model_" + StringUtils.underscore(importMapping.get(name)); + return sanitizeNimIdentifier("model_" + StringUtils.underscore(importMapping.get(name))); } else { - return "model_" + StringUtils.underscore(name); + return sanitizeNimIdentifier("model_" + StringUtils.underscore(name)); } } @@ -227,22 +284,54 @@ public String toModelImport(String name) { public String toApiImport(String name) { name = name.replaceAll("-", "_"); if (importMapping.containsKey(name)) { - return "api_" + StringUtils.underscore(importMapping.get(name)); + return sanitizeNimIdentifier("api_" + StringUtils.underscore(importMapping.get(name))); } else { - return "api_" + StringUtils.underscore(name); + return sanitizeNimIdentifier("api_" + StringUtils.underscore(name)); } } + /** + * Normalize schema names to ensure consistency across filename, import, and type name generation. + * This is called early in the pipeline so downstream methods work with consistent names. + */ + private String normalizeSchemaName(String name) { + if (name == null) { + return null; + } + // Remove underscores around and before digits (HTTP status codes, version numbers, etc.) + // e.g., "GetComments_200_response" -> "GetComments200response" + // e.g., "Config_anyOf_1" -> "ConfiganyOf1" + // This ensures consistent handling whether the name comes with or without underscores + name = name.replaceAll("_(\\d+)_", "$1"); // Underscores on both sides + name = name.replaceAll("_(\\d+)$", "$1"); // Trailing underscore before digits + return name; + } + + @Override + public CodegenModel fromModel(String name, Schema schema) { + // Normalize the schema name before any processing + name = normalizeSchemaName(name); + return super.fromModel(name, schema); + } + + @Override + public String toModelName(String name) { + // Name should be normalized by fromModel, but normalize again for safety + name = normalizeSchemaName(name); + return camelize(sanitizeName(name)); + } + @Override public String toModelFilename(String name) { + name = normalizeSchemaName(name); name = name.replaceAll("-", "_"); - return "model_" + StringUtils.underscore(name); + return sanitizeNimIdentifier("model_" + StringUtils.underscore(name)); } @Override public String toApiFilename(String name) { name = name.replaceAll("-", "_"); - return "api_" + StringUtils.underscore(name); + return sanitizeNimIdentifier("api_" + StringUtils.underscore(name)); } @Override @@ -262,6 +351,12 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List operations = objectMap.getOperation(); for (CodegenOperation operation : operations) { operation.httpMethod = operation.httpMethod.toLowerCase(Locale.ROOT); + + // Set custom flag for DELETE operations with body to use different template logic + // Nim's httpClient.delete() doesn't support a body parameter + if ("delete".equals(operation.httpMethod) && operation.getHasBodyParam()) { + operation.vendorExtensions.put("x-nim-delete-with-body", true); + } } return objs; @@ -360,6 +455,24 @@ private boolean isValidIdentifier(String identifier) { return identifier.matches("^(?:[A-Z]|[a-z]|[\\x80-\\xff])(_?(?:[A-Z]|[a-z]|[\\x80-\\xff]|[0-9]))*$"); } + /** + * Sanitize a Nim identifier by removing trailing underscores and collapsing multiple underscores. + * Nim does not allow identifiers to end with underscores. + * + * @param name the identifier to sanitize + * @return the sanitized identifier + */ + private String sanitizeNimIdentifier(String name) { + if (name == null || name.isEmpty()) { + return name; + } + // Remove trailing underscores (Nim identifiers cannot end with underscore) + name = name.replaceAll("_+$", ""); + // Collapse multiple consecutive underscores to single underscore + name = name.replaceAll("_+", "_"); + return name; + } + @Override public String toEnumVarName(String name, String datatype) { name = name.replace(" ", "_"); diff --git a/modules/openapi-generator/src/main/resources/nim-client/api.mustache b/modules/openapi-generator/src/main/resources/nim-client/api.mustache index 77d403284dc8..462fdcec5090 100644 --- a/modules/openapi-generator/src/main/resources/nim-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/nim-client/api.mustache @@ -47,9 +47,15 @@ proc {{{operationId}}}*(httpClient: HttpClient{{#allParams}}, {{{paramName}}}: { {{#formParams}} "{{{baseName}}}": ${{{paramName}}}{{#isArray}}.join(","){{/isArray}}, # {{{description}}} {{/formParams}} }){{/isMultipart}}{{/hasFormParams}}{{#returnType}} - +{{#vendorExtensions.x-nim-delete-with-body}} + let response = httpClient.request(basepath & {{^pathParams}}"{{{path}}}"{{/pathParams}}{{#hasPathParams}}fmt"{{{path}}}"{{/hasPathParams}}{{#hasQueryParams}} & "?" & url_encoded_query_params{{/hasQueryParams}}, httpMethod = HttpDelete{{#bodyParams}}, body = $(%{{{paramName}}}){{/bodyParams}}) +{{/vendorExtensions.x-nim-delete-with-body}}{{^vendorExtensions.x-nim-delete-with-body}} let response = httpClient.{{{httpMethod}}}(basepath & {{^pathParams}}"{{{path}}}"{{/pathParams}}{{#hasPathParams}}fmt"{{{path}}}"{{/hasPathParams}}{{#hasQueryParams}} & "?" & url_encoded_query_params{{/hasQueryParams}}{{#hasBodyParam}}{{#bodyParams}}, $(%{{{paramName}}}){{/bodyParams}}{{/hasBodyParam}}{{#hasFormParams}}, {{^isMultipart}}$form_data{{/isMultipart}}{{#isMultipart}}multipart=multipart_data{{/isMultipart}}{{/hasFormParams}}) - constructResult[{{{returnType}}}](response){{/returnType}}{{^returnType}} - httpClient.{{{httpMethod}}}(basepath & {{^pathParams}}"{{{path}}}"{{/pathParams}}{{#hasPathParams}}fmt"{{{path}}}"{{/hasPathParams}}{{#hasQueryParams}} & "?" & url_encoded_query_params{{/hasQueryParams}}{{#hasBodyParam}}{{#bodyParams}}, $(%{{{paramName}}}){{/bodyParams}}{{/hasBodyParam}}{{#hasFormParams}}, {{^isMultipart}}$form_data{{/isMultipart}}{{#isMultipart}}multipart=multipart_data{{/isMultipart}}{{/hasFormParams}}){{/returnType}} +{{/vendorExtensions.x-nim-delete-with-body}} + constructResult[{{{returnType}}}](response){{/returnType}}{{^returnType}}{{#vendorExtensions.x-nim-delete-with-body}} + httpClient.request(basepath & {{^pathParams}}"{{{path}}}"{{/pathParams}}{{#hasPathParams}}fmt"{{{path}}}"{{/hasPathParams}}{{#hasQueryParams}} & "?" & url_encoded_query_params{{/hasQueryParams}}, httpMethod = HttpDelete{{#bodyParams}}, body = $(%{{{paramName}}}){{/bodyParams}}) +{{/vendorExtensions.x-nim-delete-with-body}}{{^vendorExtensions.x-nim-delete-with-body}} + httpClient.{{{httpMethod}}}(basepath & {{^pathParams}}"{{{path}}}"{{/pathParams}}{{#hasPathParams}}fmt"{{{path}}}"{{/hasPathParams}}{{#hasQueryParams}} & "?" & url_encoded_query_params{{/hasQueryParams}}{{#hasBodyParam}}{{#bodyParams}}, $(%{{{paramName}}}){{/bodyParams}}{{/hasBodyParam}}{{#hasFormParams}}, {{^isMultipart}}$form_data{{/isMultipart}}{{#isMultipart}}multipart=multipart_data{{/isMultipart}}{{/hasFormParams}}) +{{/vendorExtensions.x-nim-delete-with-body}}{{/returnType}} {{/operation}}{{/operations}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/nim-client/model.mustache b/modules/openapi-generator/src/main/resources/nim-client/model.mustache index 3fd6f13594dd..51e3c73910b7 100644 --- a/modules/openapi-generator/src/main/resources/nim-client/model.mustache +++ b/modules/openapi-generator/src/main/resources/nim-client/model.mustache @@ -3,7 +3,18 @@ import json import tables {{#imports}}import {{import}} -{{/imports}}{{#models}}{{#model}}{{#vars}}{{#isEnum}} +{{/imports}}{{#models}}{{#model}}{{#isEnum}} +type {{{classname}}}* {.pure.} = enum{{#allowableValues}}{{#enumVars}} + {{{name}}}{{/enumVars}}{{/allowableValues}} + +func `%`*(v: {{{classname}}}): JsonNode = + result = case v:{{#allowableValues}}{{#enumVars}} + of {{{classname}}}.{{{name}}}: %{{{value}}}{{/enumVars}}{{/allowableValues}} + +func `$`*(v: {{{classname}}}): string = + result = case v:{{#allowableValues}}{{#enumVars}} + of {{{classname}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}} +{{/isEnum}}{{^isEnum}}{{#vars}}{{#isEnum}} type {{{enumName}}}* {.pure.} = enum{{#allowableValues}}{{#enumVars}} {{{name}}}{{/enumVars}}{{/allowableValues}} {{/isEnum}}{{/vars}} @@ -12,12 +23,10 @@ type {{{classname}}}* = object {{{name}}}*: {{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#description}} ## {{{.}}}{{/description}}{{/vars}} {{#vars}}{{#isEnum}} func `%`*(v: {{{enumName}}}): JsonNode = - let str = case v:{{#allowableValues}}{{#enumVars}} - of {{{enumName}}}.{{{name}}}: {{{value}}}{{/enumVars}}{{/allowableValues}} - - JsonNode(kind: JString, str: str) + result = case v:{{#allowableValues}}{{#enumVars}} + of {{{enumName}}}.{{{name}}}: %{{{value}}}{{/enumVars}}{{/allowableValues}} func `$`*(v: {{{enumName}}}): string = result = case v:{{#allowableValues}}{{#enumVars}} - of {{{enumName}}}.{{{name}}}: {{{value}}}{{/enumVars}}{{/allowableValues}} -{{/isEnum}}{{/vars}}{{/model}}{{/models}} \ No newline at end of file + of {{{enumName}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}} +{{/isEnum}}{{/vars}}{{/isEnum}}{{/model}}{{/models}} diff --git a/modules/openapi-generator/src/main/resources/nim-client/model_any_type.mustache b/modules/openapi-generator/src/main/resources/nim-client/model_any_type.mustache new file mode 100644 index 000000000000..f2a066d16fb2 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/nim-client/model_any_type.mustache @@ -0,0 +1,6 @@ +{{>header}} +import json + +# AnyType represents any JSON value +# This is used for fields that can contain arbitrary JSON data +type AnyType* = JsonNode diff --git a/modules/openapi-generator/src/main/resources/nim-client/model_object.mustache b/modules/openapi-generator/src/main/resources/nim-client/model_object.mustache new file mode 100644 index 000000000000..690d9160277f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/nim-client/model_object.mustache @@ -0,0 +1,7 @@ +{{>header}} +import json +import tables + +# Object represents an arbitrary JSON object +# Using JsonNode instead of the 'object' keyword to avoid Nim keyword conflicts +type Object* = JsonNode diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java index b817ad80a8d7..1886ad0423e3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java @@ -1,5 +1,6 @@ package org.openapitools.codegen.nim; +import io.swagger.v3.oas.models.media.ObjectSchema; import org.openapitools.codegen.CodegenConstants; import org.openapitools.codegen.languages.NimClientCodegen; import org.testng.Assert; @@ -35,4 +36,143 @@ public void testAdditionalPropertiesPutForConfigValues() throws Exception { Assert.assertEquals(codegen.additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP), Boolean.FALSE); Assert.assertEquals(codegen.isHideGenerationTimestamp(), false); } + + @Test + public void testUnderscoresEdgeCases() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Test model filename with trailing underscores + String result = codegen.toModelFilename("Record_string__before_string_or_null__after_string_or_null___value"); + Assert.assertFalse(result.endsWith("_"), "Model filename should not end with underscore: " + result); + + // Test model filename with multiple consecutive underscores + result = codegen.toModelFilename("Record_string_string_or_number__value"); + Assert.assertFalse(result.endsWith("_"), "Model filename should not end with underscore: " + result); + + // Verify no consecutive underscores remain (except the required prefix) + Assert.assertFalse(result.contains("__"), "Model filename should not contain consecutive underscores: " + result); + + // Test model import with trailing underscores + result = codegen.toModelImport("Record_string__before_string_or_null__after_string_or_null___value"); + Assert.assertFalse(result.endsWith("_"), "Model import should not end with underscore: " + result); + + // Test model import with multiple consecutive underscores + result = codegen.toModelImport("Record_string_string_or_number__value"); + Assert.assertFalse(result.endsWith("_"), "Model import should not end with underscore: " + result); + + // Test API filename with trailing underscores + result = codegen.toApiFilename("SomeApi_"); + Assert.assertFalse(result.endsWith("_"), "API filename should not end with underscore: " + result); + + // Test API import with trailing underscores + result = codegen.toApiImport("SomeApi_"); + Assert.assertFalse(result.endsWith("_"), "API import should not end with underscore: " + result); + } + + @Test + public void testSanitizationPreservesNormalNames() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Verify that normal names without trailing underscores are not changed + String result = codegen.toModelFilename("UserData"); + Assert.assertTrue(result.startsWith("model_"), "Model filename should start with model_"); + Assert.assertFalse(result.endsWith("_"), "Model filename should not end with underscore"); + + result = codegen.toApiFilename("DefaultApi"); + Assert.assertTrue(result.startsWith("api_"), "API filename should start with api_"); + Assert.assertFalse(result.endsWith("_"), "API filename should not end with underscore"); + } + + @Test + public void testObjectTypeMapping() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Test that object type is mapped to JsonNode to avoid Nim keyword conflict + ObjectSchema objectSchema = new ObjectSchema(); + String result = codegen.getTypeDeclaration(objectSchema); + + // object types without properties should map to JsonNode + Assert.assertEquals(result, "JsonNode", + "Free-form object type should map to JsonNode to avoid Nim 'object' keyword conflict"); + } + + @Test + public void testTypeNameConsistency() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Test that response type names don't have underscores between number and text + String result = codegen.toModelName("GetComments_200_response"); + Assert.assertFalse(result.contains("_200_"), "Type name should not contain _200_: " + result); + Assert.assertTrue(result.contains("200"), "Type name should contain 200: " + result); + + // The filename should also be consistent + String filename = codegen.toModelFilename("GetComments_200_response"); + String importName = codegen.toModelImport("GetComments_200_response"); + + // Extract the type name from the filename and import + String filenameTypePart = filename.replace("model_", ""); + String importTypePart = importName.replace("model_", ""); + + Assert.assertEquals(filenameTypePart, importTypePart, + "Filename and import should reference the same type"); + } + + @Test + public void testImportConsistencyAfterProcessing() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Simulate what happens during model processing: + // 1. Model is created with original schema name + String schemaName = "AddDomainConfig_200_response_anyOf"; + String typeName = codegen.toModelName(schemaName); // Camelizes to AddDomainConfig200ResponseAnyOf + String filename = codegen.toModelFilename(schemaName); // Creates model_add_domain_config_200_response_any_of + + // 2. When another model imports this type, it uses the camelized type name + String importFromTypeName = codegen.toModelImport(typeName); + + // The import should match the filename (or at least be loadable) + Assert.assertEquals(filename, importFromTypeName, + "Import generated from type name should match filename generated from schema name"); + } + + @Test + public void testRecordTypeReferenceFix() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Test that Record type references with underscores are fixed in dataType strings + String dataTypeWithRecord = "Table[string, Record_string__before_string_or_null__after_string_or_null___value]"; + + // Use reflection to access private method for testing + java.lang.reflect.Method method = NimClientCodegen.class.getDeclaredMethod("fixRecordTypeReferences", String.class); + method.setAccessible(true); + String result = (String) method.invoke(codegen, dataTypeWithRecord); + + Assert.assertFalse(result.contains("Record_"), + "Fixed type should not contain Record_ with underscores: " + result); + Assert.assertTrue(result.contains("RecordString"), + "Fixed type should use camelCase: " + result); + Assert.assertFalse(result.contains("__"), + "Fixed type should not contain double underscores: " + result); + } + + @Test + public void testNormalizeSchemaName() throws Exception { + final NimClientCodegen codegen = new NimClientCodegen(); + + // Test that schema names with _200_ are normalized + String result = codegen.toModelName("GetComments_200_response"); + Assert.assertEquals(result, "GetComments200response", + "Should normalize _200_ to 200: " + result); + + // Test that schema names with trailing _1 are normalized + result = codegen.toModelName("Config_anyOf_1"); + Assert.assertEquals(result, "ConfigAnyOf1", + "Should normalize _1 to 1: " + result); + + // Verify consistency between filename and import + String filename = codegen.toModelFilename("GetComments_200_response"); + String importPath = codegen.toModelImport("GetComments200response"); + Assert.assertEquals(filename, importPath, + "Filename and import should match after normalization"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml index a8f9809a1249..7c6e79b05242 100644 --- a/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml @@ -286,6 +286,54 @@ paths: description: file to upload type: string format: binary + '/pet/{petId}/favorite': + delete: + tags: + - pet + summary: Remove pet from favorites (tests DELETE with body) + description: 'Tests that DELETE operations with request bodies generate correct Nim code' + operationId: unfavoritePet + parameters: + - name: petId + in: path + description: ID of pet to unfavorite + required: true + schema: + type: integer + format: int64 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + reason: + type: string + description: Reason for unfavoriting + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/GetPetStats_200_response' + security: + - petstore_auth: + - 'write:pets' + /pet/stats: + get: + tags: + - pet + summary: Get pet statistics (tests _200_ response normalization) + operationId: getPetStats + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/GetPetStats_200_response' /store/inventory: get: tags: @@ -739,3 +787,42 @@ components: type: string message: type: string + # Test _200_ normalization in response schema names + GetPetStats_200_response: + type: object + properties: + totalPets: + type: integer + status: + type: string + # Test trailing underscores in schema names + PetMetadata_: + type: object + properties: + metadata: + type: string + # Test Record_string__ type patterns (complex generic types) + Record_string__before_string_or_null__after_string_or_null___value: + type: object + properties: + before: + type: string + after: + type: string + PetPositions: + type: object + properties: + positions: + type: object + additionalProperties: + $ref: '#/components/schemas/Record_string__before_string_or_null__after_string_or_null___value' + # Test numeric enums + PetPriority: + type: integer + enum: [0, 1, 2] + # Test anyOf with _1 suffix + PetConfig_anyOf_1: + type: object + properties: + version: + type: integer diff --git a/samples/client/petstore/nim/.openapi-generator/FILES b/samples/client/petstore/nim/.openapi-generator/FILES index ff650e2b75ac..1cff51f250c9 100644 --- a/samples/client/petstore/nim/.openapi-generator/FILES +++ b/samples/client/petstore/nim/.openapi-generator/FILES @@ -4,10 +4,19 @@ petstore.nim petstore/apis/api_pet.nim petstore/apis/api_store.nim petstore/apis/api_user.nim +petstore/models/model_any_type.nim petstore/models/model_api_response.nim petstore/models/model_category.nim +petstore/models/model_get_pet_stats200response.nim +petstore/models/model_object.nim petstore/models/model_order.nim petstore/models/model_pet.nim +petstore/models/model_pet_config_any_of1.nim +petstore/models/model_pet_metadata.nim +petstore/models/model_pet_positions.nim +petstore/models/model_pet_priority.nim +petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim petstore/models/model_tag.nim +petstore/models/model_unfavorite_pet_request.nim petstore/models/model_user.nim sample_client.nim diff --git a/samples/client/petstore/nim/README.md b/samples/client/petstore/nim/README.md index 6e76be972b32..9f48ef110058 100644 --- a/samples/client/petstore/nim/README.md +++ b/samples/client/petstore/nim/README.md @@ -30,6 +30,8 @@ api_pet | deletePet | **DELETE** /pet/{petId} | Deletes a pet api_pet | findPetsByStatus | **GET** /pet/findByStatus | Finds Pets by status api_pet | findPetsByTags | **GET** /pet/findByTags | Finds Pets by tags api_pet | getPetById | **GET** /pet/{petId} | Find pet by ID +api_pet | getPetStats | **GET** /pet/stats | Get pet statistics (tests _200_ response normalization) +api_pet | unfavoritePet | **DELETE** /pet/{petId}/favorite | Remove pet from favorites (tests DELETE with body) api_pet | updatePet | **PUT** /pet | Update an existing pet api_pet | updatePetWithForm | **POST** /pet/{petId} | Updates a pet in the store with form data api_pet | uploadFile | **POST** /pet/{petId}/uploadImage | uploads an image diff --git a/samples/client/petstore/nim/petstore.nim b/samples/client/petstore/nim/petstore.nim index 21f854f172b6..00e82002ae2b 100644 --- a/samples/client/petstore/nim/petstore.nim +++ b/samples/client/petstore/nim/petstore.nim @@ -10,16 +10,30 @@ # Models import petstore/models/model_api_response import petstore/models/model_category +import petstore/models/model_get_pet_stats200response import petstore/models/model_order import petstore/models/model_pet +import petstore/models/model_pet_config_any_of1 +import petstore/models/model_pet_metadata +import petstore/models/model_pet_positions +import petstore/models/model_pet_priority +import petstore/models/model_record_string_before_string_or_null_after_string_or_null_value import petstore/models/model_tag +import petstore/models/model_unfavorite_pet_request import petstore/models/model_user export model_api_response export model_category +export model_get_pet_stats200response export model_order export model_pet +export model_pet_config_any_of1 +export model_pet_metadata +export model_pet_positions +export model_pet_priority +export model_record_string_before_string_or_null_after_string_or_null_value export model_tag +export model_unfavorite_pet_request export model_user # APIs diff --git a/samples/client/petstore/nim/petstore.out b/samples/client/petstore/nim/petstore.out new file mode 100755 index 0000000000000000000000000000000000000000..dac73aad37aebfb47516b7bb12fb273d3b954327 GIT binary patch literal 242888 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%0D<$epT3{XwZYKyOf--Gxr^j}{_(^*2jgyPg~+ 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 M*+Gpy8n*`gf1)(WVE_OC literal 0 HcmV?d00001 diff --git a/samples/client/petstore/nim/petstore/apis/api_pet.nim b/samples/client/petstore/nim/petstore/apis/api_pet.nim index 8d53b553a3d3..28e2aa7ae7da 100644 --- a/samples/client/petstore/nim/petstore/apis/api_pet.nim +++ b/samples/client/petstore/nim/petstore/apis/api_pet.nim @@ -19,7 +19,9 @@ import typetraits import uri import ../models/model_api_response +import ../models/model_get_pet_stats200response import ../models/model_pet +import ../models/model_unfavorite_pet_request const basepath = "http://petstore.swagger.io/v2" @@ -53,6 +55,7 @@ proc deletePet*(httpClient: HttpClient, petId: int64, apiKey: string): Response httpClient.delete(basepath & fmt"/pet/{petId}") + proc findPetsByStatus*(httpClient: HttpClient, status: seq[Status]): (Option[seq[Pet]], Response) = ## Finds Pets by status let url_encoded_query_params = encodeQuery([ @@ -80,6 +83,20 @@ proc getPetById*(httpClient: HttpClient, petId: int64): (Option[Pet], Response) constructResult[Pet](response) +proc getPetStats*(httpClient: HttpClient): (Option[GetPetStats_200_response], Response) = + ## Get pet statistics (tests _200_ response normalization) + + let response = httpClient.get(basepath & "/pet/stats") + constructResult[GetPetStats_200_response](response) + + +proc unfavoritePet*(httpClient: HttpClient, petId: int64, unfavoritePetRequest: UnfavoritePetRequest): (Option[GetPetStats_200_response], Response) = + ## Remove pet from favorites (tests DELETE with body) + httpClient.headers["Content-Type"] = "application/json" + let response = httpClient.request(basepath & fmt"/pet/{petId}/favorite", httpMethod = HttpDelete, body = $(%unfavoritePetRequest)) + constructResult[GetPetStats_200_response](response) + + proc updatePet*(httpClient: HttpClient, pet: Pet): (Option[Pet], Response) = ## Update an existing pet httpClient.headers["Content-Type"] = "application/json" @@ -98,6 +115,7 @@ proc updatePetWithForm*(httpClient: HttpClient, petId: int64, name: string, stat httpClient.post(basepath & fmt"/pet/{petId}", $form_data) + proc uploadFile*(httpClient: HttpClient, petId: int64, additionalMetadata: string, file: string): (Option[ApiResponse], Response) = ## uploads an image httpClient.headers["Content-Type"] = "multipart/form-data" diff --git a/samples/client/petstore/nim/petstore/apis/api_store.nim b/samples/client/petstore/nim/petstore/apis/api_store.nim index a2f018b7cf3d..93a295009133 100644 --- a/samples/client/petstore/nim/petstore/apis/api_store.nim +++ b/samples/client/petstore/nim/petstore/apis/api_store.nim @@ -43,6 +43,7 @@ proc deleteOrder*(httpClient: HttpClient, orderId: string): Response = httpClient.delete(basepath & fmt"/store/order/{orderId}") + proc getInventory*(httpClient: HttpClient): (Option[Table[string, int]], Response) = ## Returns pet inventories by status diff --git a/samples/client/petstore/nim/petstore/apis/api_user.nim b/samples/client/petstore/nim/petstore/apis/api_user.nim index dbc56457d946..25384f40fc76 100644 --- a/samples/client/petstore/nim/petstore/apis/api_user.nim +++ b/samples/client/petstore/nim/petstore/apis/api_user.nim @@ -44,23 +44,27 @@ proc createUser*(httpClient: HttpClient, user: User): Response = httpClient.post(basepath & "/user", $(%user)) + proc createUsersWithArrayInput*(httpClient: HttpClient, user: seq[User]): Response = ## Creates list of users with given input array httpClient.headers["Content-Type"] = "application/json" httpClient.post(basepath & "/user/createWithArray", $(%user)) + proc createUsersWithListInput*(httpClient: HttpClient, user: seq[User]): Response = ## Creates list of users with given input array httpClient.headers["Content-Type"] = "application/json" httpClient.post(basepath & "/user/createWithList", $(%user)) + proc deleteUser*(httpClient: HttpClient, username: string): Response = ## Delete user httpClient.delete(basepath & fmt"/user/{username}") + proc getUserByName*(httpClient: HttpClient, username: string): (Option[User], Response) = ## Get user by user name @@ -84,8 +88,10 @@ proc logoutUser*(httpClient: HttpClient): Response = httpClient.get(basepath & "/user/logout") + proc updateUser*(httpClient: HttpClient, username: string, user: User): Response = ## Updated user httpClient.headers["Content-Type"] = "application/json" httpClient.put(basepath & fmt"/user/{username}", $(%user)) + diff --git a/samples/client/petstore/nim/petstore/models/model_any_type.nim b/samples/client/petstore/nim/petstore/models/model_any_type.nim new file mode 100644 index 000000000000..808715e6b6c5 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_any_type.nim @@ -0,0 +1,14 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json + +# AnyType represents any JSON value +# This is used for fields that can contain arbitrary JSON data +type AnyType* = JsonNode diff --git a/samples/client/petstore/nim/petstore/models/model_api_response.nim b/samples/client/petstore/nim/petstore/models/model_api_response.nim index 78f22e081ca0..c76ac9aeb13e 100644 --- a/samples/client/petstore/nim/petstore/models/model_api_response.nim +++ b/samples/client/petstore/nim/petstore/models/model_api_response.nim @@ -16,3 +16,4 @@ type ApiResponse* = object code*: int `type`*: string message*: string + diff --git a/samples/client/petstore/nim/petstore/models/model_category.nim b/samples/client/petstore/nim/petstore/models/model_category.nim index a1331c1ef415..66069f3f3cf3 100644 --- a/samples/client/petstore/nim/petstore/models/model_category.nim +++ b/samples/client/petstore/nim/petstore/models/model_category.nim @@ -15,3 +15,4 @@ type Category* = object ## A category for a pet id*: int64 name*: string + diff --git a/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim b/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim new file mode 100644 index 000000000000..ae7fefeeaade --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim @@ -0,0 +1,18 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + + +type GetPetStats200response* = object + ## + totalPets*: int + status*: string + diff --git a/samples/client/petstore/nim/petstore/models/model_object.nim b/samples/client/petstore/nim/petstore/models/model_object.nim new file mode 100644 index 000000000000..6c86e864d60c --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_object.nim @@ -0,0 +1,15 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + +# Object represents an arbitrary JSON object +# Using JsonNode instead of the 'object' keyword to avoid Nim keyword conflicts +type Object* = JsonNode diff --git a/samples/client/petstore/nim/petstore/models/model_order.nim b/samples/client/petstore/nim/petstore/models/model_order.nim index e2bb9e9cd7c7..c107cb85c50a 100644 --- a/samples/client/petstore/nim/petstore/models/model_order.nim +++ b/samples/client/petstore/nim/petstore/models/model_order.nim @@ -26,15 +26,14 @@ type Order* = object complete*: bool func `%`*(v: Status): JsonNode = - let str = case v: - of Status.Placed: "placed" - of Status.Approved: "approved" - of Status.Delivered: "delivered" - - JsonNode(kind: JString, str: str) + result = case v: + of Status.Placed: %"placed" + of Status.Approved: %"approved" + of Status.Delivered: %"delivered" func `$`*(v: Status): string = result = case v: - of Status.Placed: "placed" - of Status.Approved: "approved" - of Status.Delivered: "delivered" + of Status.Placed: $("placed") + of Status.Approved: $("approved") + of Status.Delivered: $("delivered") + diff --git a/samples/client/petstore/nim/petstore/models/model_pet.nim b/samples/client/petstore/nim/petstore/models/model_pet.nim index c2431a743c66..b3965f8cfda9 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet.nim @@ -28,15 +28,14 @@ type Pet* = object status*: Status ## pet status in the store func `%`*(v: Status): JsonNode = - let str = case v: - of Status.Available: "available" - of Status.Pending: "pending" - of Status.Sold: "sold" - - JsonNode(kind: JString, str: str) + result = case v: + of Status.Available: %"available" + of Status.Pending: %"pending" + of Status.Sold: %"sold" func `$`*(v: Status): string = result = case v: - of Status.Available: "available" - of Status.Pending: "pending" - of Status.Sold: "sold" + of Status.Available: $("available") + of Status.Pending: $("pending") + of Status.Sold: $("sold") + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim b/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim new file mode 100644 index 000000000000..359b6006dd77 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim @@ -0,0 +1,17 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + + +type PetConfigAnyOf1* = object + ## + version*: int + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim b/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim new file mode 100644 index 000000000000..340758c5fed4 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim @@ -0,0 +1,17 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + + +type PetMetadata* = object + ## + metadata*: string + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_positions.nim b/samples/client/petstore/nim/petstore/models/model_pet_positions.nim new file mode 100644 index 000000000000..8eef1796b94c --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_positions.nim @@ -0,0 +1,18 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + +import model_record_string_before_string_or_null_after_string_or_null_value + +type PetPositions* = object + ## + positions*: Table[string, RecordStringBeforeStringOrNullAfterStringOrNullValue] + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_priority.nim b/samples/client/petstore/nim/petstore/models/model_pet_priority.nim new file mode 100644 index 000000000000..2c1dede7bfd6 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_priority.nim @@ -0,0 +1,30 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + + +type PetPriority* {.pure.} = enum + `0` + `1` + `2` + +func `%`*(v: PetPriority): JsonNode = + result = case v: + of PetPriority.`0`: %"0" + of PetPriority.`1`: %"1" + of PetPriority.`2`: %"2" + +func `$`*(v: PetPriority): string = + result = case v: + of PetPriority.`0`: $("0") + of PetPriority.`1`: $("1") + of PetPriority.`2`: $("2") + diff --git a/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim b/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim new file mode 100644 index 000000000000..15507246061f --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim @@ -0,0 +1,18 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + + +type RecordStringBeforeStringOrNullAfterStringOrNullValue* = object + ## + before*: string + after*: string + diff --git a/samples/client/petstore/nim/petstore/models/model_tag.nim b/samples/client/petstore/nim/petstore/models/model_tag.nim index 28f518d6b6c0..7142bb740bd2 100644 --- a/samples/client/petstore/nim/petstore/models/model_tag.nim +++ b/samples/client/petstore/nim/petstore/models/model_tag.nim @@ -15,3 +15,4 @@ type Tag* = object ## A tag for a pet id*: int64 name*: string + diff --git a/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim b/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim new file mode 100644 index 000000000000..0b9a1d7f7fbb --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim @@ -0,0 +1,17 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables + + +type UnfavoritePetRequest* = object + ## + reason*: string ## Reason for unfavoriting + diff --git a/samples/client/petstore/nim/petstore/models/model_user.nim b/samples/client/petstore/nim/petstore/models/model_user.nim index e487793080e5..b33756f57c00 100644 --- a/samples/client/petstore/nim/petstore/models/model_user.nim +++ b/samples/client/petstore/nim/petstore/models/model_user.nim @@ -21,3 +21,4 @@ type User* = object password*: string phone*: string userStatus*: int ## User Status + From f8c31726c360dd591370f0501b8725122fe2691f Mon Sep 17 00:00:00 2001 From: winrid Date: Tue, 18 Nov 2025 22:06:50 -0800 Subject: [PATCH 2/6] remove test that is tested via yaml anyway --- .../codegen/nim/NimClientCodegenTest.java | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java index 1886ad0423e3..f9ed8cf0ae2d 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java @@ -135,26 +135,6 @@ public void testImportConsistencyAfterProcessing() throws Exception { "Import generated from type name should match filename generated from schema name"); } - @Test - public void testRecordTypeReferenceFix() throws Exception { - final NimClientCodegen codegen = new NimClientCodegen(); - - // Test that Record type references with underscores are fixed in dataType strings - String dataTypeWithRecord = "Table[string, Record_string__before_string_or_null__after_string_or_null___value]"; - - // Use reflection to access private method for testing - java.lang.reflect.Method method = NimClientCodegen.class.getDeclaredMethod("fixRecordTypeReferences", String.class); - method.setAccessible(true); - String result = (String) method.invoke(codegen, dataTypeWithRecord); - - Assert.assertFalse(result.contains("Record_"), - "Fixed type should not contain Record_ with underscores: " + result); - Assert.assertTrue(result.contains("RecordString"), - "Fixed type should use camelCase: " + result); - Assert.assertFalse(result.contains("__"), - "Fixed type should not contain double underscores: " + result); - } - @Test public void testNormalizeSchemaName() throws Exception { final NimClientCodegen codegen = new NimClientCodegen(); From ad612faced37bc71870129c9733975f9612fbc38 Mon Sep 17 00:00:00 2001 From: winrid Date: Thu, 20 Nov 2025 17:01:46 -0800 Subject: [PATCH 3/6] Only include required parameters or non-empty optional parameters in URLs, support oneOf/anyOf via nim object variants, optional field support --- .../codegen/languages/NimClientCodegen.java | 88 ++++++++++++++++++- .../main/resources/nim-client/api.mustache | 13 ++- .../main/resources/nim-client/model.mustache | 44 +++++++++- 3 files changed, 135 insertions(+), 10 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java index a7117d9336d9..6c96b25a26fc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java @@ -78,6 +78,10 @@ public NimClientCodegen() { .excludeSchemaSupportFeatures( SchemaSupportFeature.Polymorphism ) + .includeSchemaSupportFeatures( + SchemaSupportFeature.oneOf, + SchemaSupportFeature.anyOf + ) .excludeParameterFeatures( ParameterFeature.Cookie ) @@ -172,6 +176,7 @@ public NimClientCodegen() { typeMapping.put("AnyType", "JsonNode"); } + @Override public ModelsMap postProcessModels(ModelsMap objs) { objs = postProcessModelsEnum(objs); @@ -185,6 +190,7 @@ public ModelsMap postProcessModels(ModelsMap objs) { // Fix dataType fields that contain underscored type names // This handles cases like Table[string, Record_string__foo__value] + // Also wrap optional fields in Option[T] for (CodegenProperty var : cm.vars) { if (var.dataType != null && var.dataType.contains("Record_")) { var.dataType = fixRecordTypeReferences(var.dataType); @@ -192,6 +198,18 @@ public ModelsMap postProcessModels(ModelsMap objs) { if (var.datatypeWithEnum != null && var.datatypeWithEnum.contains("Record_")) { var.datatypeWithEnum = fixRecordTypeReferences(var.datatypeWithEnum); } + + // Wrap optional (non-required) or nullable fields in Option[T] + if ((!var.required || var.isNullable) && !var.isReadOnly) { + String baseType = var.datatypeWithEnum != null ? var.datatypeWithEnum : var.dataType; + if (baseType != null && !baseType.startsWith("Option[")) { + var.dataType = "Option[" + baseType + "]"; + if (var.datatypeWithEnum != null) { + var.datatypeWithEnum = "Option[" + var.datatypeWithEnum + "]"; + } + var.vendorExtensions.put("x-is-optional", true); + } + } } } @@ -311,7 +329,75 @@ private String normalizeSchemaName(String name) { public CodegenModel fromModel(String name, Schema schema) { // Normalize the schema name before any processing name = normalizeSchemaName(name); - return super.fromModel(name, schema); + CodegenModel mdl = super.fromModel(name, schema); + + // Handle oneOf/anyOf schemas to use Nim object variants + if (mdl.getComposedSchemas() != null) { + if (mdl.getComposedSchemas().getOneOf() != null && !mdl.getComposedSchemas().getOneOf().isEmpty()) { + mdl.vendorExtensions.put("x-is-one-of", true); + processComposedSchemaVariants(mdl, mdl.getComposedSchemas().getOneOf(), schema); + } else if (mdl.getComposedSchemas().getAnyOf() != null && !mdl.getComposedSchemas().getAnyOf().isEmpty()) { + mdl.vendorExtensions.put("x-is-any-of", true); + processComposedSchemaVariants(mdl, mdl.getComposedSchemas().getAnyOf(), schema); + } + } + + return mdl; + } + + /** + * Process oneOf/anyOf schemas to generate proper variant names for Nim object variants. + */ + private void processComposedSchemaVariants(CodegenModel mdl, List variants, Schema schema) { + List newVariants = new ArrayList<>(); + List schemas = ModelUtils.getInterfaces(schema); + + if (variants.size() != schemas.size()) { + LOGGER.warn("Variant size does not match schema interfaces size for model " + mdl.name); + return; + } + + for (int i = 0; i < variants.size(); i++) { + CodegenProperty variant = variants.get(i); + Schema variantSchema = schemas.get(i); + + // Create a clone to avoid modifying the original + CodegenProperty newVariant = variant.clone(); + + // Sanitize dataType to remove trailing underscores (Nim doesn't allow them) + if (newVariant.dataType != null) { + newVariant.dataType = sanitizeNimIdentifier(newVariant.dataType); + } + if (newVariant.datatypeWithEnum != null) { + newVariant.datatypeWithEnum = sanitizeNimIdentifier(newVariant.datatypeWithEnum); + } + + // Set variant name based on schema reference or type + if (variantSchema.get$ref() != null && !variantSchema.get$ref().isEmpty()) { + String refName = ModelUtils.getSimpleRef(variantSchema.get$ref()); + if (refName != null) { + newVariant.setName(toModelName(refName)); + newVariant.setBaseName(refName); + } + } else if (variantSchema.getType() != null) { + // For primitive types or inline schemas + String typeName = variantSchema.getType(); + if (variantSchema.getTitle() != null && !variantSchema.getTitle().isEmpty()) { + typeName = variantSchema.getTitle(); + } + newVariant.setName(camelize(typeName)); + newVariant.setBaseName(typeName); + } + + newVariants.add(newVariant); + } + + // Replace the original variants with the processed ones + if (mdl.getComposedSchemas().getOneOf() != null) { + mdl.getComposedSchemas().setOneOf(newVariants); + } else if (mdl.getComposedSchemas().getAnyOf() != null) { + mdl.getComposedSchemas().setAnyOf(newVariants); + } } @Override diff --git a/modules/openapi-generator/src/main/resources/nim-client/api.mustache b/modules/openapi-generator/src/main/resources/nim-client/api.mustache index 462fdcec5090..4836dc962c47 100644 --- a/modules/openapi-generator/src/main/resources/nim-client/api.mustache +++ b/modules/openapi-generator/src/main/resources/nim-client/api.mustache @@ -18,10 +18,7 @@ const basepath = "{{{basePath}}}" template constructResult[T](response: Response): untyped = if response.code in {Http200, Http201, Http202, Http204, Http206}: try: - when name(stripGenericParams(T.typedesc).typedesc) == name(Table): - (some(json.to(parseJson(response.body), T.typedesc)), response) - else: - (some(marshal.to[T](response.body)), response) + (some(to(parseJson(response.body), T)), response) except JsonParsingError: # The server returned a malformed response though the response code is 2XX # TODO: need better error handling @@ -37,9 +34,11 @@ proc {{{operationId}}}*(httpClient: HttpClient{{#allParams}}, {{{paramName}}}: { httpClient.headers["Content-Type"] = "application/x-www-form-urlencoded"{{/isMultipart}}{{#isMultipart}} httpClient.headers["Content-Type"] = "multipart/form-data"{{/isMultipart}}{{/hasFormParams}}{{#hasHeaderParams}}{{#headerParams}} httpClient.headers["{{{baseName}}}"] = {{{paramName}}}{{#isArray}}.join(","){{/isArray}}{{/headerParams}}{{#description}} ## {{{.}}}{{/description}}{{/hasHeaderParams}}{{#hasQueryParams}} - let url_encoded_query_params = encodeQuery([{{#queryParams}} - ("{{{baseName}}}", ${{{paramName}}}{{#isArray}}.join(","){{/isArray}}), # {{{description}}}{{/queryParams}} - ]){{/hasQueryParams}}{{#hasFormParams}}{{^isMultipart}} + var query_params_list: seq[(string, string)] = @[]{{#queryParams}}{{#required}} + query_params_list.add(("{{{baseName}}}", ${{{paramName}}}{{#isArray}}.join(","){{/isArray}})){{/required}}{{^required}} + if {{#isArray}}{{{paramName}}}.len > 0{{/isArray}}{{^isArray}}${{{paramName}}} != ""{{/isArray}}: + query_params_list.add(("{{{baseName}}}", ${{{paramName}}}{{#isArray}}.join(","){{/isArray}})){{/required}}{{/queryParams}} + let url_encoded_query_params = encodeQuery(query_params_list){{/hasQueryParams}}{{#hasFormParams}}{{^isMultipart}} let form_data = encodeQuery([{{#formParams}} ("{{{baseName}}}", ${{{paramName}}}{{#isArray}}.join(","){{/isArray}}), # {{{description}}}{{/formParams}} ]){{/isMultipart}}{{#isMultipart}} diff --git a/modules/openapi-generator/src/main/resources/nim-client/model.mustache b/modules/openapi-generator/src/main/resources/nim-client/model.mustache index 51e3c73910b7..1da89f626a94 100644 --- a/modules/openapi-generator/src/main/resources/nim-client/model.mustache +++ b/modules/openapi-generator/src/main/resources/nim-client/model.mustache @@ -1,6 +1,8 @@ {{>header}} import json import tables +import marshal +import options {{#imports}}import {{import}} {{/imports}}{{#models}}{{#model}}{{#isEnum}} @@ -14,7 +16,45 @@ func `%`*(v: {{{classname}}}): JsonNode = func `$`*(v: {{{classname}}}): string = result = case v:{{#allowableValues}}{{#enumVars}} of {{{classname}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}} -{{/isEnum}}{{^isEnum}}{{#vars}}{{#isEnum}} +{{/isEnum}}{{^isEnum}}{{#vendorExtensions.x-is-one-of}} +# OneOf type +type {{{classname}}}Kind* {.pure.} = enum{{#composedSchemas.oneOf}} + {{{name}}}Variant{{/composedSchemas.oneOf}} + +type {{{classname}}}* = object + ## {{{description}}} + case kind*: {{{classname}}}Kind{{#composedSchemas.oneOf}} + of {{{classname}}}Kind.{{{name}}}Variant: + {{{baseName}}}Value*: {{{dataType}}}{{/composedSchemas.oneOf}} + +proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = + ## Custom deserializer for oneOf type - tries each variant{{#composedSchemas.oneOf}} + try: + return {{{classname}}}(kind: {{{classname}}}Kind.{{{name}}}Variant, {{{baseName}}}Value: json.to(node, {{{dataType}}})) + except Exception as e: + when defined(debug): + echo "Failed to deserialize as {{{dataType}}}: ", e.msg{{/composedSchemas.oneOf}} + raise newException(ValueError, "Unable to deserialize into any variant of {{{classname}}}. JSON: " & $node) +{{/vendorExtensions.x-is-one-of}}{{#vendorExtensions.x-is-any-of}} +# AnyOf type +type {{{classname}}}Kind* {.pure.} = enum{{#composedSchemas.anyOf}} + {{{name}}}Variant{{/composedSchemas.anyOf}} + +type {{{classname}}}* = object + ## {{{description}}} + case kind*: {{{classname}}}Kind{{#composedSchemas.anyOf}} + of {{{classname}}}Kind.{{{name}}}Variant: + {{{baseName}}}Value*: {{{dataType}}}{{/composedSchemas.anyOf}} + +proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = + ## Custom deserializer for anyOf type - tries each variant{{#composedSchemas.anyOf}} + try: + return {{{classname}}}(kind: {{{classname}}}Kind.{{{name}}}Variant, {{{baseName}}}Value: json.to(node, {{{dataType}}})) + except Exception as e: + when defined(debug): + echo "Failed to deserialize as {{{dataType}}}: ", e.msg{{/composedSchemas.anyOf}} + raise newException(ValueError, "Unable to deserialize into any variant of {{{classname}}}. JSON: " & $node) +{{/vendorExtensions.x-is-any-of}}{{^vendorExtensions.x-is-one-of}}{{^vendorExtensions.x-is-any-of}}{{#vars}}{{#isEnum}} type {{{enumName}}}* {.pure.} = enum{{#allowableValues}}{{#enumVars}} {{{name}}}{{/enumVars}}{{/allowableValues}} {{/isEnum}}{{/vars}} @@ -29,4 +69,4 @@ func `%`*(v: {{{enumName}}}): JsonNode = func `$`*(v: {{{enumName}}}): string = result = case v:{{#allowableValues}}{{#enumVars}} of {{{enumName}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}} -{{/isEnum}}{{/vars}}{{/isEnum}}{{/model}}{{/models}} +{{/isEnum}}{{/vars}}{{/vendorExtensions.x-is-any-of}}{{/vendorExtensions.x-is-one-of}}{{/isEnum}}{{/model}}{{/models}} From 50cded6d43ad1f8239a43b9f13e934f2ef57c5b6 Mon Sep 17 00:00:00 2001 From: winrid Date: Thu, 20 Nov 2025 17:07:43 -0800 Subject: [PATCH 4/6] snapshot --- .../petstore/nim/petstore/apis/api_pet.nim | 17 +++++++---------- .../petstore/nim/petstore/apis/api_store.nim | 5 +---- .../petstore/nim/petstore/apis/api_user.nim | 13 +++++-------- .../nim/petstore/models/model_api_response.nim | 8 +++++--- .../nim/petstore/models/model_category.nim | 6 ++++-- .../models/model_get_pet_stats200response.nim | 6 ++++-- .../nim/petstore/models/model_order.nim | 12 +++++++----- .../petstore/nim/petstore/models/model_pet.nim | 8 +++++--- .../models/model_pet_config_any_of1.nim | 4 +++- .../nim/petstore/models/model_pet_metadata.nim | 4 +++- .../petstore/models/model_pet_positions.nim | 4 +++- .../nim/petstore/models/model_pet_priority.nim | 2 ++ ...ring_or_null_after_string_or_null_value.nim | 6 ++++-- .../petstore/nim/petstore/models/model_tag.nim | 6 ++++-- .../models/model_unfavorite_pet_request.nim | 4 +++- .../nim/petstore/models/model_user.nim | 18 ++++++++++-------- 16 files changed, 70 insertions(+), 53 deletions(-) diff --git a/samples/client/petstore/nim/petstore/apis/api_pet.nim b/samples/client/petstore/nim/petstore/apis/api_pet.nim index 28e2aa7ae7da..e5798afd71a8 100644 --- a/samples/client/petstore/nim/petstore/apis/api_pet.nim +++ b/samples/client/petstore/nim/petstore/apis/api_pet.nim @@ -28,10 +28,7 @@ const basepath = "http://petstore.swagger.io/v2" template constructResult[T](response: Response): untyped = if response.code in {Http200, Http201, Http202, Http204, Http206}: try: - when name(stripGenericParams(T.typedesc).typedesc) == name(Table): - (some(json.to(parseJson(response.body), T.typedesc)), response) - else: - (some(marshal.to[T](response.body)), response) + (some(to(parseJson(response.body), T)), response) except JsonParsingError: # The server returned a malformed response though the response code is 2XX # TODO: need better error handling @@ -58,9 +55,9 @@ proc deletePet*(httpClient: HttpClient, petId: int64, apiKey: string): Response proc findPetsByStatus*(httpClient: HttpClient, status: seq[Status]): (Option[seq[Pet]], Response) = ## Finds Pets by status - let url_encoded_query_params = encodeQuery([ - ("status", $status.join(",")), # Status values that need to be considered for filter - ]) + var query_params_list: seq[(string, string)] = @[] + query_params_list.add(("status", $status.join(","))) + let url_encoded_query_params = encodeQuery(query_params_list) let response = httpClient.get(basepath & "/pet/findByStatus" & "?" & url_encoded_query_params) constructResult[seq[Pet]](response) @@ -68,9 +65,9 @@ proc findPetsByStatus*(httpClient: HttpClient, status: seq[Status]): (Option[seq proc findPetsByTags*(httpClient: HttpClient, tags: seq[string]): (Option[seq[Pet]], Response) {.deprecated.} = ## Finds Pets by tags - let url_encoded_query_params = encodeQuery([ - ("tags", $tags.join(",")), # Tags to filter by - ]) + var query_params_list: seq[(string, string)] = @[] + query_params_list.add(("tags", $tags.join(","))) + let url_encoded_query_params = encodeQuery(query_params_list) let response = httpClient.get(basepath & "/pet/findByTags" & "?" & url_encoded_query_params) constructResult[seq[Pet]](response) diff --git a/samples/client/petstore/nim/petstore/apis/api_store.nim b/samples/client/petstore/nim/petstore/apis/api_store.nim index 93a295009133..3a2aaf4be2af 100644 --- a/samples/client/petstore/nim/petstore/apis/api_store.nim +++ b/samples/client/petstore/nim/petstore/apis/api_store.nim @@ -25,10 +25,7 @@ const basepath = "http://petstore.swagger.io/v2" template constructResult[T](response: Response): untyped = if response.code in {Http200, Http201, Http202, Http204, Http206}: try: - when name(stripGenericParams(T.typedesc).typedesc) == name(Table): - (some(json.to(parseJson(response.body), T.typedesc)), response) - else: - (some(marshal.to[T](response.body)), response) + (some(to(parseJson(response.body), T)), response) except JsonParsingError: # The server returned a malformed response though the response code is 2XX # TODO: need better error handling diff --git a/samples/client/petstore/nim/petstore/apis/api_user.nim b/samples/client/petstore/nim/petstore/apis/api_user.nim index 25384f40fc76..52c5c5aa81a3 100644 --- a/samples/client/petstore/nim/petstore/apis/api_user.nim +++ b/samples/client/petstore/nim/petstore/apis/api_user.nim @@ -25,10 +25,7 @@ const basepath = "http://petstore.swagger.io/v2" template constructResult[T](response: Response): untyped = if response.code in {Http200, Http201, Http202, Http204, Http206}: try: - when name(stripGenericParams(T.typedesc).typedesc) == name(Table): - (some(json.to(parseJson(response.body), T.typedesc)), response) - else: - (some(marshal.to[T](response.body)), response) + (some(to(parseJson(response.body), T)), response) except JsonParsingError: # The server returned a malformed response though the response code is 2XX # TODO: need better error handling @@ -74,10 +71,10 @@ proc getUserByName*(httpClient: HttpClient, username: string): (Option[User], Re proc loginUser*(httpClient: HttpClient, username: string, password: string): (Option[string], Response) = ## Logs user into the system - let url_encoded_query_params = encodeQuery([ - ("username", $username), # The user name for login - ("password", $password), # The password for login in clear text - ]) + var query_params_list: seq[(string, string)] = @[] + query_params_list.add(("username", $username)) + query_params_list.add(("password", $password)) + let url_encoded_query_params = encodeQuery(query_params_list) let response = httpClient.get(basepath & "/user/login" & "?" & url_encoded_query_params) constructResult[string](response) diff --git a/samples/client/petstore/nim/petstore/models/model_api_response.nim b/samples/client/petstore/nim/petstore/models/model_api_response.nim index c76ac9aeb13e..3342d3ffc486 100644 --- a/samples/client/petstore/nim/petstore/models/model_api_response.nim +++ b/samples/client/petstore/nim/petstore/models/model_api_response.nim @@ -9,11 +9,13 @@ import json import tables +import marshal +import options type ApiResponse* = object ## Describes the result of uploading an image resource - code*: int - `type`*: string - message*: string + code*: Option[int] + `type`*: Option[string] + message*: Option[string] diff --git a/samples/client/petstore/nim/petstore/models/model_category.nim b/samples/client/petstore/nim/petstore/models/model_category.nim index 66069f3f3cf3..efd8f365c957 100644 --- a/samples/client/petstore/nim/petstore/models/model_category.nim +++ b/samples/client/petstore/nim/petstore/models/model_category.nim @@ -9,10 +9,12 @@ import json import tables +import marshal +import options type Category* = object ## A category for a pet - id*: int64 - name*: string + id*: Option[int64] + name*: Option[string] diff --git a/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim b/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim index ae7fefeeaade..59199ee6584b 100644 --- a/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim +++ b/samples/client/petstore/nim/petstore/models/model_get_pet_stats200response.nim @@ -9,10 +9,12 @@ import json import tables +import marshal +import options type GetPetStats200response* = object ## - totalPets*: int - status*: string + totalPets*: Option[int] + status*: Option[string] diff --git a/samples/client/petstore/nim/petstore/models/model_order.nim b/samples/client/petstore/nim/petstore/models/model_order.nim index c107cb85c50a..0a710c58d574 100644 --- a/samples/client/petstore/nim/petstore/models/model_order.nim +++ b/samples/client/petstore/nim/petstore/models/model_order.nim @@ -9,6 +9,8 @@ import json import tables +import marshal +import options type Status* {.pure.} = enum @@ -18,12 +20,12 @@ type Status* {.pure.} = enum type Order* = object ## An order for a pets from the pet store - id*: int64 - petId*: int64 - quantity*: int - shipDate*: string + id*: Option[int64] + petId*: Option[int64] + quantity*: Option[int] + shipDate*: Option[string] status*: Status ## Order Status - complete*: bool + complete*: Option[bool] func `%`*(v: Status): JsonNode = result = case v: diff --git a/samples/client/petstore/nim/petstore/models/model_pet.nim b/samples/client/petstore/nim/petstore/models/model_pet.nim index b3965f8cfda9..e69d9733512e 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet.nim @@ -9,6 +9,8 @@ import json import tables +import marshal +import options import model_category import model_tag @@ -20,11 +22,11 @@ type Status* {.pure.} = enum type Pet* = object ## A pet for sale in the pet store - id*: int64 - category*: Category + id*: Option[int64] + category*: Option[Category] name*: string photoUrls*: seq[string] - tags*: seq[Tag] + tags*: Option[seq[Tag]] status*: Status ## pet status in the store func `%`*(v: Status): JsonNode = diff --git a/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim b/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim index 359b6006dd77..2160657f55cb 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet_config_any_of1.nim @@ -9,9 +9,11 @@ import json import tables +import marshal +import options type PetConfigAnyOf1* = object ## - version*: int + version*: Option[int] diff --git a/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim b/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim index 340758c5fed4..180cb903883b 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet_metadata.nim @@ -9,9 +9,11 @@ import json import tables +import marshal +import options type PetMetadata* = object ## - metadata*: string + metadata*: Option[string] diff --git a/samples/client/petstore/nim/petstore/models/model_pet_positions.nim b/samples/client/petstore/nim/petstore/models/model_pet_positions.nim index 8eef1796b94c..a232f1479348 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet_positions.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet_positions.nim @@ -9,10 +9,12 @@ import json import tables +import marshal +import options import model_record_string_before_string_or_null_after_string_or_null_value type PetPositions* = object ## - positions*: Table[string, RecordStringBeforeStringOrNullAfterStringOrNullValue] + positions*: Option[Table[string, RecordStringBeforeStringOrNullAfterStringOrNullValue]] diff --git a/samples/client/petstore/nim/petstore/models/model_pet_priority.nim b/samples/client/petstore/nim/petstore/models/model_pet_priority.nim index 2c1dede7bfd6..9f6d2cb744a8 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet_priority.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet_priority.nim @@ -9,6 +9,8 @@ import json import tables +import marshal +import options type PetPriority* {.pure.} = enum diff --git a/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim b/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim index 15507246061f..9c089c236b91 100644 --- a/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim +++ b/samples/client/petstore/nim/petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim @@ -9,10 +9,12 @@ import json import tables +import marshal +import options type RecordStringBeforeStringOrNullAfterStringOrNullValue* = object ## - before*: string - after*: string + before*: Option[string] + after*: Option[string] diff --git a/samples/client/petstore/nim/petstore/models/model_tag.nim b/samples/client/petstore/nim/petstore/models/model_tag.nim index 7142bb740bd2..b5af0450ad45 100644 --- a/samples/client/petstore/nim/petstore/models/model_tag.nim +++ b/samples/client/petstore/nim/petstore/models/model_tag.nim @@ -9,10 +9,12 @@ import json import tables +import marshal +import options type Tag* = object ## A tag for a pet - id*: int64 - name*: string + id*: Option[int64] + name*: Option[string] diff --git a/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim b/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim index 0b9a1d7f7fbb..122cafe366b7 100644 --- a/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim +++ b/samples/client/petstore/nim/petstore/models/model_unfavorite_pet_request.nim @@ -9,9 +9,11 @@ import json import tables +import marshal +import options type UnfavoritePetRequest* = object ## - reason*: string ## Reason for unfavoriting + reason*: Option[string] ## Reason for unfavoriting diff --git a/samples/client/petstore/nim/petstore/models/model_user.nim b/samples/client/petstore/nim/petstore/models/model_user.nim index b33756f57c00..c2c0ed251a6f 100644 --- a/samples/client/petstore/nim/petstore/models/model_user.nim +++ b/samples/client/petstore/nim/petstore/models/model_user.nim @@ -9,16 +9,18 @@ import json import tables +import marshal +import options type User* = object ## A User who is purchasing from the pet store - id*: int64 - username*: string - firstName*: string - lastName*: string - email*: string - password*: string - phone*: string - userStatus*: int ## User Status + id*: Option[int64] + username*: Option[string] + firstName*: Option[string] + lastName*: Option[string] + email*: Option[string] + password*: Option[string] + phone*: Option[string] + userStatus*: Option[int] ## User Status From f97c3bbb7fa45b6ac1dc9b0a6cc6b0b726dbdb70 Mon Sep 17 00:00:00 2001 From: winrid Date: Thu, 20 Nov 2025 17:51:30 -0800 Subject: [PATCH 5/6] docs gen --- docs/generators/nim.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generators/nim.md b/docs/generators/nim.md index d61882b2bc94..e7947d0874d1 100644 --- a/docs/generators/nim.md +++ b/docs/generators/nim.md @@ -226,8 +226,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl |Polymorphism|✗|OAS2,OAS3 |Union|✗|OAS3 |allOf|✗|OAS2,OAS3 -|anyOf|✗|OAS3 -|oneOf|✗|OAS3 +|anyOf|✓|OAS3 +|oneOf|✓|OAS3 |not|✗|OAS3 ### Security Feature From 16051dd99efca2c6c0bb84f87ae3760aad86de04 Mon Sep 17 00:00:00 2001 From: winrid Date: Fri, 21 Nov 2025 08:03:46 -0800 Subject: [PATCH 6/6] Improved handling of enums --- .../codegen/languages/NimClientCodegen.java | 174 +++++++++++++++- .../main/resources/nim-client/model.mustache | 124 ++++++++++- .../codegen/nim/NimClientCodegenTest.java | 2 +- .../src/test/resources/3_0/nim/petstore.yaml | 194 ++++++++++++++++++ .../petstore/nim/.openapi-generator/FILES | 8 + samples/client/petstore/nim/README.md | 7 + samples/client/petstore/nim/petstore.nim | 16 ++ .../petstore/nim/petstore/apis/api_pet.nim | 58 ++++++ .../model_get_pet_reviews200response.nim | 44 ++++ ...get_pet_reviews_response_with_presence.nim | 19 ++ .../models/model_ignored_response.nim | 55 +++++ .../nim/petstore/models/model_order.nim | 17 +- .../nim/petstore/models/model_pet.nim | 17 +- .../nim/petstore/models/model_pet_alert.nim | 22 ++ .../petstore/models/model_pet_alert_level.nim | 55 +++++ .../petstore/models/model_pet_audit_log.nim | 121 +++++++++++ .../petstore/models/model_pet_priority.nim | 25 ++- .../nim/petstore/models/model_pet_review.nim | 48 +++++ .../models/model_pet_reviews_response.nim | 45 ++++ 19 files changed, 1026 insertions(+), 25 deletions(-) create mode 100644 samples/client/petstore/nim/petstore/models/model_get_pet_reviews200response.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_get_pet_reviews_response_with_presence.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_ignored_response.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_alert.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_alert_level.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_audit_log.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_review.nim create mode 100644 samples/client/petstore/nim/petstore/models/model_pet_reviews_response.nim diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java index 6c96b25a26fc..8e13aea833e3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java @@ -34,6 +34,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; @@ -177,17 +178,113 @@ public NimClientCodegen() { } + @Override + public Map postProcessAllModels(Map allModels) { + allModels = super.postProcessAllModels(allModels); + + // First pass: identify all models that have fields with custom JSON names + Set modelsWithCustomJson = new HashSet<>(); + + for (Map.Entry entry : allModels.entrySet()) { + ModelsMap modelsMap = entry.getValue(); + for (ModelMap mo : modelsMap.getModels()) { + CodegenModel cm = mo.getModel(); + + // Check if this model has fields with custom JSON names + for (CodegenProperty var : cm.vars) { + if (var.vendorExtensions.containsKey("x-json-name")) { + modelsWithCustomJson.add(cm.classname); + break; + } + } + } + } + + // Second pass: cascade custom JSON handling to parent models and mark array fields + // We need multiple passes to handle transitive dependencies + boolean changed = true; + while (changed) { + changed = false; + for (Map.Entry entry : allModels.entrySet()) { + ModelsMap modelsMap = entry.getValue(); + for (ModelMap mo : modelsMap.getModels()) { + CodegenModel cm = mo.getModel(); + + // Check if any field's type needs custom JSON and mark array fields appropriately + for (CodegenProperty var : cm.vars) { + String fieldType = var.complexType != null ? var.complexType : var.baseType; + + // Handle arrays - check if the inner type has custom JSON + if (var.isArray && var.items != null) { + String innerType = var.items.complexType != null ? var.items.complexType : var.items.baseType; + if (innerType != null && modelsWithCustomJson.contains(innerType)) { + // Mark this array field as containing types with custom JSON + var.vendorExtensions.put("x-is-array-with-custom-json", "true"); + var.vendorExtensions.put("x-array-inner-type", innerType); + } + fieldType = innerType; + } + + // Cascade custom JSON to parent model if not already marked + if (fieldType != null && modelsWithCustomJson.contains(fieldType)) { + if (!cm.vendorExtensions.containsKey("x-has-custom-json-names")) { + cm.vendorExtensions.put("x-has-custom-json-names", true); + modelsWithCustomJson.add(cm.classname); + changed = true; + } + } + } + } + } + } + + return allModels; + } + + /** + * Strips surrounding quotes from integer enum values. + * The base OpenAPI Generator stores all enum values as quoted strings (e.g., "0", "1", "2") + * regardless of the enum's actual type. For Nim integer enums, we need the raw numbers + * without quotes so they serialize correctly: %(0) instead of %("0") + */ + private void stripQuotesFromIntegerEnumValues(Map allowableValues) { + if (allowableValues == null || !allowableValues.containsKey("enumVars")) { + return; + } + + @SuppressWarnings("unchecked") + List> enumVars = (List>) allowableValues.get("enumVars"); + for (Map enumVar : enumVars) { + Object value = enumVar.get("value"); + if (value instanceof String) { + String strValue = (String) value; + // Remove surrounding quotes if present + if (strValue.startsWith("\"") && strValue.endsWith("\"")) { + enumVar.put("value", strValue.substring(1, strValue.length() - 1)); + } + } + } + } + @Override public ModelsMap postProcessModels(ModelsMap objs) { objs = postProcessModelsEnum(objs); - // Mark top-level string enums for proper enum generation in template for (ModelMap mo : objs.getModels()) { CodegenModel cm = mo.getModel(); + if (cm.isEnum && cm.allowableValues != null && cm.allowableValues.containsKey("enumVars")) { cm.vendorExtensions.put("x-is-top-level-enum", true); + + // For integer enums, strip quotes from enum values + if (cm.vendorExtensions.containsKey("x-is-integer-enum")) { + stripQuotesFromIntegerEnumValues(cm.allowableValues); + } } + // Check if any fields need custom JSON name mapping + boolean hasCustomJsonNames = false; + // Fix dataType fields that contain underscored type names // This handles cases like Table[string, Record_string__foo__value] // Also wrap optional fields in Option[T] @@ -199,17 +296,47 @@ public ModelsMap postProcessModels(ModelsMap objs) { var.datatypeWithEnum = fixRecordTypeReferences(var.datatypeWithEnum); } + // Check if the field name was changed from the original (baseName) + // This happens for fields like "_id" which are renamed to "id" + // But we need to exclude cases where the name is just escaped with backticks + // (e.g., "from" becomes "`from`" because it's a reserved word) + if (var.baseName != null && !var.baseName.equals(var.name)) { + // Check if this is just a reserved word escaping (name is `baseName`) + String escapedName = "`" + var.baseName + "`"; + if (!var.name.equals(escapedName)) { + // This is a real rename, not just escaping + var.vendorExtensions.put("x-json-name", var.baseName); + hasCustomJsonNames = true; + } + } + // Wrap optional (non-required) or nullable fields in Option[T] - if ((!var.required || var.isNullable) && !var.isReadOnly) { - String baseType = var.datatypeWithEnum != null ? var.datatypeWithEnum : var.dataType; + // For non-enum fields only (enums are handled specially in the template) + if ((!var.required || var.isNullable) && !var.isReadOnly && !var.isEnum) { + String baseType = var.dataType; if (baseType != null && !baseType.startsWith("Option[")) { var.dataType = "Option[" + baseType + "]"; if (var.datatypeWithEnum != null) { var.datatypeWithEnum = "Option[" + var.datatypeWithEnum + "]"; } - var.vendorExtensions.put("x-is-optional", true); } } + + // For enum fields, set x-is-optional if they are not required + if (var.isEnum && (!var.required || var.isNullable)) { + var.vendorExtensions.put("x-is-optional", true); + } + + // Always set x-is-optional based on the final dataType (for non-enum fields) + // This ensures consistency between type declaration and JSON handling + if (!var.isEnum && var.dataType != null && var.dataType.startsWith("Option[")) { + var.vendorExtensions.put("x-is-optional", true); + } + } + + // Mark the model as needing custom JSON deserialization if any fields have custom names + if (hasCustomJsonNames) { + cm.vendorExtensions.put("x-has-custom-json-names", true); } } @@ -231,7 +358,7 @@ private String fixRecordTypeReferences(String typeString) { // Match Record_ followed by any characters until end or comma/bracket Pattern pattern = Pattern.compile("Record_[a-z_]+"); - java.util.regex.Matcher matcher = pattern.matcher(result); + Matcher matcher = pattern.matcher(result); StringBuffer sb = new StringBuffer(); while (matcher.find()) { @@ -331,6 +458,14 @@ public CodegenModel fromModel(String name, Schema schema) { name = normalizeSchemaName(name); CodegenModel mdl = super.fromModel(name, schema); + // Detect integer enums - check both the schema type and the dataType + if (mdl.isEnum) { + String schemaType = schema != null ? schema.getType() : null; + if ("integer".equals(schemaType) || "int".equals(mdl.dataType) || "int64".equals(mdl.dataType)) { + mdl.vendorExtensions.put("x-is-integer-enum", true); + } + } + // Handle oneOf/anyOf schemas to use Nim object variants if (mdl.getComposedSchemas() != null) { if (mdl.getComposedSchemas().getOneOf() != null && !mdl.getComposedSchemas().getOneOf().isEmpty()) { @@ -364,12 +499,35 @@ private void processComposedSchemaVariants(CodegenModel mdl, List 0 && Character.isUpperCase(sanitizedBase.charAt(0))) { + newVariant.baseName = toModelName(sanitizedBase); + } else { + newVariant.baseName = sanitizeNimIdentifier(sanitizedBase); + } + } + + // Sanitize dataType to remove underscores and properly format for Nim + // For model types (not primitives), use toModelName to get the proper type name if (newVariant.dataType != null) { - newVariant.dataType = sanitizeNimIdentifier(newVariant.dataType); + // Check if this is a model type (starts with uppercase) vs primitive + if (newVariant.dataType.length() > 0 && Character.isUpperCase(newVariant.dataType.charAt(0))) { + // This is likely a model type, use toModelName to properly format it + newVariant.dataType = toModelName(newVariant.dataType); + } else { + // Primitive type, just sanitize + newVariant.dataType = sanitizeNimIdentifier(newVariant.dataType); + } } if (newVariant.datatypeWithEnum != null) { - newVariant.datatypeWithEnum = sanitizeNimIdentifier(newVariant.datatypeWithEnum); + if (newVariant.datatypeWithEnum.length() > 0 && Character.isUpperCase(newVariant.datatypeWithEnum.charAt(0))) { + newVariant.datatypeWithEnum = toModelName(newVariant.datatypeWithEnum); + } else { + newVariant.datatypeWithEnum = sanitizeNimIdentifier(newVariant.datatypeWithEnum); + } } // Set variant name based on schema reference or type diff --git a/modules/openapi-generator/src/main/resources/nim-client/model.mustache b/modules/openapi-generator/src/main/resources/nim-client/model.mustache index 1da89f626a94..ead20075c5b2 100644 --- a/modules/openapi-generator/src/main/resources/nim-client/model.mustache +++ b/modules/openapi-generator/src/main/resources/nim-client/model.mustache @@ -9,13 +9,42 @@ import options type {{{classname}}}* {.pure.} = enum{{#allowableValues}}{{#enumVars}} {{{name}}}{{/enumVars}}{{/allowableValues}} +{{#vendorExtensions.x-is-integer-enum}} +func `%`*(v: {{{classname}}}): JsonNode = + result = case v:{{#allowableValues}}{{#enumVars}} + of {{{classname}}}.{{{name}}}: %({{{value}}}){{/enumVars}}{{/allowableValues}} + +{{/vendorExtensions.x-is-integer-enum}} +{{^vendorExtensions.x-is-integer-enum}} func `%`*(v: {{{classname}}}): JsonNode = result = case v:{{#allowableValues}}{{#enumVars}} of {{{classname}}}.{{{name}}}: %{{{value}}}{{/enumVars}}{{/allowableValues}} +{{/vendorExtensions.x-is-integer-enum}} func `$`*(v: {{{classname}}}): string = result = case v:{{#allowableValues}}{{#enumVars}} of {{{classname}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}} +{{#vendorExtensions.x-is-integer-enum}} +proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = + if node.kind != JInt: + raise newException(ValueError, "Expected integer for enum {{{classname}}}, got " & $node.kind) + let intVal = node.getInt() + case intVal:{{#allowableValues}}{{#enumVars}} + of {{{value}}}: + return {{{classname}}}.{{{name}}}{{/enumVars}}{{/allowableValues}} + else: + raise newException(ValueError, "Invalid enum value for {{{classname}}}: " & $intVal) +{{/vendorExtensions.x-is-integer-enum}}{{^vendorExtensions.x-is-integer-enum}} +proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum {{{classname}}}, got " & $node.kind) + let strVal = node.getStr() + case strVal:{{#allowableValues}}{{#enumVars}} + of $({{{value}}}): + return {{{classname}}}.{{{name}}}{{/enumVars}}{{/allowableValues}} + else: + raise newException(ValueError, "Invalid enum value for {{{classname}}}: " & strVal) +{{/vendorExtensions.x-is-integer-enum}} {{/isEnum}}{{^isEnum}}{{#vendorExtensions.x-is-one-of}} # OneOf type type {{{classname}}}Kind* {.pure.} = enum{{#composedSchemas.oneOf}} @@ -30,7 +59,7 @@ type {{{classname}}}* = object proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = ## Custom deserializer for oneOf type - tries each variant{{#composedSchemas.oneOf}} try: - return {{{classname}}}(kind: {{{classname}}}Kind.{{{name}}}Variant, {{{baseName}}}Value: json.to(node, {{{dataType}}})) + return {{{classname}}}(kind: {{{classname}}}Kind.{{{name}}}Variant, {{{baseName}}}Value: to(node, {{{dataType}}})) except Exception as e: when defined(debug): echo "Failed to deserialize as {{{dataType}}}: ", e.msg{{/composedSchemas.oneOf}} @@ -49,7 +78,7 @@ type {{{classname}}}* = object proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = ## Custom deserializer for anyOf type - tries each variant{{#composedSchemas.anyOf}} try: - return {{{classname}}}(kind: {{{classname}}}Kind.{{{name}}}Variant, {{{baseName}}}Value: json.to(node, {{{dataType}}})) + return {{{classname}}}(kind: {{{classname}}}Kind.{{{name}}}Variant, {{{baseName}}}Value: to(node, {{{dataType}}})) except Exception as e: when defined(debug): echo "Failed to deserialize as {{{dataType}}}: ", e.msg{{/composedSchemas.anyOf}} @@ -60,13 +89,96 @@ type {{{enumName}}}* {.pure.} = enum{{#allowableValues}}{{#enumVars}} {{/isEnum}}{{/vars}} type {{{classname}}}* = object ## {{{description}}}{{#vars}} - {{{name}}}*: {{#isEnum}}{{{enumName}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#description}} ## {{{.}}}{{/description}}{{/vars}} -{{#vars}}{{#isEnum}} + {{{name}}}*: {{#isEnum}}{{#vendorExtensions.x-is-optional}}Option[{{{enumName}}}]{{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}}{{{enumName}}}{{/vendorExtensions.x-is-optional}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#description}} ## {{{.}}}{{/description}}{{/vars}} +{{#vars}}{{#isEnum}}{{#vendorExtensions.x-is-integer-enum}} +func `%`*(v: {{{enumName}}}): JsonNode = + result = case v:{{#allowableValues}}{{#enumVars}} + of {{{enumName}}}.{{{name}}}: %({{{value}}}){{/enumVars}}{{/allowableValues}} +{{/vendorExtensions.x-is-integer-enum}}{{^vendorExtensions.x-is-integer-enum}} func `%`*(v: {{{enumName}}}): JsonNode = result = case v:{{#allowableValues}}{{#enumVars}} of {{{enumName}}}.{{{name}}}: %{{{value}}}{{/enumVars}}{{/allowableValues}} - +{{/vendorExtensions.x-is-integer-enum}} func `$`*(v: {{{enumName}}}): string = result = case v:{{#allowableValues}}{{#enumVars}} of {{{enumName}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}} -{{/isEnum}}{{/vars}}{{/vendorExtensions.x-is-any-of}}{{/vendorExtensions.x-is-one-of}}{{/isEnum}}{{/model}}{{/models}} +{{#vendorExtensions.x-is-integer-enum}} +proc to*(node: JsonNode, T: typedesc[{{{enumName}}}]): {{{enumName}}} = + if node.kind != JInt: + raise newException(ValueError, "Expected integer for enum {{{enumName}}}, got " & $node.kind) + let intVal = node.getInt() + case intVal:{{#allowableValues}}{{#enumVars}} + of {{{value}}}: + return {{{enumName}}}.{{{name}}}{{/enumVars}}{{/allowableValues}} + else: + raise newException(ValueError, "Invalid enum value for {{{enumName}}}: " & $intVal) +{{/vendorExtensions.x-is-integer-enum}}{{^vendorExtensions.x-is-integer-enum}} +proc to*(node: JsonNode, T: typedesc[{{{enumName}}}]): {{{enumName}}} = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum {{{enumName}}}, got " & $node.kind) + let strVal = node.getStr() + case strVal:{{#allowableValues}}{{#enumVars}} + of $({{{value}}}): + return {{{enumName}}}.{{{name}}}{{/enumVars}}{{/allowableValues}} + else: + raise newException(ValueError, "Invalid enum value for {{{enumName}}}: " & strVal) +{{/vendorExtensions.x-is-integer-enum}} +{{/isEnum}}{{/vars}}{{#vendorExtensions.x-has-custom-json-names}} + +# Custom JSON deserialization for {{{classname}}} with custom field names +proc to*(node: JsonNode, T: typedesc[{{{classname}}}]): {{{classname}}} = + result = {{{classname}}}() + if node.kind == JObject:{{#vars}}{{#vendorExtensions.x-json-name}}{{#vendorExtensions.x-is-optional}} + if node.hasKey("{{{vendorExtensions.x-json-name}}}") and node["{{{vendorExtensions.x-json-name}}}"].kind != JNull:{{#vendorExtensions.x-is-array-with-custom-json}} + # Optional array of types with custom JSON - manually iterate and deserialize + let arrayNode = node["{{{vendorExtensions.x-json-name}}}"] + if arrayNode.kind == JArray: + var arr: seq[{{{vendorExtensions.x-array-inner-type}}}] = @[] + for item in arrayNode.items: + arr.add(to(item, {{{vendorExtensions.x-array-inner-type}}})) + result.{{{name}}} = some(arr){{/vendorExtensions.x-is-array-with-custom-json}}{{^vendorExtensions.x-is-array-with-custom-json}}{{#vendorExtensions.x-is-external-enum}} + result.{{{name}}} = some({{{vendorExtensions.x-enum-module}}}.to(node["{{{vendorExtensions.x-json-name}}}"], {{{vendorExtensions.x-enum-type}}})){{/vendorExtensions.x-is-external-enum}}{{^vendorExtensions.x-is-external-enum}}{{#isEnum}} + result.{{{name}}} = some(to(node["{{{vendorExtensions.x-json-name}}}"], {{{enumName}}})){{/isEnum}}{{^isEnum}} + result.{{{name}}} = some(to(node["{{{vendorExtensions.x-json-name}}}"], typeof(result.{{{name}}}.get()))){{/isEnum}}{{/vendorExtensions.x-is-external-enum}}{{/vendorExtensions.x-is-array-with-custom-json}}{{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}} + if node.hasKey("{{{vendorExtensions.x-json-name}}}"):{{#vendorExtensions.x-is-array-with-custom-json}} + # Array of types with custom JSON - manually iterate and deserialize + let arrayNode = node["{{{vendorExtensions.x-json-name}}}"] + if arrayNode.kind == JArray: + result.{{{name}}} = @[] + for item in arrayNode.items: + result.{{{name}}}.add(to(item, {{{vendorExtensions.x-array-inner-type}}})){{/vendorExtensions.x-is-array-with-custom-json}}{{^vendorExtensions.x-is-array-with-custom-json}}{{#vendorExtensions.x-is-external-enum}} + result.{{{name}}} = {{{vendorExtensions.x-enum-module}}}.to(node["{{{vendorExtensions.x-json-name}}}"], {{{vendorExtensions.x-enum-type}}}){{/vendorExtensions.x-is-external-enum}}{{^vendorExtensions.x-is-external-enum}}{{#isEnum}} + result.{{{name}}} = to(node["{{{vendorExtensions.x-json-name}}}"], {{{enumName}}}){{/isEnum}}{{^isEnum}} + result.{{{name}}} = to(node["{{{vendorExtensions.x-json-name}}}"], {{{dataType}}}){{/isEnum}}{{/vendorExtensions.x-is-external-enum}}{{/vendorExtensions.x-is-array-with-custom-json}}{{/vendorExtensions.x-is-optional}}{{/vendorExtensions.x-json-name}}{{^vendorExtensions.x-json-name}}{{#vendorExtensions.x-is-optional}} + if node.hasKey("{{{baseName}}}") and node["{{{baseName}}}"].kind != JNull:{{#vendorExtensions.x-is-array-with-custom-json}} + # Optional array of types with custom JSON - manually iterate and deserialize + let arrayNode = node["{{{baseName}}}"] + if arrayNode.kind == JArray: + var arr: seq[{{{vendorExtensions.x-array-inner-type}}}] = @[] + for item in arrayNode.items: + arr.add(to(item, {{{vendorExtensions.x-array-inner-type}}})) + result.{{{name}}} = some(arr){{/vendorExtensions.x-is-array-with-custom-json}}{{^vendorExtensions.x-is-array-with-custom-json}}{{#vendorExtensions.x-is-external-enum}} + result.{{{name}}} = some({{{vendorExtensions.x-enum-module}}}.to(node["{{{baseName}}}"], {{{vendorExtensions.x-enum-type}}})){{/vendorExtensions.x-is-external-enum}}{{^vendorExtensions.x-is-external-enum}}{{#isEnum}} + result.{{{name}}} = some(to(node["{{{baseName}}}"], {{{enumName}}})){{/isEnum}}{{^isEnum}} + result.{{{name}}} = some(to(node["{{{baseName}}}"], typeof(result.{{{name}}}.get()))){{/isEnum}}{{/vendorExtensions.x-is-external-enum}}{{/vendorExtensions.x-is-array-with-custom-json}}{{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}} + if node.hasKey("{{{baseName}}}"):{{#vendorExtensions.x-is-array-with-custom-json}} + # Array of types with custom JSON - manually iterate and deserialize + let arrayNode = node["{{{baseName}}}"] + if arrayNode.kind == JArray: + result.{{{name}}} = @[] + for item in arrayNode.items: + result.{{{name}}}.add(to(item, {{{vendorExtensions.x-array-inner-type}}})){{/vendorExtensions.x-is-array-with-custom-json}}{{^vendorExtensions.x-is-array-with-custom-json}}{{#vendorExtensions.x-is-external-enum}} + result.{{{name}}} = {{{vendorExtensions.x-enum-module}}}.to(node["{{{baseName}}}"], {{{vendorExtensions.x-enum-type}}}){{/vendorExtensions.x-is-external-enum}}{{^vendorExtensions.x-is-external-enum}}{{#isEnum}} + result.{{{name}}} = to(node["{{{baseName}}}"], {{{enumName}}}){{/isEnum}}{{^isEnum}} + result.{{{name}}} = to(node["{{{baseName}}}"], {{{dataType}}}){{/isEnum}}{{/vendorExtensions.x-is-external-enum}}{{/vendorExtensions.x-is-array-with-custom-json}}{{/vendorExtensions.x-is-optional}}{{/vendorExtensions.x-json-name}}{{/vars}} + +# Custom JSON serialization for {{{classname}}} with custom field names +proc `%`*(obj: {{{classname}}}): JsonNode = + result = newJObject(){{#vars}}{{#vendorExtensions.x-json-name}}{{#vendorExtensions.x-is-optional}} + if obj.{{{name}}}.isSome(): + result["{{{vendorExtensions.x-json-name}}}"] = %obj.{{{name}}}.get(){{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}} + result["{{{vendorExtensions.x-json-name}}}"] = %obj.{{{name}}}{{/vendorExtensions.x-is-optional}}{{/vendorExtensions.x-json-name}}{{^vendorExtensions.x-json-name}}{{#vendorExtensions.x-is-optional}} + if obj.{{{name}}}.isSome(): + result["{{{baseName}}}"] = %obj.{{{name}}}.get(){{/vendorExtensions.x-is-optional}}{{^vendorExtensions.x-is-optional}} + result["{{{baseName}}}"] = %obj.{{{name}}}{{/vendorExtensions.x-is-optional}}{{/vendorExtensions.x-json-name}}{{/vars}} +{{/vendorExtensions.x-has-custom-json-names}}{{/vendorExtensions.x-is-any-of}}{{/vendorExtensions.x-is-one-of}}{{/isEnum}}{{/model}}{{/models}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java index f9ed8cf0ae2d..bda478fbd325 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/nim/NimClientCodegenTest.java @@ -1,7 +1,7 @@ package org.openapitools.codegen.nim; import io.swagger.v3.oas.models.media.ObjectSchema; -import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.*; import org.openapitools.codegen.languages.NimClientCodegen; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml index 7c6e79b05242..23c6f0fe4e8c 100644 --- a/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/nim/petstore.yaml @@ -334,6 +334,114 @@ paths: application/json: schema: $ref: '#/components/schemas/GetPetStats_200_response' + /comments: + get: + tags: + - pet + summary: Get pet reviews (tests _id field mapping and arrays) + operationId: getPetReviews + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetReviewsResponse' + post: + tags: + - pet + summary: Add a pet review (tests _id field mapping) + operationId: addPetReview + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PetReview' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetReview' + /comments/search: + get: + tags: + - pet + summary: Search pet reviews (tests anyOf with underscores) + operationId: searchPetReviews + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/GetPetReviews_200_response' + /notifications: + get: + tags: + - pet + summary: Get pet alerts (tests integer enum) + operationId: getPetAlerts + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetAlert' + post: + tags: + - pet + summary: Create pet alert + operationId: createPetAlert + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PetAlert' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/PetAlert' + /audit: + get: + tags: + - pet + summary: Get pet audit logs (combined test) + operationId: getPetAuditLogs + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetAuditLog' + /ignored: + post: + tags: + - pet + summary: Mark as ignored (tests inline enum) + operationId: markIgnored + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/IgnoredResponse' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/IgnoredResponse' /store/inventory: get: tags: @@ -826,3 +934,89 @@ components: properties: version: type: integer + + # Fields named _id get renamed to id in Nim + PetReview: + type: object + properties: + _id: + type: string + description: MongoDB style ID field that should be renamed to id in Nim + author: + type: string + content: + type: string + timestamp: + type: string + format: date-time + + # Array deserialization with custom JSON + PetReviewsResponse: + type: object + properties: + reviews: + type: array + items: + $ref: '#/components/schemas/PetReview' + totalCount: + type: integer + + # Integer enum deserialization + PetAlertLevel: + type: integer + description: Alert priority level with integer values + enum: [0, 1, 2, 10, 99] + + PetAlert: + type: object + properties: + id: + type: string + level: + $ref: '#/components/schemas/PetAlertLevel' + message: + type: string + + # Composed schema type naming (anyOf/oneOf with underscores) + GetPetReviews_Response_WithPresence: + type: object + properties: + review: + type: string + + GetPetReviews_200_response: + anyOf: + - $ref: '#/components/schemas/GetPetReviews_Response_WithPresence' + - $ref: '#/components/schemas/ApiResponse' + + # Inline enum handling + IgnoredResponse: + type: object + properties: + reason: + type: string + note: + type: string + enum: ["0", "1", "2", "3"] + description: Inline enum that should use enum type name not base type + + # Combined test: Model with _id field, arrays, and enums + PetAuditLog: + type: object + properties: + _id: + type: string + description: Audit log unique identifier + action: + type: string + enum: ["create", "update", "delete"] + priority: + $ref: '#/components/schemas/PetAlertLevel' + from: + type: string + enum: ["api", "web", "mobile"] + nullable: true + reviews: + type: array + items: + $ref: '#/components/schemas/PetReview' diff --git a/samples/client/petstore/nim/.openapi-generator/FILES b/samples/client/petstore/nim/.openapi-generator/FILES index 1cff51f250c9..f8e488383817 100644 --- a/samples/client/petstore/nim/.openapi-generator/FILES +++ b/samples/client/petstore/nim/.openapi-generator/FILES @@ -7,14 +7,22 @@ petstore/apis/api_user.nim petstore/models/model_any_type.nim petstore/models/model_api_response.nim petstore/models/model_category.nim +petstore/models/model_get_pet_reviews200response.nim +petstore/models/model_get_pet_reviews_response_with_presence.nim petstore/models/model_get_pet_stats200response.nim +petstore/models/model_ignored_response.nim petstore/models/model_object.nim petstore/models/model_order.nim petstore/models/model_pet.nim +petstore/models/model_pet_alert.nim +petstore/models/model_pet_alert_level.nim +petstore/models/model_pet_audit_log.nim petstore/models/model_pet_config_any_of1.nim petstore/models/model_pet_metadata.nim petstore/models/model_pet_positions.nim petstore/models/model_pet_priority.nim +petstore/models/model_pet_review.nim +petstore/models/model_pet_reviews_response.nim petstore/models/model_record_string_before_string_or_null_after_string_or_null_value.nim petstore/models/model_tag.nim petstore/models/model_unfavorite_pet_request.nim diff --git a/samples/client/petstore/nim/README.md b/samples/client/petstore/nim/README.md index 9f48ef110058..36390ae66000 100644 --- a/samples/client/petstore/nim/README.md +++ b/samples/client/petstore/nim/README.md @@ -26,11 +26,18 @@ All URIs are relative to *http://petstore.swagger.io/v2* Module | Proc | HTTP request | Description ------------ | ------------- | ------------- | ------------- api_pet | addPet | **POST** /pet | Add a new pet to the store +api_pet | addPetReview | **POST** /comments | Add a pet review (tests _id field mapping) +api_pet | createPetAlert | **POST** /notifications | Create pet alert api_pet | deletePet | **DELETE** /pet/{petId} | Deletes a pet api_pet | findPetsByStatus | **GET** /pet/findByStatus | Finds Pets by status api_pet | findPetsByTags | **GET** /pet/findByTags | Finds Pets by tags +api_pet | getPetAlerts | **GET** /notifications | Get pet alerts (tests integer enum) +api_pet | getPetAuditLogs | **GET** /audit | Get pet audit logs (combined test) api_pet | getPetById | **GET** /pet/{petId} | Find pet by ID +api_pet | getPetReviews | **GET** /comments | Get pet reviews (tests _id field mapping and arrays) api_pet | getPetStats | **GET** /pet/stats | Get pet statistics (tests _200_ response normalization) +api_pet | markIgnored | **POST** /ignored | Mark as ignored (tests inline enum) +api_pet | searchPetReviews | **GET** /comments/search | Search pet reviews (tests anyOf with underscores) api_pet | unfavoritePet | **DELETE** /pet/{petId}/favorite | Remove pet from favorites (tests DELETE with body) api_pet | updatePet | **PUT** /pet | Update an existing pet api_pet | updatePetWithForm | **POST** /pet/{petId} | Updates a pet in the store with form data diff --git a/samples/client/petstore/nim/petstore.nim b/samples/client/petstore/nim/petstore.nim index 00e82002ae2b..6a0b825c31d8 100644 --- a/samples/client/petstore/nim/petstore.nim +++ b/samples/client/petstore/nim/petstore.nim @@ -10,13 +10,21 @@ # Models import petstore/models/model_api_response import petstore/models/model_category +import petstore/models/model_get_pet_reviews200response +import petstore/models/model_get_pet_reviews_response_with_presence import petstore/models/model_get_pet_stats200response +import petstore/models/model_ignored_response import petstore/models/model_order import petstore/models/model_pet +import petstore/models/model_pet_alert +import petstore/models/model_pet_alert_level +import petstore/models/model_pet_audit_log import petstore/models/model_pet_config_any_of1 import petstore/models/model_pet_metadata import petstore/models/model_pet_positions import petstore/models/model_pet_priority +import petstore/models/model_pet_review +import petstore/models/model_pet_reviews_response import petstore/models/model_record_string_before_string_or_null_after_string_or_null_value import petstore/models/model_tag import petstore/models/model_unfavorite_pet_request @@ -24,13 +32,21 @@ import petstore/models/model_user export model_api_response export model_category +export model_get_pet_reviews200response +export model_get_pet_reviews_response_with_presence export model_get_pet_stats200response +export model_ignored_response export model_order export model_pet +export model_pet_alert +export model_pet_alert_level +export model_pet_audit_log export model_pet_config_any_of1 export model_pet_metadata export model_pet_positions export model_pet_priority +export model_pet_review +export model_pet_reviews_response export model_record_string_before_string_or_null_after_string_or_null_value export model_tag export model_unfavorite_pet_request diff --git a/samples/client/petstore/nim/petstore/apis/api_pet.nim b/samples/client/petstore/nim/petstore/apis/api_pet.nim index e5798afd71a8..0544760014ac 100644 --- a/samples/client/petstore/nim/petstore/apis/api_pet.nim +++ b/samples/client/petstore/nim/petstore/apis/api_pet.nim @@ -19,8 +19,14 @@ import typetraits import uri import ../models/model_api_response +import ../models/model_get_pet_reviews200response import ../models/model_get_pet_stats200response +import ../models/model_ignored_response import ../models/model_pet +import ../models/model_pet_alert +import ../models/model_pet_audit_log +import ../models/model_pet_review +import ../models/model_pet_reviews_response import ../models/model_unfavorite_pet_request const basepath = "http://petstore.swagger.io/v2" @@ -46,6 +52,22 @@ proc addPet*(httpClient: HttpClient, pet: Pet): (Option[Pet], Response) = constructResult[Pet](response) +proc addPetReview*(httpClient: HttpClient, petReview: PetReview): (Option[PetReview], Response) = + ## Add a pet review (tests _id field mapping) + httpClient.headers["Content-Type"] = "application/json" + + let response = httpClient.post(basepath & "/comments", $(%petReview)) + constructResult[PetReview](response) + + +proc createPetAlert*(httpClient: HttpClient, petAlert: PetAlert): (Option[PetAlert], Response) = + ## Create pet alert + httpClient.headers["Content-Type"] = "application/json" + + let response = httpClient.post(basepath & "/notifications", $(%petAlert)) + constructResult[PetAlert](response) + + proc deletePet*(httpClient: HttpClient, petId: int64, apiKey: string): Response = ## Deletes a pet httpClient.headers["api_key"] = apiKey @@ -73,6 +95,20 @@ proc findPetsByTags*(httpClient: HttpClient, tags: seq[string]): (Option[seq[Pet constructResult[seq[Pet]](response) +proc getPetAlerts*(httpClient: HttpClient): (Option[seq[PetAlert]], Response) = + ## Get pet alerts (tests integer enum) + + let response = httpClient.get(basepath & "/notifications") + constructResult[seq[PetAlert]](response) + + +proc getPetAuditLogs*(httpClient: HttpClient): (Option[seq[PetAuditLog]], Response) = + ## Get pet audit logs (combined test) + + let response = httpClient.get(basepath & "/audit") + constructResult[seq[PetAuditLog]](response) + + proc getPetById*(httpClient: HttpClient, petId: int64): (Option[Pet], Response) = ## Find pet by ID @@ -80,6 +116,13 @@ proc getPetById*(httpClient: HttpClient, petId: int64): (Option[Pet], Response) constructResult[Pet](response) +proc getPetReviews*(httpClient: HttpClient): (Option[PetReviewsResponse], Response) = + ## Get pet reviews (tests _id field mapping and arrays) + + let response = httpClient.get(basepath & "/comments") + constructResult[PetReviewsResponse](response) + + proc getPetStats*(httpClient: HttpClient): (Option[GetPetStats_200_response], Response) = ## Get pet statistics (tests _200_ response normalization) @@ -87,6 +130,21 @@ proc getPetStats*(httpClient: HttpClient): (Option[GetPetStats_200_response], Re constructResult[GetPetStats_200_response](response) +proc markIgnored*(httpClient: HttpClient, ignoredResponse: IgnoredResponse): (Option[IgnoredResponse], Response) = + ## Mark as ignored (tests inline enum) + httpClient.headers["Content-Type"] = "application/json" + + let response = httpClient.post(basepath & "/ignored", $(%ignoredResponse)) + constructResult[IgnoredResponse](response) + + +proc searchPetReviews*(httpClient: HttpClient): (Option[GetPetReviews_200_response], Response) = + ## Search pet reviews (tests anyOf with underscores) + + let response = httpClient.get(basepath & "/comments/search") + constructResult[GetPetReviews_200_response](response) + + proc unfavoritePet*(httpClient: HttpClient, petId: int64, unfavoritePetRequest: UnfavoritePetRequest): (Option[GetPetStats_200_response], Response) = ## Remove pet from favorites (tests DELETE with body) httpClient.headers["Content-Type"] = "application/json" diff --git a/samples/client/petstore/nim/petstore/models/model_get_pet_reviews200response.nim b/samples/client/petstore/nim/petstore/models/model_get_pet_reviews200response.nim new file mode 100644 index 000000000000..3dc550d70a8f --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_get_pet_reviews200response.nim @@ -0,0 +1,44 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + +import model_api_response +import model_get_pet_reviews_response_with_presence + +# AnyOf type +type GetPetReviews200responseKind* {.pure.} = enum + GetPetReviewsResponseWithPresenceVariant + ApiResponseVariant + +type GetPetReviews200response* = object + ## + case kind*: GetPetReviews200responseKind + of GetPetReviews200responseKind.GetPetReviewsResponseWithPresenceVariant: + GetPetReviews_Response_WithPresenceValue*: GetPetReviewsResponseWithPresence + of GetPetReviews200responseKind.ApiResponseVariant: + ApiResponseValue*: ApiResponse + +proc to*(node: JsonNode, T: typedesc[GetPetReviews200response]): GetPetReviews200response = + ## Custom deserializer for anyOf type - tries each variant + try: + return GetPetReviews200response(kind: GetPetReviews200responseKind.GetPetReviewsResponseWithPresenceVariant, GetPetReviews_Response_WithPresenceValue: to(node, GetPetReviewsResponseWithPresence)) + except Exception as e: + when defined(debug): + echo "Failed to deserialize as GetPetReviewsResponseWithPresence: ", e.msg + try: + return GetPetReviews200response(kind: GetPetReviews200responseKind.ApiResponseVariant, ApiResponseValue: to(node, ApiResponse)) + except Exception as e: + when defined(debug): + echo "Failed to deserialize as ApiResponse: ", e.msg + raise newException(ValueError, "Unable to deserialize into any variant of GetPetReviews200response. JSON: " & $node) + diff --git a/samples/client/petstore/nim/petstore/models/model_get_pet_reviews_response_with_presence.nim b/samples/client/petstore/nim/petstore/models/model_get_pet_reviews_response_with_presence.nim new file mode 100644 index 000000000000..5b497629851e --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_get_pet_reviews_response_with_presence.nim @@ -0,0 +1,19 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + + +type GetPetReviewsResponseWithPresence* = object + ## + review*: Option[string] + diff --git a/samples/client/petstore/nim/petstore/models/model_ignored_response.nim b/samples/client/petstore/nim/petstore/models/model_ignored_response.nim new file mode 100644 index 000000000000..e0d37705a7cf --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_ignored_response.nim @@ -0,0 +1,55 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + + +type Note* {.pure.} = enum + `0` + `1` + `2` + `3` + +type IgnoredResponse* = object + ## + reason*: Option[string] + note*: Option[Note] ## Inline enum that should use enum type name not base type + +func `%`*(v: Note): JsonNode = + result = case v: + of Note.`0`: %"0" + of Note.`1`: %"1" + of Note.`2`: %"2" + of Note.`3`: %"3" +func `$`*(v: Note): string = + result = case v: + of Note.`0`: $("0") + of Note.`1`: $("1") + of Note.`2`: $("2") + of Note.`3`: $("3") + +proc to*(node: JsonNode, T: typedesc[Note]): Note = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum Note, got " & $node.kind) + let strVal = node.getStr() + case strVal: + of $("0"): + return Note.`0` + of $("1"): + return Note.`1` + of $("2"): + return Note.`2` + of $("3"): + return Note.`3` + else: + raise newException(ValueError, "Invalid enum value for Note: " & strVal) + diff --git a/samples/client/petstore/nim/petstore/models/model_order.nim b/samples/client/petstore/nim/petstore/models/model_order.nim index 0a710c58d574..279cdb825091 100644 --- a/samples/client/petstore/nim/petstore/models/model_order.nim +++ b/samples/client/petstore/nim/petstore/models/model_order.nim @@ -24,7 +24,7 @@ type Order* = object petId*: Option[int64] quantity*: Option[int] shipDate*: Option[string] - status*: Status ## Order Status + status*: Option[Status] ## Order Status complete*: Option[bool] func `%`*(v: Status): JsonNode = @@ -32,10 +32,23 @@ func `%`*(v: Status): JsonNode = of Status.Placed: %"placed" of Status.Approved: %"approved" of Status.Delivered: %"delivered" - func `$`*(v: Status): string = result = case v: of Status.Placed: $("placed") of Status.Approved: $("approved") of Status.Delivered: $("delivered") +proc to*(node: JsonNode, T: typedesc[Status]): Status = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum Status, got " & $node.kind) + let strVal = node.getStr() + case strVal: + of $("placed"): + return Status.Placed + of $("approved"): + return Status.Approved + of $("delivered"): + return Status.Delivered + else: + raise newException(ValueError, "Invalid enum value for Status: " & strVal) + diff --git a/samples/client/petstore/nim/petstore/models/model_pet.nim b/samples/client/petstore/nim/petstore/models/model_pet.nim index e69d9733512e..5f2a47dcde98 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet.nim @@ -27,17 +27,30 @@ type Pet* = object name*: string photoUrls*: seq[string] tags*: Option[seq[Tag]] - status*: Status ## pet status in the store + status*: Option[Status] ## pet status in the store func `%`*(v: Status): JsonNode = result = case v: of Status.Available: %"available" of Status.Pending: %"pending" of Status.Sold: %"sold" - func `$`*(v: Status): string = result = case v: of Status.Available: $("available") of Status.Pending: $("pending") of Status.Sold: $("sold") +proc to*(node: JsonNode, T: typedesc[Status]): Status = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum Status, got " & $node.kind) + let strVal = node.getStr() + case strVal: + of $("available"): + return Status.Available + of $("pending"): + return Status.Pending + of $("sold"): + return Status.Sold + else: + raise newException(ValueError, "Invalid enum value for Status: " & strVal) + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_alert.nim b/samples/client/petstore/nim/petstore/models/model_pet_alert.nim new file mode 100644 index 000000000000..e63f46252cb9 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_alert.nim @@ -0,0 +1,22 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + +import model_pet_alert_level + +type PetAlert* = object + ## + id*: Option[string] + level*: Option[PetAlertLevel] + message*: Option[string] + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_alert_level.nim b/samples/client/petstore/nim/petstore/models/model_pet_alert_level.nim new file mode 100644 index 000000000000..2ad91a3a6b01 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_alert_level.nim @@ -0,0 +1,55 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + + +type PetAlertLevel* {.pure.} = enum + `0` + `1` + `2` + `10` + `99` + +func `%`*(v: PetAlertLevel): JsonNode = + result = case v: + of PetAlertLevel.`0`: %(0) + of PetAlertLevel.`1`: %(1) + of PetAlertLevel.`2`: %(2) + of PetAlertLevel.`10`: %(10) + of PetAlertLevel.`99`: %(99) + +func `$`*(v: PetAlertLevel): string = + result = case v: + of PetAlertLevel.`0`: $(0) + of PetAlertLevel.`1`: $(1) + of PetAlertLevel.`2`: $(2) + of PetAlertLevel.`10`: $(10) + of PetAlertLevel.`99`: $(99) +proc to*(node: JsonNode, T: typedesc[PetAlertLevel]): PetAlertLevel = + if node.kind != JInt: + raise newException(ValueError, "Expected integer for enum PetAlertLevel, got " & $node.kind) + let intVal = node.getInt() + case intVal: + of 0: + return PetAlertLevel.`0` + of 1: + return PetAlertLevel.`1` + of 2: + return PetAlertLevel.`2` + of 10: + return PetAlertLevel.`10` + of 99: + return PetAlertLevel.`99` + else: + raise newException(ValueError, "Invalid enum value for PetAlertLevel: " & $intVal) + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_audit_log.nim b/samples/client/petstore/nim/petstore/models/model_pet_audit_log.nim new file mode 100644 index 000000000000..b9443806dbcd --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_audit_log.nim @@ -0,0 +1,121 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + +import model_pet_alert_level +import model_pet_review + +type Action* {.pure.} = enum + Create + Update + Delete + +type `From`* {.pure.} = enum + Api + Web + Mobile + +type PetAuditLog* = object + ## + id*: Option[string] ## Audit log unique identifier + action*: Option[Action] + priority*: Option[PetAlertLevel] + `from`*: Option[`From`] + reviews*: Option[seq[PetReview]] + +func `%`*(v: Action): JsonNode = + result = case v: + of Action.Create: %"create" + of Action.Update: %"update" + of Action.Delete: %"delete" +func `$`*(v: Action): string = + result = case v: + of Action.Create: $("create") + of Action.Update: $("update") + of Action.Delete: $("delete") + +proc to*(node: JsonNode, T: typedesc[Action]): Action = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum Action, got " & $node.kind) + let strVal = node.getStr() + case strVal: + of $("create"): + return Action.Create + of $("update"): + return Action.Update + of $("delete"): + return Action.Delete + else: + raise newException(ValueError, "Invalid enum value for Action: " & strVal) + +func `%`*(v: `From`): JsonNode = + result = case v: + of `From`.Api: %"api" + of `From`.Web: %"web" + of `From`.Mobile: %"mobile" +func `$`*(v: `From`): string = + result = case v: + of `From`.Api: $("api") + of `From`.Web: $("web") + of `From`.Mobile: $("mobile") + +proc to*(node: JsonNode, T: typedesc[`From`]): `From` = + if node.kind != JString: + raise newException(ValueError, "Expected string for enum `From`, got " & $node.kind) + let strVal = node.getStr() + case strVal: + of $("api"): + return `From`.Api + of $("web"): + return `From`.Web + of $("mobile"): + return `From`.Mobile + else: + raise newException(ValueError, "Invalid enum value for `From`: " & strVal) + + +# Custom JSON deserialization for PetAuditLog with custom field names +proc to*(node: JsonNode, T: typedesc[PetAuditLog]): PetAuditLog = + result = PetAuditLog() + if node.kind == JObject: + if node.hasKey("_id") and node["_id"].kind != JNull: + result.id = some(to(node["_id"], typeof(result.id.get()))) + if node.hasKey("action") and node["action"].kind != JNull: + result.action = some(to(node["action"], Action)) + if node.hasKey("priority") and node["priority"].kind != JNull: + result.priority = some(to(node["priority"], typeof(result.priority.get()))) + if node.hasKey("from") and node["from"].kind != JNull: + result.`from` = some(to(node["from"], `From`)) + if node.hasKey("reviews") and node["reviews"].kind != JNull: + # Optional array of types with custom JSON - manually iterate and deserialize + let arrayNode = node["reviews"] + if arrayNode.kind == JArray: + var arr: seq[PetReview] = @[] + for item in arrayNode.items: + arr.add(to(item, PetReview)) + result.reviews = some(arr) + +# Custom JSON serialization for PetAuditLog with custom field names +proc `%`*(obj: PetAuditLog): JsonNode = + result = newJObject() + if obj.id.isSome(): + result["_id"] = %obj.id.get() + if obj.action.isSome(): + result["action"] = %obj.action.get() + if obj.priority.isSome(): + result["priority"] = %obj.priority.get() + if obj.`from`.isSome(): + result["from"] = %obj.`from`.get() + if obj.reviews.isSome(): + result["reviews"] = %obj.reviews.get() + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_priority.nim b/samples/client/petstore/nim/petstore/models/model_pet_priority.nim index 9f6d2cb744a8..7ab3903dd76d 100644 --- a/samples/client/petstore/nim/petstore/models/model_pet_priority.nim +++ b/samples/client/petstore/nim/petstore/models/model_pet_priority.nim @@ -20,13 +20,26 @@ type PetPriority* {.pure.} = enum func `%`*(v: PetPriority): JsonNode = result = case v: - of PetPriority.`0`: %"0" - of PetPriority.`1`: %"1" - of PetPriority.`2`: %"2" + of PetPriority.`0`: %(0) + of PetPriority.`1`: %(1) + of PetPriority.`2`: %(2) func `$`*(v: PetPriority): string = result = case v: - of PetPriority.`0`: $("0") - of PetPriority.`1`: $("1") - of PetPriority.`2`: $("2") + of PetPriority.`0`: $(0) + of PetPriority.`1`: $(1) + of PetPriority.`2`: $(2) +proc to*(node: JsonNode, T: typedesc[PetPriority]): PetPriority = + if node.kind != JInt: + raise newException(ValueError, "Expected integer for enum PetPriority, got " & $node.kind) + let intVal = node.getInt() + case intVal: + of 0: + return PetPriority.`0` + of 1: + return PetPriority.`1` + of 2: + return PetPriority.`2` + else: + raise newException(ValueError, "Invalid enum value for PetPriority: " & $intVal) diff --git a/samples/client/petstore/nim/petstore/models/model_pet_review.nim b/samples/client/petstore/nim/petstore/models/model_pet_review.nim new file mode 100644 index 000000000000..631cbcc3985c --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_review.nim @@ -0,0 +1,48 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + + +type PetReview* = object + ## + id*: Option[string] ## MongoDB style ID field that should be renamed to id in Nim + author*: Option[string] + content*: Option[string] + timestamp*: Option[string] + + +# Custom JSON deserialization for PetReview with custom field names +proc to*(node: JsonNode, T: typedesc[PetReview]): PetReview = + result = PetReview() + if node.kind == JObject: + if node.hasKey("_id") and node["_id"].kind != JNull: + result.id = some(to(node["_id"], typeof(result.id.get()))) + if node.hasKey("author") and node["author"].kind != JNull: + result.author = some(to(node["author"], typeof(result.author.get()))) + if node.hasKey("content") and node["content"].kind != JNull: + result.content = some(to(node["content"], typeof(result.content.get()))) + if node.hasKey("timestamp") and node["timestamp"].kind != JNull: + result.timestamp = some(to(node["timestamp"], typeof(result.timestamp.get()))) + +# Custom JSON serialization for PetReview with custom field names +proc `%`*(obj: PetReview): JsonNode = + result = newJObject() + if obj.id.isSome(): + result["_id"] = %obj.id.get() + if obj.author.isSome(): + result["author"] = %obj.author.get() + if obj.content.isSome(): + result["content"] = %obj.content.get() + if obj.timestamp.isSome(): + result["timestamp"] = %obj.timestamp.get() + diff --git a/samples/client/petstore/nim/petstore/models/model_pet_reviews_response.nim b/samples/client/petstore/nim/petstore/models/model_pet_reviews_response.nim new file mode 100644 index 000000000000..0fc682427719 --- /dev/null +++ b/samples/client/petstore/nim/petstore/models/model_pet_reviews_response.nim @@ -0,0 +1,45 @@ +# +# OpenAPI Petstore +# +# This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. +# The version of the OpenAPI document: 1.0.0 +# +# Generated by: https://openapi-generator.tech +# + +import json +import tables +import marshal +import options + +import model_pet_review + +type PetReviewsResponse* = object + ## + reviews*: Option[seq[PetReview]] + totalCount*: Option[int] + + +# Custom JSON deserialization for PetReviewsResponse with custom field names +proc to*(node: JsonNode, T: typedesc[PetReviewsResponse]): PetReviewsResponse = + result = PetReviewsResponse() + if node.kind == JObject: + if node.hasKey("reviews") and node["reviews"].kind != JNull: + # Optional array of types with custom JSON - manually iterate and deserialize + let arrayNode = node["reviews"] + if arrayNode.kind == JArray: + var arr: seq[PetReview] = @[] + for item in arrayNode.items: + arr.add(to(item, PetReview)) + result.reviews = some(arr) + if node.hasKey("totalCount") and node["totalCount"].kind != JNull: + result.totalCount = some(to(node["totalCount"], typeof(result.totalCount.get()))) + +# Custom JSON serialization for PetReviewsResponse with custom field names +proc `%`*(obj: PetReviewsResponse): JsonNode = + result = newJObject() + if obj.reviews.isSome(): + result["reviews"] = %obj.reviews.get() + if obj.totalCount.isSome(): + result["totalCount"] = %obj.totalCount.get() +