Skip to content

Instantly share code, notes, and snippets.

@ChaseFlorell
Created July 17, 2020 14:34
Show Gist options
  • Save ChaseFlorell/9bb47f2f25e3db7c028f5f638d6ac365 to your computer and use it in GitHub Desktop.
Save ChaseFlorell/9bb47f2f25e3db7c028f5f638d6ac365 to your computer and use it in GitHub Desktop.
Couchbase Mapping Idea
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");
}
}
}
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"
}
};
}
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; }
}
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; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment