You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(streaming): Make streaming primary, add body_or_error, rename NOTE_NO_BUFFERED
Resolves ISSUE-env-get-streaming-and-overflow-error.md by making the streaming response path the recommended default and turning the silent failure modes around tree-mode access into actionable, discoverable errors.
env.get docs + overflow message (the original issue)
- Overlay adds prose to env.get's Doxygen comment calling out that the
response is unbounded when no `name` is given, and recommending the streaming .into(JsonSink&) overload (with set_response_buffer() as the fallback when a bounded staging buffer is acceptable).
- spec_parser.py now combines OpenAPI `summary` + `description` so the
overlay can extend prose without overwriting upstream summaries.
- generate.py's markdown stripper now ignores `_` boundaries inside
word characters, preserving snake_case identifiers in `code spans` that the old regex was destroying (`set_response_buffer` → `setresponsebuffer`, `_temp.qo` → `temp.qo`, `ALT_DFU` → `ALTDFU`, `ver_major` → `vermajor`, etc.). Cascades through several api/*.hpp.
- The response-overflow error at protocol.hpp/transact.hpp now names
both escape hatches: `set_response_buffer()` and `.into(JsonSink&)`. Zero AVR flash cost (NOTE_SHORT_ERRORS collapses the literal to "E"). New test in test_buffered_bridge.cpp pins the contract.
Loud failure on body() in streaming mode
- New `Response::body_or_error()` returns an explicit
Result<const JsonReader*> with Error::NotReady + actionable message when the response was populated by the streaming SAX path. Old body() stays for back-compat (now [[nodiscard]]).
- `Response::was_streaming_parse()` exposes the flag for callers that
want to branch on mode at runtime.
- streaming_parse_used_ flag is set by Sink::reset and lives next to
the two unique_ptrs in the buffered section of Response.
- New test in test_body.cpp covers both modes.
NOTE_NO_BUFFERED → NOTE_NO_JSON_TREE
- Canonical macro renamed to match the user-facing vocabulary
(streaming vs tree, not streaming vs buffered).
- NOTE_NO_BUFFERED is honoured as a deprecated alias via a back-compat
shim in note_config.hpp — existing user build_flags keep working.
- Compile-check tests use the new name; one new compile-check
(note_no_buffered_alias.cpp) pins the back-compat shim.
Streaming as the primary path
- New `Notecard(ITransact&, Allocator = {})` convenience ctor
eliminates the nullptr-backend boilerplate when wiring streaming with a non-Protocol transport.
- docs/getting-started.md leads with the streaming pattern; tree mode
is shown as the opt-in when body() or the lambda request builder is needed.
- docs/streaming-and-tree.md documents body_or_error(), the per-
Response RAM savings of setting NOTE_NO_JSON_TREE=1 (24 bytes on 64-bit hosts, ~12 bytes on 32-bit embedded), and the rationale for not pursuing per-instance compile-time gating (covered by the macro flag + the runtime check).
- docs/feature-flags.md + docs/environment-variables.md updated.
Copy file name to clipboardExpand all lines: docs/environment-variables.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -178,7 +178,7 @@ if (rsp) {
178
178
}
179
179
```
180
180
181
-
On AVR-class builds (`NOTE_MINIMAL` sets `NOTE_NO_BUFFERED=1`), tree mode is compiled out — the `transact` + `scan` pattern above is the only option for dynamic keys.
181
+
On AVR-class builds (`NOTE_MINIMAL` sets `NOTE_NO_JSON_TREE=1`), tree mode is compiled out — the `transact` + `scan` pattern above is the only option for dynamic keys.
|`NOTE_JSONB`|`0`|**(M)**`1`| Use [JSONB binary wire format](jsonb.md) instead of JSON text. Requests/responses encoded as COBS-framed binary opcodes. CRC is bypassed (COBS provides framing but not integrity — CRC handles that separately). Raw JSON string bodies are a compile error — use lambdas or typed structs. Requires Notecard firmware 11.x+. |~1.9 KB flash (replaces JSON builder/lexer with the smaller JSONB builder/parser) |
47
-
|`NOTE_NO_BUFFERED`| off |**(M)** on | Disable `JsonBackend`/`JsonReader` tree parse path. Only streaming SAX parse available. (Macro name retained for backwards compatibility.)|~2-4 KB flash, ~300 B RAM |
47
+
|`NOTE_NO_JSON_TREE`| off |**(M)** on | Disable `JsonBackend`/`JsonReader` tree-mode parse path (`.body()`, `.body_or_error()`, `parse(reader)`). Only streaming SAX parse available. The legacy spelling `NOTE_NO_BUFFERED` is honoured as a deprecated alias.|~2-4 KB flash, ~300 B RAM |
48
48
|`NOTE_NO_CRC`| off |**(M)** on | Disable CRC32 on request/response framing. (CRC not supported by JSONB on Notecard)|~200 B flash, 64 B .data (LUT) |
49
49
|`NOTE_NO_MD5`| off |**(M)** on | Disable MD5 for binary transfer verification. |~512 B .data (tables) |
50
50
|`NOTE_NO_STD_STRING`| off |**(M)** on | Disable `std::string`-dependent features (debug wire tracing, some transport methods). Required for AVR (no `<string>` header). | Enables AVR builds |
note::Notecard nc(transport); // streaming — no JSON backend needed
62
60
note::Api api(nc);
63
61
64
62
auto r = api.card.version().execute();
65
63
if (r) std::printf("Notecard %.*s\n", (int)r.version.size(), r.version.data());
66
64
}
67
65
```
68
66
67
+
Streaming is the recommended default — typed response fields, struct body parsing via `.into(struct)`, and the rest of the API surface work without a JSON tree-mode backend linked, and the wire response can be arbitrarily large. Switch to tree mode (`Notecard nc(backend, transport);` with a `note::backends::CjsonBackend backend;`) only when you need `response.body()` for ad-hoc field walks or the `nc.request("endpoint", [&](auto& b){ … })` lambda builder — see [`docs/streaming-and-tree.md`](streaming-and-tree.md) for the full tradeoff.
68
+
69
69
Build: `cmake -S . -B build && cmake --build build`. The runnable companion to this section is [`examples/stdcpp/getting-started.cpp`](../examples/stdcpp/getting-started.cpp), which uses a mock transport so you can experiment without hardware. See [`examples/stdcpp/README.md`](../examples/stdcpp/README.md) for the full example index.
70
70
71
71
### ESP-IDF
@@ -76,7 +76,7 @@ The HAL implementation for ESP-IDF UART/I2C drivers is yours to wire (the same `
76
76
77
77
## Your first request
78
78
79
-
> Throughout the rest of this page, `nc` is the API surface. On Arduino, that's the `Notecard nc;` you declared. On stdcpp/ESP-IDF, after `Notecard nc(backend, transport); Api api(nc);` you write `api.card.version()` instead — the calls are identical, the receiver isn't. See [using-the-api.md](using-the-api.md) for the full picture.
79
+
> Throughout the rest of this page, `nc` is the API surface. On Arduino, that's the `Notecard nc;` you declared. On stdcpp/ESP-IDF, after `Notecard nc(transport); Api api(nc);` you write `api.card.version()` instead — the calls are identical, the receiver isn't. See [using-the-api.md](using-the-api.md) for the full picture.
80
80
81
81
Walking through one full request — `card.version`, the simplest readable Notecard endpoint — top to bottom:
Copy file name to clipboardExpand all lines: docs/streaming-and-tree.md
+30-3Lines changed: 30 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,7 +42,21 @@ if (r && r.body()) {
42
42
}
43
43
```
44
44
45
-
In streaming mode the response bytes are gone once the SAX parser has run, so `r.body()` returns null. The same use case is served by committing the body shape ahead of time, either with a struct via `.into(struct)` or with a SAX `JsonSink` for fully ad-hoc parsing:
45
+
In streaming mode the response bytes are gone once the SAX parser has run, so `r.body()` returns null. Because a null return is also the legitimate "response carried no body field" result in tree mode, the two cases look identical to the caller — for mixed builds where the same code path may run in either mode, prefer `r.body_or_error()`, which returns an explicit `Error::NotReady` when the Notecard was in streaming mode:
46
+
47
+
```cpp
48
+
auto r = nc.note.get("data.qi").execute();
49
+
if (r) {
50
+
auto safe = r.body_or_error();
51
+
if (safe.has_value()) {
52
+
// *safe is a const JsonReader*, may still be null if no body field
53
+
} else {
54
+
// safe.error().code == Error::NotReady — running in streaming mode
55
+
}
56
+
}
57
+
```
58
+
59
+
The same use case is served by committing the body shape ahead of time, either with a struct via `.into(struct)` or with a SAX `JsonSink` for fully ad-hoc parsing:
46
60
47
61
```cpp
48
62
structReadings {
@@ -112,11 +126,24 @@ This table summarizes the surfaces touched by the mode choice. Every other surfa
Defining `NOTE_NO_BUFFERED` removes tree mode entirely (a saving of roughly 2 to 4 KB of flash). `NOTE_MINIMAL` sets this automatically.
134
+
Defining `NOTE_NO_JSON_TREE` removes tree mode entirely (a saving of roughly 2 to 4 KB of flash). `NOTE_MINIMAL` sets this automatically. The legacy name `NOTE_NO_BUFFERED` is honoured as an alias for backward compatibility; new code should use `NOTE_NO_JSON_TREE`.
135
+
136
+
`NOTE_NO_JSON_TREE` also drops two `std::unique_ptr<JsonReader>` fields and a discriminator bool from every `Response` struct — measured at **24 bytes per response** on 64-bit hosts (and ~12 bytes on 32-bit embedded targets) even for code that never calls `body()`. The header-only `body()` / `body_or_error()` / `parse(reader)` methods themselves are DCE'd by the linker when unreferenced, so the flash cost of leaving the flag off is near zero — the per-response RAM is the real penalty. Streaming-only projects on memory-constrained MCUs should set `NOTE_NO_JSON_TREE=1` for the RAM win even if their code already happens to be streaming-clean.
137
+
138
+
## Why there is no per-instance compile-time gate
139
+
140
+
`response.body()` exists when the build allows tree mode. There is no separate `StreamingResponse` type that omits `body()` at compile time when *this particular Notecard instance* uses streaming. That sounds like a useful safety net, and it was considered, but it doesn't pay off:
141
+
142
+
- The shape needed — `Notecard<note::Streaming>` vs `Notecard<note::JsonTree>` with mode-aware `Response` traits — would require templating `Notecard`, `StaticNotecard`, and the `Api` wrapper on a mode tag, and threading that tag through every generated endpoint. Existing `Notecard nc;` syntax would need a migration shim.
143
+
- It only catches one case: mixed builds where the user wrote streaming-only code but left `NOTE_NO_JSON_TREE` off. That case is already caught at runtime by `body_or_error()` — explicit `Error::NotReady` with an actionable message — and `was_streaming_parse()` is available for introspection. Whole-program elimination is covered by the macro flag.
144
+
- For users who only ever use streaming, setting `NOTE_NO_JSON_TREE=1` removes `body()` from the compiled API surface entirely. That's the same compile-time safety the template approach would deliver, without the architectural surface.
145
+
146
+
So the safety story is: **(1)** macro flag for whole-program compile-time removal; **(2)**`body_or_error()` + `was_streaming_parse()` for runtime detection in mixed builds; **(3)**`[[nodiscard]]` on `body()` so the compiler nags about unused returns. Per-instance compile-time gating isn't planned.
Copy file name to clipboardExpand all lines: notecard-api.openapi.overlay.json
+5Lines changed: 5 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -122,6 +122,11 @@
122
122
}
123
123
}
124
124
}
125
+
},
126
+
"/env/get": {
127
+
"get": {
128
+
"description": "When called without a `name` (or `names`), `env.get` returns the full environment-variable set known to the Notecard. The response size scales with the Notehub project and may exceed the default response buffer, surfacing as `Error::Overflow`. For this shape, prefer the streaming `.into(JsonSink&)` overload — body events stream off the wire and the env set can be arbitrarily large. As an alternative, `Notecard::set_response_buffer()` can enlarge the staging buffer if a bounded response is acceptable."
0 commit comments