mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-07-06 02:52:11 +02:00
feat: canonical POST /load, deprecate /ingest (RFC-009 Phase 5) (#222)
* feat(server): canonical POST /load, deprecate /ingest (RFC-009 Phase 5) The CLI's non-deprecated `load` verb rode the deprecated `/ingest` route, so `/ingest`'s eventual removal would silently break it. Add a canonical `/load`, mirroring the shipped `/mutate`↔`/change` and `/query`↔`/read` pattern. - Extract `server_ingest`'s body into a shared `run_ingest` (branch-exists / fork-if-`from`, Cedar auth, admission, `load_as`, `IngestOutput` mapping). - `server_load` (canonical) → `run_ingest`, `Json<IngestOutput>`. - `server_ingest` (deprecated) → `run_ingest` + `#[deprecated]` + RFC 9745/8288 `Deprecation: true` / `Link: </load>; rel="successor-version"` headers. - Router mounts `/load` (same 32 MB body limit) beside `/ingest`; OpenAPI `paths(...)` gains `server_load` and flags `server_ingest` deprecated. `/load` reuses `IngestRequest`/`IngestOutput`, exactly as canonical `/mutate` reuses `Change*` — a DTO rename is a separate, larger change (out of scope). openapi.json regenerated. Tests: openapi `/load` present + not deprecated, `/ingest` deprecated, `/load` bearer-secured; data_routes `/load` happy path + `/ingest` deprecation headers. Existing `/ingest` route tests stay green (the shim is unchanged). Docs: server.md endpoint table; RFC-009 Phase 5 marked landed (incl. the hand-mount-vs-utoipa-axum registration finding). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(cli): point remote load at /load (RFC-009 Phase 5) `GraphClient::load`'s remote arm now POSTs to the canonical `/load` route instead of the deprecated `/ingest`; the deprecated `ingest` verb keeps riding `/ingest`. `parity_load` exercises `/load` on the remote arm (its documented flip); the matrix exclusions comment is updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6144bb18d6
commit
8726ca92ec
9 changed files with 325 additions and 57 deletions
|
|
@ -620,6 +620,83 @@ async fn change_endpoint_emits_deprecation_headers() {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn load_endpoint_loads_into_existing_branch() {
|
||||
// Canonical bulk-load endpoint (RFC-009 Phase 5). Same wire shape as
|
||||
// /ingest, no deprecation signal.
|
||||
let (_temp, app) = app_for_loaded_graph().await;
|
||||
let request = IngestRequest {
|
||||
branch: Some("main".to_string()),
|
||||
from: None,
|
||||
mode: Some(LoadMode::Merge),
|
||||
data: r#"{"type":"Person","data":{"name":"Loaded","age":7}}"#.to_string(),
|
||||
};
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/load")
|
||||
.method(Method::POST)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&request).unwrap()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert!(
|
||||
response.headers().get("deprecation").is_none(),
|
||||
"POST /load must not advertise itself as deprecated"
|
||||
);
|
||||
let body_bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let body: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
assert_eq!(body["branch"], "main");
|
||||
assert_eq!(body["tables"][0]["table_key"], "node:Person");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn ingest_endpoint_emits_deprecation_headers() {
|
||||
// `/ingest` is the deprecated alias of `/load` (RFC-009 Phase 5): flagged
|
||||
// at runtime per RFC 9745 (`Deprecation: true`) + RFC 8288 (`Link: </load>;
|
||||
// rel="successor-version"`). The OpenAPI side is covered by
|
||||
// `openapi_ingest_is_deprecated` in tests/openapi.rs.
|
||||
let (_temp, app) = app_for_loaded_graph().await;
|
||||
let request = IngestRequest {
|
||||
branch: Some("main".to_string()),
|
||||
from: None,
|
||||
mode: Some(LoadMode::Merge),
|
||||
data: r#"{"type":"Person","data":{"name":"Legacyer","age":33}}"#.to_string(),
|
||||
};
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/ingest")
|
||||
.method(Method::POST)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&request).unwrap()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get("deprecation")
|
||||
.and_then(|v| v.to_str().ok()),
|
||||
Some("true"),
|
||||
"POST /ingest must advertise `Deprecation: true` (RFC 9745)"
|
||||
);
|
||||
assert_eq!(
|
||||
response.headers().get("link").and_then(|v| v.to_str().ok()),
|
||||
Some("</load>; rel=\"successor-version\""),
|
||||
"POST /ingest must point at /load via `Link` rel=successor-version (RFC 8288)"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn read_endpoint_emits_deprecation_headers() {
|
||||
// `/read` is kept indefinitely for byte-stable back-compat but flagged
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ const EXPECTED_PATHS: &[&str] = &[
|
|||
"/queries/{name}",
|
||||
"/schema",
|
||||
"/schema/apply",
|
||||
"/load",
|
||||
"/ingest",
|
||||
"/branches",
|
||||
"/branches/{branch}",
|
||||
|
|
@ -300,6 +301,32 @@ fn openapi_ingest_is_post() {
|
|||
assert!(doc["paths"]["/ingest"]["post"].is_object());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openapi_load_is_not_deprecated() {
|
||||
// RFC-009 Phase 5: /load is the canonical bulk-load endpoint.
|
||||
let doc = openapi_json();
|
||||
assert!(doc["paths"]["/load"]["post"].is_object());
|
||||
let deprecated = doc["paths"]["/load"]["post"]
|
||||
.get("deprecated")
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
assert!(
|
||||
!deprecated,
|
||||
"/load is the canonical load endpoint and must not be deprecated"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openapi_ingest_is_deprecated() {
|
||||
// RFC-009 Phase 5: /ingest is now the deprecated alias of /load.
|
||||
let doc = openapi_json();
|
||||
assert_eq!(
|
||||
doc["paths"]["/ingest"]["post"]["deprecated"],
|
||||
serde_json::Value::Bool(true),
|
||||
"/ingest must be flagged deprecated now that /load is canonical"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openapi_branches_supports_get_and_post() {
|
||||
let doc = openapi_json();
|
||||
|
|
@ -705,6 +732,7 @@ fn protected_endpoints_reference_bearer_token_security() {
|
|||
("/schema/apply", "post"),
|
||||
("/queries", "get"),
|
||||
("/queries/{name}", "post"),
|
||||
("/load", "post"),
|
||||
("/ingest", "post"),
|
||||
("/export", "post"),
|
||||
("/snapshot", "get"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue