1use std::{collections::BTreeMap, sync::Arc};
10
11use serde::{Deserialize, Serialize};
12use serde_json::{value::RawValue, Value};
13
14use crate::{AnalysisError, AnalysisResult, Analyzer};
15
16mod config;
17mod hash;
18mod json;
19pub(crate) mod limits;
20mod profiles;
21
22pub use hash::AnalyzerFingerprint;
23pub use limits::AnalyzerLimits;
24use profiles::RuntimeProfiles;
25
26const FORMAT: &str = "uqa-analyzer";
27const FORMAT_VERSION: u32 = 1;
28const ALGORITHM_REVISION: u32 = 1;
29const SOURCE_MAPPING_REVISION: u32 = 1;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum TokenLengthPolicy {
35 EmittedTokens,
37 DiscountOverlaps,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43struct DescriptorData {
44 format: String,
45 format_version: u32,
46 algorithm_revision: u32,
47 source_mapping_revision: u32,
48 length_policy: TokenLengthPolicy,
49 pipeline: Value,
50 runtime_profiles: RuntimeProfiles,
51}
52
53#[derive(Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55struct Wire {
56 descriptor: DescriptorData,
57 fingerprint: AnalyzerFingerprint,
58}
59
60#[derive(Debug)]
62pub struct AnalyzerDescriptor {
63 data: DescriptorData,
64 fingerprint: AnalyzerFingerprint,
65 wire: Box<RawValue>,
66}
67
68pub(crate) struct ResolvedDescriptor {
69 pub descriptor: Arc<AnalyzerDescriptor>,
70 #[cfg(feature = "nori")]
71 pub nori: crate::nori::pipeline::ResolvedNoriPipeline,
72}
73
74impl Serialize for AnalyzerDescriptor {
75 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
76 self.wire.serialize(serializer)
77 }
78}
79
80impl AnalyzerDescriptor {
81 pub fn resolve(
82 config: &Analyzer,
83 length_policy: TokenLengthPolicy,
84 limits: AnalyzerLimits,
85 ) -> AnalysisResult<Arc<Self>> {
86 Ok(Self::resolve_inputs(
87 config,
88 length_policy,
89 limits,
90 #[cfg(feature = "nori")]
91 &crate::nori::NoriResources::default(),
92 )?
93 .descriptor)
94 }
95
96 pub(crate) fn resolve_inputs(
97 config: &Analyzer,
98 length_policy: TokenLengthPolicy,
99 limits: AnalyzerLimits,
100 #[cfg(feature = "nori")] resources: &crate::nori::NoriResources,
101 ) -> AnalysisResult<ResolvedDescriptor> {
102 config::check_config(config, limits)?;
103 let profiles = RuntimeProfiles::resolve(config)?;
104 #[cfg(feature = "nori")]
105 let (config, nori) = {
106 let mut config = config.clone();
107 let nori =
108 crate::nori::pipeline::ResolvedNoriPipeline::resolve(&mut config, resources)?;
109 (config, nori)
110 };
111 #[cfg(feature = "nori")]
112 let config = &config;
113 let pipeline = config::snapshot(config, limits)?;
114 let descriptor = Self::finish(
115 DescriptorData {
116 format: FORMAT.into(),
117 format_version: FORMAT_VERSION,
118 algorithm_revision: ALGORITHM_REVISION,
119 source_mapping_revision: SOURCE_MAPPING_REVISION,
120 length_policy,
121 pipeline,
122 runtime_profiles: profiles,
123 },
124 limits,
125 )?;
126 Ok(ResolvedDescriptor {
127 descriptor,
128 #[cfg(feature = "nori")]
129 nori,
130 })
131 }
132
133 pub fn from_json(json: &str, limits: AnalyzerLimits) -> AnalysisResult<Arc<Self>> {
135 limits::check_limit(
136 "analyzer descriptor bytes",
137 json.len(),
138 limits.max_descriptor_bytes,
139 )?;
140 json::check_unique_keys(json)?;
141 let wire: Wire = serde_json::from_str(json)?;
142 let data_bytes = limits::encode(
143 &canonical(&serde_json::to_value(&wire.descriptor)?),
144 limits.max_descriptor_bytes,
145 true,
146 )?;
147 let fingerprint = AnalyzerFingerprint::digest(&data_bytes);
148 if wire.fingerprint != fingerprint {
149 return Err(AnalysisError::DescriptorFingerprint {
150 expected: wire.fingerprint,
151 actual: fingerprint,
152 });
153 }
154 Self::check_revision(&wire.descriptor)?;
155 let config = config::restore(&wire.descriptor.pipeline, limits)?;
156 if RuntimeProfiles::resolve(&config)? != wire.descriptor.runtime_profiles {
157 return Err(invalid(
158 "runtime Unicode or regular-expression profile differs",
159 ));
160 }
161 Self::finish(wire.descriptor, limits)
162 }
163
164 fn finish(data: DescriptorData, limits: AnalyzerLimits) -> AnalysisResult<Arc<Self>> {
165 let bytes = limits::encode(
166 &canonical(&serde_json::to_value(&data)?),
167 limits.max_descriptor_bytes,
168 true,
169 )?;
170 let fingerprint = AnalyzerFingerprint::digest(&bytes);
171 let wire = canonical(&serde_json::to_value(Wire {
172 descriptor: data.clone(),
173 fingerprint,
174 })?);
175 let bytes = limits::encode(&wire, limits.max_descriptor_bytes, true)?;
176 let json = String::from_utf8(bytes).expect("JSON serializer returns UTF-8");
177 Ok(Arc::new(Self {
178 data,
179 fingerprint,
180 wire: RawValue::from_string(json)?,
181 }))
182 }
183
184 fn check_revision(data: &DescriptorData) -> AnalysisResult<()> {
185 if data.format != FORMAT {
186 return Err(invalid("unknown descriptor format"));
187 }
188 for (component, expected, actual) in [
189 ("format", FORMAT_VERSION, data.format_version),
190 ("algorithm", ALGORITHM_REVISION, data.algorithm_revision),
191 (
192 "source mapping",
193 SOURCE_MAPPING_REVISION,
194 data.source_mapping_revision,
195 ),
196 ] {
197 if expected != actual {
198 return Err(AnalysisError::DescriptorRevision {
199 component,
200 expected,
201 actual,
202 });
203 }
204 }
205 Ok(())
206 }
207
208 pub fn fingerprint(&self) -> AnalyzerFingerprint {
209 self.fingerprint
210 }
211 pub fn length_policy(&self) -> TokenLengthPolicy {
212 self.data.length_policy
213 }
214 pub fn canonical_json(&self) -> &str {
215 self.wire.get()
216 }
217
218 pub fn configuration(&self) -> AnalysisResult<Analyzer> {
220 Ok(serde_json::from_value(self.data.pipeline.clone())?)
221 }
222
223 pub(crate) fn validate_limits(&self, limits: AnalyzerLimits) -> AnalysisResult<()> {
224 limits::check_limit(
225 "analyzer descriptor bytes",
226 self.canonical_json().len(),
227 limits.max_descriptor_bytes,
228 )?;
229 config::check_config(&self.configuration()?, limits)
230 }
231}
232
233fn invalid(reason: &'static str) -> AnalysisError {
234 AnalysisError::Descriptor(reason)
235}
236
237fn canonical(value: &Value) -> Value {
238 match value {
239 Value::Object(object) => Value::Object(
240 object
241 .iter()
242 .map(|(key, value)| (key.clone(), canonical(value)))
243 .collect::<BTreeMap<_, _>>()
244 .into_iter()
245 .collect(),
246 ),
247 Value::Array(array) => Value::Array(array.iter().map(canonical).collect()),
248 _ => value.clone(),
249 }
250}