// Copyright (c) Microsoft. All rights reserved. using System; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI.Agents.Runtime; /// /// Provides a topic identifier that defines the scope of a broadcast message. /// /// /// The agent runtime implements a publish-subscribe model through its broadcast API, /// where messages must be published with a specific topic. /// public readonly partial struct TopicId : IEquatable { private const string TypePattern = @"^[\w-.:=]+$"; #if NET [GeneratedRegex(TypePattern)] private static partial Regex TypeRegex(); #else private static Regex TypeRegex() => s_typeRegex; private static readonly Regex s_typeRegex = new(TypePattern, RegexOptions.Compiled); #endif /// /// Initializes a new instance of the struct. /// /// The type of the topic. Must match the pattern: ^[\w-.:=]+$ /// The source of the event. public TopicId(string type, string? source = null) { Throw.IfNull(type); if (!TypeRegex().IsMatch(type)) { Throw.ArgumentException(nameof(type), "Invalid type format."); } // TODO: What validation should be performed on source? The cited cloudevents spec suggests it should be a URI reference. this.Type = type; this.Source = source ?? "default"; } /// /// Gets the type of the event that this represents. /// /// /// This adheres to the CloudEvents specification. /// CloudEvents Type. /// public string Type { get; } /// /// Gets the source that identifies the context in which an event happened. /// /// /// This adheres to the CloudEvents specification. /// CloudEvents Source. /// public string Source { get; } /// /// Convert a string of the format "type/key" into an . /// /// The actor ID string. /// An instance of . public static TopicId Parse(string TopicId) { if (!KeyValueParser.TryParse(TopicId, out string? type, out string? key)) { throw new FormatException($"Invalid TopicId format: '{TopicId}'. Expected format is 'type/key'."); } return new TopicId(type, key); } /// public override readonly string ToString() => $"{this.Type}/{this.Source}"; /// public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is TopicId other && this.Equals(other); /// public readonly bool Equals(TopicId other) => this.Type == other.Type && this.Source == other.Source; /// public override readonly int GetHashCode() => HashCode.Combine(this.Type, this.Source); /// public static bool operator ==(TopicId left, TopicId right) => left.Equals(right); /// public static bool operator !=(TopicId left, TopicId right) => !left.Equals(right); // TODO: Implement < for wildcard matching (type, *) //public readonly bool IsWildcardMatch(TopicId other) //{ // return this.Type == other.Type; //} }