Refactored Json2CSharpTranslator
This commit is contained in:
parent
dc7fda1eed
commit
585447193d
@ -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();
|
@ -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": {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user