diff --git a/Examples/Nodify.Calculator/APIOperationViewModel.cs b/Examples/Nodify.Calculator/APIOperationViewModel.cs index 6d25745..4ad3b82 100644 --- a/Examples/Nodify.Calculator/APIOperationViewModel.cs +++ b/Examples/Nodify.Calculator/APIOperationViewModel.cs @@ -27,5 +27,12 @@ namespace Nodify.Calculator get => _responseModelClassName; set => SetProperty(ref _responseModelClassName, value); } + + private string _requestBodyModelClassName = string.Empty; + public string RequestBodyModelClassName + { + get => _requestBodyModelClassName; + set => SetProperty(ref _requestBodyModelClassName, value); + } } } diff --git a/Examples/Nodify.Calculator/Models/SaveGraphModel.cs b/Examples/Nodify.Calculator/Models/SaveGraphModel.cs index d4e2e13..68f1de0 100644 --- a/Examples/Nodify.Calculator/Models/SaveGraphModel.cs +++ b/Examples/Nodify.Calculator/Models/SaveGraphModel.cs @@ -26,6 +26,7 @@ namespace Nodify.Calculator.Models // API node properties public string OPType { get; set; } = string.Empty; public string ResponseModelClassName { get; set; } = string.Empty; + public string RequestBodyModelClassName { get; set; } = string.Empty; // System node properties public string SystemOp { get; set; } = string.Empty; diff --git a/Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs b/Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs index 2c8efc0..ce75ee7 100644 --- a/Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs +++ b/Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs @@ -10,6 +10,7 @@ namespace Nodify.Calculator.Models public List InputNames { get; set; } = new List(); public string SwaggerFileName { get; set; } = string.Empty; public string ResponseModelClassName { get; set; } = string.Empty; + public string RequestBodyModelClassName { get; set; } = string.Empty; public string SwaggerGroup { get; set; } = string.Empty; } } diff --git a/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs b/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs index f9c5022..0af5d7e 100644 --- a/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs +++ b/Examples/Nodify.Calculator/NodeHandlers/OtherNodeHandlers.cs @@ -76,7 +76,8 @@ namespace Nodify.Calculator.NodeHandlers { Title = info.Title, OperationType = (info.OPType ?? "GET").ToUpper(), - ResponseModelClassName = info.ResponseModelClassName ?? string.Empty + ResponseModelClassName = info.ResponseModelClassName ?? string.Empty, + RequestBodyModelClassName = info.RequestBodyModelClassName ?? string.Empty }; // Flow connectors @@ -109,7 +110,35 @@ namespace Nodify.Calculator.NodeHandlers }); } - // Data inputs + // Request body input (for POST/PUT/PATCH) + var httpMethod = (info.OPType ?? "").ToUpper(); + if (httpMethod == "POST" || httpMethod == "PUT" || httpMethod == "PATCH") + { + if (!string.IsNullOrEmpty(info.RequestBodyModelClassName)) + { + var bc = info.RequestBodyModelClassName; + bool isList = bc.StartsWith("List<") && bc.EndsWith(">"); + op.Input.Add(new ConnectorViewModel + { + Title = $"Body ({bc})", + Shape = isList ? ConnectorShape.Grid : ConnectorShape.Square, + ConnectorColor = isList ? Color.MediumSpringGreen : Color.MediumPurple, + DataType = bc + }); + } + else + { + op.Input.Add(new ConnectorViewModel + { + Title = "Body", + Shape = ConnectorShape.Circle, + ConnectorColor = Color.LimeGreen, + DataType = "object" + }); + } + } + + // Other data inputs (query/path params) foreach (var label in info.Input) { op.Input.Add(new ConnectorViewModel { Title = label, ConnectorColor = Color.LimeGreen }); @@ -124,12 +153,16 @@ namespace Nodify.Calculator.NodeHandlers Title = data.Title, OPType = data.OPType?.ToLower() ?? "get", Type = OperationType.API, - ResponseModelClassName = data.ResponseModelClassName ?? string.Empty + ResponseModelClassName = data.ResponseModelClassName ?? string.Empty, + RequestBodyModelClassName = data.RequestBodyModelClassName ?? string.Empty }; + // Restore only query/path param inputs (skip flow triangles and body connectors) foreach (var ic in data.InputConnectors) { - if (ic.Shape != "Triangle") - info.Input.Add(ic.Title); + if (ic.Shape == "Triangle") continue; + if (ic.Shape == "Square" || ic.Shape == "Grid") continue; // body model connector + if ((ic.Title ?? "").StartsWith("Body")) continue; + info.Input.Add(ic.Title); } return Create(info); } @@ -141,6 +174,7 @@ namespace Nodify.Calculator.NodeHandlers { data.OPType = api.OperationType; data.ResponseModelClassName = api.ResponseModelClassName; + data.RequestBodyModelClassName = api.RequestBodyModelClassName; } } } diff --git a/Examples/Nodify.Calculator/OperationInfoViewModel.cs b/Examples/Nodify.Calculator/OperationInfoViewModel.cs index d784c0e..09dbc56 100644 --- a/Examples/Nodify.Calculator/OperationInfoViewModel.cs +++ b/Examples/Nodify.Calculator/OperationInfoViewModel.cs @@ -36,6 +36,7 @@ namespace Nodify.Calculator public List FunctionInputs { get; set; } = new List(); public List FunctionOutputs { get; set; } = new List(); public string ResponseModelClassName { get; set; } = string.Empty; + public string RequestBodyModelClassName { get; set; } = string.Empty; public string SwaggerGroup { get; set; } = string.Empty; } } diff --git a/Examples/Nodify.Calculator/OperationsMenuViewModel.cs b/Examples/Nodify.Calculator/OperationsMenuViewModel.cs index cb934f5..c41fa72 100644 --- a/Examples/Nodify.Calculator/OperationsMenuViewModel.cs +++ b/Examples/Nodify.Calculator/OperationsMenuViewModel.cs @@ -417,12 +417,24 @@ namespace Nodify.Calculator var addedParams = new HashSet(); foreach (var parameter in method.Value.Parameters) { + // Skip body parameters — they are handled by ExtractRequestBodyModel + if (parameter.Kind == NSwag.OpenApiParameterKind.Body) + continue; + if (addedParams.Add(parameter.Name)) { ovmodel.Input.Add(parameter.Name); } } + // Extract request body model for POST/PUT/PATCH + var httpMethodLower = method.Key.ToLower(); + if (httpMethodLower == "post" || httpMethodLower == "put" || httpMethodLower == "patch") + { + var bodyModelName = ExtractRequestBodyModel(method.Value, path.Key, method.Key, createdModels); + ovmodel.RequestBodyModelClassName = bodyModelName; + } + // Extract response model from 200/201 response schema var responseModelName = ExtractResponseModel(method.Value, path.Key, method.Key, createdModels); ovmodel.ResponseModelClassName = responseModelName; @@ -516,6 +528,85 @@ namespace Nodify.Calculator return string.Empty; } + private string ExtractRequestBodyModel(NSwag.OpenApiOperation operation, string path, string httpMethod, HashSet createdModels) + { + try + { + NJsonSchema.JsonSchema bodySchema = null; + + // OpenAPI 3.0: operation.RequestBody.Content + if (operation.RequestBody?.Content != null && operation.RequestBody.Content.Count > 0) + { + if (operation.RequestBody.Content.TryGetValue("application/json", out var jsonContent)) + bodySchema = jsonContent.Schema; + else + bodySchema = operation.RequestBody.Content.Values.FirstOrDefault()?.Schema; + } + + // OpenAPI 2.0 (Swagger): body parameter with In == Body + if (bodySchema == null) + { + var bodyParam = operation.Parameters.FirstOrDefault( + p => p.Kind == NSwag.OpenApiParameterKind.Body); + if (bodyParam != null) + bodySchema = bodyParam.Schema ?? bodyParam.ActualSchema; + } + + if (bodySchema == null) return string.Empty; + + var actualSchema = bodySchema.ActualSchema ?? bodySchema; + + // Resolve arrays + var targetSchema = actualSchema; + bool isList = false; + if (actualSchema.Type == NJsonSchema.JsonObjectType.Array && actualSchema.Item != null) + { + targetSchema = actualSchema.Item.ActualSchema ?? actualSchema.Item; + isList = true; + } + targetSchema = targetSchema.ActualSchema ?? targetSchema; + + // Derive class name + var className = !string.IsNullOrEmpty(targetSchema.Title) + ? targetSchema.Title + : DeriveRequestClassName(path, httpMethod); + + className = SanitizeClassName(className); + + var props = targetSchema.ActualProperties; + if (string.IsNullOrEmpty(className) || props == null || props.Count == 0) + return string.Empty; + + // Generate model file if not already done + if (createdModels.Add(className)) + { + GenerateModelFromSchema(className, targetSchema); + var modelInfo = new OperationInfoViewModel + { + Title = className, + IsModelNode = true, + Type = OperationType.System, + sysOp = SystemOperations.GET_SET, + ClassName = className + }; + AddNewModel(modelInfo); + } + + return isList ? $"List<{className}>" : className; + } + catch { /* Best-effort */ } + + return string.Empty; + } + + private static string DeriveRequestClassName(string path, string httpMethod) + { + var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries) + .Where(s => !s.StartsWith("{")) + .Select(s => Executor.ToPascalCase(s)); + return $"{Executor.ToPascalCase(httpMethod)}{string.Join("", segments)}Request"; + } + private void GenerateModelFromSchema(string className, NJsonSchema.JsonSchema schema) { var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels"); @@ -613,6 +704,7 @@ namespace Nodify.Calculator InputNames = new List(node.Input), SwaggerFileName = swaggerFileName, ResponseModelClassName = node.ResponseModelClassName ?? string.Empty, + RequestBodyModelClassName = node.RequestBodyModelClassName ?? string.Empty, SwaggerGroup = node.SwaggerGroup ?? string.Empty }); } @@ -645,6 +737,7 @@ namespace Nodify.Calculator } ovmodel.ResponseModelClassName = saved.ResponseModelClassName ?? string.Empty; + ovmodel.RequestBodyModelClassName = saved.RequestBodyModelClassName ?? string.Empty; ovmodel.Output.Add(""); SwaggerOperations.Add(ovmodel); }