This commit is contained in:
2026-07-12 15:22:42 +03:00
parent 91d9c5d959
commit 2f81cd1d30
44 changed files with 785 additions and 242 deletions
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Event | AttributeTargets.Parameter, AllowMultiple = true)]
public class AnnotationAttribute : Attribute
{
public readonly string? Key;
public readonly string Value;
public AnnotationAttribute(string annotation)
{
Key = "";
Value = annotation;
}
public AnnotationAttribute(string key, string value)
{
Key = key;
Value = value;
}
//public AnnotationAttribute(params string[] annotations)
//{
// this.Annotation = String.Join("\n", annotations);
//}
}
@@ -0,0 +1,40 @@
/*
Copyright (c) 2020 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Property)]
public class AttributeAttribute : System.Attribute
{
public string Name { get; set; }
public AttributeAttribute(string name = null)
{
this.Name = name;
}
}
@@ -0,0 +1,43 @@
/*
Copyright (c) 2021 Ahmed Kh. Zamil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Esiur.Resource;
/// <summary>
/// Indicates that an event is delivered to attached clients without
/// requiring an explicit subscription.
/// </summary>
[AttributeUsage(
AttributeTargets.Event |
AttributeTargets.Field,
AllowMultiple = false,
Inherited = true)]
public sealed class AutoDeliveredAttribute : Attribute
{
}
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
/// <summary>
/// Indicates that a running remote invocation may be cancelled.
/// </summary>
[AttributeUsage(
AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public sealed class CancellableAttribute : Attribute
{
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Event | AttributeTargets.Class | AttributeTargets.Enum)]
public class ExportAttribute : Attribute
{
public string Name { get; private set; } = null;
public PropertyPermission? Permission { get; private set; }
public ExportAttribute()
{
}
public ExportAttribute(string name)
{
Name = name;
}
public ExportAttribute(PropertyPermission permission)
{
Permission = permission;
}
public ExportAttribute(string name, PropertyPermission permission)
{
Name = name;
Permission = permission;
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
/// <summary>
/// Indicates that previous values of an exported property are retained
/// and may be fetched remotely.
/// </summary>
[AttributeUsage(
AttributeTargets.Property |
AttributeTargets.Field,
AllowMultiple = false,
Inherited = true)]
public sealed class HistoricalAttribute : Attribute
{
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
/// <summary>
/// Indicates that repeating the same invocation should have the same
/// externally observable effect as invoking it once.
/// </summary>
[AttributeUsage(
AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public sealed class IdempotentAttribute : Attribute
{
}
}
@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
public class IgnoreAttribute : Attribute
{
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Class)]
public class ImportAttribute : Attribute
{
public ImportAttribute(params string[] urls)
{
}
}
@@ -0,0 +1,28 @@
using Esiur.Data;
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
/// <summary>
/// Specifies non-default notification ordering for a property or event.
/// Absence of this attribute means strict ordering.
/// </summary>
[AttributeUsage(
AttributeTargets.Property |
AttributeTargets.Field |
AttributeTargets.Event,
AllowMultiple = false,
Inherited = true)]
public sealed class OrderingAttribute : Attribute
{
public OrderingControl Control { get; }
public OrderingAttribute(OrderingControl control)
{
Control = control;
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
/// <summary>
/// Indicates that an exported function does not change resource state,
/// or that an exported property cannot be changed remotely.
/// </summary>
[AttributeUsage(
AttributeTargets.Method |
AttributeTargets.Property |
AttributeTargets.Field,
AllowMultiple = false,
Inherited = true)]
public sealed class ReadOnlyAttribute : Attribute
{
}
}
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
namespace Esiur.Resource
{
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Class, Inherited = false)]
public class RemoteAttribute : Attribute
{
public string[] Domains { get; private set; }
public string FullName { get; private set; }
static readonly Regex StrictIPv4 = new(
@"^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
static readonly Regex IPv4Like = new(
@"^\d+(?:\.\d+){3}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
static readonly Regex HostName = new(
@"^(?=.{1,253}\.?$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)*[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.?$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
public bool IsValidFullName()
{
return IsValidQualifiedClassName(FullName, true, false); ;
}
private static readonly HashSet<string> ReservedKeywords = new HashSet<string>(
new[]
{
"abstract", "as", "base", "bool", "break", "byte", "case", "catch",
"char", "checked", "class", "const", "continue", "decimal", "default",
"delegate", "do", "double", "else", "enum", "event", "explicit",
"extern", "false", "finally", "fixed", "float", "for", "foreach",
"goto", "if", "implicit", "in", "int", "interface", "internal",
"is", "lock", "long", "namespace", "new", "null", "object",
"operator", "out", "override", "params", "private", "protected",
"public", "readonly", "ref", "return", "sbyte", "sealed", "short",
"sizeof", "stackalloc", "static", "string", "struct", "switch",
"this", "throw", "true", "try", "typeof", "uint", "ulong",
"unchecked", "unsafe", "ushort", "using", "virtual", "void",
"volatile", "while"
},
StringComparer.Ordinal);
public RemoteAttribute(string fullName, params string[] domains)
{
Domains = domains;
FullName = fullName;
}
// @TODO: support wildcard records
public bool AreValidDomains()
{
foreach(var domain in Domains)
{
if (!IsValidDomain(domain))
return false;
}
return true;
}
private bool IsValidDomain(string domain)
{
if (string.IsNullOrWhiteSpace(domain))
return false;
string s = domain.Trim();
// Accept URI-style IPv6 literals, e.g. [::1]
if (s.Length > 2 && s[0] == '[' && s[s.Length - 1] == ']')
s = s.Substring(1, s.Length - 2);
// Strict IPv4 only: 0.0.0.0 to 255.255.255.255
if (StrictIPv4.IsMatch(s))
return true;
// Reject IPv4-looking strings that failed strict IPv4,
// e.g. 999.999.999.999
if (IPv4Like.IsMatch(s))
return false;
IPAddress ip;
// IPv6
if (IPAddress.TryParse(s, out ip) &&
ip.AddressFamily == AddressFamily.InterNetworkV6)
return true;
// Hostname or domain
return HostName.IsMatch(s);
}
public static bool IsValidQualifiedClassName(string value, bool requireNamespace, bool allowVerbatimIdentifiers)
{
if (string.IsNullOrWhiteSpace(value))
return false;
string s = value.Trim();
if (s.Length == 0)
return false;
// Reject whitespace inside the name
for (int i = 0; i < s.Length; i++)
{
if (char.IsWhiteSpace(s[i]))
return false;
}
// Optional source-style prefix: global::Namespace.Type
if (s.StartsWith("global::", StringComparison.Ordinal))
s = s.Substring("global::".Length);
string[] parts = s.Split('.');
if (requireNamespace && parts.Length < 2)
return false;
for (int i = 0; i < parts.Length; i++)
{
if (!IsValidIdentifier(parts[i], allowVerbatimIdentifiers))
return false;
}
return true;
}
private static bool IsValidIdentifier(string identifier, bool allowVerbatimIdentifier)
{
if (string.IsNullOrEmpty(identifier))
return false;
string id = identifier;
if (id[0] == '@')
{
if (!allowVerbatimIdentifier)
return false;
id = id.Substring(1);
if (id.Length == 0)
return false;
}
if (!IsIdentifierStartCharacter(id[0]))
return false;
for (int i = 1; i < id.Length; i++)
{
if (!IsIdentifierPartCharacter(id[i]))
return false;
}
// Reject reserved keywords unless written as @keyword
if (identifier[0] != '@' && ReservedKeywords.Contains(id))
return false;
return true;
}
private static bool IsIdentifierStartCharacter(char ch)
{
if (ch == '_')
return true;
UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory(ch);
return category == UnicodeCategory.UppercaseLetter ||
category == UnicodeCategory.LowercaseLetter ||
category == UnicodeCategory.TitlecaseLetter ||
category == UnicodeCategory.ModifierLetter ||
category == UnicodeCategory.OtherLetter ||
category == UnicodeCategory.LetterNumber;
}
private static bool IsIdentifierPartCharacter(char ch)
{
if (IsIdentifierStartCharacter(ch))
return true;
UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory(ch);
return category == UnicodeCategory.DecimalDigitNumber ||
category == UnicodeCategory.ConnectorPunctuation ||
category == UnicodeCategory.NonSpacingMark ||
category == UnicodeCategory.SpacingCombiningMark ||
category == UnicodeCategory.Format;
}
}
}
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ResourceAttribute : Attribute
{
public ResourceAttribute()
{
}
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource;
[AttributeUsage(AttributeTargets.Property)]
public class StorageAttribute : Attribute
{
public StorageMode Mode { get; set; }
public StorageAttribute(StorageMode mode)
{
Mode = mode;
}
}
@@ -0,0 +1,34 @@
using Esiur.Data.Types;
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource
{
/// <summary>
/// Marks an exported function as streaming and specifies its delivery mode.
/// </summary>
[AttributeUsage(
AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public sealed class StreamAttribute : Attribute
{
/// <summary>
/// Gets the stream delivery mode.
/// </summary>
public StreamMode Mode { get; }
/// <summary>
/// Indicates whether a push stream may be paused and resumed remotely.
/// This should only be true when Mode is Push.
/// </summary>
public bool Pausable { get; set; }
public StreamAttribute(StreamMode mode = StreamMode.Push)
{
Mode = mode;
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource.Attributes
{
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Interface |
AttributeTargets.Enum |
AttributeTargets.Delegate |
AttributeTargets.Method |
AttributeTargets.Property |
AttributeTargets.Field |
AttributeTargets.Event |
AttributeTargets.Parameter |
AttributeTargets.ReturnValue,
AllowMultiple = false,
Inherited = true)]
public sealed class UsageAttribute : Attribute
{
public string Value { get; }
public UsageAttribute(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException(
"Usage text cannot be null or empty.",
nameof(value));
Value = value;
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Esiur.Resource.Attributes
{
/// <summary>
/// Indicates that an exported property has volatile synchronization
/// semantics. This is unrelated to the C# volatile memory modifier.
/// </summary>
[AttributeUsage(
AttributeTargets.Property |
AttributeTargets.Field,
AllowMultiple = false,
Inherited = true)]
public sealed class VolatileAttribute : Attribute
{
}
}