diff --git a/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs index 0af5d7e..e81807d 100644 --- a/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs +++ b/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs @@ -89,14 +89,46 @@ namespace Nodify.Calculator.NodeHandlers { var rc = info.ResponseModelClassName; bool isList = rc.StartsWith("List<") && rc.EndsWith(">"); - op.Output.Add(new ConnectorViewModel + var primitives = new[] { "string", "int", "double", "float", "bool", "decimal", "long", "datetime" }; + var innerType = isList ? rc.Substring(5, rc.Length - 6) : rc; + bool isPrimitive = primitives.Contains(innerType.ToLower()); + + if (isPrimitive && !isList) { - Title = rc, - IsInput = false, - Shape = isList ? ConnectorShape.Grid : ConnectorShape.Square, - ConnectorColor = isList ? Color.MediumSpringGreen : Color.MediumPurple, - DataType = rc - }); + // Primitive response (e.g. int, string, bool) + op.Output.Add(new ConnectorViewModel + { + Title = rc, + IsInput = false, + Shape = ConnectorShape.Circle, + ConnectorColor = ConnectorViewModel.GetColorForType(rc), + DataType = rc.ToLower() + }); + } + else if (isList && isPrimitive) + { + // List of primitives (e.g. List) + op.Output.Add(new ConnectorViewModel + { + Title = rc, + IsInput = false, + Shape = ConnectorShape.Grid, + ConnectorColor = Color.MediumSpringGreen, + DataType = rc + }); + } + else + { + // Model or List + op.Output.Add(new ConnectorViewModel + { + Title = rc, + IsInput = false, + Shape = isList ? ConnectorShape.Grid : ConnectorShape.Square, + ConnectorColor = isList ? Color.MediumSpringGreen : Color.MediumPurple, + DataType = rc + }); + } } else { diff --git a/Examples/Nodify.Calculator/OperationsMenuViewModel.cs b/Examples/Nodify.Calculator/OperationsMenuViewModel.cs index c41fa72..fe92cc9 100644 --- a/Examples/Nodify.Calculator/OperationsMenuViewModel.cs +++ b/Examples/Nodify.Calculator/OperationsMenuViewModel.cs @@ -437,6 +437,11 @@ namespace Nodify.Calculator // Extract response model from 200/201 response schema var responseModelName = ExtractResponseModel(method.Value, path.Key, method.Key, createdModels); + + // Fallback: if no explicit response model, use request body model for POST/PUT/PATCH + if (string.IsNullOrEmpty(responseModelName) && !string.IsNullOrEmpty(ovmodel.RequestBodyModelClassName)) + responseModelName = ovmodel.RequestBodyModelClassName; + ovmodel.ResponseModelClassName = responseModelName; ovmodel.Output.Add(""); @@ -475,6 +480,11 @@ namespace Nodify.Calculator // Resolve $ref references var actualSchema = schema.ActualSchema ?? schema; + // Handle primitive response types (string, int, bool, etc.) + var primitiveType = MapPrimitiveSchemaType(actualSchema); + if (!string.IsNullOrEmpty(primitiveType)) + return primitiveType; + // Resolve the actual item schema for arrays var targetSchema = actualSchema; bool isList = false; @@ -482,6 +492,11 @@ namespace Nodify.Calculator { targetSchema = actualSchema.Item.ActualSchema ?? actualSchema.Item; isList = true; + + // Check if array items are primitive + var itemPrimitive = MapPrimitiveSchemaType(targetSchema.ActualSchema ?? targetSchema); + if (!string.IsNullOrEmpty(itemPrimitive)) + return $"List<{itemPrimitive}>"; } // Resolve again in case of nested $ref @@ -644,6 +659,20 @@ namespace Nodify.Calculator return "string"; } + /// + /// Returns the C# primitive type name if the schema is a simple type (string, int, etc.), + /// or null if it's an object/complex type that needs a model class. + /// + private static string? MapPrimitiveSchemaType(NJsonSchema.JsonSchema schema) + { + var type = schema.Type; + if (type.HasFlag(NJsonSchema.JsonObjectType.String)) return "string"; + if (type.HasFlag(NJsonSchema.JsonObjectType.Integer)) return "int"; + if (type.HasFlag(NJsonSchema.JsonObjectType.Number)) return "double"; + if (type.HasFlag(NJsonSchema.JsonObjectType.Boolean)) return "bool"; + return null; + } + private static string DeriveClassName(string path, string httpMethod) { // e.g. /api/users/{id} + get -> GetUsersResponse diff --git a/Examples/Nodify.Calculator/swagger.json b/Examples/Nodify.Calculator/swagger.json new file mode 100644 index 0000000..de37d65 --- /dev/null +++ b/Examples/Nodify.Calculator/swagger.json @@ -0,0 +1,387 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Simple Pizza Api", + "description": "A simple ASP.NET Core Web Api application with CRUD including Patch that allows you to work with pizza data", + "contact": { + "name": "Kathleen West", + "url": "https://portfolio.katiegirl.net", + "email": "hello.kathleen.west@gmail.com" + }, + "license": { + "name": "MIT License", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "v1" + }, + "paths": { + "/error": { + "get": { + "tags": [ + "Error" + ], + "operationId": "Error", + "responses": { + "200": { + "description": "Success" + } + } + } + }, + "/api/Pizza": { + "get": { + "tags": [ + "Pizza" + ], + "summary": "Get all the pizza model objects in memory", + "operationId": "GetAll", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pizza" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "Pizza" + ], + "summary": "Creates a pizza and automatically assigns a unique identifier (int)", + "operationId": "Create", + "requestBody": { + "description": "(Pizza) pizza model object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pizza" + } + } + } + }, + "responses": { + "201": { + "description": "Created" + } + } + } + }, + "/api/Pizza/{id}": { + "get": { + "tags": [ + "Pizza" + ], + "summary": "Get one specific pizza given a unique identifier (int)", + "operationId": "Get", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "(int) unique identifier of the pizza", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pizza" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "put": { + "tags": [ + "Pizza" + ], + "summary": "Updates a specific pizza given a unique identifier (int), if it exists", + "operationId": "Update", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "(int) unique identifier of the pizza", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "(Pizza) pizza to delete", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pizza" + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Pizza" + ], + "summary": "Updates properties of a specific pizza given a unique identifier (int), if it exists", + "operationId": "Patch", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "(int) unique identifier of the pizza", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "(JsonPatchDocument) patch of operations", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Operation" + } + } + } + } + }, + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Pizza" + ], + "summary": "Delete a specific pizza given a unique identifier, if it exists", + "operationId": "Delete", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "(int) identifier of the pizza", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/errorDemo/{id}": { + "get": { + "tags": [ + "Pizza" + ], + "summary": "Demo of Custom Error Handling for Get Operation", + "operationId": "GetErrorDemo", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "(int) unique identifier of the pizza", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "500": { + "description": "Server Error" + } + } + } + } + }, + "components": { + "schemas": { + "Operation": { + "type": "object", + "properties": { + "value": { + "nullable": true + }, + "path": { + "type": "string", + "nullable": true + }, + "op": { + "type": "string", + "nullable": true + }, + "from": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "Pizza": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Unique Identifier (int) \r\nfor Pizza", + "format": "int32" + }, + "name": { + "type": "string", + "description": "Name of the Pizza (string)", + "nullable": true + }, + "isGlutenFree": { + "type": "boolean", + "description": "Is this Pizza Gluten Free\r\n(bool true --\u003E yes)" + } + }, + "additionalProperties": false, + "description": "Simple Pizza" + }, + "ProblemDetails": { + "type": "object", + "properties": { + "type": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "status": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "detail": { + "type": "string", + "nullable": true + }, + "instance": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": { + + } + } + } + } +} \ No newline at end of file