namespace AvParser.Core.Parsing; /// A recoverable problem with a single record. Parsing continues after one of these. /// 1-based position of the offending item: a line, or a place in a listing. /// What went wrong, phrased for a user rather than a developer. public sealed record ParseError(int Index, string Message) { /// /// Stable identifier for the kind of failure, or when the message is /// the only thing on offer. /// /// /// The domain stays language-free: it produces an English message plus a code, and the UI /// translates Collect.Error.{Code} with , falling back to /// . Without this, a Russian UI would still print English error text — /// and moving the strings themselves into the domain would drag localisation down there. /// public string? Code { get; init; } /// Values to substitute into the translated message. public IReadOnlyList Arguments { get; init; } = []; /// What the failure was about — the address or the line it came from. /// /// Deliberately outside and : a translated template /// has fixed placeholders, so an address cannot be appended to it without editing every /// translation. The live collection log prints this beside the translated text, which is the /// difference between "the site answered 404" and knowing which of ten thousand ids that was. /// public string? Subject { get; init; } /// What the attempt went through — a proxy address — when it went through anything. /// /// Worth carrying for the same reason as : "the site answered 404" and "the /// request timed out" mean very different things depending on whether the pool handed out a proxy /// it had confirmed or one it had never spoken to, and without this the difference is invisible. /// public string? Via { get; init; } /// Creates an error carrying a translation code. public static ParseError Create(int index, string code, string message, params object?[] arguments) => new(index, message) { Code = code, Arguments = arguments }; /// public override string ToString() => $"#{Index}: {Message}"; }