Implemented Direct model creation when swagger file is imported to the list
This commit is contained in:
@@ -20,5 +20,12 @@ namespace Nodify.Calculator
|
|||||||
get => _operationType;
|
get => _operationType;
|
||||||
set => SetProperty(ref _operationType, value);
|
set => SetProperty(ref _operationType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string _responseModelClassName = string.Empty;
|
||||||
|
public string ResponseModelClassName
|
||||||
|
{
|
||||||
|
get => _responseModelClassName;
|
||||||
|
set => SetProperty(ref _responseModelClassName, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ namespace Nodify.Calculator
|
|||||||
var sourceConnector = (c.Input == splitInput) ? c.Output : c.Input;
|
var sourceConnector = (c.Input == splitInput) ? c.Output : c.Input;
|
||||||
var sourceOp = sourceConnector.Operation;
|
var sourceOp = sourceConnector.Operation;
|
||||||
|
|
||||||
// Determine class name from the source operation title (e.g. "GET ClassName" or "SET ClassName")
|
// Determine class name from the source operation or connector
|
||||||
string className = null;
|
string className = null;
|
||||||
if (sourceOp is SystemOperationViewModel srcSys)
|
if (sourceOp is SystemOperationViewModel srcSys)
|
||||||
{
|
{
|
||||||
@@ -253,6 +253,25 @@ namespace Nodify.Calculator
|
|||||||
if (title.StartsWith("GET ")) className = title.Substring(4).Trim();
|
if (title.StartsWith("GET ")) className = title.Substring(4).Trim();
|
||||||
else if (title.StartsWith("SET ")) className = title.Split(' ').ElementAtOrDefault(1);
|
else if (title.StartsWith("SET ")) className = title.Split(' ').ElementAtOrDefault(1);
|
||||||
}
|
}
|
||||||
|
else if (sourceOp is APIOperationViewModel apiVm)
|
||||||
|
{
|
||||||
|
// API node: get response model class name
|
||||||
|
className = apiVm.ResponseModelClassName;
|
||||||
|
// Strip List<> wrapper if present
|
||||||
|
if (!string.IsNullOrEmpty(className) && className.StartsWith("List<") && className.EndsWith(">"))
|
||||||
|
className = className.Substring(5, className.Length - 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: try to get class name from the source connector's DataType
|
||||||
|
if (string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(sourceConnector.DataType))
|
||||||
|
{
|
||||||
|
var dt = sourceConnector.DataType;
|
||||||
|
if (dt.StartsWith("List<") && dt.EndsWith(">"))
|
||||||
|
dt = dt.Substring(5, dt.Length - 6);
|
||||||
|
// If it's not a primitive type, treat as class name
|
||||||
|
if (!new[] { "string", "int", "double", "bool", "object" }.Contains(dt.ToLower()))
|
||||||
|
className = dt;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(className)) return;
|
if (string.IsNullOrEmpty(className)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -437,10 +437,23 @@ namespace Nodify.Calculator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
OnLogMe?.Invoke($"Starting Execution : {url}");
|
OnLogMe?.Invoke($"Starting Execution : {url}");
|
||||||
var res = GetResponse(url, "get");
|
var httpMethod = (op is APIOperationViewModel apiVm) ? apiVm.OperationType?.ToLower() ?? "get" : "get";
|
||||||
|
var res = GetResponse(url, httpMethod);
|
||||||
if (!string.IsNullOrEmpty(res))
|
if (!string.IsNullOrEmpty(res))
|
||||||
{
|
{
|
||||||
outputs.Add(op.NodeId, res);
|
outputs[op.NodeId] = res;
|
||||||
|
|
||||||
|
// Auto-parse response to model if ResponseModelClassName is specified
|
||||||
|
if (op is APIOperationViewModel apiOp && !string.IsNullOrEmpty(apiOp.ResponseModelClassName))
|
||||||
|
{
|
||||||
|
var modelClassName = apiOp.ResponseModelClassName;
|
||||||
|
bool isList = modelClassName.StartsWith("List<") && modelClassName.EndsWith(">");
|
||||||
|
var innerClassName = isList ? modelClassName.Substring(5, modelClassName.Length - 6) : modelClassName;
|
||||||
|
|
||||||
|
// Store the parsed JSON as variable keyed by node ID for downstream SET/Split nodes
|
||||||
|
variables[op.NodeId] = res;
|
||||||
|
OnLogMe?.Invoke($"Response auto-parsed as {modelClassName}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OnLogMe?.Invoke($"Response Result : {res}");
|
OnLogMe?.Invoke($"Response Result : {res}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ namespace Nodify.Calculator.Models
|
|||||||
public string OPType { get; set; } = string.Empty;
|
public string OPType { get; set; } = string.Empty;
|
||||||
public List<string> InputNames { get; set; } = new List<string>();
|
public List<string> InputNames { get; set; } = new List<string>();
|
||||||
public string SwaggerFileName { get; set; } = string.Empty;
|
public string SwaggerFileName { get; set; } = string.Empty;
|
||||||
|
public string ResponseModelClassName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,5 +35,6 @@ namespace Nodify.Calculator
|
|||||||
public bool IsFunction { get; set; }
|
public bool IsFunction { get; set; }
|
||||||
public List<FunctionParameterInfo> FunctionInputs { get; set; } = new List<FunctionParameterInfo>();
|
public List<FunctionParameterInfo> FunctionInputs { get; set; } = new List<FunctionParameterInfo>();
|
||||||
public List<FunctionParameterInfo> FunctionOutputs { get; set; } = new List<FunctionParameterInfo>();
|
public List<FunctionParameterInfo> FunctionOutputs { get; set; } = new List<FunctionParameterInfo>();
|
||||||
|
public string ResponseModelClassName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ namespace Nodify.Calculator
|
|||||||
var _o = new APIOperationViewModel
|
var _o = new APIOperationViewModel
|
||||||
{
|
{
|
||||||
Title = info.Title,
|
Title = info.Title,
|
||||||
OperationType = info.OPType.ToUpper()
|
OperationType = info.OPType.ToUpper(),
|
||||||
|
ResponseModelClassName = info.ResponseModelClassName ?? string.Empty
|
||||||
};
|
};
|
||||||
var connectorViewModel = new ConnectorViewModel()
|
var connectorViewModel = new ConnectorViewModel()
|
||||||
{
|
{
|
||||||
@@ -263,14 +264,43 @@ namespace Nodify.Calculator
|
|||||||
IsInput = false
|
IsInput = false
|
||||||
};
|
};
|
||||||
_o.Output.Add(connectorViewModel2);
|
_o.Output.Add(connectorViewModel2);
|
||||||
_o.Output.Add(new ConnectorViewModel { ConnectorColor = Color.LimeGreen });
|
|
||||||
|
// Add typed response output based on ResponseModelClassName
|
||||||
|
if (!string.IsNullOrEmpty(info.ResponseModelClassName))
|
||||||
|
{
|
||||||
|
var responseClassName = info.ResponseModelClassName;
|
||||||
|
bool isList = responseClassName.StartsWith("List<") && responseClassName.EndsWith(">");
|
||||||
|
var innerType = isList ? responseClassName.Substring(5, responseClassName.Length - 6) : responseClassName;
|
||||||
|
|
||||||
|
// Square connector for model output
|
||||||
|
_o.Output.Add(new ConnectorViewModel
|
||||||
|
{
|
||||||
|
Title = responseClassName,
|
||||||
|
IsInput = false,
|
||||||
|
Shape = ConnectorShape.Square,
|
||||||
|
ConnectorColor = Color.MediumPurple,
|
||||||
|
DataType = responseClassName.ToLower()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Default: generic object output
|
||||||
|
_o.Output.Add(new ConnectorViewModel
|
||||||
|
{
|
||||||
|
Title = "Response",
|
||||||
|
IsInput = false,
|
||||||
|
Shape = ConnectorShape.Circle,
|
||||||
|
ConnectorColor = Color.LimeGreen,
|
||||||
|
DataType = "object"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_o.Input.Add(connectorViewModel);
|
_o.Input.Add(connectorViewModel);
|
||||||
foreach (var item in input)
|
foreach (var item in input)
|
||||||
{
|
{
|
||||||
item.ConnectorColor = Color.LimeGreen;
|
item.ConnectorColor = Color.LimeGreen;
|
||||||
_o.Input.Add(item);
|
_o.Input.Add(item);
|
||||||
}
|
}
|
||||||
//_o.Input.AddRange(input);
|
|
||||||
return _o;
|
return _o;
|
||||||
case OperationType.System:
|
case OperationType.System:
|
||||||
if (info.sysOp == SystemOperations.FUNCTION)
|
if (info.sysOp == SystemOperations.FUNCTION)
|
||||||
|
|||||||
@@ -356,6 +356,7 @@ namespace Nodify.Calculator
|
|||||||
{
|
{
|
||||||
var operations = new List<OperationInfoViewModel>();
|
var operations = new List<OperationInfoViewModel>();
|
||||||
var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;
|
var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;
|
||||||
|
var createdModels = new HashSet<string>();
|
||||||
|
|
||||||
foreach (var path in openApiDocument.Paths)
|
foreach (var path in openApiDocument.Paths)
|
||||||
{
|
{
|
||||||
@@ -377,6 +378,10 @@ namespace Nodify.Calculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract response model from 200/201 response schema
|
||||||
|
var responseModelName = ExtractResponseModel(method.Value, path.Key, method.Key, createdModels);
|
||||||
|
ovmodel.ResponseModelClassName = responseModelName;
|
||||||
|
|
||||||
ovmodel.Output.Add("");
|
ovmodel.Output.Add("");
|
||||||
operations.Add(ovmodel);
|
operations.Add(ovmodel);
|
||||||
}
|
}
|
||||||
@@ -385,6 +390,138 @@ namespace Nodify.Calculator
|
|||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ExtractResponseModel(OpenApiOperation operation, string path, string httpMethod, HashSet<string> createdModels)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check 200 and 201 responses for schema
|
||||||
|
foreach (var statusCode in new[] { "200", "201", "default" })
|
||||||
|
{
|
||||||
|
if (!operation.Responses.ContainsKey(statusCode))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var response = operation.Responses[statusCode];
|
||||||
|
|
||||||
|
// Try OpenAPI 2.0 style (response.Schema) first, then OpenAPI 3.0 (response.Content)
|
||||||
|
NJsonSchema.JsonSchema schema = response.Schema;
|
||||||
|
if (schema == null && response.Content != null && response.Content.Count > 0)
|
||||||
|
{
|
||||||
|
// OpenAPI 3.0: look for application/json or take first content entry
|
||||||
|
if (response.Content.TryGetValue("application/json", out var jsonContent))
|
||||||
|
schema = jsonContent.Schema;
|
||||||
|
else
|
||||||
|
schema = response.Content.Values.FirstOrDefault()?.Schema;
|
||||||
|
}
|
||||||
|
if (schema == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Resolve $ref references
|
||||||
|
var actualSchema = schema.ActualSchema ?? schema;
|
||||||
|
|
||||||
|
// Resolve the actual item schema for arrays
|
||||||
|
var targetSchema = actualSchema;
|
||||||
|
bool isList = false;
|
||||||
|
if (actualSchema.Type == NJsonSchema.JsonObjectType.Array && actualSchema.Item != null)
|
||||||
|
{
|
||||||
|
targetSchema = actualSchema.Item.ActualSchema ?? actualSchema.Item;
|
||||||
|
isList = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve again in case of nested $ref
|
||||||
|
targetSchema = targetSchema.ActualSchema ?? targetSchema;
|
||||||
|
|
||||||
|
// Get or derive class name from schema title, definition name, or path
|
||||||
|
var className = !string.IsNullOrEmpty(targetSchema.Title)
|
||||||
|
? targetSchema.Title
|
||||||
|
: !string.IsNullOrEmpty(targetSchema.DocumentPath) ? System.IO.Path.GetFileNameWithoutExtension(targetSchema.DocumentPath)
|
||||||
|
: DeriveClassName(path, httpMethod);
|
||||||
|
|
||||||
|
// Also try to get name from the schema's Id (definition key)
|
||||||
|
if (string.IsNullOrEmpty(className) || className == "swagger" || className == "openapi")
|
||||||
|
className = DeriveClassName(path, httpMethod);
|
||||||
|
|
||||||
|
className = SanitizeClassName(className);
|
||||||
|
|
||||||
|
// Use ActualProperties which resolves inherited/allOf properties
|
||||||
|
var props = targetSchema.ActualProperties;
|
||||||
|
if (string.IsNullOrEmpty(className) || props == null || props.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Create model .cs file if not already created
|
||||||
|
if (createdModels.Add(className))
|
||||||
|
{
|
||||||
|
GenerateModelFromSchema(className, targetSchema);
|
||||||
|
// Add to AvailableModels
|
||||||
|
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 { /* Response schema parsing is best-effort */ }
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateModelFromSchema(string className, NJsonSchema.JsonSchema schema)
|
||||||
|
{
|
||||||
|
var customModelDir = "CustomModels";
|
||||||
|
Directory.CreateDirectory(customModelDir);
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
sb.AppendLine($"public class {className}");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
// Use ActualProperties to resolve $ref and allOf/inheritance
|
||||||
|
var props = schema.ActualProperties != null && schema.ActualProperties.Count > 0
|
||||||
|
? (IEnumerable<KeyValuePair<string, NJsonSchema.JsonSchemaProperty>>)schema.ActualProperties
|
||||||
|
: schema.Properties;
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
var propType = MapJsonSchemaType(prop.Value);
|
||||||
|
var propName = Executor.ToPascalCase(prop.Key);
|
||||||
|
sb.AppendLine($"\tpublic {propType} {propName} {{ get; set; }}");
|
||||||
|
}
|
||||||
|
sb.AppendLine("}");
|
||||||
|
File.WriteAllText(Path.Combine(customModelDir, $"{className}.cs"), sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MapJsonSchemaType(NJsonSchema.JsonSchemaProperty prop)
|
||||||
|
{
|
||||||
|
// Resolve the actual schema in case of $ref
|
||||||
|
var actual = prop.ActualSchema ?? prop;
|
||||||
|
var type = actual.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";
|
||||||
|
if (type.HasFlag(NJsonSchema.JsonObjectType.Array)) return "List<object>";
|
||||||
|
if (type.HasFlag(NJsonSchema.JsonObjectType.Object)) return "object";
|
||||||
|
// If type is None but has properties, it's likely an object via $ref
|
||||||
|
if (type == NJsonSchema.JsonObjectType.None && actual.Properties.Count > 0) return "object";
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DeriveClassName(string path, string httpMethod)
|
||||||
|
{
|
||||||
|
// e.g. /api/users/{id} + get -> GetUsersResponse
|
||||||
|
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Where(s => !s.StartsWith("{"))
|
||||||
|
.Select(s => Executor.ToPascalCase(s));
|
||||||
|
return $"{Executor.ToPascalCase(httpMethod)}{string.Join("", segments)}Response";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeClassName(string name)
|
||||||
|
{
|
||||||
|
return new string(name.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
private void SaveSwaggerNodesToDb(List<OperationInfoViewModel> nodes, string swaggerFileName)
|
private void SaveSwaggerNodesToDb(List<OperationInfoViewModel> nodes, string swaggerFileName)
|
||||||
{
|
{
|
||||||
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
||||||
@@ -397,7 +534,8 @@ namespace Nodify.Calculator
|
|||||||
Title = node.Title ?? string.Empty,
|
Title = node.Title ?? string.Empty,
|
||||||
OPType = node.OPType ?? string.Empty,
|
OPType = node.OPType ?? string.Empty,
|
||||||
InputNames = new List<string>(node.Input),
|
InputNames = new List<string>(node.Input),
|
||||||
SwaggerFileName = swaggerFileName
|
SwaggerFileName = swaggerFileName,
|
||||||
|
ResponseModelClassName = node.ResponseModelClassName ?? string.Empty
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,6 +561,7 @@ namespace Nodify.Calculator
|
|||||||
ovmodel.Input.Add(inputName);
|
ovmodel.Input.Add(inputName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ovmodel.ResponseModelClassName = saved.ResponseModelClassName ?? string.Empty;
|
||||||
ovmodel.Output.Add("");
|
ovmodel.Output.Add("");
|
||||||
SwaggerOperations.Add(ovmodel);
|
SwaggerOperations.Add(ovmodel);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user