Added nested level class support for import swagger
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
@@ -356,14 +356,41 @@ namespace Nodify.Calculator
|
||||
// Add property outputs
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var propColor = ConnectorViewModel.GetColorForType(prop.Type);
|
||||
var propType = prop.Type;
|
||||
bool isList = propType.StartsWith("List<") && propType.EndsWith(">");
|
||||
var innerType = isList ? propType.Substring(5, propType.Length - 6) : propType;
|
||||
|
||||
// Check if the inner type is a model (has a .cs file in CustomModels)
|
||||
var primitiveTypes = new[] { "string", "int", "double", "bool", "float", "decimal", "long", "object", "datetime" };
|
||||
bool isModel = !primitiveTypes.Contains(innerType.ToLower())
|
||||
&& File.Exists(Path.Combine(customModelDir, $"{innerType}.cs"));
|
||||
|
||||
ConnectorShape shape;
|
||||
System.Drawing.Color color;
|
||||
|
||||
if (isList)
|
||||
{
|
||||
shape = ConnectorShape.Grid;
|
||||
color = System.Drawing.Color.MediumSpringGreen;
|
||||
}
|
||||
else if (isModel)
|
||||
{
|
||||
shape = ConnectorShape.Square;
|
||||
color = System.Drawing.Color.MediumPurple;
|
||||
}
|
||||
else
|
||||
{
|
||||
shape = ConnectorShape.Circle;
|
||||
color = ConnectorViewModel.GetColorForType(propType);
|
||||
}
|
||||
|
||||
splitOp.Output.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"{prop.Name} ({prop.Type})",
|
||||
Title = $"{prop.Name} ({propType})",
|
||||
IsInput = false,
|
||||
Shape = ConnectorShape.Circle,
|
||||
ConnectorColor = propColor,
|
||||
DataType = prop.Type.ToLower()
|
||||
Shape = shape,
|
||||
ConnectorColor = color,
|
||||
DataType = isModel || isList ? propType : propType.ToLower()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -454,13 +481,39 @@ namespace Nodify.Calculator
|
||||
// Add property inputs
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var propColor = ConnectorViewModel.GetColorForType(prop.Type);
|
||||
var propType = prop.Type;
|
||||
bool isList = propType.StartsWith("List<") && propType.EndsWith(">");
|
||||
var innerType = isList ? propType.Substring(5, propType.Length - 6) : propType;
|
||||
|
||||
var primTypes = new[] { "string", "int", "double", "bool", "float", "decimal", "long", "object", "datetime" };
|
||||
bool isModel = !primTypes.Contains(innerType.ToLower())
|
||||
&& File.Exists(Path.Combine(customModelDir, $"{innerType}.cs"));
|
||||
|
||||
ConnectorShape shape;
|
||||
System.Drawing.Color color;
|
||||
|
||||
if (isList)
|
||||
{
|
||||
shape = ConnectorShape.Grid;
|
||||
color = System.Drawing.Color.MediumSpringGreen;
|
||||
}
|
||||
else if (isModel)
|
||||
{
|
||||
shape = ConnectorShape.Square;
|
||||
color = System.Drawing.Color.MediumPurple;
|
||||
}
|
||||
else
|
||||
{
|
||||
shape = ConnectorShape.Circle;
|
||||
color = ConnectorViewModel.GetColorForType(propType);
|
||||
}
|
||||
|
||||
newObjOp.Input.Add(new ConnectorViewModel
|
||||
{
|
||||
Title = $"{prop.Name} ({prop.Type})",
|
||||
Shape = ConnectorShape.Circle,
|
||||
ConnectorColor = propColor,
|
||||
DataType = prop.Type.ToLower()
|
||||
Title = $"{prop.Name} ({propType})",
|
||||
Shape = shape,
|
||||
ConnectorColor = color,
|
||||
DataType = isModel || isList ? propType : propType.ToLower()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -393,6 +393,7 @@ namespace Nodify.Calculator
|
||||
var operations = new List<OperationInfoViewModel>();
|
||||
var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;
|
||||
var createdModels = new HashSet<string>();
|
||||
var definitions = openApiDocument.Definitions;
|
||||
|
||||
foreach (var path in openApiDocument.Paths)
|
||||
{
|
||||
@@ -431,12 +432,12 @@ namespace Nodify.Calculator
|
||||
var httpMethodLower = method.Key.ToLower();
|
||||
if (httpMethodLower == "post" || httpMethodLower == "put" || httpMethodLower == "patch")
|
||||
{
|
||||
var bodyModelName = ExtractRequestBodyModel(method.Value, path.Key, method.Key, createdModels);
|
||||
var bodyModelName = ExtractRequestBodyModel(method.Value, path.Key, method.Key, createdModels, definitions);
|
||||
ovmodel.RequestBodyModelClassName = bodyModelName;
|
||||
}
|
||||
|
||||
// Extract response model from 200/201 response schema
|
||||
var responseModelName = ExtractResponseModel(method.Value, path.Key, method.Key, createdModels);
|
||||
var responseModelName = ExtractResponseModel(method.Value, path.Key, method.Key, createdModels, definitions);
|
||||
|
||||
// Fallback: if no explicit response model, use request body model for POST/PUT/PATCH
|
||||
if (string.IsNullOrEmpty(responseModelName) && !string.IsNullOrEmpty(ovmodel.RequestBodyModelClassName))
|
||||
@@ -452,7 +453,7 @@ namespace Nodify.Calculator
|
||||
return operations;
|
||||
}
|
||||
|
||||
private string ExtractResponseModel(OpenApiOperation operation, string path, string httpMethod, HashSet<string> createdModels)
|
||||
private string ExtractResponseModel(OpenApiOperation operation, string path, string httpMethod, HashSet<string> createdModels, IDictionary<string, NJsonSchema.JsonSchema> definitions)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -522,7 +523,7 @@ namespace Nodify.Calculator
|
||||
// Create model .cs file if not already created
|
||||
if (createdModels.Add(className))
|
||||
{
|
||||
GenerateModelFromSchema(className, targetSchema);
|
||||
GenerateModelFromSchema(className, targetSchema, createdModels, definitions);
|
||||
// Add to AvailableModels
|
||||
var modelInfo = new OperationInfoViewModel
|
||||
{
|
||||
@@ -543,7 +544,7 @@ namespace Nodify.Calculator
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string ExtractRequestBodyModel(NSwag.OpenApiOperation operation, string path, string httpMethod, HashSet<string> createdModels)
|
||||
private string ExtractRequestBodyModel(NSwag.OpenApiOperation operation, string path, string httpMethod, HashSet<string> createdModels, IDictionary<string, NJsonSchema.JsonSchema> definitions)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -595,7 +596,7 @@ namespace Nodify.Calculator
|
||||
// Generate model file if not already done
|
||||
if (createdModels.Add(className))
|
||||
{
|
||||
GenerateModelFromSchema(className, targetSchema);
|
||||
GenerateModelFromSchema(className, targetSchema, createdModels, definitions);
|
||||
var modelInfo = new OperationInfoViewModel
|
||||
{
|
||||
Title = className,
|
||||
@@ -622,7 +623,7 @@ namespace Nodify.Calculator
|
||||
return $"{Executor.ToPascalCase(httpMethod)}{string.Join("", segments)}Request";
|
||||
}
|
||||
|
||||
private void GenerateModelFromSchema(string className, NJsonSchema.JsonSchema schema)
|
||||
private void GenerateModelFromSchema(string className, NJsonSchema.JsonSchema schema, HashSet<string> createdModels, IDictionary<string, NJsonSchema.JsonSchema> definitions)
|
||||
{
|
||||
var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
|
||||
Directory.CreateDirectory(customModelDir);
|
||||
@@ -635,7 +636,7 @@ namespace Nodify.Calculator
|
||||
: schema.Properties;
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var propType = MapJsonSchemaType(prop.Value);
|
||||
var propType = ResolvePropertyType(prop.Value, createdModels, definitions);
|
||||
var propName = Executor.ToPascalCase(prop.Key);
|
||||
sb.AppendLine($"\tpublic {propType} {propName} {{ get; set; }}");
|
||||
}
|
||||
@@ -643,6 +644,119 @@ namespace Nodify.Calculator
|
||||
File.WriteAllText(Path.Combine(customModelDir, $"{className}.cs"), sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the C# type for a JSON schema property. For $ref objects and arrays of $ref,
|
||||
/// recursively generates the nested model classes and returns the proper class name.
|
||||
/// </summary>
|
||||
private string ResolvePropertyType(NJsonSchema.JsonSchemaProperty prop, HashSet<string> createdModels, IDictionary<string, NJsonSchema.JsonSchema> definitions)
|
||||
{
|
||||
var actual = prop.ActualSchema ?? prop;
|
||||
var type = actual.Type;
|
||||
|
||||
// Primitives
|
||||
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";
|
||||
|
||||
// Array — resolve the item type recursively
|
||||
if (type.HasFlag(NJsonSchema.JsonObjectType.Array))
|
||||
{
|
||||
var itemSchema = actual.Item?.ActualSchema ?? actual.Item;
|
||||
if (itemSchema != null)
|
||||
{
|
||||
var itemType = ResolveSchemaAsType(itemSchema, createdModels, definitions);
|
||||
return $"List<{itemType}>";
|
||||
}
|
||||
return "List<object>";
|
||||
}
|
||||
|
||||
// Object or $ref — resolve the nested model
|
||||
if (type.HasFlag(NJsonSchema.JsonObjectType.Object)
|
||||
|| type == NJsonSchema.JsonObjectType.None)
|
||||
{
|
||||
bool hasNestedProps = (actual.ActualProperties != null && actual.ActualProperties.Count > 0)
|
||||
|| (actual.Properties != null && actual.Properties.Count > 0);
|
||||
if (hasNestedProps)
|
||||
{
|
||||
return ResolveSchemaAsType(actual, createdModels, definitions);
|
||||
}
|
||||
return "object";
|
||||
}
|
||||
|
||||
return "string";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a resolved JSON schema that represents a complex object, determines its class name,
|
||||
/// generates the model file (recursively for nested types), and registers it. Returns the class name.
|
||||
/// </summary>
|
||||
private string ResolveSchemaAsType(NJsonSchema.JsonSchema schema, HashSet<string> createdModels, IDictionary<string, NJsonSchema.JsonSchema> definitions)
|
||||
{
|
||||
var actual = schema.ActualSchema ?? schema;
|
||||
|
||||
// Primitives don't need a model class
|
||||
var primitive = MapPrimitiveSchemaType(actual);
|
||||
if (!string.IsNullOrEmpty(primitive)) return primitive;
|
||||
|
||||
var hasProps = (actual.ActualProperties != null && actual.ActualProperties.Count > 0)
|
||||
|| (actual.Properties != null && actual.Properties.Count > 0);
|
||||
if (!hasProps)
|
||||
return "object";
|
||||
|
||||
// Best approach: look up the schema in the document's Definitions by reference equality
|
||||
string nestedName = null;
|
||||
if (definitions != null)
|
||||
{
|
||||
foreach (var kvp in definitions)
|
||||
{
|
||||
if (ReferenceEquals(kvp.Value, actual) || ReferenceEquals(kvp.Value.ActualSchema, actual))
|
||||
{
|
||||
nestedName = kvp.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try schema Title, Id, or DocumentPath
|
||||
if (string.IsNullOrEmpty(nestedName) && !string.IsNullOrEmpty(actual.Title))
|
||||
nestedName = actual.Title;
|
||||
|
||||
if (string.IsNullOrEmpty(nestedName) && !string.IsNullOrEmpty(actual.Id))
|
||||
nestedName = System.IO.Path.GetFileNameWithoutExtension(actual.Id);
|
||||
|
||||
if (string.IsNullOrEmpty(nestedName) && !string.IsNullOrEmpty(actual.DocumentPath))
|
||||
{
|
||||
var segments = actual.DocumentPath.Split('/');
|
||||
nestedName = segments.LastOrDefault();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(nestedName))
|
||||
return "object";
|
||||
|
||||
nestedName = SanitizeClassName(nestedName);
|
||||
if (string.IsNullOrEmpty(nestedName))
|
||||
return "object";
|
||||
|
||||
// Generate the nested model if not already created
|
||||
if (createdModels.Add(nestedName))
|
||||
{
|
||||
GenerateModelFromSchema(nestedName, actual, createdModels, definitions);
|
||||
var modelInfo = new OperationInfoViewModel
|
||||
{
|
||||
Title = nestedName,
|
||||
IsModelNode = true,
|
||||
Type = OperationType.System,
|
||||
sysOp = SystemOperations.GET_SET,
|
||||
ClassName = nestedName
|
||||
};
|
||||
AddNewModel(modelInfo);
|
||||
}
|
||||
|
||||
return nestedName;
|
||||
}
|
||||
|
||||
// Keep legacy static version for any code that doesn't need recursion
|
||||
private static string MapJsonSchemaType(NJsonSchema.JsonSchemaProperty prop)
|
||||
{
|
||||
// Resolve the actual schema in case of $ref
|
||||
|
||||
@@ -311,6 +311,24 @@
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"AuditHistory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"createdDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Operation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -347,7 +365,10 @@
|
||||
},
|
||||
"isGlutenFree": {
|
||||
"type": "boolean",
|
||||
"description": "Is this Pizza Gluten Free\r\n(bool true --\u003E yes)"
|
||||
"description": "Is this Pizza Gluten Free\r\n(bool true --> yes)"
|
||||
},
|
||||
"auditHistory": {
|
||||
"$ref": "#/components/schemas/AuditHistory"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -378,9 +399,7 @@
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
|
||||
}
|
||||
"additionalProperties": { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user