// *********************************************************************** // Assembly : FCS.Lib // Author : FH // Created : 2020-07-01 // // Last Modified By : FH // Last Modified On : 2021-02-24 // *********************************************************************** // // Copyright © FCS 2015-2022 // // // Derived from https:github.com/csharpvitamins/CSharpVitamins.ShortGuid // Part of FCS.Lib - a set of utilities for C# - pieced together from fragments // Copyright (C) 2021 FCS // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // // *********************************************************************** using System; using System.Diagnostics; namespace FCS.Lib { /// /// A wrapper for handling URL-safe Base64 encoded globally unique identifiers (GUID). /// /// /// Special characters are replaced (/, +) or removed (==). /// Derived from https:github.com/csharpvitamins/CSharpVitamins.ShortGuid /// [DebuggerDisplay("{" + nameof(Value) + "}")] public readonly struct Squid : IEquatable { /// /// A read-only object of the Squid struct. /// Value is guaranteed to be all zeroes. /// Equivalent to . /// public static readonly Squid Empty = new(Guid.Empty); /// /// Creates a new Squid from a Squid encoded string. /// /// A valid Squid encodd string. public Squid(string value) { Value = value; Guid = DecodeSquid(value); } /// /// Creates a new Squid with the given . /// /// A valid System.Guid object. public Squid(Guid obj) { Value = EncodeGuid(obj); Guid = obj; } /// /// Gets the underlying for the encoded Squid. /// /// The unique identifier. #pragma warning disable CA1720 // Identifier contains type name public Guid Guid { get; } #pragma warning restore CA1720 // Identifier contains type name /// /// The encoded string value of the /// as an URL-safe Base64 string. /// /// The value. public string Value { get; } /// /// Returns the encoded URL-safe Base64 string. /// /// A that represents this instance. public override string ToString() { return Value; } /// /// Returns a value indicating whether this object and a specified object represent the same type and value. /// Compares for equality against other string, Guid and Squid types. /// /// A Systerm.String, System.Guid or Squid object /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object obj) { return obj is Squid other && Equals(other); } /// /// Equality comparison /// /// A valid Squid object /// A boolean indicating equality. public bool Equals(Squid obj) { return Guid.Equals(obj.Guid) && Value == obj.Value; } /// /// Returns the hash code for the underlying . /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() { unchecked { return (Guid.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); } } /// /// Initialises a new object of the Squid using . /// /// New Squid object public static Squid NewGuid() { return new(Guid.NewGuid()); } /// /// Encode string as a new Squid encoded string. /// The encoding is similar to Base64 with /// non-URL safe characters replaced, and padding removed. /// /// A valid .Tostring(). /// A 22 character URL-safe Base64 string. public static string EncodeString(string value) { var guid = new Guid(value); return EncodeGuid(guid); } /// /// Encode a object to Squid. /// The encoding is similar to Base64 with /// non-URL safe characters replaced, and padding removed. /// /// A valid object. /// A 22 character URL-safe Base64 string. public static string EncodeGuid(Guid obj) { var encoded = Convert.ToBase64String(obj.ToByteArray()); encoded = encoded .Replace("/", "_") .Replace("+", "-"); return encoded.Substring(0, 22); } /// /// Decode Squid string to a . /// See also or /// . /// /// A valid Squid encoded string. /// A new object from the parsed string. public static Guid DecodeSquid(string value) { if (value == null) return Empty; value = value .Replace("_", "/") .Replace("-", "+"); var blob = Convert.FromBase64String(value + "=="); return new Guid(blob); } /// /// Squid to Guid. /// /// A valid Squid object. /// System.Guid object. public static Guid FromSquid(Squid obj) { return obj.Guid; } /// /// String to Squid. /// /// String value to convert /// A Squid object. public static Squid FromString(string value) { if (string.IsNullOrEmpty(value)) return Empty; return TryParse(value, out Squid obj) ? obj : Empty; } /// /// Decodes the given value to a . /// /// The Squid encoded string to decode. /// A new object from the parsed string. /// A boolean indicating if the decode was successful. public static bool TryDecode(string value, out Guid obj) { try { // Decode as Squid obj = DecodeSquid(value); return true; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception) #pragma warning restore CA1031 // Do not catch general exception types { // Return empty Guid obj = Guid.Empty; return false; } } /// /// Tries to parse the given string value and /// outputs the object. /// /// The Squid encoded string or string representation of a Guid. /// A new object from the parsed string. /// A boolean indicating if the parse was successful. public static bool TryParse(string value, out Squid obj) { // Parse as Squid string. if (TryDecode(value, out var oGuid)) { obj = oGuid; return true; } // Parse as Guid string. if (Guid.TryParse(value, out oGuid)) { obj = oGuid; return true; } obj = Empty; return false; } /// /// Tries to parse the string value and /// outputs the underlying object. /// /// The Squid encoded string or string representation of a Guid. /// A new object from the parsed string. /// A boolean indicating if the parse was successful. public static bool TryParse(string value, out Guid obj) { // Try a Squid string. if (TryDecode(value, out obj)) return true; // Try a Guid string. if (Guid.TryParse(value, out obj)) return true; obj = Guid.Empty; return false; } #region Operators /// /// Determines if both Squid objects have the same /// underlying value. /// /// The x. /// The y. /// The result of the operator. public static bool operator ==(Squid x, Squid y) { return x.Guid == y.Guid; } /// /// Determines if both objects have the same /// underlying value. /// /// The x. /// The y. /// The result of the operator. public static bool operator ==(Squid x, Guid y) { return x.Guid == y; } /// /// Determines if both objects have the same /// underlying value. /// /// The x. /// The y. /// The result of the operator. public static bool operator ==(Guid x, Squid y) { return y == x; // NB: order of arguments } /// /// Determines if both Squid objects do not have the same /// underlying value. /// /// The x. /// The y. /// The result of the operator. public static bool operator !=(Squid x, Squid y) { return !(x == y); } /// /// Determines if both objects do not have the same /// underlying value. /// /// The x. /// The y. /// The result of the operator. public static bool operator !=(Squid x, Guid y) { return !(x == y); } /// /// Determines if both objects do not have the same /// underlying value. /// /// The x. /// The y. /// The result of the operator. public static bool operator !=(Guid x, Squid y) { return !(x == y); } /// /// Implicitly converts the Squid to /// its string equivalent. /// /// The o squid. /// The result of the conversion. public static implicit operator string(Squid oSquid) { return oSquid.Value; } /// /// Implicitly converts the Squid to /// its equivalent. /// /// The o squid. /// The result of the conversion. public static implicit operator Guid(Squid oSquid) { return oSquid.Guid; } /// /// Implicitly converts the string to a Squid. /// /// The value. /// The result of the conversion. public static implicit operator Squid(string value) { if (string.IsNullOrEmpty(value)) return Empty; return TryParse(value, out Squid oSquid) ? oSquid : Empty; } /// /// Implicitly converts the to a Squid. /// /// The o unique identifier. /// The result of the conversion. public static implicit operator Squid(Guid oGuid) { return oGuid == Guid.Empty ? Empty : new Squid(oGuid); } #endregion } }