Target branch: 0.7 (breaking — calls that return Ok today would return Err).
Split out of #4. That PR carries two kinds of change: fixes that turn silent corruption into correct output (non-breaking, going to main), and this one — refusing to emit keys no form can round-trip.
The keys
Verified against main at 0.6.2 — every one of these emits happily today and re-parses to something else:
| Key |
Emitted |
Re-parses as |
"" |
: v |
root flips Object → Array |
"##note" |
##note: v |
pair eaten as a comment → {} |
" k" / "k " |
raw |
edges trimmed by the parser (§ 4) |
"k\nx" |
raw |
line split |
"a,b", "a{b", … |
raw |
InvalidKey or structural break |
The CR-in-String rule (§ 5.9.7) is the precedent: unrepresentable means error, not silent corruption.
Why breaking, and why not main
These calls succeed today. The output is wrong, but it parses — so in principle someone could be depending on it. That is the line we drew: main gets everything that was already broken beyond use (unparseable output, lost bytes), 0.7 gets the cases that quietly parsed into different data.
Depends on
Do #7 first. § 4 mandates ten key escapes and the parser accepts three; once the parser catches up, keys containing ( ) { } [ ] , become representable and drop out of this issue entirely — leaving only "", "##note", edge whitespace and line breaks. Landing this issue first would bake in refusals we are about to make unnecessary.
Definition of done
- Writer validates keys on all three surfaces (
emit_canonical, serde to_string, render / to_string_force_strings) and returns a descriptive error naming the reason and the spec section.
- Regression tests in the crate: one per rejected class, per surface — asserting the error, not just "not Ok". Plus guard tests for tricky-but-representable keys so the check does not over-reject.
- Regression fixtures in
ktav-lang/spec: this needs the new "unrepresentable value" fixture category (see the spec-side issue) — a JSON value plus the expected refusal. Without fixtures the other six bindings will drift, which is exactly what the conformance suite exists to prevent.
A working implementation already exists in #4 (9 tests); it needs re-basing onto 0.7 once the escape work lands.
Target branch:
0.7(breaking — calls that returnOktoday would returnErr).Split out of #4. That PR carries two kinds of change: fixes that turn silent corruption into correct output (non-breaking, going to
main), and this one — refusing to emit keys no form can round-trip.The keys
Verified against
mainat 0.6.2 — every one of these emits happily today and re-parses to something else:"": v"##note"##note: v{}" k"/"k ""k\nx""a,b","a{b", …InvalidKeyor structural breakThe CR-in-String rule (§ 5.9.7) is the precedent: unrepresentable means error, not silent corruption.
Why breaking, and why not
mainThese calls succeed today. The output is wrong, but it parses — so in principle someone could be depending on it. That is the line we drew:
maingets everything that was already broken beyond use (unparseable output, lost bytes),0.7gets the cases that quietly parsed into different data.Depends on
Do #7 first. § 4 mandates ten key escapes and the parser accepts three; once the parser catches up, keys containing
( ) { } [ ] ,become representable and drop out of this issue entirely — leaving only"","##note", edge whitespace and line breaks. Landing this issue first would bake in refusals we are about to make unnecessary.Definition of done
emit_canonical, serdeto_string,render/to_string_force_strings) and returns a descriptive error naming the reason and the spec section.ktav-lang/spec: this needs the new "unrepresentable value" fixture category (see the spec-side issue) — a JSON value plus the expected refusal. Without fixtures the other six bindings will drift, which is exactly what the conformance suite exists to prevent.A working implementation already exists in #4 (9 tests); it needs re-basing onto
0.7once the escape work lands.