PaginationMetadata.java

package net.sasasin.sreader.service.extraction;

import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import net.sasasin.sreader.service.autopagerize.PaginationStopReason;

/**
 * AutoPagerize pagination metadata carried with extraction outcomes. DB persistence stores a
 * subset; full page URL traces are for probe diagnostics only.
 */
public record PaginationMetadata(
    long datasetId,
    String datasetSha256,
    int importerVersion,
    boolean explicitDatasetSelection,
    Optional<Integer> ruleOrdinal,
    Optional<String> ruleName,
    Optional<String> urlPattern,
    Optional<String> nextLinkXpath,
    Optional<String> pageElementXpath,
    int pageCount,
    PaginationStopReason stopReason,
    boolean complete,
    List<PaginationPageTrace> pages,
    Optional<URI> failedRequestedUrl,
    List<PageTextContribution> contributions) {

  public PaginationMetadata {
    Objects.requireNonNull(datasetSha256, "datasetSha256 must not be null");
    if (!datasetSha256.matches("^[0-9a-f]{64}$")) {
      throw new IllegalArgumentException("datasetSha256 must be 64 lowercase hex characters");
    }
    if (importerVersion < 1) {
      throw new IllegalArgumentException("importerVersion must be >= 1");
    }
    Objects.requireNonNull(ruleOrdinal, "ruleOrdinal must not be null");
    Objects.requireNonNull(ruleName, "ruleName must not be null");
    Objects.requireNonNull(urlPattern, "urlPattern must not be null");
    Objects.requireNonNull(nextLinkXpath, "nextLinkXpath must not be null");
    Objects.requireNonNull(pageElementXpath, "pageElementXpath must not be null");
    if (pageCount < 0) {
      throw new IllegalArgumentException("pageCount must be >= 0");
    }
    Objects.requireNonNull(stopReason, "stopReason must not be null");
    Objects.requireNonNull(pages, "pages must not be null");
    Objects.requireNonNull(failedRequestedUrl, "failedRequestedUrl must not be null");
    Objects.requireNonNull(contributions, "contributions must not be null");
    pages = List.copyOf(pages);
    contributions = List.copyOf(contributions);
  }

  public Optional<URI> lastPageFinalUrl() {
    if (pages.isEmpty()) {
      return Optional.empty();
    }
    return Optional.of(pages.getLast().finalUri());
  }

  public long totalBytes() {
    return pages.stream().mapToLong(PaginationPageTrace::byteSize).sum();
  }
}