An open API service indexing awesome lists of open source software.

https://github.com/erri120/transparentvalueobjects

Source generator for Value Objects.
https://github.com/erri120/transparentvalueobjects

source-generator

Last synced: 6 months ago
JSON representation

Source generator for Value Objects.

Awesome Lists containing this project

README

          

# TransparentValueObjects

Source generator and analyzer to create Value Objects.

## Example

```csharp
using System;
using TransparentValueObjects;

[ValueObject]
public readonly partial struct MyId { }
```

The attribute `ValueObject` will generate a partial implementation of this readonly struct with the following defaults:

- A `public readonly TInnerValue Value` field.
- A `public static T From(TInnerValue innerValue)` method that uses the private constructor.
- A private constructor used by the `From` method.
- A public constructor marked as `Obsolete` with `error: true` that will throw an exception if called (this behavior can be overwritten using [Augments](#augments)).
- `GetHashCode` and `ToString` implementations that call the same methods on the inner value.
- Equality methods: `object.Equals`, `IEquatable` and `IEquatable`.
- `==` and `!=` operators.
- Additional `Equals` method with `IEqualityComparer` parameter.
- Explicit cast operators.
- `IComparable` and `IComparable` if `TInnerValue` implements `IComparable`.
- `<`, `<=`, `>` and `>=` operators.
- For `Guid` only: `NewId` method that calls `From(Guid.NewGuid())`.

You can check the [test files](./tests/TransparentValueObjects.Tests/SourceOutput) to view the generated output.

## Augments

The biggest selling point of this project is the augment feature. You can use the `IAugmentWith` interface to "augment" your Value Object with additional functionality:

```csharp
using System;
using TransparentValueObjects;

[ValueObject]
public readonly partial struct SampleGuidValueObject : IAugmentWith<
DefaultValueAugment,
JsonAugment,
EfCoreAugment>
{
///
public static SampleGuidValueObject DefaultValue => From(Guid.Empty);
}
```

The following augments are currently available:

- [`DefaultValueAugment`](#default-value)
- [`DefaultEqualityComparerAugment`](#default-equality-comparer)
- [`JsonAugment`](#json-augment)
- [`EfCoreAugment`](#ef-core-augment)

### Default Value

Augments the Value Object with the `IDefaultValue` interface that has a static member `DefaultValue`:

```csharp
public static abstract TValueObject DefaultValue { get; }
```

```csharp
using System;
using TransparentValueObjects;

[ValueObject]
public readonly partial struct SampleGuidValueObject : IAugmentWith
{
///
public static SampleGuidValueObject DefaultValue => From(Guid.Empty);
}
```

You have to implement the `DefaultValue` member when using this augment. The public constructor will now also be available:

```csharp
public SampleGuidValueObject()
{
Value = DefaultValue.Value;
}
```

### Default Equality Comparer

Augments the Value Object with the `IDefaultEqualityComparer` interface that has a static member `InnerValueDefaultEqualityComparer`:

```csharp
public static abstract IEqualityComparer InnerValueDefaultEqualityComparer { get; }
```

```csharp
[ValueObject]
public readonly partial struct SampleStringValueObject : IAugmentWith
{
///
public static IEqualityComparer InnerValueDefaultEqualityComparer => StringComparer.OrdinalIgnoreCase;
}
```

This default equality comparer will be used by the following methods:

```csharp
public bool Equals(SampleStringValueObject other) => Equals(other.Value);

public bool Equals(string? other) => InnerValueDefaultEqualityComparer.Equals(Value, other);

public override int GetHashCode() => InnerValueDefaultEqualityComparer.GetHashCode(Value);
```

This augment is especially great for strings if you want to always use the case-insensitive equality comparer.

### Json Augment

Augments the Value Object with a JSON converter:

```csharp
[ValueObject]
public readonly partial struct SampleStringValueObject : IAugmentWith { }
```

**Only** `System.Text.Json` is currently supported! See https://github.com/erri120/TransparentValueObjects/issues/11 for `Newtonsoft.Json` support.

```csharp
[JsonConverter(typeof(JsonConverter))]
readonly partial struct SampleStringValueObject
{
public class JsonConverter : JsonConverter { /* omitted */ }
}
```

The `JsonConverterAttribute` will be added to the Value Object, meaning that you can use the added converter without needing to manually add it to the JSON options.

The source generator will create a custom converter for the following types:

- `string`
- `Guid`
- `Int16`
- `Int32`
- `Int64`
- `UInt16`
- `UInt32`
- `UInt64`

All other types will use a fallback converter that fetches an existing converter for `TInnerValue`.

**Note:** If the inner value type is a reference type, like `string`, then you will **need** to also augment the Value Object with the `DefaultValueAugment`.

### EF Core Augment

Augments the Value Object with a `ValueConverter` and `ValueComparer`:

```csharp
[ValueObject]
public readonly partial struct SampleStringValueObject : IAugmentWith { }
```

```csharp
public class EfCoreValueConverter : ValueConverter
{
public EfCoreValueConverter() : this(mappingHints: null) { }

public EfCoreValueConverter(ConverterMappingHints? mappingHints = null) : base(
static value => value.Value,
static innerValue => From(innerValue),
mappingHints
) { }
}

public class EfCoreValueComparer : ValueComparer
{
public EfCoreValueComparer() : base(
static (left, right) => left.Equals(right),
static value => value.GetHashCode(),
static value => From(value.Value)
) { }

///
public override bool Equals(SampleStringValueObject left, SampleStringValueObject right) => left.Equals(right);

///
public override SampleStringValueObject Snapshot(SampleStringValueObject instance) => From(instance.Value);

///
public override int GetHashCode(SampleStringValueObject instance) => instance.GetHashCode();
}
```

## License

See [LICENSE](./LICENSE) for details.