Browse Source

A lot more tests

pull/1/head
Sebastian 10 years ago
parent
commit
fd178e8e37
  1. 1
      Squidex.sln.DotSettings
  2. 1
      src/Squidex.Core/Schemas/FieldRegistry.cs
  3. 101
      src/Squidex.Core/Schemas/Json/SchemaJsonSerializer.cs
  4. 2
      src/Squidex.Core/Schemas/NumberFieldProperties.cs
  5. 6
      src/Squidex.Core/Schemas/StringField.cs
  6. 14
      src/Squidex.Core/Schemas/StringFieldProperties.cs
  7. 2
      src/Squidex.Core/Schemas/Validators/PatternValidator.cs
  8. 14
      src/Squidex.Infrastructure/TypeNameRegistry.cs
  9. 51
      src/Squidex.Store.MongoDb/Schemas/Models/FieldModel.cs
  10. 60
      src/Squidex.Store.MongoDb/Schemas/Models/SchemaModel.cs
  11. 18
      src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs
  12. 24
      src/Squidex.Store.MongoDb/Schemas/MongoSchemaRepository.cs
  13. 10
      src/Squidex.Store.MongoDb/Utils/EntityMapper.cs
  14. 5
      src/Squidex/Config/Domain/InfrastructureModule.cs
  15. 68
      src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs
  16. 8
      src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs
  17. 8
      src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberField.cs
  18. 15
      src/Squidex/Controllers/Api/Schemas/Models/Fields/StringField.cs
  19. 12
      src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs
  20. 5
      src/Squidex/Controllers/Api/Schemas/SchemasController.cs
  21. 5
      tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs
  22. 52
      tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs
  23. 1
      tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs
  24. 20
      tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs
  25. 8
      tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs
  26. 17
      tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs

1
Squidex.sln.DotSettings

@ -39,6 +39,7 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue"></s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue"></s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/JavaScriptCodeFormatting/QUOTE_STYLE/@EntryValue">SingleQuoted</s:String>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD; <s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">==========================================================================&#xD;
$FILENAME$&#xD; $FILENAME$&#xD;

1
src/Squidex.Core/Schemas/FieldRegistry.cs

@ -52,6 +52,7 @@ namespace Squidex.Core.Schemas
public FieldRegistry() public FieldRegistry()
{ {
Add<NumberFieldProperties>((id, name, properties) => new NumberField(id, name, (NumberFieldProperties)properties)); Add<NumberFieldProperties>((id, name, properties) => new NumberField(id, name, (NumberFieldProperties)properties));
Add<StringFieldProperties>((id, name, properties) => new StringField(id, name, (StringFieldProperties)properties));
} }
public void Add<TFieldProperties>(FactoryFunction fieldFactory) public void Add<TFieldProperties>(FactoryFunction fieldFactory)

101
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<long, FieldModel> 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<long, FieldModel>(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<SchemaModel>(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;
}
}
}

2
src/Squidex.Core/Schemas/NumberFieldProperties.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
[TypeName("number")] [TypeName("NumberField")]
public sealed class NumberFieldProperties : FieldProperties public sealed class NumberFieldProperties : FieldProperties
{ {
private double? maxValue; private double? maxValue;

6
src/Squidex.Core/Schemas/StringField.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Squidex.Core.Schemas.Validators; using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -35,6 +36,11 @@ namespace Squidex.Core.Schemas
{ {
yield return new PatternValidator(Properties.Pattern, Properties.PatternMessage); yield return new PatternValidator(Properties.Pattern, Properties.PatternMessage);
} }
if (Properties.AllowedValues != null)
{
yield return new AllowedValuesValidator<string>(Properties.AllowedValues.ToArray());
}
} }
protected override object ConvertValue(PropertyValue property) protected override object ConvertValue(PropertyValue property)

14
src/Squidex.Core/Schemas/StringFieldProperties.cs

@ -10,16 +10,19 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using System.Collections.Immutable;
// ReSharper disable ObjectCreationAsStatement // ReSharper disable ObjectCreationAsStatement
namespace Squidex.Core.Schemas namespace Squidex.Core.Schemas
{ {
[TypeName("StringField")]
public sealed class StringFieldProperties : FieldProperties public sealed class StringFieldProperties : FieldProperties
{ {
private int? minLength; private int? minLength;
private int? maxLength; private int? maxLength;
private string pattern; private string pattern;
private string patternMessage; private string patternMessage;
private ImmutableList<string> allowedValues;
public int? MinLength public int? MinLength
{ {
@ -65,6 +68,17 @@ namespace Squidex.Core.Schemas
} }
} }
public ImmutableList<string> AllowedValues
{
get { return allowedValues; }
set
{
ThrowIfFrozen();
allowedValues = value;
}
}
protected override IEnumerable<ValidationError> ValidateCore() protected override IEnumerable<ValidationError> ValidateCore()
{ {
if (MaxLength.HasValue && MinLength.HasValue && MinLength.Value >= MaxLength.Value) if (MaxLength.HasValue && MinLength.HasValue && MinLength.Value >= MaxLength.Value)

2
src/Squidex.Core/Schemas/Validators/PatternValidator.cs

@ -25,7 +25,7 @@ namespace Squidex.Core.Schemas.Validators
{ {
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
regex = new Regex(pattern); regex = new Regex("^" + pattern + "$");
} }
public Task ValidateAsync(object value, ICollection<string> errors) public Task ValidateAsync(object value, ICollection<string> errors)

14
src/Squidex.Infrastructure/TypeNameRegistry.cs

@ -30,9 +30,12 @@ namespace Squidex.Infrastructure
} }
catch (ArgumentException) 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 try
@ -41,9 +44,12 @@ namespace Squidex.Infrastructure
} }
catch (ArgumentException) 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));
}
} }
} }
} }

51
src/Squidex.Store.MongoDb/Schemas/Models/FieldModel.cs

@ -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;
}
}
}

60
src/Squidex.Store.MongoDb/Schemas/Models/SchemaModel.cs

@ -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<long, FieldModel> 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;
}
}
}

18
src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs

@ -9,17 +9,16 @@
using System; using System;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Core.Schemas.Json;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Store.MongoDb.Schemas.Models;
using Squidex.Store.MongoDb.Utils; using Squidex.Store.MongoDb.Utils;
namespace Squidex.Store.MongoDb.Schemas namespace Squidex.Store.MongoDb.Schemas
{ {
public sealed class MongoSchemaEntity : MongoEntity, ISchemaEntityWithSchema public sealed class MongoSchemaEntity : MongoEntity, ISchemaEntityWithSchema
{ {
private Schema schema; private Lazy<Schema> schema;
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
@ -39,19 +38,14 @@ namespace Squidex.Store.MongoDb.Schemas
Schema ISchemaEntityWithSchema.Schema Schema ISchemaEntityWithSchema.Schema
{ {
get { return schema; } get { return schema.Value; }
} }
public void DeserializeSchema(JsonSerializerSettings serializerSettings, FieldRegistry fieldRegistry) public Lazy<Schema> DeserializeSchema(SchemaJsonSerializer serializer)
{ {
if (schema != null) schema = new Lazy<Schema>(() => schema != null ? null : serializer.Deserialize(Schema.ToJToken()));
{
return;
}
var dto = Schema.ToJsonObject<SchemaModel>(serializerSettings); return schema;
schema = dto?.ToSchema(fieldRegistry);
} }
} }
} }

24
src/Squidex.Store.MongoDb/Schemas/MongoSchemaRepository.cs

@ -11,8 +11,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Core.Schemas.Json;
using Squidex.Events.Schemas; using Squidex.Events.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
@ -20,23 +20,23 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Store.MongoDb.Schemas.Models;
using Squidex.Store.MongoDb.Utils; using Squidex.Store.MongoDb.Utils;
namespace Squidex.Store.MongoDb.Schemas namespace Squidex.Store.MongoDb.Schemas
{ {
public sealed class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository, ICatchEventConsumer public sealed class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository, ICatchEventConsumer
{ {
private readonly JsonSerializerSettings serializerSettings; private readonly SchemaJsonSerializer serializer;
private readonly FieldRegistry fieldRegistry; private readonly FieldRegistry fieldRegistry;
public MongoSchemaRepository(IMongoDatabase database, JsonSerializerSettings serializerSettings, FieldRegistry fieldRegistry) public MongoSchemaRepository(IMongoDatabase database, SchemaJsonSerializer serializer, FieldRegistry fieldRegistry)
: base(database) : base(database)
{ {
Guard.NotNull(serializerSettings, nameof(serializerSettings)); Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); Guard.NotNull(fieldRegistry, nameof(fieldRegistry));
this.serializerSettings = serializerSettings; this.serializer = serializer;
this.fieldRegistry = fieldRegistry; this.fieldRegistry = fieldRegistry;
} }
@ -63,7 +63,7 @@ namespace Squidex.Store.MongoDb.Schemas
await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted) await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
entity?.DeserializeSchema(serializerSettings, fieldRegistry); entity?.DeserializeSchema(serializer);
return entity; return entity;
} }
@ -74,7 +74,7 @@ namespace Squidex.Store.MongoDb.Schemas
await Collection.Find(s => s.Id == schemaId && !s.IsDeleted) await Collection.Find(s => s.Id == schemaId && !s.IsDeleted)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
entity?.DeserializeSchema(serializerSettings, fieldRegistry); entity?.DeserializeSchema(serializer);
return entity; return entity;
} }
@ -161,16 +161,12 @@ namespace Squidex.Store.MongoDb.Schemas
private void Serialize(MongoSchemaEntity entity, Schema schema) private void Serialize(MongoSchemaEntity entity, Schema schema)
{ {
var dto = SchemaModel.Create(schema); entity.Schema = serializer.Serialize(schema).ToBsonDocument();
entity.Schema = dto.ToJsonBsonDocument(serializerSettings);
} }
private Schema Deserialize(MongoSchemaEntity entity) private Schema Deserialize(MongoSchemaEntity entity)
{ {
var dto = entity?.Schema.ToJsonObject<SchemaModel>(serializerSettings); return entity.DeserializeSchema(serializer).Value;
return dto?.ToSchema(fieldRegistry);
} }
} }
} }

10
src/Squidex.Store.MongoDb/Utils/EntityMapper.cs

@ -10,7 +10,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json; using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Read; using Squidex.Read;
@ -34,18 +34,18 @@ namespace Squidex.Store.MongoDb.Utils
return Update(entity, headers); return Update(entity, headers);
} }
public static BsonDocument ToJsonBsonDocument<T>(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); return BsonDocument.Parse(json);
} }
public static T ToJsonObject<T>(this BsonDocument document, JsonSerializerSettings settings) public static JToken ToJToken(this BsonDocument document)
{ {
var json = document.ToJson().Replace("§type", "$type"); var json = document.ToJson().Replace("§type", "$type");
return JsonConvert.DeserializeObject<T>(json, settings); return JToken.Parse(json);
} }
public static T Update<T>(T entity, EnvelopeHeaders headers) where T : IEntity public static T Update<T>(T entity, EnvelopeHeaders headers) where T : IEntity

5
src/Squidex/Config/Domain/InfrastructureModule.cs

@ -10,6 +10,7 @@ using Autofac;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Core.Schemas.Json;
using Squidex.Infrastructure.CQRS.Autofac; using Squidex.Infrastructure.CQRS.Autofac;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.EventStore; using Squidex.Infrastructure.CQRS.EventStore;
@ -49,6 +50,10 @@ namespace Squidex.Config.Domain
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();
builder.RegisterType<SchemaJsonSerializer>()
.AsSelf()
.SingleInstance();
builder.RegisterType<FieldRegistry>() builder.RegisterType<FieldRegistry>()
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();

68
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<Type, Func<Field, FieldDto>> Factories = new Dictionary<Type, Func<Field, FieldDto>>
{
{
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<FieldDto>();
foreach (var field in entity.Schema.Fields.Values)
{
var fieldDto = Factories[field.RawProperties.GetType()](field);
dto.Fields.Add(fieldDto);
}
return dto;
}
}
}

8
src/Squidex/Controllers/Api/Schemas/Models/FieldDto.cs

@ -10,14 +10,14 @@ using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
using NJsonSchema.Converters; using NJsonSchema.Converters;
using Squidex.Controllers.Api.Schemas.Models.Fields; using Squidex.Core.Schemas;
namespace Squidex.Controllers.Api.Schemas.Models namespace Squidex.Controllers.Api.Schemas.Models
{ {
[JsonConverter(typeof(JsonInheritanceConverter), "fieldType")] [JsonConverter(typeof(JsonInheritanceConverter), "$type")]
[KnownType(typeof(NumberField))] [KnownType(typeof(NumberField))]
[KnownType(typeof(StringField))] [KnownType(typeof(StringField))]
public class FieldDto public abstract class FieldDto
{ {
/// <summary> /// <summary>
/// The name of the field. Must be unique within the schema. /// 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. /// Indicates if the field is required.
/// </summary> /// </summary>
public bool IsRequired { get; set; } public bool IsRequired { get; set; }
public abstract FieldProperties ToProperties();
} }
} }

8
src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberField.cs

@ -6,6 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models.Fields namespace Squidex.Controllers.Api.Schemas.Models.Fields
{ {
public class NumberField : FieldDto public class NumberField : FieldDto
@ -29,5 +32,10 @@ namespace Squidex.Controllers.Api.Schemas.Models.Fields
/// The allowed values for the field value. /// The allowed values for the field value.
/// </summary> /// </summary>
public double[] AllowedValues { get; set; } public double[] AllowedValues { get; set; }
public override FieldProperties ToProperties()
{
return SimpleMapper.Map(this, new NumberFieldProperties());
}
} }
} }

15
src/Squidex/Controllers/Api/Schemas/Models/Fields/StringField.cs

@ -6,6 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models.Fields namespace Squidex.Controllers.Api.Schemas.Models.Fields
{ {
public sealed class StringField : FieldDto public sealed class StringField : FieldDto
@ -20,6 +23,11 @@ namespace Squidex.Controllers.Api.Schemas.Models.Fields
/// </summary> /// </summary>
public string Pattern { get; set; } public string Pattern { get; set; }
/// <summary>
/// The validation message for the pattern.
/// </summary>
public string PatternMessage { get; set; }
/// <summary> /// <summary>
/// The minimum allowed length for the field value. /// The minimum allowed length for the field value.
/// </summary> /// </summary>
@ -33,6 +41,11 @@ namespace Squidex.Controllers.Api.Schemas.Models.Fields
/// <summary> /// <summary>
/// The allowed values for the field value. /// The allowed values for the field value.
/// </summary> /// </summary>
public double[] AllowedValues { get; set; } public string[] AllowedValues { get; set; }
public override FieldProperties ToProperties()
{
return SimpleMapper.Map(this, new StringFieldProperties());
}
} }
} }

12
src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs

@ -9,10 +9,11 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Controllers.Api.Schemas.Models; using Squidex.Controllers.Api.Schemas.Models;
using Squidex.Infrastructure;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Write.Schemas.Commands; using Squidex.Write.Schemas.Commands;
@ -51,7 +52,12 @@ namespace Squidex.Controllers.Api.Schemas
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
public async Task<IActionResult> PostField(string app, string name, [FromBody] FieldDto model) public async Task<IActionResult> 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 context = await CommandBus.PublishAsync(command);
var result = context.Result<long>(); var result = context.Result<long>();
@ -77,7 +83,7 @@ namespace Squidex.Controllers.Api.Schemas
[ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(ErrorDto), 400)]
public async Task<IActionResult> PutField(string app, string name, long id, [FromBody] FieldDto model) public async Task<IActionResult> 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); await CommandBus.PublishAsync(command);

5
src/Squidex/Controllers/Api/Schemas/SchemasController.cs

@ -15,6 +15,7 @@ using NSwag.Annotations;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Controllers.Api.Schemas.Models; using Squidex.Controllers.Api.Schemas.Models;
using Squidex.Controllers.Api.Schemas.Models.Converters;
using Squidex.Pipeline; using Squidex.Pipeline;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Write.Schemas.Commands; using Squidex.Write.Schemas.Commands;
@ -76,7 +77,9 @@ namespace Squidex.Controllers.Api.Schemas
return NotFound(); return NotFound();
} }
return Ok(null); var model = entity.ToModel();
return Ok(model);
} }
/// <summary> /// <summary>

5
tests/Squidex.Core.Tests/Schemas/FieldRegistryTests.cs

@ -28,7 +28,8 @@ namespace Squidex.Core.Tests.Schemas
static FieldRegistryTests() static FieldRegistryTests()
{ {
TypeNameRegistry.Map(typeof(NumberFieldProperties), "number"); TypeNameRegistry.Map(typeof(NumberFieldProperties), "NumberField");
TypeNameRegistry.Map(typeof(StringFieldProperties), "StringField");
TypeNameRegistry.Map(typeof(InvalidProperties), "invalid"); TypeNameRegistry.Map(typeof(InvalidProperties), "invalid");
} }
@ -71,7 +72,7 @@ namespace Squidex.Core.Tests.Schemas
[Fact] [Fact]
public void Should_find_registration_by_name() public void Should_find_registration_by_name()
{ {
var registry = sut.FindByTypeName("number"); var registry = sut.FindByTypeName("NumberField");
Assert.Equal(typeof(NumberFieldProperties), registry.PropertiesType); Assert.Equal(typeof(NumberFieldProperties), registry.PropertiesType);
} }

52
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);
}
}
}

1
tests/Squidex.Core.Tests/Schemas/StringFieldPropertiesTests.cs

@ -12,7 +12,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using FluentAssertions; using FluentAssertions;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Xunit; using Xunit;

20
tests/Squidex.Core.Tests/Schemas/StringFieldTests.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -47,7 +48,7 @@ namespace Squidex.Core.Tests.Schemas
} }
[Fact] [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 }); var sut = new StringField(1, "name", new StringFieldProperties { Label = "Name", MinLength = 10 });
@ -58,7 +59,7 @@ namespace Squidex.Core.Tests.Schemas
} }
[Fact] [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 }); 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" }); 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] [Fact]
public async Task Should_add_errors_if_number_is_not_valid_pattern() 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); await sut.ValidateAsync(CreateValue("abc"), errors);
@ -82,7 +94,7 @@ namespace Squidex.Core.Tests.Schemas
[Fact] [Fact]
public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message() 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); await sut.ValidateAsync(CreateValue("abc"), errors);

8
tests/Squidex.Core.Tests/Schemas/Validators/PatternValidatorTests.cs

@ -21,7 +21,7 @@ namespace Squidex.Core.Tests.Schemas.Validators
[Fact] [Fact]
public async Task Should_not_add_error_if_value_is_valid() 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); await sut.ValidateAsync("abc:12", errors);
@ -31,7 +31,7 @@ namespace Squidex.Core.Tests.Schemas.Validators
[Fact] [Fact]
public async Task Should_not_add_error_if_value_is_null() 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); await sut.ValidateAsync(null, errors);
@ -41,7 +41,7 @@ namespace Squidex.Core.Tests.Schemas.Validators
[Fact] [Fact]
public async Task Should_add_error_with_default_message_if_value_is_not_valid() 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); await sut.ValidateAsync("foo", errors);
@ -52,7 +52,7 @@ namespace Squidex.Core.Tests.Schemas.Validators
[Fact] [Fact]
public async Task Should_add_error_with_custom_message_if_value_is_not_valid() 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); await sut.ValidateAsync("foo", errors);

17
tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs

@ -22,13 +22,13 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_register_and_retrieve_types() public void Should_register_and_retrieve_types()
{ {
TypeNameRegistry.Map(typeof(int), "number"); TypeNameRegistry.Map(typeof(int), "NumberField");
Assert.Equal("number", TypeNameRegistry.GetName<int>()); Assert.Equal("NumberField", TypeNameRegistry.GetName<int>());
Assert.Equal("number", TypeNameRegistry.GetName(typeof(int))); Assert.Equal("NumberField", TypeNameRegistry.GetName(typeof(int)));
Assert.Equal(typeof(int), TypeNameRegistry.GetType("number")); Assert.Equal(typeof(int), TypeNameRegistry.GetType("NumberField"));
Assert.Equal(typeof(int), TypeNameRegistry.GetType("Number")); Assert.Equal(typeof(int), TypeNameRegistry.GetType("NumberField"));
} }
[Fact] [Fact]
@ -43,6 +43,13 @@ namespace Squidex.Infrastructure
Assert.Equal(typeof(MyType), TypeNameRegistry.GetType("My")); 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] [Fact]
public void Should_throw_if_type_is_already_registered() public void Should_throw_if_type_is_already_registered()
{ {

Loading…
Cancel
Save