Compare commits

..
Author SHA1 Message Date
Niels Lohmann fe7a90063d Throw type_error.321 for discarded values in to_bon8
write_bon8_value silently skipped discarded values, but the array/object
count marker still counted them, so [1, discarded] produced 82 91: a marker
announcing two elements followed by one. Throw type_error.321 like the other
binary writers do, at any nesting level.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-10 13:24:20 +02:00
6 changed files with 65 additions and 12 deletions

No files matched your search

+3 -7
View File
@@ -337,17 +337,14 @@ def is_remote(url) -> bool:
def download(url, docs) -> str: def download(url, docs) -> str:
"""Download url into assets/external and return the path relative to docs.""" """Download url into assets/external and return the path relative to docs."""
u = urllib.parse.urlparse(url if not url.startswith('//') else 'https:' + url) u = urllib.parse.urlparse(url if not url.startswith('//') else 'https:' + url)
if u.scheme.lower() not in ('http', 'https'):
raise ValueError(f'not an http(s) URL: {url}')
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT}) req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
# (the scheme is checked above) with urllib.request.urlopen(req, timeout=20) as r:
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
data = r.read() data = r.read()
ctype = r.headers.get_content_type() ctype = r.headers.get_content_type()
path = urllib.parse.unquote(u.path).lstrip('/') path = urllib.parse.unquote(u.path).lstrip('/')
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if u.query or not ext or path.endswith('/'): if u.query or not ext or path.endswith('/'):
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12] digest = hashlib.sha1(url.encode()).hexdigest()[:12]
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path)) rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
out = os.path.join(docs, rel) out = os.path.join(docs, rel)
@@ -388,8 +385,7 @@ def localize_images(docs) -> None:
def load_mkdocs_yml() -> dict: def load_mkdocs_yml() -> dict:
"""Load mkdocs.yml, ignoring tags like !ENV and !!python/name.""" """Load mkdocs.yml, ignoring tags like !ENV and !!python/name."""
with open(MKDOCS_YML, encoding='utf-8') as f: with open(MKDOCS_YML, encoding='utf-8') as f:
# (Loader is a yaml.SafeLoader) return yaml.load(f, Loader=Loader)
return yaml.load(f, Loader=Loader) # nosec B506
def localize_site_urls(docs, site_url) -> None: def localize_site_urls(docs, site_url) -> None:
@@ -41,6 +41,8 @@ With (2), the bytes written before the exception remain in the output adapter.
above 9223372036854775807, which BON8 cannot represent above 9223372036854775807, which BON8 cannot represent
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
valid UTF-8 valid UTF-8
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to BON8"`
## Complexity ## Complexity
+1 -1
View File
@@ -810,7 +810,7 @@ does not list an enumerator and it is therefore converted like the first listed
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or [`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON. nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, BSON, or BON8.
!!! failure "Example message" !!! failure "Example message"
@@ -809,6 +809,8 @@ class binary_writer
/*! /*!
@param[in] j JSON value to serialize @param[in] j JSON value to serialize
@throw type_error.321 if @a j or a value nested in it is discarded
*/ */
void write_bon8(const BasicJsonType& j) void write_bon8(const BasicJsonType& j)
{ {
@@ -2570,6 +2572,8 @@ class binary_writer
@param[in] j JSON value to serialize @param[in] j JSON value to serialize
@param[in,out] string_open whether the output ends with a non-empty @param[in,out] string_open whether the output ends with a non-empty
string that has not been terminated with 0xFF string that has not been terminated with 0xFF
@throw type_error.321 if @a j or a value nested in it is discarded
*/ */
void write_bon8_value(const BasicJsonType& j, bool& string_open) void write_bon8_value(const BasicJsonType& j, bool& string_open)
{ {
@@ -2678,7 +2682,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
break; throw_on_discarded(j, "BON8");
} }
} }
+5 -1
View File
@@ -22426,6 +22426,8 @@ class binary_writer
/*! /*!
@param[in] j JSON value to serialize @param[in] j JSON value to serialize
@throw type_error.321 if @a j or a value nested in it is discarded
*/ */
void write_bon8(const BasicJsonType& j) void write_bon8(const BasicJsonType& j)
{ {
@@ -24187,6 +24189,8 @@ class binary_writer
@param[in] j JSON value to serialize @param[in] j JSON value to serialize
@param[in,out] string_open whether the output ends with a non-empty @param[in,out] string_open whether the output ends with a non-empty
string that has not been terminated with 0xFF string that has not been terminated with 0xFF
@throw type_error.321 if @a j or a value nested in it is discarded
*/ */
void write_bon8_value(const BasicJsonType& j, bool& string_open) void write_bon8_value(const BasicJsonType& j, bool& string_open)
{ {
@@ -24295,7 +24299,7 @@ class binary_writer
case value_t::discarded: case value_t::discarded:
default: default:
break; throw_on_discarded(j, "BON8");
} }
} }
+49 -2
View File
@@ -82,9 +82,56 @@ TEST_CASE("BON8")
{ {
SECTION("discarded") SECTION("discarded")
{ {
// discarded values are not serialized // a discarded value cannot be serialized to BON8
const json j = json::value_t::discarded; const json j = json::value_t::discarded;
CHECK(json::to_bon8(j).empty()); CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
}
SECTION("discarded values nested in a container")
{
const json discarded = json::value_t::discarded;
SECTION("in a small array")
{
const json j = {1, discarded};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BON8", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
#endif
}
SECTION("as a small object value")
{
json j;
j["a"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/a) cannot serialize discarded value to BON8", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
#endif
}
SECTION("in a large array")
{
const json j = {1, 2, 3, 4, 5, discarded};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/5) cannot serialize discarded value to BON8", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
#endif
}
SECTION("as a large object value")
{
json j = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}};
j["f"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] (/f) cannot serialize discarded value to BON8", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bon8(j), "[json.exception.type_error.321] cannot serialize discarded value to BON8", json::type_error&);
#endif
}
} }
SECTION("null") SECTION("null")