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() .AddMapping() .AddMapping(); }); } [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(); 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 { {"breed", PetFixture.Fido.Breed}, {"name", PetFixture.Fido.Name} }); // act var pet = dict.ToObject(); // 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 { {"b", PetFixture.Fido.Breed}, {"n", PetFixture.Fido.Name} }); // act var pet = dict.ToObject(); // 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 { {"Mk", VehicleFixture.ToyotaTundra.Make}, {"Mdl", VehicleFixture.ToyotaTundra.Model} }); // act var vehicle = dict.ToObject(); // assert vehicle.Make.Should() .Be(VehicleFixture.ToyotaTundra.Make); vehicle.Model.Should() .Be(VehicleFixture.ToyotaTundra.Model); } [Test] public void ShouldMapComplexObjectWithMap() { // arrange var map = _mappingProvider.GetMap(); 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 { {"firstName", PersonFixture.JohnSmith.FirstName}, {"lastName", PersonFixture.JohnSmith.LastName}, { "address", new Dictionary { {"street", PersonFixture.JohnSmith.Address.Street}, {"postalCode", PersonFixture.JohnSmith.Address.PostalCode} } } }); // act var person = dict.ToObject(); // 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() where T : class; } public sealed class MappingProvider : IMappingProvider { private static MappingOptions __mappingOptions; public static void Configure(Action 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() 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 _mappings = new Dictionary(); public MappingOptions(MappingProvider mappingProvider) => _mappingProvider = mappingProvider; public MappingOptions AddMapping() where T : Mapping { var mapping = (T) Activator.CreateInstance(typeof(T), _mappingProvider); _mappings[mapping.ObjectTypeName] = mapping; return this; } public IPropertyNameConverter GetMap() => 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 : Mapping where T : class { protected Mapping(MappingProvider mappingProvider) : base(mappingProvider) => ObjectTypeName = typeof(T).Name; protected void MappingFor(Expression> 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 { 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
{ public AddressMap(MappingProvider provider) : base(provider) { MappingFor(x => x.Street, "st"); MappingFor(x => x.PostalCode, "pc"); } } public class PetMap : Mapping { public PetMap(MappingProvider provider) : base(provider) { MappingFor(x => x.Breed, "b"); MappingFor(x => x.Name, "n"); } } }