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