TextExtractionOutcome.java

package net.sasasin.sreader.service.extraction;

import java.util.Objects;
import java.util.Optional;
import net.sasasin.sreader.service.outcome.OperationFailure;
import net.sasasin.sreader.service.outcome.OutcomePreconditions;

/** Outcome of extracting article or feed full text. */
public sealed interface TextExtractionOutcome
    permits TextExtractionOutcome.Extracted,
        TextExtractionOutcome.NoContent,
        TextExtractionOutcome.Skipped,
        TextExtractionOutcome.Failed {

  /**
   * Successful extraction. Optional {@link #pagination()} carries AutoPagerize metadata when the
   * method used multi-page tracking (or single-page fallback after rule miss). {@link
   * #extractedUrl()} is the first-page final/extracted URL when known.
   */
  record Extracted(
      String text,
      ExtractionDecision decision,
      Optional<PaginationMetadata> pagination,
      Optional<String> extractedUrl)
      implements TextExtractionOutcome {

    public Extracted(String text, ExtractionDecision decision) {
      this(text, decision, Optional.empty(), Optional.empty());
    }

    public Extracted(
        String text, ExtractionDecision decision, Optional<PaginationMetadata> pagination) {
      this(text, decision, pagination, Optional.empty());
    }

    public Extracted {
      text = OutcomePreconditions.requireNonBlank(text, "text");
      Objects.requireNonNull(decision, "decision must not be null");
      Objects.requireNonNull(pagination, "pagination must not be null");
      Objects.requireNonNull(extractedUrl, "extractedUrl must not be null");
      extractedUrl = extractedUrl.map(String::trim).filter(s -> !s.isEmpty());
    }

    public Extracted withPagination(PaginationMetadata metadata) {
      return new Extracted(text, decision, Optional.of(metadata), extractedUrl);
    }

    public Extracted withExtractedUrl(String url) {
      return new Extracted(text, decision, pagination, Optional.of(url));
    }
  }

  record NoContent(
      NoContentReason reason, ExtractionDecision decision, Optional<PaginationMetadata> pagination)
      implements TextExtractionOutcome {

    public NoContent(NoContentReason reason, ExtractionDecision decision) {
      this(reason, decision, Optional.empty());
    }

    public NoContent {
      Objects.requireNonNull(reason, "reason must not be null");
      Objects.requireNonNull(decision, "decision must not be null");
      Objects.requireNonNull(pagination, "pagination must not be null");
    }

    public NoContent withPagination(PaginationMetadata metadata) {
      return new NoContent(reason, decision, Optional.of(metadata));
    }
  }

  record Skipped(TextExtractionSkipReason reason) implements TextExtractionOutcome {
    public Skipped {
      Objects.requireNonNull(reason, "reason must not be null");
    }
  }

  /**
   * Extraction failure. Optional {@link #pagination()} carries partial page traces when
   * AutoPagerize failed after loading one or more pages (never used for successful partial text
   * persistence).
   */
  record Failed(OperationFailure failure, Optional<PaginationMetadata> pagination)
      implements TextExtractionOutcome {
    public Failed(OperationFailure failure) {
      this(failure, Optional.empty());
    }

    public Failed {
      Objects.requireNonNull(failure, "failure must not be null");
      Objects.requireNonNull(pagination, "pagination must not be null");
    }

    public Failed withPagination(PaginationMetadata metadata) {
      return new Failed(failure, Optional.of(metadata));
    }
  }
}