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 DevDisciples.Json.Tools.API;
|
||||||
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
// 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.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
|
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
|
||||||
@ -22,6 +34,8 @@ builder.Services.AddSwaggerGen();
|
|||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseRateLimiter();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
@ -35,6 +49,6 @@ app.UseHttpsRedirection();
|
|||||||
|
|
||||||
app.UseCors("default");
|
app.UseCors("default");
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers().RequireRateLimiting("sliding");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
@ -12,7 +12,7 @@
|
|||||||
"http": {
|
"http": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": false,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "http://localhost:5000",
|
"applicationUrl": "http://localhost:5000",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"https": {
|
"https": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": false,
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
"environmentVariables": {
|
"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)
|
public void Translate(Context context)
|
||||||
{
|
{
|
||||||
if (Properties.Count == 0) return;
|
|
||||||
|
|
||||||
context.Builder.Append($"public class {Name.Pascalize()}\n");
|
context.Builder.Append($"public class {Name.Pascalize()}\n");
|
||||||
context.Builder.Append("{\n");
|
context.Builder.Append("{\n");
|
||||||
|
|
||||||
|
if (Properties.Count != 0)
|
||||||
|
{
|
||||||
var last = Properties.Last();
|
var last = Properties.Last();
|
||||||
foreach (var property in Properties)
|
foreach (var property in Properties)
|
||||||
{
|
{
|
||||||
property.Translate(context);
|
property.Translate(context);
|
||||||
context.Builder.Append(property.Equals(last) ? string.Empty : "\n");
|
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 System.Text;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
@ -6,14 +7,32 @@ public static partial class Json2CSharpTranslator
|
|||||||
{
|
{
|
||||||
public class Context
|
public class Context
|
||||||
{
|
{
|
||||||
|
public enum TranslationScope
|
||||||
|
{
|
||||||
|
Array,
|
||||||
|
Object,
|
||||||
|
}
|
||||||
|
|
||||||
public const string DefaultRootClassName = "Root";
|
public const string DefaultRootClassName = "Root";
|
||||||
public const string DefaultNamespace = "My.Namespace";
|
public const string DefaultNamespace = "My.Namespace";
|
||||||
|
|
||||||
public string RootClassName { get; init; } = DefaultRootClassName;
|
public string RootClassName { get; init; } = DefaultRootClassName;
|
||||||
public string Namespace { get; init; } = DefaultNamespace;
|
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 List<ClassTranslation> Classes { get; } = new();
|
||||||
public readonly StringBuilder Builder = 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;
|
||||||
using DevDisciples.Json.Parser.Syntax;
|
using DevDisciples.Json.Parser.Syntax;
|
||||||
|
using DevDisciples.Json.Tools.Extensions;
|
||||||
using DevDisciples.Parsing;
|
using DevDisciples.Parsing;
|
||||||
using DevDisciples.Parsing.Extensions;
|
using DevDisciples.Parsing.Extensions;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
@ -26,7 +27,7 @@ public static partial class Json2CSharpTranslator
|
|||||||
throw new SyntaxException("Expected a JSON object.");
|
throw new SyntaxException("Expected a JSON object.");
|
||||||
|
|
||||||
context ??= new();
|
context ??= new();
|
||||||
context.CurrentName = context.RootClassName;
|
context.Name.Push(context.RootClassName);
|
||||||
|
|
||||||
var visitor = Visitors[typeof(JsonObjectSyntax)];
|
var visitor = Visitors[typeof(JsonObjectSyntax)];
|
||||||
visitor(root, context);
|
visitor(root, context);
|
||||||
@ -35,59 +36,93 @@ public static partial class Json2CSharpTranslator
|
|||||||
context.Builder.Append("//using System.Collections.Generic;\n");
|
context.Builder.Append("//using System.Collections.Generic;\n");
|
||||||
context.Builder.Append('\n');
|
context.Builder.Append('\n');
|
||||||
context.Builder.Append($"namespace {context.Namespace};\n\n");
|
context.Builder.Append($"namespace {context.Namespace};\n\n");
|
||||||
|
context.Classes.Reverse();
|
||||||
context.Classes.ForEach(@class => @class.Translate(context));
|
context.Classes.ForEach(@class => @class.Translate(context));
|
||||||
|
|
||||||
|
context.Name.Pop();
|
||||||
|
|
||||||
return context.Builder.ToString();
|
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)
|
private static ITranslation Object(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var @object = visitee.As<JsonObjectSyntax>();
|
var @object = visitee.As<JsonObjectSyntax>();
|
||||||
var @class = new ClassTranslation
|
var @class = new ClassTranslation { Name = context.CurrentClassName, Properties = new() };
|
||||||
{
|
|
||||||
Name = context.CurrentName,
|
|
||||||
Properties = new()
|
|
||||||
};
|
|
||||||
|
|
||||||
context.Classes.Add(@class);
|
context.Class.Push(@class);
|
||||||
|
|
||||||
|
context.Scope.Push(Context.TranslationScope.Object);
|
||||||
|
|
||||||
foreach (var prop in @object.Properties)
|
foreach (var prop in @object.Properties)
|
||||||
{
|
{
|
||||||
context.CurrentName = prop.Key.Lexeme;
|
context.Name.Push(prop.Key.Lexeme);
|
||||||
var visitor = Visitors[prop.Value.GetType()];
|
|
||||||
var translation = visitor(prop.Value, context);
|
var translation = Translate(prop.Value, context);
|
||||||
|
|
||||||
switch (translation)
|
switch (translation)
|
||||||
{
|
{
|
||||||
case ClassTranslation:
|
case ClassTranslation:
|
||||||
@class.Properties.Add(new PropertyTranslation
|
// TODO: Handle class exists
|
||||||
|
context.Class.Peek().Properties.Add(new PropertyTranslation
|
||||||
{
|
{
|
||||||
Type = translation.Name,
|
Type = translation.Name,
|
||||||
Name = translation.Name,
|
Name = prop.Key.Lexeme,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PropertyTranslation property:
|
case PropertyTranslation property:
|
||||||
@class.Properties.Add(property);
|
// TODO: Handle property exists
|
||||||
|
context.Class.Peek().Properties.Add(property);
|
||||||
break;
|
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)
|
private static ITranslation Array(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var array = visitee.As<JsonArraySyntax>();
|
var array = visitee.As<JsonArraySyntax>();
|
||||||
|
|
||||||
var type = "object";
|
var type = "object";
|
||||||
|
|
||||||
|
context.Scope.Push(Context.TranslationScope.Array);
|
||||||
|
|
||||||
if (array.Elements.Length == 0)
|
if (array.Elements.Length == 0)
|
||||||
{
|
{
|
||||||
type = "object";
|
type = "object";
|
||||||
}
|
}
|
||||||
else if (array.Elements.All(e => e is JsonObjectSyntax))
|
else if (array.Elements.All(e => e is JsonObjectSyntax))
|
||||||
{
|
{
|
||||||
context.Classes.Add(SquashObjects(context.CurrentName, array.Elements, context));
|
type = context.CurrentClassName;
|
||||||
type = context.Classes.Last().Name;
|
|
||||||
|
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 _)))
|
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";
|
type = array.Elements.Any(e => ((JsonNumberSyntax)e).Token.Lexeme.Contains('.')) ? "double" : "int";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.Scope.Pop();
|
||||||
|
|
||||||
return new PropertyTranslation
|
return new PropertyTranslation
|
||||||
{
|
{
|
||||||
Type = $"List<{type}>",
|
Type = $"List<{type}>",
|
||||||
Name = context.CurrentName,
|
Name = context.Name.Peek(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +154,7 @@ public static partial class Json2CSharpTranslator
|
|||||||
return new PropertyTranslation
|
return new PropertyTranslation
|
||||||
{
|
{
|
||||||
Type = type,
|
Type = type,
|
||||||
Name = context.CurrentName,
|
Name = context.Name.Peek(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +163,7 @@ public static partial class Json2CSharpTranslator
|
|||||||
return new PropertyTranslation
|
return new PropertyTranslation
|
||||||
{
|
{
|
||||||
Type = visitee.As<JsonNumberSyntax>().Token.Lexeme.Contains('.') ? "double" : "int",
|
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
|
return new PropertyTranslation
|
||||||
{
|
{
|
||||||
Type = "bool",
|
Type = "bool",
|
||||||
Name = context.CurrentName,
|
Name = context.Name.Peek(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,27 +181,7 @@ public static partial class Json2CSharpTranslator
|
|||||||
return new PropertyTranslation
|
return new PropertyTranslation
|
||||||
{
|
{
|
||||||
Type = "object",
|
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))
|
if (context.Match(JsonPathToken.LeftBracket))
|
||||||
return IndexAccessorExpression(context);
|
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)
|
private static IJsonPathSyntax PropertyAccessorExpression(Context context)
|
||||||
@ -43,7 +43,7 @@ public static partial class JsonPath
|
|||||||
else if (context.Match(JsonPathToken.Identifier))
|
else if (context.Match(JsonPathToken.Identifier))
|
||||||
getter = new PropertySyntax(context.Previous());
|
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);
|
return new PropertyAccessorSyntax(token, getter);
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ public static partial class JsonPath
|
|||||||
else if (context.Match(JsonPathToken.String))
|
else if (context.Match(JsonPathToken.String))
|
||||||
syntax = ObjectIndexExpression(token, context);
|
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.");
|
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.");
|
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);
|
indexes.Add(index);
|
||||||
} while (!context.Ended() && context.Match(JsonPathToken.Comma));
|
} 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)
|
public Lexer<TToken>.Token Consume(TToken type, string message)
|
||||||
{
|
{
|
||||||
if (Check(type)) return Advance();
|
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));
|
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));
|
return new SyntaxException(Report.FormatMessage(token, message));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user