// 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;
//}
}