From bbe57d744890b55a4480d5d6d15eec8ada89ef4d Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 17 Sep 2016 21:10:56 +0200 Subject: [PATCH] More tests --- .gitignore | 5 +- PinkParrot.sln.DotSettings | 1 + src/PinkParrot/Modules/Api/ErrorDto.cs | 2 +- .../Schemas/{ => Models}/CreateFieldDto.cs | 9 +- .../Schemas/{ => Models}/CreateSchemaDto.cs | 5 +- .../Api/Schemas/{ => Models}/ListSchemaDto.cs | 2 +- .../Schemas/{ => Models}/UpdateFieldDto.cs | 4 +- .../Api/Schemas/Models/UpdateSchemaDto.cs} | 11 +- .../Api/Schemas/SchemaFieldsController.cs | 6 + .../Modules/Api/Schemas/SchemasController.cs | 9 +- .../Pipeline/ApiExceptionFilterAttribute.cs | 77 ++++++ src/PinkParrot/project.json | 4 +- src/RunCoverage.bat | 37 +++ .../PinkParrot.Core/Schemas/Field.cs | 23 +- .../Schemas/FieldProperties.cs | 2 +- .../PinkParrot.Core/Schemas/FieldRegistry.cs | 6 +- .../PinkParrot.Core/Schemas/Field_Generic.cs | 19 +- .../Schemas/IRegisteredField.cs | 2 +- .../PinkParrot.Core/Schemas/Schema.cs | 4 +- .../PinkParrot.Events/Schemas/FieldAdded.cs | 2 +- .../PinkParrot.Events/Schemas/FieldUpdated.cs | 2 +- .../CollectionExtensionsTests.cs | 58 +++++ .../GuardTests.cs | 225 ++++++++++++++++-- .../PinkParrot.Infrastructure.Tests.xproj | 3 + .../Reflection/SimpleMapperTests.cs | 83 +++++++ .../CollectionExtensions.cs | 11 - .../PinkParrot.Infrastructure/Guard.cs | 79 ++---- .../TypeNameRegistry.cs | 22 +- .../PinkParrot.Read/Models/FieldDto.cs | 34 ++- .../PinkParrot.Read/Models/SchemaDto.cs | 10 +- .../Schemas/SchemaCommandHandler.cs | 19 +- .../Schemas/SchemaDomainObject.cs | 4 +- 32 files changed, 611 insertions(+), 169 deletions(-) rename src/PinkParrot/Modules/Api/Schemas/{ => Models}/CreateFieldDto.cs (85%) rename src/PinkParrot/Modules/Api/Schemas/{ => Models}/CreateSchemaDto.cs (90%) rename src/PinkParrot/Modules/Api/Schemas/{ => Models}/ListSchemaDto.cs (93%) rename src/PinkParrot/Modules/Api/Schemas/{ => Models}/UpdateFieldDto.cs (83%) rename src/{pinkparrot_core/PinkParrot.Core/Schemas/IFieldProperties.cs => PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs} (57%) create mode 100644 src/PinkParrot/Pipeline/ApiExceptionFilterAttribute.cs create mode 100644 src/RunCoverage.bat create mode 100644 src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/Reflection/SimpleMapperTests.cs diff --git a/.gitignore b/.gitignore index 3a2238d6b..8ad573c14 100644 --- a/.gitignore +++ b/.gitignore @@ -242,4 +242,7 @@ ModelManifest.xml .paket/paket.exe # FAKE - F# Make -.fake/ \ No newline at end of file +.fake/ + +# OpenCover +GeneratedReports/ \ No newline at end of file diff --git a/PinkParrot.sln.DotSettings b/PinkParrot.sln.DotSettings index 94cc32e31..da6c0b707 100644 --- a/PinkParrot.sln.DotSettings +++ b/PinkParrot.sln.DotSettings @@ -90,4 +90,5 @@ True True True + True True \ No newline at end of file diff --git a/src/PinkParrot/Modules/Api/ErrorDto.cs b/src/PinkParrot/Modules/Api/ErrorDto.cs index 18ae1e68e..1ec56b443 100644 --- a/src/PinkParrot/Modules/Api/ErrorDto.cs +++ b/src/PinkParrot/Modules/Api/ErrorDto.cs @@ -17,6 +17,6 @@ namespace PinkParrot.Modules.Api public string[] Details { get; set; } - public int? StatusCode { get; set; } + public int? StatusCode { get; set; } = 400; } } diff --git a/src/PinkParrot/Modules/Api/Schemas/CreateFieldDto.cs b/src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs similarity index 85% rename from src/PinkParrot/Modules/Api/Schemas/CreateFieldDto.cs rename to src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs index ee359419e..1fed7092a 100644 --- a/src/PinkParrot/Modules/Api/Schemas/CreateFieldDto.cs +++ b/src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs @@ -10,18 +10,17 @@ using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace PinkParrot.Modules.Api.Schemas +namespace PinkParrot.Modules.Api.Schemas.Models { public class CreateFieldDto { [Required] [JsonProperty("$type")] - public string Name { get; set; } - - [Required] public string Type { get; set; } [Required] - public JToken Properties { get; set; } + public string Name { get; set; } + + public JObject Properties { get; set; } } } diff --git a/src/PinkParrot/Modules/Api/Schemas/CreateSchemaDto.cs b/src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs similarity index 90% rename from src/PinkParrot/Modules/Api/Schemas/CreateSchemaDto.cs rename to src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs index 13b32e241..21dab2543 100644 --- a/src/PinkParrot/Modules/Api/Schemas/CreateSchemaDto.cs +++ b/src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs @@ -9,14 +9,13 @@ using System.ComponentModel.DataAnnotations; using PinkParrot.Core.Schemas; -namespace PinkParrot.Modules.Api.Schemas +namespace PinkParrot.Modules.Api.Schemas.Models { public class CreateSchemaDto { [Required] public string Name { get; set; } - - [Required] + public FieldProperties Properties { get; set; } } } diff --git a/src/PinkParrot/Modules/Api/Schemas/ListSchemaDto.cs b/src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs similarity index 93% rename from src/PinkParrot/Modules/Api/Schemas/ListSchemaDto.cs rename to src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs index 8bbbb5ff2..93bd1ecaf 100644 --- a/src/PinkParrot/Modules/Api/Schemas/ListSchemaDto.cs +++ b/src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs @@ -9,7 +9,7 @@ using System; using System.ComponentModel.DataAnnotations; -namespace PinkParrot.Modules.Api.Schemas +namespace PinkParrot.Modules.Api.Schemas.Models { public class ListSchemaDto { diff --git a/src/PinkParrot/Modules/Api/Schemas/UpdateFieldDto.cs b/src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs similarity index 83% rename from src/PinkParrot/Modules/Api/Schemas/UpdateFieldDto.cs rename to src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs index d558c0e95..6cbea4b22 100644 --- a/src/PinkParrot/Modules/Api/Schemas/UpdateFieldDto.cs +++ b/src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs @@ -9,11 +9,11 @@ using System.ComponentModel.DataAnnotations; using Newtonsoft.Json.Linq; -namespace PinkParrot.Modules.Api.Schemas +namespace PinkParrot.Modules.Api.Schemas.Models { public class UpdateFieldDto { [Required] - public JToken Properties { get; set; } + public JObject Properties { get; set; } } } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/IFieldProperties.cs b/src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs similarity index 57% rename from src/pinkparrot_core/PinkParrot.Core/Schemas/IFieldProperties.cs rename to src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs index 4855988a6..b7c84e77e 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/IFieldProperties.cs +++ b/src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs @@ -1,16 +1,19 @@ // ========================================================================== -// IFieldProperties.cs +// UpdateSchemaDto.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== -using PinkParrot.Infrastructure; +using System.ComponentModel.DataAnnotations; +using PinkParrot.Core.Schemas; -namespace PinkParrot.Core.Schemas +namespace PinkParrot.Modules.Api.Schemas.Models { - public interface IFieldProperties : IValidatable + public class UpdateSchemaDto { + [Required] + public SchemaProperties Properties { get; set; } } } diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs index be171b58b..d5ed99ba0 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs @@ -8,12 +8,16 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.Reflection; +using PinkParrot.Modules.Api.Schemas.Models; +using PinkParrot.Pipeline; using PinkParrot.Write.Schemas.Commands; namespace PinkParrot.Modules.Api.Schemas { + [ApiExceptionFilter] public class SchemasFieldsController : ControllerBase { public SchemasFieldsController(ICommandBus commandBus) @@ -27,6 +31,8 @@ namespace PinkParrot.Modules.Api.Schemas { var command = SimpleMapper.Map(model, new AddField()); + command.Properties = command.Properties ?? new JObject(); + return CommandBus.PublishAsync(command); } diff --git a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs index 1baaa45b2..35da7592f 100644 --- a/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs +++ b/src/PinkParrot/Modules/Api/Schemas/SchemasController.cs @@ -14,12 +14,15 @@ using Microsoft.AspNetCore.Mvc; using PinkParrot.Core.Schemas; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.Reflection; +using PinkParrot.Modules.Api.Schemas.Models; +using PinkParrot.Pipeline; using PinkParrot.Read.Models; using PinkParrot.Read.Repositories; using PinkParrot.Write.Schemas.Commands; namespace PinkParrot.Modules.Api.Schemas { + [ApiExceptionFilter] public class SchemasController : ControllerBase { private readonly ISchemaRepository schemaRepository; @@ -59,6 +62,8 @@ namespace PinkParrot.Modules.Api.Schemas { var command = SimpleMapper.Map(model, new CreateSchema { AggregateId = Guid.NewGuid() }); + command.Properties = command.Properties ?? new SchemaProperties(null, null); + await CommandBus.PublishAsync(command); return CreatedAtAction("Query", new EntityCreatedDto { Id = command.AggregateId }); @@ -66,9 +71,9 @@ namespace PinkParrot.Modules.Api.Schemas [HttpPut] [Route("api/schemas/{name}/")] - public async Task Update(string name, [FromBody] SchemaProperties schema) + public async Task Update(string name, [FromBody] UpdateSchemaDto model) { - var command = new UpdateSchema { Properties = schema }; + var command = SimpleMapper.Map(model, new UpdateSchema()); await CommandBus.PublishAsync(command); diff --git a/src/PinkParrot/Pipeline/ApiExceptionFilterAttribute.cs b/src/PinkParrot/Pipeline/ApiExceptionFilterAttribute.cs new file mode 100644 index 000000000..2a3362b5a --- /dev/null +++ b/src/PinkParrot/Pipeline/ApiExceptionFilterAttribute.cs @@ -0,0 +1,77 @@ +// ========================================================================== +// ExceptionFilter.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using PinkParrot.Infrastructure; +using PinkParrot.Modules.Api; + +// ReSharper disable InvertIf + +namespace PinkParrot.Pipeline +{ + public class ApiExceptionFilterAttribute : ActionFilterAttribute, IExceptionFilter + { + private static readonly List> handlers = new List>(); + + private static void AddHandler(Func handler) where T : Exception + { + handlers.Add(ex => + { + var typed = ex as T; + + return typed != null ? handler(typed) : null; + }); + } + + static ApiExceptionFilterAttribute() + { + AddHandler(ex => + new NotFoundResult()); + + AddHandler(ex => + new BadRequestObjectResult(new ErrorDto { Message = ex.Message })); + + AddHandler(ex => + new BadRequestObjectResult(new ErrorDto { Message = ex.Message, Details = ex.Errors.Select(e => e.Message).ToArray() })); + } + + public override void OnActionExecuting(ActionExecutingContext context) + { + if (!context.ModelState.IsValid) + { + var errors = context.ModelState.Values.SelectMany(g => g.Errors).Select(e => new ValidationError(e.ErrorMessage)).ToList(); + + throw new ValidationException("The model is not valid.", errors); + } + } + + public void OnException(ExceptionContext context) + { + IActionResult result = null; + + foreach (var handler in handlers) + { + result = handler(context.Exception); + + if (result != null) + { + break; + } + } + + if (result != null) + { + context.Result = result; + } + } + } +} diff --git a/src/PinkParrot/project.json b/src/PinkParrot/project.json index a1d0603c9..c1cabf022 100644 --- a/src/PinkParrot/project.json +++ b/src/PinkParrot/project.json @@ -22,11 +22,13 @@ "type": "platform" }, "MongoDB.Driver": "2.3.0-rc1", + "OpenCover": "4.6.519", "PinkParrot.Core": "1.0.0-*", "PinkParrot.Events": "1.0.0-*", "PinkParrot.Infrastructure": "1.0.0-*", "PinkParrot.Read": "1.0.0-*", - "PinkParrot.Write": "1.0.0-*" + "PinkParrot.Write": "1.0.0-*", + "ReportGenerator": "2.4.5" }, "tools": { diff --git a/src/RunCoverage.bat b/src/RunCoverage.bat new file mode 100644 index 000000000..fd8216ef3 --- /dev/null +++ b/src/RunCoverage.bat @@ -0,0 +1,37 @@ +REM Create a 'GeneratedReports' folder if it does not exist +if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports" + +REM Remove any previously created test output directories +CD %~dp0 +FOR /D /R %%X IN (%USERNAME%*) DO RD /S /Q "%%X" + +REM Run the tests against the targeted output +call :RunOpenCoverUnitTestMetrics + +REM Generate the report output based on the test results +if %errorlevel% equ 0 ( + call :RunReportGeneratorOutput +) + +REM Launch the report +if %errorlevel% equ 0 ( + call :RunLaunchReport +) +exit /b %errorlevel% + +:RunOpenCoverUnitTestMetrics +"%UserProfile%\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ^ +-register:user ^ +-target:"C:\Program Files\dotnet\dotnet.exe" ^ +-targetargs:"test %~dp0\pinkparrot_infrastructure\PinkParrot.Infrastructure.Tests" ^ +-filter:"+[PinkParrot*]*" ^ +-skipautoprops ^ +-output:"%~dp0\GeneratedReports\Infrastructure.xml" ^ +-oldStyle +exit /b %errorlevel% + +:RunReportGeneratorOutput +"%UserProfile%\.nuget\packages\ReportGenerator\2.4.5\tools\ReportGenerator.exe" ^ +-reports:"%~dp0\GeneratedReports\Infrastructure.xml" ^ +-targetdir:"%~dp0\GeneratedReports\Output" +exit /b %errorlevel% \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/Field.cs b/src/pinkparrot_core/PinkParrot.Core/Schemas/Field.cs index 129b9d412..20b93d31c 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/Field.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schemas/Field.cs @@ -34,6 +34,16 @@ namespace PinkParrot.Core.Schemas get { return name; } } + public string Hints + { + get { return RawProperties.Hints; } + } + + public string Label + { + get { return RawProperties.Label; } + } + public bool IsHidden { get { return isHidden; } @@ -44,13 +54,12 @@ namespace PinkParrot.Core.Schemas get { return isDisabled; } } - public abstract IFieldProperties RawProperties { get; } - - public abstract string Label { get; } - - public abstract string Hints { get; } + public bool IsRequired + { + get { return RawProperties.IsRequired; } + } - public abstract bool IsRequired { get; } + public abstract FieldProperties RawProperties { get; } protected Field(long id, string name) { @@ -62,7 +71,7 @@ namespace PinkParrot.Core.Schemas this.name = name; } - public abstract Field Update(IFieldProperties newProperties); + public abstract Field Update(FieldProperties newProperties); public Task ValidateAsync(PropertyValue property, ICollection errors) { diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldProperties.cs b/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldProperties.cs index ee00d2ce4..edac1223a 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldProperties.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldProperties.cs @@ -11,7 +11,7 @@ using PinkParrot.Infrastructure; namespace PinkParrot.Core.Schemas { - public abstract class FieldProperties : NamedElementProperties, IFieldProperties + public abstract class FieldProperties : NamedElementProperties, IValidatable { public bool IsRequired { get; } diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldRegistry.cs b/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldRegistry.cs index f2756e4a9..2090534b0 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldRegistry.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schemas/FieldRegistry.cs @@ -12,7 +12,7 @@ using PinkParrot.Infrastructure; namespace PinkParrot.Core.Schemas { - public delegate Field FactoryFunction(long id, string name, IFieldProperties properties); + public delegate Field FactoryFunction(long id, string name, FieldProperties properties); public sealed class FieldRegistry { @@ -43,7 +43,7 @@ namespace PinkParrot.Core.Schemas this.propertiesType = propertiesType; } - Field IRegisteredField.CreateField(long id, string name, IFieldProperties properties) + Field IRegisteredField.CreateField(long id, string name, FieldProperties properties) { return fieldFactory(id, name, properties); } @@ -64,7 +64,7 @@ namespace PinkParrot.Core.Schemas fieldsByPropertyType[registered.PropertiesType] = registered; } - public Field CreateField(long id, string name, IFieldProperties properties) + public Field CreateField(long id, string name, FieldProperties properties) { var registered = fieldsByPropertyType[properties.GetType()]; diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/Field_Generic.cs b/src/pinkparrot_core/PinkParrot.Core/Schemas/Field_Generic.cs index b1a1e0ab7..10f9a66c2 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/Field_Generic.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schemas/Field_Generic.cs @@ -15,26 +15,11 @@ namespace PinkParrot.Core.Schemas { private T properties; - public override IFieldProperties RawProperties + public override FieldProperties RawProperties { get { return properties; } } - public override string Label - { - get { return properties.Label ?? Name; } - } - - public override string Hints - { - get { return properties.Hints; } - } - - public override bool IsRequired - { - get { return properties.IsRequired; } - } - public T Properties { get { return properties; } @@ -48,7 +33,7 @@ namespace PinkParrot.Core.Schemas this.properties = properties; } - public override Field Update(IFieldProperties newProperties) + public override Field Update(FieldProperties newProperties) { Guard.NotNull(newProperties, nameof(newProperties)); diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/IRegisteredField.cs b/src/pinkparrot_core/PinkParrot.Core/Schemas/IRegisteredField.cs index 78cf8ca40..cd1f2a00f 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/IRegisteredField.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schemas/IRegisteredField.cs @@ -14,6 +14,6 @@ namespace PinkParrot.Core.Schemas { Type PropertiesType { get; } - Field CreateField(long id, string name, IFieldProperties properties); + Field CreateField(long id, string name, FieldProperties properties); } } \ No newline at end of file diff --git a/src/pinkparrot_core/PinkParrot.Core/Schemas/Schema.cs b/src/pinkparrot_core/PinkParrot.Core/Schemas/Schema.cs index 5ce45e5dd..cf9398e77 100644 --- a/src/pinkparrot_core/PinkParrot.Core/Schemas/Schema.cs +++ b/src/pinkparrot_core/PinkParrot.Core/Schemas/Schema.cs @@ -55,8 +55,6 @@ namespace PinkParrot.Core.Schemas public static Schema Create(string name, SchemaProperties newProperties) { - newProperties = newProperties ?? new SchemaProperties(null, null); - if (!name.IsSlug()) { var error = new ValidationError("Name must be a valid slug", "Name"); @@ -86,7 +84,7 @@ namespace PinkParrot.Core.Schemas return new Schema(name, properties, fieldsById.SetItem(field.Id, field)); } - public Schema UpdateField(long fieldId, IFieldProperties newProperties) + public Schema UpdateField(long fieldId, FieldProperties newProperties) { return UpdateField(fieldId, field => field.Update(newProperties)); } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldAdded.cs b/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldAdded.cs index 5dd9d7afe..f706561c8 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldAdded.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldAdded.cs @@ -18,6 +18,6 @@ namespace PinkParrot.Events.Schemas public string Name { get; set; } - public IFieldProperties Properties { get; set; } + public FieldProperties Properties { get; set; } } } diff --git a/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldUpdated.cs b/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldUpdated.cs index e857ecc28..7452979b0 100644 --- a/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldUpdated.cs +++ b/src/pinkparrot_events/PinkParrot.Events/Schemas/FieldUpdated.cs @@ -16,6 +16,6 @@ namespace PinkParrot.Events.Schemas { public long FieldId { get; set; } - public IFieldProperties Properties { get; set; } + public FieldProperties Properties { get; set; } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs index 03f863e53..464571c03 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs @@ -112,6 +112,14 @@ namespace PinkParrot.Infrastructure Assert.Equal(list, listDictionary[12]); } + [Fact] + public void SequentialHashCode_should_ignore_null_values() + { + var collection = new string[] { null, null }; + + Assert.Equal(17, collection.SequentialHashCode()); + } + [Fact] public void SequentialHashCode_should_return_same_hash_codes_for_list_with_same_order() { @@ -165,5 +173,55 @@ namespace PinkParrot.Infrastructure Assert.Equal(collection2.OrderedHashCode(), collection1.OrderedHashCode()); } + + [Fact] + public void EqualsDictionary_should_return_true_for_equal_dictionaries() + { + var lhs = new Dictionary + { + [1] = 1, + [2] = 2 + }; + var rhs = new Dictionary + { + [1] = 1, + [2] = 2 + }; + + Assert.True(lhs.EqualsDictionary(rhs)); + } + + [Fact] + public void EqualsDictionary_should_return_false_for_different_sizes() + { + var lhs = new Dictionary + { + [1] = 1, + [2] = 2 + }; + var rhs = new Dictionary + { + [1] = 1 + }; + + Assert.False(lhs.EqualsDictionary(rhs)); + } + + [Fact] + public void EqualsDictionary_should_return_false_for_different_values() + { + var lhs = new Dictionary + { + [1] = 1, + [2] = 2 + }; + var rhs = new Dictionary + { + [1] = 1, + [3] = 3 + }; + + Assert.False(lhs.EqualsDictionary(rhs)); + } } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs index ec4417a14..763545628 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs @@ -16,85 +16,103 @@ namespace PinkParrot.Infrastructure [Theory] [InlineData("")] [InlineData(" ")] - public void Should_throw_when_target_is_null_for_empty_string(string invalidString) + public void NotNullOrEmpty_should_throw_for_empy_strings(string invalidString) { Assert.Throws(() => Guard.NotNullOrEmpty(invalidString, "parameter")); } [Fact] - public void Should_do_nothing_if_target_string_is_valid() + public void NotNullOrEmpty_should_throw_for_null_string() + { + Assert.Throws(() => Guard.NotNullOrEmpty(null, "parameter")); + } + + [Fact] + public void NotNullOrEmpty_should_do_nothing_for_vaid_string() { Guard.NotNullOrEmpty("value", "parameter"); } [Fact] - public void Should_do_nothing_if_target_is_not_null() + public void NotNull_should_throw_for_null_value() { - Guard.NotNull("value", "parameter"); + Assert.Throws(() => Guard.NotNull(null, "parameter")); } [Fact] - public void Should_do_nothing_if_enum_is_valid() + public void NotNull_should_do_nothing_for_valid_value() { - Guard.Enum(DateTimeKind.Local, "Parameter"); + Guard.NotNull("value", "parameter"); } [Fact] - public void Should_throw_if_enum_is_not_valid() + public void Enum_should_throw_for_invalid_enum() { Assert.Throws(() => Guard.Enum((DateTimeKind)13, "Parameter")); } [Fact] - public void Should_do_nothing_when_guid_is_not_empty() + public void Enum_should_do_nothing_for_valid_enum() { - Guard.NotEmpty(Guid.NewGuid(), "parameter"); + Guard.Enum(DateTimeKind.Local, "Parameter"); } [Fact] - public void Should_throw_when_guid_is_empty() + public void NotEmpty_should_throw_for_empty_guid() { Assert.Throws(() => Guard.NotEmpty(Guid.Empty, "parameter")); } [Fact] - public void Should_throw_when_target_is_null() + public void NotEmpty_should_do_nothing_for_valid_guid() { - Assert.Throws(() => Guard.NotNull(null, "parameter")); + Guard.NotEmpty(Guid.NewGuid(), "parameter"); } [Fact] - public void Should_throw_when_target_is_null_for_null_string() + public void HasType_should_throw_for_other_type() { - Assert.Throws(() => Guard.NotNullOrEmpty(null, "parameter")); + Assert.Throws(() => Guard.HasType("value", "parameter")); } [Fact] - public void Should_do_nothing_when_target_has_correct_type() + public void HasType_should_do_nothing_for_null_value() + { + Guard.HasType(null, "parameter"); + } + + [Fact] + public void HasType_should_do_nothing_for_correct_type() { Guard.HasType(123, "parameter"); } [Fact] - public void Should_throw_when_target_has_wrong_type() + public void HasType_nongeneric_should_throw_for_other_type() { - Assert.Throws(() => Guard.HasType("value", "parameter")); + Assert.Throws(() => Guard.HasType("value", typeof(int), "parameter")); } [Fact] - public void Should_throw_when_checking_for_null_and_target_is_null() + public void HasType_nongeneric_should_do_nothing_for_null_value() { - Assert.Throws(() => Guard.HasType(null, "parameter")); + Guard.HasType(null, typeof(int), "parameter"); } [Fact] - public void Should_do_nothing_when_target_is_not_default_value() + public void HasType_nongeneric_should_do_nothing_for_correct_type() { - Guard.NotDefault(Guid.NewGuid(), "parameter"); + Guard.HasType(123, typeof(int), "parameter"); } [Fact] - public void Should_throw_exception_when_value_has_default() + public void HasType_nongeneric_should_do_nothing_for_null_type() + { + Guard.HasType(123, null, "parameter"); + } + + [Fact] + public void NotDefault_should_throw_for_default_values() { Assert.Throws(() => Guard.NotDefault(Guid.Empty, "parameter")); Assert.Throws(() => Guard.NotDefault(0, "parameter")); @@ -102,6 +120,12 @@ namespace PinkParrot.Infrastructure Assert.Throws(() => Guard.NotDefault(false, "parameter")); } + [Fact] + public void NotDefault_should_do_nothing_for_non_default_value() + { + Guard.NotDefault(Guid.NewGuid(), "parameter"); + } + [Theory] [InlineData("")] [InlineData(" ")] @@ -110,7 +134,7 @@ namespace PinkParrot.Infrastructure [InlineData(" not-a-slug ")] [InlineData("-not-a-slug-")] [InlineData("not$-a-slug")] - public void Should_throw_exception_for_invalid_slug(string slug) + public void ValidSlug_should_throw_for_invalid_slugs(string slug) { Assert.Throws(() => Guard.ValidSlug(slug, "slug")); } @@ -120,9 +144,162 @@ namespace PinkParrot.Infrastructure [InlineData("slug23")] [InlineData("other-slug")] [InlineData("just-another-slug")] - public void Should_do_nothing_for_valid_slug(string slug) + public void ValidSlug_should_do_nothing_for_valid_slugs(string slug) { Guard.ValidSlug(slug, "parameter"); } + + [Theory] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NegativeInfinity)] + [InlineData(double.NaN)] + public void ValidNumber_should_throw_for_invalid_doubles(double value) + { + Assert.Throws(() => Guard.ValidNumber(value, "parameter")); + } + + [Theory] + [InlineData(0d)] + [InlineData(-1000d)] + [InlineData(1000d)] + public void ValidNumber_do_nothing_for_valid_double(double value) + { + Guard.ValidNumber(value, "parameter"); + } + + [Theory] + [InlineData(float.PositiveInfinity)] + [InlineData(float.NegativeInfinity)] + [InlineData(float.NaN)] + public void ValidNumber_should_throw_for_invalid_float(float value) + { + Assert.Throws(() => Guard.ValidNumber(value, "parameter")); + } + + [Theory] + [InlineData(0f)] + [InlineData(-1000f)] + [InlineData(1000f)] + public void ValidNumber_do_nothing_for_valid_float(float value) + { + Guard.ValidNumber(value, "parameter"); + } + + [Theory] + [InlineData(4)] + [InlineData(104)] + public void Between_should_throw_for_values_outside_of_range(int value) + { + Assert.Throws(() => Guard.Between(value, 10, 100, "parameter")); + } + + [Theory] + [InlineData(10)] + [InlineData(55)] + [InlineData(100)] + public void Between_should_do_nothing_for_values_in_range(int value) + { + Guard.Between(value, 10, 100, "parameter"); + } + + [Theory] + [InlineData(0)] + [InlineData(100)] + public void GreaterThan_should_throw_for_smaller_values(int value) + { + Assert.Throws(() => Guard.GreaterThan(value, 100, "parameter")); + } + + [Theory] + [InlineData(101)] + [InlineData(200)] + public void GreaterThan_should_do_nothing_for_greater_values(int value) + { + Guard.GreaterThan(value, 100, "parameter"); + } + + [Theory] + [InlineData(0)] + [InlineData(99)] + public void GreaterEquals_should_throw_for_smaller_values(int value) + { + Assert.Throws(() => Guard.GreaterEquals(value, 100, "parameter")); + } + + [Theory] + [InlineData(100)] + [InlineData(200)] + public void GreaterEquals_should_do_nothing_for_greater_values(int value) + { + Guard.GreaterEquals(value, 100, "parameter"); + } + + [Theory] + [InlineData(1000)] + [InlineData(100)] + public void LessThan_should_throw_for_greater_values(int value) + { + Assert.Throws(() => Guard.LessThan(value, 100, "parameter")); + } + + [Theory] + [InlineData(99)] + [InlineData(50)] + public void LessThan_should_do_nothing_for_smaller_values(int value) + { + Guard.LessThan(value, 100, "parameter"); + } + + [Theory] + [InlineData(1000)] + [InlineData(101)] + public void LessEquals_should_throw_for_greater_values(int value) + { + Assert.Throws(() => Guard.LessEquals(value, 100, "parameter")); + } + + [Theory] + [InlineData(100)] + [InlineData(50)] + public void LessEquals_should_do_nothing_for_smaller_values(int value) + { + Guard.LessEquals(value, 100, "parameter"); + } + + [Fact] + public void NotEmpty_should_throw_for_empty_collection() + { + Assert.Throws(() => Guard.NotEmpty(new int[0], "parameter")); + } + + [Fact] + public void NotEmpty_should_throw_for_null_collection() + { + Assert.Throws(() => Guard.NotEmpty((int[])null, "parameter")); + } + + [Fact] + public void NotEmpty_should_do_nothing_for_value_collection() + { + Guard.NotEmpty(new [] { 1, 2, 3 }, "parameter"); + } + + [Fact] + public void ValidFileName_should_throw_for_invalid_file_name() + { + Assert.Throws(() => Guard.ValidFileName("File/Name", "Parameter")); + } + + [Fact] + public void ValidFileName_should_throw_for_null_file_name() + { + Assert.Throws(() => Guard.ValidFileName(null, "Parameter")); + } + + [Fact] + public void ValidFileName_should_do_nothing_for_valid_file_name() + { + Guard.ValidFileName("FileName", "Parameter"); + } } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj index d1144a582..14f6cb7a2 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj @@ -15,5 +15,8 @@ 2.0 + + + \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/Reflection/SimpleMapperTests.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/Reflection/SimpleMapperTests.cs new file mode 100644 index 000000000..c6ad001e9 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/Reflection/SimpleMapperTests.cs @@ -0,0 +1,83 @@ +// ========================================================================== +// SimpleMapperTests.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using Xunit; + +namespace PinkParrot.Infrastructure.Reflection +{ + public class SimpleMapperTests + { + public class Class1Base + { + public string MappedString { get; set; } + + public string MappedNull { get; set; } + + public long MappedNumber { get; set; } + + public Guid MappedGuid { get; set; } + } + + public class Class1 : Class1Base + { + public string UnmappedString { get; set; } + } + + public class Class2Base + { + public string MappedString { get; protected set; } + + public int MappedNull { get; set; } + + public int MappedNumber { get; set; } + + public string MappedGuid { get; set; } + } + + public class Class2 : Class2Base + { + public string UnmappedString + { + get { return "Value"; } + } + } + + [Fact] + public void Should_throw_if_mapping_with_null_source() + { + Assert.Throws(() => SimpleMapper.Map((Class1)null, new Class2())); + } + + [Fact] + public void Should_throw_if_mapping_with_null_target() + { + Assert.Throws(() => SimpleMapper.Map(new Class1(), (Class2)null)); + } + + [Fact] + public void Should_map_between_types() + { + var class1 = new Class1 + { + UnmappedString = Guid.NewGuid().ToString(), + MappedString = Guid.NewGuid().ToString(), + MappedNumber = 123, + MappedGuid = Guid.NewGuid() + }; + var class2 = new Class2(); + + SimpleMapper.Map(class1, class2); + + Assert.Equal(class1.MappedString, class2.MappedString); + Assert.Equal(class1.MappedNumber, class2.MappedNumber); + Assert.Equal(class1.MappedGuid.ToString(), class2.MappedGuid); + Assert.NotEqual(class1.UnmappedString, class2.UnmappedString); + } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs index 5c4e1bac4..dc47c0a3d 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs @@ -106,16 +106,5 @@ namespace PinkParrot.Infrastructure return result; } - - public static bool TryGetValueAsObject(this IReadOnlyDictionary dictionary, TKey key, out object value) - { - TValue result; - - var isFound = dictionary.TryGetValue(key, out result); - - value = result; - - return isFound; - } } } \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs index f999e5560..fb5cca769 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs @@ -56,11 +56,19 @@ namespace PinkParrot.Infrastructure [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void HasType(object target, string parameterName) { - NotNull(target, "parameterName"); + if (target != null && target.GetType() != typeof(T)) + { + throw new ArgumentException($"The parameter must be of type {typeof(T)}", parameterName); + } + } - if (target.GetType() != typeof(T)) + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void HasType(object target, Type expectedType, string parameterName) + { + if (target != null && expectedType != null && target.GetType() != expectedType) { - throw new ArgumentException("The parameter must be of type " + typeof(T), parameterName); + throw new ArgumentException($"The parameter must be of type {expectedType}", parameterName); } } @@ -70,9 +78,7 @@ namespace PinkParrot.Infrastructure { if (!target.IsBetween(lower, upper)) { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be between {0} and {1}", lower, upper); - - throw new ArgumentException(message, parameterName); + throw new ArgumentException($"Value must be between {lower} and {upper}", parameterName); } } @@ -82,9 +88,7 @@ namespace PinkParrot.Infrastructure { if (!target.IsEnumValue()) { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be a valid enum type {0}", typeof(TEnum)); - - throw new ArgumentException(message, parameterName); + throw new ArgumentException($"Value must be a valid enum type {typeof(TEnum)}", parameterName); } } @@ -94,9 +98,7 @@ namespace PinkParrot.Infrastructure { if (target.CompareTo(lower) <= 0) { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", lower); - - throw new ArgumentException(message, parameterName); + throw new ArgumentException($"Value must be greater than {lower}", parameterName); } } @@ -106,9 +108,7 @@ namespace PinkParrot.Infrastructure { if (target.CompareTo(lower) < 0) { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", lower); - - throw new ArgumentException(message, parameterName); + throw new ArgumentException($"Value must be greater or equals than {lower}", parameterName); } } @@ -118,9 +118,7 @@ namespace PinkParrot.Infrastructure { if (target.CompareTo(upper) >= 0) { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", upper); - - throw new ArgumentException(message, parameterName); + throw new ArgumentException($"Value must be less than {upper}", parameterName); } } @@ -130,9 +128,7 @@ namespace PinkParrot.Infrastructure { if (target.CompareTo(upper) > 0) { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", upper); - - throw new ArgumentException(message, parameterName); + throw new ArgumentException($"Value must be less or equals than {upper}", parameterName); } } @@ -140,10 +136,7 @@ namespace PinkParrot.Infrastructure [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void NotEmpty(ICollection enumerable, string parameterName) { - if (enumerable == null) - { - throw new ArgumentNullException(nameof(enumerable)); - } + NotNull(enumerable, parameterName); if (enumerable.Count == 0) { @@ -183,22 +176,14 @@ namespace PinkParrot.Infrastructure [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void NotNullOrEmpty(string target, string parameterName, bool allowWhitespacesAtStartOrEnd = true) + public static void NotNullOrEmpty(string target, string parameterName) { - if (target == null) - { - throw new ArgumentNullException(parameterName); - } + NotNull(target, parameterName); if (string.IsNullOrWhiteSpace(target)) { throw new ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName); } - - if (!allowWhitespacesAtStartOrEnd && target.Trim() != target) - { - throw new ArgumentException("String cannot start or end with whitespaces", parameterName); - } } [DebuggerStepThrough] @@ -212,29 +197,5 @@ namespace PinkParrot.Infrastructure throw new ArgumentException("Value contains an invalid character.", parameterName); } } - - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsType(object target, string parameterName) - { - if (target != null && target.GetType() != typeof(T)) - { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be of type {0}", typeof(T)); - - throw new ArgumentException(message, parameterName); - } - } - - [DebuggerStepThrough] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsType(object target, Type expectedType, string parameterName) - { - if (target != null && expectedType != null && target.GetType() != expectedType) - { - var message = string.Format(CultureInfo.CurrentCulture, "Value must be of type {0}", expectedType); - - throw new ArgumentException(message, parameterName); - } - } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs index 754cadf9a..928ebb50f 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs @@ -24,9 +24,27 @@ namespace PinkParrot.Infrastructure lock (namesByType) { - namesByType.Add(type, name); + try + { + namesByType.Add(type, name); + } + catch (ArgumentException) + { + var message = $"The type '{type}' is already registered with name '{namesByType[type]}'"; + + throw new ArgumentException(message, nameof(type)); + } - typesByName.Add(name, type); + try + { + typesByName.Add(name, type); + } + catch (ArgumentException) + { + var message = $"The name '{name}' is already registered with type '{typesByName[name]}'"; + + throw new ArgumentException(message, nameof(type)); + } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/FieldDto.cs b/src/pinkparrot_read/PinkParrot.Read/Models/FieldDto.cs index f04ded3ed..c2db13356 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/FieldDto.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Models/FieldDto.cs @@ -6,22 +6,46 @@ // All rights reserved. // ========================================================================== -using Newtonsoft.Json; using PinkParrot.Core.Schemas; namespace PinkParrot.Read.Models { public class FieldDto { - [JsonProperty] public string Name { get; set; } - [JsonProperty] - public IFieldProperties Properties { get; set; } + public bool IsHidden { get; set; } + + public bool IsDisabled { get; set; } + + public FieldProperties Properties { get; set; } public static FieldDto Create(Field field) { - return new FieldDto { Name = field.Name, Properties = field.RawProperties }; + return new FieldDto + { + 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/pinkparrot_read/PinkParrot.Read/Models/SchemaDto.cs b/src/pinkparrot_read/PinkParrot.Read/Models/SchemaDto.cs index c06edd658..6ad92e387 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/SchemaDto.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Models/SchemaDto.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; using PinkParrot.Core.Schemas; using PinkParrot.Infrastructure; @@ -19,13 +18,10 @@ namespace PinkParrot.Read.Models { public sealed class SchemaDto { - [JsonProperty] public string Name { get; set; } - - [JsonProperty] + public Dictionary Fields { get; set; } - - [JsonProperty] + public SchemaProperties Properties { get; set; } public static SchemaDto Create(Schema schema) @@ -54,7 +50,7 @@ namespace PinkParrot.Read.Models { var field = kvp.Value; - schema = schema.AddOrUpdateField(registry.CreateField(kvp.Key, field.Name, field.Properties)); + schema = schema.AddOrUpdateField(field.ToField(kvp.Key, registry)); } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaCommandHandler.cs b/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaCommandHandler.cs index c99b0eadc..f8a728a14 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaCommandHandler.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaCommandHandler.cs @@ -14,6 +14,7 @@ using PinkParrot.Core.Schemas; using PinkParrot.Infrastructure; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.Dispatching; +using PinkParrot.Read.Services; using PinkParrot.Write.Schemas.Commands; namespace PinkParrot.Write.Schemas @@ -21,18 +22,20 @@ namespace PinkParrot.Write.Schemas public class SchemaCommandHandler : CommandHandler { private readonly FieldRegistry registry; + private readonly ISchemaProvider schemaProvider; private readonly JsonSerializer serializer; public SchemaCommandHandler( FieldRegistry registry, + ISchemaProvider schemaProvider, IDomainObjectFactory domainObjectFactory, IDomainObjectRepository domainObjectRepository, JsonSerializer serializer) : base(domainObjectFactory, domainObjectRepository) { this.registry = registry; - this.serializer = serializer; + this.schemaProvider = schemaProvider; } public override Task HandleAsync(CommandContext context) @@ -40,9 +43,15 @@ namespace PinkParrot.Write.Schemas return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command); } - public Task On(CreateSchema command) + public async Task On(CreateSchema command) { - return CreateAsync(command, s => s.Create(command.TenantId, command.Name, command.Properties)); + if (await schemaProvider.FindSchemaIdByNameAsync(command.TenantId, command.Name) != null) + { + var error = new ValidationError($"A schema with name '{command.Name}' already exists", "Name"); + + throw new ValidationException("Cannot create a new schema", error); + } + await CreateAsync(command, s => s.Create(command.TenantId, command.Name, command.Properties)); } public Task On(DeleteSchema command) @@ -106,9 +115,9 @@ namespace PinkParrot.Write.Schemas }); } - private IFieldProperties CreateProperties(JToken token, Type type) + private FieldProperties CreateProperties(JToken token, Type type) { - return (IFieldProperties)token.ToObject(type, serializer); + return (FieldProperties)token.ToObject(type, serializer); } } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaDomainObject.cs b/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaDomainObject.cs index 9c3b12094..3dd286354 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaDomainObject.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaDomainObject.cs @@ -101,7 +101,7 @@ namespace PinkParrot.Write.Schemas isDeleted = false; } - public void AddField(string name, IFieldProperties properties) + public void AddField(string name, FieldProperties properties) { VerifyCreatedAndNotDeleted(); @@ -122,7 +122,7 @@ namespace PinkParrot.Write.Schemas RaiseEvent(new SchemaUpdated { Properties = properties }); } - public void UpdateField(long fieldId, IFieldProperties properties) + public void UpdateField(long fieldId, FieldProperties properties) { VerifyCreatedAndNotDeleted();