use crate::DocumentError;
use crate::ids::ContentDigest;
use crate::remote::RemoteCode;
use crate::remote_context::{checked_document, invalid};
use crate::run::RunRecord;
use crate::scope::Handle;
use serde::{Deserialize, Serialize};
pub const PROTOCOL: &str = "rustev.remote-sink/1";
pub const DESCRIBE: &str = "rustev.remote-sink-describe/1";
pub const DELIVER: &str = "rustev.remote-sink-deliver/1";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DescribeRequest {
pub schema: String,
pub versions: Vec<u32>,
}
crate::document::document!(DescribeRequest, DESCRIBE, crate::limits::REMOTE_V1);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DescribeResponse {
pub schema: String,
pub version: u32,
pub record_schemas: Vec<RecordSchema>,
pub durability_states: Vec<String>,
pub deduplication_window_ms: u64,
pub max_record_bytes: u64,
}
crate::document::document!(DescribeResponse, DESCRIBE, crate::limits::REMOTE_V1);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub enum RecordSchema {
#[serde(rename = "rustev.run/1")]
V1,
#[serde(rename = "rustev.run/2")]
V2,
#[serde(rename = "rustev.run/3")]
V3,
}
impl RecordSchema {
pub const fn as_str(self) -> &'static str {
match self {
Self::V1 => crate::schema::RUN,
Self::V2 => crate::schema::RUN_V2,
Self::V3 => crate::schema::RUN_V3,
}
}
}
pub type RegistrationId = ContentDigest;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum Signer {
Unsigned,
Ed25519 { key_id: String },
Sigstore { identity: String, issuer: String },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeliverRequest {
pub schema: String,
pub version: u32,
pub decision_id: Handle,
pub record_schema: RecordSchema,
pub record: String,
pub record_digest: ContentDigest,
pub registration_id: RegistrationId,
pub signer: Signer,
pub service_build: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub traceparent: Option<String>,
}
pub const DELIVER_LIMITS: crate::bounded::ParseLimits = crate::bounded::ParseLimits {
max_bytes: 32 * 1024 * 1024,
max_string_bytes: 24 * 1024 * 1024,
..crate::limits::REMOTE_V1
};
checked_document!(
DeliverRequest,
DELIVER,
DELIVER_LIMITS,
|d: &DeliverRequest| {
crate::remote_context::validate_traceparent(d.traceparent.as_deref())?;
let run = RunRecord::parse(d.record.as_bytes())?;
if run.schema != d.record_schema.as_str() || run.decision_id != d.decision_id.as_str() {
return Err(invalid(
"record schema or decision id differs from envelope",
));
}
let canonical = run.record_canonical().map_err(|e| invalid(e.to_string()))?;
if canonical != d.record.as_bytes() {
return Err(invalid("run record is not canonical"));
}
if run.record_digest().map_err(|e| invalid(e.to_string()))? != d.record_digest {
return Err(invalid("run record digest differs from envelope"));
}
Ok(())
}
);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeliverResponse {
pub schema: String,
pub result: DeliveryResult,
}
crate::document::document!(DeliverResponse, DELIVER, crate::limits::REMOTE_V1);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum DeliveryResult {
Accepted {
durability: String,
external_id: String,
accepted_digest: ContentDigest,
},
Duplicate {
accepted_digest: ContentDigest,
},
Conflict,
Rejected {
code: RemoteCode,
},
}
impl DeliverResponse {
pub fn validate_digest(&self, sent: &ContentDigest) -> Result<(), DocumentError> {
match &self.result {
DeliveryResult::Accepted {
accepted_digest, ..
}
| DeliveryResult::Duplicate { accepted_digest }
if accepted_digest != sent =>
{
Err(invalid("malformed_response: accepted digest differs"))
}
_ => Ok(()),
}
}
}