Skip to main content

Represents a globally unique identifier (GUID) with a shorter string value.

using System;

namespace PKISharp.WACS.DomainObjects
{
    /// <summary>
    /// Represents a globally unique identifier (GUID) with a
    /// shorter string value. Sguid
    /// Taken from https://www.singular.co.nz/2007/12/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp/
    /// </summary>
    public struct ShortGuid
    {
        #region Static

        /// <summary>
        /// A read-only instance of the ShortGuid class whose value
        /// is guaranteed to be all zeroes.
        /// </summary>
        public static readonly ShortGuid Empty = new(Guid.Empty);

        #endregion

        #region Fields

        private Guid _guid;
        private string _value;

        #endregion

        #region Contructors

        /// <summary>
        /// Creates a ShortGuid from a base64 encoded string
        /// </summary>
        /// <param name="value">The encoded guid as a
        /// base64 string</param>
        public ShortGuid(string value)
        {
            _value = value;
            _guid = Decode(value);
        }

        /// <summary>
        /// Creates a ShortGuid from a Guid
        /// </summary>
        /// <param name="guid">The Guid to encode</param>
        public ShortGuid(Guid guid)
        {
            _value = Encode(guid);
            _guid = guid;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets/sets the underlying Guid
        /// </summary>
        public Guid Guid
        {
            get => _guid;
            set
            {
                if (value != _guid)
                {
                    _guid = value;
                    _value = Encode(value);
                }
            }
        }

        /// <summary>
        /// Gets/sets the underlying base64 encoded string
        /// </summary>
        public string Value
        {
            get => _value;
            set
            {
                if (value != _value)
                {
                    _value = value;
                    _guid = Decode(value);
                }
            }
        }

        #endregion

        #region ToString

        /// <summary>
        /// Returns the base64 encoded guid as a string
        /// </summary>
        /// <returns></returns>
        public override string ToString() => _value;

        #endregion

        #region Equals

        /// <summary>
        /// Returns a value indicating whether this instance and a
        /// specified Object represent the same type and value.
        /// </summary>
        /// <param name="obj">The object to compare</param>
        /// <returns></returns>
        public override bool Equals(object? obj)
        {
            if (obj is ShortGuid shortGuid)
            {
                return _guid.Equals(shortGuid._guid);
            }
            if (obj is Guid guid)
            {
                return _guid.Equals(guid);
            }
            if (obj is string)
            {
                return _guid.Equals(((ShortGuid)obj)._guid);
            }
            return false;
        }

        #endregion

        #region GetHashCode

        /// <summary>
        /// Returns the HashCode for underlying Guid.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode() => _guid.GetHashCode();

        #endregion

        #region NewGuid

        /// <summary>
        /// Initialises a new instance of the ShortGuid class
        /// </summary>
        /// <returns></returns>
        public static ShortGuid NewGuid() => new(Guid.NewGuid());

        #endregion

        #region Encode

        /// <summary>
        /// Creates a new instance of a Guid using the string value,
        /// then returns the base64 encoded version of the Guid.
        /// </summary>
        /// <param name="value">An actual Guid string (i.e. not a ShortGuid)</param>
        /// <returns></returns>
        public static string Encode(string value)
        {
            var guid = new Guid(value);
            return Encode(guid);
        }

        /// <summary>
        /// Encodes the given Guid as a base64 string that is 22
        /// characters long.
        /// </summary>
        /// <param name="guid">The Guid to encode</param>
        /// <returns></returns>
        public static string Encode(Guid guid)
        {
            var encoded = Convert.ToBase64String(guid.ToByteArray());
            encoded = encoded
                .Replace("/", "_")
                .Replace("+", "-");
            return encoded.Substring(0, 22);
        }

        #endregion

        #region Decode

        /// <summary>
        /// Decodes the given base64 string
        /// </summary>
        /// <param name="value">The base64 encoded string of a Guid</param>
        /// <returns>A new Guid</returns>
        public static Guid Decode(string value)
        {
            value = value
                .Replace("_", "/")
                .Replace("-", "+");
            var buffer = Convert.FromBase64String(value + "==");
            return new Guid(buffer);
        }

        #endregion

        #region Operators

        /// <summary>
        /// Determines if both ShortGuids have the same underlying
        /// Guid value.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static bool operator ==(ShortGuid x, ShortGuid y)
        {
            if ((object)x == null)
            {
                return (object)y == null;
            }

            return x._guid == y._guid;
        }

        /// <summary>
        /// Determines if both ShortGuids do not have the
        /// same underlying Guid value.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static bool operator !=(ShortGuid x, ShortGuid y) => !(x == y);

        /// <summary>
        /// Implicitly converts the ShortGuid to it's string equivilent
        /// </summary>
        /// <param name="shortGuid"></param>
        /// <returns></returns>
        public static implicit operator string(ShortGuid shortGuid) => shortGuid._value;

        /// <summary>
        /// Implicitly converts the ShortGuid to it's Guid equivilent
        /// </summary>
        /// <param name="shortGuid"></param>
        /// <returns></returns>
        public static implicit operator Guid(ShortGuid shortGuid) => shortGuid._guid;

        /// <summary>
        /// Implicitly converts the string to a ShortGuid
        /// </summary>
        /// <param name="shortGuid"></param>
        /// <returns></returns>
        public static implicit operator ShortGuid(string shortGuid) => new(shortGuid);

        /// <summary>
        /// Implicitly converts the Guid to a ShortGuid
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        public static implicit operator ShortGuid(Guid guid) => new(guid);

        #endregion
    }
}

// ----

// Using the ShortGuid
// The ShortGuid is compatible with normal Guid's and other ShortGuid strings. Let's see an example:

Guid guid = Guid.NewGuid();
ShortGuid sguid1 = guid; // implicitly cast the guid as a shortguid
Console.WriteLine(sguid1);
Console.WriteLine(sguid1.Guid);

// This produces a new guid, uses that guid to create a ShortGuid, and displays the two equivalent values in the console. Results would be something along the lines of:
// FEx1sZbSD0ugmgMAF_RGHw b1754c14-d296-4b0f-a09a-030017f4461f

// Or you can implicitly cast a string to a ShortGuid as well.
string code = "Xy0MVKupFES9NpmZ9TiHcw";
ShortGuid sguid2 = code; // implicitly cast the string as a shortguid
Console.WriteLine(sguid2);
Console.WriteLine(sguid2.Guid);

// Which produces the following:
// Xy0MVKupFES9NpmZ9TiHcw
// 540c2d5f-a9ab-4414-bd36-9999f5388773

// Flexible with your other data types
// The ShortGuid is made to be easily used with the different types, so you can simplify your code. Take note of the following examples:

// for a new ShortGuid, just like Guid.NewGuid()
ShortGuid sguid = ShortGuid.NewGuid();

// to cast the string "myString" as a ShortGuid,
string myString = "Xy0MVKupFES9NpmZ9TiHcw";

// the following 3 lines are equivalent
ShortGuid sguid = new ShortGuid(myString); // traditional
ShortGuid sguid = (ShortGuid)myString;     // explicit cast
ShortGuid sguid = myString;                // implicit cast

// Likewise, to cast the Guid "myGuid" as a ShortGuid
Guid myGuid = new Guid("540c2d5f-a9ab-4414-bd36-9999f5388773");

// the following 3 lines are equivalents
ShortGuid sguid = new ShortGuid(myGuid); // traditional
ShortGuid sguid = (ShortGuid)myGuid;     // explicit cast
ShortGuid sguid = myGuid;                // implicit cast

// After you've created your ShortGuid's the 3 members of most interest are the
// original Guid value, the new short string (the short encoded guid string),
// and the ToString() method, which also returns the short encoded guid string.

sguid.Guid;       // gets the Guid part
sguid.Value;      // gets the encoded Guid as a string
sguid.ToString(); // same as sguid.Value
Easy comparison with guid's and strings

// You can also do equals comparison against the three types, Guid, string and
// ShortGuid like in the following example:

Guid myGuid = new Guid("540c2d5f-a9ab-4414-bd36-9999f5388773");
ShortGuid sguid = (ShortGuid)"Xy0MVKupFES9NpmZ9TiHcw";

if (sguid == myGuid)
	// logic if guid and sguid are equal

if (sguid == "Xy0MVKupFES9NpmZ9TiHcw")
	// logic if string and sguid are equal