Skip to content

Commit

Permalink
task: readded support for the IDecimal primitive type.
Browse files Browse the repository at this point in the history
  • Loading branch information
dtanglr committed Jul 14, 2024
1 parent 27b1308 commit f67e4f7
Show file tree
Hide file tree
Showing 27 changed files with 1,008 additions and 245 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<PackageOutputPath>$(MSBuildThisFileDirectory)..\artifacts</PackageOutputPath>
<version>1.5.0-alpha.37</version>
<version>1.5.0-alpha.38</version>
</PropertyGroup>

<ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/Primitively.Abstractions/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public enum DataType
/// </summary>
DateOnly,

/// <summary>
/// Represents an <see cref="IDecimal"/> type that encapsulates a <see cref="decimal"/> value.
/// </summary>
Decimal,

/// <summary>
/// Represents an <see cref="IDouble"/> type that encapsulates a <see cref="double"/> value.
/// </summary>
Expand Down
67 changes: 67 additions & 0 deletions src/Primitively.Abstractions/DecimalAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace Primitively;

/// <summary>
/// This attribute should be used on a <c>partial record struct</c> to source generate
/// a Primitively <see cref="IDecimal"/> type that encapsulates a <see cref="decimal"/> value.
/// </summary>
/// <example>
/// These examples show how to use the Decimal attribute to source generate a Primitively <see cref="IDecimal"/> type.
/// <code>
/// [Decimal]
/// public partial record struct Example;
/// </code>
/// <code>
/// [Decimal(3)]
/// public partial record struct Example;
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
public sealed class DecimalAttribute : NumericAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="DecimalAttribute"/> class.
/// </summary>
public DecimalAttribute()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DecimalAttribute"/> class with the specified number of fractional digits.
/// </summary>
/// <param name="digits">The number of fractional digits in the value of the source generated Primitively <see cref="IDecimal"/> type.</param>
public DecimalAttribute(int digits)
{
Digits = digits;
}

/// <summary>
/// Initializes a new instance of the <see cref="DecimalAttribute"/> class with the specified number of fractional digits and rounding mode.
/// </summary>
/// <param name="digits">The number of fractional digits in the value of the source generated Primitively <see cref="IDecimal"/> type.</param>
/// <param name="mode">The rounding specification for how to round value of the source generated Primitively <see cref="IDecimal"/> type if it is midway between two other numbers.</param>
public DecimalAttribute(int digits, MidpointRounding mode)
{
Digits = digits;
Mode = mode;
}

/// <summary>
/// Gets the number of fractional digits in the value of the source generated Primitively <see cref="IDecimal"/> type.
/// </summary>
/// <remarks>
/// Valid values are: -1 to 28.
/// Values above 28 will default to: -1.
/// Values below -1 will default to: -1.
/// A value of -1 will result in: no rounding.
/// </remarks>
public int Digits { get; }

/// <summary>
/// Gets the rounding specification for how to round value of the source generated Primitively <see cref="IDecimal"/> type
/// if it is midway between two other numbers.
/// </summary>
/// <remarks>
/// If the value of <see cref="Digits"/> is -1, this property will have no effect.
/// </remarks>
public MidpointRounding Mode { get; }
}
22 changes: 22 additions & 0 deletions src/Primitively.Abstractions/DecimalInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Primitively;

/// <summary>
/// This class represents metadata properties common to all source generated Primitively numeric types.
/// </summary>
/// <param name="Type">The .NET type of the Primitively type.</param>
/// <param name="Example">An optional example of the integer.</param>
/// <param name="CreateFrom">A function that creates an instance of the Primitively type from a string.</param>
/// <param name="Minimum">The minimum value that can be set on the source generated Primitively type.</param>
/// <param name="Maximum">The maximum value that can be set on the source generated Primitively type.</param>
/// <param name="Digits">The number of fractional digits in the value on the source generated Primitively type</param>
/// <param name="Mode">The rounding specification for how to round value of the source generated Primitively type
/// if it is midway between two other numbers.</param>
public sealed record DecimalInfo(
Type Type,
string? Example,
Func<string?, IPrimitive> CreateFrom,
decimal Minimum,
decimal Maximum,
int Digits,
MidpointRounding Mode)
: NumericInfo<decimal>(DataType.Decimal, Type, Example, CreateFrom, Minimum, Maximum);
11 changes: 11 additions & 0 deletions src/Primitively.Abstractions/IDecimal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Primitively;

/// <summary>
/// Defines a contract for a Primitively type that encapsulates a <see cref="decimal"/> value.
/// </summary>
/// <remarks>
/// Implementations of this interface are source generated by Primitively and inherit from both <see cref="IPrimitive{T}"/> and <see cref="INumeric"/> interfaces.
/// </remarks>
public interface IDecimal : IPrimitive<decimal>, INumeric
{
}
4 changes: 2 additions & 2 deletions src/Primitively.Abstractions/NumericAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ public abstract class NumericAttribute : PrimitiveAttribute
/// <summary>
/// Gets or sets the maximum value that can be set on the source generated Primitively type.
/// </summary>
public object? Maximum { get; set; }
public object? Maximum { get; }

/// <summary>
/// Gets or sets the minimum value that can be set on the source generated Primitively type.
/// </summary>
public object? Minimum { get; set; }
public object? Minimum { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,17 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
schema.Type = numericInfo.DataType switch
{
DataType.Single => "number",
DataType.Decimal => "number",
DataType.Double => "number",
DataType.Single => "number",
_ => "integer"
};
schema.Properties = null;
schema.Example = !string.IsNullOrWhiteSpace(numericInfo.Example) ? new OpenApiString(numericInfo.Example) : null;
schema.Minimum = numericInfo switch
{
NumericInfo<byte> byteInfo => byteInfo.Minimum,
NumericInfo<decimal> decimalInfo => decimalInfo.Minimum,
NumericInfo<double> doubleInfo => TryGetDecimal(doubleInfo.Minimum),
NumericInfo<int> intInfo => intInfo.Minimum,
NumericInfo<long> longInfo => longInfo.Minimum,
Expand All @@ -109,6 +111,7 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
schema.Maximum = numericInfo switch
{
NumericInfo<byte> byteInfo => byteInfo.Maximum,
NumericInfo<decimal> decimalInfo => decimalInfo.Maximum,
NumericInfo<double> doubleInfo => TryGetDecimal(doubleInfo.Maximum),
NumericInfo<int> intInfo => intInfo.Maximum,
NumericInfo<long> longInfo => longInfo.Maximum,
Expand Down
1 change: 1 addition & 0 deletions src/Primitively.MongoDB.Bson/BsonOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ private static IEnumerable<IBsonSerializerOptions> GetAll()
// Initialises the default instance for each option
yield return new BsonIByteSerializerOptions();
yield return new BsonIDateOnlySerializerOptions();
yield return new BsonIDecimalSerializerOptions();
yield return new BsonIDoubleSerializerOptions();
yield return new BsonIGuidSerializerOptions();
yield return new BsonIIntSerializerOptions();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;

namespace Primitively.MongoDB.Bson.Serialization.Serializers;

/// <summary>
/// Represents a BSON serializer for Primitively <see cref="IDecimal"/> types that encapsulate <see cref="decimal"/> values.
/// </summary>
public class BsonIDecimalSerializer<TPrimitive> :
StructSerializerBase<TPrimitive>,
IRepresentationConfigurable<BsonIDecimalSerializer<TPrimitive>>,
IRepresentationConverterConfigurable<BsonIDecimalSerializer<TPrimitive>> where TPrimitive : struct, IDecimal
{
private readonly DecimalSerializer _serializer;

/// <summary>
/// Initializes a new instance of the <see cref="BsonIDecimalSerializer{TPrimitive}"/> class.
/// </summary>
/// <remarks>
/// The default representation is <see cref="BsonType.String"/>.
/// </remarks>
public BsonIDecimalSerializer()
{
_serializer = new DecimalSerializer();
}

/// <summary>
/// Initializes a new instance of the <see cref="BsonIDecimalSerializer{TPrimitive}"/> class.
/// </summary>
/// <param name="representation">The representation.</param>
public BsonIDecimalSerializer(BsonType representation)
{
_serializer = new DecimalSerializer(representation);
}

/// <summary>
/// Initializes a new instance of the <see cref="BsonIDecimalSerializer{TPrimitive}"/> class.
/// </summary>
/// <param name="representation">The representation.</param>
/// <param name="converter">The converter.</param>
public BsonIDecimalSerializer(BsonType representation, RepresentationConverter converter)
{
_serializer = new DecimalSerializer(representation, converter);
}

/// <summary>
/// Initializes a new instance of the <see cref="BsonIDecimalSerializer{TPrimitive}"/> class.
/// </summary>
/// <param name="serializer">The serializer.</param>
private BsonIDecimalSerializer(DecimalSerializer serializer)
{
_serializer = serializer;
}
/// <summary>
/// Gets a cached instance of the <see cref="BsonIDecimalSerializer{TPrimitive}"/> class.
/// </summary>
public static BsonIDecimalSerializer<TPrimitive> Instance { get; } = new();

/// <summary>
/// Gets the converter.
/// </summary>
public RepresentationConverter Converter => _serializer.Converter;

/// <summary>
/// Gets the representation.
/// </summary>
public BsonType Representation => _serializer.Representation;

/// <summary>
/// Initializes a new instance of the <see cref="BsonIDecimalSerializer{TPrimitive}"/> class.
/// </summary>
/// <param name="serializer">The serializer.</param>
public static BsonIDecimalSerializer<TPrimitive> Create(DecimalSerializer serializer) => new(serializer);

/// <summary>
/// Deserializes a value.
/// </summary>
/// <param name="context">The deserialization context.</param>
/// <param name="args">The deserialization args.</param>
/// <returns>A deserialized value.</returns>
public override TPrimitive Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var value = _serializer.Deserialize(context, args);

return (TPrimitive)Activator.CreateInstance(typeof(TPrimitive), value)!;
}

/// <summary>
/// Serializes a value.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="args">The serialization args.</param>
/// <param name="value">The object.</param>
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TPrimitive value)
{
_serializer.Serialize(context, args, value.Value);
}

/// <summary>
/// Returns a serializer that has been reconfigured with the specified item serializer.
/// </summary>
/// <param name="converter">The converter.</param>
/// <returns>The reconfigured serializer.</returns>
public BsonIDecimalSerializer<TPrimitive> WithConverter(RepresentationConverter converter)
{
if (converter == _serializer.Converter)
{
return this;
}

return new BsonIDecimalSerializer<TPrimitive>(_serializer.Representation, converter);
}

// Explicit interface implementations
IBsonSerializer IRepresentationConverterConfigurable.WithConverter(RepresentationConverter converter)
{
return WithConverter(converter);
}

/// <summary>
/// Returns a serializer that has been reconfigured with the specified representation.
/// </summary>
/// <param name="representation">The representation.</param>
/// <returns>The reconfigured serializer.</returns>
public BsonIDecimalSerializer<TPrimitive> WithRepresentation(BsonType representation)
{
if (representation == _serializer.Representation)
{
return this;
}

return new BsonIDecimalSerializer<TPrimitive>(representation, _serializer.Converter);
}
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
{
return WithRepresentation(representation);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;

namespace Primitively.MongoDB.Bson.Serialization.Serializers;

/// <summary>
/// Represents the options used to configure the BSON serialization of Primitively <see cref="IDecimal"/> types.
/// </summary>
public class BsonIDecimalSerializerOptions : IBsonConvertibleSerializerOptions<BsonIDecimalSerializerOptions>
{
/// <summary>
/// Gets the <see cref="Primitively.DataType"/> of the Primitively <see cref="IDecimal"/> type.
/// </summary>
/// <value><see cref="DataType.Decimal"/></value>
public DataType DataType { get; } = DataType.Decimal;

/// <summary>
/// Gets or sets the <see cref="BsonType"/> used to represent the Primitively <see cref="IDecimal"/> type.
/// </summary>
/// <value><see cref="BsonType.Decimal128"/></value>
public BsonType Representation { get; set; } = BsonType.Decimal128;

/// <summary>
/// Gets or sets the type used to serialize Primitively <see cref="IDecimal"/> types.
/// </summary>
/// <value><![CDATA[typeof(BsonIDecimalSerializer<>)]]></value>
public Type SerializerType { get; set; } = typeof(BsonIDecimalSerializer<>);

/// <summary>
/// Gets or sets whether to allow overflow.
/// </summary>
public bool AllowOverflow { get; set; }

/// <summary>
/// Gets or sets whether to allow truncation.
/// </summary>
public bool AllowTruncation { get; set; }

/// <summary>
/// Gets or sets the function used to create an instance of the type used to serialize Primitively <see cref="IDecimal"/> types.
/// </summary>
public Func<BsonIDecimalSerializerOptions, Type, IBsonSerializer> CreateInstance { get; set; } = (options, primitiveType) =>
{
// Construct a Bson serializer for the given Primitively type using the options
var serializerType = options.GetSerializerType(primitiveType);
// Create an instance of the serializer
var serializerInstance = (IBsonSerializer)Activator.CreateInstance(
serializerType,
options.Representation,
new RepresentationConverter(options.AllowOverflow, options.AllowTruncation))!;
return serializerInstance;
};

Func<Type, IBsonSerializer> IBsonSerializerOptions.CreateInstance => (primitiveType) => CreateInstance.Invoke(this, primitiveType);
}
1 change: 1 addition & 0 deletions src/Primitively/EmbeddedResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public readonly struct Numeric
public readonly struct FloatingPoint
{
public static readonly string Base = GetEmbeddedResource(nameof(Numeric), nameof(FloatingPoint), nameof(Base));
public static readonly string DecimalPreMatchCheckMethod = GetEmbeddedResource(nameof(Numeric), nameof(FloatingPoint), nameof(DecimalPreMatchCheckMethod));
public static readonly string DoublePreMatchCheckMethod = GetEmbeddedResource(nameof(Numeric), nameof(FloatingPoint), nameof(DoublePreMatchCheckMethod));
public static readonly string SinglePreMatchCheckMethod = GetEmbeddedResource(nameof(Numeric), nameof(FloatingPoint), nameof(SinglePreMatchCheckMethod));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

private static void PreMatchCheck(ref global::PRIMITIVE_VALUE_TYPE value)
{
if (Digits >= 0)
{
value = global::System.Math.Round(value, Digits, Mode);
}
}
Loading

0 comments on commit f67e4f7

Please sign in to comment.