diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/AbsolutePathAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/AbsolutePathAttribute.cs new file mode 100644 index 00000000..16d42d07 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/AbsolutePathAttribute.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; +using NexusMods.Paths; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an . +/// +[PublicAPI] +public sealed class AbsolutePathAttribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override string ToLowLevel(AbsolutePath value) => value.ToString(); + + /// + protected override AbsolutePath FromLowLevel(string value, AttributeResolver resolver) + { + return resolver.ServiceProvider.GetRequiredService().FromUnsanitizedFullPath(value); + } +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs index c33a5627..6cc15e09 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BackReferenceAttribute.cs @@ -1,4 +1,5 @@ -using NexusMods.MnemonicDB.Abstractions.Models; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.Models; // ReSharper disable UnusedTypeParameter #pragma warning disable CS9113 // Parameter is unread. @@ -8,7 +9,5 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// A meta attribute the expresses a backwards reference some other /// model has /// -public class BackReferenceAttribute(ReferenceAttribute referenceAttribute) -where TOtherModel : IModelDefinition -{ -} +[PublicAPI] +public sealed class BackReferenceAttribute(ReferenceAttribute referenceAttribute) where TOtherModel : IModelDefinition { } diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/BooleanAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BooleanAttribute.cs new file mode 100644 index 00000000..5431b9af --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/BooleanAttribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds a boolean value. +/// +[PublicAPI] +public sealed class BooleanAttribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override byte ToLowLevel(bool value) => value ? (byte)1 : (byte)0; + + /// + protected override bool FromLowLevel(byte value, AttributeResolver resolver) => value == 1; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs index c5549a82..69a2e2c3 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CardinalityAttribute.cs @@ -1,4 +1,4 @@ -using NexusMods.MnemonicDB.Abstractions.ElementComparers; +using JetBrains.Annotations; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -6,6 +6,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// Used to mark the cardinality of an attribute in the database /// +[PublicAPI] public sealed class CardinalityAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs index ca8d0500..fca17541 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/CollectionAttribute.cs @@ -1,4 +1,5 @@ -using NexusMods.MnemonicDB.Abstractions.IndexSegments; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.IndexSegments; using NexusMods.MnemonicDB.Abstractions.Models; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -6,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that represents a collection of values /// +[PublicAPI] public abstract class CollectionAttribute(string ns, string name) : Attribute(ns, name, cardinality: Cardinality.Many) where TSerializer : IValueSerializer diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/EnumAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/EnumAttribute.cs new file mode 100644 index 00000000..9d267d4c --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/EnumAttribute.cs @@ -0,0 +1,53 @@ +using System; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an enum value backed by an int32 value. +/// +[PublicAPI] +public sealed class EnumAttribute(string ns, string name) : ScalarAttribute(ns, name) + where T : Enum +{ + /// + protected override int ToLowLevel(T value) + { + // NOTE(halgari): Looks like an allocation, but the cast to object is removed by the JIT since the type of + // T is a compile-time constant. Verified via sharpLab.io: + // https://sharplab.io/#v2:EYLgtghglgdgNAFxBAzmAPgAQEwEYCwAUJgAwAEAygBYQBOADgDITAB0ASgK4wJRgCmAbiJEA2gCkoCAOL8Y/WlADGACgQBPevwD2AMxUBZdQFEYnMAEoLAXTKYAzHexkAwgB4AKgD4yAdyoK/GQeZCBkpuZkAN5EZHHxDmSwCGQGKiEAbhAANpz8FtGx8cWYAOxkKskWKtrAAFb8SggWWblCRXEAvkTdhESJcpFGEWDRZABi2tpkALxkJHBkAEJ0s2S4ZJ2CQA= + return (int)(object)value; + } + + /// + protected override T FromLowLevel(int value, AttributeResolver resolver) + { + // Same as ToLowLevel, the cast to object is removed by the JIT + return (T)(object)value; + } +} + +/// +/// An attribute that holds an enum value backed by a byte value. +/// +[PublicAPI] +public sealed class EnumByteAttribute(string ns, string name) : ScalarAttribute(ns, name) + where T : Enum +{ + /// + protected override byte ToLowLevel(T value) + { + // NOTE(halgari): Looks like an allocation, but the cast to object is removed by the JIT since the type of + // T is a compile-time constant. Verified via sharpLab.io: + // https://sharplab.io/#v2:EYLgtghglgdgNAFxBAzmAPgAQEwEYCwAUJgAwAEAygBYQBOADgDITAB0ASgK4wJRgCmAbiJEA2gCkoCAOL8Y/WlADGACgQBPevwD2AMxUBZdQFEYnMAEoLAXTKYAzHexkAwgB4AKgD4yAdyoK/GQeZCBkpuZkAN5EZHHxDmSwCGQGKiEAbhAANpz8FtGx8cWYAOxkKskWKtrAAFb8SggWWblCRXEAvkTdhESJcpFGEWDRZABi2tpkALxkJHBkAEJ0s2S4ZJ2CQA= + return (byte)(object)value; + } + + /// + protected override T FromLowLevel(byte value, AttributeResolver resolver) + { + // Same as ToLowLevel, the cast to object is removed by the JIT + return (T)(object)value; + } +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs new file mode 100644 index 00000000..ca5798e9 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float32Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds a float32 value. +/// +[PublicAPI] +public sealed class Float32Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override float ToLowLevel(float value) => value; + + /// + protected override float FromLowLevel(float value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float64Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float64Attribute.cs new file mode 100644 index 00000000..2c5b55ab --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Float64Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds a float64 value. +/// +[PublicAPI] +public sealed class Float64Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override double ToLowLevel(double value) => value; + + /// + protected override double FromLowLevel(double value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int128Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int128Attribute.cs new file mode 100644 index 00000000..d658ed1b --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int128Attribute.cs @@ -0,0 +1,18 @@ +using System; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an int128 value. +/// +[PublicAPI] +public sealed class Int128Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override Int128 ToLowLevel(Int128 value) => value; + + /// + protected override Int128 FromLowLevel(Int128 value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int16Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int16Attribute.cs new file mode 100644 index 00000000..83272038 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int16Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an int16 value. +/// +[PublicAPI] +public sealed class Int16Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override short ToLowLevel(short value) => value; + + /// + protected override short FromLowLevel(short value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int32Attribute.cs new file mode 100644 index 00000000..bc58c1af --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int32Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an int32 value. +/// +[PublicAPI] +public sealed class Int32Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override int ToLowLevel(int value) => value; + + /// + protected override int FromLowLevel(int value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int64Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int64Attribute.cs new file mode 100644 index 00000000..0eed6908 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/Int64Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an uint64 value. +/// +[PublicAPI] +public sealed class Int64Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override long ToLowLevel(long value) => value; + + /// + protected override long FromLowLevel(long value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs index 6122ef31..79d2be46 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/MarkerAttribute.cs @@ -1,5 +1,5 @@ -using NexusMods.MnemonicDB.Abstractions.ElementComparers; -using NexusMods.MnemonicDB.Abstractions.Models; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ElementComparers; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -8,29 +8,12 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// An attribute that doesn't have a value, but is used to dispatch logic or to mark an entity /// as being of a certain type. /// -/// -/// -public class MarkerAttribute(string ns, string name) : Attribute(ns, name) +[PublicAPI] +public sealed class MarkerAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// protected override Null ToLowLevel(Null value) => value; /// protected override Null FromLowLevel(Null value, AttributeResolver resolver) => value; - - /// - /// Returns true if the entity contains the attribute. - /// - public bool Contains(IHasIdAndIndexSegment entity) - { - var segment = entity.IndexSegment; - var dbId = entity.Db.Connection.AttributeCache.GetAttributeId(Id); - for (var i = 0; i < segment.Count; i++) - { - var datom = segment[i]; - if (datom.A != dbId) continue; - return true; - } - return false; - } } diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs index fc7c8a45..4d873db6 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferenceAttribute.cs @@ -1,4 +1,5 @@ -using NexusMods.MnemonicDB.Abstractions.Models; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.Models; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -6,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that references another entity. /// +[PublicAPI] public class ReferenceAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// @@ -18,5 +20,6 @@ public class ReferenceAttribute(string ns, string name) : ScalarAttribute /// A typesafe reference attribute, that references entities of type T. /// +[PublicAPI] public sealed class ReferenceAttribute(string ns, string name) : ReferenceAttribute(ns, name) where T : IModelDefinition; diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs index 29e16e28..af4a9504 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ReferencesAttribute.cs @@ -1,4 +1,4 @@ -using NexusMods.MnemonicDB.Abstractions.ElementComparers; +using JetBrains.Annotations; using NexusMods.MnemonicDB.Abstractions.Models; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; @@ -7,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// Represents a collection of references to other entities. /// +[PublicAPI] public class ReferencesAttribute(string ns, string name) : CollectionAttribute(ns, name) { /// @@ -19,5 +20,6 @@ public class ReferencesAttribute(string ns, string name) : CollectionAttribute /// A typesafe reference attribute, that references entities of type T. /// +[PublicAPI] public sealed class ReferencesAttribute(string ns, string name) : ReferencesAttribute(ns, name) where T : IModelDefinition; diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/RelativePathAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/RelativePathAttribute.cs new file mode 100644 index 00000000..f3f622ef --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/RelativePathAttribute.cs @@ -0,0 +1,22 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; +using NexusMods.Paths; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an . +/// +[PublicAPI] +public sealed class RelativePathAttribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override string ToLowLevel(RelativePath value) => value.ToString(); + + /// + protected override RelativePath FromLowLevel(string value, AttributeResolver resolver) + { + return new RelativePath(value); + } +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs index 00a7d263..28abfe98 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using DynamicData.Kernel; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; +using JetBrains.Annotations; using NexusMods.MnemonicDB.Abstractions.Models; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -8,6 +8,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that represents a scalar value, where there is a 1:1 ratio between the attribute and the value. /// +[PublicAPI] public abstract class ScalarAttribute(string ns, string name) : Attribute(ns, name) where TSerializer : IValueSerializer @@ -22,6 +23,14 @@ public bool IsOptional init => DeclaredOptional = value; } + /// + /// True whether the index segment contains this attribute. + /// + public bool Contains(T entity) where T : IHasIdAndIndexSegment + { + return entity.IndexSegment.Contains(this); + } + /// /// Gets the value of the attribute from the entity. /// diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/SizeAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SizeAttribute.cs new file mode 100644 index 00000000..0010725f --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SizeAttribute.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; +using NexusMods.Paths; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an . +/// +[PublicAPI] +public sealed class SizeAttribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override ulong ToLowLevel(Size value) => value.Value; + + /// + protected override Size FromLowLevel(ulong value, AttributeResolver resolver) => Size.From(value); +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs index e7481e9c..daab05ca 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringAttribute.cs @@ -1,10 +1,12 @@ -using NexusMods.MnemonicDB.Abstractions.ValueSerializers; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that represents a string. /// +[PublicAPI] public sealed class StringAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/StringsAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringsAttribute.cs similarity index 61% rename from tests/NexusMods.MnemonicDB.TestModel/Attributes/StringsAttribute.cs rename to src/NexusMods.MnemonicDB.Abstractions/Attributes/StringsAttribute.cs index 9dd586f3..1bd33b1d 100644 --- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/StringsAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/StringsAttribute.cs @@ -1,13 +1,17 @@ -using NexusMods.MnemonicDB.Abstractions; -using NexusMods.MnemonicDB.Abstractions.Attributes; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; +using JetBrains.Annotations; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; -namespace NexusMods.MnemonicDB.TestModel.Attributes; +namespace NexusMods.MnemonicDB.Abstractions.Attributes; +/// +/// An attribute that holds a collection of strings. +/// +[PublicAPI] public sealed class StringsAttribute(string ns, string name) : CollectionAttribute(ns, name) { + /// protected override string ToLowLevel(string value) => value; + /// protected override string FromLowLevel(string value, AttributeResolver resolver) => value; } diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs index 4025a438..7506bf7a 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/SymbolAttribute.cs @@ -1,11 +1,13 @@ -using NexusMods.MnemonicDB.Abstractions.ValueSerializers; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that encodes a symbol. /// -public class SymbolAttribute(string ns, string name) : ScalarAttribute(ns, name) +[PublicAPI] +public sealed class SymbolAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// protected override string ToLowLevel(Symbol value) => value.Id; diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs index 796b1ecd..48698209 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TimestampAttribute.cs @@ -1,6 +1,5 @@ using System; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; -using NexusMods.MnemonicDB.Abstractions.Models; +using JetBrains.Annotations; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -8,7 +7,8 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that holds a timestamp /// -public class TimestampAttribute(string ns, string name) : ScalarAttribute(ns, name) +[PublicAPI] +public sealed class TimestampAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// protected override long ToLowLevel(DateTimeOffset value) => value.ToUnixTimeMilliseconds(); diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/TxIdAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TxIdAttribute.cs new file mode 100644 index 00000000..2047a7c8 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/TxIdAttribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds a . +/// +[PublicAPI] +public sealed class TxIdAttribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override ulong ToLowLevel(TxId value) => value.Value; + + /// + protected override TxId FromLowLevel(ulong value, AttributeResolver resolver) => TxId.From(value); +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs new file mode 100644 index 00000000..510a76cb --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt128Attribute.cs @@ -0,0 +1,18 @@ +using System; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an uint128 value. +/// +[PublicAPI] +public sealed class UInt128Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override UInt128 ToLowLevel(UInt128 value) => value; + + /// + protected override UInt128 FromLowLevel(UInt128 value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt16Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt16Attribute.cs new file mode 100644 index 00000000..1859091b --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt16Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an uint16 value. +/// +[PublicAPI] +public sealed class UInt16Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override ushort ToLowLevel(ushort value) => value; + + /// + protected override ushort FromLowLevel(ushort value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt32Attribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt32Attribute.cs new file mode 100644 index 00000000..6e67682f --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt32Attribute.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds an uint32 value. +/// +[PublicAPI] +public sealed class UInt32Attribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override uint ToLowLevel(uint value) => value; + + /// + protected override uint FromLowLevel(uint value, AttributeResolver resolver) => value; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ULongAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt64Attribute.cs similarity index 63% rename from src/NexusMods.MnemonicDB.Abstractions/Attributes/ULongAttribute.cs rename to src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt64Attribute.cs index ef4cc88c..2ab1edca 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ULongAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UInt64Attribute.cs @@ -1,11 +1,13 @@ +using JetBrains.Annotations; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// -/// UInt64 attribute (ulong) +/// An attribute that holds an uint64 value. /// -public sealed class ULongAttribute(string ns, string name) : ScalarAttribute(ns, name) +[PublicAPI] +public sealed class UInt64Attribute(string ns, string name) : ScalarAttribute(ns, name) { /// protected override ulong ToLowLevel(ulong value) => value; diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/UriAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UriAttribute.cs new file mode 100644 index 00000000..88a30428 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/UriAttribute.cs @@ -0,0 +1,18 @@ +using System; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ValueSerializers; + +namespace NexusMods.MnemonicDB.Abstractions.Attributes; + +/// +/// An attribute that holds a . +/// +[PublicAPI] +public sealed class UriAttribute(string ns, string name) : ScalarAttribute(ns, name) +{ + /// + protected override string ToLowLevel(Uri value) => value.ToString(); + + /// + protected override Uri FromLowLevel(string value, AttributeResolver resolver) => new(value); +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs index ff70daac..0bb24c71 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Attributes/ValuesTagAttribute.cs @@ -1,4 +1,5 @@ -using NexusMods.MnemonicDB.Abstractions.ElementComparers; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.ElementComparers; using NexusMods.MnemonicDB.Abstractions.ValueSerializers; namespace NexusMods.MnemonicDB.Abstractions.Attributes; @@ -6,6 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Attributes; /// /// An attribute that represents a value tag value /// +[PublicAPI] public sealed class ValuesTagAttribute(string ns, string name) : ScalarAttribute(ns, name) { /// diff --git a/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs b/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs index da7e31d6..955f5b68 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/BuiltInEntities/Transaction.cs @@ -20,5 +20,5 @@ public partial class Transaction : IModelDefinition /// /// If this database is associated with a excision, this attribute will contain the number of datoms that were excised. /// - public static readonly ULongAttribute ExcisedDatoms = new(Namespace, "ExcisedDatoms") { IsOptional = true }; + public static readonly UInt64Attribute ExcisedDatoms = new(Namespace, "ExcisedDatoms") { IsOptional = true }; } diff --git a/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs b/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs new file mode 100644 index 00000000..d33207e2 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/EntityExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; +using JetBrains.Annotations; +using NexusMods.MnemonicDB.Abstractions.BuiltInEntities; +using NexusMods.MnemonicDB.Abstractions.Models; + +namespace NexusMods.MnemonicDB.Abstractions; + +/// +/// Extension methods for entities. +/// +[PublicAPI] +public static class EntityExtensions +{ + /// + /// Gets the timestamp of the transaction that created the model. + /// + public static DateTimeOffset GetCreatedAt(this T model, DateTimeOffset defaultValue = default) + where T : IReadOnlyModel + { + if (model.Count == 0) return defaultValue; + var minTx = model.Min(m => m.T); + + var tx = new Transaction.ReadOnly(model.Db, EntityId.From(minTx.Value)); + return Transaction.Timestamp.Get(tx); + } +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs b/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs index 895a37a9..4ab2d6c7 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/EntityId.cs @@ -1,4 +1,7 @@ -using TransparentValueObjects; +using System; +using System.Globalization; +using JetBrains.Annotations; +using TransparentValueObjects; namespace NexusMods.MnemonicDB.Abstractions; @@ -6,6 +9,7 @@ namespace NexusMods.MnemonicDB.Abstractions; /// A unique identifier for an entity. /// [ValueObject] +[PublicAPI] public readonly partial struct EntityId : IAugmentWith { /// @@ -38,4 +42,24 @@ public override string ToString() { return "EId:" + Value.ToString("X"); } + + /// + /// Tries to parse a hex string as an entity ID. + /// + public static bool TryParseFromHex(ReadOnlySpan input, out EntityId id) + { + const string prefix = "EId:"; + + if (input.StartsWith(prefix)) + input = input[prefix.Length..]; + + if (ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) + { + id = EntityId.From(result); + return true; + } + + id = default; + return false; + } } diff --git a/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs b/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs index c65ead72..d45cd35b 100644 --- a/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs +++ b/tests/NexusMods.MnemonicDB.TestModel/ArchiveFile.cs @@ -1,4 +1,5 @@ -using NexusMods.MnemonicDB.Abstractions.Models; +using NexusMods.MnemonicDB.Abstractions.Attributes; +using NexusMods.MnemonicDB.Abstractions.Models; using NexusMods.MnemonicDB.TestModel.Attributes; namespace NexusMods.MnemonicDB.TestModel; diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/AbsolutePathAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/AbsolutePathAttribute.cs deleted file mode 100644 index e0bb4e99..00000000 --- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/AbsolutePathAttribute.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using NexusMods.MnemonicDB.Abstractions; -using NexusMods.MnemonicDB.Abstractions.Attributes; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; -using NexusMods.MnemonicDB.Abstractions.ValueSerializers; -using NexusMods.Paths; - -namespace NexusMods.MnemonicDB.TestModel.Attributes; - -public sealed class AbsolutePathAttribute(string ns, string name) : ScalarAttribute(ns, name) -{ - protected override string ToLowLevel(AbsolutePath value) - => value.ToString(); - - protected override AbsolutePath FromLowLevel(string value, AttributeResolver resolver) - => resolver.ServiceProvider.GetRequiredService().FromUnsanitizedFullPath(value); -} diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/RelativePathAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/RelativePathAttribute.cs deleted file mode 100644 index c040c513..00000000 --- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/RelativePathAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NexusMods.MnemonicDB.Abstractions; -using NexusMods.MnemonicDB.Abstractions.Attributes; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; -using NexusMods.MnemonicDB.Abstractions.ValueSerializers; -using NexusMods.Paths; - -namespace NexusMods.MnemonicDB.TestModel.Attributes; - -public sealed class RelativePathAttribute(string ns, string name) : - ScalarAttribute(ns, name) -{ - protected override string ToLowLevel(RelativePath value) => value.Path; - - protected override RelativePath FromLowLevel(string value, AttributeResolver resolver) => new(value); -} diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/SizeAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/SizeAttribute.cs deleted file mode 100644 index e6edff0d..00000000 --- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/SizeAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using NexusMods.MnemonicDB.Abstractions; -using NexusMods.MnemonicDB.Abstractions.Attributes; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; -using NexusMods.MnemonicDB.Abstractions.ValueSerializers; -using NexusMods.Paths; - -namespace NexusMods.MnemonicDB.TestModel.Attributes; - -public sealed class SizeAttribute(string ns, string name) : ScalarAttribute(ns, name) { - protected override ulong ToLowLevel(Size value) => value.Value; - - protected override Size FromLowLevel(ulong value, AttributeResolver resolver) - => Size.From(value); -} diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/UriAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/UriAttribute.cs deleted file mode 100644 index 8052d8fa..00000000 --- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/UriAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NexusMods.MnemonicDB.Abstractions; -using NexusMods.MnemonicDB.Abstractions.Attributes; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; -using NexusMods.MnemonicDB.Abstractions.ValueSerializers; - -namespace NexusMods.MnemonicDB.TestModel.Attributes; - -public sealed class UriAttribute(string ns, string name) : ScalarAttribute(ns, name) -{ - protected override string ToLowLevel(Uri value) - => value.ToString(); - - protected override Uri FromLowLevel(string lowLevelValue, AttributeResolver resolver) - => new(lowLevelValue); -} diff --git a/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs b/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs index d67457f9..b9424a18 100644 --- a/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs +++ b/tests/NexusMods.MnemonicDB.Tests/MigrationTests.cs @@ -101,7 +101,7 @@ public async Task CanConvertValues() var aid = cache.GetAttributeId(Mod.Description.Id); cache.IsIndexed(aid).Should().BeFalse(); - var withIndex = new ULongAttribute(Mod.Description.Id.Namespace, Mod.Description.Id.Name) { IsIndexed = true, IsOptional = false }; + var withIndex = new UInt64Attribute(Mod.Description.Id.Namespace, Mod.Description.Id.Name) { IsIndexed = true, IsOptional = false }; var prevTxId = Connection.Db.BasisTxId; await Connection.UpdateSchema(withIndex); @@ -117,7 +117,7 @@ public async Task CanConvertValues() public async Task ConvertingValuesIncorrectlyFails() { await AddData(); - var withIndex = new ULongAttribute(Mod.Source.Id.Namespace, Mod.Source.Id.Name) { IsIndexed = true, IsOptional = false }; + var withIndex = new UInt64Attribute(Mod.Source.Id.Namespace, Mod.Source.Id.Name) { IsIndexed = true, IsOptional = false }; var act = async () => await Connection.UpdateSchema(withIndex); await act.Should().ThrowAsync("Converting values for attribute Mod.Source from String to ULong where the source is a URI should fail"); }