Browse Source

Geolocation Field in API

pull/1/head
Sebastian 9 years ago
parent
commit
0e587dec82
  1. 3
      src/Squidex.Core/Schemas/BooleanField.cs
  2. 2
      src/Squidex.Core/Schemas/DateTimeField.cs
  3. 2
      src/Squidex.Core/Schemas/DateTimeFieldProperties.cs
  4. 4
      src/Squidex.Core/Schemas/Field.cs
  5. 3
      src/Squidex.Core/Schemas/FieldRegistry.cs
  6. 89
      src/Squidex.Core/Schemas/GeolocationField.cs
  7. 15
      src/Squidex.Core/Schemas/GeolocationFieldEditor.cs
  8. 44
      src/Squidex.Core/Schemas/GeolocationFieldProperties.cs
  9. 3
      src/Squidex.Core/Schemas/JsonField.cs
  10. 3
      src/Squidex.Core/Schemas/NumberField.cs
  11. 3
      src/Squidex.Core/Schemas/StringField.cs
  12. 11
      src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs
  13. 1
      src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs
  14. 38
      src/Squidex/Controllers/Api/Schemas/Models/GeolocationPropertiesDto.cs
  15. 98
      tests/Squidex.Core.Tests/Schemas/GeolocationFieldTests.cs
  16. 79
      tests/Squidex.Core.Tests/Schemas/GeolocationPropertiesTests.cs
  17. 12
      tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs
  18. 4
      tests/Squidex.Core.Tests/Schemas/SchemaTests.cs

3
src/Squidex.Core/Schemas/BooleanField.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library; using Microsoft.OData.Edm.Library;
@ -35,7 +36,7 @@ namespace Squidex.Core.Schemas
return (bool?)value; return (bool?)value;
} }
protected override void PrepareJsonSchema(JsonProperty jsonProperty) protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{ {
jsonProperty.Type = JsonObjectType.Boolean; jsonProperty.Type = JsonObjectType.Boolean;
} }

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

@ -65,7 +65,7 @@ namespace Squidex.Core.Schemas
throw new InvalidCastException("Invalid json type, expected string."); throw new InvalidCastException("Invalid json type, expected string.");
} }
protected override void PrepareJsonSchema(JsonProperty jsonProperty) protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{ {
jsonProperty.Type = JsonObjectType.String; jsonProperty.Type = JsonObjectType.String;

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

@ -67,7 +67,7 @@ namespace Squidex.Core.Schemas
public override JToken GetDefaultValue() public override JToken GetDefaultValue()
{ {
return DefaultValue != null ? DefaultValue.ToString() : null; return DefaultValue?.ToString();
} }
protected override IEnumerable<ValidationError> ValidateCore() protected override IEnumerable<ValidationError> ValidateCore()

4
src/Squidex.Core/Schemas/Field.cs

@ -196,7 +196,7 @@ namespace Squidex.Core.Schemas
{ {
var languageProperty = new JsonProperty { Description = language.EnglishName }; var languageProperty = new JsonProperty { Description = language.EnglishName };
PrepareJsonSchema(languageProperty); PrepareJsonSchema(languageProperty, schemaResolver);
languagesObject.Properties.Add(language.Iso2Code, languageProperty); languagesObject.Properties.Add(language.Iso2Code, languageProperty);
} }
@ -231,7 +231,7 @@ namespace Squidex.Core.Schemas
protected abstract IEdmTypeReference CreateEdmType(); protected abstract IEdmTypeReference CreateEdmType();
protected abstract void PrepareJsonSchema(JsonProperty jsonProperty); protected abstract void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver);
protected abstract object ConvertValue(JToken value); protected abstract object ConvertValue(JToken value);
} }

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

@ -70,6 +70,9 @@ namespace Squidex.Core.Schemas
Add<JsonFieldProperties>( Add<JsonFieldProperties>(
(id, name, p) => new JsonField(id, name, (JsonFieldProperties)p)); (id, name, p) => new JsonField(id, name, (JsonFieldProperties)p));
Add<GeolocationFieldProperties>(
(id, name, p) => new GeolocationField(id, name, (GeolocationFieldProperties)p));
} }
public void Add<TFieldProperties>(FactoryFunction fieldFactory) public void Add<TFieldProperties>(FactoryFunction fieldFactory)

89
src/Squidex.Core/Schemas/GeolocationField.cs

@ -0,0 +1,89 @@
// ==========================================================================
// GeolocationField.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Newtonsoft.Json.Linq;
using NJsonSchema;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
public sealed class GeolocationField : Field<GeolocationFieldProperties>
{
public GeolocationField(long id, string name, GeolocationFieldProperties properties)
: base(id, name, properties)
{
}
protected override IEnumerable<IValidator> CreateValidators()
{
if (Properties.IsRequired)
{
yield return new RequiredValidator();
}
}
protected override object ConvertValue(JToken value)
{
var geolocation = (JObject)value;
foreach (var property in geolocation.Properties())
{
if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidCastException("Geolocation can only have latitude and longitude property.");
}
}
var lat = (double)geolocation["latitude"];
var lon = (double)geolocation["longitude"];
Guard.Between(lat, -90, 90, "latitude");
Guard.Between(lon, -180, 180, "longitude");
return value;
}
protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{
jsonProperty.Type = JsonObjectType.Object;
var geolocationSchema = new JsonSchema4();
geolocationSchema.Properties.Add("latitude", new JsonProperty
{
Type = JsonObjectType.Number,
Minimum = -90,
Maximum = 90,
IsRequired = true
});
geolocationSchema.Properties.Add("longitude", new JsonProperty
{
Type = JsonObjectType.Number,
Minimum = -180,
Maximum = 180,
IsRequired = true
});
geolocationSchema.AllowAdditionalProperties = false;
var schemaReference = schemaResolver("GeolocationDto", geolocationSchema);
jsonProperty.SchemaReference = schemaReference;
}
protected override IEdmTypeReference CreateEdmType()
{
return null;
}
}
}

15
src/Squidex.Core/Schemas/GeolocationFieldEditor.cs

@ -0,0 +1,15 @@
// ==========================================================================
// GeolocationFieldEditor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Core.Schemas
{
public enum GeolocationFieldEditor
{
Map
}
}

44
src/Squidex.Core/Schemas/GeolocationFieldProperties.cs

@ -0,0 +1,44 @@
// ==========================================================================
// GeolocationFieldProperties.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
[TypeName("GeolocationField")]
public sealed class GeolocationFieldProperties : FieldProperties
{
private GeolocationFieldEditor editor;
public GeolocationFieldEditor Editor
{
get { return editor; }
set
{
ThrowIfFrozen();
editor = value;
}
}
public override JToken GetDefaultValue()
{
return null;
}
protected override IEnumerable<ValidationError> ValidateCore()
{
if (!Editor.IsEnumValue())
{
yield return new ValidationError("Editor ist not a valid value", nameof(Editor));
}
}
}
}

3
src/Squidex.Core/Schemas/JsonField.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -34,7 +35,7 @@ namespace Squidex.Core.Schemas
return value; return value;
} }
protected override void PrepareJsonSchema(JsonProperty jsonProperty) protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{ {
jsonProperty.Type = JsonObjectType.Object; jsonProperty.Type = JsonObjectType.Object;
} }

3
src/Squidex.Core/Schemas/NumberField.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
@ -41,7 +42,7 @@ namespace Squidex.Core.Schemas
} }
} }
protected override void PrepareJsonSchema(JsonProperty jsonProperty) protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{ {
jsonProperty.Type = JsonObjectType.Number; jsonProperty.Type = JsonObjectType.Number;

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

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
@ -47,7 +48,7 @@ namespace Squidex.Core.Schemas
} }
} }
protected override void PrepareJsonSchema(JsonProperty jsonProperty) protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func<string, JsonSchema4, JsonSchema4> schemaResolver)
{ {
jsonProperty.Type = JsonObjectType.String; jsonProperty.Type = JsonObjectType.String;

11
src/Squidex/Controllers/Api/Schemas/Models/Converters/SchemaConverter.cs

@ -38,6 +38,10 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters
{ {
typeof(BooleanFieldProperties), typeof(BooleanFieldProperties),
p => Convert((BooleanFieldProperties)p) p => Convert((BooleanFieldProperties)p)
},
{
typeof(GeolocationFieldProperties),
p => Convert((GeolocationFieldProperties)p)
} }
}; };
@ -85,6 +89,13 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters
return result; return result;
} }
private static FieldPropertiesDto Convert(GeolocationFieldProperties source)
{
var result = SimpleMapper.Map(source, new GeolocationFieldPropertiesDto());
return result;
}
private static FieldPropertiesDto Convert(StringFieldProperties source) private static FieldPropertiesDto Convert(StringFieldProperties source)
{ {
var result = SimpleMapper.Map(source, new StringFieldPropertiesDto()); var result = SimpleMapper.Map(source, new StringFieldPropertiesDto());

1
src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs

@ -17,6 +17,7 @@ namespace Squidex.Controllers.Api.Schemas.Models
[JsonConverter(typeof(JsonInheritanceConverter), "fieldType")] [JsonConverter(typeof(JsonInheritanceConverter), "fieldType")]
[KnownType(typeof(BooleanFieldPropertiesDto))] [KnownType(typeof(BooleanFieldPropertiesDto))]
[KnownType(typeof(DateTimeFieldPropertiesDto))] [KnownType(typeof(DateTimeFieldPropertiesDto))]
[KnownType(typeof(GeolocationFieldPropertiesDto))]
[KnownType(typeof(JsonFieldPropertiesDto))] [KnownType(typeof(JsonFieldPropertiesDto))]
[KnownType(typeof(NumberFieldPropertiesDto))] [KnownType(typeof(NumberFieldPropertiesDto))]
[KnownType(typeof(StringFieldPropertiesDto))] [KnownType(typeof(StringFieldPropertiesDto))]

38
src/Squidex/Controllers/Api/Schemas/Models/GeolocationPropertiesDto.cs

@ -0,0 +1,38 @@
// ==========================================================================
// GeolocationFieldPropertiesDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NJsonSchema.Annotations;
using Squidex.Core.Schemas;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Controllers.Api.Schemas.Models
{
[JsonSchema("Geolocation")]
public sealed class GeolocationFieldPropertiesDto : FieldPropertiesDto
{
/// <summary>
/// The default value for the field value.
/// </summary>
public bool? DefaultValue { get; set; }
/// <summary>
/// The editor that is used to manage this field.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public GeolocationFieldEditor Editor { get; set; }
public override FieldProperties ToProperties()
{
var result = SimpleMapper.Map(this, new GeolocationFieldProperties());
return result;
}
}
}

98
tests/Squidex.Core.Tests/Schemas/GeolocationFieldTests.cs

@ -0,0 +1,98 @@
// ==========================================================================
// GeolocationFieldTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Squidex.Core.Schemas
{
public class GeolocationFieldTests
{
private readonly List<string> errors = new List<string>();
[Fact]
public void Should_instantiate_field()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties());
Assert.Equal("my-geolocation", sut.Name);
}
[Fact]
public void Should_clone_object()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties());
Assert.NotEqual(sut, sut.Enable());
}
[Fact]
public async Task Should_not_add_error_if_geolocation_is_valid()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation" });
var geolocation = new JObject(
new JProperty("latitude", 0),
new JProperty("longitude", 0));
await sut.ValidateAsync(CreateValue(geolocation), errors);
Assert.Empty(errors);
}
[Fact]
public async Task Should_add_errors_if_geolocation_has_invalid_properties()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation", IsRequired = true });
var geolocation = new JObject(
new JProperty("latitude", 200),
new JProperty("longitude", 0));
await sut.ValidateAsync(CreateValue(geolocation), errors);
errors.ShouldBeEquivalentTo(
new[] { "my-geolocation is not a valid value" });
}
[Fact]
public async Task Should_add_errors_if_geolocation_has_too_many_properties()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation", IsRequired = true });
var geolocation = new JObject(
new JProperty("invalid", 0),
new JProperty("latitude", 0),
new JProperty("longitude", 0));
await sut.ValidateAsync(CreateValue(geolocation), errors);
errors.ShouldBeEquivalentTo(
new[] { "my-geolocation is not a valid value" });
}
[Fact]
public async Task Should_add_errors_if_geolocation_is_required()
{
var sut = new GeolocationField(1, "my-geolocation", new GeolocationFieldProperties { Label = "my-geolocation", IsRequired = true });
await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors);
errors.ShouldBeEquivalentTo(
new[] { "my-geolocation is required" });
}
private static JToken CreateValue(JToken v)
{
return v;
}
}
}

79
tests/Squidex.Core.Tests/Schemas/GeolocationPropertiesTests.cs

@ -0,0 +1,79 @@
// ==========================================================================
// GeolocationFieldTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Core.Schemas
{
public class GeolocationFieldPropertiesTests
{
private readonly List<ValidationError> errors = new List<ValidationError>();
[Fact]
public void Should_add_error_if_editor_is_not_valid()
{
var sut = new GeolocationFieldProperties { Editor = (GeolocationFieldEditor)123 };
sut.Validate(errors);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Editor ist not a valid value", "Editor")
});
}
[Fact]
public void Should_set_or_freeze_sut()
{
var sut = new GeolocationFieldProperties();
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen"))
{
var value =
property.PropertyType.GetTypeInfo().IsValueType ?
Activator.CreateInstance(property.PropertyType) :
null;
property.SetValue(sut, value);
var result = property.GetValue(sut);
Assert.Equal(value, result);
}
sut.Freeze();
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen"))
{
var value =
property.PropertyType.GetTypeInfo().IsValueType ?
Activator.CreateInstance(property.PropertyType) :
null;
Assert.Throws<InvalidOperationException>(() =>
{
try
{
property.SetValue(sut, value);
}
catch (Exception ex)
{
throw ex.InnerException;
}
});
}
}
}
}

12
tests/Squidex.Core.Tests/Schemas/Json/JsonSerializerTests.cs

@ -34,13 +34,17 @@ namespace Squidex.Core.Schemas.Json
var schema = var schema =
Schema.Create("my-schema", new SchemaProperties()) Schema.Create("my-schema", new SchemaProperties())
.AddOrUpdateField(new StringField(1, "my-string", .AddOrUpdateField(new StringField(1, "my-string",
new StringFieldProperties { Label = "My-String", Pattern = "[0-9]{3}" })).DisableField(1) new StringFieldProperties { Label = "My-String", Pattern = "[0-9]{3}" }))
.AddOrUpdateField(new NumberField(2, "my-number", .AddOrUpdateField(new NumberField(2, "my-number",
new NumberFieldProperties { Hints = "My-Number" })) new NumberFieldProperties { Hints = "My-Number" }))
.AddOrUpdateField(new BooleanField(3, "my-boolean", .AddOrUpdateField(new BooleanField(3, "my-boolean",
new BooleanFieldProperties())).HideField(2) new BooleanFieldProperties())).HideField(3)
.AddOrUpdateField(new DateTimeField(4, "my-datetime", .AddOrUpdateField(new JsonField(4, "my-json",
new DateTimeFieldProperties())).HideField(2) new JsonFieldProperties())).DisableField(4)
.AddOrUpdateField(new DateTimeField(5, "my-datetime",
new DateTimeFieldProperties()))
.AddOrUpdateField(new GeolocationField(6, "my-geolocation",
new GeolocationFieldProperties()))
.Publish(); .Publish();
var deserialized = sut.Deserialize(sut.Serialize(schema)); var deserialized = sut.Deserialize(sut.Serialize(schema));

4
tests/Squidex.Core.Tests/Schemas/SchemaTests.cs

@ -290,7 +290,9 @@ namespace Squidex.Core.Schemas
.AddOrUpdateField(new DateTimeField(6, "my-datetime", .AddOrUpdateField(new DateTimeField(6, "my-datetime",
new DateTimeFieldProperties { Editor = DateTimeFieldEditor.DateTime })) new DateTimeFieldProperties { Editor = DateTimeFieldEditor.DateTime }))
.AddOrUpdateField(new DateTimeField(7, "my-date", .AddOrUpdateField(new DateTimeField(7, "my-date",
new DateTimeFieldProperties { Editor = DateTimeFieldEditor.Date })); new DateTimeFieldProperties { Editor = DateTimeFieldEditor.Date }))
.AddOrUpdateField(new GeolocationField(8, "my-geolocation",
new GeolocationFieldProperties()));
return schema; return schema;
} }

Loading…
Cancel
Save