From 8c5ce56d76530c9f4ef82f1e6171f1bda489dc41 Mon Sep 17 00:00:00 2001 From: KeviM Date: Sat, 26 Sep 2026 01:06:22 -0700 Subject: [PATCH 1/2] build: move Glaze to 9.0.0 Glaze 9 parses every fixture to the same output with the same allocations and 0.5 to 7% fewer instructions. It also rejects a body that is only a number with an empty exponent, such as `1e`, which 8.3 parsed to an empty result. Tests now cover malformed numbers at the end of the input and inside a document, on both the parser and client read paths. --- CHANGELOG.md | 11 +++++++++++ CMakeLists.txt | 7 +++---- tests/test_client_parity.cpp | 1 + tests/test_corpus.cpp | 13 +++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b8369a..fe6de6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- Glaze is now 9.0.0. Parsing gives the same results and allocates the same + memory, and it runs up to 7% fewer instructions on the larger fixtures. + +### Fixed + +- A body made of nothing but a number with an empty exponent, such as `1e`, + parsed as valid JSON and gave an empty result. The clients now return + `ParseError` and the `parse_*` functions throw, as for any malformed body. + ## [0.4.3] - 2026-09-25 ### Fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index f996053..625c8bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# Glaze 8.3 needs 3.31. +# Glaze needs 3.31. cmake_minimum_required(VERSION 3.31) # include/spc/version.hpp holds the version, so builds without CMake get it too. @@ -100,12 +100,11 @@ find_package(CURL 7.85 REQUIRED) include(FetchContent) # Glaze reads SPC's loosely shaped JSON. It is a private build dependency: no -# installed header includes it. kalshi-cpp pins the same version, so a build -# that fetches both SDKs gets a single glaze target. +# installed header includes it. FetchContent_Declare( glaze GIT_REPOSITORY https://github.com/stephenberry/glaze.git - GIT_TAG v8.3.0 + GIT_TAG v9.0.0 GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(glaze) diff --git a/tests/test_client_parity.cpp b/tests/test_client_parity.cpp index 3da2039..2d21683 100644 --- a/tests/test_client_parity.cpp +++ b/tests/test_client_parity.cpp @@ -327,6 +327,7 @@ TEST(ClientParity, AMalformedBodyFailsWithTheParsersMessage) { R"({"features":[)", R"({"features":[1,2)", R"({"a":"x")", + "1e", read_fixture("arcgis_day1_categorical.geojson").substr(0, 5000), }; diff --git a/tests/test_corpus.cpp b/tests/test_corpus.cpp index 3ae2da1..9efb920 100644 --- a/tests/test_corpus.cpp +++ b/tests/test_corpus.cpp @@ -6,6 +6,7 @@ #include "spc/models/outlook.hpp" #include "support/fixtures.hpp" +#include #include #include #include @@ -72,6 +73,18 @@ TEST(Corpus, MalformedBodyThrows) { std::runtime_error); } +TEST(Corpus, RejectsMalformedNumbers) { + // Glaze reads a number that ends the input on a different path from one + // followed by more JSON, so check both. + for (const std::string_view number : + {"1e", "1e+", "1E-", "1.", ".5", "01", "-", "+1", "1.e5"}) { + EXPECT_THROW((void)parse_categorical(number, 1), std::runtime_error) << number; + const std::string body = + std::format(R"({{"features":[{{"properties":{{"DN":{}}}}}]}})", number); + EXPECT_THROW((void)parse_categorical(body, 1), std::runtime_error) << body; + } +} + TEST(Corpus, ParsesOnlyTheViewItIsGiven) { // The closing brace sits just past the view, so reading past the view's end // would accept a truncated body. From e2097d06aba0bcf7d65638c6b633cc091c8eabf0 Mon Sep 17 00:00:00 2001 From: KeviM Date: Sat, 26 Sep 2026 01:10:30 -0700 Subject: [PATCH 2/2] test: check every empty-exponent spelling on the client read path --- tests/test_client_parity.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_client_parity.cpp b/tests/test_client_parity.cpp index 2d21683..8218f21 100644 --- a/tests/test_client_parity.cpp +++ b/tests/test_client_parity.cpp @@ -328,6 +328,8 @@ TEST(ClientParity, AMalformedBodyFailsWithTheParsersMessage) { R"({"features":[1,2)", R"({"a":"x")", "1e", + "1e+", + "1E-", read_fixture("arcgis_day1_categorical.geojson").substr(0, 5000), };