feat(encoding): add encoding/percent for RFC 3986 URI components - #4214
feat(encoding): add encoding/percent for RFC 3986 URI components#4214bobzhang wants to merge 1 commit into
Conversation
Add `encoding/percent`, a string-to-string percent-encoding codec for URI components, so applications stop hand-rolling it or shimming JavaScript's `encodeURIComponent`/`decodeURIComponent` (moonbitlang/openseek#1408). - `encode` converts the text to UTF-8 and escapes every byte outside the RFC 3986 unreserved set as uppercase `%XX`. It walks UTF-16 code units itself and replaces unpaired surrogates with U+FFFD, because `@utf8.encode` panics on them on the non-JS backends. - `decode` decodes every `%XX` (either hex case) once, interprets each maximal escape run as UTF-8, copies literal code units through unchanged (including `+`, BOMs and unpaired surrogates) and raises `Malformed` with the whole input on a bad escape or invalid escaped UTF-8. - `decode_lossy` keeps a malformed `%` literally, resumes at the next code unit, and replaces invalid escaped UTF-8 with U+FFFD; it agrees with `decode` whenever `decode` succeeds. Literal spans are sliced with `StringView::view`, which checks bounds but not surrogate boundaries, so views that split a pair are handled instead of aborting. Tests cover the ASCII table, UTF-8 boundaries, surrogate cases, nonzero-offset views, the malformed table from openseek, lossy replacement counts, and quickcheck properties against an independent UTF-16 repair oracle. Ten single-point mutations each fail between 2 and 8 of the 24 tests. Codex CLI review: "No blocking issues found ... An independent transcription check passed for every code point and surrogate pair." Signed-off-by: Codex CLI <codex@openai.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MVKQs2BgHmc3VBDXBU8AgD
6e608db to
753c46d
Compare
|
Codex CLI review (gpt-6-astra, medium) Three rounds, run against this branch in a read-only sandbox:
Final verdict:
|
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is self-contained, matches established encoding/* patterns, and includes thorough deterministic and property-based test coverage for the specified semantics.
Pull request overview
Adds a new encoding/percent package to the core library implementing RFC 3986 percent-encoding/decoding for individual URI components, providing a backend-independent alternative to JavaScript encodeURIComponent/decodeURIComponent.
Changes:
- Introduces
encode,decode(raisingMalformed), anddecode_lossyAPIs for RFC 3986 percent-encoding of URI components. - Adds comprehensive unit + quickcheck tests covering ASCII tables, UTF-8 boundaries, surrogate behavior, malformed escapes, and lossy semantics.
- Documents the package behavior and records the addition in the root changelog.
File summaries
| File | Description |
|---|---|
| encoding/percent/README.mbt.md | Package documentation and usage examples for encode/decode/decode_lossy semantics. |
| encoding/percent/encode.mbt | Implements strict RFC 3986 percent-encoding with surrogate repair behavior. |
| encoding/percent/decode.mbt | Implements strict decode (raising Malformed) and lossy decode with precise resumption rules. |
| encoding/percent/encode_test.mbt | Deterministic unit tests for encoding rules and edge cases. |
| encoding/percent/decode_test.mbt | Deterministic unit tests for decoding, malformed handling, lossy behavior, and views. |
| encoding/percent/quickcheck_test.mbt | Property tests against independent reference models and UTF-16 repair oracle. |
| encoding/percent/moon.pkg | Declares package dependencies (runtime + test-only). |
| encoding/percent/extends.mbt | Hides deprecated Debug method promotion for Malformed from generated interface (consistent with other encoding packages). |
| encoding/percent/pkg.generated.mbti | Generated public interface for the new package. |
| CHANGELOG.md | Notes addition of encoding/percent in the release changelog. |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Coverage Report for CI Build 6587Coverage increased (+0.03%) to 89.304%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Summary
Adds
encoding/percent, a string-to-string RFC 3986 percent-encoding codec for URI components, so applications stop hand-rolling it or shimming JavaScript'sencodeURIComponent/decodeURIComponent(see moonbitlang/openseek#1408, which replaced those shims with a pure MoonBitdesktop/internal/uripackage).encodeconverts the text to UTF-8 and escapes every byte outside the RFC 3986 unreserved setA-Z a-z 0-9 - . _ ~as uppercase%XX. A space is%20, never+. It is stricter thanencodeURIComponent, which leaves!*'()unescaped. It walks UTF-16 code units itself and replaces an unpaired surrogate with U+FFFD, because@utf8.encodepanics on one on the non-JS backends; soencodenever raises.decodedecodes every%XX(either hex case) exactly once, interprets each maximal escape run as UTF-8, copies literal code units through unchanged (including+, BOMs and unpaired surrogates), and raisesMalformedwith the whole input on a bad escape or invalid escaped UTF-8. This is the input that makesdecodeURIComponentthrow.decode_lossykeeps a malformed%literally and resumes at the next code unit (%2%41→%2A), and replaces invalid escaped UTF-8 with U+FFFD via@utf8.decode_lossy. It returns the same string asdecodewheneverdecodesucceeds.Deliberately out of scope for this first version, documented in the README: byte-level entry points, per-component presets (path / query),
application/x-www-form-urlencoded, and URL parsing. All can be added later without breaking these signatures.Design notes
StringView::view, which checks bounds but not surrogate boundaries, so a view that splits a pair is handled instead of aborting.Malformedcarries the whole input view, matchingencoding/hexandencoding/base64.Testing
moon test -p encoding/percent --target all: 24/24 on wasm, wasm-gc, js, native.moon check --deny-warn --target all,moon info,moon fmt: clean.moon coverage analyzereports no uncovered lines in the package.%2with a digit right after it), the malformed-escape and invalid-UTF-8 table from openseek, lossy replacement counts, and quickcheck properties against an independent UTF-16 repair oracle and a byte-at-a-time reference encoder.%/ skips 3, no buffer reset between runs, strict path using lossy UTF-8,!unreserved, missing literal flush) each fail between 2 and 8 of the 24 tests.Review
Codex CLI reviewed the design (two blockers, both addressed: backend-independent surrogate policy; exact lossy consumption rule) and then the implementation: "No blocking issues found ... An independent transcription check passed for every code point and surrogate pair."
Signed-off-by: Codex CLI codex@openai.com
🤖 Generated with Claude Code
https://claude.ai/code/session_01MVKQs2BgHmc3VBDXBU8AgD