Skip to content

Instantly share code, notes, and snippets.

@INTERNALINTERFERENCE
Created July 9, 2023 21:38
Show Gist options
  • Save INTERNALINTERFERENCE/06691907f3413b51a2afffff9626a955 to your computer and use it in GitHub Desktop.
Save INTERNALINTERFERENCE/06691907f3413b51a2afffff9626a955 to your computer and use it in GitHub Desktop.
internal class OpenApiGenerator
{
public string GenerateYaml(
Type apiType,
CultureInfo culture)
{
var metadata = ApiMetadata.FromType(apiType);
if (metadata == null)
throw new Exception("The given type is not API");
var yaml = GenerateYaml(
metadata,
culture ?? CultureInfo.CurrentCulture);
return yaml;
}
private string GenerateYaml(
IApiEndpointMetadata metadata,
CultureInfo culture )
{
var commands = metadata.Commands;
var openApiDocument = new OpenApiDocument
{
Info = GenerateApiInfo(metadata)
};
openApiDocument.Components = new();
openApiDocument.Paths = new();
foreach (var command in commands)
{
var name = command.Key;
var arguments = command.Value.ArgumentType;
var response = command.Value.ReturnType;
var properties = GenerateProperties(arguments);
var properties2 = GenerateProperties(response);
openApiDocument.Components.Schemas.Add(
$"{name}_arguments",
new OpenApiSchema
{
Type = "object",
Properties = properties
} );
openApiDocument.Components.Schemas.Add(
$"{name}_response",
new OpenApiSchema
{
Type = "object",
Properties = properties2
} );
openApiDocument.Paths.Add(
$"/{name}",
new OpenApiPathItem
{
Operations =
{
[OperationType.Post] = new()
{
RequestBody = new OpenApiRequestBody()
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Id = $"{name}_arguments",
Type = ReferenceType.Schema
}
}
}
}
},
Responses = new OpenApiResponses()
{
["200"] = new OpenApiResponse()
{
Description = "OK",
Content = new Dictionary<string, OpenApiMediaType>()
{
[ "application/json" ] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Id = $"{name}_response",
Type = ReferenceType.Schema
}
}
}
}
},
["504"] = new OpenApiResponse
{
Description = "MQTT timeout error"
},
["500"] = new OpenApiResponse
{
Description = "Internal error"
}
}
}
}
} );
}
return openApiDocument.Serialize(
OpenApiSpecVersion.OpenApi3_0,
OpenApiFormat.Yaml);
}
private IDictionary<string, OpenApiSchema> GenerateProperties(Type arguments)
{
var properties = new Dictionary<string, OpenApiSchema>();
var processedTypes = new HashSet<Type>(); // Maintain a set of processed types
GeneratePropertiesRecursive(arguments, properties, processedTypes);
return properties;
}
private void GeneratePropertiesRecursive(Type type, IDictionary<string, OpenApiSchema> properties, HashSet<Type> processedTypes)
{
var propertyInfos = type.GetProperties();
foreach (var propertyInfo in propertyInfos)
{
if ( propertyInfo.GetCustomAttribute<JsonPropertyAttribute>() == null )
continue;
var propertyName = propertyInfo.Name;
var propertyType = propertyInfo.PropertyType;
var propertySchema = new OpenApiSchema();
if ( processedTypes.Contains(propertyType))
{
// Skip processing if property type has already been processed
// This prevents infinite recursion in case of circular references
continue;
}
processedTypes.Add(propertyType);
if (propertyType.IsClass)
{
GeneratePropertiesRecursive(propertyType, propertySchema.Properties, processedTypes);
}
if (propertyType == typeof(int))
{
propertySchema.Type = "integer";
propertySchema.Format = "int64";
}
else if (propertyType == typeof(string))
{
propertySchema.Type = "string";
}
else if (propertyType == typeof(bool))
{
propertySchema.Type = "boolean";
}
properties.Add(propertyName, propertySchema);
}
}
private OpenApiInfo GenerateApiInfo( IApiEndpointMetadata metadata ) =>
new()
{
Version = "1.0.0",
Title = metadata.Title,
Description = metadata.Description
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment