Refactored Json2CSharpTranslator

This commit is contained in:
mdnapo 2024-09-29 19:57:31 +02:00
parent dc7fda1eed
commit 585447193d
8 changed files with 122 additions and 59 deletions

View File

@ -1,9 +1,21 @@
using System.Threading.RateLimiting;
using DevDisciples.Json.Tools.API;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddRateLimiter(o => o
.AddSlidingWindowLimiter(policyName: "sliding", options =>
{
options.PermitLimit = 10; // Maximum 10 requests per 1-second window
options.Window = TimeSpan.FromSeconds(1);
options.SegmentsPerWindow = 10;
options.QueueLimit = 2;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
}));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddControllers();
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
@ -22,6 +34,8 @@ builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseRateLimiter();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
@ -35,6 +49,6 @@ app.UseHttpsRedirection();
app.UseCors("default");
app.MapControllers();
app.MapControllers().RequireRateLimiting("sliding");
app.Run();

View File

@ -12,7 +12,7 @@
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
@ -22,7 +22,7 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {

View File

@ -0,0 +1,11 @@
using TranslationScope = DevDisciples.Json.Tools.Json2CSharpTranslator.Context.TranslationScope;
namespace DevDisciples.Json.Tools.Extensions;
public static class TranslationScopeExtensions
{
public static bool IsChildOf(this Stack<TranslationScope> stack, TranslationScope type)
{
return stack.Count > 1 && stack.ElementAt(1) == type;
}
}

View File

@ -11,19 +11,21 @@ public static partial class Json2CSharpTranslator
public void Translate(Context context)
{
if (Properties.Count == 0) return;
context.Builder.Append($"public class {Name.Pascalize()}\n");
context.Builder.Append("{\n");
var last = Properties.Last();
foreach (var property in Properties)
if (Properties.Count != 0)
{
property.Translate(context);
context.Builder.Append(property.Equals(last) ? string.Empty : "\n");
var last = Properties.Last();
foreach (var property in Properties)
{
property.Translate(context);
context.Builder.Append(property.Equals(last) ? string.Empty : "\n");
}
}
context.Builder.Append("}\n");
context.Builder.Append("}\n\n");
}
}
}

View File

@ -1,4 +1,5 @@
using System.Text;
using Humanizer;
namespace DevDisciples.Json.Tools;
@ -6,14 +7,32 @@ public static partial class Json2CSharpTranslator
{
public class Context
{
public enum TranslationScope
{
Array,
Object,
}
public const string DefaultRootClassName = "Root";
public const string DefaultNamespace = "My.Namespace";
public string RootClassName { get; init; } = DefaultRootClassName;
public string Namespace { get; init; } = DefaultNamespace;
public string CurrentName { get; set; } = string.Empty;
public Stack<string> Name { get; init; } = new();
public Stack<ClassTranslation> Class { get; init; } = new();
public Stack<TranslationScope> Scope { get; init; } = new();
public List<ClassTranslation> Classes { get; } = new();
public readonly StringBuilder Builder = new();
public string CurrentClassName =>
string.Join(
string.Empty,
Class
.SkipLast(1)
.Select(cls => cls.Name.Pascalize().Singularize())
.Prepend(Name.Peek().Pascalize().Singularize())
.Reverse()
.ToArray()
);
}
}

View File

@ -1,5 +1,6 @@
using DevDisciples.Json.Parser;
using DevDisciples.Json.Parser.Syntax;
using DevDisciples.Json.Tools.Extensions;
using DevDisciples.Parsing;
using DevDisciples.Parsing.Extensions;
using Humanizer;
@ -26,7 +27,7 @@ public static partial class Json2CSharpTranslator
throw new SyntaxException("Expected a JSON object.");
context ??= new();
context.CurrentName = context.RootClassName;
context.Name.Push(context.RootClassName);
var visitor = Visitors[typeof(JsonObjectSyntax)];
visitor(root, context);
@ -35,59 +36,93 @@ public static partial class Json2CSharpTranslator
context.Builder.Append("//using System.Collections.Generic;\n");
context.Builder.Append('\n');
context.Builder.Append($"namespace {context.Namespace};\n\n");
context.Classes.Reverse();
context.Classes.ForEach(@class => @class.Translate(context));
context.Name.Pop();
return context.Builder.ToString();
}
private static ITranslation Translate(IJsonSyntax visitee, Context context)
{
return Visitors[visitee.GetType()](visitee, context);
}
private static ITranslation Object(IJsonSyntax visitee, Context context)
{
var @object = visitee.As<JsonObjectSyntax>();
var @class = new ClassTranslation
{
Name = context.CurrentName,
Properties = new()
};
var @class = new ClassTranslation { Name = context.CurrentClassName, Properties = new() };
context.Classes.Add(@class);
context.Class.Push(@class);
context.Scope.Push(Context.TranslationScope.Object);
foreach (var prop in @object.Properties)
{
context.CurrentName = prop.Key.Lexeme;
var visitor = Visitors[prop.Value.GetType()];
var translation = visitor(prop.Value, context);
context.Name.Push(prop.Key.Lexeme);
var translation = Translate(prop.Value, context);
switch (translation)
{
case ClassTranslation:
@class.Properties.Add(new PropertyTranslation
// TODO: Handle class exists
context.Class.Peek().Properties.Add(new PropertyTranslation
{
Type = translation.Name,
Name = translation.Name,
Name = prop.Key.Lexeme,
});
break;
case PropertyTranslation property:
@class.Properties.Add(property);
// TODO: Handle property exists
context.Class.Peek().Properties.Add(property);
break;
}
context.Name.Pop();
}
return @class;
if (!context.Scope.IsChildOf(Context.TranslationScope.Array))
{
context.Classes.Add(@class);
}
context.Scope.Pop();
return context.Class.Pop();
}
private static ITranslation Array(IJsonSyntax visitee, Context context)
{
var array = visitee.As<JsonArraySyntax>();
var type = "object";
context.Scope.Push(Context.TranslationScope.Array);
if (array.Elements.Length == 0)
{
type = "object";
}
else if (array.Elements.All(e => e is JsonObjectSyntax))
{
context.Classes.Add(SquashObjects(context.CurrentName, array.Elements, context));
type = context.Classes.Last().Name;
type = context.CurrentClassName;
var composite = array.Elements
.Select(el => Translate(el, context))
.Cast<ClassTranslation>();
var @class = new ClassTranslation
{
Name = type,
Properties = composite
.SelectMany(cls => cls.Properties)
.ToList(),
};
context.Classes.Add(@class);
}
else if (array.Elements.All(e => e is JsonStringSyntax es && DateTime.TryParse(es.Value, out _)))
{
@ -102,10 +137,12 @@ public static partial class Json2CSharpTranslator
type = array.Elements.Any(e => ((JsonNumberSyntax)e).Token.Lexeme.Contains('.')) ? "double" : "int";
}
context.Scope.Pop();
return new PropertyTranslation
{
Type = $"List<{type}>",
Name = context.CurrentName,
Name = context.Name.Peek(),
};
}
@ -117,7 +154,7 @@ public static partial class Json2CSharpTranslator
return new PropertyTranslation
{
Type = type,
Name = context.CurrentName,
Name = context.Name.Peek(),
};
}
@ -126,7 +163,7 @@ public static partial class Json2CSharpTranslator
return new PropertyTranslation
{
Type = visitee.As<JsonNumberSyntax>().Token.Lexeme.Contains('.') ? "double" : "int",
Name = context.CurrentName,
Name = context.Name.Peek(),
};
}
@ -135,7 +172,7 @@ public static partial class Json2CSharpTranslator
return new PropertyTranslation
{
Type = "bool",
Name = context.CurrentName,
Name = context.Name.Peek(),
};
}
@ -144,27 +181,7 @@ public static partial class Json2CSharpTranslator
return new PropertyTranslation
{
Type = "object",
Name = context.CurrentName,
Name = context.Name.Peek(),
};
}
private static ClassTranslation SquashObjects(string className, IEnumerable<IJsonSyntax> objects, Context context)
{
var classes = objects
.Select(@object => Object(@object, context))
.ToArray();
var squashed = new ClassTranslation
{
Name = className.Singularize(),
Properties = new()
};
foreach (var @class in classes)
foreach (var prop in ((ClassTranslation)@class).Properties)
if (squashed.Properties.All(p => p.Name != prop.Name))
squashed.Properties.Add(prop);
return squashed;
}
}

View File

@ -28,7 +28,7 @@ public static partial class JsonPath
if (context.Match(JsonPathToken.LeftBracket))
return IndexAccessorExpression(context);
throw context.Error($"Invalid expression '{context.Current.Lexeme}'.");
throw context.SyntaxException($"Invalid expression '{context.Current.Lexeme}'.");
}
private static IJsonPathSyntax PropertyAccessorExpression(Context context)
@ -43,7 +43,7 @@ public static partial class JsonPath
else if (context.Match(JsonPathToken.Identifier))
getter = new PropertySyntax(context.Previous());
else throw context.Error("Expected a getter expression");
else throw context.SyntaxException("Expected a getter expression");
return new PropertyAccessorSyntax(token, getter);
}
@ -63,7 +63,7 @@ public static partial class JsonPath
else if (context.Match(JsonPathToken.String))
syntax = ObjectIndexExpression(token, context);
else throw context.Error("Expected an index expression.");
else throw context.SyntaxException("Expected an index expression.");
context.Consume(JsonPathToken.RightBracket, "Expected ']' after index expression.");
@ -82,7 +82,7 @@ public static partial class JsonPath
{
index = context.Consume(JsonPathToken.Number, "Invalid array index.");
if (!int.TryParse(index.Lexeme, out _)) throw context.Error(index, "Invalid array index.");
if (!int.TryParse(index.Lexeme, out _)) throw context.SyntaxException(index, "Invalid array index.");
indexes.Add(index);
} while (!context.Ended() && context.Match(JsonPathToken.Comma));

View File

@ -86,15 +86,15 @@ public class ParserContext<TToken> : ParsableStream<Lexer<TToken>.Token> where T
public Lexer<TToken>.Token Consume(TToken type, string message)
{
if (Check(type)) return Advance();
throw Error(message);
throw SyntaxException(message);
}
public Exception Error(string message)
public Exception SyntaxException(string message)
{
return new SyntaxException(Report.FormatMessage(Current, message));
}
public Exception Error(Lexer<TToken>.Token token, string message)
public Exception SyntaxException(Lexer<TToken>.Token token, string message)
{
return new SyntaxException(Report.FormatMessage(token, message));
}