< Summary

Information
Class: IceRpc.ServerAddressComparer
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/ServerAddress.cs
Tag: 275_13775359185
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 262
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage
100%
Covered methods: 3
Total methods: 3
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_OptionalTransport()100%11100%
Equals(...)100%1212100%
GetHashCode(...)100%11100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/ServerAddress.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Internal;
 4using System.Collections.Immutable;
 5using System.ComponentModel;
 6using System.Globalization;
 7using System.Net;
 8using System.Text;
 9
 10namespace IceRpc;
 11
 12/// <summary>A server address specifies the address of the server-end of an ice or icerpc connection: a server listens
 13/// on a server address and a client establishes a connection to a server address.</summary>
 14// The properties of this struct are sorted in URI order.
 15[TypeConverter(typeof(ServerAddressTypeConverter))]
 16public readonly record struct ServerAddress
 17{
 18    /// <summary>Gets the protocol of this server address.</summary>
 19    /// <value>Either <see cref="Protocol.IceRpc" /> or <see cref="Protocol.Ice" />.</value>
 20    public Protocol Protocol { get; }
 21
 22    /// <summary>Gets or initializes the host.</summary>
 23    /// <value>The host of this server address. Defaults to <c>::0</c> meaning that the server will listen on all the
 24    /// network interfaces. This default value is parsed into <see cref="IPAddress.IPv6Any" />.</value>
 25    public string Host
 26    {
 27        get => _host;
 28
 29        init
 30        {
 31            if (Uri.CheckHostName(value) == UriHostNameType.Unknown)
 32            {
 33                throw new ArgumentException($"Cannot set {nameof(Host)} to '{value}'.", nameof(value));
 34            }
 35            _host = value;
 36            OriginalUri = null; // new host invalidates OriginalUri
 37        }
 38    }
 39
 40    /// <summary>Gets or initializes the port number.</summary>
 41    /// <value>The port number of this server address. Defaults to <see cref="Protocol.DefaultPort" />.</value>
 42    public ushort Port
 43    {
 44        get => _port;
 45
 46        init
 47        {
 48            _port = value;
 49            OriginalUri = null; // new port invalidates OriginalUri
 50        }
 51    }
 52
 53    /// <summary>Gets or initializes the transport.</summary>
 54    /// <value>The name of the transport, or <see langword="null"/> if the transport is unspecified. Defaults to
 55    /// <see langword="null"/>.</value>
 56    public string? Transport
 57    {
 58        get => _transport;
 59
 60        init
 61        {
 62            _transport = value is null || (ServiceAddress.IsValidParamValue(value) && value.Length > 0) ? value :
 63                throw new ArgumentException($"The value '{value}' is not valid transport name", nameof(value));
 64            OriginalUri = null; // new transport invalidates OriginalUri
 65        }
 66    }
 67
 68    /// <summary>Gets or initializes transport-specific parameters.</summary>
 69    /// <value>The server address parameters. Defaults to <see cref="ImmutableDictionary{TKey, TValue}.Empty" />.
 70    /// </value>
 71    public ImmutableDictionary<string, string> Params
 72    {
 73        get => _params;
 74
 75        init
 76        {
 77            try
 78            {
 79                ServiceAddress.CheckParams(value);
 80            }
 81            catch (FormatException exception)
 82            {
 83                throw new ArgumentException("Invalid parameters.", nameof(value), exception);
 84            }
 85            _params = value;
 86            OriginalUri = null; // new params invalidates OriginalUri
 87        }
 88    }
 89
 90    /// <summary>Gets the URI used to create this server address.</summary>
 91    /// <value>The <see cref="Uri" /> of this server address if it was constructed from a URI; otherwise,
 92    /// <see langword="null"/>.</value>
 93    public Uri? OriginalUri { get; private init; }
 94
 95    private readonly string _host = "::0";
 96    private readonly ImmutableDictionary<string, string> _params = ImmutableDictionary<string, string>.Empty;
 97    private readonly ushort _port;
 98    private readonly string? _transport;
 99
 100    /// <summary>Constructs a server address with default values.</summary>
 101    public ServerAddress()
 102        : this(Protocol.IceRpc)
 103    {
 104    }
 105
 106    /// <summary>Constructs a server address from a supported protocol.</summary>
 107    /// <param name="protocol">The protocol.</param>
 108    public ServerAddress(Protocol protocol)
 109    {
 110        Protocol = protocol;
 111        _port = Protocol.DefaultPort;
 112        _transport = null;
 113        OriginalUri = null;
 114    }
 115
 116    /// <summary>Constructs a server address from a <see cref="Uri" />.</summary>
 117    /// <param name="uri">An absolute URI.</param>
 118    /// <exception cref="ArgumentException">Thrown if <paramref name="uri" /> is not an absolute URI, or if its scheme
 119    /// is not a supported protocol, or if it has a non-empty path or fragment, or if it has an empty host, or if its
 120    /// query can't be parsed or if it has an alt-server query parameter.</exception>
 121    public ServerAddress(Uri uri)
 122    {
 123        if (!uri.IsAbsoluteUri)
 124        {
 125            throw new ArgumentException("Cannot create a server address from a relative URI.", nameof(uri));
 126        }
 127
 128        Protocol = Protocol.TryParse(uri.Scheme, out Protocol? protocol) ? protocol :
 129            throw new ArgumentException($"Cannot create a server address with protocol '{uri.Scheme}'", nameof(uri));
 130
 131        _host = uri.IdnHost;
 132        if (_host.Length == 0)
 133        {
 134            throw new ArgumentException("Cannot create a server address with an empty host.", nameof(uri));
 135        }
 136
 137        _port = uri.Port == -1 ? Protocol.DefaultPort : checked((ushort)uri.Port);
 138
 139        if (uri.UserInfo.Length > 0)
 140        {
 141            throw new ArgumentException("Cannot create a server address with a user info.", nameof(uri));
 142        }
 143
 144        if (uri.AbsolutePath.Length > 1)
 145        {
 146            throw new ArgumentException("Cannot create a server address with a path.", nameof(uri));
 147        }
 148
 149        if (uri.Fragment.Length > 0)
 150        {
 151            throw new ArgumentException("Cannot create a server address with a fragment.", nameof(uri));
 152        }
 153
 154        try
 155        {
 156            (_params, string? altServerValue, _transport) = uri.ParseQuery();
 157
 158            if (altServerValue is not null)
 159            {
 160                throw new ArgumentException(
 161                    "Cannot create a server address with an alt-server query parameter.",
 162                    nameof(uri));
 163            }
 164        }
 165        catch (FormatException exception)
 166        {
 167            throw new ArgumentException("Cannot parse query of server address URI.", nameof(uri), exception);
 168        }
 169
 170        OriginalUri = uri;
 171    }
 172
 173    /// <summary>Checks if this server address is equal to another server address.</summary>
 174    /// <param name="other">The other server address.</param>
 175    /// <returns><see langword="true" /> when the two server addresses have the same properties, including the same
 176    /// parameters; otherwise, <see langword="false" />.</returns>
 177    public bool Equals(ServerAddress other) =>
 178        Protocol == other.Protocol &&
 179        Host == other.Host &&
 180        Port == other.Port &&
 181        Transport == other.Transport &&
 182        Params.DictionaryEqual(other.Params);
 183
 184    /// <summary>Computes the hash code for this server address.</summary>
 185    /// <returns>The hash code.</returns>
 186    public override int GetHashCode() => HashCode.Combine(Protocol, Host, Port, Transport, Params.Count);
 187
 188    /// <summary>Converts this server address into a string.</summary>
 189    /// <returns>The string representation of this server address.</returns>
 190    public override string ToString() =>
 191        OriginalUri?.ToString() ?? new StringBuilder().AppendServerAddress(this).ToString();
 192
 193    /// <summary>Converts this server address into a URI.</summary>
 194    /// <returns>The URI.</returns>
 195    public Uri ToUri() => OriginalUri ?? new Uri(ToString(), UriKind.Absolute);
 196
 197    /// <summary>Constructs a server address from a protocol, a host, a port and parsed parameters, without parameter
 198    /// validation.</summary>
 199    /// <remarks>This constructor is used by <see cref="ServiceAddress" /> for its main server address and by the Slice
 200    /// decoder for Slice1 server addresses.</remarks>
 201    internal ServerAddress(
 202        Protocol protocol,
 203        string host,
 204        ushort port,
 205        string? transport,
 206        ImmutableDictionary<string, string> serverAddressParams)
 207    {
 208        Protocol = protocol;
 209        _host = host;
 210        _port = port;
 211        _transport = transport;
 212        _params = serverAddressParams;
 213        OriginalUri = null;
 214    }
 215}
 216
 217/// <summary>Equality comparer for <see cref="ServerAddress" />.</summary>
 218public abstract class ServerAddressComparer : EqualityComparer<ServerAddress>
 219{
 220    /// <summary>Gets a server address comparer that compares all server address properties, except a transport mismatch
 221    /// where the transport of one of the server addresses is null results in equality.</summary>
 222    /// <value>A <see cref="ServerAddressComparer" /> instance that compares server address properties with the
 223    /// exception of the <see cref="ServerAddress.Transport" /> properties which are only compared if non-null.</value>
 75224    public static ServerAddressComparer OptionalTransport { get; } = new OptionalTransportServerAddressComparer();
 225
 226    private class OptionalTransportServerAddressComparer : ServerAddressComparer
 227    {
 228        public override bool Equals(ServerAddress lhs, ServerAddress rhs) =>
 166229            lhs.Protocol == rhs.Protocol &&
 166230            lhs.Host == rhs.Host &&
 166231            lhs.Port == rhs.Port &&
 166232            (lhs.Transport == rhs.Transport || lhs.Transport is null || rhs.Transport is null) &&
 166233            lhs.Params.DictionaryEqual(rhs.Params);
 234
 235        public override int GetHashCode(ServerAddress serverAddress) =>
 208236            HashCode.Combine(serverAddress.Protocol, serverAddress.Host, serverAddress.Port, serverAddress.Params.Count)
 237    }
 238}
 239
 240/// <summary>The server address type converter specifies how to convert a string to a serverAddress. It's used by
 241/// sub-systems such as the Microsoft ConfigurationBinder to bind string values to ServerAddress properties.</summary>
 242public class ServerAddressTypeConverter : TypeConverter
 243{
 244    /// <summary>Returns whether this converter can convert an object of the given type into a
 245    /// <see cref="ServerAddress"/> value, using the specified context.</summary>
 246    /// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
 247    /// <param name="sourceType">A <see cref="Type"/> that represents the type you want to convert from.</param>
 248    /// <returns><see langword="true"/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
 249    /// </returns>
 250    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
 251        sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
 252
 253    /// <summary>Converts the given object into a <see cref="ServerAddress"/> value, using the specified context and
 254    /// culture information.</summary>
 255    /// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
 256    /// <param name="culture">The <see cref="CultureInfo"/> to use as the current culture.</param>
 257    /// <param name="value">The <see cref="object "/> to convert.</param>
 258    /// <returns>An <see cref="object "/> that represents the converted <see cref="ServerAddress"/> value.</returns>
 259    /// <remarks><see cref="TypeConverter"/>.</remarks>
 260    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) =>
 261        value is string valueStr ? new ServerAddress(new Uri(valueStr)) : base.ConvertFrom(context, culture, value);
 262}