diff options
| author | tv <tv@krebsco.de> | 2026-11-09 03:22:54 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-09 04:27:44 +0100 |
| commit | acafad962b5eea4772479d7f44912038d71e528e (patch) | |
| tree | 980fa159c58e10c2d2fa0f073363b9b8acd308cd /src/remake | |
| parent | 3c1403409877b0006594324a0daa5e9dd55ec3f4 (diff) | |
remake: replayWindow and archiveRetention in an octet-stream import's query
An import uploaded as `application/octet-stream` takes `rules`, `baseHeads`, `settle`
and `switchDelay` in the query, but not the retentions: for those the document had to
go base64-encoded in the JSON form. Now `replayWindow` and `archiveRetention` go in the
query too, as JSON like the rules (`{"epochs":1,"age":"7d"}`, as in the body).
Diffstat (limited to 'src/remake')
| -rw-r--r-- | src/remake/api.rs | 52 |
1 files changed, 50 insertions, 2 deletions
diff --git a/src/remake/api.rs b/src/remake/api.rs index a767706..a4d4cf0 100644 --- a/src/remake/api.rs +++ b/src/remake/api.rs @@ -119,6 +119,10 @@ pub(crate) struct StartQuery { settle: Option<Duration>, #[serde(default, deserialize_with = "crate::units::optional_duration")] switch_delay: Option<Duration>, + /// As JSON (as in the body), e.g. `{"epochs":1,"age":"7d"}`. + replay_window: Option<String>, + /// As JSON (as in the body). + archive_retention: Option<String>, } impl StartQuery { @@ -127,6 +131,8 @@ impl StartQuery { || self.base_heads.is_some() || self.settle.is_some() || self.switch_delay.is_some() + || self.replay_window.is_some() + || self.archive_retention.is_some() } /// An import request for a document uploaded as is. @@ -136,6 +142,15 @@ impl StartQuery { Some(rules) => serde_json::from_str(rules) .map_err(|error| format!("rules are not valid JSON: {error}"))?, }; + let retention = |given: &Option<String>, name: &str| { + given + .as_deref() + .map(|given| { + serde_json::from_str::<RetentionRequest>(given) + .map_err(|error| format!("{name} is not a valid retention: {error}")) + }) + .transpose() + }; Ok(StartRequest { kind: Some("import".to_owned()), spec: Some(json!({ @@ -151,7 +166,8 @@ impl StartQuery { }), settle: self.settle, switch_delay: self.switch_delay, - ..StartRequest::default() + replay_window: retention(&self.replay_window, "replayWindow")?, + archive_retention: retention(&self.archive_retention, "archiveRetention")?, }) } } @@ -344,7 +360,7 @@ async fn start_kind<H: Host>( } else if query.has_request_fields() { return error_response( StatusCode::BAD_REQUEST, - "rules, baseHeads, settle and switchDelay go in the query only for an application/octet-stream import; put them in the JSON body", + "rules, baseHeads, settle, switchDelay, replayWindow and archiveRetention go in the query only for an application/octet-stream import; put them in the JSON body", None, ); } else if body.is_empty() { @@ -997,3 +1013,35 @@ pub(crate) mod testing { .collect() } } + +#[cfg(test)] +mod tests { + use super::*; + + /// An octet-stream import takes the retentions from the query, as JSON. + #[test] + fn import_takes_retentions_from_the_query() { + let query = StartQuery { + replay_window: Some(r#"{"epochs":2,"age":"1h"}"#.to_owned()), + archive_retention: Some(r#"{"epochs":3}"#.to_owned()), + ..StartQuery::default() + }; + assert!(query.has_request_fields()); + let request = query.import_request(b"").unwrap(); + let replay = Retention::from(request.replay_window.unwrap()); + assert_eq!(replay.epochs, Some(2)); + assert_eq!(replay.age_ms, Some(3_600_000)); + let archive = Retention::from(request.archive_retention.unwrap()); + assert_eq!((archive.epochs, archive.age_ms), (Some(3), None)); + + let bad = StartQuery { + replay_window: Some(r#"{"days":2}"#.to_owned()), + ..StartQuery::default() + }; + assert!( + bad.import_request(b"") + .unwrap_err() + .contains("replayWindow") + ); + } +} |
