Skip to content

Add canonical route cache key derivation and tests#346

Draft
leynos wants to merge 1 commit intomainfrom
test-plan-cache-key-canonicalization-fxiafq
Draft

Add canonical route cache key derivation and tests#346
leynos wants to merge 1 commit intomainfrom
test-plan-cache-key-canonicalization-fxiafq

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Apr 24, 2026

Summary

  • Introduces domain-owned canonicalization for route cache keys. Added a constructor that derives RouteCacheKey from a route request payload, ensuring semantically equivalent requests map to the same cache key (namespace route:v1 with a sha256 digest).
  • Reuses existing domain hashing while applying normalization rules: sorted object keys, stable theme arrays, and rounded coordinates.
  • Adds unit and behavioural tests, plus focused documentation to reflect the new canonicalization seam.

Changes

Core functionality

  • Implemented RouteCacheKey::for_route_request(payload) in backend/src/domain/ports/cache_key.rs
    • Normalizes payload before hashing to produce a stable digest.
    • Normalization rules include:
      • Sort object keys at every level
      • Sort documented array fields (themes, themeIds, interestThemeIds) when they are arrays of strings
      • Round coordinate fields (lat, lng, lon, latitude, longitude) to five decimal places
    • Final key format remains route:v1: using the existing payload hashing helper.
  • Added support types and helpers within the same module:
    • const ROUTE_CACHE_NAMESPACE, COORDINATE_PRECISION_FACTOR, SORTED_ARRAY_KEYS, ROUNDED_COORDINATE_KEYS
    • normalize_route_request_value, should_sort_array, should_round_coordinate, round_coordinate
  • Public API exposure
    • RouteCacheKeyDerivationError is introduced and exported (alongside RouteCacheKeyValidationError) to describe derivation failures.
    • RouteCacheKey::for_route_request returns Result<Self, RouteCacheKeyDerivationError> and validates the final key.

Public API surface

  • Updated backend/src/domain/ports/mod.rs to export RouteCacheKeyDerivationError (in addition to RouteCacheKeyValidationError)
  • RouteCacheKey now provides a stable, derivation-based constructor for route requests.

Tests

  • Added unit tests for canonicalization in backend/src/domain/ports/cache_key.rs (within the existing #[cfg(test)] mod tests)
    • Verifies namespace and hash shape (route:v1: with 64 lowercase hex chars)
    • Verifies sorting of documented theme arrays for multiple fields
    • Verifies rounding behavior on coordinate fields
    • Verifies that material payload differences change the key
    • Verifies non-theme array orders are not altered (and still affect the key)
  • Introduced Redis-backed behavioural tests to validate integration between canonical keys and caching:
    • backend/tests/features/route_cache_key_canonicalization.feature
    • backend/tests/route_cache_key_canonicalization_bdd.rs
    • These tests require a running Redis server; they are designed to be skipped if Redis is unavailable or SKIP_REDIS_TESTS is set.

Documentation

  • New ExecPlan doc: docs/execplans/backend-5-1-4-cache-key-canonicalization-contract-tests.md
  • Architecture docs updated to reflect the canonicalization seam ownership:
    • docs/wildside-backend-architecture.md now notes that RouteCacheKey encapsulates canonical derivation and that the Redis adapter persists the derived key.

Validation notes

  • The unit tests target the canonicalization contract and payload normalization logic.
  • Redis-backed behavioural tests are gated and will be skipped when Redis is unavailable or SKIP_REDIS_TESTS is set. When run with Redis available, they validate the end-to-end behavior:
    • Equivalent route requests canonicalize to the same key
    • A plan stored under the first canonical key can be retrieved using the second canonical key
    • The canonical key follows the route v1 sha256 format

How to test locally

  • Fast unit tests (without Redis):
    • cargo test -p backend route_request_key --lib (or run the project’s full unit test suite)
  • Redis-backed behaviour tests (requires Redis in PATH):
    • SKIP_REDIS_TESTS=0 cargo test -p backend --test route_cache_key_canonicalization_bdd -- --nocapture
  • If Redis is not available, set SKIP_REDIS_TESTS=1 to skip the behavioural tests while still running unit tests.

Rationale / design notes

  • The canonicalization seam remains in the domain layer to satisfy the contract that the service owns normalization and namespace rules, not the Redis adapter.
  • The implementation reuses the existing domain hash helper to avoid duplicating SHA-256 logic and to ensure consistency across the codebase.
  • A new RouteCacheKeyDerivationError surface provides a precise error taxonomy for key derivation failures while preserving compatibility with existing error types.

Risks and mitigations

  • Overfitting normalization rules could affect cache usefulness. Mitigation: adhere strictly to documented semantics (sorted arrays, rounded coordinates, stable object order).
  • Redis-backed tests introduce environmental dependency. Mitigation: keep tests gated and provide clear skip messaging when Redis is unavailable.

Progress indicators

  • Implemented domain-owned canonical route cache key derivation
  • Added focused unit tests and Redis-backed behavioural tests
  • Updated architectural and execplan documentation to reflect the new contract
  • Exported new derivation error type for downstream consumers

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/013b1644-7ade-47fd-a954-ba6ec8fda3ff

Summary by Sourcery

Introduce domain-owned canonical route cache key derivation for route requests and validate it with unit, integration, and documentation updates.

New Features:

  • Add RouteCacheKey::for_route_request constructor to derive canonical cache keys from route request JSON payloads using a stable namespace and hash format.
  • Expose RouteCacheKeyDerivationError as a public error type for cache key derivation failures.

Enhancements:

  • Normalize route request payloads before hashing by sorting object keys, sorting specific theme arrays, and rounding coordinate fields to five decimal places.
  • Clarify architecture documentation to state that RouteCacheKey owns canonical cache key derivation while the Redis adapter only persists derived keys.

Documentation:

  • Add an ExecPlan document describing the route cache key canonicalization contract tests, constraints, risks, and validation steps.
  • Update backend architecture documentation to document the canonicalization seam location and responsibilities.

Tests:

  • Add unit tests covering canonical cache key format, array sorting behavior, coordinate rounding, and sensitivity to material payload differences.
  • Add Redis-backed BDD tests and feature scenarios to verify that semantically equivalent route requests share a single Redis cache entry and respect the route:v1 sha256 key format.

…tically equal requests

- Introduce `RouteCacheKey::for_route_request` to derive stable cache keys
- Normalize route request JSON by sorting theme arrays, stable key ordering,
  and rounding coordinates to five decimal places
- Use domain SHA-256 hashing helper to produce keys in `route:v1:<sha256>` format
- Add unit tests for canonicalization logic covering sorting and rounding rules
- Add Redis-backed behavioural tests ensuring equivalent requests share cache slot
- Update domain exports and architecture docs to reflect canonicalization seam

This enables improved cache hit rates by unifying semantically equivalent route
requests under the same cache key while keeping canonicalization logic inside
the domain cache-key layer, consistent with architecture guidelines.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ab4a558-1547-4bac-a1db-db0acf4589de

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test-plan-cache-key-canonicalization-fxiafq

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

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 24, 2026

Reviewer's Guide

Implements domain-owned canonical route cache key derivation for route requests, exposes a new derivation error type, adds unit and Redis-backed behavioural tests around canonicalization, and updates architecture/execplan docs to reflect the new cache-key seam.

Class diagram for RouteCacheKey canonical derivation and errors

classDiagram
    class RouteCacheKey {
        -String value
        +RouteCacheKey new(String raw) Result~RouteCacheKey, RouteCacheKeyValidationError~
        +RouteCacheKey for_route_request(Value payload) Result~RouteCacheKey, RouteCacheKeyDerivationError~
        +&str as_str()
        +String to_string()
    }

    class RouteCacheKeyValidationError {
        <<enum>>
        +InvalidNamespace
        +InvalidHashLength
        +InvalidCharacters
        +ContainsWhitespace
    }

    class RouteCacheKeyDerivationError {
        <<enum>>
        +Hash
        +Validation
    }

    class PayloadHashError {
        <<error>>
    }

    class RouteRequestNormalizer {
        <<utility>>
        +Value normalize_route_request_value(Value value, Option~&str~ current_key)
        +bool should_sort_array(Option~&str~ current_key)
        +bool should_round_coordinate(Option~&str~ current_key)
        +Number round_coordinate(Number number)
        +const ROUTE_CACHE_NAMESPACE : &str
        +const COORDINATE_PRECISION_FACTOR : f64
        +const SORTED_ARRAY_KEYS : &[&str]
        +const ROUNDED_COORDINATE_KEYS : &[&str]
    }

    class DomainHashing {
        <<utility>>
        +String canonicalize_and_hash(Value value) Result~String, PayloadHashError~
    }

    RouteCacheKey --> RouteCacheKeyValidationError : uses
    RouteCacheKey --> RouteCacheKeyDerivationError : returns
    RouteCacheKeyDerivationError --> RouteCacheKeyValidationError : wraps
    RouteCacheKeyDerivationError --> PayloadHashError : wraps
    RouteCacheKey --> RouteRequestNormalizer : uses
    RouteCacheKey --> DomainHashing : uses
Loading

File-Level Changes

Change Details Files
Add canonical RouteCacheKey derivation from route request payloads with normalization rules before hashing.
  • Introduce RouteCacheKey::for_route_request(&Value) that normalizes the payload, hashes it via canonicalize_and_hash, and prefixes it with the route:v1 namespace.
  • Define constants for route cache namespace, coordinate precision, sortable array keys, and rounded coordinate keys.
  • Implement normalize_route_request_value to recursively sort object keys, sort selected string arrays, and round selected numeric coordinate fields before hashing.
  • Add helpers should_sort_array, should_round_coordinate, and round_coordinate, reusing serde_json::Value/Number types.
backend/src/domain/ports/cache_key.rs
Extend cache key error surface with a derivation error type and export it from the domain ports module.
  • Add RouteCacheKeyDerivationError enum to wrap PayloadHashError and RouteCacheKeyValidationError from derivation.
  • Change RouteCacheKey::for_route_request to return Result<Self, RouteCacheKeyDerivationError> and map validation failures accordingly.
  • Re-export RouteCacheKeyDerivationError in domain ports mod so adapters can depend on it.
backend/src/domain/ports/cache_key.rs
backend/src/domain/ports/mod.rs
Add unit tests validating canonicalization behaviour and key format for route cache keys.
  • Extend existing cache_key tests module to cover namespace and sha256 digest shape for route:v1 keys.
  • Add rstest-based cases ensuring documented theme arrays are sorted but non-theme arrays remain order-sensitive.
  • Add tests verifying rounding behaviour for documented coordinate fields and that material payload differences change the key.
backend/src/domain/ports/cache_key.rs
Introduce Redis-backed BDD tests and feature describing canonical route cache key behaviour end-to-end.
  • Create RouteCacheKeyWorld test harness that bootstraps a temporary Redis server, handles skip logic via SKIP_REDIS_TESTS and should_skip_redis_tests, and wires a RedisRouteCache.
  • Derive two RouteCacheKey values from semantically equivalent JSON payloads and assert equality plus route:v1 sha256 format.
  • Store a TestPlan under the first key, read with the second key, and assert the same plan is returned.
  • Back the scenario with a Gherkin feature file documenting the canonicalization behaviour and Redis dependency.
backend/tests/route_cache_key_canonicalization_bdd.rs
backend/tests/features/route_cache_key_canonicalization.feature
Update and add documentation to describe the canonicalization contract and its implementation status.
  • Update architecture doc to describe RouteCacheKey as owning canonical cache-key derivation and to document that canonicalization lives in backend/src/domain/ports/cache_key.rs while the Redis adapter just persists the derived key.
  • Add an ExecPlan document capturing constraints, risks, implementation outline, progress, and validation strategy for the cache key canonicalization contract tests.
docs/wildside-backend-architecture.md
docs/execplans/backend-5-1-4-cache-key-canonicalization-contract-tests.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant