Created
July 17, 2020 14:34
-
-
Save ChaseFlorell/9bb47f2f25e3db7c028f5f638d6ac365 to your computer and use it in GitHub Desktop.
Revisions
-
ChaseFlorell created this gist
Jul 17, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,369 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Couchbase.Lite; using Couchbase.Lite.Mapping; using FluentAssertions; using NUnit.Framework; // ReSharper disable TailRecursiveCall // because the recursive call is easier to read ;) namespace SomeMagicalNamespace { [TestFixture] public class CouchbaseMappingTests { private readonly IMappingProvider _mappingProvider; public CouchbaseMappingTests() { _mappingProvider = new MappingProvider(); MappingProvider.Configure(opts => { opts.AddMapping<PersonMap>() .AddMapping<AddressMap>() .AddMapping<PetMap>(); }); } [Test] public void ShouldMapComplexObject() { // arrange var person = PersonFixture.JohnSmith; // act var dictionaryObject = person.ToMutableDocument(); // assert dictionaryObject["firstName"] .Value .Should() .Be(PersonFixture.JohnSmith.FirstName); dictionaryObject["lastName"] .Value .Should() .Be(PersonFixture.JohnSmith.LastName); dictionaryObject["address"]["street"] .Value .Should() .Be(PersonFixture.JohnSmith.Address.Street); dictionaryObject["address"]["postalCode"] .Value .Should() .Be(PersonFixture.JohnSmith.Address.PostalCode); } [Test] public void ShouldMapBasicObject() { // arrange var pet = PetFixture.Fido; // act var dictionaryObject = pet.ToMutableDocument(); // assert dictionaryObject["breed"] .String .Should() .Be(PetFixture.Fido.Breed); dictionaryObject["name"] .String .Should() .Be(PetFixture.Fido.Name); } [Test] public void ShouldMapBasicObjectWithMap() { // arrange var map = _mappingProvider.GetMap<Pet>(); var pet = PetFixture.Fido; // act var dictionaryObject = pet.ToMutableDocument(map); // assert dictionaryObject["b"] .String .Should() .Be(PetFixture.Fido.Breed); dictionaryObject["n"] .String .Should() .Be(PetFixture.Fido.Name); } [Test] public void ShouldMapBasicDictionary() { // arrange var dict = new MutableDocument(new Dictionary<string, object> { {"breed", PetFixture.Fido.Breed}, {"name", PetFixture.Fido.Name} }); // act var pet = dict.ToObject<Pet>(); // assert pet.Name.Should() .Be(PetFixture.Fido.Name); pet.Breed.Should() .Be(PetFixture.Fido.Breed); } [Test] public void ShouldMapBasicDictionaryWithMap() { // arrange var dict = new MutableDocument(new Dictionary<string, object> { {"b", PetFixture.Fido.Breed}, {"n", PetFixture.Fido.Name} }); // act var pet = dict.ToObject<Pet>(); // assert pet.Name.Should() .Be(PetFixture.Fido.Name); pet.Breed.Should() .Be(PetFixture.Fido.Breed); } [Test] public void ShouldMapBasicDictionaryWithAttributeMap() { // arrange var dict = new MutableDocument(new Dictionary<string, object> { {"Mk", VehicleFixture.ToyotaTundra.Make}, {"Mdl", VehicleFixture.ToyotaTundra.Model} }); // act var vehicle = dict.ToObject<Vehicle>(); // assert vehicle.Make.Should() .Be(VehicleFixture.ToyotaTundra.Make); vehicle.Model.Should() .Be(VehicleFixture.ToyotaTundra.Model); } [Test] public void ShouldMapComplexObjectWithMap() { // arrange var map = _mappingProvider.GetMap<Person>(); var person = PersonFixture.JohnSmith; // act var dictionaryObject = person.ToMutableDocument(map); // assert dictionaryObject["fn"] .Value .Should() .Be(PersonFixture.JohnSmith.FirstName); dictionaryObject["ln"] .Value .Should() .Be(PersonFixture.JohnSmith.LastName); dictionaryObject["addr"]["st"] .Value .Should() .Be(PersonFixture.JohnSmith.Address.Street); dictionaryObject["addr"]["pc"] .Value .Should() .Be(PersonFixture.JohnSmith.Address.PostalCode); } [Test] public void ShouldMapComplexDictionaryWithMap() { throw new NotImplementedException(); } [Test] public void ShouldMapComplexDictionary() { // arrange var dict = new MutableDocument(new Dictionary<string, object> { {"firstName", PersonFixture.JohnSmith.FirstName}, {"lastName", PersonFixture.JohnSmith.LastName}, { "address", new Dictionary<string, object> { {"street", PersonFixture.JohnSmith.Address.Street}, {"postalCode", PersonFixture.JohnSmith.Address.PostalCode} } } }); // act var person = dict.ToObject<Person>(); // assert person.FirstName.Should() .Be(PersonFixture.JohnSmith.FirstName); person.LastName.Should() .Be(PersonFixture.JohnSmith.LastName); person.Address.Street.Should() .Be(PersonFixture.JohnSmith.Address.Street); person.Address.PostalCode.Should() .Be(PersonFixture.JohnSmith.Address.PostalCode); } } public interface IMappingProvider { IPropertyNameConverter GetMap<T>() where T : class; } public sealed class MappingProvider : IMappingProvider { private static MappingOptions __mappingOptions; public static void Configure(Action<MappingOptions> action) { if (__mappingOptions != null) { throw new InvalidOperationException("You can only configure your mapping one time."); } var provider = new MappingProvider(); __mappingOptions = new MappingOptions(provider); action.Invoke(__mappingOptions); } public IPropertyNameConverter GetMap<T>() where T : class => GetMap(typeof(T).Name); public IPropertyNameConverter GetMap(string objectPropertyName) { if (__mappingOptions is null) { throw new InvalidOperationException("You must configure your mapping before trying to acquire a map."); } return __mappingOptions.GetMap(objectPropertyName); } } public sealed class MappingOptions { private readonly MappingProvider _mappingProvider; private readonly Dictionary<string, Mapping> _mappings = new Dictionary<string, Mapping>(); public MappingOptions(MappingProvider mappingProvider) => _mappingProvider = mappingProvider; public MappingOptions AddMapping<T>() where T : Mapping { var mapping = (T) Activator.CreateInstance(typeof(T), _mappingProvider); _mappings[mapping.ObjectTypeName] = mapping; return this; } public IPropertyNameConverter GetMap<T>() => GetMap(typeof(T).Name); public IPropertyNameConverter GetMap(string objectTypeName) => _mappings[objectTypeName]; } public abstract class Mapping : IPropertyNameConverter { protected Mapping(MappingProvider mappingProvider) => _mappingProvider = mappingProvider; protected readonly HashSet<(string objectPropetyName, string documentPropertyName)> PropertyMap = new HashSet<(string objectPropetyName, string documentPropertyName)>(); private readonly Dictionary<(string objectPropetyName, string documentPropertyName), Mapping> _childMaps = new Dictionary<(string objectPropetyName, string documentPropertyName), Mapping>(); public string ObjectTypeName { get; protected set; } public string Convert(string val) { var propertyMap = PropertyMap.FirstOrDefault(x => x.objectPropetyName == val); if (propertyMap != default) { return propertyMap.documentPropertyName; } // mapping must be on a complex object var childMapping = _mappingProvider.GetMap(val); // how do we access complex child objects? throw new NotImplementedException(); } private readonly MappingProvider _mappingProvider; } public abstract class Mapping<T> : Mapping where T : class { protected Mapping(MappingProvider mappingProvider) : base(mappingProvider) => ObjectTypeName = typeof(T).Name; protected void MappingFor(Expression<Func<T, object>> expression, string propertyName) { var memberName = GetMemberName(expression.Body); PropertyMap.Add((memberName, propertyName)); } private static string GetMemberName(Expression expression) { switch (expression.NodeType) { case ExpressionType.MemberAccess: return ((MemberExpression) expression).Member.Name; case ExpressionType.Convert: return GetMemberName(((UnaryExpression) expression).Operand); default: throw new NotSupportedException(expression.NodeType.ToString()); } } } public class PersonMap : Mapping<Person> { public PersonMap(MappingProvider provider) : base(provider) { MappingFor(x => x.FirstName, "fn"); MappingFor(x => x.LastName, "ln"); MappingFor(x => x.Address, "addr"); } } public class AddressMap : Mapping<Address> { public AddressMap(MappingProvider provider) : base(provider) { MappingFor(x => x.Street, "st"); MappingFor(x => x.PostalCode, "pc"); } } public class PetMap : Mapping<Pet> { public PetMap(MappingProvider provider) : base(provider) { MappingFor(x => x.Breed, "b"); MappingFor(x => x.Name, "n"); } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,26 @@ public sealed class Person { public string FirstName { get; set; } public string LastName { get; set; } public Address Address { get; set; } } public sealed class Address { public string Street { get; set; } public string PostalCode { get; set; } } public static class PersonFixture { public static Person JohnSmith = new Person { FirstName = "John", LastName = "Smith", Address = new Address { Street = "123 Elm", PostalCode = "A1A 1A1" } }; } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,13 @@ public class PetFixture { public static Pet Fido = new Pet { Breed = "Shitzu", Name = "Fido" }; } public class Pet { public string Breed { get; set; } public string Name { get; set; } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,14 @@ public class VehicleFixture { public static readonly Vehicle ToyotaTundra = new Vehicle { Make = "Toyota", Model = "Tundra" }; } public sealed class Vehicle { [MappingPropertyName("Mk")] public string Make { get; set; } [MappingPropertyName("Mdl")] public string Model { get; set; } }