Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/generators/nim.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,19 +34,27 @@ 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}}
let multipart_data = newMultipartData({
{{#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}}
181 changes: 171 additions & 10 deletions modules/openapi-generator/src/main/resources/nim-client/model.mustache
Original file line number Diff line number Diff line change
@@ -1,23 +1,184 @@
{{>header}}
import json
import tables
import marshal
import options

{{#imports}}import {{import}}
{{/imports}}{{#models}}{{#model}}{{#vars}}{{#isEnum}}
{{/imports}}{{#models}}{{#model}}{{#isEnum}}
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}}
{{{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: 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: 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}}
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 =
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}}
{{/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}}{{/model}}{{/models}}
of {{{enumName}}}.{{{name}}}: $({{{value}}}){{/enumVars}}{{/allowableValues}}
{{#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}}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading