1use crate::{EvidenceSelector, PolicyKind, wire::representation_identity};
2use sim_kernel::{ContentId, Datum};
3use sim_lib_net_core::RetrievalUri;
4use std::{error::Error, fmt};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct DecodeLimits {
9 pub max_text_bytes: usize,
10 pub max_body_bytes: usize,
11 pub max_items: usize,
12}
13impl Default for DecodeLimits {
14 fn default() -> Self {
15 Self {
16 max_text_bytes: 1_048_576,
17 max_body_bytes: 16_777_216,
18 max_items: 4_096,
19 }
20 }
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum WebRecordError {
26 BoundExceeded(&'static str),
27 InvalidSelector,
28 ContentIdentity,
29 MissingDecision(PolicyKind),
30 InvalidRecord(&'static str),
31}
32impl fmt::Display for WebRecordError {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "{self:?}")
35 }
36}
37impl Error for WebRecordError {}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct WebCapture {
42 pub retrieval_uri: RetrievalUri,
43 pub content_id: ContentId,
44 pub body: Vec<u8>,
45 pub exchange: WebExchange,
46}
47impl WebCapture {
48 pub fn checked(
49 retrieval_uri: RetrievalUri,
50 content_id: ContentId,
51 body: Vec<u8>,
52 exchange: WebExchange,
53 limits: DecodeLimits,
54 ) -> Result<Self, WebRecordError> {
55 if body.len() > limits.max_body_bytes {
56 return Err(WebRecordError::BoundExceeded("raw body"));
57 }
58 let actual = Datum::Bytes(body.clone())
59 .content_id()
60 .map_err(|_| WebRecordError::ContentIdentity)?;
61 if actual != content_id {
62 return Err(WebRecordError::ContentIdentity);
63 }
64 Ok(Self {
65 retrieval_uri,
66 content_id,
67 body,
68 exchange,
69 })
70 }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct WebExchange {
76 pub method: String,
77 pub status: u16,
78 pub final_uri: String,
79 pub media_type: Option<String>,
80 pub received_bytes: u64,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct WebRepresentation {
86 pub content_id: ContentId,
87 pub raw_source_id: ContentId,
88 pub text: String,
89 pub codec: String,
90 pub codec_version: String,
91 pub media_type: String,
92 pub charset: Option<String>,
93 pub language: Option<String>,
94 pub fidelity_warnings: Vec<String>,
95}
96#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct RepresentationMetadata {
99 pub codec: String,
100 pub codec_version: String,
101 pub media_type: String,
102 pub charset: Option<String>,
103 pub language: Option<String>,
104 pub fidelity_warnings: Vec<String>,
105}
106impl WebRepresentation {
107 pub fn checked(
108 raw_source_id: ContentId,
109 text: String,
110 metadata: RepresentationMetadata,
111 limits: DecodeLimits,
112 ) -> Result<Self, WebRecordError> {
113 if text.len() > limits.max_text_bytes || metadata.fidelity_warnings.len() > limits.max_items
114 {
115 return Err(WebRecordError::BoundExceeded("representation"));
116 }
117 let identity = representation_identity(&raw_source_id, &text, &metadata);
118 let content_id = identity
119 .content_id()
120 .map_err(|_| WebRecordError::ContentIdentity)?;
121 if content_id == raw_source_id {
122 return Err(WebRecordError::ContentIdentity);
123 }
124 Ok(Self {
125 content_id,
126 raw_source_id,
127 text,
128 codec: metadata.codec,
129 codec_version: metadata.codec_version,
130 media_type: metadata.media_type,
131 charset: metadata.charset,
132 language: metadata.language,
133 fidelity_warnings: metadata.fidelity_warnings,
134 })
135 }
136 pub fn select(&self, start: u32, end: u32) -> Result<EvidenceSelector, WebRecordError> {
138 let count = self.text.chars().count();
139 if start > end || end as usize > count {
140 return Err(WebRecordError::InvalidSelector);
141 }
142 let exact = self
143 .text
144 .chars()
145 .skip(start as usize)
146 .take((end - start) as usize)
147 .collect();
148 EvidenceSelector::checked(self.content_id.clone(), start, end, exact, &self.text)
149 }
150}