1use crate::DocumentError;
5use crate::ids::ContentDigest;
6use crate::remote::RemoteCode;
7use crate::remote_context::{checked_document, invalid};
8use crate::run::RunRecord;
9use crate::scope::Handle;
10use serde::{Deserialize, Serialize};
11
12pub const PROTOCOL: &str = "rustev.remote-sink/1";
13pub const DESCRIBE: &str = "rustev.remote-sink-describe/1";
14pub const DELIVER: &str = "rustev.remote-sink-deliver/1";
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
17#[serde(deny_unknown_fields)]
18pub struct DescribeRequest {
19 pub schema: String,
20 pub versions: Vec<u32>,
21}
22crate::document::document!(DescribeRequest, DESCRIBE, crate::limits::REMOTE_V1);
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
25#[serde(deny_unknown_fields)]
26pub struct DescribeResponse {
27 pub schema: String,
28 pub version: u32,
29 pub record_schemas: Vec<RecordSchema>,
30 pub durability_states: Vec<String>,
32 pub deduplication_window_ms: u64,
33 pub max_record_bytes: u64,
34}
35crate::document::document!(DescribeResponse, DESCRIBE, crate::limits::REMOTE_V1);
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
38pub enum RecordSchema {
39 #[serde(rename = "rustev.run/1")]
40 V1,
41 #[serde(rename = "rustev.run/2")]
42 V2,
43 #[serde(rename = "rustev.run/3")]
44 V3,
45}
46impl RecordSchema {
47 pub const fn as_str(self) -> &'static str {
48 match self {
49 Self::V1 => crate::schema::RUN,
50 Self::V2 => crate::schema::RUN_V2,
51 Self::V3 => crate::schema::RUN_V3,
52 }
53 }
54}
55
56pub type RegistrationId = ContentDigest;
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
61#[serde(rename_all = "snake_case", deny_unknown_fields)]
62pub enum Signer {
63 Unsigned,
64 Ed25519 { key_id: String },
65 Sigstore { identity: String, issuer: String },
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
69#[serde(deny_unknown_fields)]
70pub struct DeliverRequest {
71 pub schema: String,
72 pub version: u32,
73 pub decision_id: Handle,
75 pub record_schema: RecordSchema,
76 pub record: String,
78 pub record_digest: ContentDigest,
79 pub registration_id: RegistrationId,
80 pub signer: Signer,
81 pub service_build: String,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub traceparent: Option<String>,
84}
85pub const DELIVER_LIMITS: crate::bounded::ParseLimits = crate::bounded::ParseLimits {
88 max_bytes: 32 * 1024 * 1024,
89 max_string_bytes: 24 * 1024 * 1024,
90 ..crate::limits::REMOTE_V1
91};
92checked_document!(
93 DeliverRequest,
94 DELIVER,
95 DELIVER_LIMITS,
96 |d: &DeliverRequest| {
97 crate::remote_context::validate_traceparent(d.traceparent.as_deref())?;
98 let run = RunRecord::parse(d.record.as_bytes())?;
99 if run.schema != d.record_schema.as_str() || run.decision_id != d.decision_id.as_str() {
100 return Err(invalid(
101 "record schema or decision id differs from envelope",
102 ));
103 }
104 let canonical = run.record_canonical().map_err(|e| invalid(e.to_string()))?;
105 if canonical != d.record.as_bytes() {
106 return Err(invalid("run record is not canonical"));
107 }
108 if run.record_digest().map_err(|e| invalid(e.to_string()))? != d.record_digest {
109 return Err(invalid("run record digest differs from envelope"));
110 }
111 Ok(())
112 }
113);
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
116#[serde(deny_unknown_fields)]
117pub struct DeliverResponse {
118 pub schema: String,
119 pub result: DeliveryResult,
120}
121crate::document::document!(DeliverResponse, DELIVER, crate::limits::REMOTE_V1);
122
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
124#[serde(rename_all = "snake_case", deny_unknown_fields)]
125pub enum DeliveryResult {
126 Accepted {
127 durability: String,
128 external_id: String,
129 accepted_digest: ContentDigest,
130 },
131 Duplicate {
132 accepted_digest: ContentDigest,
133 },
134 Conflict,
135 Rejected {
136 code: RemoteCode,
137 },
138}
139impl DeliverResponse {
140 pub fn validate_digest(&self, sent: &ContentDigest) -> Result<(), DocumentError> {
142 match &self.result {
143 DeliveryResult::Accepted {
144 accepted_digest, ..
145 }
146 | DeliveryResult::Duplicate { accepted_digest }
147 if accepted_digest != sent =>
148 {
149 Err(invalid("malformed_response: accepted digest differs"))
150 }
151 _ => Ok(()),
152 }
153 }
154}