using System; namespace GisConverter.Lib.Models { /// /// Represents the outcome of a conversion operation. /// /// /// /// is an immutable value object intended to be returned by converter /// implementations and propagated between layers (for example from the conversion library back to /// the CLI). It contains: /// /// /// — the logical result code (Success/Failure). /// — a short, human-friendly message suitable for logs and CLI output. /// — creation time in UTC for diagnostics and ordering. /// /// /// /// Construction /// - Use the factory helpers and to create /// instances. These helpers normalize the message and set the timestamp consistently. /// /// /// /// Immutability & thread-safety /// - Instances are immutable after construction and therefore safe to share between threads. /// - The type exposes no mutating members. /// /// /// /// Equality semantics /// - Implements to compare logical results. /// - Equality is based on and using ordinal comparison. /// - is intentionally excluded from equality because it is diagnostic /// metadata (creation time) rather than part of the logical outcome. /// - If a caller needs timestamp-aware equality, implement a custom comparer or wrap this type /// rather than changing global semantics. /// /// /// /// Usage guidance /// - Return from converters when the operation completed and produced /// expected outputs. Keep messages concise (tests often assert on substrings). /// - Return for failures; include clear diagnostic tokens suitable for logs /// and CI debugging. Avoid exposing secrets in messages. /// /// public sealed class ConversionResult : IEquatable { /// /// The status of the conversion. /// public ConversionStatus Status { get; } /// /// Human-friendly message describing the result. Empty string when not provided. /// Use this to surface concise diagnostics to CLI users or logs. /// /// /// Keep messages brief and stable; tests and automation often match on case-insensitive /// substrings rather than exact full-text matches. Avoid embedding environment-specific /// paths or transient identifiers unless necessary. /// public string Message { get; } /// /// UTC timestamp when the result instance was created. /// /// /// This value is intended for diagnostics (correlation, ordering) and is not considered /// in logical equality. For deterministic tests avoid asserting on this value or provide /// a test seam that controls timestamps. /// public DateTime TimestampUtc { get; } /// /// True when the conversion succeeded ( is ). /// public bool IsSuccess => Status == ConversionStatus.Success; private ConversionResult(ConversionStatus status, string message) { Status = status; Message = message ?? string.Empty; TimestampUtc = DateTime.UtcNow; } /// /// Create a success result with an optional message. /// /// Optional human-friendly message; null is treated as empty. /// A new with == . /// /// Use this factory from converters and hosts to represent successful conversions. /// Example: return ConversionResult.Success("Wrote 3 files"); /// public static ConversionResult? Success(string? message = null) => new ConversionResult(ConversionStatus.Success, message ?? string.Empty); /// /// Create a failure result with an optional message describing the error. /// /// Optional error message; null is treated as empty. /// A new with == . /// /// Provide concise diagnostic text suitable for logs and CI failure analysis. Prefer stable /// tokens that tests and automation can match against. /// Example: return ConversionResult.Failure("Input archive missing .shp file"); /// public static ConversionResult? Failure(string? message = null) => new ConversionResult(ConversionStatus.Failure, message ?? string.Empty); /// /// Returns a compact textual representation useful for logs. /// /// String in the form "{Status}: {Message}". public override string ToString() => $"{Status}: {Message}"; /// /// Strongly-typed equality comparing logical result values. /// - Compares and (ordinal). /// - Does not consider in equality. /// /// Other to compare with. /// true when logically equal; otherwise false. public bool Equals(ConversionResult? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Status == other.Status && string.Equals(Message, other.Message, StringComparison.Ordinal); } /// /// Object equality override delegates to strongly-typed equality. /// public override bool Equals(object? obj) => obj is ConversionResult other && Equals(other); /// /// Hash code based on the logical equality components. /// /// /// Combines and (ordinal) into the hash. The timestamp is /// intentionally excluded so logically-equal results share the same hash code. /// public override int GetHashCode() { unchecked { // combine Status and Message (ordinal) into hash int hash = 17; hash = hash * 31 + Status.GetHashCode(); hash = hash * 31 + (Message != null ? StringComparer.Ordinal.GetHashCode(Message) : 0); return hash; } } /// /// Equality operator uses the same semantics as . /// public static bool operator ==(ConversionResult? left, ConversionResult? right) { if (ReferenceEquals(left, right)) return true; if (ReferenceEquals(left, null)) return false; return left.Equals(right); } /// /// Inequality operator. /// public static bool operator !=(ConversionResult? left, ConversionResult? right) => !(left == right); } /// /// Status codes for . /// public enum ConversionStatus { /// Conversion completed successfully. Success = 0, /// Conversion failed (see for details). Failure = 1 } }