From 1813a8da2c9600805a268dd926332a14dd870811 Mon Sep 17 00:00:00 2001 From: curquiza Date: Mon, 26 Oct 2026 10:54:36 +0100 Subject: [PATCH 1/3] Say in the contract who refuses a value, the CLI or the API The rule was decided while writing `meili search` and lived only in a comment: the CLI validates an input when it cannot hand it to the API, and otherwise sends it as it is. A body has to be parsed before the flags can be merged into it, so `--file` and `--data` are validated. A flag value is one field and can always travel as text, so the API refuses it with its own code and names the value. Two bullets in the Input section of output.md. No code: #25 implements it, and its tests pin it. Co-Authored-By: Claude Opus 5 (1M context) --- docs/contract/output.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/contract/output.md b/docs/contract/output.md index 3c21822..53033a4 100644 --- a/docs/contract/output.md +++ b/docs/contract/output.md @@ -104,6 +104,8 @@ Destructive commands are those of the verb `delete`, as `commands.md` derives th - A body-field flag given together with `--file` or `--data` overrides that field in the body. A flag overrides its environment variable the same way. - The value `null` on a flag sends a JSON null: `--expires-at null`, `--stop-words null`. - A command never reads stdin unless `--file -` or `--project-api-key -` is given. This avoids hanging when an agent runs it in a pipeline. +- The CLI validates an input only when it cannot hand it to the API. A body has to be parsed before the flags can be merged into it, so `--file` and `--data` fail as above. A flag whose values are a closed list the document declares carries that list in its `--help`, so a value outside it is `cli_usage` and exit 2 before any call. +- Every other value travels as it is. A flag the route types as a number or an object is converted when its text converts, and sent as that text when it does not, so the API refuses it with its own code and names the value: `meili search --ranking-score-threshold abc` answers `invalid_search_ranking_score_threshold` and exits 1. The CLI never re-checks what the API checks, and never invents a message or a code for it. Converting anyway would be worse than either: `NaN` is written `null` in JSON, and an API that reads a null as "no value" would answer 0 and the wrong data. ## Pagination From 2ca15069606a753f71aca27cad2278bd4181eb55 Mon Sep 17 00:00:00 2001 From: curquiza Date: Mon, 26 Oct 2026 11:03:10 +0100 Subject: [PATCH 2/3] Say "spells a number", not "converts" `Number` converts more than a decimal number: `0x1` and an overflow to Infinity both convert, and both change the value in silence. The bullet now says which text becomes a number, and why the line is drawn there. Follows the fix on #25, found by CodeRabbit. Co-Authored-By: Claude Opus 5 (1M context) --- docs/contract/output.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contract/output.md b/docs/contract/output.md index 53033a4..3a7b2f6 100644 --- a/docs/contract/output.md +++ b/docs/contract/output.md @@ -105,7 +105,7 @@ Destructive commands are those of the verb `delete`, as `commands.md` derives th - The value `null` on a flag sends a JSON null: `--expires-at null`, `--stop-words null`. - A command never reads stdin unless `--file -` or `--project-api-key -` is given. This avoids hanging when an agent runs it in a pipeline. - The CLI validates an input only when it cannot hand it to the API. A body has to be parsed before the flags can be merged into it, so `--file` and `--data` fail as above. A flag whose values are a closed list the document declares carries that list in its `--help`, so a value outside it is `cli_usage` and exit 2 before any call. -- Every other value travels as it is. A flag the route types as a number or an object is converted when its text converts, and sent as that text when it does not, so the API refuses it with its own code and names the value: `meili search --ranking-score-threshold abc` answers `invalid_search_ranking_score_threshold` and exits 1. The CLI never re-checks what the API checks, and never invents a message or a code for it. Converting anyway would be worse than either: `NaN` is written `null` in JSON, and an API that reads a null as "no value" would answer 0 and the wrong data. +- Every other value travels as it is. A flag the route types as a number or an object is converted when its text spells one, and sent as that text when it does not, so the API refuses it with its own code and names the value: `meili search --ranking-score-threshold abc` answers `invalid_search_ranking_score_threshold` and exits 1. The CLI never re-checks what the API checks, and never invents a message or a code for it. Converting more than that would be worse than either, because it changes the value in silence: `Number` reads `0x1` as 1, and writes an overflow as `null`, which an API that reads a null as "no value" answers 0 and the wrong data for. ## Pagination From f493cd6ab0909eb9b9d38e74a6057f26361dcfef Mon Sep 17 00:00:00 2001 From: curquiza Date: Mon, 26 Oct 2026 11:08:44 +0100 Subject: [PATCH 3/3] Cover the flags the parser types, which the rule contradicted `--limit` and `--offset` are `Flags.integer` in `BaseCommand`, so `meili get indexes --limit abc` already answers `cli_usage` and exit 2 before any call. The second bullet said every value but a body travels as it is, which that falsifies. The line is now drawn where it really runs: a flag `--help` types, an integer or a closed list, is refused by the parser; a flag the command converts itself, a fractional number or an object, is sent as its text when the text does not spell the value. Co-Authored-By: Claude Opus 5 (1M context) --- docs/contract/output.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contract/output.md b/docs/contract/output.md index 3a7b2f6..81afbf1 100644 --- a/docs/contract/output.md +++ b/docs/contract/output.md @@ -104,8 +104,8 @@ Destructive commands are those of the verb `delete`, as `commands.md` derives th - A body-field flag given together with `--file` or `--data` overrides that field in the body. A flag overrides its environment variable the same way. - The value `null` on a flag sends a JSON null: `--expires-at null`, `--stop-words null`. - A command never reads stdin unless `--file -` or `--project-api-key -` is given. This avoids hanging when an agent runs it in a pipeline. -- The CLI validates an input only when it cannot hand it to the API. A body has to be parsed before the flags can be merged into it, so `--file` and `--data` fail as above. A flag whose values are a closed list the document declares carries that list in its `--help`, so a value outside it is `cli_usage` and exit 2 before any call. -- Every other value travels as it is. A flag the route types as a number or an object is converted when its text spells one, and sent as that text when it does not, so the API refuses it with its own code and names the value: `meili search --ranking-score-threshold abc` answers `invalid_search_ranking_score_threshold` and exits 1. The CLI never re-checks what the API checks, and never invents a message or a code for it. Converting more than that would be worse than either, because it changes the value in silence: `Number` reads `0x1` as 1, and writes an overflow as `null`, which an API that reads a null as "no value" answers 0 and the wrong data for. +- The CLI refuses an input only when it can say what is wrong without the API. A body has to be parsed before the flags can be merged into it, so `--file` and `--data` fail as above with `cli_input_invalid`. A flag that `--help` types, an integer or one of a closed list of values, is refused with `cli_usage` and exit 2 before any call. +- Every other value travels as it is, and the API refuses it with its own code and its own message. The command converts what the route types as a fractional number or as an object, `--ranking-score-threshold`, `--vector`, `--hybrid`: text that spells the value becomes it, text that does not is sent as that text, so `meili search --ranking-score-threshold abc` answers `invalid_search_ranking_score_threshold` and exits 1. The CLI never re-checks what the API checks, and never invents a message or a code for it. Converting more than that would change the value in silence: `Number` reads `0x1` as 1 and writes an overflow as `null`, which an API that reads a null as "no value" answers 0 and the wrong data for. ## Pagination