type_bridge/
canonical_codec.rs1use std::time::{Duration, Instant};
4
5use type_bridge_contract::diagnostic::Diagnostic;
6use type_bridge_contract::limits::{
7 CodecLimits, MAX_CANONICAL_COLLECTION_LEN, MAX_CANONICAL_DEPTH, MAX_CANONICAL_STRING_BYTES,
8};
9use type_bridge_contract::projected_record::{
10 MAX_PROJECTED_ARCHIVE_BYTES, MAX_PROJECTED_ARCHIVE_RECORDS, MAX_PROJECTED_DECODED_WEIGHT,
11 MAX_PROJECTED_RECORD_BYTES,
12};
13use type_bridge_contract::sdk_diagnostic::SdkExecutionDiagnostic;
14
15use crate::{AnswerCancellation, Error, ModelValidationPhase};
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub struct CanonicalCodecLimits {
20 max_input_bytes: usize,
21 max_output_bytes: usize,
22 max_depth: usize,
23 max_records: usize,
24 max_members: usize,
25}
26
27impl Default for CanonicalCodecLimits {
28 fn default() -> Self {
29 Self {
30 max_input_bytes: MAX_PROJECTED_ARCHIVE_BYTES,
31 max_output_bytes: MAX_PROJECTED_ARCHIVE_BYTES,
32 max_depth: MAX_CANONICAL_DEPTH,
33 max_records: MAX_PROJECTED_ARCHIVE_RECORDS,
34 max_members: MAX_PROJECTED_DECODED_WEIGHT.min(MAX_CANONICAL_COLLECTION_LEN),
35 }
36 }
37}
38
39impl CanonicalCodecLimits {
40 #[must_use]
42 pub fn new() -> Self {
43 Self::default()
44 }
45
46 #[must_use]
48 pub fn with_max_input_bytes(mut self, value: usize) -> Self {
49 self.max_input_bytes = self.max_input_bytes.min(value);
50 self
51 }
52
53 #[must_use]
55 pub fn with_max_output_bytes(mut self, value: usize) -> Self {
56 self.max_output_bytes = self.max_output_bytes.min(value);
57 self
58 }
59
60 #[must_use]
62 pub fn with_max_depth(mut self, value: usize) -> Self {
63 self.max_depth = self.max_depth.min(value);
64 self
65 }
66
67 #[must_use]
69 pub fn with_max_records(mut self, value: usize) -> Self {
70 self.max_records = self.max_records.min(value);
71 self
72 }
73
74 #[must_use]
76 pub fn with_max_members(mut self, value: usize) -> Self {
77 self.max_members = self.max_members.min(value);
78 self
79 }
80}
81
82#[derive(Clone, Debug, Default)]
84pub struct CanonicalCodecOptions {
85 limits: CanonicalCodecLimits,
86 cancellation: AnswerCancellation,
87 timeout: Option<Duration>,
88}
89
90impl CanonicalCodecOptions {
91 #[must_use]
93 pub fn new() -> Self {
94 Self::default()
95 }
96
97 #[must_use]
99 pub fn with_limits(mut self, limits: CanonicalCodecLimits) -> Self {
100 self.limits = limits;
101 self
102 }
103
104 #[must_use]
106 pub fn with_cancellation(mut self, cancellation: AnswerCancellation) -> Self {
107 self.cancellation = cancellation;
108 self
109 }
110
111 #[must_use]
113 pub fn with_timeout(mut self, timeout: Duration) -> Self {
114 self.timeout = Some(timeout);
115 self
116 }
117}
118
119pub(crate) struct CapturedCanonicalCodecControl {
120 limits: CanonicalCodecLimits,
121 cancellation: AnswerCancellation,
122 deadline: Option<Instant>,
123 archive: bool,
124}
125
126impl CapturedCanonicalCodecControl {
127 pub(crate) fn capture(options: &CanonicalCodecOptions, archive: bool) -> crate::Result<Self> {
128 let deadline = options
129 .timeout
130 .map(|timeout| {
131 Instant::now().checked_add(timeout).ok_or_else(|| {
132 codec_error(SdkExecutionDiagnostic::projected_codec_deadline_exceeded())
133 })
134 })
135 .transpose()?;
136 Ok(Self {
137 limits: options.limits,
138 cancellation: options.cancellation.clone(),
139 deadline,
140 archive,
141 })
142 }
143
144 pub(crate) fn check(&self) -> crate::Result<()> {
145 if self.cancellation.is_cancelled() {
146 Err(codec_error(
147 SdkExecutionDiagnostic::projected_codec_cancelled(),
148 ))
149 } else if self
150 .deadline
151 .is_some_and(|deadline| Instant::now() >= deadline)
152 {
153 Err(codec_error(
154 SdkExecutionDiagnostic::projected_codec_deadline_exceeded(),
155 ))
156 } else {
157 Ok(())
158 }
159 }
160
161 pub(crate) fn input_limits(&self) -> CodecLimits {
162 let ceiling = if self.archive {
163 MAX_PROJECTED_ARCHIVE_BYTES
164 } else {
165 MAX_PROJECTED_RECORD_BYTES
166 };
167 let max_bytes = self.limits.max_input_bytes.min(ceiling);
168 CodecLimits {
169 max_bytes,
170 max_depth: self.limits.max_depth,
171 max_collection_len: self.limits.max_members,
172 max_string_bytes: MAX_CANONICAL_STRING_BYTES.min(max_bytes),
173 }
174 }
175
176 pub(crate) fn output_limits(&self) -> CodecLimits {
177 let ceiling = if self.archive {
178 MAX_PROJECTED_ARCHIVE_BYTES
179 } else {
180 MAX_PROJECTED_RECORD_BYTES
181 };
182 let max_bytes = self.limits.max_output_bytes.min(ceiling);
183 CodecLimits {
184 max_bytes,
185 max_depth: self.limits.max_depth,
186 max_collection_len: self.limits.max_members,
187 max_string_bytes: MAX_CANONICAL_STRING_BYTES.min(max_bytes),
188 }
189 }
190
191 pub(crate) fn check_record_count(&self, count: usize) -> crate::Result<()> {
192 if count > self.limits.max_records {
193 Err(codec_error(
194 SdkExecutionDiagnostic::projected_codec_member_limit(),
195 ))
196 } else {
197 Ok(())
198 }
199 }
200
201 pub(crate) fn check_decoded_weight(&self, weight: usize) -> crate::Result<()> {
202 if weight > self.limits.max_members {
203 Err(codec_error(
204 SdkExecutionDiagnostic::projected_codec_member_limit(),
205 ))
206 } else {
207 Ok(())
208 }
209 }
210
211 pub(crate) fn check_output_bytes(&self, bytes: usize) -> crate::Result<()> {
212 if bytes > self.limits.max_output_bytes {
213 Err(codec_error(
214 SdkExecutionDiagnostic::projected_codec_output_limit(),
215 ))
216 } else {
217 Ok(())
218 }
219 }
220}
221
222pub(crate) fn input_error(error: Diagnostic) -> Error {
223 limit_error(error, true)
224}
225
226pub(crate) fn output_error(error: Diagnostic) -> Error {
227 limit_error(error, false)
228}
229
230fn limit_error(error: Diagnostic, input: bool) -> Error {
231 let diagnostic = match error.code().as_str() {
232 "canonical_json_too_deep" => SdkExecutionDiagnostic::projected_codec_depth_limit(),
233 "canonical_collection_too_large" => SdkExecutionDiagnostic::projected_codec_member_limit(),
234 "canonical_json_too_large" | "canonical_string_too_large" if input => {
235 SdkExecutionDiagnostic::projected_codec_input_limit()
236 }
237 "canonical_json_too_large" | "canonical_string_too_large" => {
238 SdkExecutionDiagnostic::projected_codec_output_limit()
239 }
240 _ => return Error::from_contract_diagnostic(error),
241 };
242 codec_error(diagnostic)
243}
244
245fn codec_error(error: SdkExecutionDiagnostic) -> Error {
246 Error::from_sdk_execution(error, ModelValidationPhase::Input)
247}