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
|
// Add property outputs
|
||||||
foreach (var prop in properties)
|
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
|
splitOp.Output.Add(new ConnectorViewModel
|
||||||
{
|
{
|
||||||
Title = $"{prop.Name} ({prop.Type})",
|
Title = $"{prop.Name} ({propType})",
|
||||||
IsInput = false,
|
IsInput = false,
|
||||||
Shape = ConnectorShape.Circle,
|
Shape = shape,
|
||||||
ConnectorColor = propColor,
|
ConnectorColor = color,
|
||||||
DataType = prop.Type.ToLower()
|
DataType = isModel || isList ? propType : propType.ToLower()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,13 +481,39 @@ namespace Nodify.Calculator
|
|||||||
// Add property inputs
|
// Add property inputs
|
||||||
foreach (var prop in properties)
|
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
|
newObjOp.Input.Add(new ConnectorViewModel
|
||||||
{
|
{
|
||||||
Title = $"{prop.Name} ({prop.Type})",
|
Title = $"{prop.Name} ({propType})",
|
||||||
Shape = ConnectorShape.Circle,
|
Shape = shape,
|
||||||
ConnectorColor = propColor,
|
ConnectorColor = color,
|
||||||
DataType = prop.Type.ToLower()
|
DataType = isModel || isList ? propType : propType.ToLower()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -393,6 +393,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>();
|
var createdModels = new HashSet<string>();
|
||||||
|
var definitions = openApiDocument.Definitions;
|
||||||
|
|
||||||
foreach (var path in openApiDocument.Paths)
|
foreach (var path in openApiDocument.Paths)
|
||||||
{
|
{
|
||||||
@@ -431,12 +432,12 @@ namespace Nodify.Calculator
|
|||||||
var httpMethodLower = method.Key.ToLower();
|
var httpMethodLower = method.Key.ToLower();
|
||||||
if (httpMethodLower == "post" || httpMethodLower == "put" || httpMethodLower == "patch")
|
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;
|
ovmodel.RequestBodyModelClassName = bodyModelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract response model from 200/201 response schema
|
// 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
|
// Fallback: if no explicit response model, use request body model for POST/PUT/PATCH
|
||||||
if (string.IsNullOrEmpty(responseModelName) && !string.IsNullOrEmpty(ovmodel.RequestBodyModelClassName))
|
if (string.IsNullOrEmpty(responseModelName) && !string.IsNullOrEmpty(ovmodel.RequestBodyModelClassName))
|
||||||
@@ -452,7 +453,7 @@ namespace Nodify.Calculator
|
|||||||
return operations;
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -522,7 +523,7 @@ namespace Nodify.Calculator
|
|||||||
// Create model .cs file if not already created
|
// Create model .cs file if not already created
|
||||||
if (createdModels.Add(className))
|
if (createdModels.Add(className))
|
||||||
{
|
{
|
||||||
GenerateModelFromSchema(className, targetSchema);
|
GenerateModelFromSchema(className, targetSchema, createdModels, definitions);
|
||||||
// Add to AvailableModels
|
// Add to AvailableModels
|
||||||
var modelInfo = new OperationInfoViewModel
|
var modelInfo = new OperationInfoViewModel
|
||||||
{
|
{
|
||||||
@@ -543,7 +544,7 @@ namespace Nodify.Calculator
|
|||||||
return string.Empty;
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -595,7 +596,7 @@ namespace Nodify.Calculator
|
|||||||
// Generate model file if not already done
|
// Generate model file if not already done
|
||||||
if (createdModels.Add(className))
|
if (createdModels.Add(className))
|
||||||
{
|
{
|
||||||
GenerateModelFromSchema(className, targetSchema);
|
GenerateModelFromSchema(className, targetSchema, createdModels, definitions);
|
||||||
var modelInfo = new OperationInfoViewModel
|
var modelInfo = new OperationInfoViewModel
|
||||||
{
|
{
|
||||||
Title = className,
|
Title = className,
|
||||||
@@ -622,7 +623,7 @@ namespace Nodify.Calculator
|
|||||||
return $"{Executor.ToPascalCase(httpMethod)}{string.Join("", segments)}Request";
|
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");
|
var customModelDir = Path.Combine(ProjectManager.ProjectDirectory, "CustomModels");
|
||||||
Directory.CreateDirectory(customModelDir);
|
Directory.CreateDirectory(customModelDir);
|
||||||
@@ -635,7 +636,7 @@ namespace Nodify.Calculator
|
|||||||
: schema.Properties;
|
: schema.Properties;
|
||||||
foreach (var prop in props)
|
foreach (var prop in props)
|
||||||
{
|
{
|
||||||
var propType = MapJsonSchemaType(prop.Value);
|
var propType = ResolvePropertyType(prop.Value, createdModels, definitions);
|
||||||
var propName = Executor.ToPascalCase(prop.Key);
|
var propName = Executor.ToPascalCase(prop.Key);
|
||||||
sb.AppendLine($"\tpublic {propType} {propName} {{ get; set; }}");
|
sb.AppendLine($"\tpublic {propType} {propName} {{ get; set; }}");
|
||||||
}
|
}
|
||||||
@@ -643,6 +644,119 @@ namespace Nodify.Calculator
|
|||||||
File.WriteAllText(Path.Combine(customModelDir, $"{className}.cs"), sb.ToString());
|
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)
|
private static string MapJsonSchemaType(NJsonSchema.JsonSchemaProperty prop)
|
||||||
{
|
{
|
||||||
// Resolve the actual schema in case of $ref
|
// Resolve the actual schema in case of $ref
|
||||||
|
|||||||
@@ -311,6 +311,24 @@
|
|||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
"AuditHistory": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"createdBy": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"createdDate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"Operation": {
|
"Operation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -347,7 +365,10 @@
|
|||||||
},
|
},
|
||||||
"isGlutenFree": {
|
"isGlutenFree": {
|
||||||
"type": "boolean",
|
"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,
|
"additionalProperties": false,
|
||||||
@@ -378,9 +399,7 @@
|
|||||||
"nullable": true
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": {
|
"additionalProperties": { }
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user