Skip to content

Instantly share code, notes, and snippets.

@ChaseFlorell
Created July 17, 2020 14:34
Show Gist options
  • Select an option

  • Save ChaseFlorell/9bb47f2f25e3db7c028f5f638d6ac365 to your computer and use it in GitHub Desktop.

Select an option

Save ChaseFlorell/9bb47f2f25e3db7c028f5f638d6ac365 to your computer and use it in GitHub Desktop.

Revisions

  1. ChaseFlorell created this gist Jul 17, 2020.
    369 changes: 369 additions & 0 deletions CoucnbaseMappingTests.cs
    Original 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");
    }
    }
    }
    26 changes: 26 additions & 0 deletions PersonFixture.cs
    Original 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"
    }
    };
    }
    13 changes: 13 additions & 0 deletions PetFixture.cs
    Original 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; }
    }
    14 changes: 14 additions & 0 deletions VehicleFixture.cs
    Original 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; }
    }