// ***********************************************************************
// Assembly : FCS.Lib.Utility
// Author : FH
// Created : 2020-07-01
//
// Last Modified By : FH
// Last Modified On : 02-24-2022
// ***********************************************************************
//
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU 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
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
// Derived from https:github.com/csharpvitamins/CSharpVitamins.ShortGuid
// ***********************************************************************
using System;
using System.Diagnostics;
namespace FCS.Lib.Utility
{
///
/// 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
}
}