Skip to content

Commit

Permalink
Merge pull request #72 from neuroglia-io/feat-serialization
Browse files Browse the repository at this point in the history
Updated the IJsonSerializer to define methods to serialize to JsonElements, JsonNodes and JsonDocuments
  • Loading branch information
cdavernas authored Dec 4, 2023
2 parents d7b4b16 + 1475c59 commit 7eb6de0
Show file tree
Hide file tree
Showing 15 changed files with 383 additions and 106 deletions.
14 changes: 7 additions & 7 deletions Neuroglia Framework.sln
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neuroglia.Mediation.Abstrac
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neuroglia.Integration", "src\Neuroglia.Integration\Neuroglia.Integration.csproj", "{B9DF1C37-F41F-4AC8-8BB0-19AD9EB978D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neuroglia.JsonSchema.Generation", "src\Neuroglia.JsonSchema.Generation\Neuroglia.JsonSchema.Generation.csproj", "{30EF0A31-DD4D-4A4C-A383-8AF88DE8FD93}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neuroglia.Measurements.Sdk.TypeScript", "src\Neuroglia.Measurements.Sdk.TypeScript\Neuroglia.Measurements.Sdk.TypeScript.csproj", "{4BA3F773-5C01-4064-9441-EED11C3264EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neuroglia.Data.Schemas.Json", "src\Neuroglia.Data.Schemas.Json\Neuroglia.Data.Schemas.Json.csproj", "{A555095D-AA84-4D67-BF84-A0038EC783BD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -276,14 +276,14 @@ Global
{B9DF1C37-F41F-4AC8-8BB0-19AD9EB978D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9DF1C37-F41F-4AC8-8BB0-19AD9EB978D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9DF1C37-F41F-4AC8-8BB0-19AD9EB978D7}.Release|Any CPU.Build.0 = Release|Any CPU
{30EF0A31-DD4D-4A4C-A383-8AF88DE8FD93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30EF0A31-DD4D-4A4C-A383-8AF88DE8FD93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30EF0A31-DD4D-4A4C-A383-8AF88DE8FD93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30EF0A31-DD4D-4A4C-A383-8AF88DE8FD93}.Release|Any CPU.Build.0 = Release|Any CPU
{4BA3F773-5C01-4064-9441-EED11C3264EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4BA3F773-5C01-4064-9441-EED11C3264EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4BA3F773-5C01-4064-9441-EED11C3264EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4BA3F773-5C01-4064-9441-EED11C3264EC}.Release|Any CPU.Build.0 = Release|Any CPU
{A555095D-AA84-4D67-BF84-A0038EC783BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A555095D-AA84-4D67-BF84-A0038EC783BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A555095D-AA84-4D67-BF84-A0038EC783BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A555095D-AA84-4D67-BF84-A0038EC783BD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -331,8 +331,8 @@ Global
{63922ABE-916D-4A4D-A3E1-8162BEED060E} = {AFFADE1D-C17D-4ED8-8407-BFE108714262}
{23AEEAC4-03C7-4E6F-AB74-26588908AAB9} = {BDED6037-3D6E-4781-8C58-2EB7D0E53CEA}
{B9DF1C37-F41F-4AC8-8BB0-19AD9EB978D7} = {BDED6037-3D6E-4781-8C58-2EB7D0E53CEA}
{30EF0A31-DD4D-4A4C-A383-8AF88DE8FD93} = {BDED6037-3D6E-4781-8C58-2EB7D0E53CEA}
{4BA3F773-5C01-4064-9441-EED11C3264EC} = {BDED6037-3D6E-4781-8C58-2EB7D0E53CEA}
{A555095D-AA84-4D67-BF84-A0038EC783BD} = {BDED6037-3D6E-4781-8C58-2EB7D0E53CEA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B390984D-3A9D-4F6D-A73B-E93CCEB586E4}
Expand Down
6 changes: 3 additions & 3 deletions src/Neuroglia.Core/Utilities/XmlDocumentationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ namespace Neuroglia;
public static partial class XmlDocumentationHelper
{

private static ConcurrentDictionary<string, AssemblyXmlDocumentationContainer> LoadedAssemblyXmlDocumentation = new();
private static ConcurrentDictionary<string, AssemblyXmlDocumentationContainer> LoadedAssemblyXmlDocumentation = [];

/// <summary>
/// Gets the specified type's XML code documentation
/// </summary>
/// <param name="type">The type to get the XML code documentation for</param>
/// <returns>The XML code documentation of the specified type</returns>
public static string DocumentationOf(Type type)
public static string? DocumentationOf(Type type)
{
ArgumentNullException.ThrowIfNull(type);

Expand Down Expand Up @@ -182,7 +182,7 @@ partial class AssemblyXmlDocumentationContainer

AssemblyXmlDocumentationContainer() { }

public Dictionary<string, string> XmlDocumentation { get; } = new();
public Dictionary<string, string> XmlDocumentation { get; } = [];

public string? GetDocumentationFor(Type type)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Neuroglia.Json.Schema.Generation;
namespace Neuroglia.Data.Schemas.Json;

/// <summary>
/// Exposes methods to handle <see cref="SchemaGeneratorConfiguration"/>s
Expand Down
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>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -10,7 +10,7 @@
<Authors>Neuroglia SRL</Authors>
<RepositoryUrl>https://github.com/neuroglia-io/framework</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>neuroglia framework json json-schema schema generation</PackageTags>
<PackageTags>neuroglia framework schema json json-schema</PackageTags>
<Version>4.7.1</Version>
<NeutralLanguage>en</NeutralLanguage>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
Expand All @@ -32,4 +32,9 @@
<PackageReference Include="JsonSchema.Net.Generation" Version="3.5.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Neuroglia.Data.PatchModel\Neuroglia.Data.PatchModel.csproj" />
<ProjectReference Include="..\Neuroglia.Serialization.Abstractions\Neuroglia.Serialization.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Neuroglia.Json.Schema.Generation;
namespace Neuroglia.Data.Schemas.Json;

/// <summary>
/// Represents the <see cref="ISchemaGenerator"/> used to handle <see cref="DateTimeOffset"/>s
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Neuroglia.Data.Schemas.Json;

/// <summary>
/// Defines the fundamentals of a service used to resolve <see cref="JsonSchema"/>s
/// </summary>
public interface IJsonSchemaResolver
{

/// <summary>
/// Resolves the $refs of the specified <see cref="JsonSchema"/>
/// </summary>
/// <param name="schema">The <see cref="JsonSchema"/> to resolve</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>The resolved <see cref="JsonSchema"/></returns>
Task<JsonSchema> ResolveSchemaAsync(JsonSchema schema, CancellationToken cancellationToken = default);

}
175 changes: 175 additions & 0 deletions src/Neuroglia.Data.Schemas.Json/Services/JsonSchemaResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright © 2021-Present Neuroglia SRL. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using JsonCons.Utilities;
using Neuroglia.Serialization;
using System.Text.Json;
using System.Text.Json.Nodes;
using JsonPointer = Json.Pointer.JsonPointer;

namespace Neuroglia.Data.Schemas.Json;

/// <summary>
/// Represents the default implementation of the <see cref="IJsonSchemaResolver"/> interface
/// </summary>
/// <param name="serializer">The service used to serialize/deserialize objects to/from JSON</param>
/// <param name="httpClient">The service used to perform HTTP requests</param>
public class JsonSchemaResolver(IJsonSerializer serializer, HttpClient httpClient)
: IJsonSchemaResolver
{

/// <summary>
/// Gets the service used to serialize/deserialize objects to/from JSON
/// </summary>
protected IJsonSerializer Serializer { get; } = serializer;

/// <summary>
/// Gets the service used to perform HTTP requests
/// </summary>
protected HttpClient HttpClient { get; } = httpClient;

/// <inheritdoc/>
public virtual async Task<JsonSchema> ResolveSchemaAsync(JsonSchema schema, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(schema);

var jsonElement = this.Serializer.SerializeToElement(schema)!;
jsonElement = await this.ResolveSchemaAsync(jsonElement.Value, jsonElement, cancellationToken).ConfigureAwait(false);
var json = this.Serializer.SerializeToText(jsonElement)!;
return JsonSchema.FromText(json);
}

/// <summary>
/// Resolves the specified <see cref="JsonSchema"/>
/// </summary>
/// <param name="schema">The <see cref="JsonElement"/> representation of the <see cref="JsonSchema"/> to resolve</param>
/// <param name="rootSchema">The <see cref="JsonElement"/> representation of the root <see cref="JsonSchema"/>, if any, of the <see cref="JsonSchema"/> to resolve</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>The <see cref="JsonElement"/> representation of the <see cref="JsonSchema"/> to resolve</returns>
protected virtual async Task<JsonElement> ResolveSchemaAsync(JsonElement schema, JsonElement? rootSchema = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(schema);
rootSchema ??= schema;

switch (schema.ValueKind)
{
case JsonValueKind.Array:
var schemaArray = new JsonArray();
foreach (var item in schema.EnumerateArray())
{
schemaArray.Add(await this.ResolveSchemaAsync(item, rootSchema, cancellationToken).ConfigureAwait(false));
}
return this.Serializer.SerializeToElement(schemaArray)!.Value;
case JsonValueKind.Object:
var refSchemas = await this.ResolveReferencedSchemasAsync(schema, rootSchema.Value, cancellationToken).ConfigureAwait(false);
var mergedSchema = this.RemoveReferenceProperties(schema);
foreach (var refSchema in refSchemas)
{
mergedSchema = JsonMergePatch.ApplyMergePatch(mergedSchema, refSchema).RootElement;
}
var mergedSchemaNode = this.Serializer.SerializeToNode(mergedSchema)!.AsObject()!;
foreach (var property in mergedSchema.EnumerateObject())
{
mergedSchemaNode[property.Name] = this.Serializer.SerializeToNode(await this.ResolveSchemaAsync(property.Value, rootSchema, cancellationToken).ConfigureAwait(false));
}
return this.Serializer.SerializeToElement(mergedSchemaNode)!.Value;
default: return schema;
}
}

/// <summary>
/// Resolves the references specified <see cref="JsonSchema"/>
/// </summary>
/// <param name="schema">The <see cref="JsonElement"/> representation of the <see cref="JsonSchema"/> to resolve</param>
/// <param name="rootSchema">The <see cref="JsonElement"/> representation of the root <see cref="JsonSchema"/>, if any, of the <see cref="JsonSchema"/> to resolve</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>The <see cref="JsonElement"/> representation of the <see cref="JsonSchema"/> to resolve</returns>
protected virtual async Task<IEnumerable<JsonElement>> ResolveReferencedSchemasAsync(JsonElement schema, JsonElement rootSchema, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(schema);
ArgumentNullException.ThrowIfNull(rootSchema);

if (schema.ValueKind != JsonValueKind.Object) return Array.Empty<JsonElement>();

var refSchemas = new List<JsonElement>();
var keywords = schema.EnumerateObject();
var refKeyword = keywords.FirstOrDefault(k => k.NameEquals(RefKeyword.Name));
var allOfKeyword = keywords.FirstOrDefault(k => k.NameEquals(AllOfKeyword.Name));

if (refKeyword.Value.ValueKind == JsonValueKind.String)
{
var reference = refKeyword.Value.Deserialize<string>()!;
var refSchema = await this.ResolveReferencedSchemaAsync(new Uri(reference, UriKind.RelativeOrAbsolute), rootSchema, cancellationToken).ConfigureAwait(false);
if (refSchema.HasValue) refSchemas.Add(refSchema);
}

if (allOfKeyword.Value.ValueKind == JsonValueKind.Array)
{
foreach (var refSchema in allOfKeyword.Value.EnumerateArray())
{
refSchemas.Add(await this.ResolveSchemaAsync(refSchema, null, cancellationToken).ConfigureAwait(false));
}
}

return refSchemas;
}

/// <summary>
/// Resolves the specified <see cref="JsonSchema"/>
/// </summary>
/// <param name="uri">The <see cref="Uri"/> of the <see cref="JsonSchema"/> to resolve</param>
/// <param name="rootSchema">The <see cref="JsonElement"/> representation of the root <see cref="JsonSchema"/>, if any, of the <see cref="JsonSchema"/> to resolve</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>The <see cref="JsonElement"/> representation of the <see cref="JsonSchema"/> to resolve</returns>
protected virtual async Task<JsonElement?> ResolveReferencedSchemaAsync(Uri uri, JsonElement rootSchema, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(uri);
ArgumentNullException.ThrowIfNull(rootSchema);
var useRootSchema = true;

JsonElement? schema;
if (uri.IsAbsoluteUri)
{
var schemaJson = await this.HttpClient.GetStringAsync(uri, cancellationToken).ConfigureAwait(false);
schema = this.Serializer.SerializeToElement(schemaJson);
useRootSchema = false;
}
else
{
if (!JsonPointer.TryParse(uri.OriginalString, out var jsonPointer) || jsonPointer == null) throw new NullReferenceException($"Failed to find the JSON schema at '{uri}'");
schema = jsonPointer.Evaluate(rootSchema);
}

if (schema == null) return null;
else return await this.ResolveSchemaAsync(schema.Value, useRootSchema ? rootSchema : null, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Removes references from the specified <see cref="JsonSchema"/>
/// </summary>
/// <param name="schema">The <see cref="JsonElement"/> representation of the <see cref="JsonSchema"/> to remove references from</param>
/// <returns>The <see cref="JsonElement"/> purged out of references</returns>
protected virtual JsonElement RemoveReferenceProperties(JsonElement schema)
{
ArgumentNullException.ThrowIfNull(schema);

var node = this.Serializer.SerializeToNode(schema)?.AsObject();
if (node == null) return schema;

node.Remove(RefKeyword.Name);
node.Remove(AllOfKeyword.Name);

return this.Serializer.SerializeToElement(node)!.Value;
}

}
File renamed without changes.
3 changes: 3 additions & 0 deletions src/Neuroglia.Integration/DataTransferObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Runtime.Serialization;

namespace Neuroglia;

/// <summary>
/// Represents the base class of all Data Transfer Objects (DTOs)
/// </summary>
[DataContract]
public abstract record DataTransferObject
: IDataTransferObject
{
Expand Down
24 changes: 9 additions & 15 deletions src/Neuroglia.Integration/IntegrationEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Runtime.Serialization;

namespace Neuroglia;

/// <summary>
/// Represents the base class for all integration events
/// </summary>
public abstract record IntegrationEvent
[DataContract]
public abstract record IntegrationEvent<TKey>
: DataTransferObject, IIntegrationEvent
{

/// <summary>
/// Gets/sets the date and time at which the integration event has been produced
/// </summary>
[DataMember]
public virtual DateTimeOffset CreatedAt { get; set; }

/// <summary>
/// Gets/sets the id of the aggregate, if any, that has produced the event
/// </summary>
public virtual object? AggregateId { get; set; }
[DataMember]
public virtual TKey? AggregateId { get; set; }

/// <summary>
/// Gets/sets the version of the aggregate, if any, that has produced the event
/// </summary>
[DataMember]
public ulong? AggregateVersion { get; set; }

}

/// <summary>
/// Represents the base class for all integration events
/// </summary>
public abstract record IntegrationEvent<TKey>
: IntegrationEvent
{

/// <summary>
/// Gets/sets the id of the aggregate, if any, that has produced the event
/// </summary>
public virtual new TKey? AggregateId { get; set; }
object? IIntegrationEvent.AggregateId => this.AggregateId;

}
Loading

0 comments on commit 7eb6de0

Please sign in to comment.