Skip to content

refactor(metadata): improve hardcover edition selection#1800

Open
sabrina553 wants to merge 89 commits into
grimmory-tools:developfrom
sabrina553:feat/hardcover-best-edition-dev
Open

refactor(metadata): improve hardcover edition selection#1800
sabrina553 wants to merge 89 commits into
grimmory-tools:developfrom
sabrina553:feat/hardcover-best-edition-dev

Conversation

@sabrina553

@sabrina553 sabrina553 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Description

Improve overall functionality of the hardcover parser. For faster and more consistent, reliable results, and fixes issue with
hardcover reading tracker

Linked Issue

#1284

Changes

  • new searchbyID uses lists for multiple isbns/hcid's, retrives metadata score+reading format, reduce api calls
  • improved booksearch query, uses weights, filters, fields etc to improve initial search for title/author only
  • introduce levenshtiendistance calculations for titles/alternative-titles/author names to further filter, search results, and align best-results, including for non-english titles.
  • further filtering of editions based on PRIMARY file format (audiobook/not audiobook) , and users locale.default for languages. format filter will improve compatibility with hardcover reading tracker.. where audiobook isbn's will not correlate to pages read on hc website.
  • a general restructuring of the relevant code to improve readability and where relevant introduced new tests, updated, or removed if otherwise no longer relevant

Manual Testing Steps

  1. Manually testing each test where updates were needed
  2. Testing a sample library of ~200 books by title only, with a success rate of 99.5%, this increases to 100% when author is included also

Additional Context (Optional)

This is a good start in regards to the general improvements here and the feasibility of using hardcover as p1 provider, and also introducing "guardrails." However there are still inadequacies mostly on the end of hc's data. But Specifically regarding these changes. I think extra thought should go toward the filtering editions section...

Using the user's locale is not the be all and end all to searching languages, and their will be plenty of edge cases. Looking at manga. Where this method isn't sufficient.

Furthermore, The use of alternative titles, and matching therein, does work wholly. It isn't so straightforward after this point, to return the given title and potential foreign-language/edition specific results, like the description. There is some work to weight this up between hc md quality, and

With the audiobook working via the primary file only it doesn't register when the audiobook is there, but not a primary file. this isn't a massive to fix, but again requires some further consideration with implementation

AI Disclosure

Qwen-coder: local model for checking over my code.

Checklist

  • This PR links and implements an accepted issue.
  • This PR is a single focused change.
  • There are new or updated tests validating this change.
  • I ran just ui check and just api check.
  • I have added screenshots if there were any UI changes.
  • I have disclosed any AI usage as per the organization AI Policy above.
  • I understand all of my submitted changes.

Summary by CodeRabbit

  • Refactor
    • Improved hardcover metadata retrieval with a prioritized flow (ISBN first, then title+author, then title-only) and distance-based matching for better result ranking.
    • Enhanced edition selection by requested file format (including audiobook handling) and locale language.
    • Improved metadata normalization and mapping consistency (including ISBN normalization) and derived ISBN-based provider lookup from ISBN-13 settings.
  • Bug Fixes
    • Invalid release/publish dates are now omitted from returned metadata.
  • Tests
    • Refreshed and expanded hardcover parser/search tests using deterministic, fixture-based data, including edition filtering coverage.

sabrina553 and others added 30 commits June 1, 2026 10:34
Introduces `HardcoverWorkResponse` and `HardcoverWorkDetails` classes to handle metadata parsing for hardcover editions.
Implements logic to retrieve ISBN-10 and ISBN-13 using `HardcoverWorkDetails` and update results dynamically during metadata parsing.
…itions by title and author

Implements GraphQL query to retrieve ISBN-10 and ISBN-13 for hardcover editions and returns the top result based on score.
Determine if result is the same as the search term, or if it only contains it, and if so does the author match then this is the Highest quality result, and we can skip further parsing.

Once best Result is select, determine the language to filter
…First (main) Author from that result, and the language, and reading format as filter.

If returns a valid result update the result object with the correct isbn details.

Otherwise return oldResults (no processing applied as a fallback)
…r, language, and reading format. returns isbn
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

HardcoverParser is refactored from a document-centric flow into a book/editions-centric search pipeline: ISBN-first → title+author → title-only, with Levenshtein-based filtering, audiobook-format exclusion, and locale-based edition narrowing. HardcoverBookSearchService switches to list-based ISBN/HCID lookups and parameterized searchBooks overloads. GraphQLResponse gains Levenshtein scoring and edition format/score fields. BookMetadataService switches provider chain derivation from title to ISBN-13. Tests, fixtures, and service configuration are updated throughout.

Changes

Hardcover Metadata Search and Mapping Refactor

Layer / File(s) Summary
GraphQL model extensions and HardcoverBookSearchService API changes
backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java, backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
GraphQLResponse.Document gains levenshteinDistanceTitle and levenshteinDistanceAuthor fields; GraphQLResponse.Edition gains readingFormatId and score with @JsonProperty mappings; Lombok imports made explicit. HardcoverBookSearchService switches ISBN lookup to list-based searchBookByIsbn and new searchBookByHcid, refactors searchBooks into parameterized title/author/limit overloads, updates GraphQL queries to use _in filters with score-ordered editions, removes fetchBookDetails, and lowers INITIAL_DELAY_MS to 1000.
HardcoverParser search pipeline and Levenshtein filtering
backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java (search entry and strategies)
fetchMetadata is rewired to a fixed sequence: searchByIsbn (cleaned) → searchByTitle (title+author, fallback to title-only). filterSearch converts hits to documents and applies filterTitle/filterAuthor using LevenshteinDistance scoring and length-derived thresholds, sorted by title distance. searchById extracts document IDs, queries hardcover by ID, and reorders results to match original document order.
HardcoverParser edition filtering, metadata mapping, and provider chain configuration
backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java (filtering and mapping), backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
filterEditionsByFormat excludes audiobook editions when appropriate (readingFormatId == 2); filterEditionsByLanguage keeps editions matching default locale. mapBookToMetadata populates BookMetadata from BookWithEditions and selected Edition, covering hardcover IDs, title/subtitle, series, rating, release-date, review counts, cached moods/categories/tags, contributors, ISBN 10↔13 derivation, language, publisher, thumbnail URL, and provider. BookMetadataService.deriveProviderChainFromSettings() switches from title field provider to ISBN-13 field provider, affecting which provider sequence is used for ISBN-based metadata lookups.
HardcoverParserTest, HardcoverBookSearchServiceTest, fixture updates, and metadata service test configuration
backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java, backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java, backend/src/test/resources/hardcover/example-results-author.json.fixture, backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
HardcoverParserTest is rebuilt with MockedStatic<Jsoup>, explicit locale setup/cleanup, fixture-driven helpers, and updated stubs matching list-based searchBookByIsbn/searchBookByHcid signatures; new Edition Filtering Tests cover audiobook format and locale-based filtering. HardcoverBookSearchServiceTest removes the fetchBookDetails nested suite and repositions setupAppSettingsWithToken. Fixture adds single-book data.books structure with contributor/edition/image data. BookMetadataServiceTest consolidates imports and updates test setup to use ISBN-13 field provider instead of title field provider.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant HardcoverParser
    participant HardcoverBookSearchService
    participant GraphQLAPI

    Caller->>HardcoverParser: fetchMetadata(request)
    alt ISBN present
        HardcoverParser->>HardcoverBookSearchService: searchBookByIsbn(List~String~)
        HardcoverBookSearchService->>GraphQLAPI: GraphQL query (_in isbn/hcid, score desc)
        GraphQLAPI-->>HardcoverBookSearchService: List~BookWithEditions~
        HardcoverBookSearchService-->>HardcoverParser: List~BookWithEditions~
    else No ISBN or empty result
        HardcoverParser->>HardcoverBookSearchService: searchBooks(title, author)
        HardcoverBookSearchService->>GraphQLAPI: parameterized searchBooks query
        GraphQLAPI-->>HardcoverBookSearchService: List~Hit~
        HardcoverBookSearchService-->>HardcoverParser: List~Hit~
        HardcoverParser->>HardcoverParser: filterSearch (Levenshtein title+author scoring)
        HardcoverParser->>HardcoverBookSearchService: searchBookByHcid(List~Integer~)
        HardcoverBookSearchService->>GraphQLAPI: GraphQL query (_in hcid)
        GraphQLAPI-->>HardcoverBookSearchService: List~BookWithEditions~
        HardcoverBookSearchService-->>HardcoverParser: List~BookWithEditions~
    end
    HardcoverParser->>HardcoverParser: filterEditionsByFormat (audiobook vs other)
    HardcoverParser->>HardcoverParser: filterEditionsByLanguage (locale match)
    HardcoverParser->>HardcoverParser: mapBookToMetadata (series, rating, ISBN, contributors, moods)
    HardcoverParser-->>Caller: List~BookMetadata~
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • grimmory-tools/grimmory#87: Both PRs modify Hardcover metadata mapping by changing how series fields are derived and populated in HardcoverParser and their backing GraphQLResponse models.
  • grimmory-tools/grimmory#1206: Both PRs touch backend/.../HardcoverBookSearchService.java; the retrieved PR refactors GraphQL variable construction imports, while the main PR rewrites the same search/GraphQL request flow.

Suggested reviewers

  • balazs-szucs
  • imnotjames
  • zachyale
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'refactor(metadata): improve hardcover edition selection' follows conventional commit format with type (refactor) and scope (metadata), accurately summarizing the main changes to hardcover edition filtering and selection logic.
Description check ✅ Passed The PR description includes all required sections: Description, Linked Issue (#1284), Changes (detailed), Manual Testing Steps, Additional Context, AI Disclosure, and a completed Checklist. The description comprehensively documents the refactoring work and acknowledges known limitations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Warning

Review ran into problems

🔥 Problems

Linked repositories: Your configuration references 1 linked repositories, but your current plan allows 0. Analyzed ``, skipped grimmory-tools/grimmory-docs.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java`:
- Around line 167-168: The methods getAuthorNames() on line 167, getTitle() on
line 206, and getUsersCount() on line 223 are being called without null checks
on their parent objects, which can cause NullPointerException at runtime when
upstream fields are null. Add null checks before each of these method calls to
ensure the parent object (doc) and any intermediate objects in the call chain
are not null before dereferencing them.
- Around line 421-424: The code in the series mapping section dereferences
getSeries() without null-checking its return value, which can cause a
NullPointerException when a partial payload lacks series metadata. Add an
additional null check after calling getSeries() on the result of
getFeaturedBookSeries() to ensure that getSeries() does not return null before
invoking getName() and getPrimaryBooksCount() on it. This will guard against
null series objects in the metadata payload.
- Around line 191-198: The conditional logic in the ternary operator that checks
whether best is less than threshold is inverted, causing low-quality author
matches to be included when a good match exists. Swap the two branches of the
ternary operator so that when best is less than threshold (indicating a good
match exists), the method returns the filtered docs stream using the Levenshtein
distance filter, and when best is greater than or equal to threshold (no good
match), it returns all docs unfiltered. This ensures poor author matches are
filtered out when better matches are available.
- Around line 302-313: The language filtering logic in the stream operation
incorrectly retrieves editions from the first book in results using
results.getFirst().getEditions() instead of from the current book being
processed. Replace results.getFirst().getEditions() with result.getEditions() in
the filter operation to ensure each book's editions are filtered individually
rather than all books being filtered against the first book's editions.
- Around line 52-57: In the searchByIsbn method, the null check on line 56 is
checking if the isbnCleaned list is null, but the list itself is always non-null
since it's created on line 53. The actual issue is that ParserUtils.cleanIsbn
might return null, which gets added to the list and triggers unnecessary remote
queries. Instead, check if the cleaned ISBN returned from ParserUtils.cleanIsbn
is null before adding it to the list, and if it is null, return
Collections.emptyList() immediately to prevent the avoidable failure path.

In
`@backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java`:
- Around line 56-67: The setUp method mutates global state by setting the
default locale to ENGLISH at line 61, but the tearDown method does not restore
the original locale, which can cause test suite flakiness. Store the original
default locale before calling Locale.setDefault(Locale.ENGLISH) in the setUp
method as a class field, then restore it in the tearDown method after closing
the mockJsoup static mock. This ensures that any locale changes made during test
execution are properly cleaned up and do not affect other tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78b8fffc-158c-40ca-acc9-5c64d3aa8186

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc48a8 and db56ae2.

📒 Files selected for processing (11)
  • backend/src/main/java/org/booklore/model/enums/BookCategory.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/test/resources/hardcover/example-results-author.json.fixture
  • backend/src/test/resources/hardcover/example-results-lamb.json.fixture
  • backend/src/test/resources/hardcover/example-results-purged.json.fixture
  • backend/src/test/resources/hardcover/example-results.json.fixture
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (6)
**/*

⚙️ CodeRabbit configuration file

**/*: This project is being developed using current and future-facing technologies:

  • Java 25 with --enable-preview (preview features are INTENTIONAL and encouraged)
  • Spring Boot 4 (latest major version, check APIs accordingly)
  • Jackson 3 (new package: tools.jackson.* instead of com.fasterxml.jackson.*)
  • Hibernate 7.3.x (Jakarta Persistence 3.2, new APIs; avoid deprecated Hibernate 5/6 patterns)
  • Angular 21 (signals-based reactivity, no NgModules unless legacy)

Grimmory Internal Tools

Metadata Standards and Compliance

  • For all metadata writing and parsing logic, double-check against Dublin Core and ANSI standards to ensure perfect official compliance.
  • We strictly follow the widespread and official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.

General Java and Spring rules

  • ALWAYS prefer modern, idiomatic Java 25 constructs over legacy patterns.
  • Preview features (--enable-preview) are enabled and intentional; do NOT flag them as risky unless there is a concrete runtime issue.
  • Prefer: records, sealed classes/interfaces, pattern matching (switch expressions, instanceof), structured concurrency (StructuredTaskScope), scoped values, string templates, unnamed patterns/variables.
  • Prefer virtual threads (Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor()) over platform threads for I/O-bound work.
  • Prefer the new Sequenced Collections API (SequencedCollection, SequencedMap) where applicable.
  • Prefer var for local variables when the type is obvious from context.
  • Use stream().toList() instead of stream().collect(Collectors.toList()) for imm...

Files:

  • backend/src/test/resources/hardcover/example-results-author.json.fixture
  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
backend/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

backend/src/**/*.java: Use 4-space indentation and match surrounding Java style in backend code
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection in backend code
Use MapStruct for entity/DTO mapping in backend code
Keep JPA entities on the *Entity suffix in backend code

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
backend/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required in backend code

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/service/**/*.java

⚙️ CodeRabbit configuration file

**/service/**/*.java: Spring Framework 7 service layer review:

  • Flag missing @Transactional on methods that perform multiple writes.
  • Prefer constructor injection over @Autowired field injection. Use @AllArgsConstructor.
  • Use ApiError enum for throwing exceptions.
  • Flag checked exceptions swallowed silently; must log or rethrow.
  • Flag Thread.sleep(); prefer Duration-based overloads or ScheduledExecutorService.
  • Prefer virtual threads (Thread.ofVirtual()) for I/O-bound operations.
  • Flag mutable shared state in singleton beans.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/metadata/**/*.java

⚙️ CodeRabbit configuration file

**/metadata/**/*.java: Metadata and Sidecar review:

  • Ensure perfect official compliance with Dublin Core and ANSI standards.
  • Strictly follow official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.
  • Sidecar files must be written in a way that is compatible with widespread e-reader standards.
  • Avoid proprietary or non-standard XML tags unless absolutely necessary for internal tracking.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/*Test.java

⚙️ CodeRabbit configuration file

**/*Test.java: Java test review:

  • Prefer @ExtendWith(SpringExtension.class) or @SpringBootTest for integration tests.
  • Flag tests with no assertions.
  • Flag Thread.sleep() in tests; use Awaitility or virtual-thread-friendly alternatives.
  • Flag hardcoded ports or file paths.
  • Flag missing edge case coverage: null, empty, boundary values.
  • Prefer AssertJ over JUnit's built-in assertions for readability.
  • Prefer @Sql or Testcontainers for database state; not hand-rolled setup/teardown.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
🧠 Learnings (13)
📚 Learning: 2026-04-10T08:15:37.436Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 449
File: booklore-api/src/main/java/org/booklore/service/book/BookDownloadService.java:139-145
Timestamp: 2026-04-10T08:15:37.436Z
Learning: When using Spring `ContentDisposition.builder(...).filename(name, StandardCharsets.UTF_8).build()` (i.e., explicitly providing UTF-8), the resulting header value should include both the quoted `filename="=?UTF-8?..."` and the RFC 5987 `filename*=` parameters. In this case, any extra ASCII fallback computation (e.g., deriving an ASCII `fallbackFilename` via `NON_ASCII_PATTERN` and calling `.filename(fallbackFilename)`) is likely redundant—prefer calling only `.filename(fallbackName?, StandardCharsets.UTF_8)` as appropriate and let Spring handle the UTF-8 header parameters. Verify by comparing the emitted header for `filename` and `filename*` before deciding to keep an ASCII fallback.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-04-14T12:43:08.698Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 502
File: booklore-api/src/main/java/org/booklore/service/reader/ChapterCacheService.java:0-0
Timestamp: 2026-04-14T12:43:08.698Z
Learning: For this codebase (booklore-api), target Java 25 with `--enable-preview`, so `_` is intentionally used as an unnamed/ignored variable (e.g., lambda parameter or pattern variable) per Java’s preview feature JEP 456. Do not flag `_` in those contexts as an invalid/reserved identifier; only flag it if it’s used in a non-supported position (e.g., where an unnamed variable is not applicable for the Java preview rules).

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-07T21:21:55.233Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:0-0
Timestamp: 2026-05-07T21:21:55.233Z
Learning: When reviewing Java 23+ code, treat `java.time.Instant#until(Instant endExclusive)` as a valid API/method call (it returns a `Duration`, equivalent to `Duration.between(this, endExclusive)`). Do not flag `instant.until(otherInstant)` as a compile error or API misuse when the project targets Java 25+ (as in grimmory-tools/grimmory); the call should be considered correct and returns a `Duration`.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-08T06:19:20.621Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1201
File: backend/src/main/java/org/booklore/model/dto/AccessTokenDto.java:3-10
Timestamp: 2026-05-08T06:19:20.621Z
Learning: For Jackson 3 codebases, do not treat imports from `com.fasterxml.jackson.annotation.*` (e.g., `JsonInclude`, `JsonProperty`, `JsonView`) as incorrect. In Jackson 3, `jackson-annotations` intentionally remains under `com.fasterxml.jackson.annotation.*` for backward compatibility, while only the core processing packages (e.g., `jackson-core`, `jackson-databind`) move to the `tools.jackson.*` namespace.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-04-27T15:25:55.042Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 930
File: backend/src/test/java/org/booklore/service/metadata/parser/ComicvineBookParserTest.java:68-75
Timestamp: 2026-04-27T15:25:55.042Z
Learning: In this repository’s JUnit 5 test sources (e.g., under backend/src/test/java/), do not flag “bare” `assert` statements as a bug. The project test runner is configured to always execute tests with assertions enabled (e.g., `-ea`), so `assert` behavior is consistent. Continue to review for correctness, but don’t treat unguarded `assert` usage in test classes as a static-analysis issue.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-22T03:20:45.559Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1446
File: backend/src/test/java/org/booklore/service/metadata/MetadataManagementServiceTest.java:465-470
Timestamp: 2026-05-22T03:20:45.559Z
Learning: In backend unit tests (Java files under backend/src/test/java), do not flag hardcoded placeholder filesystem paths (e.g., setting LibraryPathEntity.path = "/example") as violations when the value is intentionally unused for filesystem access. Only suppress the "no hardcoded paths" concern if the test does not perform any filesystem/network IO using that path (no reads/writes/Files.* calls or code paths that access the filesystem with that value); if the placeholder is actually used to touch the filesystem, flag it.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-04T05:01:33.919Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1081
File: backend/src/test/java/org/booklore/service/metadata/parser/AudibleParserTest.java:132-135
Timestamp: 2026-05-04T05:01:33.919Z
Learning: In grimmory-tools/grimmory unit/integration test classes (e.g., files matching **/*Test.java under backend/src/test/java/**), it is acceptable to directly instantiate Jackson mappers (e.g., `new ObjectMapper()` / `new JsonMapper(...)`) and this should NOT be flagged. The Jackson guidance to use Spring bean injection or `JsonMapper.shared()` applies only to production code; tests may construct dependencies directly as standard practice. Production-code mapper instantiation rules should still be enforced outside test sources.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-04T20:31:11.075Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1086
File: backend/src/main/java/org/booklore/service/metadata/BookReviewUpdateService.java:63-66
Timestamp: 2026-05-04T20:31:11.075Z
Learning: For this repository, reviewers should treat string truncation done via `String.length()` and `String.substring(0, maxLength)` (UTF-16 code units) as an accepted, consistent convention. Do not flag individual occurrences of this pattern as bugs, even though it is not code-point-aware for surrogate pairs. A separate global effort is already tracked to move toward code-point-aware truncation, so per-site fixes should be avoided during code review.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-13T12:34:49.607Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1293
File: backend/src/main/java/org/booklore/service/metadata/DuckDuckGoCoverService.java:246-252
Timestamp: 2026-05-13T12:34:49.607Z
Learning: In this repo’s Java code, when catching Jsoup `org.jsoup.HttpStatusException` (and similar exceptions originating from external libraries) and wrapping/rethrowing them, do not require preserving the original exception stack trace (e.g., as flagged by PMD `PreserveStackTrace`) as long as the application already captures the actionable diagnostics in logs or the thrown exception message (such as HTTP status code and the requested URL). Reviewers should still ensure the log/message contains those details; the intent is to avoid noisy stack traces that only reflect external-library internals rather than application code.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-17T13:38:16.462Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1366
File: backend/src/main/java/org/booklore/service/FileStreamingService.java:65-66
Timestamp: 2026-05-17T13:38:16.462Z
Learning: In the grimmory-tools/grimmory repo, it’s an accepted pattern to pass the raw AccessDeniedException.getMessage() (even if it may include filesystem path details) into ApiError.PERMISSION_DENIED.createException(...). During code review, do not raise a security/information-disclosure issue solely based on that exception message being propagated to the API when using ApiError.PERMISSION_DENIED.createException with the AccessDeniedException message.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-23T23:01:25.769Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1456
File: backend/src/main/java/org/booklore/service/metadata/parser/GoodReadsParser.java:618-630
Timestamp: 2026-05-23T23:01:25.769Z
Learning: In this codebase (grimmory-tools/grimmory), it’s intentional to omit per-request timeouts on individual Java HttpRequest.Builder instances (e.g., GoodReadsParser.fetchJson). During reviews, do not flag missing builder-level timeouts as a best-practice violation; rely on framework-level and/or HttpClient-level timeouts configured elsewhere for consistent behavior. Only raise an issue if you can verify that no effective timeout is configured at the HttpClient/framework level.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-06-12T01:10:31.416Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1724
File: backend/src/main/java/org/booklore/repository/BookRepository.java:58-59
Timestamp: 2026-06-12T01:10:31.416Z
Learning: In this codebase (grimmory-tools/grimmory), reviews should not treat inline `LIMIT`/`OFFSET` clauses inside `Query` JPQL/HQL strings as a JPA compliance risk. This is intentional: `hibernate.jpa.compliance.query=true` is intentionally not set, and Hibernate 7.3+ supports `LIMIT`/`OFFSET` as valid HQL extensions. Therefore, do not flag or require changes to `Query` annotations solely due to `LIMIT`/`OFFSET` usage.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-07T21:37:46.988Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:121-123
Timestamp: 2026-05-07T21:37:46.988Z
Learning: In grimmory-tools/grimmory service-layer code, if arithmetic overflow occurs inside inference/business-logic (e.g., when deriving inferred fields like durationSeconds from start/end timestamps), treat it as a server-side anomaly. Prefer letting the global exception handler translate it into a generic 5xx response rather than throwing an explicit ApiError 4xx (e.g., do not convert overflow into a client error).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
🪛 PMD (7.25.0)
backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java

[Medium] 48-48: UnusedPrivateField (Best Practices): Avoid unused private fields such as 'log'.

(UnusedPrivateField (Best Practices))

🔇 Additional comments (5)
backend/src/main/java/org/booklore/service/metadata/MetadataRefreshService.java (1)

31-31: LGTM!

backend/src/test/resources/hardcover/example-results-author.json.fixture (1)

1-62: LGTM!

backend/src/test/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchServiceTest.java (1)

31-40: LGTM!

backend/src/main/java/org/booklore/service/metadata/parser/hardcover/GraphQLResponse.java (1)

5-8: LGTM!

Also applies to: 166-167, 245-249

backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java (1)

38-50: LGTM!

Also applies to: 122-140, 149-163, 219-219

Comment thread backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java Outdated
Comment thread backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java Outdated
Comment thread backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java Outdated
@coderabbitai coderabbitai Bot added the feature label Jun 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java (3)

280-280: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Potential NPE if getBookType() returns null.

Line 276 guards against null primaryFile, but if getBookType() returns null, calling .equals() on it will throw NPE.

-        boolean isAudiobook = book.getPrimaryFile().getBookType().equals(BookFileType.AUDIOBOOK);
+        boolean isAudiobook = BookFileType.AUDIOBOOK.equals(book.getPrimaryFile().getBookType());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java`
at line 280, The code at line 280 calls `.equals()` on the result of
`getBookType()` without checking if it returns null, which will cause a null
pointer exception. Add a null check for the `getBookType()` result before
calling `.equals()` on it in the isAudiobook assignment. Consider using a
null-safe approach such as checking if getBookType() is not null before
comparing, or using Optional if available in your codebase.

341-341: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Duplicate series mapping—mapSeiesData and mapSeriesInfo both called.

Both methods set seriesName, seriesTotal, and seriesNumber from the same book.getFeaturedBookSeries() source. This redundantly overwrites the same fields twice. Additionally, mapSeiesData has a typo in its name.

Remove one of these methods and its invocation.

Suggested fix

Keep only mapSeriesInfo (which has the null-safe guards) and remove the duplicate:

-        mapSeiesData(metadata, book);
         mapRating(metadata, book);

Then delete the entire mapSeiesData method (lines 389-403).

Also applies to: 351-351

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java`
at line 341, Remove the duplicate series mapping by deleting all invocations of
the misnamed method mapSeiesData (which appear at lines 341 and 351 in the
HardcoverParser class) and then delete the entire mapSeiesData method definition
(lines 389-403). Keep only the mapSeriesInfo method invocation and its
implementation, as it contains proper null-safe guards and achieves the same
result without redundancy.

331-333: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Subtitle removal uses inconsistent sources.

The condition checks edition.getSubtitle(), but the replacement removes book.getSubtitle(). If these differ, the replacement will fail to match or remove the wrong text.

         if (edition.getSubtitle() != null && book.getTitle().contains(edition.getSubtitle())) {
-            book.setTitle(book.getTitle().replace(": " + book.getSubtitle(), ""));
+            book.setTitle(book.getTitle().replace(": " + edition.getSubtitle(), ""));
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java`
around lines 331 - 333, The subtitle removal logic in the HardcoverParser class
contains an inconsistency where the condition checks edition.getSubtitle() but
the replacement uses book.getSubtitle(). If these two values differ, the
replacement will fail to match the expected text. Ensure consistency by using
the same subtitle source in both the condition and the replace operation.
Determine whether to use edition.getSubtitle() or book.getSubtitle() throughout
based on the intended logic, and update both the condition check and the
replacement call to use the same source.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java`:
- Around line 965-968: The Edition Filtering Tests in HardcoverParserTest are
providing an author parameter via .author("Ernest Cline") in the request but
only stubbing the single-argument hardcoverBookSearchService.searchBooks(String
title) method. When an author is present, the parser invokes the two-argument
searchBooks(title, author) method first, which is not stubbed and returns null,
causing the test to fall back to the single-argument method. To fix this and
make the tests directly exercise the intended code path, add a stub for the
two-argument method by calling
when(hardcoverBookSearchService.searchBooks("Ready Player One", "Ernest
Cline")).thenReturn(hit) before the existing single-argument stub. Apply this
change to all affected test methods at the specified line ranges: 965-968,
1000-1003, 1034-1037, and 1069-1072.

---

Outside diff comments:
In
`@backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java`:
- Line 280: The code at line 280 calls `.equals()` on the result of
`getBookType()` without checking if it returns null, which will cause a null
pointer exception. Add a null check for the `getBookType()` result before
calling `.equals()` on it in the isAudiobook assignment. Consider using a
null-safe approach such as checking if getBookType() is not null before
comparing, or using Optional if available in your codebase.
- Line 341: Remove the duplicate series mapping by deleting all invocations of
the misnamed method mapSeiesData (which appear at lines 341 and 351 in the
HardcoverParser class) and then delete the entire mapSeiesData method definition
(lines 389-403). Keep only the mapSeriesInfo method invocation and its
implementation, as it contains proper null-safe guards and achieves the same
result without redundancy.
- Around line 331-333: The subtitle removal logic in the HardcoverParser class
contains an inconsistency where the condition checks edition.getSubtitle() but
the replacement uses book.getSubtitle(). If these two values differ, the
replacement will fail to match the expected text. Ensure consistency by using
the same subtitle source in both the condition and the replace operation.
Determine whether to use edition.getSubtitle() or book.getSubtitle() throughout
based on the intended logic, and update both the condition check and the
replacement call to use the same source.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2fd3e14a-781d-47c9-8a1c-762420b49d4c

📥 Commits

Reviewing files that changed from the base of the PR and between db56ae2 and f9da5a3.

📒 Files selected for processing (2)
  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (6)
backend/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

backend/src/**/*.java: Use 4-space indentation and match surrounding Java style in backend code
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection in backend code
Use MapStruct for entity/DTO mapping in backend code
Keep JPA entities on the *Entity suffix in backend code

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/*

⚙️ CodeRabbit configuration file

**/*: This project is being developed using current and future-facing technologies:

  • Java 25 with --enable-preview (preview features are INTENTIONAL and encouraged)
  • Spring Boot 4 (latest major version, check APIs accordingly)
  • Jackson 3 (new package: tools.jackson.* instead of com.fasterxml.jackson.*)
  • Hibernate 7.3.x (Jakarta Persistence 3.2, new APIs; avoid deprecated Hibernate 5/6 patterns)
  • Angular 21 (signals-based reactivity, no NgModules unless legacy)

Grimmory Internal Tools

Metadata Standards and Compliance

  • For all metadata writing and parsing logic, double-check against Dublin Core and ANSI standards to ensure perfect official compliance.
  • We strictly follow the widespread and official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.

General Java and Spring rules

  • ALWAYS prefer modern, idiomatic Java 25 constructs over legacy patterns.
  • Preview features (--enable-preview) are enabled and intentional; do NOT flag them as risky unless there is a concrete runtime issue.
  • Prefer: records, sealed classes/interfaces, pattern matching (switch expressions, instanceof), structured concurrency (StructuredTaskScope), scoped values, string templates, unnamed patterns/variables.
  • Prefer virtual threads (Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor()) over platform threads for I/O-bound work.
  • Prefer the new Sequenced Collections API (SequencedCollection, SequencedMap) where applicable.
  • Prefer var for local variables when the type is obvious from context.
  • Use stream().toList() instead of stream().collect(Collectors.toList()) for imm...

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/service/**/*.java

⚙️ CodeRabbit configuration file

**/service/**/*.java: Spring Framework 7 service layer review:

  • Flag missing @Transactional on methods that perform multiple writes.
  • Prefer constructor injection over @Autowired field injection. Use @AllArgsConstructor.
  • Use ApiError enum for throwing exceptions.
  • Flag checked exceptions swallowed silently; must log or rethrow.
  • Flag Thread.sleep(); prefer Duration-based overloads or ScheduledExecutorService.
  • Prefer virtual threads (Thread.ofVirtual()) for I/O-bound operations.
  • Flag mutable shared state in singleton beans.

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/metadata/**/*.java

⚙️ CodeRabbit configuration file

**/metadata/**/*.java: Metadata and Sidecar review:

  • Ensure perfect official compliance with Dublin Core and ANSI standards.
  • Strictly follow official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.
  • Sidecar files must be written in a way that is compatible with widespread e-reader standards.
  • Avoid proprietary or non-standard XML tags unless absolutely necessary for internal tracking.

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
backend/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required in backend code

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/*Test.java

⚙️ CodeRabbit configuration file

**/*Test.java: Java test review:

  • Prefer @ExtendWith(SpringExtension.class) or @SpringBootTest for integration tests.
  • Flag tests with no assertions.
  • Flag Thread.sleep() in tests; use Awaitility or virtual-thread-friendly alternatives.
  • Flag hardcoded ports or file paths.
  • Flag missing edge case coverage: null, empty, boundary values.
  • Prefer AssertJ over JUnit's built-in assertions for readability.
  • Prefer @Sql or Testcontainers for database state; not hand-rolled setup/teardown.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
🧠 Learnings (13)
📚 Learning: 2026-04-10T08:15:37.436Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 449
File: booklore-api/src/main/java/org/booklore/service/book/BookDownloadService.java:139-145
Timestamp: 2026-04-10T08:15:37.436Z
Learning: When using Spring `ContentDisposition.builder(...).filename(name, StandardCharsets.UTF_8).build()` (i.e., explicitly providing UTF-8), the resulting header value should include both the quoted `filename="=?UTF-8?..."` and the RFC 5987 `filename*=` parameters. In this case, any extra ASCII fallback computation (e.g., deriving an ASCII `fallbackFilename` via `NON_ASCII_PATTERN` and calling `.filename(fallbackFilename)`) is likely redundant—prefer calling only `.filename(fallbackName?, StandardCharsets.UTF_8)` as appropriate and let Spring handle the UTF-8 header parameters. Verify by comparing the emitted header for `filename` and `filename*` before deciding to keep an ASCII fallback.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-04-14T12:43:08.698Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 502
File: booklore-api/src/main/java/org/booklore/service/reader/ChapterCacheService.java:0-0
Timestamp: 2026-04-14T12:43:08.698Z
Learning: For this codebase (booklore-api), target Java 25 with `--enable-preview`, so `_` is intentionally used as an unnamed/ignored variable (e.g., lambda parameter or pattern variable) per Java’s preview feature JEP 456. Do not flag `_` in those contexts as an invalid/reserved identifier; only flag it if it’s used in a non-supported position (e.g., where an unnamed variable is not applicable for the Java preview rules).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-07T21:21:55.233Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:0-0
Timestamp: 2026-05-07T21:21:55.233Z
Learning: When reviewing Java 23+ code, treat `java.time.Instant#until(Instant endExclusive)` as a valid API/method call (it returns a `Duration`, equivalent to `Duration.between(this, endExclusive)`). Do not flag `instant.until(otherInstant)` as a compile error or API misuse when the project targets Java 25+ (as in grimmory-tools/grimmory); the call should be considered correct and returns a `Duration`.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-08T06:19:20.621Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1201
File: backend/src/main/java/org/booklore/model/dto/AccessTokenDto.java:3-10
Timestamp: 2026-05-08T06:19:20.621Z
Learning: For Jackson 3 codebases, do not treat imports from `com.fasterxml.jackson.annotation.*` (e.g., `JsonInclude`, `JsonProperty`, `JsonView`) as incorrect. In Jackson 3, `jackson-annotations` intentionally remains under `com.fasterxml.jackson.annotation.*` for backward compatibility, while only the core processing packages (e.g., `jackson-core`, `jackson-databind`) move to the `tools.jackson.*` namespace.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-04T20:31:11.075Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1086
File: backend/src/main/java/org/booklore/service/metadata/BookReviewUpdateService.java:63-66
Timestamp: 2026-05-04T20:31:11.075Z
Learning: For this repository, reviewers should treat string truncation done via `String.length()` and `String.substring(0, maxLength)` (UTF-16 code units) as an accepted, consistent convention. Do not flag individual occurrences of this pattern as bugs, even though it is not code-point-aware for surrogate pairs. A separate global effort is already tracked to move toward code-point-aware truncation, so per-site fixes should be avoided during code review.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
📚 Learning: 2026-05-13T12:34:49.607Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1293
File: backend/src/main/java/org/booklore/service/metadata/DuckDuckGoCoverService.java:246-252
Timestamp: 2026-05-13T12:34:49.607Z
Learning: In this repo’s Java code, when catching Jsoup `org.jsoup.HttpStatusException` (and similar exceptions originating from external libraries) and wrapping/rethrowing them, do not require preserving the original exception stack trace (e.g., as flagged by PMD `PreserveStackTrace`) as long as the application already captures the actionable diagnostics in logs or the thrown exception message (such as HTTP status code and the requested URL). Reviewers should still ensure the log/message contains those details; the intent is to avoid noisy stack traces that only reflect external-library internals rather than application code.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
📚 Learning: 2026-05-17T13:38:16.462Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1366
File: backend/src/main/java/org/booklore/service/FileStreamingService.java:65-66
Timestamp: 2026-05-17T13:38:16.462Z
Learning: In the grimmory-tools/grimmory repo, it’s an accepted pattern to pass the raw AccessDeniedException.getMessage() (even if it may include filesystem path details) into ApiError.PERMISSION_DENIED.createException(...). During code review, do not raise a security/information-disclosure issue solely based on that exception message being propagated to the API when using ApiError.PERMISSION_DENIED.createException with the AccessDeniedException message.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
📚 Learning: 2026-05-23T23:01:25.769Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1456
File: backend/src/main/java/org/booklore/service/metadata/parser/GoodReadsParser.java:618-630
Timestamp: 2026-05-23T23:01:25.769Z
Learning: In this codebase (grimmory-tools/grimmory), it’s intentional to omit per-request timeouts on individual Java HttpRequest.Builder instances (e.g., GoodReadsParser.fetchJson). During reviews, do not flag missing builder-level timeouts as a best-practice violation; rely on framework-level and/or HttpClient-level timeouts configured elsewhere for consistent behavior. Only raise an issue if you can verify that no effective timeout is configured at the HttpClient/framework level.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
📚 Learning: 2026-06-12T01:10:31.416Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1724
File: backend/src/main/java/org/booklore/repository/BookRepository.java:58-59
Timestamp: 2026-06-12T01:10:31.416Z
Learning: In this codebase (grimmory-tools/grimmory), reviews should not treat inline `LIMIT`/`OFFSET` clauses inside `Query` JPQL/HQL strings as a JPA compliance risk. This is intentional: `hibernate.jpa.compliance.query=true` is intentionally not set, and Hibernate 7.3+ supports `LIMIT`/`OFFSET` as valid HQL extensions. Therefore, do not flag or require changes to `Query` annotations solely due to `LIMIT`/`OFFSET` usage.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
📚 Learning: 2026-05-07T21:37:46.988Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:121-123
Timestamp: 2026-05-07T21:37:46.988Z
Learning: In grimmory-tools/grimmory service-layer code, if arithmetic overflow occurs inside inference/business-logic (e.g., when deriving inferred fields like durationSeconds from start/end timestamps), treat it as a server-side anomaly. Prefer letting the global exception handler translate it into a generic 5xx response rather than throwing an explicit ApiError 4xx (e.g., do not convert overflow into a client error).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java
📚 Learning: 2026-04-27T15:25:55.042Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 930
File: backend/src/test/java/org/booklore/service/metadata/parser/ComicvineBookParserTest.java:68-75
Timestamp: 2026-04-27T15:25:55.042Z
Learning: In this repository’s JUnit 5 test sources (e.g., under backend/src/test/java/), do not flag “bare” `assert` statements as a bug. The project test runner is configured to always execute tests with assertions enabled (e.g., `-ea`), so `assert` behavior is consistent. Continue to review for correctness, but don’t treat unguarded `assert` usage in test classes as a static-analysis issue.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-22T03:20:45.559Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1446
File: backend/src/test/java/org/booklore/service/metadata/MetadataManagementServiceTest.java:465-470
Timestamp: 2026-05-22T03:20:45.559Z
Learning: In backend unit tests (Java files under backend/src/test/java), do not flag hardcoded placeholder filesystem paths (e.g., setting LibraryPathEntity.path = "/example") as violations when the value is intentionally unused for filesystem access. Only suppress the "no hardcoded paths" concern if the test does not perform any filesystem/network IO using that path (no reads/writes/Files.* calls or code paths that access the filesystem with that value); if the placeholder is actually used to touch the filesystem, flag it.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-04T05:01:33.919Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1081
File: backend/src/test/java/org/booklore/service/metadata/parser/AudibleParserTest.java:132-135
Timestamp: 2026-05-04T05:01:33.919Z
Learning: In grimmory-tools/grimmory unit/integration test classes (e.g., files matching **/*Test.java under backend/src/test/java/**), it is acceptable to directly instantiate Jackson mappers (e.g., `new ObjectMapper()` / `new JsonMapper(...)`) and this should NOT be flagged. The Jackson guidance to use Spring bean injection or `JsonMapper.shared()` applies only to production code; tests may construct dependencies directly as standard practice. Production-code mapper instantiation rules should still be enforced outside test sources.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
🔇 Additional comments (8)
backend/src/main/java/org/booklore/service/metadata/parser/HardcoverParser.java (7)

54-58: LGTM!


167-169: LGTM!


194-198: LGTM!


206-208: LGTM!

Also applies to: 223-226


305-309: LGTM!


425-428: LGTM!


65-87: LGTM!

Also applies to: 89-120, 122-129, 131-161, 231-264, 266-272, 361-380, 382-387, 405-412, 414-422, 441-448, 450-457, 459-466, 468-488, 490-498, 500-509, 511-515, 517-521, 523-527

backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java (1)

49-50: LGTM!

Also applies to: 63-70

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java (2)

728-728: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Simplify by removing unnecessary stream conversion.

books is already a List, so .stream().toList() creates an unnecessary intermediate copy before calling forEach. You can call forEach directly on the list.

Simplified version
-        books.stream().toList().forEach(n -> n.setImage(null));
+        books.forEach(n -> n.setImage(null));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java`
at line 728, The line in HardcoverParserTest where
books.stream().toList().forEach is called creates an unnecessary intermediate
stream and list conversion. Since books is already a List, remove the
unnecessary stream() and toList() calls and invoke forEach directly on the books
list object. This simplifies the code by eliminating redundant conversions that
don't add any value.

949-985: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Complete the stubbing fix: add two-argument searchBooks stub.

This test provides an author (line 962) but only stubs the single-argument searchBooks("Ready Player One") method (line 967). The sibling tests in this nested class at lines 1000-1020, 1034-1055, and 1069-1089 were updated to stub the two-argument method when author is provided, but this test was missed.

Without the two-argument stub, the parser calls searchBooks(title, author) → returns null → falls back to searchBooks(title). The test passes but unintentionally exercises the search-fallback path rather than directly testing edition filtering.

Proposed fix matching sibling tests
         List<GraphQLResponse.Hit> hit = new ArrayList<>();
         hit.add(createHitWithAuthor("Ready Player One", "Ernest Cline", "26363"));
-        when(hardcoverBookSearchService.searchBooks("Ready Player One"))
+        when(hardcoverBookSearchService.searchBooks("Ready Player One", "Ernest Cline"))
                 .thenReturn(hit);

         String booksFixture = readFixture("example-results.json");
         List<GraphQLResponse.BookWithEditions> books = parseBooks(booksFixture);
 
         when(hardcoverBookSearchService.searchBookByHcid((List<Integer>) any()))
                 .thenReturn(books);
 
         // Act
         List<BookMetadata> results = parser.fetchMetadata(book, request);
 
         // Assert
-        verify(hardcoverBookSearchService).searchBooks("Ready Player One");
+        verify(hardcoverBookSearchService).searchBooks("Ready Player One", "Ernest Cline");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java`
around lines 949 - 985, The test fetchAudiobookEditions_whenFormat_returnsList
provides an author in the FetchMetadataRequest but only stubs the
single-argument searchBooks method. Add a stub for the two-argument searchBooks
method that accepts both title and author parameters (following the pattern of
the sibling test cases) right after the existing single-argument stub to ensure
the parser tests the intended code path with the author parameter instead of
falling back to the title-only search.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java`:
- Line 728: The line in HardcoverParserTest where
books.stream().toList().forEach is called creates an unnecessary intermediate
stream and list conversion. Since books is already a List, remove the
unnecessary stream() and toList() calls and invoke forEach directly on the books
list object. This simplifies the code by eliminating redundant conversions that
don't add any value.
- Around line 949-985: The test fetchAudiobookEditions_whenFormat_returnsList
provides an author in the FetchMetadataRequest but only stubs the
single-argument searchBooks method. Add a stub for the two-argument searchBooks
method that accepts both title and author parameters (following the pattern of
the sibling test cases) right after the existing single-argument stub to ensure
the parser tests the intended code path with the author parameter instead of
falling back to the title-only search.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 83193628-fe6d-4875-b6a6-e42d5ec9401a

📥 Commits

Reviewing files that changed from the base of the PR and between f9da5a3 and 50dd86a.

📒 Files selected for processing (1)
  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (6)
backend/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

backend/src/**/*.java: Use 4-space indentation and match surrounding Java style in backend code
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection in backend code
Use MapStruct for entity/DTO mapping in backend code
Keep JPA entities on the *Entity suffix in backend code

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
backend/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required in backend code

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/*

⚙️ CodeRabbit configuration file

**/*: This project is being developed using current and future-facing technologies:

  • Java 25 with --enable-preview (preview features are INTENTIONAL and encouraged)
  • Spring Boot 4 (latest major version, check APIs accordingly)
  • Jackson 3 (new package: tools.jackson.* instead of com.fasterxml.jackson.*)
  • Hibernate 7.3.x (Jakarta Persistence 3.2, new APIs; avoid deprecated Hibernate 5/6 patterns)
  • Angular 21 (signals-based reactivity, no NgModules unless legacy)

Grimmory Internal Tools

Metadata Standards and Compliance

  • For all metadata writing and parsing logic, double-check against Dublin Core and ANSI standards to ensure perfect official compliance.
  • We strictly follow the widespread and official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.

General Java and Spring rules

  • ALWAYS prefer modern, idiomatic Java 25 constructs over legacy patterns.
  • Preview features (--enable-preview) are enabled and intentional; do NOT flag them as risky unless there is a concrete runtime issue.
  • Prefer: records, sealed classes/interfaces, pattern matching (switch expressions, instanceof), structured concurrency (StructuredTaskScope), scoped values, string templates, unnamed patterns/variables.
  • Prefer virtual threads (Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor()) over platform threads for I/O-bound work.
  • Prefer the new Sequenced Collections API (SequencedCollection, SequencedMap) where applicable.
  • Prefer var for local variables when the type is obvious from context.
  • Use stream().toList() instead of stream().collect(Collectors.toList()) for imm...

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/service/**/*.java

⚙️ CodeRabbit configuration file

**/service/**/*.java: Spring Framework 7 service layer review:

  • Flag missing @Transactional on methods that perform multiple writes.
  • Prefer constructor injection over @Autowired field injection. Use @AllArgsConstructor.
  • Use ApiError enum for throwing exceptions.
  • Flag checked exceptions swallowed silently; must log or rethrow.
  • Flag Thread.sleep(); prefer Duration-based overloads or ScheduledExecutorService.
  • Prefer virtual threads (Thread.ofVirtual()) for I/O-bound operations.
  • Flag mutable shared state in singleton beans.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/metadata/**/*.java

⚙️ CodeRabbit configuration file

**/metadata/**/*.java: Metadata and Sidecar review:

  • Ensure perfect official compliance with Dublin Core and ANSI standards.
  • Strictly follow official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.
  • Sidecar files must be written in a way that is compatible with widespread e-reader standards.
  • Avoid proprietary or non-standard XML tags unless absolutely necessary for internal tracking.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
**/*Test.java

⚙️ CodeRabbit configuration file

**/*Test.java: Java test review:

  • Prefer @ExtendWith(SpringExtension.class) or @SpringBootTest for integration tests.
  • Flag tests with no assertions.
  • Flag Thread.sleep() in tests; use Awaitility or virtual-thread-friendly alternatives.
  • Flag hardcoded ports or file paths.
  • Flag missing edge case coverage: null, empty, boundary values.
  • Prefer AssertJ over JUnit's built-in assertions for readability.
  • Prefer @Sql or Testcontainers for database state; not hand-rolled setup/teardown.

Files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
🧠 Learnings (7)
📚 Learning: 2026-04-10T08:15:37.436Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 449
File: booklore-api/src/main/java/org/booklore/service/book/BookDownloadService.java:139-145
Timestamp: 2026-04-10T08:15:37.436Z
Learning: When using Spring `ContentDisposition.builder(...).filename(name, StandardCharsets.UTF_8).build()` (i.e., explicitly providing UTF-8), the resulting header value should include both the quoted `filename="=?UTF-8?..."` and the RFC 5987 `filename*=` parameters. In this case, any extra ASCII fallback computation (e.g., deriving an ASCII `fallbackFilename` via `NON_ASCII_PATTERN` and calling `.filename(fallbackFilename)`) is likely redundant—prefer calling only `.filename(fallbackName?, StandardCharsets.UTF_8)` as appropriate and let Spring handle the UTF-8 header parameters. Verify by comparing the emitted header for `filename` and `filename*` before deciding to keep an ASCII fallback.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-04-14T12:43:08.698Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 502
File: booklore-api/src/main/java/org/booklore/service/reader/ChapterCacheService.java:0-0
Timestamp: 2026-04-14T12:43:08.698Z
Learning: For this codebase (booklore-api), target Java 25 with `--enable-preview`, so `_` is intentionally used as an unnamed/ignored variable (e.g., lambda parameter or pattern variable) per Java’s preview feature JEP 456. Do not flag `_` in those contexts as an invalid/reserved identifier; only flag it if it’s used in a non-supported position (e.g., where an unnamed variable is not applicable for the Java preview rules).

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-07T21:21:55.233Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:0-0
Timestamp: 2026-05-07T21:21:55.233Z
Learning: When reviewing Java 23+ code, treat `java.time.Instant#until(Instant endExclusive)` as a valid API/method call (it returns a `Duration`, equivalent to `Duration.between(this, endExclusive)`). Do not flag `instant.until(otherInstant)` as a compile error or API misuse when the project targets Java 25+ (as in grimmory-tools/grimmory); the call should be considered correct and returns a `Duration`.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-08T06:19:20.621Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1201
File: backend/src/main/java/org/booklore/model/dto/AccessTokenDto.java:3-10
Timestamp: 2026-05-08T06:19:20.621Z
Learning: For Jackson 3 codebases, do not treat imports from `com.fasterxml.jackson.annotation.*` (e.g., `JsonInclude`, `JsonProperty`, `JsonView`) as incorrect. In Jackson 3, `jackson-annotations` intentionally remains under `com.fasterxml.jackson.annotation.*` for backward compatibility, while only the core processing packages (e.g., `jackson-core`, `jackson-databind`) move to the `tools.jackson.*` namespace.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-04-27T15:25:55.042Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 930
File: backend/src/test/java/org/booklore/service/metadata/parser/ComicvineBookParserTest.java:68-75
Timestamp: 2026-04-27T15:25:55.042Z
Learning: In this repository’s JUnit 5 test sources (e.g., under backend/src/test/java/), do not flag “bare” `assert` statements as a bug. The project test runner is configured to always execute tests with assertions enabled (e.g., `-ea`), so `assert` behavior is consistent. Continue to review for correctness, but don’t treat unguarded `assert` usage in test classes as a static-analysis issue.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-22T03:20:45.559Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1446
File: backend/src/test/java/org/booklore/service/metadata/MetadataManagementServiceTest.java:465-470
Timestamp: 2026-05-22T03:20:45.559Z
Learning: In backend unit tests (Java files under backend/src/test/java), do not flag hardcoded placeholder filesystem paths (e.g., setting LibraryPathEntity.path = "/example") as violations when the value is intentionally unused for filesystem access. Only suppress the "no hardcoded paths" concern if the test does not perform any filesystem/network IO using that path (no reads/writes/Files.* calls or code paths that access the filesystem with that value); if the placeholder is actually used to touch the filesystem, flag it.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java
📚 Learning: 2026-05-04T05:01:33.919Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1081
File: backend/src/test/java/org/booklore/service/metadata/parser/AudibleParserTest.java:132-135
Timestamp: 2026-05-04T05:01:33.919Z
Learning: In grimmory-tools/grimmory unit/integration test classes (e.g., files matching **/*Test.java under backend/src/test/java/**), it is acceptable to directly instantiate Jackson mappers (e.g., `new ObjectMapper()` / `new JsonMapper(...)`) and this should NOT be flagged. The Jackson guidance to use Spring bean injection or `JsonMapper.shared()` applies only to production code; tests may construct dependencies directly as standard practice. Production-code mapper instantiation rules should still be enforced outside test sources.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/parser/HardcoverParserTest.java

@imnotjames imnotjames changed the title Feat/hardcover best edition dev refactor(metadata): improve hardcover edition selection Jun 19, 2026
@imnotjames imnotjames self-requested a review June 19, 2026 12:41
@sabrina553

Copy link
Copy Markdown
Contributor Author

Applied and tested rabbits fixes, mostly null handling, biggest error with filterAuthor function, fixed this by flipping the "<" to ">" rather than it's own suggestion which seemed pointlessly long-winded, had to adjust some tests to account for this change and hence returned an empty list rather than previous behaviour which was to return the initially received "hits" . Which should lead to:

try title+author -> no author matches -> try title only.

where as prior it was just continuing with the same list of hits, rather than trying again, with a title only search

++

Fixed the nitpicks in the tests.

++

Ran one more test with the above changes. where Only searching by title, then following up with searching by, title+author for same 200 library sample..

Key takeaways:

total time - 556189ms (2.82s/book), with the initial PR I was getting around 489000ms (2.48s/book), Though I believe that might be accounted for with grabbing covers aswell, None of the recent commits really should have such a significant affect on performance.

Which is on-par with the code as I inherited it, if you don't account for the second time one would fetch, where it would grab all new information, totalling about (4s/book), which makes sense, most of the time is api (1 call per second), and both methods required about 2 calls. Now the Isbn search should only require one call, and the title/author still takes 2 calls, though these calls are now more refined, as is the logic within grimmory's code. Looking at about a 15-41% speed increase.

meanwhile accuracy is vastly improved where initally i was finding myself with 13 languages and 4 variations of english for my sample, it is now all "en" as expected..

Moreover, 173(/197) of these books sit within 70-79% (good) metadata score, from title search alone

  • 77% is the maximum value you can achieve with one provider and accounts for books in a seires, and 72% for standalone books.

20 of them sit in 50-69% (fair), these fall under a few categorises

  • limited hc md, being the modal, with these books mostly having a metadata score w/ hardcover of ~1600 for various reasons, we start to see HC doesn't have a cover image, or reviews, a designated language.

Finally there are 4 books in the weak (30-49%)

  • not much to say other than poor hc data, 3 don't have a cover. however all do have data, and do resolve automatically with ISBN's or atleast with a HCID and a link to their own page. Which ultimately means down the line p2->x they would resolve if (and very likely so the book can be found with another provider)

++

On covers, I didn't want to check, all of them. as this is both tedious and not something that can really be helped, genuinely i think the best fix would just to let some other provider take the wheel for covers. But of 100: 1 was "ugly", 12 were of poor quality, 2 were photographs, and 3 had none. I was also being very generous in saying so.

++

Further notes:

somethings i forgot to mention, but there definitely could also be some improvements with differentiating titles and subtitles. I did provide a little bit of logic in this regard, and i think if the "alternative" titles, multiple languages comes into full fruition it would be accounted for here. But it's very much a bit of a rabbit-hole and it's best imo to just get the barebones product released before being too nitpicky.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java (1)

141-149: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale ISBN lookup tests to configure fieldOptions.isbn13, not title.

Several LookupByIsbn tests still seed provider order via fieldOptions.title(...), but the service now derives the chain from fieldOptions.isbn13. These tests can pass via fallback and no longer validate intended provider ordering/dedup behavior. Please switch these setups to isbn13(...) (except explicit fallback tests), and keep one dedicated null-edge test for isbn13 == null.

As per coding guidelines: "Flag missing edge case coverage: null, empty, boundary values."

Also applies to: 196-205, 219-227, 275-285, 302-312

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java`
around lines 141 - 149, The LookupByIsbn test cases are configuring the provider
chain via fieldOptions.title() but the service now derives the provider order
from fieldOptions.isbn13(). Update the builder chain in the test setup to call
isbn13() instead of title() when configuring the FieldOptions.FieldProvider
within the MetadataRefreshOptions configuration. Apply this change to the test
methods mentioned (including the diff shown and those at lines 196-205, 219-227,
275-285, 302-312), and add one dedicated test case where isbn13 is left null to
ensure edge case coverage for the null scenario.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java`:
- Around line 141-149: The LookupByIsbn test cases are configuring the provider
chain via fieldOptions.title() but the service now derives the provider order
from fieldOptions.isbn13(). Update the builder chain in the test setup to call
isbn13() instead of title() when configuring the FieldOptions.FieldProvider
within the MetadataRefreshOptions configuration. Apply this change to the test
methods mentioned (including the diff shown and those at lines 196-205, 219-227,
275-285, 302-312), and add one dedicated test case where isbn13 is left null to
ensure edge case coverage for the null scenario.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a4204f6-61b3-4fd3-9fab-5cc79725e51c

📥 Commits

Reviewing files that changed from the base of the PR and between 50dd86a and 731aa84.

📒 Files selected for processing (2)
  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Test Suite / Backend Tests
🧰 Additional context used
📓 Path-based instructions (6)
backend/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

backend/src/**/*.java: Use 4-space indentation and match surrounding Java style in backend code
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection in backend code
Use MapStruct for entity/DTO mapping in backend code
Keep JPA entities on the *Entity suffix in backend code

Files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
**/*

⚙️ CodeRabbit configuration file

**/*: This project is being developed using current and future-facing technologies:

  • Java 25 with --enable-preview (preview features are INTENTIONAL and encouraged)
  • Spring Boot 4 (latest major version, check APIs accordingly)
  • Jackson 3 (new package: tools.jackson.* instead of com.fasterxml.jackson.*)
  • Hibernate 7.3.x (Jakarta Persistence 3.2, new APIs; avoid deprecated Hibernate 5/6 patterns)
  • Angular 21 (signals-based reactivity, no NgModules unless legacy)

Grimmory Internal Tools

Metadata Standards and Compliance

  • For all metadata writing and parsing logic, double-check against Dublin Core and ANSI standards to ensure perfect official compliance.
  • We strictly follow the widespread and official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.

General Java and Spring rules

  • ALWAYS prefer modern, idiomatic Java 25 constructs over legacy patterns.
  • Preview features (--enable-preview) are enabled and intentional; do NOT flag them as risky unless there is a concrete runtime issue.
  • Prefer: records, sealed classes/interfaces, pattern matching (switch expressions, instanceof), structured concurrency (StructuredTaskScope), scoped values, string templates, unnamed patterns/variables.
  • Prefer virtual threads (Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor()) over platform threads for I/O-bound work.
  • Prefer the new Sequenced Collections API (SequencedCollection, SequencedMap) where applicable.
  • Prefer var for local variables when the type is obvious from context.
  • Use stream().toList() instead of stream().collect(Collectors.toList()) for imm...

Files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
**/service/**/*.java

⚙️ CodeRabbit configuration file

**/service/**/*.java: Spring Framework 7 service layer review:

  • Flag missing @Transactional on methods that perform multiple writes.
  • Prefer constructor injection over @Autowired field injection. Use @AllArgsConstructor.
  • Use ApiError enum for throwing exceptions.
  • Flag checked exceptions swallowed silently; must log or rethrow.
  • Flag Thread.sleep(); prefer Duration-based overloads or ScheduledExecutorService.
  • Prefer virtual threads (Thread.ofVirtual()) for I/O-bound operations.
  • Flag mutable shared state in singleton beans.

Files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
**/metadata/**/*.java

⚙️ CodeRabbit configuration file

**/metadata/**/*.java: Metadata and Sidecar review:

  • Ensure perfect official compliance with Dublin Core and ANSI standards.
  • Strictly follow official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.
  • Sidecar files must be written in a way that is compatible with widespread e-reader standards.
  • Avoid proprietary or non-standard XML tags unless absolutely necessary for internal tracking.

Files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
backend/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required in backend code

Files:

  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
**/*Test.java

⚙️ CodeRabbit configuration file

**/*Test.java: Java test review:

  • Prefer @ExtendWith(SpringExtension.class) or @SpringBootTest for integration tests.
  • Flag tests with no assertions.
  • Flag Thread.sleep() in tests; use Awaitility or virtual-thread-friendly alternatives.
  • Flag hardcoded ports or file paths.
  • Flag missing edge case coverage: null, empty, boundary values.
  • Prefer AssertJ over JUnit's built-in assertions for readability.
  • Prefer @Sql or Testcontainers for database state; not hand-rolled setup/teardown.

Files:

  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
🧠 Learnings (13)
📚 Learning: 2026-04-10T08:15:37.436Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 449
File: booklore-api/src/main/java/org/booklore/service/book/BookDownloadService.java:139-145
Timestamp: 2026-04-10T08:15:37.436Z
Learning: When using Spring `ContentDisposition.builder(...).filename(name, StandardCharsets.UTF_8).build()` (i.e., explicitly providing UTF-8), the resulting header value should include both the quoted `filename="=?UTF-8?..."` and the RFC 5987 `filename*=` parameters. In this case, any extra ASCII fallback computation (e.g., deriving an ASCII `fallbackFilename` via `NON_ASCII_PATTERN` and calling `.filename(fallbackFilename)`) is likely redundant—prefer calling only `.filename(fallbackName?, StandardCharsets.UTF_8)` as appropriate and let Spring handle the UTF-8 header parameters. Verify by comparing the emitted header for `filename` and `filename*` before deciding to keep an ASCII fallback.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📚 Learning: 2026-04-14T12:43:08.698Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 502
File: booklore-api/src/main/java/org/booklore/service/reader/ChapterCacheService.java:0-0
Timestamp: 2026-04-14T12:43:08.698Z
Learning: For this codebase (booklore-api), target Java 25 with `--enable-preview`, so `_` is intentionally used as an unnamed/ignored variable (e.g., lambda parameter or pattern variable) per Java’s preview feature JEP 456. Do not flag `_` in those contexts as an invalid/reserved identifier; only flag it if it’s used in a non-supported position (e.g., where an unnamed variable is not applicable for the Java preview rules).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📚 Learning: 2026-05-07T21:21:55.233Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:0-0
Timestamp: 2026-05-07T21:21:55.233Z
Learning: When reviewing Java 23+ code, treat `java.time.Instant#until(Instant endExclusive)` as a valid API/method call (it returns a `Duration`, equivalent to `Duration.between(this, endExclusive)`). Do not flag `instant.until(otherInstant)` as a compile error or API misuse when the project targets Java 25+ (as in grimmory-tools/grimmory); the call should be considered correct and returns a `Duration`.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📚 Learning: 2026-05-08T06:19:20.621Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1201
File: backend/src/main/java/org/booklore/model/dto/AccessTokenDto.java:3-10
Timestamp: 2026-05-08T06:19:20.621Z
Learning: For Jackson 3 codebases, do not treat imports from `com.fasterxml.jackson.annotation.*` (e.g., `JsonInclude`, `JsonProperty`, `JsonView`) as incorrect. In Jackson 3, `jackson-annotations` intentionally remains under `com.fasterxml.jackson.annotation.*` for backward compatibility, while only the core processing packages (e.g., `jackson-core`, `jackson-databind`) move to the `tools.jackson.*` namespace.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📚 Learning: 2026-05-04T20:31:11.075Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1086
File: backend/src/main/java/org/booklore/service/metadata/BookReviewUpdateService.java:63-66
Timestamp: 2026-05-04T20:31:11.075Z
Learning: For this repository, reviewers should treat string truncation done via `String.length()` and `String.substring(0, maxLength)` (UTF-16 code units) as an accepted, consistent convention. Do not flag individual occurrences of this pattern as bugs, even though it is not code-point-aware for surrogate pairs. A separate global effort is already tracked to move toward code-point-aware truncation, so per-site fixes should be avoided during code review.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
📚 Learning: 2026-05-13T12:34:49.607Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1293
File: backend/src/main/java/org/booklore/service/metadata/DuckDuckGoCoverService.java:246-252
Timestamp: 2026-05-13T12:34:49.607Z
Learning: In this repo’s Java code, when catching Jsoup `org.jsoup.HttpStatusException` (and similar exceptions originating from external libraries) and wrapping/rethrowing them, do not require preserving the original exception stack trace (e.g., as flagged by PMD `PreserveStackTrace`) as long as the application already captures the actionable diagnostics in logs or the thrown exception message (such as HTTP status code and the requested URL). Reviewers should still ensure the log/message contains those details; the intent is to avoid noisy stack traces that only reflect external-library internals rather than application code.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
📚 Learning: 2026-05-17T13:38:16.462Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1366
File: backend/src/main/java/org/booklore/service/FileStreamingService.java:65-66
Timestamp: 2026-05-17T13:38:16.462Z
Learning: In the grimmory-tools/grimmory repo, it’s an accepted pattern to pass the raw AccessDeniedException.getMessage() (even if it may include filesystem path details) into ApiError.PERMISSION_DENIED.createException(...). During code review, do not raise a security/information-disclosure issue solely based on that exception message being propagated to the API when using ApiError.PERMISSION_DENIED.createException with the AccessDeniedException message.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
📚 Learning: 2026-05-23T23:01:25.769Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1456
File: backend/src/main/java/org/booklore/service/metadata/parser/GoodReadsParser.java:618-630
Timestamp: 2026-05-23T23:01:25.769Z
Learning: In this codebase (grimmory-tools/grimmory), it’s intentional to omit per-request timeouts on individual Java HttpRequest.Builder instances (e.g., GoodReadsParser.fetchJson). During reviews, do not flag missing builder-level timeouts as a best-practice violation; rely on framework-level and/or HttpClient-level timeouts configured elsewhere for consistent behavior. Only raise an issue if you can verify that no effective timeout is configured at the HttpClient/framework level.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
📚 Learning: 2026-06-12T01:10:31.416Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1724
File: backend/src/main/java/org/booklore/repository/BookRepository.java:58-59
Timestamp: 2026-06-12T01:10:31.416Z
Learning: In this codebase (grimmory-tools/grimmory), reviews should not treat inline `LIMIT`/`OFFSET` clauses inside `Query` JPQL/HQL strings as a JPA compliance risk. This is intentional: `hibernate.jpa.compliance.query=true` is intentionally not set, and Hibernate 7.3+ supports `LIMIT`/`OFFSET` as valid HQL extensions. Therefore, do not flag or require changes to `Query` annotations solely due to `LIMIT`/`OFFSET` usage.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
📚 Learning: 2026-05-07T21:37:46.988Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:121-123
Timestamp: 2026-05-07T21:37:46.988Z
Learning: In grimmory-tools/grimmory service-layer code, if arithmetic overflow occurs inside inference/business-logic (e.g., when deriving inferred fields like durationSeconds from start/end timestamps), treat it as a server-side anomaly. Prefer letting the global exception handler translate it into a generic 5xx response rather than throwing an explicit ApiError 4xx (e.g., do not convert overflow into a client error).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java
📚 Learning: 2026-04-27T15:25:55.042Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 930
File: backend/src/test/java/org/booklore/service/metadata/parser/ComicvineBookParserTest.java:68-75
Timestamp: 2026-04-27T15:25:55.042Z
Learning: In this repository’s JUnit 5 test sources (e.g., under backend/src/test/java/), do not flag “bare” `assert` statements as a bug. The project test runner is configured to always execute tests with assertions enabled (e.g., `-ea`), so `assert` behavior is consistent. Continue to review for correctness, but don’t treat unguarded `assert` usage in test classes as a static-analysis issue.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📚 Learning: 2026-05-22T03:20:45.559Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1446
File: backend/src/test/java/org/booklore/service/metadata/MetadataManagementServiceTest.java:465-470
Timestamp: 2026-05-22T03:20:45.559Z
Learning: In backend unit tests (Java files under backend/src/test/java), do not flag hardcoded placeholder filesystem paths (e.g., setting LibraryPathEntity.path = "/example") as violations when the value is intentionally unused for filesystem access. Only suppress the "no hardcoded paths" concern if the test does not perform any filesystem/network IO using that path (no reads/writes/Files.* calls or code paths that access the filesystem with that value); if the placeholder is actually used to touch the filesystem, flag it.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
📚 Learning: 2026-05-04T05:01:33.919Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1081
File: backend/src/test/java/org/booklore/service/metadata/parser/AudibleParserTest.java:132-135
Timestamp: 2026-05-04T05:01:33.919Z
Learning: In grimmory-tools/grimmory unit/integration test classes (e.g., files matching **/*Test.java under backend/src/test/java/**), it is acceptable to directly instantiate Jackson mappers (e.g., `new ObjectMapper()` / `new JsonMapper(...)`) and this should NOT be flagged. The Jackson guidance to use Spring bean injection or `JsonMapper.shared()` applies only to production code; tests may construct dependencies directly as standard practice. Production-code mapper instantiation rules should still be enforced outside test sources.

Applied to files:

  • backend/src/test/java/org/booklore/service/metadata/BookMetadataServiceTest.java
🔇 Additional comments (1)
backend/src/main/java/org/booklore/service/metadata/BookMetadataService.java (1)

14-22: LGTM!

Also applies to: 39-42, 137-141

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to remove the whitespace changes from BookCategory?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File should just be deleted, no? was a remanent of something that didn't pass a preliminary check

Comment on lines -21 to +22
private static final long INITIAL_DELAY_MS = 1200;
private static final long INITIAL_DELAY_MS = 1000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 1200 -> 1000 here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was my understanding this related to the rate-limiting.

"API calls are rate-limited to 60 requests per minute."
https://docs.hardcover.app/api/getting-started/

titleProvider.getP1(), titleProvider.getP2(),
titleProvider.getP3(), titleProvider.getP4())
isbnProvider.getP1(), isbnProvider.getP2(),
isbnProvider.getP3(), isbnProvider.getP4())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change required for the hardcover side of things? Could it be brought in separately?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required is a stretch, but it definitely is needed to get the best out of Hardcover as is, and makes sense in the grand scheme of things. happy to apply it elsewhere but is required ASAP in my opinon

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this affect other providers rather than just hardcover, though? Will it negatively affect providers that don't pull ISBN?

I suggest carving it out into its own PR.

Co-authored-by: James Ward <james@notjam.es>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java (1)

215-218: ⚠️ Potential issue | 🟡 Minor

Use Duration-based sleep in the service rate-limiter.

Thread.sleep(long) violates the service-layer guideline. Refactor to use the Duration overload and add the necessary import.

Proposed fix
+import java.time.Duration;
...
-                Thread.sleep(delay - elapsed);
+                Thread.sleep(Duration.ofMillis(delay - elapsed));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java`
around lines 215 - 218, In the HardcoverBookSearchService class, the
rate-limiter logic is using the long-based Thread.sleep() overload which
violates service-layer guidelines. Replace the Thread.sleep(delay - elapsed)
call with Thread.sleep(Duration.ofMillis(delay - elapsed)) to use the
Duration-based overload instead. Add the necessary import statement for
java.time.Duration at the top of the file.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java`:
- Around line 215-218: In the HardcoverBookSearchService class, the rate-limiter
logic is using the long-based Thread.sleep() overload which violates
service-layer guidelines. Replace the Thread.sleep(delay - elapsed) call with
Thread.sleep(Duration.ofMillis(delay - elapsed)) to use the Duration-based
overload instead. Add the necessary import statement for java.time.Duration at
the top of the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 049f749b-ce19-4863-a390-897a53456e1c

📥 Commits

Reviewing files that changed from the base of the PR and between 731aa84 and b9642e6.

📒 Files selected for processing (1)
  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Frontend Lint Threshold Check
🧰 Additional context used
📓 Path-based instructions (4)
backend/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

backend/src/**/*.java: Use 4-space indentation and match surrounding Java style in backend code
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection in backend code
Use MapStruct for entity/DTO mapping in backend code
Keep JPA entities on the *Entity suffix in backend code

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
**/*

⚙️ CodeRabbit configuration file

**/*: This project is being developed using current and future-facing technologies:

  • Java 25 with --enable-preview (preview features are INTENTIONAL and encouraged)
  • Spring Boot 4 (latest major version, check APIs accordingly)
  • Jackson 3 (new package: tools.jackson.* instead of com.fasterxml.jackson.*)
  • Hibernate 7.3.x (Jakarta Persistence 3.2, new APIs; avoid deprecated Hibernate 5/6 patterns)
  • Angular 21 (signals-based reactivity, no NgModules unless legacy)

Grimmory Internal Tools

Metadata Standards and Compliance

  • For all metadata writing and parsing logic, double-check against Dublin Core and ANSI standards to ensure perfect official compliance.
  • We strictly follow the widespread and official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.

General Java and Spring rules

  • ALWAYS prefer modern, idiomatic Java 25 constructs over legacy patterns.
  • Preview features (--enable-preview) are enabled and intentional; do NOT flag them as risky unless there is a concrete runtime issue.
  • Prefer: records, sealed classes/interfaces, pattern matching (switch expressions, instanceof), structured concurrency (StructuredTaskScope), scoped values, string templates, unnamed patterns/variables.
  • Prefer virtual threads (Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor()) over platform threads for I/O-bound work.
  • Prefer the new Sequenced Collections API (SequencedCollection, SequencedMap) where applicable.
  • Prefer var for local variables when the type is obvious from context.
  • Use stream().toList() instead of stream().collect(Collectors.toList()) for imm...

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
**/service/**/*.java

⚙️ CodeRabbit configuration file

**/service/**/*.java: Spring Framework 7 service layer review:

  • Flag missing @Transactional on methods that perform multiple writes.
  • Prefer constructor injection over @Autowired field injection. Use @AllArgsConstructor.
  • Use ApiError enum for throwing exceptions.
  • Flag checked exceptions swallowed silently; must log or rethrow.
  • Flag Thread.sleep(); prefer Duration-based overloads or ScheduledExecutorService.
  • Prefer virtual threads (Thread.ofVirtual()) for I/O-bound operations.
  • Flag mutable shared state in singleton beans.

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
**/metadata/**/*.java

⚙️ CodeRabbit configuration file

**/metadata/**/*.java: Metadata and Sidecar review:

  • Ensure perfect official compliance with Dublin Core and ANSI standards.
  • Strictly follow official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.
  • Sidecar files must be written in a way that is compatible with widespread e-reader standards.
  • Avoid proprietary or non-standard XML tags unless absolutely necessary for internal tracking.

Files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
🧠 Learnings (10)
📚 Learning: 2026-04-10T08:15:37.436Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 449
File: booklore-api/src/main/java/org/booklore/service/book/BookDownloadService.java:139-145
Timestamp: 2026-04-10T08:15:37.436Z
Learning: When using Spring `ContentDisposition.builder(...).filename(name, StandardCharsets.UTF_8).build()` (i.e., explicitly providing UTF-8), the resulting header value should include both the quoted `filename="=?UTF-8?..."` and the RFC 5987 `filename*=` parameters. In this case, any extra ASCII fallback computation (e.g., deriving an ASCII `fallbackFilename` via `NON_ASCII_PATTERN` and calling `.filename(fallbackFilename)`) is likely redundant—prefer calling only `.filename(fallbackName?, StandardCharsets.UTF_8)` as appropriate and let Spring handle the UTF-8 header parameters. Verify by comparing the emitted header for `filename` and `filename*` before deciding to keep an ASCII fallback.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-04-14T12:43:08.698Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 502
File: booklore-api/src/main/java/org/booklore/service/reader/ChapterCacheService.java:0-0
Timestamp: 2026-04-14T12:43:08.698Z
Learning: For this codebase (booklore-api), target Java 25 with `--enable-preview`, so `_` is intentionally used as an unnamed/ignored variable (e.g., lambda parameter or pattern variable) per Java’s preview feature JEP 456. Do not flag `_` in those contexts as an invalid/reserved identifier; only flag it if it’s used in a non-supported position (e.g., where an unnamed variable is not applicable for the Java preview rules).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-07T21:21:55.233Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:0-0
Timestamp: 2026-05-07T21:21:55.233Z
Learning: When reviewing Java 23+ code, treat `java.time.Instant#until(Instant endExclusive)` as a valid API/method call (it returns a `Duration`, equivalent to `Duration.between(this, endExclusive)`). Do not flag `instant.until(otherInstant)` as a compile error or API misuse when the project targets Java 25+ (as in grimmory-tools/grimmory); the call should be considered correct and returns a `Duration`.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-08T06:19:20.621Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1201
File: backend/src/main/java/org/booklore/model/dto/AccessTokenDto.java:3-10
Timestamp: 2026-05-08T06:19:20.621Z
Learning: For Jackson 3 codebases, do not treat imports from `com.fasterxml.jackson.annotation.*` (e.g., `JsonInclude`, `JsonProperty`, `JsonView`) as incorrect. In Jackson 3, `jackson-annotations` intentionally remains under `com.fasterxml.jackson.annotation.*` for backward compatibility, while only the core processing packages (e.g., `jackson-core`, `jackson-databind`) move to the `tools.jackson.*` namespace.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-04T20:31:11.075Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1086
File: backend/src/main/java/org/booklore/service/metadata/BookReviewUpdateService.java:63-66
Timestamp: 2026-05-04T20:31:11.075Z
Learning: For this repository, reviewers should treat string truncation done via `String.length()` and `String.substring(0, maxLength)` (UTF-16 code units) as an accepted, consistent convention. Do not flag individual occurrences of this pattern as bugs, even though it is not code-point-aware for surrogate pairs. A separate global effort is already tracked to move toward code-point-aware truncation, so per-site fixes should be avoided during code review.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-13T12:34:49.607Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1293
File: backend/src/main/java/org/booklore/service/metadata/DuckDuckGoCoverService.java:246-252
Timestamp: 2026-05-13T12:34:49.607Z
Learning: In this repo’s Java code, when catching Jsoup `org.jsoup.HttpStatusException` (and similar exceptions originating from external libraries) and wrapping/rethrowing them, do not require preserving the original exception stack trace (e.g., as flagged by PMD `PreserveStackTrace`) as long as the application already captures the actionable diagnostics in logs or the thrown exception message (such as HTTP status code and the requested URL). Reviewers should still ensure the log/message contains those details; the intent is to avoid noisy stack traces that only reflect external-library internals rather than application code.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-17T13:38:16.462Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 1366
File: backend/src/main/java/org/booklore/service/FileStreamingService.java:65-66
Timestamp: 2026-05-17T13:38:16.462Z
Learning: In the grimmory-tools/grimmory repo, it’s an accepted pattern to pass the raw AccessDeniedException.getMessage() (even if it may include filesystem path details) into ApiError.PERMISSION_DENIED.createException(...). During code review, do not raise a security/information-disclosure issue solely based on that exception message being propagated to the API when using ApiError.PERMISSION_DENIED.createException with the AccessDeniedException message.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-23T23:01:25.769Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1456
File: backend/src/main/java/org/booklore/service/metadata/parser/GoodReadsParser.java:618-630
Timestamp: 2026-05-23T23:01:25.769Z
Learning: In this codebase (grimmory-tools/grimmory), it’s intentional to omit per-request timeouts on individual Java HttpRequest.Builder instances (e.g., GoodReadsParser.fetchJson). During reviews, do not flag missing builder-level timeouts as a best-practice violation; rely on framework-level and/or HttpClient-level timeouts configured elsewhere for consistent behavior. Only raise an issue if you can verify that no effective timeout is configured at the HttpClient/framework level.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-06-12T01:10:31.416Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1724
File: backend/src/main/java/org/booklore/repository/BookRepository.java:58-59
Timestamp: 2026-06-12T01:10:31.416Z
Learning: In this codebase (grimmory-tools/grimmory), reviews should not treat inline `LIMIT`/`OFFSET` clauses inside `Query` JPQL/HQL strings as a JPA compliance risk. This is intentional: `hibernate.jpa.compliance.query=true` is intentionally not set, and Hibernate 7.3+ supports `LIMIT`/`OFFSET` as valid HQL extensions. Therefore, do not flag or require changes to `Query` annotations solely due to `LIMIT`/`OFFSET` usage.

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
📚 Learning: 2026-05-07T21:37:46.988Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1194
File: backend/src/main/java/org/booklore/service/ReadingSessionService.java:121-123
Timestamp: 2026-05-07T21:37:46.988Z
Learning: In grimmory-tools/grimmory service-layer code, if arithmetic overflow occurs inside inference/business-logic (e.g., when deriving inferred fields like durationSeconds from start/end timestamps), treat it as a server-side anomaly. Prefer letting the global exception handler translate it into a generic 5xx response rather than throwing an explicit ApiError 4xx (e.g., do not convert overflow into a client error).

Applied to files:

  • backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java
🔇 Additional comments (1)
backend/src/main/java/org/booklore/service/metadata/parser/hardcover/HardcoverBookSearchService.java (1)

22-172: LGTM!

Also applies to: 224-239

@imnotjames imnotjames left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A big part of my comments have to do with reworking the code to avoid creating methods that cause side effects by updating objects in place and instead try to use the various stream APIs.

Comment on lines +82 to +84
if (metadata != null) {
results.add(metadata);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can never be null.

Comment on lines +76 to +81
BookMetadata metadata = new BookMetadata();
log.debug("Processing edition '{}' with id '{}' of book '{}'", book.getEditions().getFirst().getTitle(),
book.getEditions().getFirst().getId(),
book.getTitle());
mapBookToMetadata(book, book.getEditions().getFirst(), metadata);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably can move the book metadata creation into the mapBookToMetadata helper.

private BookMetadata toMetadata(GraphQLResponse.BookWithEditions book, GraphQLResponse.Edition edition) {
    var builder = BookMetadata.builder()
        .hardcoverId(book.getSlug());

    // etc

    return builder.build();
}

Then the mapping helpers just need to accept the builder class instead if you still want them all split up like that, and they can have void returns.

return docs.stream()
.sorted(Comparator.comparingDouble(GraphQLResponse.Document::getLevenshteinDistanceTitle))
.toList();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to do this by using predicates/functions and a single stream instead of multiple array lists? We could also avoid using the GraphQLResponse document holding state by creating a helper record in this class.

eg, a private record in the HardcoverParser

    record ScoredDocument (
            GraphQLResponse.Document document,
            double score
    ) {}

We'd update the filter to provide a predicate:

    private Function<GraphQLResponse.Document, ScoredDocument> mapTitleLevenshtein(String searchTitle) {
        LevenshteinDistance levenshtein = LevenshteinDistance.getDefaultInstance();
        String searchTitleLower = searchTitle.toLowerCase();

        return (GraphQLResponse.Document doc) -> {
            if (doc.getTitle() == null || doc.getTitle().isBlank()) {
                return new ScoredDocument(doc, Double.MAX_VALUE);
            }
            // calculate the lev.dist for the Work-level title and the search provided term
            double totalScore = levenshtein.apply(searchTitleLower, doc.getTitle().toLowerCase());

            if (doc.getAlternativeTitles() != null) {
                //repeat the lev.dist calc for each of the alternative titles
                totalScore += doc.getAlternativeTitles()
                        .stream()
                        .map(String::toLowerCase)
                        .map(title -> levenshtein.apply(searchTitleLower, title))
                        .min(Double::compare)
                        .orElse(Integer.MAX_VALUE);
            }

            // (minTitleDist+minAltTitleDist)/1+userCount. best scores should approach 0
            int usersCount = doc.getUsersCount() == null ? 0 : doc.getUsersCount();
            return new ScoredDocument(doc, (totalScore + 1)/(usersCount + 1));
        };
    }

and then finally..

    private List<GraphQLResponse.Document> filterSearch(List<GraphQLResponse.Hit> hits, FetchMetadataRequest request) {
        if (hits == null || hits.isEmpty()) {
            log.info("Hardcover: No results found for title '{}'", request.getTitle());
            return Collections.emptyList();
        }

        String searchTitle = request.getTitle() != null ? request.getTitle() : "";
        String searchAuthor = request.getAuthor() != null ? formatRequestAuthor(request.getAuthor()) : "";

        //convert hits into document format.
        Stream<GraphQLResponse.Document> docs = hits.stream()
                .map(GraphQLResponse.Hit::getDocument)
                .filter(Objects::nonNull);

        log.debug("Filtering by title: '{}', author: '{}'", searchTitle, searchAuthor);

        if (!searchTitle.isBlank()) {
            docs = docs
                    .map(mapTitleLevenshtein(searchTitle))
                    .sorted(Comparator.comparingDouble(ScoredDocument::score))
                    .map(ScoredDocument::document);
            log.debug("Filtered by title: {}", searchTitle);
        }

        // Similar with the author - different because it uses "max" distance as part of a filter?

        return docs.toList();
}

Happy to chat through this more, but I would suggest similar throughout this change set to make for an easier to follow code path.

filterEditionsByLanguage(results, result);
}
return results;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be more cleanly defined as a map over a stream rather than relying on mutations of objects we're passing into the functions, right?

titleProvider.getP1(), titleProvider.getP2(),
titleProvider.getP3(), titleProvider.getP4())
isbnProvider.getP1(), isbnProvider.getP2(),
isbnProvider.getP3(), isbnProvider.getP4())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this affect other providers rather than just hardcover, though? Will it negatively affect providers that don't pull ISBN?

I suggest carving it out into its own PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants