Init
This commit is contained in:
14
Prefab.Base/Catalog/Attributes/AttributeRules.cs
Normal file
14
Prefab.Base/Catalog/Attributes/AttributeRules.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Prefab.Base.Catalog.Attributes;
|
||||
|
||||
public static class AttributeDefinitionRules
|
||||
{
|
||||
public const int NameMaxLength = 256;
|
||||
public const int UnitMaxLength = 32;
|
||||
}
|
||||
|
||||
public static class ProductAttributeValueRules
|
||||
{
|
||||
public const int ValueMaxLength = 1024;
|
||||
public const int UnitCodeMaxLength = 32;
|
||||
public const int EnumCodeMaxLength = 64;
|
||||
}
|
||||
11
Prefab.Base/Catalog/Categories/CategoryRules.cs
Normal file
11
Prefab.Base/Catalog/Categories/CategoryRules.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Prefab.Base.Catalog.Categories;
|
||||
|
||||
public class CategoryRules
|
||||
{
|
||||
public const int NameMaxLength = 100;
|
||||
public const int DescriptionMaxLength = 500;
|
||||
|
||||
public const int SlugMaxLength = 128;
|
||||
public const int HeroImageUrlMaxLength = 512;
|
||||
public const int IconMaxLength = 128;
|
||||
}
|
||||
14
Prefab.Base/Catalog/Options/OptionRules.cs
Normal file
14
Prefab.Base/Catalog/Options/OptionRules.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Prefab.Base.Catalog.Options;
|
||||
|
||||
public static class OptionDefinitionRules
|
||||
{
|
||||
public const int CodeMaxLength = 64;
|
||||
public const int NameMaxLength = 256;
|
||||
public const int UnitMaxLength = 32;
|
||||
}
|
||||
|
||||
public static class OptionValueRules
|
||||
{
|
||||
public const int CodeMaxLength = 64;
|
||||
public const int LabelMaxLength = 256;
|
||||
}
|
||||
10
Prefab.Base/Catalog/Products/ProductRules.cs
Normal file
10
Prefab.Base/Catalog/Products/ProductRules.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Prefab.Base.Catalog.Products;
|
||||
|
||||
public static class ProductRules
|
||||
{
|
||||
public const int NameMaxLength = 256;
|
||||
public const int SlugMaxLength = 256;
|
||||
public const int SkuMaxLength = 64;
|
||||
public const int DescriptionMaxLength = 2048;
|
||||
public const string SlugPattern = "^[a-z0-9]+(?:-[a-z0-9]+)*$";
|
||||
}
|
||||
18
Prefab.Base/Enums.cs
Normal file
18
Prefab.Base/Enums.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the supported default cultures.
|
||||
/// </summary>
|
||||
public enum Culture
|
||||
{
|
||||
[Display(Order = 1, Name = "en-US")]
|
||||
UnitedStates,
|
||||
|
||||
[Display(Order = 2, Name = "en-GB")]
|
||||
UnitedKingdom,
|
||||
|
||||
[Display(Order = 2, Name = "ja-JP")]
|
||||
Japan
|
||||
}
|
||||
6
Prefab.Base/GenericAttributeChange.cs
Normal file
6
Prefab.Base/GenericAttributeChange.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a change in a custom attribute.
|
||||
/// </summary>
|
||||
public record GenericAttributeChange(string Key, object? OldValue, object? NewValue);
|
||||
88
Prefab.Base/GenericAttributes.cs
Normal file
88
Prefab.Base/GenericAttributes.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a customizable attribute bag with change tracking for Prefab domain objects.
|
||||
/// </summary>
|
||||
public class GenericAttributes : ICanBeCustomized
|
||||
{
|
||||
private readonly Dictionary<string, object> _attributes = new();
|
||||
private readonly List<GenericAttributeChange> _changes = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
IReadOnlyDictionary<string, object> IHasGenericAttributes.GenericAttributes => new ReadOnlyDictionary<string, object>(_attributes);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ordered list of attribute mutations recorded since the last call to <see cref="ClearChanges"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<GenericAttributeChange> Changes => _changes.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Provides an extension point for enforcing key/value requirements before persisting the attribute.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key being modified.</param>
|
||||
/// <param name="value">The proposed attribute value.</param>
|
||||
protected virtual void ValidateAttribute(string key, object value)
|
||||
{
|
||||
// Default: no validation. Override in derived classes for custom logic.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or replaces an attribute value and records the mutation.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key to set.</param>
|
||||
/// <param name="value">The attribute value.</param>
|
||||
public void SetAttribute(string key, object value)
|
||||
{
|
||||
_attributes.TryGetValue(key, out var oldValue);
|
||||
ValidateAttribute(key, value);
|
||||
_attributes[key] = value;
|
||||
_changes.Add(new GenericAttributeChange(key, oldValue, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an attribute if present and records the removal.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key to remove.</param>
|
||||
/// <returns><c>true</c> when the key existed and was removed; otherwise <c>false</c>.</returns>
|
||||
public bool RemoveAttribute(string key)
|
||||
{
|
||||
if (_attributes.TryGetValue(key, out var oldValue) && _attributes.Remove(key))
|
||||
{
|
||||
_changes.Add(new GenericAttributeChange(key, oldValue, null));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to read an attribute value and cast it to the requested type.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute key to read.</param>
|
||||
/// <param name="value">Receives the typed attribute value when successful.</param>
|
||||
/// <typeparam name="T">The expected attribute type.</typeparam>
|
||||
/// <returns><c>true</c> when the attribute exists and matches the requested type.</returns>
|
||||
public bool TryGetAttribute<T>(string key, out T value)
|
||||
{
|
||||
if (_attributes.TryGetValue(key, out var obj) && obj is T t)
|
||||
{
|
||||
value = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an immutable snapshot copy of the current attributes.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> Snapshot() => new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(_attributes));
|
||||
|
||||
/// <summary>
|
||||
/// Clears all recorded attribute changes while keeping the current attribute values intact.
|
||||
/// </summary>
|
||||
public void ClearChanges() => _changes.Clear();
|
||||
}
|
||||
30
Prefab.Base/ICanBeCustomized.cs
Normal file
30
Prefab.Base/ICanBeCustomized.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Contract for models that expose a customizable attribute bag to module developers.
|
||||
/// </summary>
|
||||
public interface ICanBeCustomized : IHasGenericAttributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets or updates a custom attribute value.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute name.</param>
|
||||
/// <param name="value">The value to store.</param>
|
||||
void SetAttribute(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a custom attribute by key.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute to remove.</param>
|
||||
/// <returns><c>true</c> when the key existed and was removed.</returns>
|
||||
bool RemoveAttribute(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to read an attribute value.
|
||||
/// </summary>
|
||||
/// <param name="key">The attribute to inspect.</param>
|
||||
/// <param name="value">Receives the typed attribute value when present.</param>
|
||||
/// <typeparam name="T">The expected attribute type.</typeparam>
|
||||
/// <returns><c>true</c> when the attribute exists and matches the requested type.</returns>
|
||||
bool TryGetAttribute<T>(string key, out T value);
|
||||
}
|
||||
9
Prefab.Base/IEvent.cs
Normal file
9
Prefab.Base/IEvent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Marker interface for events.
|
||||
/// </summary>
|
||||
public interface IEvent
|
||||
{
|
||||
|
||||
}
|
||||
12
Prefab.Base/IHasGenericAttributes.cs
Normal file
12
Prefab.Base/IHasGenericAttributes.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Read‑only view of the generic attributes bag.
|
||||
/// </summary>
|
||||
public interface IHasGenericAttributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the read-only dictionary of custom attributes.
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, object> GenericAttributes { get; }
|
||||
}
|
||||
12
Prefab.Base/ISortOrder.cs
Normal file
12
Prefab.Base/ISortOrder.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a contract for entities that expose an explicit sort position.
|
||||
/// </summary>
|
||||
public interface ISortOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the relative sort order for the entity.
|
||||
/// </summary>
|
||||
int SortOrder { get; set; }
|
||||
}
|
||||
53
Prefab.Base/LookupEntity.cs
Normal file
53
Prefab.Base/LookupEntity.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a lookup entity that can be used to represent a PrefabEnum in a database table.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type that will represent the entity's backed <see cref="PrefabEnum" /> type.</typeparam>
|
||||
public abstract class LookupEntity<TEnum> : ISortOrder where TEnum : PrefabEnum<TEnum>
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying integer value of the enum.
|
||||
/// </summary>
|
||||
public int Id { get; set; } // This will match the PrefabEnum's underlying int value
|
||||
|
||||
/// <summary>
|
||||
/// The name of the enum member (e.g., "Pending").
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The short name of the enum member (e.g., "Pend").
|
||||
/// </summary>
|
||||
public string? ShortName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional description of the enum member.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
protected LookupEntity() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to create from a PrefabEnum instance.
|
||||
/// </summary>
|
||||
protected LookupEntity(TEnum prefabEnum)
|
||||
{
|
||||
Id = prefabEnum.Value;
|
||||
Name = prefabEnum.Name;
|
||||
ShortName = null; // Set as needed
|
||||
Description = null; // Set as needed
|
||||
SortOrder = prefabEnum.Value; // Or set as needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all PrefabEnum instances for this type.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<TEnum> GetAllEnumInstances() => PrefabEnum<TEnum>.List;
|
||||
}
|
||||
13
Prefab.Base/Prefab.Base.csproj
Normal file
13
Prefab.Base/Prefab.Base.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="12.0.0" />
|
||||
<PackageReference Include="Scrutor" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
57
Prefab.Base/PrefabEnum.cs
Normal file
57
Prefab.Base/PrefabEnum.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Base type for rich enumerations that behave like enums but support additional metadata and behavior.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">Concrete enumeration type deriving from <see cref="PrefabEnum{TEnum}"/>.</typeparam>
|
||||
public abstract class PrefabEnum<TEnum> where TEnum : PrefabEnum<TEnum>
|
||||
{
|
||||
private static readonly List<TEnum> Instances = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the canonical name of the enumeration value.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the integral value associated with the enumeration value.
|
||||
/// </summary>
|
||||
public int Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new enumeration value and registers it in the global instance list.
|
||||
/// </summary>
|
||||
/// <param name="name">Logical name exposed to consumers.</param>
|
||||
/// <param name="value">Numeric identifier for persistence.</param>
|
||||
protected PrefabEnum(string name, int value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
Instances.Add(this as TEnum ?? throw new InvalidOperationException("PrefabEnum must be instantiated with a derived type."));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all declared enumeration values for <typeparamref name="TEnum"/>.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<TEnum> List => Instances;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an enumeration value by its logical name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to match.</param>
|
||||
/// <returns>The matching enumeration.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when no enumeration value matches the supplied name.</exception>
|
||||
public static TEnum FromName(string name) =>
|
||||
Instances.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
?? throw new ArgumentException($"Invalid name: {name}", nameof(name));
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an enumeration value by its integral value.
|
||||
/// </summary>
|
||||
/// <param name="value">The integer value to match.</param>
|
||||
/// <returns>The matching enumeration.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when no enumeration value matches the supplied value.</exception>
|
||||
public static TEnum FromValue(int value) =>
|
||||
Instances.FirstOrDefault(x => x.Value == value)
|
||||
?? throw new ArgumentException($"Invalid value: {value}", nameof(value));
|
||||
}
|
||||
22
Prefab.Base/PrefabEnumJsonConverter.cs
Normal file
22
Prefab.Base/PrefabEnumJsonConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Serializes <see cref="PrefabEnum{TEnum}"/> values as their logical names and deserializes by name or value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">Enumeration type to convert.</typeparam>
|
||||
public class PrefabEnumJsonConverter<TEnum> : JsonConverter<TEnum> where TEnum : PrefabEnum<TEnum>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.String => PrefabEnum<TEnum>.FromName(reader.GetString()!),
|
||||
JsonTokenType.Number => PrefabEnum<TEnum>.FromValue(reader.GetInt32()),
|
||||
_ => throw new JsonException($"Unsupported token type '{reader.TokenType}' for Prefab enum deserialization.")
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) => writer.WriteStringValue(value.Name);
|
||||
}
|
||||
65
Prefab.Base/PrefabEnumJsonConverters.cs
Normal file
65
Prefab.Base/PrefabEnumJsonConverters.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Prefab.Base;
|
||||
|
||||
/// <summary>
|
||||
/// Creates JSON converters that serialize Prefab enums using their logical names.
|
||||
/// </summary>
|
||||
public sealed class PrefabEnumJsonConverterFactory : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
if (!typeToConvert.IsClass)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var baseType = typeToConvert;
|
||||
while (baseType is not null)
|
||||
{
|
||||
if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(PrefabEnum<>))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var converterType = typeof(PrefabEnumJsonConverter<>).MakeGenericType(typeToConvert);
|
||||
return (JsonConverter)Activator.CreateInstance(converterType)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converter used for concrete Prefab enum types.
|
||||
/// </summary>
|
||||
private sealed class PrefabEnumJsonConverter<TEnum> : JsonConverter<TEnum> where TEnum : PrefabEnum<TEnum>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TEnum? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
var name = reader.GetString() ?? string.Empty;
|
||||
return PrefabEnum<TEnum>.FromName(name);
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out var value))
|
||||
{
|
||||
return PrefabEnum<TEnum>.FromValue(value);
|
||||
}
|
||||
|
||||
throw new JsonException($"Unexpected token {reader.TokenType} when parsing {typeof(TEnum).Name}.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) => writer.WriteStringValue(value.Name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user