feat(MR-656): inline query strings in CLI and HTTP server

CLI:
- Add -e / --query-string <STRING> to omnigraph read and omnigraph change
- Exactly one of --query, --query-string, --alias is required (3-way XOR)
- Empty --query-string is rejected with a clear error

HTTP:
- New POST /query (read-only, clean field names: query/name/params/branch/snapshot)
- Mutations on /query are rejected with 400 -- use POST /change instead
- ChangeRequest fields polished: query (alias query_source), name (alias query_name)
- POST /read and POST /change remain byte-compatible for existing clients

Tests:
- cli.rs: -e happy-path on read/change, mutex error vs --query, empty -e rejected
- system_local.rs: inline -e read and -e change exercise the local flow
- system_remote.rs: inline -e read/change over HTTP plus direct /query 200/400
- server.rs: /query 200, /query 400 on mutation, /change legacy field alias
- openapi.rs: new /query path, QueryRequest schema, ChangeRequest field-name polish

Docs: cli.md (-e examples), cli-reference.md (read/change rows), server.md (/query)
Co-Authored-By: Ragnor Comerford <ragnor.comerford@gmail.com>
This commit is contained in:
Devin AI 2026-05-22 17:54:26 +00:00
parent aadfa11ecb
commit 4152d9d5dc
14 changed files with 708 additions and 75 deletions

View file

@ -159,6 +159,7 @@ const EXPECTED_PATHS: &[&str] = &[
"/healthz",
"/snapshot",
"/read",
"/query",
"/export",
"/change",
"/schema",
@ -278,6 +279,7 @@ const EXPECTED_SCHEMAS: &[&str] = &[
"BranchMergeRequest",
"ChangeOutput",
"ChangeRequest",
"QueryRequest",
"CommitListOutput",
"CommitOutput",
"ErrorCode",
@ -368,13 +370,65 @@ fn read_output_schema_has_expected_fields() {
#[test]
fn change_request_schema_has_expected_fields() {
// Canonical field names on the wire are now `query` and `name`. The
// schema descriptions document `query_source` and `query_name` as
// legacy deserialization aliases for backward compatibility.
let doc = openapi_json();
let schema = &doc["components"]["schemas"]["ChangeRequest"];
let props = schema["properties"].as_object().unwrap();
assert!(props.contains_key("query_source"));
assert!(props.contains_key("query_name"));
assert!(props.contains_key("query"));
assert!(props.contains_key("name"));
assert!(props.contains_key("params"));
assert!(props.contains_key("branch"));
let query_desc = schema["properties"]["query"]["description"]
.as_str()
.unwrap_or_default();
assert!(
query_desc.contains("query_source"),
"expected `query` description to mention the legacy `query_source` alias, got: {query_desc}"
);
}
#[test]
fn query_request_schema_has_expected_fields() {
let doc = openapi_json();
let schema = &doc["components"]["schemas"]["QueryRequest"];
let props = schema["properties"].as_object().unwrap();
assert!(props.contains_key("query"));
assert!(props.contains_key("name"));
assert!(props.contains_key("params"));
assert!(props.contains_key("branch"));
assert!(props.contains_key("snapshot"));
}
#[test]
fn query_request_query_is_required() {
let doc = openapi_json();
let schema = &doc["components"]["schemas"]["QueryRequest"];
let required: Vec<&str> = schema["required"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert!(required.contains(&"query"));
}
#[test]
fn openapi_query_is_post() {
let doc = openapi_json();
assert!(doc["paths"]["/query"]["post"].is_object());
}
#[test]
fn query_endpoint_documents_mutation_400() {
let doc = openapi_json();
let four_hundred = &doc["paths"]["/query"]["post"]["responses"]["400"];
let description = four_hundred["description"].as_str().unwrap_or_default();
assert!(
description.contains("mutations") || description.contains("POST /change"),
"expected /query 400 response to mention mutation rejection, got: {description}"
);
}
#[test]