From fd178e8e371acf4e6a7c795ffdcf3627006057cf Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 27 Nov 2016 00:55:37 +0100 Subject: [PATCH] A lot more tests --- Squidex.sln.DotSettings | 1 + src/Squidex.Core/Schemas/FieldRegistry.cs | 1 + .../Schemas/Json/SchemaJsonSerializer.cs | 101 ++++++++++++++++++ .../Schemas/NumberFieldProperties.cs | 2 +- src/Squidex.Core/Schemas/StringField.cs | 6 ++ .../Schemas/StringFieldProperties.cs | 14 +++ .../Schemas/Validators/PatternValidator.cs | 2 +- .../TypeNameRegistry.cs | 14 ++- .../Schemas/Models/FieldModel.cs | 51 --------- .../Schemas/Models/SchemaModel.cs | 60 ----------- .../Schemas/MongoSchemaEntity.cs | 18 ++-- .../Schemas/MongoSchemaRepository.cs | 24 ++--- .../Utils/EntityMapper.cs | 10 +- .../Config/Domain/InfrastructureModule.cs | 5 + .../Models/Converters/SchemaConverter.cs | 68 ++++++++++++ .../Api/Schemas/Models/FieldDto.cs | 8 +- .../Api/Schemas/Models/Fields/NumberField.cs | 8 ++ .../Api/Schemas/Models/Fields/StringField.cs | 15 ++- .../Api/Schemas/SchemaFieldsController.cs | 12 ++- .../Api/Schemas/SchemasController.cs | 5 +- .../Schemas/FieldRegistryTests.cs | 5 +- .../Schemas/Json/JsonSerializerTests.cs | 52 +++++++++ .../Schemas/StringFieldPropertiesTests.cs | 1 - .../Schemas/StringFieldTests.cs | 20 +++- .../Validators/PatternValidatorTests.cs | 8 +- .../TypeNameRegistryTests.cs | 17 ++- 26 files changed, 356 insertions(+), 172 deletions(-) create mode 100644 src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs delete mode 100644 src/Squidex.Store.MongoDb/Schemas/Models/FieldModel.cs delete mode 100644 src/Squidex.Store.MongoDb/Schemas/Models/SchemaModel.cs create mode 100644 src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs create mode 100644 tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs diff --git a/Squidex.sln.DotSettings b/Squidex.sln.DotSettings index ff47dbd96..0a5047168 100644 --- a/Squidex.sln.DotSettings +++ b/Squidex.sln.DotSettings @@ -39,6 +39,7 @@ <?xml version="1.0" encoding="utf-16"?><Profile name="Namespaces"><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> <?xml version="1.0" encoding="utf-16"?><Profile name="Typescript"><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs></Profile> + False SingleQuoted ========================================================================== $FILENAME$ diff --git a/src/Squidex.Core/Schemas/FieldRegistry.cs b/src/Squidex.Core/Schemas/FieldRegistry.cs index d3c4b3a26..7974e0cc9 100644 --- a/src/Squidex.Core/Schemas/FieldRegistry.cs +++ b/src/Squidex.Core/Schemas/FieldRegistry.cs @@ -52,6 +52,7 @@ namespace Squidex.Core.Schemas public FieldRegistry() { Add((id, name, properties) => new NumberField(id, name, (NumberFieldProperties)properties)); + Add((id, name, properties) => new StringField(id, name, (StringFieldProperties)properties)); } public void Add(FactoryFunction fieldFactory) diff --git a/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs b/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs new file mode 100644 index 000000000..b12dd85d9 --- /dev/null +++ b/src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs @@ -0,0 +1,101 @@ +// ========================================================================== +// SchemaJsonSerializer.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Squidex.Infrastructure; +// ReSharper disable UseObjectOrCollectionInitializer + +namespace Squidex.Core.Schemas.Json +{ + public sealed class SchemaJsonSerializer + { + private readonly FieldRegistry fieldRegistry; + private readonly JsonSerializer serializer; + + public class FieldModel + { + public string Name; + + public bool IsHidden; + + public bool IsDisabled; + + public FieldProperties Properties; + } + + public sealed class SchemaModel + { + public string Name; + + public SchemaProperties Properties; + + public Dictionary Fields; + } + + public SchemaJsonSerializer(FieldRegistry fieldRegistry, JsonSerializerSettings serializerSettings) + { + Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); + Guard.NotNull(serializerSettings, nameof(serializerSettings)); + + this.fieldRegistry = fieldRegistry; + + serializer = JsonSerializer.Create(serializerSettings); + } + + public JToken Serialize(Schema schema) + { + var model = new SchemaModel { Name = schema.Name, Properties = schema.Properties }; + + model.Fields = + schema.Fields + .Select(x => + new KeyValuePair(x.Key, + new FieldModel + { + Name = x.Value.Name, + IsHidden = x.Value.IsHidden, + IsDisabled = x.Value.IsDisabled, + Properties = x.Value.RawProperties + })) + .ToDictionary(x => x.Key, x => x.Value); + + return JToken.FromObject(model, serializer); + } + + public Schema Deserialize(JToken token) + { + var model = token.ToObject(serializer); + + var schema = Schema.Create(model.Name, model.Properties); + + foreach (var kvp in model.Fields) + { + var fieldModel = kvp.Value; + + var field = fieldRegistry.CreateField(kvp.Key, fieldModel.Name, fieldModel.Properties); + + if (fieldModel.IsDisabled) + { + field = field.Disable(); + } + + if (fieldModel.IsHidden) + { + field = field.Hide(); + } + + schema = schema.AddOrUpdateField(field); + } + + return schema; + } + } +} \ No newline at end of file diff --git a/src/Squidex.Core/Schemas/NumberFieldProperties.cs b/src/Squidex.Core/Schemas/NumberFieldProperties.cs index 9aec4a111..f58b8fac1 100644 --- a/src/Squidex.Core/Schemas/NumberFieldProperties.cs +++ b/src/Squidex.Core/Schemas/NumberFieldProperties.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure; namespace Squidex.Core.Schemas { - [TypeName("number")] + [TypeName("NumberField")] public sealed class NumberFieldProperties : FieldProperties { private double? maxValue; diff --git a/src/Squidex.Core/Schemas/StringField.cs b/src/Squidex.Core/Schemas/StringField.cs index 6048f0d8d..8d1b5a434 100644 --- a/src/Squidex.Core/Schemas/StringField.cs +++ b/src/Squidex.Core/Schemas/StringField.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Collections.Generic; +using System.Linq; using Squidex.Core.Schemas.Validators; using Squidex.Infrastructure; @@ -35,6 +36,11 @@ namespace Squidex.Core.Schemas { yield return new PatternValidator(Properties.Pattern, Properties.PatternMessage); } + + if (Properties.AllowedValues != null) + { + yield return new AllowedValuesValidator(Properties.AllowedValues.ToArray()); + } } protected override object ConvertValue(PropertyValue property) diff --git a/src/Squidex.Core/Schemas/StringFieldProperties.cs b/src/Squidex.Core/Schemas/StringFieldProperties.cs index 74ccf5b8b..1bb2b27ef 100644 --- a/src/Squidex.Core/Schemas/StringFieldProperties.cs +++ b/src/Squidex.Core/Schemas/StringFieldProperties.cs @@ -10,16 +10,19 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Squidex.Infrastructure; +using System.Collections.Immutable; // ReSharper disable ObjectCreationAsStatement namespace Squidex.Core.Schemas { + [TypeName("StringField")] public sealed class StringFieldProperties : FieldProperties { private int? minLength; private int? maxLength; private string pattern; private string patternMessage; + private ImmutableList allowedValues; public int? MinLength { @@ -65,6 +68,17 @@ namespace Squidex.Core.Schemas } } + public ImmutableList AllowedValues + { + get { return allowedValues; } + set + { + ThrowIfFrozen(); + + allowedValues = value; + } + } + protected override IEnumerable ValidateCore() { if (MaxLength.HasValue && MinLength.HasValue && MinLength.Value >= MaxLength.Value) diff --git a/src/Squidex.Core/Schemas/Validators/PatternValidator.cs b/src/Squidex.Core/Schemas/Validators/PatternValidator.cs index fbebf7a4c..45401d8f7 100644 --- a/src/Squidex.Core/Schemas/Validators/PatternValidator.cs +++ b/src/Squidex.Core/Schemas/Validators/PatternValidator.cs @@ -25,7 +25,7 @@ namespace Squidex.Core.Schemas.Validators { this.errorMessage = errorMessage; - regex = new Regex(pattern); + regex = new Regex("^" + pattern + "$"); } public Task ValidateAsync(object value, ICollection errors) diff --git a/src/Squidex.Infrastructure/TypeNameRegistry.cs b/src/Squidex.Infrastructure/TypeNameRegistry.cs index 979062ddd..edfc382b8 100644 --- a/src/Squidex.Infrastructure/TypeNameRegistry.cs +++ b/src/Squidex.Infrastructure/TypeNameRegistry.cs @@ -30,9 +30,12 @@ namespace Squidex.Infrastructure } catch (ArgumentException) { - var message = $"The type '{type}' is already registered with name '{namesByType[type]}'"; + if (namesByType[type] != name) + { + var message = $"The type '{type}' is already registered with name '{namesByType[type]}'"; - throw new ArgumentException(message, nameof(type)); + throw new ArgumentException(message, nameof(type)); + } } try @@ -41,9 +44,12 @@ namespace Squidex.Infrastructure } catch (ArgumentException) { - var message = $"The name '{name}' is already registered with type '{typesByName[name]}'"; + if (typesByName[name] != type) + { + var message = $"The name '{name}' is already registered with type '{typesByName[name]}'"; - throw new ArgumentException(message, nameof(type)); + throw new ArgumentException(message, nameof(type)); + } } } } diff --git a/src/Squidex.Store.MongoDb/Schemas/Models/FieldModel.cs b/src/Squidex.Store.MongoDb/Schemas/Models/FieldModel.cs deleted file mode 100644 index a1bf0f5c0..000000000 --- a/src/Squidex.Store.MongoDb/Schemas/Models/FieldModel.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ========================================================================== -// FieldDto.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Core.Schemas; - -namespace Squidex.Store.MongoDb.Schemas.Models -{ - public class FieldModel - { - public string Name { get; set; } - - public bool IsHidden { get; set; } - - public bool IsDisabled { get; set; } - - public FieldProperties Properties { get; set; } - - public static FieldModel Create(Field field) - { - return new FieldModel - { - Name = field.Name, - IsHidden = field.IsHidden, - IsDisabled = field.IsDisabled, - Properties = field.RawProperties - }; - } - - public Field ToField(long id, FieldRegistry registry) - { - var field = registry.CreateField(id, Name, Properties); - - if (IsHidden) - { - field = field.Hide(); - } - - if (IsDisabled) - { - field = field.Disable(); - } - - return field; - } - } -} \ No newline at end of file diff --git a/src/Squidex.Store.MongoDb/Schemas/Models/SchemaModel.cs b/src/Squidex.Store.MongoDb/Schemas/Models/SchemaModel.cs deleted file mode 100644 index ceb46933a..000000000 --- a/src/Squidex.Store.MongoDb/Schemas/Models/SchemaModel.cs +++ /dev/null @@ -1,60 +0,0 @@ -// ========================================================================== -// SchemaDto.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Collections.Generic; -using System.Linq; -using Squidex.Core.Schemas; -using Squidex.Infrastructure; - -// ReSharper disable UseObjectOrCollectionInitializer -// ReSharper disable InvertIf - -namespace Squidex.Store.MongoDb.Schemas.Models -{ - public sealed class SchemaModel - { - public string Name { get; set; } - - public Dictionary Fields { get; set; } - - public SchemaProperties Properties { get; set; } - - public static SchemaModel Create(Schema schema) - { - Guard.NotNull(schema, nameof(schema)); - - var dto = new SchemaModel { Properties = schema.Properties, Name = schema.Name }; - - dto.Fields = - schema.Fields.ToDictionary( - kvp => kvp.Key, - kvp => FieldModel.Create(kvp.Value)); - - return dto; - } - - public Schema ToSchema(FieldRegistry registry) - { - Guard.NotNull(registry, nameof(registry)); - - var schema = Schema.Create(Name, Properties); - - if (Fields != null) - { - foreach (var kvp in Fields) - { - var field = kvp.Value; - - schema = schema.AddOrUpdateField(field.ToField(kvp.Key, registry)); - } - } - - return schema; - } - } -} diff --git a/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs b/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs index 8ed6049af..a77a62039 100644 --- a/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs +++ b/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs @@ -9,17 +9,16 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Newtonsoft.Json; using Squidex.Core.Schemas; +using Squidex.Core.Schemas.Json; using Squidex.Read.Schemas.Repositories; -using Squidex.Store.MongoDb.Schemas.Models; using Squidex.Store.MongoDb.Utils; namespace Squidex.Store.MongoDb.Schemas { public sealed class MongoSchemaEntity : MongoEntity, ISchemaEntityWithSchema { - private Schema schema; + private Lazy schema; [BsonRequired] [BsonElement] @@ -39,19 +38,14 @@ namespace Squidex.Store.MongoDb.Schemas Schema ISchemaEntityWithSchema.Schema { - get { return schema; } + get { return schema.Value; } } - public void DeserializeSchema(JsonSerializerSettings serializerSettings, FieldRegistry fieldRegistry) + public Lazy DeserializeSchema(SchemaJsonSerializer serializer) { - if (schema != null) - { - return; - } + schema = new Lazy(() => schema != null ? null : serializer.Deserialize(Schema.ToJToken())); - var dto = Schema.ToJsonObject(serializerSettings); - - schema = dto?.ToSchema(fieldRegistry); + return schema; } } } diff --git a/src/Squidex.Store.MongoDb/Schemas/MongoSchemaRepository.cs b/src/Squidex.Store.MongoDb/Schemas/MongoSchemaRepository.cs index 361bb7c0a..aff3a978a 100644 --- a/src/Squidex.Store.MongoDb/Schemas/MongoSchemaRepository.cs +++ b/src/Squidex.Store.MongoDb/Schemas/MongoSchemaRepository.cs @@ -11,8 +11,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MongoDB.Driver; -using Newtonsoft.Json; using Squidex.Core.Schemas; +using Squidex.Core.Schemas.Json; using Squidex.Events.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS; @@ -20,23 +20,23 @@ using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Reflection; using Squidex.Read.Schemas.Repositories; -using Squidex.Store.MongoDb.Schemas.Models; using Squidex.Store.MongoDb.Utils; namespace Squidex.Store.MongoDb.Schemas { public sealed class MongoSchemaRepository : MongoRepositoryBase, ISchemaRepository, ICatchEventConsumer { - private readonly JsonSerializerSettings serializerSettings; + private readonly SchemaJsonSerializer serializer; private readonly FieldRegistry fieldRegistry; - public MongoSchemaRepository(IMongoDatabase database, JsonSerializerSettings serializerSettings, FieldRegistry fieldRegistry) + public MongoSchemaRepository(IMongoDatabase database, SchemaJsonSerializer serializer, FieldRegistry fieldRegistry) : base(database) { - Guard.NotNull(serializerSettings, nameof(serializerSettings)); + Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); - this.serializerSettings = serializerSettings; + this.serializer = serializer; + this.fieldRegistry = fieldRegistry; } @@ -63,7 +63,7 @@ namespace Squidex.Store.MongoDb.Schemas await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted) .FirstOrDefaultAsync(); - entity?.DeserializeSchema(serializerSettings, fieldRegistry); + entity?.DeserializeSchema(serializer); return entity; } @@ -74,7 +74,7 @@ namespace Squidex.Store.MongoDb.Schemas await Collection.Find(s => s.Id == schemaId && !s.IsDeleted) .FirstOrDefaultAsync(); - entity?.DeserializeSchema(serializerSettings, fieldRegistry); + entity?.DeserializeSchema(serializer); return entity; } @@ -161,16 +161,12 @@ namespace Squidex.Store.MongoDb.Schemas private void Serialize(MongoSchemaEntity entity, Schema schema) { - var dto = SchemaModel.Create(schema); - - entity.Schema = dto.ToJsonBsonDocument(serializerSettings); + entity.Schema = serializer.Serialize(schema).ToBsonDocument(); } private Schema Deserialize(MongoSchemaEntity entity) { - var dto = entity?.Schema.ToJsonObject(serializerSettings); - - return dto?.ToSchema(fieldRegistry); + return entity.DeserializeSchema(serializer).Value; } } } diff --git a/src/Squidex.Store.MongoDb/Utils/EntityMapper.cs b/src/Squidex.Store.MongoDb/Utils/EntityMapper.cs index cfeb7165c..f89400358 100644 --- a/src/Squidex.Store.MongoDb/Utils/EntityMapper.cs +++ b/src/Squidex.Store.MongoDb/Utils/EntityMapper.cs @@ -10,7 +10,7 @@ using System; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Driver; -using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Squidex.Infrastructure.CQRS; using Squidex.Read; @@ -34,18 +34,18 @@ namespace Squidex.Store.MongoDb.Utils return Update(entity, headers); } - public static BsonDocument ToJsonBsonDocument(this T value, JsonSerializerSettings settings) + public static BsonDocument ToBsonDocument(this JToken value) { - var json = JsonConvert.SerializeObject(value, settings).Replace("$type", "§type"); + var json = value.ToString().Replace("$type", "§type"); return BsonDocument.Parse(json); } - public static T ToJsonObject(this BsonDocument document, JsonSerializerSettings settings) + public static JToken ToJToken(this BsonDocument document) { var json = document.ToJson().Replace("§type", "$type"); - return JsonConvert.DeserializeObject(json, settings); + return JToken.Parse(json); } public static T Update(T entity, EnvelopeHeaders headers) where T : IEntity diff --git a/src/Squidex/Config/Domain/InfrastructureModule.cs b/src/Squidex/Config/Domain/InfrastructureModule.cs index 8a55aaf2e..0e27d2ab7 100644 --- a/src/Squidex/Config/Domain/InfrastructureModule.cs +++ b/src/Squidex/Config/Domain/InfrastructureModule.cs @@ -10,6 +10,7 @@ using Autofac; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; using Squidex.Core.Schemas; +using Squidex.Core.Schemas.Json; using Squidex.Infrastructure.CQRS.Autofac; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.EventStore; @@ -49,6 +50,10 @@ namespace Squidex.Config.Domain .AsSelf() .SingleInstance(); + builder.RegisterType() + .AsSelf() + .SingleInstance(); + builder.RegisterType() .AsSelf() .SingleInstance(); diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs b/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs new file mode 100644 index 000000000..de149a74e --- /dev/null +++ b/src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs @@ -0,0 +1,68 @@ +// ========================================================================== +// SchemaConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Core.Schemas; +using Squidex.Infrastructure.Reflection; +using Squidex.Read.Schemas.Repositories; +using Dtos = Squidex.Controllers.Api.Schemas.Models.Fields; + +namespace Squidex.Controllers.Api.Schemas.Models.Converters +{ + public static class SchemaConverter + { + private static readonly Dictionary> Factories = new Dictionary> + { + { + typeof(NumberField), + field => + { + var dto = new Dtos.NumberField(); + + SimpleMapper.Map(field, dto); + SimpleMapper.Map((NumberFieldProperties)field.RawProperties, dto); + + return dto; + } + }, + { + typeof(StringField), + field => + { + var dto = new Dtos.StringField(); + + SimpleMapper.Map(field, dto); + SimpleMapper.Map((StringFieldProperties)field.RawProperties, dto); + + return dto; + } + } + }; + + public static SchemaDetailsDto ToModel(this ISchemaEntityWithSchema entity) + { + var dto = new SchemaDetailsDto(); + + SimpleMapper.Map(entity, dto); + SimpleMapper.Map(entity.Schema, dto); + SimpleMapper.Map(entity.Schema.Properties, dto); + + dto.Fields = new List(); + + foreach (var field in entity.Schema.Fields.Values) + { + var fieldDto = Factories[field.RawProperties.GetType()](field); + + dto.Fields.Add(fieldDto); + } + + return dto; + } + } +} diff --git a/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs index dace217f0..e9fae2f05 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs @@ -10,14 +10,14 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Newtonsoft.Json; using NJsonSchema.Converters; -using Squidex.Controllers.Api.Schemas.Models.Fields; +using Squidex.Core.Schemas; namespace Squidex.Controllers.Api.Schemas.Models { - [JsonConverter(typeof(JsonInheritanceConverter), "fieldType")] + [JsonConverter(typeof(JsonInheritanceConverter), "$type")] [KnownType(typeof(NumberField))] [KnownType(typeof(StringField))] - public class FieldDto + public abstract class FieldDto { /// /// The name of the field. Must be unique within the schema. @@ -48,5 +48,7 @@ namespace Squidex.Controllers.Api.Schemas.Models /// Indicates if the field is required. /// public bool IsRequired { get; set; } + + public abstract FieldProperties ToProperties(); } } diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberField.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberField.cs index a2290fe7f..bebcbd6e2 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberField.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberField.cs @@ -6,6 +6,9 @@ // All rights reserved. // ========================================================================== +using Squidex.Core.Schemas; +using Squidex.Infrastructure.Reflection; + namespace Squidex.Controllers.Api.Schemas.Models.Fields { public class NumberField : FieldDto @@ -29,5 +32,10 @@ namespace Squidex.Controllers.Api.Schemas.Models.Fields /// The allowed values for the field value. /// public double[] AllowedValues { get; set; } + + public override FieldProperties ToProperties() + { + return SimpleMapper.Map(this, new NumberFieldProperties()); + } } } diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Fields/StringField.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/StringField.cs index f3516b5fa..4f3ab24b9 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/Fields/StringField.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/StringField.cs @@ -6,6 +6,9 @@ // All rights reserved. // ========================================================================== +using Squidex.Core.Schemas; +using Squidex.Infrastructure.Reflection; + namespace Squidex.Controllers.Api.Schemas.Models.Fields { public sealed class StringField : FieldDto @@ -20,6 +23,11 @@ namespace Squidex.Controllers.Api.Schemas.Models.Fields /// public string Pattern { get; set; } + /// + /// The validation message for the pattern. + /// + public string PatternMessage { get; set; } + /// /// The minimum allowed length for the field value. /// @@ -33,6 +41,11 @@ namespace Squidex.Controllers.Api.Schemas.Models.Fields /// /// The allowed values for the field value. /// - public double[] AllowedValues { get; set; } + public string[] AllowedValues { get; set; } + + public override FieldProperties ToProperties() + { + return SimpleMapper.Map(this, new StringFieldProperties()); + } } } diff --git a/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs b/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs index 1052db697..c2084a390 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs @@ -9,10 +9,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; using NSwag.Annotations; using Squidex.Infrastructure.CQRS.Commands; -using Squidex.Infrastructure.Reflection; using Squidex.Controllers.Api.Schemas.Models; +using Squidex.Infrastructure; using Squidex.Pipeline; using Squidex.Write.Schemas.Commands; @@ -51,7 +52,12 @@ namespace Squidex.Controllers.Api.Schemas [ProducesResponseType(typeof(ErrorDto), 400)] public async Task PostField(string app, string name, [FromBody] FieldDto model) { - var command = SimpleMapper.Map(model, new AddField()); + var properties = model.ToProperties(); + + var fieldName = model.Name; + var fieldType = TypeNameRegistry.GetName(properties.GetType()); + + var command = new AddField { Name = fieldName, Type = fieldType, Properties = JToken.FromObject(properties) }; var context = await CommandBus.PublishAsync(command); var result = context.Result(); @@ -77,7 +83,7 @@ namespace Squidex.Controllers.Api.Schemas [ProducesResponseType(typeof(ErrorDto), 400)] public async Task PutField(string app, string name, long id, [FromBody] FieldDto model) { - var command = SimpleMapper.Map(model, new UpdateField()); + var command = new UpdateField { FieldId = id, Properties = JToken.FromObject(model) }; await CommandBus.PublishAsync(command); diff --git a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs index 0282e1682..96092f067 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs @@ -15,6 +15,7 @@ using NSwag.Annotations; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Controllers.Api.Schemas.Models; +using Squidex.Controllers.Api.Schemas.Models.Converters; using Squidex.Pipeline; using Squidex.Read.Schemas.Repositories; using Squidex.Write.Schemas.Commands; @@ -76,7 +77,9 @@ namespace Squidex.Controllers.Api.Schemas return NotFound(); } - return Ok(null); + var model = entity.ToModel(); + + return Ok(model); } /// diff --git a/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs b/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs index 517a8bd35..2ab372436 100644 --- a/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs @@ -28,7 +28,8 @@ namespace Squidex.Core.Tests.Schemas static FieldRegistryTests() { - TypeNameRegistry.Map(typeof(NumberFieldProperties), "number"); + TypeNameRegistry.Map(typeof(NumberFieldProperties), "NumberField"); + TypeNameRegistry.Map(typeof(StringFieldProperties), "StringField"); TypeNameRegistry.Map(typeof(InvalidProperties), "invalid"); } @@ -71,7 +72,7 @@ namespace Squidex.Core.Tests.Schemas [Fact] public void Should_find_registration_by_name() { - var registry = sut.FindByTypeName("number"); + var registry = sut.FindByTypeName("NumberField"); Assert.Equal(typeof(NumberFieldProperties), registry.PropertiesType); } diff --git a/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs b/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs new file mode 100644 index 000000000..f206753f9 --- /dev/null +++ b/tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// JsonSerializerTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Reflection; +using FluentAssertions; +using Newtonsoft.Json; +using Squidex.Core.Schemas; +using Squidex.Core.Schemas.Json; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; +using Xunit; + +namespace Squidex.Core.Tests.Schemas.Json +{ + public class JsonSerializerTests + { + private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); + + static JsonSerializerTests() + { + TypeNameRegistry.Map(typeof(FieldRegistry).GetTypeInfo().Assembly); + + serializerSettings.TypeNameHandling = TypeNameHandling.Auto; + serializerSettings.SerializationBinder = new TypeNameSerializationBinder(); + } + + [Fact] + public void Should_serialize_and_deserialize_schema() + { + var schema = + Schema.Create("my-schema", new SchemaProperties()) + .AddOrUpdateField(new StringField(1, "field1", new StringFieldProperties { Label = "Field1", Pattern = "[0-9]{3}" })) + .AddOrUpdateField(new NumberField(2, "field2", new NumberFieldProperties { Hints = "Hints" })) + .DisableField(1) + .HideField(2); + + + var sut = new SchemaJsonSerializer(new FieldRegistry(), serializerSettings); + + var token = sut.Serialize(schema); + + var deserialized = sut.Deserialize(token); + + deserialized.ShouldBeEquivalentTo(schema); + } + } +} diff --git a/tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs b/tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs index f7387e39f..be69a2fc2 100644 --- a/tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs @@ -12,7 +12,6 @@ using System.Linq; using System.Reflection; using FluentAssertions; using Squidex.Core.Schemas; -using Squidex.Core.Schemas.Validators; using Squidex.Infrastructure; using Xunit; diff --git a/tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs b/tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs index deda03077..b741ec2d7 100644 --- a/tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using Squidex.Core.Schemas; using Squidex.Infrastructure; @@ -47,7 +48,7 @@ namespace Squidex.Core.Tests.Schemas } [Fact] - public async Task Should_add_errors_if_string_shorter_than_max() + public async Task Should_add_errors_if_string_is_shorter_than_min_length() { var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", MinLength = 10 }); @@ -58,7 +59,7 @@ namespace Squidex.Core.Tests.Schemas } [Fact] - public async Task Should_add_errors_if_number_is_greater_than_max() + public async Task Should_add_errors_if_string_is_longer_than_max_length() { var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", MaxLength = 5 }); @@ -68,10 +69,21 @@ namespace Squidex.Core.Tests.Schemas new[] { "Name must have less than '5' characters" }); } + [Fact] + public async Task Should_add_errors_if_string_not_allowed() + { + var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", AllowedValues = ImmutableList.Create("Foo") }); + + await sut.ValidateAsync(CreateValue("Bar"), errors); + + errors.ShouldBeEquivalentTo( + new[] { "Name is not an allowed value" }); + } + [Fact] public async Task Should_add_errors_if_number_is_not_valid_pattern() { - var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", Pattern = "^[0-9]{3}$" }); + var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", Pattern = "[0-9]{3}" }); await sut.ValidateAsync(CreateValue("abc"), errors); @@ -82,7 +94,7 @@ namespace Squidex.Core.Tests.Schemas [Fact] public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message() { - var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", Pattern = "^[0-9]{3}$", PatternMessage = "Custom Error Message" }); + var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message" }); await sut.ValidateAsync(CreateValue("abc"), errors); diff --git a/tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs b/tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs index e860f26aa..fda98e25c 100644 --- a/tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs +++ b/tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs @@ -21,7 +21,7 @@ namespace Squidex.Core.Tests.Schemas.Validators [Fact] public async Task Should_not_add_error_if_value_is_valid() { - var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$"); + var sut = new PatternValidator("[a-z]{3}:[0-9]{2}"); await sut.ValidateAsync("abc:12", errors); @@ -31,7 +31,7 @@ namespace Squidex.Core.Tests.Schemas.Validators [Fact] public async Task Should_not_add_error_if_value_is_null() { - var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$"); + var sut = new PatternValidator("[a-z]{3}:[0-9]{2}"); await sut.ValidateAsync(null, errors); @@ -41,7 +41,7 @@ namespace Squidex.Core.Tests.Schemas.Validators [Fact] public async Task Should_add_error_with_default_message_if_value_is_not_valid() { - var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$"); + var sut = new PatternValidator("[a-z]{3}:[0-9]{2}"); await sut.ValidateAsync("foo", errors); @@ -52,7 +52,7 @@ namespace Squidex.Core.Tests.Schemas.Validators [Fact] public async Task Should_add_error_with_custom_message_if_value_is_not_valid() { - var sut = new PatternValidator("^[a-z]{3}:[0-9]{2}$", "Custom Error Message"); + var sut = new PatternValidator("[a-z]{3}:[0-9]{2}", "Custom Error Message"); await sut.ValidateAsync("foo", errors); diff --git a/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs b/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs index 8ce665ba6..bb2e27202 100644 --- a/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs +++ b/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs @@ -22,13 +22,13 @@ namespace Squidex.Infrastructure [Fact] public void Should_register_and_retrieve_types() { - TypeNameRegistry.Map(typeof(int), "number"); + TypeNameRegistry.Map(typeof(int), "NumberField"); - Assert.Equal("number", TypeNameRegistry.GetName()); - Assert.Equal("number", TypeNameRegistry.GetName(typeof(int))); + Assert.Equal("NumberField", TypeNameRegistry.GetName()); + Assert.Equal("NumberField", TypeNameRegistry.GetName(typeof(int))); - Assert.Equal(typeof(int), TypeNameRegistry.GetType("number")); - Assert.Equal(typeof(int), TypeNameRegistry.GetType("Number")); + Assert.Equal(typeof(int), TypeNameRegistry.GetType("NumberField")); + Assert.Equal(typeof(int), TypeNameRegistry.GetType("NumberField")); } [Fact] @@ -43,6 +43,13 @@ namespace Squidex.Infrastructure Assert.Equal(typeof(MyType), TypeNameRegistry.GetType("My")); } + [Fact] + public void Should_not_throw_if_type_is_already_registered_with_same_name() + { + TypeNameRegistry.Map(typeof(long), "long"); + TypeNameRegistry.Map(typeof(long), "long"); + } + [Fact] public void Should_throw_if_type_is_already_registered() {