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(
254 Debug,
255 Clone,
256 Copy,
257 PartialEq,
258 Eq,
259 PartialOrd,
260 Ord,
261 Hash,
262 Serialize,
263 Deserialize,
264 schemars::JsonSchema,
265)]
266#[serde(rename_all = "snake_case", deny_unknown_fields)]
267pub enum AbsenceReason {
268 Missing,
269 WrongType,
270 TooLong,
271 ControlCharacter,
272 NotInVocabulary,
273 DuplicateItem,
274 TooManyItems,
275 OutOfBounds,
276 InvalidDate,
277 VocabularyUnavailable,
278 SourceUnavailable,
279 ScreenBlocked,
280 ScreenUnavailable,
281 RequestFailed { reason: RequestFailure },
282 FallbackNoMatch,
283 FallbackInvalid,
284}
285#[derive(
286 Debug,
287 Clone,
288 Copy,
289 PartialEq,
290 Eq,
291 PartialOrd,
292 Ord,
293 Hash,
294 Serialize,
295 Deserialize,
296 schemars::JsonSchema,
297)]
298#[serde(rename_all = "snake_case")]
299pub enum RequestFailure {
300 BackendUnavailable,
301 InvalidBackendOutput,
302 DeadlineExceeded,
303 BudgetExhausted,
304 Cancelled,
305}
306fn fields<'de, D: Deserializer<'de>, const MAX: usize>(
307 d: D,
308) -> Result<Vec<ExtractionField>, D::Error> {
309 let v = Vec::<ExtractionField>::deserialize(d)?;
310 let mut names = BTreeSet::new();
311 if !(1..=MAX).contains(&v.len())
312 || v.iter()
313 .any(|f| !crate::parameter::is_parameter_name(&f.name) || !names.insert(&f.name))
314 {
315 return Err(serde::de::Error::custom(
316 "invalid extraction field count or names",
317 ));
318 }
319 Ok(v)
320}
321fn record_fields<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<ExtractionField>, D::Error> {
322 let v = fields::<D, 16>(d)?;
323 if v.iter().any(|f| {
324 matches!(
325 f.ty,
326 ExtractionType::List { .. } | ExtractionType::Record { .. }
327 )
328 }) {
329 return Err(serde::de::Error::custom("record subfields must be scalar"));
330 }
331 Ok(v)
332}
333fn list_item<'de, D: Deserializer<'de>>(d: D) -> Result<Box<ExtractionType>, D::Error> {
334 let v = ExtractionType::deserialize(d)?;
335 if !matches!(v, ExtractionType::Text { .. } | ExtractionType::Enum { .. }) {
336 return Err(serde::de::Error::custom("list items must be text or enum"));
337 }
338 Ok(Box::new(v))
339}
340fn integer_bounds<'de, D: Deserializer<'de>>(d: D) -> Result<[i64; 2], D::Error> {
341 let v = <[i64; 2]>::deserialize(d)?;
342 if v[0] > v[1] {
343 return Err(serde::de::Error::custom("reversed integer bounds"));
344 }
345 Ok(v)
346}
347fn notice<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
348 let v = String::deserialize(d)?;
349 if !crate::parameter::is_parameter_name(&v) {
350 return Err(serde::de::Error::custom("invalid notice code"));
351 }
352 Ok(v)
353}
354fn valid_ref(s: &str, prefix: &str, min: usize, max: usize) -> bool {
355 s.strip_prefix(prefix).is_some_and(|s| {
356 let parts: Vec<_> = s.split('/').collect();
357 (min..=max).contains(&parts.len())
358 && parts
359 .iter()
360 .all(|p| !p.is_empty() && !p.contains(':') && !p.chars().any(char::is_whitespace))
361 })
362}
363pub(crate) fn input_ref<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
364 let v = String::deserialize(d)?;
365 if !valid_ref(&v, "input:", 1, 2) {
366 return Err(serde::de::Error::custom("invalid input reference"));
367 }
368 Ok(v)
369}
370fn field_ref<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
371 let v = String::deserialize(d)?;
372 if !valid_ref(&v, "input:", 1, 2) && !valid_ref(&v, "step:", 2, 2) {
373 return Err(serde::de::Error::custom(
374 "invalid acceptance field reference",
375 ));
376 }
377 Ok(v)
378}
379fn projection<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<String>, D::Error> {
380 let v = Vec::<String>::deserialize(d)?;
381 if !(1..=8).contains(&v.len())
382 || v.iter()
383 .any(|s| !valid_ref(s, "input:", 1, 2) && !valid_ref(s, "step:", 1, 3))
384 {
385 return Err(serde::de::Error::custom("invalid extraction projection"));
386 }
387 Ok(v)
388}
389fn rules_program<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
390 let v = String::deserialize(d)?;
391 if !v
392 .split_once('@')
393 .is_some_and(|(n, ver)| !n.is_empty() && !ver.is_empty() && !ver.contains('@'))
394 || v.chars().any(char::is_whitespace)
395 {
396 return Err(serde::de::Error::custom(
397 "expected rules program name@version",
398 ));
399 }
400 Ok(v)
401}