/// <summary>
        /// Creates a new <see cref="BinarySerializerBuilderCaseResult" /> for an unsuccessful
        /// outcome.
        /// </summary>
        /// <param name="exception">
        /// An exception describing the inapplicability of the case.
        /// </param>
        /// <returns>
        /// A <see cref="BinarySerializerBuilderCaseResult" /> with <see cref="Exceptions" />
        /// populated and <see cref="Expression" /> <c>null</c>.
        /// </returns>
        public static BinarySerializerBuilderCaseResult FromException(Exception exception)
        {
            var result = new BinarySerializerBuilderCaseResult();

            result.Exceptions.Add(exception);

            return(result);
        }
Esempio n. 2
0
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for an <see cref="EnumSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is an <see cref="EnumSchema" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> is an enum type and <paramref name="schema" />
        /// does not have a matching symbol for each member or when <paramref name="type" /> cannot
        /// be converted to <see cref="string" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema is EnumSchema enumSchema)
            {
                var writeInteger = typeof(BinaryWriter)
                                   .GetMethod(nameof(BinaryWriter.WriteInteger), new[] { typeof(long) });

                var underlying = Nullable.GetUnderlyingType(type) ?? type;

                // enum fields will always be public static, so no need to expose binding flags:
                var fields  = type.GetFields(BindingFlags.Public | BindingFlags.Static);
                var symbols = enumSchema.Symbols.ToList();

                var cases = type.IsEnum
                    ? fields
                            .Select(field =>
                {
                    var index = symbols.FindIndex(symbol => IsMatch(symbol, field));

                    if (index < 0)
                    {
                        throw new UnsupportedTypeException(type, $"{type} has a field {field.Name} that cannot be serialized.");
                    }

                    if (symbols.FindLastIndex(symbol => IsMatch(symbol, field)) != index)
                    {
                        throw new UnsupportedTypeException(type, $"{type} has an ambiguous field {field.Name}.");
                    }

                    return(Expression.SwitchCase(
                               Expression.Call(context.Writer, writeInteger, Expression.Constant((long)index)),
                               Expression.Constant(Enum.Parse(type, field.Name))));
                })
                    : symbols
                            .Select((symbol, index) =>
                {
                    return(Expression.SwitchCase(
                               Expression.Call(context.Writer, writeInteger, Expression.Constant((long)index)),
                               Expression.Constant(symbol)));
                });

                var exceptionConstructor = typeof(ArgumentOutOfRangeException)
                                           .GetConstructor(new[] { typeof(string) });

                var exception    = Expression.New(exceptionConstructor, Expression.Constant("Enum value out of range."));
                var intermediate = Expression.Variable(type.IsEnum ? type : typeof(string));

                return(BinarySerializerBuilderCaseResult.FromExpression(
                           Expression.Block(
                               new[] { intermediate },
                               Expression.Assign(intermediate, BuildConversion(value, intermediate.Type)),
                               Expression.Switch(intermediate, Expression.Throw(exception), cases.ToArray()))));
            }
            else
            {
                return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryEnumSerializerBuilderCase)} can only be applied to {nameof(EnumSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for a <see cref="DurationLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="DurationLogicalType" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="FixedSchema" /> with size
        /// <c>12</c>.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be converted to <see cref="TimeSpan" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema.LogicalType is DurationLogicalType)
            {
                if (!(schema is FixedSchema fixedSchema && fixedSchema.Size == 12))
                {
                    throw new UnsupportedSchemaException(schema);
                }

                Expression Write(Expression value)
                {
                    var getBytes = typeof(BitConverter)
                                   .GetMethod(nameof(BitConverter.GetBytes), new[] { value.Type });

                    Expression bytes = Expression.Call(null, getBytes, value);

                    if (!BitConverter.IsLittleEndian)
                    {
                        var buffer  = Expression.Variable(bytes.Type);
                        var reverse = typeof(Array)
                                      .GetMethod(nameof(Array.Reverse), new[] { bytes.Type });

                        bytes = Expression.Block(
                            new[] { buffer },
                            Expression.Assign(buffer, bytes),
                            Expression.Call(null, reverse, buffer),
                            buffer);
                    }

                    var writeFixed = typeof(BinaryWriter)
                                     .GetMethod(nameof(BinaryWriter.WriteFixed), new[] { bytes.Type });

                    return(Expression.Call(context.Writer, writeFixed, bytes));
                }

                var totalDays = typeof(TimeSpan).GetProperty(nameof(TimeSpan.TotalDays));
                var totalMs   = typeof(TimeSpan).GetProperty(nameof(TimeSpan.TotalMilliseconds));

                return(BinarySerializerBuilderCaseResult.FromExpression(
                           Expression.Block(
                               Write(Expression.Constant(0U)),
                               Write(
                                   Expression.ConvertChecked(Expression.Property(value, totalDays), typeof(uint))),
                               Write(
                                   Expression.ConvertChecked(
                                       Expression.Subtract(
                                           Expression.Convert(Expression.Property(value, totalMs), typeof(ulong)),
                                           Expression.Multiply(
                                               Expression.Convert(Expression.Property(value, totalDays), typeof(ulong)),
                                               Expression.Constant(86400000UL))),
                                       typeof(uint))))));
            }
            else
            {
                return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDurationSerializerBuilderCase)} can only be applied schemas with a {nameof(DurationLogicalType)}.")));
            }
        }
Esempio n. 4
0
 /// <summary>
 /// Builds a <see cref="BinarySerializer{T}" /> for a <see cref="NullSchema" />.
 /// </summary>
 /// <returns>
 /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
 /// is a <see cref="NullSchema" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
 /// otherwise.
 /// </returns>
 /// <inheritdoc />
 public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
 {
     if (schema is NullSchema)
     {
         return(BinarySerializerBuilderCaseResult.FromExpression(Expression.Empty()));
     }
     else
     {
         return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryNullSerializerBuilderCase)} can only be applied to {nameof(NullSchema)}s.")));
     }
 }
Esempio n. 5
0
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for a <see cref="TimestampLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="TimestampLogicalType" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="LongSchema" /> or when
        /// <paramref name="schema" /> does not have a <see cref="MicrosecondTimestampLogicalType" />
        /// or a <see cref="MillisecondTimestampLogicalType" />.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be converted to <see cref="DateTimeOffset" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema.LogicalType is TimestampLogicalType)
            {
                if (schema is not LongSchema)
                {
                    throw new UnsupportedSchemaException(schema);
                }

                Expression expression;

                try
                {
                    expression = BuildConversion(value, typeof(DateTimeOffset));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }

                var factor = schema.LogicalType switch
                {
                    MicrosecondTimestampLogicalType => TimeSpan.TicksPerMillisecond / 1000,
                    MillisecondTimestampLogicalType => TimeSpan.TicksPerMillisecond,
                    _ => throw new UnsupportedSchemaException(schema, $"{schema.LogicalType} is not a supported {nameof(TimestampLogicalType)}."),
                };

                var utcTicks = typeof(DateTimeOffset)
                               .GetProperty(nameof(DateTimeOffset.UtcTicks));

                var writeInteger = typeof(BinaryWriter)
                                   .GetMethod(nameof(BinaryWriter.WriteInteger), new[] { typeof(long) });

                // return writer.WriteInteger((value.UtcTicks - epoch) / factor);
                return(BinarySerializerBuilderCaseResult.FromExpression(
                           Expression.Call(
                               context.Writer,
                               writeInteger,
                               Expression.Divide(
                                   Expression.Subtract(
                                       Expression.Property(expression, utcTicks),
                                       Expression.Constant(Epoch.Ticks)),
                                   Expression.Constant(factor)))));
            }
            else
            {
                return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryTimestampSerializerBuilderCase)} can only be applied schemas with a {nameof(TimestampLogicalType)}.")));
            }
        }
Esempio n. 6
0
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for a <see cref="StringSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="StringSchema" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be converted to <see cref="string" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema is StringSchema stringSchema)
            {
                var writeString = typeof(BinaryWriter)
                                  .GetMethod(nameof(BinaryWriter.WriteString), new[] { typeof(string) });

                try
                {
                    return(BinarySerializerBuilderCaseResult.FromExpression(
                               Expression.Call(context.Writer, writeString, BuildConversion(value, typeof(string)))));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {stringSchema} to {type}.", exception);
                }
            }
            else
            {
                return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryStringSerializerBuilderCase)} can only be applied to {nameof(StringSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for an <see cref="FixedSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> <paramref name="schema" />
        /// is a <see cref="FixedSchema" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be converted to <see cref="T:System.Byte[]" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema is FixedSchema fixedSchema)
            {
                Expression expression;

                try
                {
                    expression = BuildConversion(value, typeof(byte[]));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {fixedSchema} to {type}.", exception);
                }

                var bytes = Expression.Parameter(typeof(byte[]));

                var exceptionConstructor = typeof(OverflowException)
                                           .GetConstructor(new[] { typeof(string) });

                var writeFixed = typeof(BinaryWriter)
                                 .GetMethod(nameof(BinaryWriter.WriteFixed), new[] { typeof(byte[]) });

                return(BinarySerializerBuilderCaseResult.FromExpression(
                           Expression.Block(
                               new[] { bytes },
                               Expression.Assign(bytes, expression),
                               Expression.IfThen(
                                   Expression.NotEqual(Expression.ArrayLength(bytes), Expression.Constant(fixedSchema.Size)),
                                   Expression.Throw(Expression.New(exceptionConstructor, Expression.Constant($"Only arrays of size {fixedSchema.Size} can be serialized to {fixedSchema}.")))),
                               Expression.Call(context.Writer, writeFixed, bytes))));
            }
            else
            {
                return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryFixedSerializerBuilderCase)} can only be applied to {nameof(FixedSchema)}s.")));
            }
        }
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for an <see cref="MapSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="type" />
        /// is a dictionary type and <paramref name="schema" /> is a <see cref="MapSchema" />; an
        /// unsuccessful <see cref="BinarySerializerBuilderCaseResult" /> otherwise.
        /// </returns>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> does not implement <see cref="T:System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair`2}" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema is MapSchema mapSchema)
            {
                var dictionaryTypes = GetDictionaryTypes(type);

                if (dictionaryTypes is not null || type == typeof(object))
                {
                    // support dynamic mapping:
                    var keyType   = dictionaryTypes?.Key ?? typeof(object);
                    var valueType = dictionaryTypes?.Value ?? typeof(object);

                    var pairType   = typeof(KeyValuePair <,>).MakeGenericType(keyType, valueType);
                    var collection = Expression.Variable(typeof(ICollection <>).MakeGenericType(pairType));
                    var enumerable = Expression.Variable(typeof(IEnumerable <>).MakeGenericType(pairType));
                    var enumerator = Expression.Variable(typeof(IEnumerator <>).MakeGenericType(pairType));
                    var loop       = Expression.Label();

                    var getCount = collection.Type
                                   .GetProperty("Count");

                    var getEnumerator = enumerable.Type
                                        .GetMethod("GetEnumerator", Type.EmptyTypes);

                    var getCurrent = enumerator.Type
                                     .GetProperty(nameof(IEnumerator.Current));

                    var getKey = pairType
                                 .GetProperty("Key")
                                 .GetGetMethod();

                    var getValue = pairType
                                   .GetProperty("Value")
                                   .GetGetMethod();

                    var moveNext = typeof(IEnumerator)
                                   .GetMethod(nameof(IEnumerator.MoveNext), Type.EmptyTypes);

                    var writeInteger = typeof(BinaryWriter)
                                       .GetMethod(nameof(BinaryWriter.WriteInteger), new[] { typeof(long) });

                    var writeKey = SerializerBuilder
                                   .BuildExpression(Expression.Property(Expression.Property(enumerator, getCurrent), getKey), new StringSchema(), context);

                    var writeValue = SerializerBuilder
                                     .BuildExpression(Expression.Property(Expression.Property(enumerator, getCurrent), getValue), mapSchema.Value, context);

                    var dispose = typeof(IDisposable)
                                  .GetMethod(nameof(IDisposable.Dispose), Type.EmptyTypes);

                    Expression expression;

                    try
                    {
                        expression = BuildConversion(value, collection.Type);
                    }
                    catch (Exception exception)
                    {
                        throw new UnsupportedTypeException(type, $"Failed to map {mapSchema} to {type}.", exception);
                    }

                    // if (collection.Count > 0)
                    // {
                    //     writer.WriteInteger((long)collection.Count);
                    //
                    //     var enumerator = collection.GetEnumerator();
                    //
                    //     try
                    //     {
                    //         // primitive foreach:
                    //         loop: while (true)
                    //         {
                    //             if (enumerator.MoveNext())
                    //             {
                    //                 ...
                    //             }
                    //             else
                    //             {
                    //                 break loop;
                    //             }
                    //         }
                    //     }
                    //     finally
                    //     {
                    //         enumerator.Dispose();
                    //     }
                    // }
                    //
                    // // write closing block:
                    // writer.WriteInteger(0L);
                    return(BinarySerializerBuilderCaseResult.FromExpression(
                               Expression.Block(
                                   new[] { collection, enumerator },
                                   Expression.Assign(collection, expression),
                                   Expression.IfThen(
                                       Expression.GreaterThan(
                                           Expression.Property(collection, getCount),
                                           Expression.Constant(0)),
                                       Expression.Block(
                                           Expression.Call(
                                               context.Writer,
                                               writeInteger,
                                               Expression.Convert(
                                                   Expression.Property(collection, getCount),
                                                   typeof(long))),
                                           Expression.Assign(
                                               enumerator,
                                               Expression.Call(collection, getEnumerator)),
                                           Expression.TryFinally(
                                               Expression.Loop(
                                                   Expression.IfThenElse(
                                                       Expression.Call(enumerator, moveNext),
                                                       Expression.Block(writeKey, writeValue),
                                                       Expression.Break(loop)),
                                                   loop),
                                               Expression.Call(enumerator, dispose)))),
                                   Expression.Call(
                                       context.Writer,
                                       writeInteger,
                                       Expression.Constant(0L)))));
                }
                else
                {
                    return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryMapSerializerBuilderCase)} can only be applied to dictionary types.")));
                }
            }
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for a <see cref="UnionSchema" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
        /// is a <see cref="UnionSchema" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> has no <see cref="UnionSchema.Schemas" />.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be mapped to at least one <see cref="Schema" />
        /// in <paramref name="schema" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema is UnionSchema unionSchema)
            {
                if (unionSchema.Schemas.Count < 1)
                {
                    throw new UnsupportedSchemaException(schema, "A serializer cannot be built for an empty union.");
                }

                var schemas    = unionSchema.Schemas.ToList();
                var candidates = schemas.Where(s => s is not NullSchema).ToList();
                var @null      = schemas.Find(s => s is NullSchema);

                var writeInteger = typeof(BinaryWriter)
                                   .GetMethod(nameof(BinaryWriter.WriteInteger), new[] { typeof(long) });

                Expression expression;

                // if there are non-null schemas, select the first matching one for each possible type:
                if (candidates.Count > 0)
                {
                    var cases      = new Dictionary <Type, Expression>();
                    var exceptions = new List <Exception>();

                    foreach (var candidate in candidates)
                    {
                        var selected = SelectType(type, candidate);

                        if (cases.ContainsKey(selected))
                        {
                            continue;
                        }

                        var underlying = Nullable.GetUnderlyingType(selected) ?? selected;

                        Expression body;

                        try
                        {
                            body = Expression.Block(
                                Expression.Call(
                                    context.Writer,
                                    writeInteger,
                                    Expression.Constant((long)schemas.IndexOf(candidate))),
                                SerializerBuilder.BuildExpression(Expression.Convert(value, underlying), candidate, context));
                        }
                        catch (Exception exception)
                        {
                            exceptions.Add(exception);
                            continue;
                        }

                        if (@null != null && !(selected.IsValueType && Nullable.GetUnderlyingType(selected) == null))
                        {
                            body = Expression.IfThenElse(
                                Expression.Equal(value, Expression.Constant(null, selected)),
                                Expression.Call(
                                    context.Writer,
                                    writeInteger,
                                    Expression.Constant((long)schemas.IndexOf(@null))),
                                body);
                        }

                        cases.Add(selected, body);
                    }

                    if (cases.Count == 0)
                    {
                        throw new UnsupportedTypeException(
                                  type,
                                  $"{type.Name} does not match any non-null members of {unionSchema}.",
                                  new AggregateException(exceptions));
                    }

                    if (cases.Count == 1 && cases.First() is var first && first.Key == type)
                    {
                        expression = first.Value;
                    }
                    else
                    {
                        var exceptionConstructor = typeof(InvalidOperationException)
                                                   .GetConstructor(new[] { typeof(string) });

                        expression = Expression.Throw(Expression.New(
                                                          exceptionConstructor,
                                                          Expression.Constant($"Unexpected type encountered serializing to {type}.")));

                        foreach (var @case in cases)
                        {
                            expression = Expression.IfThenElse(
                                Expression.TypeIs(value, @case.Key),
                                @case.Value,
                                expression);
                        }
                    }
                }

                // otherwise, we know that the schema is just ["null"]:
                else
                {
                    expression = Expression.Call(
                        context.Writer,
                        writeInteger,
                        Expression.Constant((long)schemas.IndexOf(@null)));
                }

                return(BinarySerializerBuilderCaseResult.FromExpression(expression));
            }
Esempio n. 10
0
        /// <summary>
        /// Builds a <see cref="BinarySerializer{T}" /> for a <see cref="DecimalLogicalType" />.
        /// </summary>
        /// <returns>
        /// A successful <see cref="BinarySerializerBuilderCaseResult" /> if <paramref name="schema" />
        /// has a <see cref="DecimalLogicalType" />; an unsuccessful <see cref="BinarySerializerBuilderCaseResult" />
        /// otherwise.
        /// </returns>
        /// <exception cref="UnsupportedSchemaException">
        /// Thrown when <paramref name="schema" /> is not a <see cref="BytesSchema" /> or a
        /// <see cref="FixedSchema "/>.
        /// </exception>
        /// <exception cref="UnsupportedTypeException">
        /// Thrown when <paramref name="type" /> cannot be converted to <see cref="decimal" />.
        /// </exception>
        /// <inheritdoc />
        public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression value, Type type, Schema schema, BinarySerializerBuilderContext context)
        {
            if (schema.LogicalType is DecimalLogicalType decimalLogicalType)
            {
                var precision = decimalLogicalType.Precision;
                var scale     = decimalLogicalType.Scale;

                Expression expression;

                try
                {
                    expression = BuildConversion(value, typeof(decimal));
                }
                catch (InvalidOperationException exception)
                {
                    throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception);
                }

                // declare variables for in-place transformation:
                var bytes = Expression.Variable(typeof(byte[]));

                var integerConstructor = typeof(BigInteger)
                                         .GetConstructor(new[] { typeof(decimal) });

                var reverse = typeof(Array)
                              .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) });

                var toByteArray = typeof(BigInteger)
                                  .GetMethod(nameof(BigInteger.ToByteArray), Type.EmptyTypes);

                // var fraction = new BigInteger(...) * BigInteger.Pow(10, scale);
                // var whole = new BigInteger((... % 1m) * (decimal)Math.Pow(10, scale));
                // var bytes = (fraction + whole).ToByteArray();
                //
                // // BigInteger is little-endian, so reverse:
                // Array.Reverse(bytes);
                //
                // return bytes;
                expression = Expression.Block(
                    Expression.Assign(
                        bytes,
                        Expression.Call(
                            Expression.Add(
                                Expression.Multiply(
                                    Expression.New(
                                        integerConstructor,
                                        expression),
                                    Expression.Constant(BigInteger.Pow(10, scale))),
                                Expression.New(
                                    integerConstructor,
                                    Expression.Multiply(
                                        Expression.Modulo(expression, Expression.Constant(1m)),
                                        Expression.Constant((decimal)Math.Pow(10, scale))))),
                            toByteArray)),
                    Expression.Call(null, reverse, bytes),
                    bytes);

                // figure out how to write:
                if (schema is BytesSchema)
                {
                    var writeBytes = typeof(BinaryWriter)
                                     .GetMethod(nameof(BinaryWriter.WriteBytes), new[] { typeof(byte[]) });

                    expression = Expression.Block(
                        new[] { bytes },
                        expression,
                        Expression.Call(context.Writer, writeBytes, bytes));
                }
                else if (schema is FixedSchema fixedSchema)
                {
                    var exceptionConstructor = typeof(OverflowException)
                                               .GetConstructor(new[] { typeof(string) });

                    var writeFixed = typeof(BinaryWriter)
                                     .GetMethod(nameof(BinaryWriter.WriteFixed), new[] { typeof(byte[]) });

                    expression = Expression.Block(
                        new[] { bytes },
                        expression,
                        Expression.IfThen(
                            Expression.NotEqual(Expression.ArrayLength(bytes), Expression.Constant(fixedSchema.Size)),
                            Expression.Throw(Expression.New(exceptionConstructor, Expression.Constant($"Size mismatch between {fixedSchema} (size {fixedSchema.Size}) and decimal with precision {precision} and scale {scale}.")))),
                        Expression.Call(context.Writer, writeFixed, bytes));
                }
                else
                {
                    throw new UnsupportedSchemaException(schema);
                }

                return(BinarySerializerBuilderCaseResult.FromExpression(expression));
            }
            else
            {
                return(BinarySerializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDecimalSerializerBuilderCase)} can only be applied schemas with a {nameof(DecimalLogicalType)}.")));
            }
        }