Skip to content

Commit

Permalink
Merge pull request #534 from anhthidao/OpenApi
Browse files Browse the repository at this point in the history
Implement code to generate route handler code from a JSON file
  • Loading branch information
captainsafia committed Aug 10, 2022
2 parents 615dd2a + 13c958f commit 21cbde6
Show file tree
Hide file tree
Showing 5 changed files with 691 additions and 58 deletions.
318 changes: 318 additions & 0 deletions src/OpenApi/CodeGenerator/CodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;

namespace CodeGenerator;

public class App
{
public static void Main(string[] args)
{
if (args.Length != 2)
{
Console.Error.WriteLine("Please enter two arguments: an input file path and an output file path.");
Environment.Exit(1); // Code 1 is for problems with passed in file paths
}

var document = ReadJson(args[0]);
var paths = document?.Paths;

if (paths is null || paths.Count == 0)
{
Console.Error.WriteLine("No path were found in the schema.");
Environment.Exit(2); // Code 2 is for problems with paths in schema
}

var fileProperties = new Dictionary<string, Dictionary<string, Dictionary<string, string?>>>();

foreach (var path in paths)
{
var operations = path.Value.Operations;
if (operations is null || operations.Count == 0)
{
Console.Error.WriteLine("No operation was found in path.");
Environment.Exit(3); // Code 3 is for problems with operations
}
var pathString = path.Key.ToString();
fileProperties.Add(pathString, new Dictionary<string, Dictionary<string, string?>> ());

var dict = new Dictionary<string, Dictionary<string, string>> ();
foreach (var operation in operations)
{
var method = operation.Key.ToString().ToLower();
method = GetHttpMethod(method);

if (method == string.Empty)
{
Console.Error.WriteLine($"Unsupported HTTP method found: '{operation.Key}'");
Environment.Exit(3);
}

fileProperties[pathString].Add(method, new Dictionary<string, string?> ());

var parameters = operation.Value.Parameters;
string parametersList = string.Empty;

for (int i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
if (parameter.Schema.Type.ToLower() == "array")
{
parametersList += GetArrayKeyword(parameter.Schema) + " " + parameter.Name;
}
else if (parameter.Schema.Type.ToLower() == "object")
{
parametersList += parameter.Schema.Reference?.Id + $" user{parameter.Schema.Reference?.Id}";
}
else
{
parametersList += GetDataTypeKeyword(parameter.Schema) + " " + parameter.Name;
}

if (i < parameters.Count - 1)
{
parametersList += ", ";
}
}

fileProperties[pathString][method].Add("parameters", parametersList);

var responses = operation.Value.Responses;

foreach (var response in responses)
{
string returnValue;

// some responses doesn't have "content" property
// so these would later return a default value
if (response.Value.Content == null || response.Value.Content.Count == 0)
{
fileProperties[pathString][method].Add(response.Key, null);
continue;
}
var content = response.Value.Content.First().Value;
var schema = content.Schema;

if (schema == null)
{
Console.Error.WriteLine("No schema was found for the response.");
Environment.Exit(3);
}

if (schema.Type.ToLower() == "array")
{
returnValue = "new " + GetArrayKeyword(schema) + " {}";
}
else if (schema.Type.ToLower() == "object")
{
returnValue = "new " + schema?.Reference?.Id + "()";
}
else
{
returnValue = GetPrimitiveValue(schema);
}

// this code below is for parsing sample values
// this is used for demoing the project
if (content.Example != null)
{
returnValue = GetSampleValue(content.Example, schema);
}

if (content.Examples != null && content.Examples.Count > 0)
{
returnValue = GetSampleValue(content.Examples.First().Value.Value, schema);
}

fileProperties[pathString][method].Add(response.Key, returnValue);
}
}
}

var schemas = document?.Components?.Schemas;

Dictionary<string, Dictionary<string, string>> schemaDict = new Dictionary<string, Dictionary<string, string>> ();
if (schemas != null && schemas.Count > 0)
{
foreach (var schema in schemas)
{
schemaDict.Add(schema.Key, new Dictionary<string, string>());
foreach (var property in schema.Value.Properties)
{
string propertyType;
if (property.Value.Type.ToLower() == "array")
{
propertyType = GetArrayKeyword(property.Value);
}
else if (property.Value.Reference?.Id != null)
{
propertyType = property.Value.Reference.Id;
}
else
{
propertyType = GetDataTypeKeyword(property.Value);
}
schemaDict[schema.Key].Add(property.Key, propertyType);
}
}
}

var page = new MinimalApiTemplate
{
FileProperties = fileProperties,
Schemas = schemaDict
};

var pageContent = page.TransformText();
File.WriteAllText(args[1], pageContent);
}

private static string GetSampleValue(IOpenApiAny example, OpenApiSchema? schema) => example switch
{
OpenApiString castedExample => $"\"{castedExample.Value}\"",
OpenApiInteger castedExample => castedExample.Value.ToString(),
OpenApiBoolean castedExample => castedExample.Value.ToString(),
OpenApiFloat castedExample => castedExample.Value.ToString(),
OpenApiDouble castedExample => castedExample.Value.ToString(),
OpenApiArray castedExample => "new " + GetDataTypeKeyword(schema) + $"[] {{{GetArrayValues(castedExample)}}}",
OpenApiObject castedExample => GetObjectArguments(castedExample, schema),
OpenApiNull castedExample => "null",
_ => string.Empty
};

private static string GetArrayValues(OpenApiArray example)
{
int count = example.Count;
string returnValue = string.Empty;
foreach (var value in example)
{
returnValue += GetSampleValue(value,null);
if (count > 1)
{
returnValue += ", ";
}
count--;
}
return returnValue;
}

private static string GetObjectArguments(OpenApiObject example, OpenApiSchema? schema)
{
string arguments = $"new {schema?.Reference?.Id}(";
for (int i = 0; i < example.Values.Count; i++)
{
if (schema?.Properties?.Values.Count > i)
{
arguments += $"{GetSampleValue(example.Values.ElementAt(i), schema?.Properties?.Values?.ElementAt(i))}, ";
}
else
{
arguments += $"{GetSampleValue(example.Values.ElementAt(i), null)}, ";
}
}
return arguments.Substring(0, arguments.Length - 2) + ")";
}

private static string GetHttpMethod(string method) => method switch
{
"get" => "MapGet",
"post" => "MapPost",
"put" => "MapPut",
"delete" => "MapDelete",
_ => string.Empty
};

private static string GetDataTypeKeyword(OpenApiSchema? schema) => schema?.Type switch
{
"string" => "string",
"integer" => "int",
"float" => "float",
"boolean" => "bool",
"double" => "double",
_ => string.Empty
};

private static string GetArrayKeyword(OpenApiSchema? schema)
{
if (schema == null)
{
return string.Empty;
}
string returnValue = "[";
while (schema.Items.Type == "array")
{
returnValue += ",";
schema = schema.Items;
}
if (schema.Items.Type.ToLower() == "object")
{
returnValue = schema?.Items.Reference?.Id + returnValue + "]";
}
else
{
returnValue = GetDataTypeKeyword(schema.Items) + returnValue + "]";
}
return returnValue;
}

private static string GetPrimitiveValue(OpenApiSchema? schema) => schema?.Type switch
{
"string" => "\"\"",
"integer" => "0",
"boolean" => "false",
"float" => "0.0f",
"double" => "0.0d",
_ => string.Empty,
};

private static OpenApiDocument? ReadJson(string args)
{
if (!Path.IsPathRooted(args))
{
Console.Error.WriteLine("The file path you entered does not have a root");
return null;
}

OpenApiStreamReader reader = new OpenApiStreamReader();
var diagnostic = new OpenApiDiagnostic();

try
{
string path = Path.GetFullPath(args);
Stream stream = File.OpenRead(path);
OpenApiDocument newDocument = reader.Read(stream, out diagnostic);
return newDocument;
}
catch (FileNotFoundException e)
{
Console.WriteLine("Check to make sure you entered a correct file path because the file was not found.");
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
return null;
}
catch (Exception e)
{
Console.WriteLine("Check the file path you entered for errors.");
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
return null;
}
finally
{
if (diagnostic.Errors.Count == 0)
{
Console.WriteLine("Read File Successfully");
}
else
{
foreach (var error in diagnostic.Errors)
{
Console.WriteLine($"There was an error reading in the file: {error.Pointer}");
Console.Error.WriteLine(error.Message);
Environment.Exit(1);
}
}

}
}
}
23 changes: 22 additions & 1 deletion src/OpenApi/CodeGenerator/CodeGenerator.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -7,9 +7,30 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Content Include="MinimalApiTemplate.tt">
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>MinimalApiTemplate.cs</LastGenOutput>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.OpenApi" Version="1.3.2" />
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.3.2" />
<PackageReference Include="Microsoft.VisualStudio.TextTemplating.14.0" Version="14.3.25407" />
<PackageReference Include="System.CodeDom" Version="6.0.2-mauipre.1.22102.15" />
</ItemGroup>

<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>

<ItemGroup>
<Compile Update="MinimalApiTemplate.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>MinimalApiTemplate.tt</DependentUpon>
</Compile>
</ItemGroup>

</Project>
Loading

0 comments on commit 21cbde6

Please sign in to comment.