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;
|
||||
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 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;
|
||||
if (sourceOp is SystemOperationViewModel srcSys)
|
||||
{
|
||||
@@ -253,6 +253,25 @@ namespace Nodify.Calculator
|
||||
if (title.StartsWith("GET ")) className = title.Substring(4).Trim();
|
||||
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;
|
||||
|
||||
|
||||
@@ -437,10 +437,23 @@ namespace Nodify.Calculator
|
||||
return;
|
||||
}
|
||||
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))
|
||||
{
|
||||
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}");
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ namespace Nodify.Calculator.Models
|
||||
public string OPType { get; set; } = string.Empty;
|
||||
public List<string> InputNames { get; set; } = new List<string>();
|
||||
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 List<FunctionParameterInfo> FunctionInputs { 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
|
||||
{
|
||||
Title = info.Title,
|
||||
OperationType = info.OPType.ToUpper()
|
||||
OperationType = info.OPType.ToUpper(),
|
||||
ResponseModelClassName = info.ResponseModelClassName ?? string.Empty
|
||||
};
|
||||
var connectorViewModel = new ConnectorViewModel()
|
||||
{
|
||||
@@ -263,14 +264,43 @@ namespace Nodify.Calculator
|
||||
IsInput = false
|
||||
};
|
||||
_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);
|
||||
foreach (var item in input)
|
||||
{
|
||||
item.ConnectorColor = Color.LimeGreen;
|
||||
_o.Input.Add(item);
|
||||
}
|
||||
//_o.Input.AddRange(input);
|
||||
return _o;
|
||||
case OperationType.System:
|
||||
if (info.sysOp == SystemOperations.FUNCTION)
|
||||
|
||||
@@ -356,6 +356,7 @@ namespace Nodify.Calculator
|
||||
{
|
||||
var operations = new List<OperationInfoViewModel>();
|
||||
var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;
|
||||
var createdModels = new HashSet<string>();
|
||||
|
||||
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("");
|
||||
operations.Add(ovmodel);
|
||||
}
|
||||
@@ -385,6 +390,138 @@ namespace Nodify.Calculator
|
||||
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)
|
||||
{
|
||||
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
||||
@@ -397,7 +534,8 @@ namespace Nodify.Calculator
|
||||
Title = node.Title ?? string.Empty,
|
||||
OPType = node.OPType ?? string.Empty,
|
||||
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.ResponseModelClassName = saved.ResponseModelClassName ?? string.Empty;
|
||||
ovmodel.Output.Add("");
|
||||
SwaggerOperations.Add(ovmodel);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user