Coder Social home page Coder Social logo

altasoft / domainprimitives Goto Github PK

View Code? Open in Web Editor NEW
51.0 7.0 0.0 3.09 MB

C# Domain Primitives generator

License: MIT License

Batchfile 0.04% C# 99.96%
ddd domain entity value-object dotnet-core strongly-typed dotnet primitive-types source-generators

domainprimitives's Introduction

DomainPrimitives for C#

Version Dot NET 7+

Table of Contents

Introduction

Welcome to AltaSoft.DomainPrimitives - a C# toolkit purposefully designed to accelerate the development of domain-specific primitives within your applications. This streamlined solution empowers developers to efficiently encapsulate fundamental domain logic. Through this toolkit, you'll significantly reduce code complexity while improving the maintainability of your project.

Key Features

  • Simplified Primitive Creation - Utilize source generators to swiftly create domain-specific primitives with ease and precision.
  • Versatile Underlying Type Support - Embrace a wide array of underlying types, catering to diverse application requirements.
  • Enhanced Code Quality - Create clean, maintainable, and thoroughly testable code through encapsulation and robust design principles.

With AltaSoft.DomainPrimitives, experience an accelerated development process while upholding code quality standards. This toolkit empowers developers to focus on the core business logic without compromising on precision or efficiency.

Generator Features

The AltaSoft.DomainPrimitives.Generator offers a diverse set of features:

  • Implicit Operators: Streamlines type conversion to/from the underlying primitive type. Example
  • Specialized Constructor Generation: Automatically validates and constructs instances of this domain type. This constructor, tailored for the domain primitive, utilizes the underlying type as a parameter, ensuring the value's correctness within the domain.
  • TryCreate method: Introduces a TryCreate method that attempts to create an instance of the domain type and returns a bool indicating the success or failure of the creation process, along with any validation errors.
  • JsonConverters: Handles JSON serialization and deserialization for the underlying type. Example
  • TypeConverters: Assists in type conversion to/from it's underlying type. Please refer to generated type converter below
  • Swagger Custom Type Mappings: Facilitates easy integration with Swagger by treating the primitive type as it's underlying type. Please refer to generated swagger helper below
  • Interface Implementations: All DomainPritmitives Implement IConvertible, IComparable, IComparable<T>, IEquatable<T>, IEqualityComparer<T>, IParsable interfaces.
  • NumberType Operations: Automatically generates basic arithmetic and comparison operators, by implementing Static abstract interfaces. More details regarding numeric types
  • IParsable Implementation: Automatically generates parsing for non-string types.
  • XML Serialiaziton Generates IXmlSerializable interface implementation, to serialize and deserialize from/to xml.
  • EntityFrameworkCore ValueConverters Facilitates seamless integration with EntityFrameworkCore by using ValueConverters to treat the primitive type as its underlying type. For more details, refer to EntityFrameworkCore ValueConverters

Supported Underlying types

  1. string
  2. Guid
  3. byte
  4. sbyte
  5. short
  6. ushort
  7. int
  8. uint
  9. long
  10. ulong
  11. decimal
  12. double
  13. float
  14. bool
  15. char
  16. TimeSpan
  17. DateTime
  18. DateTimeOffset
  19. DateOnly
  20. TimeOnly

Getting Started

Prerequisites

  • .NET 7 or higher
  • NuGet Package Manager

Installation

To use AltaSoft.DomainPrimitives, install two NuGet packages:

  1. AltaSoft.DomainPrimitives
  2. AltaSoft.DomainPrimitives.Generator

In your project file add references as follows:

<ItemGroup>
  <PackageReference Include="AltaSoft.DomainPrimitives" Version="x.x.x" />
  <PackageReference Include="AltaSoft.DomainPrimitives.Generator" Version="x.x.x" PrivateAssets="all" />
</ItemGroup>

Creating your Domain type

For optimal performance, it is recommended to use readonly struct, especially when wrapping value types. If the type is a reference type, consider using class over struct.

public readonly partial struct PositiveInteger : IDomainValue<int>
{
    /// <inheritdoc/>
    public static PrimitiveValidationResult Validate(int value)
    {
        if (value <= 0)
            return PrimitiveValidationResult.Error("value is non-positive");

        return PrimitiveValidationResult.Ok;
    }
}

This will automatically generate by default 4 classes

PositiveInteger.Generated

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by 'AltaSoft DomainPrimitives Generator'.
//     Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable enable

using System;
using System.Numerics;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using AltaSoft.DomainPrimitives;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using AltaSoft.DomainPrimitives.XmlDataTypes.Converters;
using System.ComponentModel;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace AltaSoft.DomainPrimitives.XmlDataTypes;

[JsonConverter(typeof(PositiveIntegerJsonConverter))]
[TypeConverter(typeof(PositiveIntegerTypeConverter))]
[UnderlyingPrimitiveType(typeof(int))]
[DebuggerDisplay("{_value}")]
public readonly partial struct PositiveInteger : IEquatable<PositiveInteger>
        , IComparable
        , IComparable<PositiveInteger>
        , IAdditionOperators<PositiveInteger, PositiveInteger, PositiveInteger>
        , ISubtractionOperators<PositiveInteger, PositiveInteger, PositiveInteger>
        , IMultiplyOperators<PositiveInteger, PositiveInteger, PositiveInteger>
        , IDivisionOperators<PositiveInteger, PositiveInteger, PositiveInteger>
        , IModulusOperators<PositiveInteger, PositiveInteger, PositiveInteger>
        , IComparisonOperators<PositiveInteger, PositiveInteger, bool>
        , IParsable<PositiveInteger>
        , IConvertible
        , IXmlSerializable
#if NET8_0_OR_GREATER
        , IUtf8SpanFormattable
#endif
{
    /// <inheritdoc/>
     public Type GetUnderlyingPrimitiveType() => typeof(int);
    /// <inheritdoc/>
     public object GetUnderlyingPrimitiveValue() => (int)this;

    private int _valueOrThrow => _isInitialized ? _value : throw new InvalidDomainValueException("The domain value has not been initialized", this);
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly int _value;
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly bool _isInitialized;

    /// <summary>
    /// Initializes a new instance of the <see cref="PositiveInteger"/> class by validating the specified <see cref="int"/> value using <see cref="Validate"/> static method.
    /// </summary>
    /// <param name="value">The value to be validated.</param>
    public PositiveInteger(int value) : this(value, true)
    {
    }

    private PositiveInteger(int value, bool validate) 
    {
        if (validate)
        {
            ValidateOrThrow(value);
        }
        _value = value;
        _isInitialized = true;
    }

    /// <inheritdoc/>
    [Obsolete("Domain primitive cannot be created using empty Constructor", true)]
    public PositiveInteger()
    {
    }

    /// <summary>
    /// Tries to create an instance of AsciiString from the specified value.
    /// </summary>
    /// <param name="value">The value to create PositiveInteger from</param>
    /// <param name="result">When this method returns, contains the created PositiveInteger if the conversion succeeded, or null if the conversion failed.</param>
    /// <returns>true if the conversion succeeded; otherwise, false.</returns>
    public static bool TryCreate(int value, [NotNullWhen(true)] out PositiveInteger? result)
    {
        return TryCreate(value, out result, out _);
    }

    /// <summary>
    /// Tries to create an instance of AsciiString from the specified value.
    /// </summary>
    /// <param name="value">The value to create PositiveInteger from</param>
    /// <param name="result">When this method returns, contains the created PositiveInteger if the conversion succeeded, or null if the conversion failed.</param>
    /// <param name="errorMessage">When this method returns, contains the error message if the conversion failed; otherwise, null.</param>
    /// <returns>true if the conversion succeeded; otherwise, false.</returns>
    public static bool TryCreate(int value,[NotNullWhen(true)]  out PositiveInteger? result, [NotNullWhen(false)]  out string? errorMessage)
    {
        var validationResult = Validate(value);
        if (!validationResult.IsValid)
        {
            result = null;
            errorMessage = validationResult.ErrorMessage;
            return false;
        }

        result = new (value, false);
        errorMessage = null;
        return true;
    }

    /// <summary>
    ///  Validates the specified value and throws an exception if it is not valid.
    /// </summary>
    /// <param name="value">The value to validate</param>
    /// <exception cref="InvalidDomainValueException">Thrown when the value is not valid.</exception>
    public void ValidateOrThrow(int value)
    {
        var result = Validate(value);
        if (!result.IsValid)
        	throw new InvalidDomainValueException(result.ErrorMessage, this);
    }


    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override bool Equals(object? obj) => obj is PositiveInteger other && Equals(other);
    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Equals(PositiveInteger other)
    {
        if (!_isInitialized || !other._isInitialized)
            return false;
        return _value.Equals(other._value);
    }
    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator ==(PositiveInteger left, PositiveInteger right) => left.Equals(right);
    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator !=(PositiveInteger left, PositiveInteger right) => !(left == right);

    /// <inheritdoc/>
    public int CompareTo(object? obj)
    {
        if (obj is null)
            return 1;

        if (obj is PositiveInteger c)
            return CompareTo(c);

        throw new ArgumentException("Object is not a PositiveInteger", nameof(obj));
    }

    /// <inheritdoc/>
    public int CompareTo(PositiveInteger other)
    {
        if (!other._isInitialized)
            return 1;
        if (!_isInitialized)
            return -1;
        return _value.CompareTo(other._value);
    }

    /// <summary>
    /// Implicit conversion from <see cref = "int"/> to <see cref = "PositiveInteger"/>
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator PositiveInteger(int value) => new(value);

    /// <summary>
    /// Implicit conversion from <see cref = "int"/> (nullable) to <see cref = "PositiveInteger"/> (nullable)
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    [return: NotNullIfNotNull(nameof(value))]
    public static implicit operator PositiveInteger?(int? value) => value is null ? null : new(value.Value);

    /// <summary>
    /// Implicit conversion from <see cref = "PositiveInteger"/> to <see cref = "int"/>
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator int(PositiveInteger value) => (int)value._valueOrThrow;

    /// <summary>
    /// Implicit conversion from <see cref = "PositiveInteger"/> (nullable) to <see cref = "int"/> (nullable)
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    [return: NotNullIfNotNull(nameof(value))]
    public static implicit operator int?(PositiveInteger? value) => value is null ? null : (int?)value.Value._valueOrThrow;

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static PositiveInteger operator +(PositiveInteger left, PositiveInteger right) => new(left._valueOrThrow + right._valueOrThrow);

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static PositiveInteger operator -(PositiveInteger left, PositiveInteger right) => new(left._valueOrThrow - right._valueOrThrow);

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static PositiveInteger operator *(PositiveInteger left, PositiveInteger right) => new(left._valueOrThrow * right._valueOrThrow);

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static PositiveInteger operator /(PositiveInteger left, PositiveInteger right) => new(left._valueOrThrow / right._valueOrThrow);

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static PositiveInteger operator %(PositiveInteger left, PositiveInteger right) => new(left._valueOrThrow % right._valueOrThrow);

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator <(PositiveInteger left, PositiveInteger right) => left._valueOrThrow < right._valueOrThrow;

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator <=(PositiveInteger left, PositiveInteger right) => left._valueOrThrow <= right._valueOrThrow;

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator >(PositiveInteger left, PositiveInteger right) => left._valueOrThrow > right._valueOrThrow;

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator >=(PositiveInteger left, PositiveInteger right) => left._valueOrThrow >= right._valueOrThrow;


    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static PositiveInteger Parse(string s, IFormatProvider? provider) => int.Parse(s, provider);

    /// <inheritdoc/>
    public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out PositiveInteger result)
    {
        if (!int.TryParse(s, provider, out var value))
        {
            result = default;
            return false;
        }

        if (TryCreate(value, out var created))
        {
            result = created.Value;
            return true;
        }

        result = default;
        return false;
    }

#if NET8_0_OR_GREATER
    /// <inheritdoc cref="IUtf8SpanFormattable.TryFormat"/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
    {
        return ((IUtf8SpanFormattable)_valueOrThrow).TryFormat(utf8Destination, out bytesWritten, format, provider);
    }
#endif

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override int GetHashCode() => _valueOrThrow.GetHashCode();

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    TypeCode IConvertible.GetTypeCode() => ((IConvertible)(Int32)_valueOrThrow).GetTypeCode();

    /// <inheritdoc/>
    bool IConvertible.ToBoolean(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToBoolean(provider);

    /// <inheritdoc/>
    byte IConvertible.ToByte(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToByte(provider);

    /// <inheritdoc/>
    char IConvertible.ToChar(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToChar(provider);

    /// <inheritdoc/>
    DateTime IConvertible.ToDateTime(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToDateTime(provider);

    /// <inheritdoc/>
    decimal IConvertible.ToDecimal(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToDecimal(provider);

    /// <inheritdoc/>
    double IConvertible.ToDouble(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToDouble(provider);

    /// <inheritdoc/>
    short IConvertible.ToInt16(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToInt16(provider);

    /// <inheritdoc/>
    int IConvertible.ToInt32(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToInt32(provider);

    /// <inheritdoc/>
    long IConvertible.ToInt64(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToInt64(provider);

    /// <inheritdoc/>
    sbyte IConvertible.ToSByte(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToSByte(provider);

    /// <inheritdoc/>
    float IConvertible.ToSingle(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToSingle(provider);

    /// <inheritdoc/>
    string IConvertible.ToString(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToString(provider);

    /// <inheritdoc/>
    object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToType(conversionType, provider);

    /// <inheritdoc/>
    ushort IConvertible.ToUInt16(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToUInt16(provider);

    /// <inheritdoc/>
    uint IConvertible.ToUInt32(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToUInt32(provider);

    /// <inheritdoc/>
    ulong IConvertible.ToUInt64(IFormatProvider? provider) => ((IConvertible)(Int32)_valueOrThrow).ToUInt64(provider);

    /// <inheritdoc/>
    public XmlSchema? GetSchema() => null;

    /// <inheritdoc/>
    public void ReadXml(XmlReader reader)
    {
        var value = reader.ReadElementContentAs<int>();
        ValidateOrThrow(value);
        System.Runtime.CompilerServices.Unsafe.AsRef(in _value) = value;
        System.Runtime.CompilerServices.Unsafe.AsRef(in _isInitialized) = true;
    }

    /// <inheritdoc/>
    public void WriteXml(XmlWriter writer) => writer.WriteValue(((int)_valueOrThrow).ToXmlString());

    /// <inheritdoc/>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override string ToString() => _valueOrThrow.ToString();
}

JsonConverter

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by 'AltaSoft DomainPrimitives Generator'.
//     Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable enable

using AltaSoft.DomainPrimitives;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Globalization;
using System.Text.Json.Serialization.Metadata;


namespace AltaSoft.DomainPrimitives.Converters;

/// <summary>
/// JsonConverter for <see cref = "PositiveInteger"/>
/// </summary>
public sealed class PositiveIntegerJsonConverter : JsonConverter<PositiveInteger>
{
	/// <inheritdoc/>
	public override PositiveInteger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		try
		{
			return JsonInternalConverters.Int32Converter.Read(ref reader, typeToConvert, options);
		}
		catch (InvalidDomainValueException ex)
		{
			throw new JsonException(ex.Message);
		}
	}

	/// <inheritdoc/>
	public override void Write(Utf8JsonWriter writer, PositiveInteger value, JsonSerializerOptions options)
	{
		JsonInternalConverters.Int32Converter.Write(writer, (int)value, options);
	}

	/// <inheritdoc/>
	public override PositiveInteger ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
	{
		try
		{
			return JsonInternalConverters.Int32Converter.ReadAsPropertyName(ref reader, typeToConvert, options);
		}
		catch (InvalidDomainValueException ex)
		{
			throw new JsonException(ex.Message);
		}
	}

	/// <inheritdoc/>
	public override void WriteAsPropertyName(Utf8JsonWriter writer, PositiveInteger value, JsonSerializerOptions options)
	{
		JsonInternalConverters.Int32Converter.WriteAsPropertyName(writer, (int)value, options);
	}
}

Type Converter

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by 'AltaSoft DomainPrimitives Generator'.
//     Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable enable

using AltaSoft.DomainPrimitives;
using System;
using System.ComponentModel;
using System.Globalization;


namespace AltaSoft.DomainPrimitives.Converters;

/// <summary>
/// TypeConverter for <see cref = "PositiveInteger"/>
/// </summary>
public sealed class PositiveIntegerTypeConverter : Int32Converter
{
	/// <inheritdoc/>
	public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
	{
		var result = base.ConvertFrom(context, culture, value);
		if (result is null)
			return null;
		try
		{
			return new PositiveInteger((int)result);
		}
		catch (InvalidDomainValueException ex)
		{
			throw new FormatException("Cannot parse PositiveInteger", ex);
		}
	}
}

Swagger Mappers

A single file for all domainPrimitives containing all type mappings is generated.

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by 'AltaSoft DomainPrimitives Generator'.
//     Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

#nullable enable

using AltaSoft.DomainPrimitives;
using Microsoft.Extensions.DependencyInjection;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.OpenApi.Models;

namespace AltaSoft.DomainPrimitives.Converters.Extensions;

/// <summary>
/// Helper class providing methods to configure Swagger mappings for DomainPrimitive types of AltaSoft.DomainPrimitives
/// </summary>
public static class SwaggerTypeHelper
{
	/// <summary>
	/// Adds Swagger mappings for specific custom types to ensure proper OpenAPI documentation generation.
	/// </summary>
	/// <param name="options">The SwaggerGenOptions instance to which mappings are added.</param>
	/// <remarks>
	/// The method adds Swagger mappings for the following types:
	/// <see cref="PositiveInteger"/>
	/// </remarks>
	public static void AddSwaggerMappings(this SwaggerGenOptions options)
	{
		options.MapType<PositiveInteger>(() => new OpenApiSchema
		{
			Type = "integer",
			Format = "int32",
			Title = "PositiveInteger",
			Description = @"A domain primitive type representing a positive integer."
		});
		options.MapType<PositiveInteger?>(() => new OpenApiSchema
		{
			Type = "integer",
			Format = "int32",
			Nullable = true,
			Title = "Nullable<PositiveInteger>",
			Description = @"A domain primitive type representing a positive integer."
		});
	}

Specialized ToString method

By Default IDomainValue uses its underlying type's ToString method however this can be overriden by implementing a method specified below

static virtual string ToString(T value) => value.ToString() ?? string.Empty;

Managing Generated Operators for numeric types

Mathematical operators for particular numeric types can be customized using the SupportedOperationsAttribute. If left unspecified, all operators are generated by default (as shown below). Once this attribute is applied, manual specification of the operators becomes mandatory. Note that for byte, sbyte, short, and ushort types, mathematical operators will not be generated by default.

Default numeric types Generated Operators

  1. byte, sbyte => None
  2. short, ushort => None
  3. int, uint => + - / * %
  4. long, ulong => + - / * %
  5. double => + - / * %
  6. decimal => + - / * %

using SupportedOperationsAttribute

[SupportedOperations(Addition = false, Division = false, Modulus = false, Multiplication = true, Subtraction = true)]
public readonly partial struct PositiveInteger : IDomainValue<int>
{
	 /// <inheritdoc/>
    public static PrimitiveValidationResult Validate(int value)
    {
        if (value <= 0)
            return PrimitiveValidationResult.Error("value is non-positive");

        return PrimitiveValidationResult.Ok;
    }
}

For further customization of the operators, consider implementing specific interfaces. This action will override the generated operators for the respective domain type:

public readonly partial struct PositiveInteger :
	IDomainValue<int>,
	IAdditionOperators<PositiveInteger, PositiveInteger, PositiveInteger>
{
	 /// <inheritdoc/>
    public static PrimitiveValidationResult Validate(int value)
    {
        if (value <= 0)
            return PrimitiveValidationResult.Error("value is non-positive");

        return PrimitiveValidationResult.Ok;
    }
	
	// custom + operator
	public static PositiveInteger operator +(PositiveInteger left, PositiveInteger right)
	{
		return (left._value + right._value + 1);
	}
}

Managing Serialization Format for date-related types

Certain date-related types like DateTime, DateOnly, TimeOnly, DateTimeOffset, and TimeSpan can modify their serialization/deserialization format using the SerializationFormatAttribute. For instance, consider the GDay type, which represents an XML gDay value. It implements the IDomainValue<DateOnly> interface and utilizes the SerializationFormatAttribute to specify a serialization format.

/// <summary>
/// Represents an XML GDay value object, providing operations for parsing and handling gDay values.
/// </summary>
[SerializationFormat("dd")]
public readonly partial struct GDay : IDomainValue<DateOnly>
{
	 /// <inheritdoc/>
    public static PrimitiveValidationResult Validate(int value)
    {
        return PrimitiveValidationResult.Ok;
    }

	/// <inheritdoc/>
	public static DateOnly Default => default;

	// Customized string representation of DateOnly
	/// <inheritdoc/>
	public static string ToString(DateOnly value) => value.ToString("dd");
}

Disable Generation of Converters

To disable the generation of Converters, Swagger Mappers or XML serialization, in .csproj file follow the below described steps.

  <PropertyGroup>
    <DomainPrimitiveGenerator_GenerateJsonConverters>false</DomainPrimitiveGenerator_GenerateJsonConverters>
    <DomainPrimitiveGenerator_GenerateTypeConverters>false</DomainPrimitiveGenerator_GenerateTypeConverters>
    <DomainPrimitiveGenerator_GenerateSwaggerConverters>false</DomainPrimitiveGenerator_GenerateSwaggerConverters>
    <DomainPrimitiveGenerator_GenerateXmlSerialization>false</DomainPrimitiveGenerator_GenerateXmlSerialization> 
  </PropertyGroup>

โš ๏ธ Please note that DomainPrimitiveGenerator_GenerateXmlSerialization value by default is false.

Additional Features

  1. PrimitiveValidationResult: Offers an Ok result and a string containing the error message. It also includes an implicit operator to automatically convert a string to an error value. PrimitiveValidationResult

  2. AltaSoft.DomainPrimitives.StringLengthAttribute can be used to specify minimum and maximum length restrictions on DomainPrimitives

    [StringLength(minimumLength:1, maximumLength:100, validate:false)]
    public partial class AsciiString : IDomainValue<string>
    {
    	/// <inheritdoc/>
    	public static PrimitiveValidationResult Validate(string value)
    	{
    		var input = value.AsSpan();
    
    		// ReSharper disable once ForCanBeConvertedToForeach
    		for (var i = 0; i < input.Length; i++)
    		{
    			if (!char.IsAscii(input[i]))
    				return "value contains non-ascii characters";
    		}
    
    		return PrimitiveValidationResult.Ok;
    	}
    }

    The Validate property can be used to automatically enforce boolean and string length validations within domain primitives.
    Additionally, this attribute can be utilized by ORMs to impose string length restrictions in the database.

  3. Chaining Primitive Types

    • Chaining of primitive types is possible. For instance, considering the PositiveInteger and BetweenOneAnd100 DomainPrimitives:
    public readonly partial struct PositiveInteger : IDomainValue<int>
    {
    /// <inheritdoc/>
    public static PrimitiveValidationResult Validate(int value)
    {
        if (value <= 0)
            return PrimitiveValidationResult.Error("value is non-positive");
    
        return PrimitiveValidationResult.Ok;
    }
    }
    
    public readonly partial struct BetweenOneAnd100 : IDomainValue<PositiveInteger>
    {
    	public static PrimitiveValidationResult Validate(PositiveInteger value)
    	{
    		if (value < 100)
    			return "Value must be less than 100"; //implicit operator to convert string to PrimitiveValidationResult
    
    		return PrimitiveValidationResult.Ok;		
    	}
    }
  4. Defined type BetweenOneAnd100 automatically inherits restrictions from PositiveInteger. Operators restricted in PositiveInteger are also inherited. Further restrictions on operators can be added using the SupportedOperationsAttribute:

    [SupportedOperations(Addition=false)]
    public readonly partial struct BetweenOneAnd100 : IDomainValue<PositiveInteger>
    {
    	public static PrimitiveValidationResult Validate(PositiveInteger value)
    	{
    		if (value < 100)
    			return "Value must be less than 100";
    
    		return PrimitiveValidationResult.Ok;		
    	}
    }

Restrictions

  1. Implementation of IDomainValue Interface

    • DomainPrimitives are mandated to implement the IDomainValue<T> interface to ensure adherence to domain-specific constraints and behaviors.
  2. Constructor Limitation

    • No constructors should be explicitly defined within DomainPrimitives. Doing so will result in a compiler error.
  3. Prohibition of Public Properties or Fields

    • DomainPrimitive types should not contain any explicitly defined public properties or fields. The backing field will be automatically generated.
      • If any property or field is explicitly named _value, _valueOrDefault, or _isInitialized, a compiler error will be triggered.

Examples

Implicit Usage of DomainType

public readonly partial struct PositiveAmount : IDomainValue<decimal>
{
	public static PrimitiveValidationResult Validate(decimal value)
	{
		if (value <= 0m)
			return "Must be a a positive number";

		return PrimitiveValidationResult.Ok;			
	}

}

public static class Example
{
	public static void ImplicitConversion()
	{
		var amount = new PositiveAmount(100m);
		PositiveAmount amount2 = 100m; // implicitly converted to PositiveAmount

		//implicilty casted to decimal
		decimal amountInDecimal = amount + amount2;        
	}
}

Json Conversion

[SupportedOperations] // no mathematical operators should be generated
public readonly partial struct CustomerId : IDomainValue<int>
{
	public static PrimitiveValidationResult Validate(decimal value)
	{
		if (value <= 0m)
			return "Must be a a positive number";

		return PrimitiveValidationResult.Ok;		
	}
}

public sealed class Transaction
{
	public CustomerId FromId { get; set; }
	public CustomerId? ToId { get; set; }
	public PositiveAmount Amount { get; set; }
	public PositiveAmount? Fees { get; set; }
}

public static void JsonSerializationAndDeserialization()
{
	var amount = new Transaction()
        {
            Amount = 100.523m,
            Fees = null,
            FromId = 1,
            ToId = null
        };

    var jsonValue = JsonSerializer.Serialize(amount); //this will produce the same result as changing customerId to int and PositiveAmount to decimal
    var newValue = JsonSerializer.Deserialize<Transaction>(jsonValue)
}

Serialized Json

{
    "FromId": 1,
    "ToId": null,
    "Amount": 100.523,
    "Fees": null
}

Contributions

Contributions to AltaSoft.DomainPrimitives are welcome! Whether you have suggestions or wish to contribute code, feel free to submit a pull request or open an issue.

Contact

For support, questions, or additional information, please visit GitHub Issues.

License

This project is licensed under MIT. See the LICENSE file for details.

domainprimitives's People

Contributors

gregorynikolaishvili avatar temonk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

domainprimitives's Issues

Keeps generating swagger file, even if putting false in csproj

I have tried to add

	<PropertyGroup>
		<DomainPrimitiveGenerator_GenerateJsonConverters>false</DomainPrimitiveGenerator_GenerateJsonConverters>
		<DomainPrimitiveGenerator_GenerateTypeConverters>false</DomainPrimitiveGenerator_GenerateTypeConverters>
		<DomainPrimitiveGenerator_GenerateSwaggerConverters>false</DomainPrimitiveGenerator_GenerateSwaggerConverters>
	</PropertyGroup>

to the generator in order to not generate data for Swagger. However this version

<ItemGroup>
	<PackageReference Include="AltaSoft.DomainPrimitives.Abstractions" Version="1.0.3" />
	<PackageReference Include="AltaSoft.DomainPrimitives.Generator" Version="1.0.3" OutputItemType="Analyzer" ReferenceOutputAssembly="false" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>
<PropertyGroup>
	<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
	<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
	```
keeps generating files for swagger. Attached the project  - please see folder obj/GX
[DomainPrimitives.zip](https://github.com/altasoft/DomainPrimitives/files/13897845/DomainPrimitives.zip)


( I have tested because I wanted to add to my list of RSCG, https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG ) 

Please add support for XML Serialization

Feature Request

Description:
I propose adding XML serialization support to the "altasoft/DomainPrimitives" project. This enhancement is particularly crucial for ensuring compatibility with ISO20022 standards, which rely heavily on XML for financial messaging formats. The ability to serialize and deserialize domain primitives in XML format will significantly aid in the development of systems adhering to these standards.

Importance of XML for ISO20022:

  • Compliance with Financial Standards: ISO20022 sets a global standard for financial messaging, where XML is a primary format.
  • Global Banking and Payment Systems: Many international banking and payment systems are based on ISO20022, making XML support essential.

Benefits:

  • Interoperability with Financial Systems: Ensures seamless integration with global financial systems that use ISO20022.
  • Flexibility and Legacy Support: Offers diverse serialization options, crucial for both modern and legacy financial systems.

Suggested Implementation:
The implementation should ensure reliable XML serialization and deserialization of domain primitives, ensuring alignment with ISO20022 specifications.

Your input and contributions to this feature would be greatly appreciated!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.