diff --git a/src/Spard.Service/BackgroundServices/ExamplesLoader.cs b/src/Spard.Service/BackgroundServices/ExamplesLoader.cs
index 88cf926..5414681 100644
--- a/src/Spard.Service/BackgroundServices/ExamplesLoader.cs
+++ b/src/Spard.Service/BackgroundServices/ExamplesLoader.cs
@@ -1,13 +1,16 @@
using Spard.Service.Contracts;
using Spard.Service.Models;
using System.Text.Json;
+using System.Text.Json.Serialization;
namespace Spard.Service.BackgroundServices;
///
/// Loads SPARD examples from folder into .
///
-internal sealed class ExamplesLoader : BackgroundService
+internal sealed class ExamplesLoader(
+ IExamplesRepository examplesRepository,
+ ILogger logger) : BackgroundService
{
///
/// Folder name containing examples.
@@ -19,18 +22,6 @@ internal sealed class ExamplesLoader : BackgroundService
///
public const string IndexFileName = "index.json";
- private readonly IExamplesRepository _examplesRepository;
- private readonly ILogger _logger;
-
- ///
- /// Initializes a new instance of class.
- ///
- public ExamplesLoader(IExamplesRepository examplesRepository, ILogger logger)
- {
- _examplesRepository = examplesRepository;
- _logger = logger;
- }
-
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var examplesFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ExampleFolderName);
@@ -38,18 +29,20 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
if (!File.Exists(indexFilePath))
{
- _logger.LogWarning("Examples file {fileName} not found!", indexFilePath);
+ logger.LogWarning("Examples file {fileName} not found!", indexFilePath);
return Task.CompletedTask;
}
- var examplesDict = JsonSerializer.Deserialize>(File.ReadAllText(indexFilePath))
- ?? new Dictionary();
+ var examplesDict = JsonSerializer.Deserialize(
+ File.ReadAllText(indexFilePath),
+ ExampleModelsContext.Default.DictionaryStringExampleModel)
+ ?? [];
foreach (var exampleEntry in examplesDict)
{
if (!int.TryParse(exampleEntry.Key, out var id))
{
- _logger.LogWarning("Example id {exampleId} is not a number!", exampleEntry.Key);
+ logger.LogWarning("Example id {exampleId} is not a number!", exampleEntry.Key);
continue;
}
@@ -57,7 +50,7 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
if (!File.Exists(spardFileName))
{
- _logger.LogWarning("Example SPARD file {fileName} not found!", spardFileName);
+ logger.LogWarning("Example SPARD file {fileName} not found!", spardFileName);
continue;
}
@@ -70,9 +63,12 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
Transform = spardText
};
- _examplesRepository.AddExample(id, example);
+ examplesRepository.AddExample(id, example);
}
return Task.CompletedTask;
}
}
+
+[JsonSerializable(typeof(Dictionary))]
+internal partial class ExampleModelsContext : JsonSerializerContext { }
diff --git a/src/Spard.Service/Dockerfile b/src/Spard.Service/Dockerfile
index 3d1d602..7c200c1 100644
--- a/src/Spard.Service/Dockerfile
+++ b/src/Spard.Service/Dockerfile
@@ -1,10 +1,11 @@
-FROM mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine AS base
+FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine AS base
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs
WORKDIR /app
-EXPOSE 5000
+EXPOSE 8080
-FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/Spard.Service/Spard.Service.csproj", "src/Spard.Service/"]
COPY ["src/Spard.Service.Contract/Spard.Service.Contract.csproj", "src/Spard.Service.Contract/"]
@@ -12,9 +13,9 @@ COPY ["src/Spard/Spard.csproj", "src/Spard/"]
RUN dotnet restore "src/Spard.Service/Spard.Service.csproj" -r linux-musl-x64
COPY . .
WORKDIR "/src/src/Spard.Service"
-RUN dotnet publish "Spard.Service.csproj" -p:PublishSingleFile=true -r linux-musl-x64 -p:PublishTrimmed=true -p:InvariantGlobalization=false -c Release -o /app/publish
+RUN dotnet publish "Spard.Service.csproj" -p:PublishSingleFile=true -r linux-musl-x64 -p:PublishTrimmed=true -p:InvariantGlobalization=false -c $BUILD_CONFIGURATION -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
-ENTRYPOINT ["./Spard.Service", "--urls", "http://*:5000"]
\ No newline at end of file
+ENTRYPOINT ["./Spard.Service"]
\ No newline at end of file
diff --git a/src/Spard.Service/Metrics/OtelMetrics.cs b/src/Spard.Service/Metrics/OtelMetrics.cs
index b44ef71..e4a8875 100644
--- a/src/Spard.Service/Metrics/OtelMetrics.cs
+++ b/src/Spard.Service/Metrics/OtelMetrics.cs
@@ -7,6 +7,8 @@ namespace Spard.Service.Metrics;
///
public sealed class OtelMetrics
{
+ public const string MeterName = "Spard";
+
private Counter TransformCounter { get; }
private Counter TransformTableCounter { get; }
@@ -15,12 +17,9 @@ public sealed class OtelMetrics
private Counter GenerateSourceCodeCounter { get; }
- public string MeterName { get; }
-
- public OtelMetrics(string meterName = "Spard")
+ public OtelMetrics(IMeterFactory meterFactory)
{
- var meter = new Meter(meterName);
- MeterName = meterName;
+ var meter = meterFactory.Create(MeterName);
TransformCounter = meter.CreateCounter("transforms");
TransformTableCounter = meter.CreateCounter("table-transforms");
diff --git a/src/Spard.Service/Program.cs b/src/Spard.Service/Program.cs
index a010398..ee49b88 100644
--- a/src/Spard.Service/Program.cs
+++ b/src/Spard.Service/Program.cs
@@ -4,15 +4,18 @@
using Serilog;
using Spard.Service.BackgroundServices;
using Spard.Service.Configuration;
+using Spard.Service.Contract;
using Spard.Service.Contracts;
using Spard.Service.EndpointDefinitions;
-using Spard.Service.Helpers;
using Spard.Service.Metrics;
using Spard.Service.Services;
+using System.Text.Json.Serialization;
-var builder = WebApplication.CreateBuilder(args);
+var builder = WebApplication.CreateSlimBuilder(args);
builder.Host.UseSerilog((ctx, lc) => lc
+ .WriteTo.Console(new Serilog.Formatting.Display.MessageTemplateTextFormatter(
+ "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level}] {Message:lj} {Exception}{NewLine}"))
.ReadFrom.Configuration(ctx.Configuration)
.Filter.ByExcluding(logEvent =>
logEvent.Exception is BadHttpRequestException
@@ -38,7 +41,12 @@ static void ConfigureServices(IServiceCollection services, IConfiguration config
services.AddHostedService();
AddRateLimits(services, configuration);
- AddMetrics(services);
+ AddMetrics(services, configuration);
+
+ services.ConfigureHttpJsonOptions(options =>
+ {
+ options.SerializerOptions.TypeInfoResolverChain.Insert(0, SpardSerializerContext.Default);
+ });
}
static void AddRateLimits(IServiceCollection services, IConfiguration configuration)
@@ -50,20 +58,26 @@ static void AddRateLimits(IServiceCollection services, IConfiguration configurat
services.AddInMemoryRateLimiting();
}
-static void AddMetrics(IServiceCollection services)
+static void AddMetrics(IServiceCollection services, IConfiguration configuration)
{
- var meters = new OtelMetrics();
+ services.AddSingleton();
services.AddOpenTelemetry().WithMetrics(builder =>
builder
.ConfigureResource(rb => rb.AddService("Spard"))
- .AddMeter(meters.MeterName)
+ .AddMeter(OtelMetrics.MeterName)
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddProcessInstrumentation()
- .AddPrometheusExporter());
-
- services.AddSingleton(meters);
+ .AddOtlpExporter(options =>
+ {
+ var otelUri = configuration["OpenTelemetry:ServiceUri"];
+
+ if (otelUri != null)
+ {
+ options.Endpoint = new Uri(otelUri);
+ }
+ }));
}
static void Configure(WebApplication app)
@@ -78,6 +92,11 @@ static void Configure(WebApplication app)
TransformEndpointDefinitions.DefineExamplesEndpoint(app);
app.UseIpRateLimiting();
+}
- app.UseOpenTelemetryPrometheusScrapingEndpoint();
-}
\ No newline at end of file
+[JsonSerializable(typeof(IEnumerable))]
+[JsonSerializable(typeof(SpardExampleInfo))]
+[JsonSerializable(typeof(TransformRequest))]
+[JsonSerializable(typeof(TransformTableResult))]
+[JsonSerializable(typeof(ProcessResult))]
+internal partial class SpardSerializerContext : JsonSerializerContext { }
\ No newline at end of file
diff --git a/src/Spard.Service/Spard.Service.csproj b/src/Spard.Service/Spard.Service.csproj
index 4b79e9d..19921fc 100644
--- a/src/Spard.Service/Spard.Service.csproj
+++ b/src/Spard.Service/Spard.Service.csproj
@@ -102,13 +102,13 @@
-
-
-
+
+
+
-
-
+
+
diff --git a/src/Spard.Service/appsettings.json b/src/Spard.Service/appsettings.json
index d0aa371..7b2d38c 100644
--- a/src/Spard.Service/appsettings.json
+++ b/src/Spard.Service/appsettings.json
@@ -21,7 +21,7 @@
]
},
"Serilog": {
- "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
+ "Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
@@ -29,28 +29,6 @@
"System": "Warning"
}
},
- "WriteTo": [
- {
- "Name": "Console",
- "Args": {
- "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level}] {Message:lj} {Exception}{NewLine}",
- "theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console"
- }
- },
- {
- "Name": "File",
- "Args": {
- "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level}] {Message:lj} {Exception}{NewLine}",
- "path": "logs/spard.log",
- "shared": "true",
- "fileSizeLimitBytes": "5000000",
- "rollOnFileSizeLimit": true,
- "rollingInterval": "Day",
- "retainedFileCountLimit": 5,
- "flushToDiskInterval": "1"
- }
- }
- ],
"Enrich": [ "FromLogContext", "WithThreadId", "WithMachineName" ]
}
}
diff --git a/src/Spard/Spard.csproj b/src/Spard/Spard.csproj
index a1ae1fb..132c1a6 100644
--- a/src/Spard/Spard.csproj
+++ b/src/Spard/Spard.csproj
@@ -20,6 +20,6 @@
-
+
\ No newline at end of file
diff --git a/test/Spard.Service.IntegrationTests/ExamplesTests.cs b/test/Spard.Service.IntegrationTests/ExamplesTests.cs
index 5e9919a..19c31ad 100644
--- a/test/Spard.Service.IntegrationTests/ExamplesTests.cs
+++ b/test/Spard.Service.IntegrationTests/ExamplesTests.cs
@@ -11,13 +11,13 @@ public sealed class ExamplesTests : TestsBase
public async Task GetExamples_OkAsync()
{
var examples = await SpardClient.Examples.GetExamplesAsync();
- Assert.Greater(examples.Length, 0);
+ Assert.That(examples.Length, Is.GreaterThan(0));
var firstExample = examples.OrderBy(e => e.Id).First();
var example = await SpardClient.Examples.GetExampleAsync(firstExample.Id);
- Assert.NotNull(example);
- Assert.AreEqual(firstExample.Id, example.Id);
- Assert.AreEqual(firstExample.Name, example.Name);
+ Assert.That(example, Is.Not.Null);
+ Assert.That(firstExample.Id, Is.EqualTo(example.Id));
+ Assert.That(firstExample.Name, Is.EqualTo(example.Name));
}
[Test]
@@ -32,9 +32,9 @@ public async Task GetExample_Localized_OkAsync()
var spardClientRus = ServiceCollectionExtensions.CreateSpardClient(options);
var example = await spardClientRus.Examples.GetExampleAsync(5);
- Assert.AreEqual("������� ������", example.Name);
+ Assert.That(example.Name, Is.EqualTo("������� ������"));
var example2 = await SpardClient.Examples.GetExampleAsync(5);
- Assert.AreEqual("Double replacement", example2.Name);
+ Assert.That(example2.Name, Is.EqualTo("Double replacement"));
}
}
\ No newline at end of file
diff --git a/test/Spard.Service.IntegrationTests/Spard.Service.IntegrationTests.csproj b/test/Spard.Service.IntegrationTests/Spard.Service.IntegrationTests.csproj
index 01b1774..49b28e0 100644
--- a/test/Spard.Service.IntegrationTests/Spard.Service.IntegrationTests.csproj
+++ b/test/Spard.Service.IntegrationTests/Spard.Service.IntegrationTests.csproj
@@ -8,9 +8,9 @@
-
+
-
+
diff --git a/test/Spard.Service.IntegrationTests/SpardTests.cs b/test/Spard.Service.IntegrationTests/SpardTests.cs
index 1e52a4e..8feb1cf 100644
--- a/test/Spard.Service.IntegrationTests/SpardTests.cs
+++ b/test/Spard.Service.IntegrationTests/SpardTests.cs
@@ -14,8 +14,8 @@ public async Task GenerateTable_OkAsync()
var actualResult = await SpardClient.Spard.GenerateTableAsync(transform);
- Assert.AreEqual(result.Replace("\r", ""), actualResult.Result.Replace("\r", ""));
- Assert.Greater(actualResult.Duration, TimeSpan.Zero);
+ Assert.That(result.Replace("\r", ""), Is.EqualTo(actualResult.Result.Replace("\r", "")));
+ Assert.That(actualResult.Duration, Is.GreaterThan(TimeSpan.Zero));
}
[Test]
@@ -26,7 +26,7 @@ public async Task GenerateSourceCode_OkAsync()
var actualResult = await SpardClient.Spard.GenerateSourceCodeAsync(transform);
- Assert.AreEqual(result.Replace("\r", ""), actualResult.Result.Replace("\r", ""));
- Assert.Greater(actualResult.Duration, TimeSpan.Zero);
+ Assert.That(result.Replace("\r", ""), Is.EqualTo(actualResult.Result.Replace("\r", "")));
+ Assert.That(actualResult.Duration, Is.GreaterThan(TimeSpan.Zero));
}
}
diff --git a/test/Spard.Service.IntegrationTests/TransformTests.cs b/test/Spard.Service.IntegrationTests/TransformTests.cs
index 1300e64..e9bde26 100644
--- a/test/Spard.Service.IntegrationTests/TransformTests.cs
+++ b/test/Spard.Service.IntegrationTests/TransformTests.cs
@@ -20,7 +20,7 @@ public async Task RunAllExamples_OkAsync()
{
if (!exampleResults.TryGetValue(example.Id, out var expectedResult))
{
- Assert.Fail("Unknown example: {0}", example.Id);
+ Assert.Fail($"Unknown example: {example.Id}");
}
var exampleData = await SpardClient.Examples.GetExampleAsync(example.Id);
@@ -31,8 +31,8 @@ public async Task RunAllExamples_OkAsync()
Transform = exampleData.Transform
});
- Assert.AreEqual(expectedResult.Replace("\r", ""), result.Result.Replace("\r", ""));
- Assert.Greater(result.Duration, TimeSpan.Zero);
+ Assert.That(expectedResult.Replace("\r", ""), Is.EqualTo(result.Result.Replace("\r", "")));
+ Assert.That(result.Duration, Is.GreaterThan(TimeSpan.Zero));
}
}
@@ -49,11 +49,11 @@ public async Task RunTableTransform_OkAsync()
Transform = transform
});
- Assert.AreEqual(result, actualResult.Result);
- Assert.IsTrue(actualResult.IsStandardResultTheSame);
- Assert.Greater(actualResult.ParseDuration, TimeSpan.Zero);
- Assert.Greater(actualResult.StandardTransformDuration, TimeSpan.Zero);
- Assert.Greater(actualResult.TableBuildDuration, TimeSpan.Zero);
- Assert.Greater(actualResult.TableTransformDuration, TimeSpan.Zero);
+ Assert.That(result, Is.EqualTo(actualResult.Result));
+ Assert.That(actualResult.IsStandardResultTheSame, Is.True);
+ Assert.That(actualResult.ParseDuration, Is.GreaterThan(TimeSpan.Zero));
+ Assert.That(actualResult.StandardTransformDuration, Is.GreaterThan(TimeSpan.Zero));
+ Assert.That(actualResult.TableBuildDuration, Is.GreaterThan(TimeSpan.Zero));
+ Assert.That(actualResult.TableTransformDuration, Is.GreaterThan(TimeSpan.Zero));
}
}
diff --git a/test/Spard.UnitTests/Spard.UnitTests.csproj b/test/Spard.UnitTests/Spard.UnitTests.csproj
index 7cc5b11..d246199 100644
--- a/test/Spard.UnitTests/Spard.UnitTests.csproj
+++ b/test/Spard.UnitTests/Spard.UnitTests.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
@@ -8,12 +8,12 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/test/Spard.UnitTests/TableTransformerTests.cs b/test/Spard.UnitTests/TableTransformerTests.cs
index 1a728c2..9598149 100644
--- a/test/Spard.UnitTests/TableTransformerTests.cs
+++ b/test/Spard.UnitTests/TableTransformerTests.cs
@@ -140,7 +140,7 @@ private static void RunTableTest(string transform, string[] tests, TableTestMode
var res1 = transformer.TransformToText(test);
sw1.Stop();
- Assert.AreEqual(res1, res2);
+ Assert.That(res1, Is.EqualTo(res2));
var sw3 = new Stopwatch();
@@ -150,7 +150,7 @@ private static void RunTableTest(string transform, string[] tests, TableTestMode
var res3 = tableTransformer2.TransformToText(test);
sw3.Stop();
- Assert.AreEqual(res1, res3);
+ Assert.That(res1, Is.EqualTo(res3));
}
var sw5 = new Stopwatch();
@@ -161,10 +161,10 @@ private static void RunTableTest(string transform, string[] tests, TableTestMode
var res5 = new string(tt.ToArray());
sw5.Stop();
- Assert.AreEqual(res1, res5);
+ Assert.That(res1, Is.EqualTo(res5));
}
- Assert.Pass("{0}: {1} {2} {3}", test, sw1.Elapsed, sw2.Elapsed, sw5.Elapsed);
+ Assert.Pass($"{test}: {sw1.Elapsed} {sw2.Elapsed} {sw5.Elapsed}");
}
}
}
diff --git a/test/Spard.UnitTests/TreeTransformerTests.cs b/test/Spard.UnitTests/TreeTransformerTests.cs
index b9e13de..978c088 100644
--- a/test/Spard.UnitTests/TreeTransformerTests.cs
+++ b/test/Spard.UnitTests/TreeTransformerTests.cs
@@ -300,11 +300,11 @@ public static void TestTextExpressionTree(string spard, params TestData[] rules)
try
{
var result = tree.TransformToText(pair.Item1);
- Assert.AreEqual(pair.Item2, result);
+ Assert.That(pair.Item2, Is.EqualTo(result));
}
catch (TransformException)
{
- Assert.IsNull(pair.Item2);
+ Assert.That(pair.Item2, Is.Null);
}
}
}