< Summary

Information
Class: IceRpc.ServerAddressComparer
Assembly: IceRpc
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc/ServerAddress.cs
Tag: 2300_35243572715
Line coverage
100%
Covered lines: 7
Uncovered lines: 0
Coverable lines: 7
Total lines: 267
Line coverage: 100%
Branch coverage
91%
Covered branches: 11
Total branches: 12
Branch coverage: 91.6%
Method coverage
100%
Covered methods: 3
Fully covered methods: 3
Total methods: 3
Method coverage: 100%
Full method coverage: 100%

Metrics

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