Skip to content

feat: add missing encoder/decoder capabilities + missing tests - #699

Merged
tada5hi merged 2 commits into
masterfrom
698-codec-simple
Feb 3, 2026
Merged

feat: add missing encoder/decoder capabilities + missing tests#699
tada5hi merged 2 commits into
masterfrom
698-codec-simple

Conversation

@tada5hi

@tada5hi tada5hi commented Feb 3, 2026

Copy link
Copy Markdown
Owner

resolves #698

Summary by CodeRabbit

  • New Features

    • URL encoder/decoder now supports pagination, relations, and sort parameters; dedicated "simple" codec package introduced.
  • Bug Fixes

    • Improved handling of field operators during URL encoding.
  • Tests

    • Added comprehensive unit tests covering fields, filters, pagination, relations, and sorts.
  • Chores

    • Package renamed to reflect the simple dialect.

@coderabbitai

coderabbitai Bot commented Feb 3, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a new simple URL codec package, including encoder and decoder APIs, serializer reset support, serializer visibility changes, relation model simplification, parser robustness for optional schemas, and new unit tests covering fields, pagination, relations, and sorts.

Changes

Cohort / File(s) Summary
Package Metadata
packages/codec-url-simple/package.json
Renamed package to @rapiq/codec-url-simple and updated description to reference the "simple dialect".
Decoder (new)
packages/codec-url-simple/src/decoder/module.ts
Added URLDecoder class with decode(), decodeFields(), decodeFilters(), decodePagination(), decodeRelations(), and decodeSort() to parse query strings into Query substructures.
Encoder extensions
packages/codec-url-simple/src/encoder/module.ts
Added URLEncoder.encodePagination(), encodeRelations(), and encodeSort() and adjusted serializer flow to capture and reset serialized output.
Serializer API & impls
packages/codec-url-simple/src/encoder/serializer/types.ts, .../module.ts, .../array.ts, .../record.ts, .../record-array.ts
Introduced ISerializer.reset(): void and implemented reset() across serializers; changed several value fields from public readonly to protected to allow internal resets.
Visitor tweak
packages/codec-url-simple/src/encoder/visitors/fields.ts
Field visitor now omits operator prefix when operator is absent (uses name alone); preserves previous behavior when operator exists.
Tests added/updated
packages/codec-url-simple/test/unit/fields.spec.ts, pagination.spec.ts, relations.spec.ts, sort.spec.ts, packages/codec-url-simple/test/unit/filters.spec.ts
Added unit tests for fields, pagination, relations, and sorts round-trip; adjusted filters tests (imports and descriptions).
Removed decoder (moved)
packages/codec-url/src/decoder/module.ts
Removed URLDecoder from the base codec package (functionality added to the simple codec package).
Core model change
packages/core/src/parameter/relations/record/module.ts
Removed Relation public properties fields, filters, pagination, and sort, leaving only name and accept() (relations made shallow).
Parser adjustments
packages/core/src/parser/parameter/relations/base.ts, packages/parser-simple/src/parameter/relations/module.ts
Made resolveSchema return optional schema (removed default defineRelationsSchema fallback) and hardened relation parsing to handle optional/absent schemas with guarded mapping, validation, and clearer failure paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through query strings, neat and nimble,
Encoding fields, relations, sorts so simple.
Serializers reset, relations made light,
Tests all cheering in the moonlight.
thump thump — a rabbit's tiny code delight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to packages/core remove public properties from the Relation class and modify its API, which appears unrelated to adding encoder/decoder capabilities. Clarify whether removing Relation properties (fields, filters, pagination, sort) is intentional and necessary for the codec-url-simple feature, or if it should be in a separate PR.
Linked Issues check ❓ Inconclusive The linked issue #698 contains no specific requirements, acceptance criteria, or detailed feature specifications to validate against the implementation. The linked issue should include explicit requirements or acceptance criteria to enable proper validation of implementation completeness.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding missing encoder/decoder capabilities and tests for the codec-url-simple package.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 698-codec-simple

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@packages/codec-url-simple/package.json`:
- Around line 2-4: In package.json update the repository.directory field to
match the renamed package by changing its value from the old path
(packages/codec) to the new directory (packages/codec-url-simple); open the
package.json for `@rapiq/codec-url-simple` and modify the "repository.directory"
property so it points to "packages/codec-url-simple" to keep repository metadata
consistent.

In `@packages/codec-url-simple/test/unit/sort.spec.ts`:
- Line 13: The describe block name is wrong: update the test suite declaration
describe('relations', () => { to describe('sort', () => { so the suite name
matches the file and tests for the sort functionality (look for the describe
call in packages/codec-url-simple/test/unit/sort.spec.ts and change the string
literal from 'relations' to 'sort').

In `@packages/parser-simple/src/parameter/relations/module.ts`:
- Around line 60-75: The code currently checks only schema.throwOnFailure when
deciding to throw for an invalid key (in the isPathAllowed branch), which
ignores the parsed-in option throwOnFailure; update the logic so the throw
occurs if either schema.throwOnFailure or the incoming throwOnFailure option is
true (i.e., use a combined condition like schema.throwOnFailure ||
throwOnFailure) when calling RelationsParseError.keyInvalid for key.name; keep
the existing behavior for the later isValidPath branch that already uses
throwOnFailure and reuse isPathAllowed, isValidPath, and
RelationsParseError.keyInvalid to locate where to change.
- Around line 96-109: The nested-relation branch uses isPathAllowed(key,
schema.allowed) which skips validation when schema.allowed is undefined; update
the logic in the block handling schema (around applyMapping and isPathAllowed)
so that if schema.allowed is undefined you call the general isValidPath(key)
check (same behavior as top-level), otherwise use isPathAllowed(key,
schema.allowed); ensure the code throws RelationsParseError.keyPathInvalid(key)
when that validation fails and then proceeds to
this.registry.resolve(schema.name, key) only on success.
🧹 Nitpick comments (5)
packages/codec-url-simple/src/encoder/serializer/array.ts (1)

16-25: Consider clearing the array in place.

This avoids an allocation and preserves any internal references to value.

♻️ Proposed tweak
-    reset() {
-        this.value = [];
-    }
+    reset() {
+        this.value.length = 0;
+    }
packages/codec-url-simple/test/unit/pagination.spec.ts (2)

22-30: Mutating value before assertion masks encode/decode asymmetry.

Line 28 modifies the original value.offset = 0 before comparison. This suggests decodePagination returns a Pagination with offset: 0 when only limit was provided, but the original Pagination(50) presumably had offset: undefined.

If this is intentional behavior (decoder normalizes missing offset to 0), consider documenting it or testing the behavior explicitly. Otherwise, this masks an encode/decode round-trip asymmetry.

Also, using the non-null assertion encoded! will silently fail if encoding unexpectedly returns null. Consider adding an explicit assertion:

 const encoded = encoder.encodePagination(value);
+expect(encoded).not.toBeNull();
 const decoded = decoder.decodePagination(encoded!);

22-48: Tests are marked async but contain no await operations.

The test functions don't perform any asynchronous operations. Remove async for clarity.

✨ Suggested fix
-    it('should encode & decode with limit', async () => {
+    it('should encode & decode with limit', () => {

Apply similarly to other test cases.

packages/codec-url-simple/test/unit/sort.spec.ts (1)

22-54: Same observations as pagination tests: unnecessary async and non-null assertions.

Consider removing async since there are no await calls, and add explicit null checks on encoded values for better test failure messages.

packages/codec-url-simple/test/unit/fields.spec.ts (1)

14-14: TODO comment indicates missing operator test coverage.

The comment // todo: operator missing suggests field operators (include/exclude) are not yet tested.

Would you like me to help generate test cases for field operators, or should I open an issue to track this?

Comment on lines +2 to +4
"name": "@rapiq/codec-url-simple",
"version": "1.0.0",
"description": "A package containing an url encoder & decoder",
"description": "A package containing an url encoder & decoder for the simple dialect.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update repository.directory to match the renamed package.

After renaming to @rapiq/codec-url-simple, the repository directory still points to packages/codec, which can mislead consumers. Please update it to the new path (Line 66).

🛠 Proposed fix
-        "directory": "packages/codec"
+        "directory": "packages/codec-url-simple"
🤖 Prompt for AI Agents
In `@packages/codec-url-simple/package.json` around lines 2 - 4, In package.json
update the repository.directory field to match the renamed package by changing
its value from the old path (packages/codec) to the new directory
(packages/codec-url-simple); open the package.json for `@rapiq/codec-url-simple`
and modify the "repository.directory" property so it points to
"packages/codec-url-simple" to keep repository metadata consistent.

Comment thread packages/codec-url-simple/test/unit/sort.spec.ts Outdated
Comment on lines +60 to +75
if (schema) {
key.name = applyMapping(key.name, schema.mapping);

if (!isPathAllowed(key.name, schema.allowed)) {
if (schema.throwOnFailure) {
throw RelationsParseError.keyInvalid(key.name);
}

continue;
} else if (
typeof schema.allowed === 'undefined' &&
!this.isValidPath(key.name)
) {
if (throwOnFailure) {
throw RelationsParseError.keyInvalid(key.name);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Respect options.throwOnFailure when schema is present.

On Line 64, schema.throwOnFailure bypasses the options.throwOnFailure override computed on Line 36, so callers can’t force throws.

Suggested fix
-                        if (schema.throwOnFailure) {
+                        if (throwOnFailure) {
                             throw RelationsParseError.keyInvalid(key.name);
                         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (schema) {
key.name = applyMapping(key.name, schema.mapping);
if (!isPathAllowed(key.name, schema.allowed)) {
if (schema.throwOnFailure) {
throw RelationsParseError.keyInvalid(key.name);
}
continue;
} else if (
typeof schema.allowed === 'undefined' &&
!this.isValidPath(key.name)
) {
if (throwOnFailure) {
throw RelationsParseError.keyInvalid(key.name);
}
if (schema) {
key.name = applyMapping(key.name, schema.mapping);
if (!isPathAllowed(key.name, schema.allowed)) {
if (throwOnFailure) {
throw RelationsParseError.keyInvalid(key.name);
}
continue;
} else if (
typeof schema.allowed === 'undefined' &&
!this.isValidPath(key.name)
) {
if (throwOnFailure) {
throw RelationsParseError.keyInvalid(key.name);
}
🤖 Prompt for AI Agents
In `@packages/parser-simple/src/parameter/relations/module.ts` around lines 60 -
75, The code currently checks only schema.throwOnFailure when deciding to throw
for an invalid key (in the isPathAllowed branch), which ignores the parsed-in
option throwOnFailure; update the logic so the throw occurs if either
schema.throwOnFailure or the incoming throwOnFailure option is true (i.e., use a
combined condition like schema.throwOnFailure || throwOnFailure) when calling
RelationsParseError.keyInvalid for key.name; keep the existing behavior for the
later isValidPath branch that already uses throwOnFailure and reuse
isPathAllowed, isValidPath, and RelationsParseError.keyInvalid to locate where
to change.

Comment on lines +96 to +109
if (schema) {
key = applyMapping(keys[i], schema.mapping);

continue;
}
if (!isPathAllowed(key, schema.allowed)) {
if (throwOnFailure) {
throw RelationsParseError.keyPathInvalid(key);
}

// todo: also pass options.schema
const relationSchema = this.registry.resolve(schema.name, key);
if (!relationSchema) {
if (throwOnFailure) {
throw RelationsParseError.keyPathInvalid(key);
continue;
}

continue;
relationSchema = this.registry.resolve(schema.name, key);
} else {
key = keys[i];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Nested relations skip isValidPath when allowed is undefined.

Top-level keys validate with isValidPath for undefined schema.allowed, but nested keys don’t. This creates inconsistent validation for invalid paths.

Suggested fix
                 if (!isPathAllowed(key, schema.allowed)) {
                     if (throwOnFailure) {
                         throw RelationsParseError.keyPathInvalid(key);
                     }

                     continue;
                 }
+
+                if (
+                    typeof schema.allowed === 'undefined' &&
+                    !this.isValidPath(key)
+                ) {
+                    if (throwOnFailure) {
+                        throw RelationsParseError.keyPathInvalid(key);
+                    }
+
+                    continue;
+                }
 
                 relationSchema = this.registry.resolve(schema.name, key);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (schema) {
key = applyMapping(keys[i], schema.mapping);
continue;
}
if (!isPathAllowed(key, schema.allowed)) {
if (throwOnFailure) {
throw RelationsParseError.keyPathInvalid(key);
}
// todo: also pass options.schema
const relationSchema = this.registry.resolve(schema.name, key);
if (!relationSchema) {
if (throwOnFailure) {
throw RelationsParseError.keyPathInvalid(key);
continue;
}
continue;
relationSchema = this.registry.resolve(schema.name, key);
} else {
key = keys[i];
if (schema) {
key = applyMapping(keys[i], schema.mapping);
if (!isPathAllowed(key, schema.allowed)) {
if (throwOnFailure) {
throw RelationsParseError.keyPathInvalid(key);
}
continue;
}
if (
typeof schema.allowed === 'undefined' &&
!this.isValidPath(key)
) {
if (throwOnFailure) {
throw RelationsParseError.keyPathInvalid(key);
}
continue;
}
relationSchema = this.registry.resolve(schema.name, key);
} else {
key = keys[i];
🤖 Prompt for AI Agents
In `@packages/parser-simple/src/parameter/relations/module.ts` around lines 96 -
109, The nested-relation branch uses isPathAllowed(key, schema.allowed) which
skips validation when schema.allowed is undefined; update the logic in the block
handling schema (around applyMapping and isPathAllowed) so that if
schema.allowed is undefined you call the general isValidPath(key) check (same
behavior as top-level), otherwise use isPathAllowed(key, schema.allowed); ensure
the code throws RelationsParseError.keyPathInvalid(key) when that validation
fails and then proceeds to this.registry.resolve(schema.name, key) only on
success.

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.

Feature: Codec URL Simple

1 participant