1use crate::{
3 Decimal,
4 definition::{BackendReq, Predicate},
5 generation::{CostLimit, GenerateScreen, Repair, ScreenOutput, TemplateRef, Vocabulary},
6};
7use serde::{Deserialize, Deserializer, Serialize};
8use std::collections::{BTreeMap, BTreeSet};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
11#[serde(deny_unknown_fields)]
12pub struct Extract {
13 pub for_each: NoneOnly,
14 pub template: TemplateRef,
15 #[serde(deserialize_with = "projection")]
16 pub projection: Vec<String>,
17 pub vocabularies: BTreeMap<String, ExtractionVocabulary>,
18 pub output: ExtractionSchema,
19 pub distributions: Distributions,
20 pub repair: Repair,
21 pub screen: GenerateScreen,
22 pub limits: ExtractionLimits,
23 pub backend: BackendReq,
24 pub fallback: Fallback,
25}
26impl<'de> Deserialize<'de> for Extract {
27 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
28 #[derive(Deserialize)]
29 #[serde(remote = "Extract", deny_unknown_fields)]
30 struct Wire {
31 pub for_each: NoneOnly,
32 pub template: TemplateRef,
33 #[serde(deserialize_with = "projection")]
34 pub projection: Vec<String>,
35 pub vocabularies: BTreeMap<String, ExtractionVocabulary>,
36 pub output: ExtractionSchema,
37 pub distributions: Distributions,
38 pub repair: Repair,
39 pub screen: GenerateScreen,
40 pub limits: ExtractionLimits,
41 pub backend: BackendReq,
42 pub fallback: Fallback,
43 }
44 let v = Wire::deserialize(d)?;
45 if v.repair.max_attempts != 0 || v.screen.output != ScreenOutput::None {
46 return Err(serde::de::Error::custom(
47 "extraction requires no repair or output screening",
48 ));
49 }
50 fn declared(ty: &ExtractionType, vocab: &BTreeMap<String, ExtractionVocabulary>) -> bool {
51 match ty {
52 ExtractionType::Enum { vocabulary } => vocab.contains_key(vocabulary),
53 ExtractionType::List { item, .. } => declared(item, vocab),
54 ExtractionType::Record { fields } => fields.iter().all(|f| declared(&f.ty, vocab)),
55 _ => true,
56 }
57 }
58 if !v
59 .output
60 .fields
61 .iter()
62 .all(|f| declared(&f.ty, &v.vocabularies))
63 {
64 return Err(serde::de::Error::custom("undeclared extraction vocabulary"));
65 }
66 Ok(v)
67 }
68}
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
70#[serde(rename_all = "snake_case")]
71pub enum NoneOnly {
72 None,
73}
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
75#[serde(untagged, deny_unknown_fields)]
79pub enum ExtractionVocabulary {
80 Envelope(Vocabulary),
81 Input {
82 #[serde(deserialize_with = "input_ref")]
83 input: String,
84 },
85}
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
87#[serde(rename_all = "snake_case")]
88pub enum Distributions {
89 None,
90 WhenAvailable,
91}
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
93#[serde(deny_unknown_fields)]
94pub struct ExtractionLimits {
95 #[serde(deserialize_with = "crate::generation::deadline_share")]
96 pub deadline_share: Decimal,
97 pub max_cost_units: CostLimit,
98}
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
100#[serde(rename_all = "snake_case", deny_unknown_fields)]
101pub enum Fallback {
102 None,
103 #[serde(untagged)]
104 Rules(#[serde(deserialize_with = "crate::generation::object")] ExtractionRules),
105}
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
107#[serde(deny_unknown_fields)]
108pub struct ExtractionRules {
109 #[serde(deserialize_with = "rules_program")]
110 pub rules_program: String,
111 pub fill: Fill,
112}
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
114#[serde(rename_all = "snake_case")]
115pub enum Fill {
116 RequestFailure,
117 AbsentFields,
118}
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
120#[serde(deny_unknown_fields)]
121pub struct ExtractionSchema {
122 #[serde(deserialize_with = "fields::<_, 32>")]
123 pub fields: Vec<ExtractionField>,
124}
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
126#[serde(deny_unknown_fields)]
127pub struct ExtractionField {
128 pub name: String,
129 #[serde(rename = "type")]
130 pub ty: ExtractionType,
131}
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
133#[serde(rename_all = "snake_case", deny_unknown_fields)]
134pub enum ExtractionType {
135 Bool {},
136 Integer {
137 #[serde(deserialize_with = "integer_bounds")]
138 bounds: [i64; 2],
139 },
140 Decimal(DecimalBounds),
141 Text {
142 #[serde(deserialize_with = "crate::generation::bound::<_, 4096>")]
143 max_bytes: u64,
144 },
145 Date {},
146 Enum {
147 vocabulary: String,
148 },
149 List {
150 #[serde(deserialize_with = "list_item")]
151 item: Box<ExtractionType>,
152 #[serde(deserialize_with = "crate::generation::bound::<_, 64>")]
153 max_items: u64,
154 },
155 Record {
156 #[serde(deserialize_with = "record_fields")]
157 fields: Vec<ExtractionField>,
158 },
159}
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
161#[serde(deny_unknown_fields)]
162pub struct DecimalBounds {
163 pub bounds: [String; 2],
164 pub scale: u32,
165}
166impl<'de> Deserialize<'de> for DecimalBounds {
167 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
168 #[derive(Deserialize)]
169 #[serde(remote = "DecimalBounds", deny_unknown_fields)]
170 struct Wire {
171 bounds: [String; 2],
172 scale: u32,
173 }
174 let v = Wire::deserialize(d)?;
175 let [lo, hi] = [&v.bounds[0], &v.bounds[1]].map(|s| Decimal::parse(s));
176 if v.scale > 9
177 || v.bounds.iter().any(|s| {
178 s.split_once('.')
179 .is_some_and(|(_, f)| f.len() > v.scale as usize)
180 })
181 || !matches!((lo, hi), (Ok(lo), Ok(hi)) if lo <= hi)
182 {
183 return Err(serde::de::Error::custom(
184 "invalid extraction decimal bounds or scale",
185 ));
186 }
187 Ok(v)
188 }
189}
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
191#[serde(deny_unknown_fields)]
192pub struct AcceptModelDerived {
193 #[serde(deserialize_with = "field_ref")]
194 pub field: String,
195 pub validate: Validation,
196 pub min_mass: MinMass,
197 pub on_fail: OnFail,
198}
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
200#[serde(rename_all = "snake_case", deny_unknown_fields)]
201pub enum Validation {
202 None,
203 Predicate(Predicate),
204}
205#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
206#[serde(rename_all = "snake_case", deny_unknown_fields)]
207pub enum MinMass {
208 None,
209 #[serde(untagged)]
210 Threshold(#[serde(deserialize_with = "crate::generation::object")] MassThreshold),
211}
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
213#[serde(deny_unknown_fields)]
214pub struct MassThreshold {
215 pub option: String,
216 #[serde(deserialize_with = "probability_threshold")]
217 pub at_least: Decimal,
218 pub basis: MassBasis,
219}
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
221#[serde(rename_all = "snake_case", deny_unknown_fields)]
222pub enum MassBasis {
223 Calibration(String),
224 UncalibratedThreshold(#[serde(deserialize_with = "uncalibrated_reason")] String),
225}
226fn probability_threshold<'de, D: Deserializer<'de>>(d: D) -> Result<Decimal, D::Error> {
227 let v = Decimal::deserialize(d)?;
228 if v < Decimal::ZERO || v > Decimal::from_i64(1) {
229 return Err(serde::de::Error::custom(
230 "a probability threshold is in [0, 1]",
231 ));
232 }
233 Ok(v)
234}
235fn uncalibrated_reason<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
236 let v = String::deserialize(d)?;
237 if v.is_empty() {
238 return Err(serde::de::Error::custom(
239 "an uncalibrated threshold states its reason",
240 ));
241 }
242 Ok(v)
243}
244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
245#[serde(rename_all = "snake_case", deny_unknown_fields)]
246pub enum OnFail {
247 DropField {
248 #[serde(deserialize_with = "notice")]
249 notice: String,
250 },
251 Unresolved,
252}
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
254#[serde(rename_all = "snake_case", deny_unknown_fields)]
255pub enum AbsenceReason {
256 Missing,
257 WrongType,
258 TooLong,
259 ControlCharacter,
260 NotInVocabulary,
261 DuplicateItem,
262 TooManyItems,
263 OutOfBounds,
264 InvalidDate,
265 VocabularyUnavailable,
266 SourceUnavailable,
267 ScreenBlocked,
268 ScreenUnavailable,
269 RequestFailed { reason: RequestFailure },
270 FallbackNoMatch,
271 FallbackInvalid,
272}
273#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
274#[serde(rename_all = "snake_case")]
275pub enum RequestFailure {
276 BackendUnavailable,
277 InvalidBackendOutput,
278 DeadlineExceeded,
279 BudgetExhausted,
280 Cancelled,
281}
282fn fields<'de, D: Deserializer<'de>, const MAX: usize>(
283 d: D,
284) -> Result<Vec<ExtractionField>, D::Error> {
285 let v = Vec::<ExtractionField>::deserialize(d)?;
286 let mut names = BTreeSet::new();
287 if !(1..=MAX).contains(&v.len())
288 || v.iter()
289 .any(|f| !crate::parameter::is_parameter_name(&f.name) || !names.insert(&f.name))
290 {
291 return Err(serde::de::Error::custom(
292 "invalid extraction field count or names",
293 ));
294 }
295 Ok(v)
296}
297fn record_fields<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<ExtractionField>, D::Error> {
298 let v = fields::<D, 16>(d)?;
299 if v.iter().any(|f| {
300 matches!(
301 f.ty,
302 ExtractionType::List { .. } | ExtractionType::Record { .. }
303 )
304 }) {
305 return Err(serde::de::Error::custom("record subfields must be scalar"));
306 }
307 Ok(v)
308}
309fn list_item<'de, D: Deserializer<'de>>(d: D) -> Result<Box<ExtractionType>, D::Error> {
310 let v = ExtractionType::deserialize(d)?;
311 if !matches!(v, ExtractionType::Text { .. } | ExtractionType::Enum { .. }) {
312 return Err(serde::de::Error::custom("list items must be text or enum"));
313 }
314 Ok(Box::new(v))
315}
316fn integer_bounds<'de, D: Deserializer<'de>>(d: D) -> Result<[i64; 2], D::Error> {
317 let v = <[i64; 2]>::deserialize(d)?;
318 if v[0] > v[1] {
319 return Err(serde::de::Error::custom("reversed integer bounds"));
320 }
321 Ok(v)
322}
323fn notice<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
324 let v = String::deserialize(d)?;
325 if !crate::parameter::is_parameter_name(&v) {
326 return Err(serde::de::Error::custom("invalid notice code"));
327 }
328 Ok(v)
329}
330fn valid_ref(s: &str, prefix: &str, min: usize, max: usize) -> bool {
331 s.strip_prefix(prefix).is_some_and(|s| {
332 let parts: Vec<_> = s.split('/').collect();
333 (min..=max).contains(&parts.len())
334 && parts
335 .iter()
336 .all(|p| !p.is_empty() && !p.contains(':') && !p.chars().any(char::is_whitespace))
337 })
338}
339pub(crate) fn input_ref<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
340 let v = String::deserialize(d)?;
341 if !valid_ref(&v, "input:", 1, 2) {
342 return Err(serde::de::Error::custom("invalid input reference"));
343 }
344 Ok(v)
345}
346fn field_ref<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
347 let v = String::deserialize(d)?;
348 if !valid_ref(&v, "input:", 1, 2) && !valid_ref(&v, "step:", 2, 2) {
349 return Err(serde::de::Error::custom(
350 "invalid acceptance field reference",
351 ));
352 }
353 Ok(v)
354}
355fn projection<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<String>, D::Error> {
356 let v = Vec::<String>::deserialize(d)?;
357 if !(1..=8).contains(&v.len())
358 || v.iter()
359 .any(|s| !valid_ref(s, "input:", 1, 2) && !valid_ref(s, "step:", 1, 1))
360 {
361 return Err(serde::de::Error::custom("invalid extraction projection"));
362 }
363 Ok(v)
364}
365fn rules_program<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
366 let v = String::deserialize(d)?;
367 if !v
368 .split_once('@')
369 .is_some_and(|(n, ver)| !n.is_empty() && !ver.is_empty() && !ver.contains('@'))
370 || v.chars().any(char::is_whitespace)
371 {
372 return Err(serde::de::Error::custom(
373 "expected rules program name@version",
374 ));
375 }
376 Ok(v)
377}