FeedTomlIssue.java

package net.sasasin.sreader.service.feed.toml;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

/**
 * Unified diagnostic for feed TOML import. Kind separates syntax (TomlJ), schema shape/types, and
 * domain conversion.
 */
record FeedTomlIssue(
    Kind kind, FeedTomlPosition position, String path, String message, int sequence) {

  enum Kind {
    SYNTAX,
    SCHEMA,
    DOMAIN
  }

  private static final Comparator<FeedTomlIssue> ORDER =
      Comparator.comparing((FeedTomlIssue issue) -> issue.position().isKnown() ? 0 : 1)
          .thenComparingInt(issue -> issue.position().lineOrMax())
          .thenComparingInt(issue -> issue.position().columnOrMax())
          .thenComparingInt(issue -> issue.kind().ordinal())
          .thenComparingInt(FeedTomlIssue::sequence);

  FeedTomlIssue {
    Objects.requireNonNull(kind, "kind must not be null");
    Objects.requireNonNull(position, "position must not be null");
    if (message == null || message.isBlank()) {
      throw new IllegalArgumentException("message must not be blank");
    }
  }

  static FeedTomlIssue syntax(FeedTomlPosition position, String message, int sequence) {
    return new FeedTomlIssue(Kind.SYNTAX, position, null, message, sequence);
  }

  static FeedTomlIssue schema(
      FeedTomlPosition position, String path, String message, int sequence) {
    return new FeedTomlIssue(Kind.SCHEMA, position, path, message, sequence);
  }

  static FeedTomlIssue domain(
      FeedTomlPosition position, String path, String message, int sequence) {
    return new FeedTomlIssue(Kind.DOMAIN, position, path, message, sequence);
  }

  String render() {
    // message already carries the stable public wording (including path when relevant).
    if (!position.isKnown()) {
      return message;
    }
    return "line "
        + position.line().getAsInt()
        + ", column "
        + position.column().getAsInt()
        + ": "
        + message;
  }

  static List<FeedTomlIssue> sortedCopy(List<FeedTomlIssue> issues) {
    List<FeedTomlIssue> copy = new ArrayList<>(Objects.requireNonNull(issues, "issues"));
    copy.sort(ORDER);
    return List.copyOf(copy);
  }

  static List<String> renderAll(List<FeedTomlIssue> issues) {
    return sortedCopy(issues).stream().map(FeedTomlIssue::render).toList();
  }
}