mirror of https://github.com/Squidex/squidex.git
Browse Source
* Started with main project. * Temporary. * Simplified type registry. * More tests and thread safety. * Update dockerfile and OpenApi. * Add missing class. * Update dependencies and OpenAPI * Fix naming in tests * Fix Squid/SquidMiddleware.cs * OpenAPI fixes. * Remove useless locks. * Fix serializer. * Usings simplified. * Fix type names. * Changelog for 7.3. * Fix tests * Fix tests * Another fix for testspull/942/head
committed by
GitHub
550 changed files with 2067 additions and 3572 deletions
@ -1,52 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Infrastructure.Json.System; |
|||
|
|||
public sealed class InheritanceConverter<T> : InheritanceConverterBase<T> where T : notnull |
|||
{ |
|||
private readonly TypeNameRegistry typeNameRegistry; |
|||
|
|||
public InheritanceConverter(TypeNameRegistry typeNameRegistry) |
|||
: base("$type") |
|||
{ |
|||
this.typeNameRegistry = typeNameRegistry; |
|||
} |
|||
|
|||
public override Type GetDiscriminatorType(string name, Type typeToConvert) |
|||
{ |
|||
var typeInfo = typeNameRegistry.GetTypeOrNull(name); |
|||
|
|||
if (typeInfo == null) |
|||
{ |
|||
typeInfo = Type.GetType(name); |
|||
} |
|||
|
|||
if (typeInfo == null) |
|||
{ |
|||
ThrowHelper.JsonException($"Object has invalid discriminator '{name}'."); |
|||
return default!; |
|||
} |
|||
|
|||
return typeInfo; |
|||
} |
|||
|
|||
public override string GetDiscriminatorValue(Type type) |
|||
{ |
|||
var typeName = typeNameRegistry.GetNameOrNull(type); |
|||
|
|||
if (typeName == null) |
|||
{ |
|||
// Use the type name as a fallback.
|
|||
typeName = type.AssemblyQualifiedName!; |
|||
} |
|||
|
|||
return typeName; |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text.Json; |
|||
using System.Text.Json.Serialization; |
|||
using Squidex.Infrastructure.ObjectPool; |
|||
|
|||
namespace Squidex.Infrastructure.Json.System; |
|||
|
|||
public abstract class InheritanceConverterBase<T> : JsonConverter<T> where T : notnull |
|||
{ |
|||
private readonly JsonEncodedText discriminatorProperty; |
|||
|
|||
public string DiscriminatorName { get; } |
|||
|
|||
protected InheritanceConverterBase(string discriminatorName) |
|||
{ |
|||
discriminatorProperty = JsonEncodedText.Encode(discriminatorName); |
|||
|
|||
DiscriminatorName = discriminatorName; |
|||
} |
|||
|
|||
public abstract Type GetDiscriminatorType(string name, Type typeToConvert); |
|||
|
|||
public abstract string GetDiscriminatorValue(Type type); |
|||
|
|||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
|||
{ |
|||
// Creating a copy of the reader (The derived deserialisation has to be done from the start)
|
|||
Utf8JsonReader typeReader = reader; |
|||
|
|||
if (typeReader.TokenType != JsonTokenType.StartObject) |
|||
{ |
|||
throw new JsonException(); |
|||
} |
|||
|
|||
if (!typeReader.Read() || typeReader.TokenType != JsonTokenType.PropertyName) |
|||
{ |
|||
throw new JsonException(); |
|||
} |
|||
|
|||
var propertyName = typeReader.GetString(); |
|||
|
|||
if (typeReader.Read() && typeReader.TokenType == JsonTokenType.String && propertyName == DiscriminatorName) |
|||
{ |
|||
var type = GetDiscriminatorType(typeReader.GetString()!, typeToConvert); |
|||
|
|||
return (T?)JsonSerializer.Deserialize(ref reader, type, options); |
|||
} |
|||
else |
|||
{ |
|||
using var document = JsonDocument.ParseValue(ref reader); |
|||
|
|||
if (!document.RootElement.TryGetProperty(DiscriminatorName, out var discriminator)) |
|||
{ |
|||
ThrowHelper.JsonException($"Object has no discriminator '{DiscriminatorName}."); |
|||
return default!; |
|||
} |
|||
|
|||
var type = GetDiscriminatorType(discriminator.GetString()!, typeToConvert); |
|||
|
|||
using var bufferWriter = DefaultPools.MemoryStream.GetStream(); |
|||
|
|||
using (var writer = new Utf8JsonWriter(bufferWriter)) |
|||
{ |
|||
document.RootElement.WriteTo(writer); |
|||
} |
|||
|
|||
return (T?)JsonSerializer.Deserialize(bufferWriter.ToArray(), type, options); |
|||
} |
|||
} |
|||
|
|||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) |
|||
{ |
|||
var name = GetDiscriminatorValue(value.GetType()); |
|||
|
|||
writer.WriteStartObject(); |
|||
writer.WriteString(discriminatorProperty, name); |
|||
|
|||
using (var document = JsonSerializer.SerializeToDocument(value, value.GetType(), options)) |
|||
{ |
|||
foreach (var property in document.RootElement.EnumerateObject()) |
|||
{ |
|||
property.WriteTo(writer); |
|||
} |
|||
} |
|||
|
|||
writer.WriteEndObject(); |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text.Json; |
|||
using System.Text.Json.Serialization; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Infrastructure.Json.System; |
|||
|
|||
public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class |
|||
{ |
|||
private readonly JsonEncodedText discriminatorProperty; |
|||
private readonly string discriminatorName; |
|||
private readonly TypeRegistry typeRegistry; |
|||
|
|||
public PolymorphicConverter(TypeRegistry typeRegistry) |
|||
{ |
|||
this.typeRegistry = typeRegistry; |
|||
|
|||
typeRegistry.TryGetConfig<T>(out var config); |
|||
|
|||
discriminatorName = config?.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty; |
|||
discriminatorProperty = JsonEncodedText.Encode(discriminatorName); |
|||
} |
|||
|
|||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) |
|||
{ |
|||
// Creating a copy of the reader (The derived deserialisation has to be done from the start)
|
|||
Utf8JsonReader typeReader = reader; |
|||
|
|||
if (typeReader.TokenType != JsonTokenType.StartObject) |
|||
{ |
|||
throw new JsonException(); |
|||
} |
|||
|
|||
while (typeReader.Read()) |
|||
{ |
|||
if (typeReader.TokenType == JsonTokenType.PropertyName && IsDiscriminiator(typeReader)) |
|||
{ |
|||
// Advance the reader to the property value
|
|||
typeReader.Read(); |
|||
|
|||
if (typeReader.TokenType != JsonTokenType.String) |
|||
{ |
|||
ThrowHelper.JsonException($"Expected string discriminator value, got '{reader.TokenType}'"); |
|||
return default!; |
|||
} |
|||
|
|||
// Resolve the type from the discriminator value.
|
|||
var type = GetDiscriminatorType(typeReader.GetString()!); |
|||
|
|||
// Perform the actual deserialization with the original reader
|
|||
return (T)JsonSerializer.Deserialize(ref reader, type, options)!; |
|||
} |
|||
else if (typeReader.TokenType == JsonTokenType.StartObject || typeReader.TokenType == JsonTokenType.StartArray) |
|||
{ |
|||
if (!typeReader.TrySkip()) |
|||
{ |
|||
typeReader.Skip(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ThrowHelper.JsonException($"Object has no discriminator '{discriminatorName}."); |
|||
return default!; |
|||
} |
|||
|
|||
private bool IsDiscriminiator(Utf8JsonReader typeReader) |
|||
{ |
|||
return |
|||
typeReader.ValueTextEquals(discriminatorProperty.EncodedUtf8Bytes) || |
|||
typeReader.ValueTextEquals(Constants.DefaultDiscriminatorProperty); |
|||
} |
|||
|
|||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) |
|||
{ |
|||
EnsureTypeResolver(options); |
|||
|
|||
JsonSerializer.Serialize<object>(writer, value!, options); |
|||
} |
|||
|
|||
private static void EnsureTypeResolver(JsonSerializerOptions options) |
|||
{ |
|||
if (options.TypeInfoResolver is not PolymorphicTypeResolver) |
|||
{ |
|||
ThrowHelper.JsonException($"TypeInfoResolver must be of type PolymorphicTypeResolver."); |
|||
} |
|||
} |
|||
|
|||
private Type GetDiscriminatorType(string name) |
|||
{ |
|||
if (!typeRegistry.TryGetType<T>(name, out var type)) |
|||
{ |
|||
ThrowHelper.JsonException($"Object has invalid discriminator '{name}'."); |
|||
return default!; |
|||
} |
|||
|
|||
return type; |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text.Json; |
|||
using System.Text.Json.Serialization.Metadata; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Infrastructure.Json.System; |
|||
|
|||
public sealed class PolymorphicTypeResolver : DefaultJsonTypeInfoResolver |
|||
{ |
|||
private readonly TypeRegistry typeRegistry; |
|||
|
|||
public PolymorphicTypeResolver(TypeRegistry typeRegistry) |
|||
{ |
|||
Guard.NotNull(typeRegistry); |
|||
|
|||
this.typeRegistry = typeRegistry; |
|||
} |
|||
|
|||
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) |
|||
{ |
|||
var typeInfo = base.GetTypeInfo(type, options); |
|||
|
|||
var baseType = type.BaseType; |
|||
|
|||
while (baseType != null) |
|||
{ |
|||
if (typeRegistry.TryGetConfig(baseType, out var config) && config.TryGetName(type, out var typeName)) |
|||
{ |
|||
var discriminiatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty; |
|||
var discriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), discriminiatorName); |
|||
|
|||
discriminatorField.Get = x => |
|||
{ |
|||
return typeName; |
|||
}; |
|||
|
|||
typeInfo.Properties.Insert(0, discriminatorField); |
|||
} |
|||
|
|||
baseType = baseType.BaseType; |
|||
} |
|||
|
|||
return typeInfo; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Reflection; |
|||
|
|||
namespace Squidex.Infrastructure.Reflection; |
|||
|
|||
public sealed class AssemblyTypeProvider<T> : ITypeProvider where T : class |
|||
{ |
|||
private readonly Assembly assembly; |
|||
|
|||
public string? DiscriminatorProperty { get; } |
|||
|
|||
public AssemblyTypeProvider(string? discriminatorProperty = null) |
|||
: this(typeof(T).Assembly, discriminatorProperty) |
|||
{ |
|||
} |
|||
|
|||
public AssemblyTypeProvider(Assembly assembly, string? discriminatorProperty = null) |
|||
{ |
|||
Guard.NotNull(assembly); |
|||
|
|||
this.assembly = assembly; |
|||
|
|||
DiscriminatorProperty = discriminatorProperty; |
|||
} |
|||
|
|||
public void Map(TypeRegistry typeRegistry) |
|||
{ |
|||
var baseType = typeof(T); |
|||
|
|||
foreach (var derivedType in assembly.GetTypes()) |
|||
{ |
|||
if (derivedType.IsAssignableTo(baseType) && !derivedType.IsAbstract) |
|||
{ |
|||
var typeName = derivedType.GetCustomAttribute<TypeNameAttribute>()?.TypeName; |
|||
|
|||
if (string.IsNullOrWhiteSpace(typeName)) |
|||
{ |
|||
typeName = derivedType.TypeName(false, baseType.Name); |
|||
} |
|||
|
|||
typeRegistry.Add<T>(derivedType, typeName); |
|||
} |
|||
} |
|||
|
|||
typeRegistry.Discriminator<T>(DiscriminatorProperty); |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Reflection; |
|||
|
|||
[AttributeUsage(AttributeTargets.Property)] |
|||
public sealed class IgnoreEqualsAttribute : Attribute |
|||
{ |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace Squidex.Infrastructure.Reflection; |
|||
|
|||
public sealed class TypeConfig |
|||
{ |
|||
private readonly List<(Type DerivedType, string TypeName)> derivedTypes = new List<(Type DervicedType, string TypeName)>(); |
|||
private Dictionary<string, Type>? mapByName; |
|||
private Dictionary<Type, string>? mapByType; |
|||
|
|||
public string? DiscriminatorProperty { get; set; } |
|||
|
|||
public IReadOnlyList<(Type DerivedType, string TypeName)> DerivedTypes |
|||
{ |
|||
get => derivedTypes; |
|||
} |
|||
|
|||
internal void Add(Type derivedType, string typeName) |
|||
{ |
|||
Guard.NotNull(derivedType); |
|||
Guard.NotNullOrEmpty(typeName); |
|||
|
|||
if (!derivedTypes.Contains((derivedType, typeName))) |
|||
{ |
|||
derivedTypes.Add((derivedType, typeName)); |
|||
} |
|||
|
|||
var (conflict, _) = derivedTypes.Find(x => x.TypeName == typeName && x.DerivedType != derivedType); |
|||
|
|||
if (conflict != null) |
|||
{ |
|||
ThrowHelper.ArgumentException($"Type name '{typeName}' is already used by type '{conflict}", nameof(typeName)); |
|||
} |
|||
|
|||
mapByName = null; |
|||
mapByType = null; |
|||
} |
|||
|
|||
public bool TryGetType(string typeName, [MaybeNullWhen(false)] out Type derivedType) |
|||
{ |
|||
var map = mapByName ??= BuildMapByName(); |
|||
|
|||
return map.TryGetValue(typeName, out derivedType); |
|||
} |
|||
|
|||
public bool TryGetName(Type derivedType, [MaybeNullWhen(false)] out string typeName) |
|||
{ |
|||
var map = mapByType ??= BuildMapByType(); |
|||
|
|||
return map.TryGetValue(derivedType, out typeName); |
|||
} |
|||
|
|||
private Dictionary<string, Type> BuildMapByName() |
|||
{ |
|||
var result = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
foreach (var (derivedType, typeName) in derivedTypes) |
|||
{ |
|||
result[typeName] = derivedType; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private Dictionary<Type, string> BuildMapByType() |
|||
{ |
|||
var result = new Dictionary<Type, string>(); |
|||
|
|||
foreach (var (derivedType, typeName) in derivedTypes) |
|||
{ |
|||
result[derivedType] = typeName; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Runtime.Serialization; |
|||
|
|||
namespace Squidex.Infrastructure.Reflection; |
|||
|
|||
[Serializable] |
|||
public class TypeNameNotFoundException : Exception |
|||
{ |
|||
public TypeNameNotFoundException(string? message = null, Exception? inner = null) |
|||
: base(message, inner) |
|||
{ |
|||
} |
|||
|
|||
protected TypeNameNotFoundException(SerializationInfo info, StreamingContext context) |
|||
: base(info, context) |
|||
{ |
|||
} |
|||
} |
|||
@ -1,156 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Reflection; |
|||
|
|||
namespace Squidex.Infrastructure.Reflection; |
|||
|
|||
public sealed class TypeNameRegistry |
|||
{ |
|||
private readonly Dictionary<Type, string> namesByType = new Dictionary<Type, string>(); |
|||
private readonly Dictionary<string, Type> typesByName = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
public TypeNameRegistry(IEnumerable<ITypeProvider>? providers = null) |
|||
{ |
|||
if (providers != null) |
|||
{ |
|||
foreach (var provider in providers) |
|||
{ |
|||
Map(provider); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public TypeNameRegistry MapObsolete(Type type, string name) |
|||
{ |
|||
Guard.NotNull(type); |
|||
Guard.NotNull(name); |
|||
|
|||
lock (namesByType) |
|||
{ |
|||
if (typesByName.TryGetValue(name, out var existingType) && existingType != type) |
|||
{ |
|||
var message = $"The name '{name}' is already registered with type '{typesByName[name]}'"; |
|||
|
|||
ThrowHelper.ArgumentException(message, nameof(type)); |
|||
} |
|||
|
|||
typesByName[name] = type; |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public TypeNameRegistry Map(ITypeProvider provider) |
|||
{ |
|||
Guard.NotNull(provider); |
|||
|
|||
provider.Map(this); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public TypeNameRegistry Map(Type type) |
|||
{ |
|||
Guard.NotNull(type); |
|||
|
|||
var typeNameAttribute = type.GetCustomAttribute<TypeNameAttribute>(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName)) |
|||
{ |
|||
Map(type, typeNameAttribute.TypeName); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public TypeNameRegistry Map(Type type, string name) |
|||
{ |
|||
Guard.NotNull(type); |
|||
Guard.NotNull(name); |
|||
|
|||
lock (namesByType) |
|||
{ |
|||
if (namesByType.TryGetValue(type, out var existingName) && existingName != name) |
|||
{ |
|||
var message = $"The type '{type}' is already registered with name '{namesByType[type]}'"; |
|||
|
|||
ThrowHelper.ArgumentException(message, nameof(type)); |
|||
} |
|||
|
|||
namesByType[type] = name; |
|||
|
|||
if (typesByName.TryGetValue(name, out var existingType) && existingType != type) |
|||
{ |
|||
var message = $"The name '{name}' is already registered with type '{typesByName[name]}'"; |
|||
|
|||
ThrowHelper.ArgumentException(message, nameof(type)); |
|||
} |
|||
|
|||
typesByName[name] = type; |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public TypeNameRegistry MapUnmapped(Assembly assembly) |
|||
{ |
|||
foreach (var type in assembly.GetTypes()) |
|||
{ |
|||
if (!namesByType.ContainsKey(type)) |
|||
{ |
|||
Map(type); |
|||
} |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public string GetName<T>() |
|||
{ |
|||
return GetName(typeof(T)); |
|||
} |
|||
|
|||
public string? GetNameOrNull<T>() |
|||
{ |
|||
return GetNameOrNull(typeof(T)); |
|||
} |
|||
|
|||
public string? GetNameOrNull(Type type) |
|||
{ |
|||
return namesByType.GetValueOrDefault(type); |
|||
} |
|||
|
|||
public Type? GetTypeOrNull(string name) |
|||
{ |
|||
return typesByName.GetValueOrDefault(name); |
|||
} |
|||
|
|||
public string GetName(Type type) |
|||
{ |
|||
var result = namesByType.GetValueOrDefault(type); |
|||
|
|||
if (result == null) |
|||
{ |
|||
throw new TypeNameNotFoundException($"There is no name for type '{type}"); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public Type GetType(string name) |
|||
{ |
|||
var result = typesByName.GetValueOrDefault(name); |
|||
|
|||
if (result == null) |
|||
{ |
|||
throw new TypeNameNotFoundException($"There is no type for name '{name}"); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace Squidex.Infrastructure.Reflection; |
|||
|
|||
public sealed class TypeRegistry |
|||
{ |
|||
private readonly Dictionary<Type, TypeConfig> configs = new Dictionary<Type, TypeConfig>(); |
|||
|
|||
public TypeRegistry(IEnumerable<ITypeProvider>? providers = null) |
|||
{ |
|||
if (providers != null) |
|||
{ |
|||
foreach (var provider in providers) |
|||
{ |
|||
Map(provider); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public TypeRegistry Map(ITypeProvider provider) |
|||
{ |
|||
Guard.NotNull(provider); |
|||
|
|||
provider.Map(this); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public TypeRegistry Add<TBase, TDerived>(string typeName) where TDerived : TBase where TBase : class |
|||
{ |
|||
return Add<TBase>(typeof(TDerived), typeName); |
|||
} |
|||
|
|||
public TypeRegistry Add<T>(Type derivedType, string typeName) where T : class |
|||
{ |
|||
lock (configs) |
|||
{ |
|||
configs.GetOrAddNew(typeof(T)).Add(derivedType, typeName); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public TypeRegistry Discriminator<T>(string? discriminiatorProperty) |
|||
{ |
|||
lock (configs) |
|||
{ |
|||
configs.GetOrAddNew(typeof(T)).DiscriminatorProperty ??= discriminiatorProperty; |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
public string GetName<TBase, TDerived>() where TDerived : TBase where TBase : class |
|||
{ |
|||
return GetName<TBase>(typeof(TDerived)); |
|||
} |
|||
|
|||
public string GetName<T>(Type derivedType) where T : class |
|||
{ |
|||
if (!TryGetName<T>(derivedType, out var name)) |
|||
{ |
|||
ThrowHelper.ArgumentException($"Unknown derived type {derivedType}.", nameof(derivedType)); |
|||
return default!; |
|||
} |
|||
|
|||
return name; |
|||
} |
|||
|
|||
public Type GetType<T>(string typeName) where T : class |
|||
{ |
|||
if (!TryGetType<T>(typeName, out var name)) |
|||
{ |
|||
ThrowHelper.ArgumentException($"Unknown derived type {typeName}.", nameof(typeName)); |
|||
return default!; |
|||
} |
|||
|
|||
return name; |
|||
} |
|||
|
|||
public bool TryGetName<T>(Type derivedType, [MaybeNullWhen(false)] out string typeName) where T : class |
|||
{ |
|||
typeName = null!; |
|||
|
|||
return TryGetConfig<T>(out var config) && config.TryGetName(derivedType, out typeName); |
|||
} |
|||
|
|||
public bool TryGetType<T>(string typeName, [MaybeNullWhen(false)] out Type derivedType) where T : class |
|||
{ |
|||
derivedType = null!; |
|||
|
|||
return TryGetConfig<T>(out var config) && config.TryGetType(typeName, out derivedType); |
|||
} |
|||
|
|||
public bool TryGetConfig<T>([MaybeNullWhen(false)] out TypeConfig config) where T : class |
|||
{ |
|||
return TryGetConfig(typeof(T), out config); |
|||
} |
|||
|
|||
public bool TryGetConfig(Type baseType, [MaybeNullWhen(false)] out TypeConfig config) |
|||
{ |
|||
return configs.TryGetValue(baseType, out config); |
|||
} |
|||
} |
|||
@ -1,108 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Reflection; |
|||
using System.Runtime.Serialization; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Json.System; |
|||
|
|||
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
|||
|
|||
namespace Squidex.Web.Json; |
|||
|
|||
public class JsonInheritanceConverter<T> : InheritanceConverterBase<T> where T : notnull |
|||
{ |
|||
private static readonly Lazy<Dictionary<string, Type>> DefaultMapping = new Lazy<Dictionary<string, Type>>(() => |
|||
{ |
|||
var baseName = typeof(T).Name; |
|||
|
|||
var result = new Dictionary<string, Type>(); |
|||
|
|||
void AddType(Type type) |
|||
{ |
|||
var typeName = type.Name; |
|||
|
|||
if (typeName.EndsWith(baseName, StringComparison.CurrentCulture)) |
|||
{ |
|||
typeName = typeName[..^baseName.Length]; |
|||
} |
|||
|
|||
result[typeName] = type; |
|||
} |
|||
|
|||
foreach (var attribute in typeof(T).GetCustomAttributes<KnownTypeAttribute>()) |
|||
{ |
|||
if (attribute.Type != null) |
|||
{ |
|||
if (!attribute.Type.IsAbstract) |
|||
{ |
|||
AddType(attribute.Type); |
|||
} |
|||
} |
|||
else if (!string.IsNullOrWhiteSpace(attribute.MethodName)) |
|||
{ |
|||
var method = typeof(T).GetMethod(attribute.MethodName); |
|||
|
|||
if (method != null && method.IsStatic) |
|||
{ |
|||
var types = (IEnumerable<Type>)method.Invoke(null, Array.Empty<object>())!; |
|||
|
|||
foreach (var type in types) |
|||
{ |
|||
if (!type.IsAbstract) |
|||
{ |
|||
AddType(type); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}); |
|||
|
|||
private readonly IReadOnlyDictionary<string, Type> mapping; |
|||
|
|||
public JsonInheritanceConverter() |
|||
: this(null, DefaultMapping.Value) |
|||
{ |
|||
} |
|||
|
|||
public JsonInheritanceConverter(string? discriminatorName) |
|||
: this(discriminatorName, DefaultMapping.Value) |
|||
{ |
|||
} |
|||
|
|||
public JsonInheritanceConverter(string? discriminatorName, IReadOnlyDictionary<string, Type> mapping) |
|||
: base(GetDiscriminatorName(discriminatorName)) |
|||
{ |
|||
this.mapping = mapping ?? DefaultMapping.Value; |
|||
} |
|||
|
|||
private static string GetDiscriminatorName(string? discriminatorName) |
|||
{ |
|||
var attribute = typeof(T).GetCustomAttribute<JsonInheritanceConverterAttribute>(); |
|||
|
|||
return attribute?.DiscriminatorName ?? discriminatorName ?? "discriminator"; |
|||
} |
|||
|
|||
public override Type GetDiscriminatorType(string name, Type typeToConvert) |
|||
{ |
|||
if (!mapping.TryGetValue(name, out var type)) |
|||
{ |
|||
ThrowHelper.JsonException($"Could not find subtype of '{typeToConvert.Name}' with discriminator '{name}'."); |
|||
return default!; |
|||
} |
|||
|
|||
return type; |
|||
} |
|||
|
|||
public override string GetDiscriminatorValue(Type type) |
|||
{ |
|||
return mapping.FirstOrDefault(x => x.Value == type).Key ?? type.Name; |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text.Json.Serialization; |
|||
|
|||
namespace Squidex.Web.Json; |
|||
|
|||
public sealed class JsonInheritanceConverterAttribute : JsonConverterAttribute |
|||
{ |
|||
public string DiscriminatorName { get; } |
|||
|
|||
public JsonInheritanceConverterAttribute(Type baseType, string discriminatorName = "$type") |
|||
: base(typeof(JsonInheritanceConverter<>).MakeGenericType(baseType)) |
|||
{ |
|||
DiscriminatorName = discriminatorName; |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using NJsonSchema; |
|||
using NJsonSchema.Generation; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Config.OpenApi; |
|||
|
|||
public sealed class DiscriminatorProcessor : ISchemaProcessor |
|||
{ |
|||
private readonly TypeRegistry typeRegistry; |
|||
|
|||
public DiscriminatorProcessor(TypeRegistry typeRegistry) |
|||
{ |
|||
this.typeRegistry = typeRegistry; |
|||
} |
|||
|
|||
public void Process(SchemaProcessorContext context) |
|||
{ |
|||
if (context.Schema.DiscriminatorObject != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!typeRegistry.TryGetConfig(context.ContextualType.Type, out var config) || |
|||
config.DerivedTypes.Count <= 0 || |
|||
config.DiscriminatorProperty == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var discriminatorName = config.DiscriminatorProperty; |
|||
var discriminatorObject = new OpenApiDiscriminator |
|||
{ |
|||
PropertyName = discriminatorName |
|||
}; |
|||
|
|||
var schema = context.Schema; |
|||
|
|||
foreach (var (derivedType, typeName) in config.DerivedTypes) |
|||
{ |
|||
var derivedSchema = context.Generator.Generate(derivedType, context.Resolver); |
|||
|
|||
discriminatorObject.Mapping[typeName] = new JsonSchema |
|||
{ |
|||
Reference = derivedSchema |
|||
}; |
|||
} |
|||
|
|||
schema.DiscriminatorObject = discriminatorObject; |
|||
|
|||
if (!schema.Properties.TryGetValue(discriminatorName, out var existingProperty)) |
|||
{ |
|||
schema.Properties[discriminatorName] = existingProperty = new JsonSchemaProperty |
|||
{ |
|||
Type = JsonObjectType.String |
|||
}; |
|||
} |
|||
|
|||
existingProperty.IsRequired = true; |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using NJsonSchema; |
|||
using NSwag.Generation.Processors; |
|||
using NSwag.Generation.Processors.Contexts; |
|||
|
|||
namespace Squidex.Areas.Api.Config.OpenApi; |
|||
|
|||
public sealed class FixProcessor : IOperationProcessor |
|||
{ |
|||
private static readonly JsonSchema StringSchema = new JsonSchema { Type = JsonObjectType.String }; |
|||
|
|||
public bool Process(OperationProcessorContext context) |
|||
{ |
|||
foreach (var (_, parameter) in context.Parameters) |
|||
{ |
|||
if (parameter.IsRequired && parameter.Schema is { Type: JsonObjectType.String }) |
|||
{ |
|||
parameter.Schema = StringSchema; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using NJsonSchema.Generation; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Areas.Api.Config.OpenApi; |
|||
|
|||
public sealed class SchemaNameGenerator : DefaultSchemaNameGenerator |
|||
{ |
|||
public override string Generate(Type type) |
|||
{ |
|||
if (type.BaseType == typeof(RuleAction)) |
|||
{ |
|||
return $"{type.TypeName(false, "Action")}RuleActionDto"; |
|||
} |
|||
|
|||
if (type == typeof(RuleAction)) |
|||
{ |
|||
return $"RuleActionDto"; |
|||
} |
|||
|
|||
return base.Generate(type); |
|||
} |
|||
} |
|||
@ -1,54 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Text.RegularExpressions; |
|||
using Namotion.Reflection; |
|||
using NSwag; |
|||
using NSwag.Generation.Processors; |
|||
using NSwag.Generation.Processors.Contexts; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Areas.Api.Config.OpenApi; |
|||
|
|||
public sealed class XmlResponseTypesProcessor : IOperationProcessor |
|||
{ |
|||
private static readonly Regex ResponseRegex = new Regex("(?<Code>[0-9]{3})[\\s]*=((>)|>)[\\s]*(?<Description>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture); |
|||
|
|||
public bool Process(OperationProcessorContext context) |
|||
{ |
|||
var operation = context.OperationDescription.Operation; |
|||
|
|||
var returnsDescription = context.MethodInfo.GetXmlDocsTag("returns"); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(returnsDescription)) |
|||
{ |
|||
foreach (var match in ResponseRegex.Matches(returnsDescription).OfType<Match>()) |
|||
{ |
|||
var statusCode = match.Groups["Code"].Value; |
|||
|
|||
if (!operation.Responses.TryGetValue(statusCode, out var response)) |
|||
{ |
|||
response = new OpenApiResponse(); |
|||
|
|||
operation.Responses[statusCode] = response; |
|||
} |
|||
|
|||
var description = match.Groups["Description"].Value; |
|||
|
|||
if (description.Contains("=>", StringComparison.Ordinal)) |
|||
{ |
|||
ThrowHelper.InvalidOperationException("Description not formatted correcly."); |
|||
return default!; |
|||
} |
|||
|
|||
response.Description = description; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue