using EdjCase.JsonRpc.Core; using System; using System.Collections.Generic; using System.Linq; namespace EdjCase.JsonRpc.Router { /// /// Represents the url path for Rpc routing purposes /// public struct RpcPath : IEquatable { /// /// Default/Empty path /// public static RpcPath Default => new RpcPath(); /// /// Path components split on forward slashes /// private readonly string[] componentsValue; private int? hashCodeCache; /// Uri components for the path private RpcPath(string[] components = null) { this.componentsValue = components ?? new string[0]; this.hashCodeCache = null; } public static bool operator ==(RpcPath path1, RpcPath path2) { return path1.Equals(path2); } public static bool operator !=(RpcPath path1, RpcPath path2) { return !path1.Equals(path2); } public bool StartsWith(RpcPath other) { if ((other.componentsValue?.Length ?? 0) == 0) { return true; } if ((this.componentsValue?.Length ?? 0) == 0) { return false; } if (other.componentsValue.Length > this.componentsValue.Length) { return false; } for (int i = 0; i < other.componentsValue.Length; i++) { string component = this.componentsValue[i]; string otherComponent = other.componentsValue[i]; if (!string.Equals(component, otherComponent)) { return false; } } return true; } public bool Equals(RpcPath other) { return this.GetHashCode() == other.GetHashCode(); } public override bool Equals(object obj) { if (obj is RpcPath path) { return this.Equals(path); } return false; } public override int GetHashCode() { //TODO best way to optimize gethashcode? multithread? if (this.hashCodeCache == null) { int hash; if (this.componentsValue == null || this.componentsValue.Length == 0) { hash = 0; } else { hash = 1337; foreach (string component in this.componentsValue) { hash = (hash * 7) + component.GetHashCode(); } } this.hashCodeCache = hash; } return this.hashCodeCache.Value; } /// /// Creates a based on the string form of the path /// /// Uri/route path /// Rpc path based on the path string public static RpcPath Parse(string path) { if (!RpcPath.TryParse(path, out RpcPath rpcPath)) { throw new RpcException(RpcErrorCode.ParseError, $"Rpc path could not be parsed from '{path}'."); } return rpcPath; } /// /// Creates a based on the string form of the path /// /// Uri/route path /// True if the path parses, otherwise false public static bool TryParse(string path, out RpcPath rpcPath) { if (string.IsNullOrWhiteSpace(path)) { rpcPath = new RpcPath(); return true; } else { try { string[] pathComponents = path .ToLowerInvariant() .Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); rpcPath = new RpcPath(pathComponents); return true; } catch { rpcPath = default; return false; } } } /// /// Removes the base path path from this path /// /// Base path to remove /// A new path that is the full path without the base path public RpcPath RemoveBasePath(RpcPath basePath) { if (!this.TryRemoveBasePath(basePath, out RpcPath path)) { throw new RpcException(RpcErrorCode.ParseError, $"Count not remove path '{basePath}' from path '{this}'."); } return path; } /// /// Tries to remove the base path path from this path /// /// Base path to remove /// True if removed the base path. Otherwise false public bool TryRemoveBasePath(RpcPath basePath, out RpcPath path) { if (basePath == default) { path = this.Clone(); return true; } if (!this.StartsWith(basePath)) { path = default; return false; } var newComponents = new string[this.componentsValue.Length - basePath.componentsValue.Length]; if (newComponents.Length > 0) { Array.Copy(this.componentsValue, basePath.componentsValue.Length, newComponents, 0, newComponents.Length); } path = new RpcPath(newComponents); return true; } /// /// Merges the two paths to create a new Rpc path that is the combination of the two /// /// Other path to add to the end of the current path /// A new path that is the combination of the two paths public RpcPath Add(RpcPath other) { if (other.componentsValue == null) { return this.Clone(); } if (this.componentsValue == null) { return other.Clone(); } int componentCount = this.componentsValue.Length + other.componentsValue.Length; string[] newComponents = new string[componentCount]; this.componentsValue.CopyTo(newComponents, 0); other.componentsValue.CopyTo(newComponents, this.componentsValue.Length); return new RpcPath(newComponents); } public override string ToString() { if (this.componentsValue == null) { return "/"; } return "/" + string.Join("/", this.componentsValue); } public RpcPath Clone() { if (this.componentsValue == null || this.componentsValue.Length == 0) { return new RpcPath(); } int componentCount = this.componentsValue.Length; string[] newComponents = new string[componentCount]; this.componentsValue.CopyTo(newComponents, 0); return new RpcPath(newComponents); } public static implicit operator string(RpcPath path) { return path.ToString(); } public static implicit operator RpcPath(string s) { return RpcPath.Parse(s); } } }