Rate Policy

This commit is contained in:
2026-07-13 17:15:00 +03:00
parent 31bda58460
commit 4f6d2d0801
46 changed files with 3142 additions and 468 deletions
+2 -3
View File
@@ -14,7 +14,6 @@ public class ConstantDef : MemberDef
{
public object Value { get; set; }
public Map<string, string> Annotations { get; set; }
public Tru ValueType { get; set; }
public FieldInfo FieldInfo { get; set; }
@@ -128,7 +127,7 @@ public class ConstantDef : MemberDef
return new ConstantDef()
return DefinitionAttributeReader.Apply(ci, new ConstantDef()
{
Name = customName,
Index = index,
@@ -137,7 +136,7 @@ public class ConstantDef : MemberDef
Value = value,
FieldInfo = ci,
Annotations = annotations,
};
});
}
@@ -0,0 +1,74 @@
using Esiur.Resource;
using System;
using System.Linq;
using System.Reflection;
namespace Esiur.Data.Types;
internal static class DefinitionAttributeReader
{
internal static void Apply(Type source, TypeDef target)
{
target.Usage = source.GetCustomAttribute<UsageAttribute>(true)?.Value;
target.Description = source.GetCustomAttribute<DescriptionAttribute>(true)?.Value;
target.Example = source.GetCustomAttributes<ExampleAttribute>(true)
.Select(x => x.Value)
.FirstOrDefault();
target.Category = source.GetCustomAttribute<CategoryAttribute>(true)?.Value;
target.Since = source.GetCustomAttribute<SinceAttribute>(true)?.Value;
}
internal static T Apply<T>(MemberInfo source, T target) where T : MemberDef
{
target.Description = source.GetCustomAttribute<DescriptionAttribute>(true)?.Value;
target.Usage = source.GetCustomAttribute<UsageAttribute>(true)?.Value;
var examples = source.GetCustomAttributes<ExampleAttribute>(true)
.Select(x => x.Value)
.ToList();
target.Examples = examples.Count == 0 ? null : examples;
var tags = source.GetCustomAttribute<TagsAttribute>(true)?.Values;
target.Tags = tags == null || tags.Length == 0 ? null : tags.ToList();
target.Unit = source.GetCustomAttribute<UnitAttribute>(true)?.Value;
target.Minimum = source.GetCustomAttribute<MinimumAttribute>(true)?.Value;
target.Maximum = source.GetCustomAttribute<MaximumAttribute>(true)?.Value;
var allowedValues = source.GetCustomAttributes<AllowedValueAttribute>(true)
.Select(x => x.Value)
.ToList();
target.AllowedValues = allowedValues.Count == 0 ? null : allowedValues;
target.Pattern = source.GetCustomAttribute<PatternAttribute>(true)?.Value;
target.Format = source.GetCustomAttribute<FormatAttribute>(true)?.Value;
var preconditions = source.GetCustomAttributes<PreconditionAttribute>(true)
.Select(x => x.Value)
.ToList();
target.Preconditions = preconditions.Count == 0 ? null : preconditions;
var postconditions = source.GetCustomAttributes<PostconditionAttribute>(true)
.Select(x => x.Value)
.ToList();
target.Postconditions = postconditions.Count == 0 ? null : postconditions;
target.Effects = source.GetCustomAttribute<EffectsAttribute>(true)?.Value
?? OperationEffects.None;
var warnings = source.GetCustomAttributes<WarningAttribute>(true)
.Select(x => x.Value)
.ToList();
target.Warnings = warnings.Count == 0 ? null : warnings;
var relatedMembers = source.GetCustomAttribute<RelatedMembersAttribute>(true)?.Indexes;
target.RelatedMembers = relatedMembers == null || relatedMembers.Length == 0
? null
: relatedMembers.ToList();
var obsolete = source.GetCustomAttribute<ObsoleteAttribute>(true);
target.Deprecated = obsolete != null;
target.DeprecationMessage = obsolete?.Message;
return target;
}
}
+11 -11
View File
@@ -15,12 +15,6 @@ namespace Esiur.Data.Types;
public class EventDef : MemberDef
{
public Map<string, string> Annotations
{
get;
set;
}
public override string ToString()
{
return $"{Name}: {ArgumentType}";
@@ -35,7 +29,9 @@ public class EventDef : MemberDef
set => AutoDelivered = !value;
}
public bool Deprecated { get; set; }
public bool Historical { get; set; }
public OrderingControl OrderingControl { get; set; }
public EventInfo EventInfo { get; set; }
@@ -166,7 +162,9 @@ public class EventDef : MemberDef
throw new Exception($"Unsupported type `{argType}` in event `{type.Name}.{ei.Name}`");
var annotationAttrs = ei.GetCustomAttributes<AnnotationAttribute>(true);
var autoDeliveredAttr = ei.GetCustomAttribute<AutoDeliveredAttribute>(true);
var autoDeliveryAttr = ei.GetCustomAttribute<AutoDeliveryAttribute>(true);
var historicalAttr = ei.GetCustomAttribute<HistoricalAttribute>(true);
var orderingAttr = ei.GetCustomAttribute<OrderingAttribute>(true);
//evtType.Nullable = new NullabilityInfoContext().Create(ei).ReadState is NullabilityState.Nullable;
@@ -209,7 +207,7 @@ public class EventDef : MemberDef
}
return new EventDef()
return DefinitionAttributeReader.Apply(ei, new EventDef()
{
Name = name,
ArgumentType = evtType,
@@ -217,8 +215,10 @@ public class EventDef : MemberDef
Inherited = ei.DeclaringType != type,
Annotations = annotations,
EventInfo = ei,
AutoDelivered = autoDeliveredAttr != null
};
AutoDelivered = autoDeliveryAttr != null,
Historical = historicalAttr != null,
OrderingControl = orderingAttr?.Control ?? OrderingControl.Strict,
});
}
}
+58 -20
View File
@@ -15,12 +15,6 @@ namespace Esiur.Data.Types;
public class FunctionDef : MemberDef
{
public Map<string, string> Annotations
{
get;
set;
}
//public bool IsVoid
//{
// get;
@@ -33,7 +27,7 @@ public class FunctionDef : MemberDef
public bool ReadOnly { get; set; }
public bool Idempotent { get; set; }
public bool Cancellable { get; set; }
public bool Deprecated { get; set; }
public bool Pausable { get; set; }
//public FunctionDefFlags Flags { get; set; }
public StreamMode StreamMode { get; set; }
@@ -175,28 +169,61 @@ public class FunctionDef : MemberDef
{
var genericRtType = mi.ReturnType.IsGenericType ? mi.ReturnType.GetGenericTypeDefinition() : null;
var streamAttribute = mi.GetCustomAttribute<StreamAttribute>(true);
var streamMode = StreamMode.None;
var pausable = false;
Tru rtType;
if (genericRtType == typeof(AsyncReply<>))
if (streamAttribute != null &&
genericRtType != typeof(AsyncReply<>) &&
genericRtType != typeof(AsyncStreamReply<>))
throw new Exception($"Method `{type.Name}.{mi.Name}` uses StreamAttribute and must return AsyncReply<T> or AsyncStreamReply<T>.");
if (genericRtType == typeof(IAsyncEnumerable<>))
{
streamMode = StreamMode.Pull;
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0], warehouse);
}
else if (genericRtType == typeof(Task<>))
else if (genericRtType == typeof(IEnumerable<>))
{
streamMode = StreamMode.Push;
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0], warehouse);
}
else if (genericRtType == typeof(IEnumerable<>) || genericRtType == typeof(IAsyncEnumerable<>))
else if (genericRtType == typeof(AsyncStreamReply<>))
{
if (streamAttribute == null)
throw new Exception($"Method `{type.Name}.{mi.Name}` returning AsyncStreamReply<T> must declare StreamAttribute.");
streamMode = streamAttribute.Mode;
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0], warehouse);
}
else if (streamAttribute != null)
{
streamMode = streamAttribute.Mode;
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0], warehouse);
}
else if (genericRtType == typeof(AsyncReply<>) || genericRtType == typeof(Task<>))
{
// get export
rtType = Tru.FromType(mi.ReturnType.GetGenericArguments()[0], warehouse);
}
else
{
if (mi.ReturnType == typeof(Task))
rtType = Tru.FromType(null, warehouse);
else
rtType = Tru.FromType(mi.ReturnType, warehouse);
rtType = mi.ReturnType == typeof(Task)
? Tru.FromType(null, warehouse)
: Tru.FromType(mi.ReturnType, warehouse);
}
if (streamAttribute != null)
{
if (streamAttribute.Mode != StreamMode.Push && streamAttribute.Mode != StreamMode.Pull)
throw new Exception($"Stream method `{type.Name}.{mi.Name}` must use Push or Pull mode.");
if (streamMode != streamAttribute.Mode)
throw new Exception($"Stream mode `{streamAttribute.Mode}` conflicts with return type `{mi.ReturnType}` in method `{type.Name}.{mi.Name}`.");
pausable = streamAttribute.Pausable;
if (pausable && streamMode != StreamMode.Push)
throw new Exception($"Only push stream method `{type.Name}.{mi.Name}` can be pausable.");
}
if (rtType == null)
@@ -237,7 +264,12 @@ public class FunctionDef : MemberDef
//var rtFlags = rtNullableAttr?.Flags?.ToList() ?? new List<byte>();
//var rtFlags = ((byte[])rtNullableAttr?.NullableFlags ?? new byte[0]).ToList();
if (rtNullableAttrFlags.Count > 0 && genericRtType == typeof(AsyncReply<>))
if (rtNullableAttrFlags.Count > 0 &&
(genericRtType == typeof(AsyncReply<>) ||
genericRtType == typeof(Task<>) ||
genericRtType == typeof(IEnumerable<>) ||
genericRtType == typeof(IAsyncEnumerable<>) ||
genericRtType == typeof(AsyncStreamReply<>)))
rtNullableAttrFlags.RemoveAt(0);
if (rtNullableContextAttrFlag == 2)
@@ -333,7 +365,7 @@ public class FunctionDef : MemberDef
}
return new FunctionDef()
return DefinitionAttributeReader.Apply(mi, new FunctionDef()
{
Name = name,
Index = index,
@@ -342,8 +374,14 @@ public class FunctionDef : MemberDef
ReturnType = rtType,
Arguments = arguments,
MethodInfo = mi,
Annotations = annotations
};
Annotations = annotations,
ReadOnly = mi.GetCustomAttribute<ReadOnlyAttribute>(true) != null,
Idempotent = mi.GetCustomAttribute<IdempotentAttribute>(true) != null,
Cancellable = mi.GetCustomAttribute<CancellableAttribute>(true) != null,
RatePolicyName = mi.GetCustomAttribute<RateControlAttribute>(true)?.PolicyName,
StreamMode = streamMode,
Pausable = pausable,
});
}
@@ -15,5 +15,6 @@ namespace Esiur.Data.Types
ReadOnly = 0x08,
Idempotent = 0x10,
Cancellable = 0x20,
Pausable = 0x40,
}
}
}
@@ -61,6 +61,9 @@ public class LocalTypeDef:TypeDef
if (genericType == typeof(List<>)
|| genericType == typeof(PropertyContext<>)
|| genericType == typeof(AsyncReply<>)
|| genericType == typeof(Task<>)
|| genericType == typeof(IEnumerable<>)
|| genericType == typeof(IAsyncEnumerable<>)
|| genericType == typeof(ResourceLink<>))
{
return GetDistributedTypes(genericTypeArgs[0]);
@@ -273,6 +276,8 @@ public class LocalTypeDef:TypeDef
_typeName = GetTypeName(type);
DefinitionAttributeReader.Apply(type, this);
warehouse.TryRegisterLocalTypeDef(this);
+10
View File
@@ -16,6 +16,14 @@ public class MemberDef
public bool Inherited { get; set; }
public bool Deprecated { get; set; }
/// <summary>
/// Name of the server-side Warehouse rate policy applied to this member.
/// This is local execution metadata and is not sent to remote clients.
/// </summary>
public string? RatePolicyName { get; set; }
public TypeDef Definition { get; set; } = null!;
// Human-readable metadata
@@ -54,6 +62,8 @@ public class MemberDef
// Compatibility guidance
public string? DeprecationMessage { get; set; }
public Map<string, string>? Annotations { get; set; }
public string Fullname =>
Definition is null || string.IsNullOrEmpty(Definition.Name)
? Name
@@ -34,5 +34,8 @@ namespace Esiur.Data.Types
// Compatibility guidance
DeprecationMessage = 0x2F, // string
// Extensible application metadata
Annotations = 0x30, // Map<string, string>
}
}
@@ -68,4 +68,7 @@ public class MemberDefInfo : IndexedStructure
[Index((int)MemberDefField.DeprecationMessage)]
public string? DeprecationMessage { get; set; }
[Index((int)MemberDefField.Annotations)]
public Map<string, string>? Annotations { get; set; }
}
+18 -9
View File
@@ -14,11 +14,6 @@ namespace Esiur.Data.Types;
public class PropertyDef : MemberDef
{
public Map<string, string> Annotations { get; set; }
public PropertyInfo PropertyInfo
{
get;
@@ -52,10 +47,15 @@ public class PropertyDef : MemberDef
}
public bool Constant { get; set; }
public bool Volatile { get; set; }
//public bool IsNullable { get; set; }
public bool Historical { get; set; }
public OrderingControl OrderingControl { get; set; }
public object? DefaultValue { get; set; }
public bool HasHistory
{
get => Historical;
@@ -258,6 +258,10 @@ public class PropertyDef : MemberDef
var annotationAttrs = pi.GetCustomAttributes<AnnotationAttribute>(true);
var historicalAttr = pi.GetCustomAttribute<HistoricalAttribute>(true);
var readOnlyAttr = pi.GetCustomAttribute<ReadOnlyAttribute>(true);
var volatileAttr = pi.GetCustomAttribute<VolatileAttribute>(true);
var orderingAttr = pi.GetCustomAttribute<OrderingAttribute>(true);
var defaultValueAttr = pi.GetCustomAttribute<System.ComponentModel.DefaultValueAttribute>(true);
//var nullabilityContext = new NullabilityInfoContext();
//propType.Nullable = nullabilityContext.Create(pi).ReadState is NullabilityState.Nullable;
@@ -306,16 +310,21 @@ public class PropertyDef : MemberDef
}
return new PropertyDef()
return DefinitionAttributeReader.Apply(pi, new PropertyDef()
{
Name = name,
Index = index,
Inherited = pi.DeclaringType != type,
ValueType = propType,
PropertyInfo = pi,
Historical = historicalAttr == null,
RatePolicyName = pi.GetCustomAttribute<RateControlAttribute>(true)?.PolicyName,
ReadOnly = readOnlyAttr != null,
Volatile = volatileAttr != null,
Historical = historicalAttr != null,
OrderingControl = orderingAttr?.Control ?? OrderingControl.Strict,
DefaultValue = defaultValueAttr?.Value,
Annotations = annotations,
};
});
}
@@ -325,7 +334,7 @@ public class PropertyDef : MemberDef
PropertyPermission permission, TypeDef typeDef)
{
var definition = MakePropertyDef(warehouse, type, pi, name, index, typeDef);
definition.Permission = permission;
definition.ReadOnly = definition.ReadOnly || permission == PropertyPermission.Read;
return definition;
}
+14 -2
View File
@@ -194,6 +194,11 @@ public class RemoteTypeDef:TypeDef
: $"{info.Namespace}.{info.Name}";
definition._typeDefKind = info.Kind;
definition._version = info.Version;
definition.Usage = info.Usage;
definition.Description = info.Description;
definition.Example = info.Example;
definition.Category = info.Category;
definition.Since = info.Since;
definition.Annotations = info.Annotations;
definition._properties.Clear();
@@ -218,6 +223,7 @@ public class RemoteTypeDef:TypeDef
member.Index = info.Index;
member.Name = info.Name;
member.Inherited = (info.Flags & (byte)MemberDefFlags.Inherited) != 0;
member.Deprecated = (info.Flags & (byte)MemberDefFlags.Deprecated) != 0;
member.Description = info.Description;
member.Usage = info.Usage;
member.Examples = info.Examples;
@@ -234,6 +240,7 @@ public class RemoteTypeDef:TypeDef
member.Warnings = info.Warnings;
member.RelatedMembers = info.RelatedMembers;
member.DeprecationMessage = info.DeprecationMessage;
member.Annotations = info.Annotations;
return member;
}
@@ -245,7 +252,10 @@ public class RemoteTypeDef:TypeDef
ValueType = info.ValueType,
ReadOnly = flags.HasFlag(PropertyDefFlags.ReadOnly),
Constant = flags.HasFlag(PropertyDefFlags.Constant),
Volatile = flags.HasFlag(PropertyDefFlags.Volatile),
Historical = flags.HasFlag(PropertyDefFlags.Historical) || info.HistoryControl != 0,
OrderingControl = info.OrderingControl,
DefaultValue = info.DefaultValue,
});
}
@@ -260,7 +270,7 @@ public class RemoteTypeDef:TypeDef
ReadOnly = flags.HasFlag(FunctionDefFlags.ReadOnly),
Idempotent = flags.HasFlag(FunctionDefFlags.Idempotent),
Cancellable = flags.HasFlag(FunctionDefFlags.Cancellable),
Deprecated = flags.HasFlag(FunctionDefFlags.Deprecated),
Pausable = flags.HasFlag(FunctionDefFlags.Pausable),
Arguments = info.Arguments?.Select(ToArgument).ToArray() ?? Array.Empty<ArgumentDef>(),
});
}
@@ -275,6 +285,7 @@ public class RemoteTypeDef:TypeDef
Optional = flags.HasFlag(ArgumentDefFlags.Optional),
Type = info.ValueType,
DefaultValue = info.DefaultValue,
Annotations = info.Annotations,
};
}
@@ -285,7 +296,8 @@ public class RemoteTypeDef:TypeDef
{
ArgumentType = info.ArgumentType,
AutoDelivered = flags.HasFlag(EventDefFlags.AutoDelivered),
Deprecated = flags.HasFlag(EventDefFlags.Deprecated),
Historical = flags.HasFlag(EventDefFlags.Historical) || info.HistoryControl != 0,
OrderingControl = info.OrderingControl,
});
}
+10
View File
@@ -23,6 +23,16 @@ public class TypeDef
public Map<string, string> Annotations { get; set; }
public string? Usage { get; set; }
public string? Description { get; set; }
public object? Example { get; set; }
public string? Category { get; set; }
public string? Since { get; set; }
protected string _typeName;
protected List<FunctionDef> _functions = new List<FunctionDef>();
protected List<EventDef> _events = new List<EventDef>();
+16
View File
@@ -66,6 +66,11 @@ public class TypeDefInfo : IndexedStructure
Name = definition.Name,
Kind = definition.Kind,
Parent = definition.ParentTypeId,
Usage = definition.Usage,
Description = definition.Description,
Example = definition.Example,
Category = definition.Category,
Since = definition.Since,
Annotations = definition.Annotations,
Properties = definition.Properties.Select(FromProperty).ToList(),
Functions = definition.Functions.Select(FromFunction).ToList(),
@@ -94,21 +99,26 @@ public class TypeDefInfo : IndexedStructure
target.Warnings = source.Warnings;
target.RelatedMembers = source.RelatedMembers;
target.DeprecationMessage = source.DeprecationMessage;
target.Annotations = source.Annotations;
return target;
}
private static PropertyDefInfo FromProperty(PropertyDef source)
{
var flags = source.Inherited ? PropertyDefFlags.Inherited : PropertyDefFlags.None;
if (source.Deprecated) flags |= PropertyDefFlags.Deprecated;
if (source.ReadOnly) flags |= PropertyDefFlags.ReadOnly;
if (source.Constant) flags |= PropertyDefFlags.Constant;
if (source.Volatile) flags |= PropertyDefFlags.Volatile;
if (source.Historical) flags |= PropertyDefFlags.Historical;
return CopyMember(source, new PropertyDefInfo
{
Flags = (byte)flags,
ValueType = source.ValueType,
OrderingControl = source.OrderingControl,
HistoryControl = source.Historical ? (byte)1 : (byte)0,
DefaultValue = source.DefaultValue,
});
}
@@ -120,6 +130,7 @@ public class TypeDefInfo : IndexedStructure
if (source.ReadOnly) flags |= FunctionDefFlags.ReadOnly;
if (source.Idempotent) flags |= FunctionDefFlags.Idempotent;
if (source.Cancellable) flags |= FunctionDefFlags.Cancellable;
if (source.Pausable) flags |= FunctionDefFlags.Pausable;
return CopyMember(source, new FunctionDefInfo
{
@@ -139,6 +150,7 @@ public class TypeDefInfo : IndexedStructure
Flags = source.Optional ? (byte)ArgumentDefFlags.Optional : (byte)ArgumentDefFlags.None,
ValueType = source.Type,
DefaultValue = source.DefaultValue,
Annotations = source.Annotations,
};
}
@@ -147,17 +159,21 @@ public class TypeDefInfo : IndexedStructure
var flags = source.Inherited ? EventDefFlags.Inherited : EventDefFlags.None;
if (source.Deprecated) flags |= EventDefFlags.Deprecated;
if (source.AutoDelivered) flags |= EventDefFlags.AutoDelivered;
if (source.Historical) flags |= EventDefFlags.Historical;
return CopyMember(source, new EventDefInfo
{
Flags = (byte)flags,
ArgumentType = source.ArgumentType,
OrderingControl = source.OrderingControl,
HistoryControl = source.Historical ? (byte)1 : (byte)0,
});
}
private static ConstantDefInfo FromConstant(ConstantDef source)
{
var flags = source.Inherited ? ConstantDefFlags.Inherited : ConstantDefFlags.None;
if (source.Deprecated) flags |= ConstantDefFlags.Deprecated;
return CopyMember(source, new ConstantDefInfo
{
Flags = (byte)flags,