1use std::collections::{BTreeMap, BTreeSet};
4
5use presolve_parser::{ParsedFile, SourceSpan};
6
7use crate::{
8 normalize_authored_semantics_v1, AuthoredSemanticCandidateKindV1,
9 AuthoredSemanticNormalizationErrorV1, AuthoredSourceRangeV1,
10 CanonicalAuthoredDeclarationKindV1, CanonicalAuthoredSemanticModelV1, CanonicalIntrinsicKindV1,
11 ResolvedAuthoredSemanticCandidateV1, ResolvedIntrinsicIdentityV1,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct FormValidationDefinitionSiteV1 {
16 pub subject: String,
17 pub owner_field_subject: String,
18 pub declaration_source: AuthoredSourceRangeV1,
19 pub callee_source: AuthoredSourceRangeV1,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ResolvedFormValidationDefinitionV1 {
24 pub callee_source: AuthoredSourceRangeV1,
25 pub kind: ResolvedFormValidationDefinitionKindV1,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum ResolvedFormValidationDefinitionKindV1 {
30 PresolveRule {
31 validation_identity: ResolvedIntrinsicIdentityV1,
32 },
33 StandardSchema {
34 module_specifier: String,
35 export_name: String,
36 declaration_modules: Vec<String>,
37 input_type: Option<String>,
38 output_type: Option<String>,
39 },
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum FormValidationDefinitionLoweringErrorV1 {
44 SourcePathMismatch,
45 DuplicateResolution { start: usize, end: usize },
46 UnknownResolution { start: usize, end: usize },
47 ValidationOutsideCanonicalField { subject: String },
48 InvalidAuthoredSemantics(AuthoredSemanticNormalizationErrorV1),
49}
50
51impl std::fmt::Display for FormValidationDefinitionLoweringErrorV1 {
52 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::SourcePathMismatch => write!(
55 formatter,
56 "Form Field and validation products must describe the same source file"
57 ),
58 Self::DuplicateResolution { start, end } => {
59 write!(
60 formatter,
61 "duplicate Form validation resolution at {start}..{end}"
62 )
63 }
64 Self::UnknownResolution { start, end } => {
65 write!(
66 formatter,
67 "unknown Form validation resolution at {start}..{end}"
68 )
69 }
70 Self::ValidationOutsideCanonicalField { subject } => write!(
71 formatter,
72 "resolved Form validation `{subject}` is not owned by a canonical Form Field"
73 ),
74 Self::InvalidAuthoredSemantics(error) => error.fmt(formatter),
75 }
76 }
77}
78
79impl std::error::Error for FormValidationDefinitionLoweringErrorV1 {}
80
81pub fn form_validation_definition_sites_v1(
82 parsed: &ParsedFile,
83 component_model: &CanonicalAuthoredSemanticModelV1,
84) -> Result<Vec<FormValidationDefinitionSiteV1>, FormValidationDefinitionLoweringErrorV1> {
85 if component_model.source_path != parsed.path {
86 return Err(FormValidationDefinitionLoweringErrorV1::SourcePathMismatch);
87 }
88 let components = component_model
89 .declarations
90 .iter()
91 .filter(|declaration| declaration.kind == CanonicalAuthoredDeclarationKindV1::Component)
92 .map(|declaration| declaration.subject.as_str())
93 .collect::<BTreeSet<_>>();
94 let mut sites = Vec::new();
95 for class in parsed
96 .classes
97 .iter()
98 .filter(|class| components.contains(class.name.as_str()))
99 {
100 for property in &class.properties {
101 let Some(shape) = property.form_definition_shape.as_ref() else {
102 continue;
103 };
104 for field in &shape.fields {
105 let owner = format!("{}.{}.{}", class.name, property.name, field.path.join("."));
106 for (ordinal, validation) in field.validations.iter().enumerate() {
107 let Some(callee_span) = validation.callee_span else {
108 continue;
109 };
110 sites.push(FormValidationDefinitionSiteV1 {
111 subject: format!("{owner}.validation.{ordinal}"),
112 owner_field_subject: owner.clone(),
113 declaration_source: range(validation.span),
114 callee_source: range(callee_span),
115 });
116 }
117 }
118 }
119 }
120 sites.sort_by_key(|site| (site.callee_source.start, site.subject.clone()));
121 Ok(sites)
122}
123
124pub fn lower_form_validation_definitions_v1(
125 parsed: &ParsedFile,
126 component_model: &CanonicalAuthoredSemanticModelV1,
127 field_model: &CanonicalAuthoredSemanticModelV1,
128 resolutions: impl IntoIterator<Item = ResolvedFormValidationDefinitionV1>,
129) -> Result<CanonicalAuthoredSemanticModelV1, FormValidationDefinitionLoweringErrorV1> {
130 if field_model.source_path != parsed.path {
131 return Err(FormValidationDefinitionLoweringErrorV1::SourcePathMismatch);
132 }
133 let sites = form_validation_definition_sites_v1(parsed, component_model)?;
134 let known = sites
135 .iter()
136 .map(|site| range_key(site.callee_source))
137 .collect::<BTreeSet<_>>();
138 let fields = field_model
139 .declarations
140 .iter()
141 .filter(|declaration| declaration.kind == CanonicalAuthoredDeclarationKindV1::FormField)
142 .map(|declaration| declaration.subject.as_str())
143 .collect::<BTreeSet<_>>();
144 let mut by_site = BTreeMap::new();
145 for resolution in resolutions {
146 let key = range_key(resolution.callee_source);
147 if !known.contains(&key) {
148 return Err(FormValidationDefinitionLoweringErrorV1::UnknownResolution {
149 start: key.0,
150 end: key.1,
151 });
152 }
153 if by_site.insert(key, resolution).is_some() {
154 return Err(
155 FormValidationDefinitionLoweringErrorV1::DuplicateResolution {
156 start: key.0,
157 end: key.1,
158 },
159 );
160 }
161 }
162 let candidates = sites
163 .iter()
164 .filter_map(|site| {
165 by_site
166 .get(&range_key(site.callee_source))
167 .map(|proof| (site, proof))
168 })
169 .map(|(site, proof)| {
170 if !fields.contains(site.owner_field_subject.as_str()) {
171 return Err(
172 FormValidationDefinitionLoweringErrorV1::ValidationOutsideCanonicalField {
173 subject: site.subject.clone(),
174 },
175 );
176 }
177 let kind = match &proof.kind {
178 ResolvedFormValidationDefinitionKindV1::PresolveRule {
179 validation_identity,
180 } => AuthoredSemanticCandidateKindV1::ResolvedIntrinsic {
181 intrinsic_kind: CanonicalIntrinsicKindV1::Validate,
182 intrinsic_identity: validation_identity.clone(),
183 },
184 ResolvedFormValidationDefinitionKindV1::StandardSchema {
185 module_specifier,
186 export_name,
187 declaration_modules,
188 input_type,
189 output_type,
190 } => AuthoredSemanticCandidateKindV1::DerivedStandardSchemaValidation {
191 module_specifier: module_specifier.clone(),
192 export_name: export_name.clone(),
193 declaration_modules: declaration_modules.clone(),
194 input_type: input_type.clone(),
195 output_type: output_type.clone(),
196 },
197 };
198 Ok(ResolvedAuthoredSemanticCandidateV1 {
199 subject: site.subject.clone(),
200 source: site.declaration_source,
201 kind,
202 })
203 })
204 .collect::<Result<Vec<_>, _>>()?;
205 normalize_authored_semantics_v1(parsed, candidates)
206 .map_err(FormValidationDefinitionLoweringErrorV1::InvalidAuthoredSemantics)
207}
208
209fn range(span: SourceSpan) -> AuthoredSourceRangeV1 {
210 AuthoredSourceRangeV1 {
211 start: span.start,
212 end: span.end,
213 line: span.line,
214 column: span.column,
215 }
216}
217
218fn range_key(range: AuthoredSourceRangeV1) -> (usize, usize) {
219 (range.start, range.end)
220}