1use super::{
2 Definition, FsIncludeResolver, InterfaceDcl, ModuleDcl, ParserProperties, Specification,
3 TypeDcl, VersionedIncludes, expand_annotations, interface_codegen, parse_xidlc_pragma,
4};
5use crate::jsonrpc_hir;
6use crate::parser::IncludeResolver;
7use crate::rest_hir::{self, HirProjectionKind, ProjectedHir};
8use crate::semantic;
9use crate::versioning::{self, VersionedProjection, VersioningRecord};
10use serde_json::Value;
11use std::path::Path;
12
13impl From<crate::typed_ast::Specification> for Specification {
14 fn from(value: crate::typed_ast::Specification) -> Self {
15 let mut resolver = FsIncludeResolver;
16 let mut includes = VersionedIncludes::new(&mut resolver, None);
17 spec_from_typed_ast(value, true, &mut includes)
18 }
19}
20
21impl Specification {
22 pub fn from_typed_ast_with_properties(
23 value: crate::typed_ast::Specification,
24 properties: ParserProperties,
25 ) -> Self {
26 let mut resolver = FsIncludeResolver;
27 let mut includes = VersionedIncludes::new(&mut resolver, None);
28 spec_from_typed_ast(value, expand_interface(&properties), &mut includes)
29 }
30
31 pub fn from_typed_ast_with_properties_and_path(
32 value: crate::typed_ast::Specification,
33 properties: ParserProperties,
34 path: impl AsRef<Path>,
35 ) -> crate::error::ParserResult<Self> {
36 let mut resolver = FsIncludeResolver;
37 Self::from_typed_ast_with_resolver(value, properties, path, &mut resolver)
38 }
39
40 pub fn from_typed_ast_with_resolver(
45 value: crate::typed_ast::Specification,
46 properties: ParserProperties,
47 path: impl AsRef<Path>,
48 resolver: &mut dyn IncludeResolver,
49 ) -> crate::error::ParserResult<Self> {
50 let current = Self::current_path(path.as_ref());
51 let mut includes = VersionedIncludes::new(resolver, current);
52 Self::lower(value, expand_interface(&properties), &mut includes)
53 }
54
55 pub fn from_typed_ast_with_path(
56 value: crate::typed_ast::Specification,
57 path: impl AsRef<Path>,
58 ) -> crate::error::ParserResult<Self> {
59 let mut resolver = FsIncludeResolver;
60 let current = Self::current_path(path.as_ref());
61 let mut includes = VersionedIncludes::new(&mut resolver, current);
62 Self::lower(value, true, &mut includes)
63 }
64
65 pub fn project_typed_ast_versions(
72 value: crate::typed_ast::Specification,
73 properties: ParserProperties,
74 path: impl AsRef<Path>,
75 ) -> crate::error::ParserResult<Vec<VersionedProjection>> {
76 let mut resolver = FsIncludeResolver;
77 Self::project_typed_ast_versions_with_resolver(value, properties, path, &mut resolver)
78 }
79
80 pub fn project_typed_ast_versions_with_resolver(
91 value: crate::typed_ast::Specification,
92 properties: ParserProperties,
93 path: impl AsRef<Path>,
94 resolver: &mut dyn IncludeResolver,
95 ) -> crate::error::ParserResult<Vec<VersionedProjection>> {
96 let current = Self::current_path(path.as_ref());
97 let mut includes = VersionedIncludes::new(resolver, current);
98 let layout = versioning::VersionLayout::from_properties(&properties)?;
99 let selection = versioning::selection_from_properties(&properties)?;
100 let expand = expand_interface(&properties);
101 let source = Self::lower_source(value, &mut includes)?;
102 let Some(record) = VersioningRecord::analyze(&source)? else {
103 if let Some(selection) = &selection {
104 return Err(versioning::unversioned_selection_error(selection));
105 }
106 let spec = Self::finalize(source, expand)?;
107 return Ok(vec![VersionedProjection {
108 versions: Vec::new(),
109 version: None,
110 hir: Self::project_hir(spec, &properties, None, Vec::new())?,
111 }]);
112 };
113 let versions = versioning::selected_versions(selection.as_ref(), &record)?;
114 let version_parameter = record.parameter();
115 let version_values = |defs: &[versioning::VersionDef]| {
116 defs.iter()
117 .filter(|_| version_parameter.is_some())
118 .map(|def| (def.ident.clone(), def.value.clone()))
119 .collect::<Vec<_>>()
120 };
121 match layout {
122 versioning::VersionLayout::Merged => {
123 let selected = versions
124 .iter()
125 .filter_map(|version| record.timeline().get(*version).cloned())
126 .collect::<Vec<_>>();
127 let values = version_values(&selected);
128 let spec = record.merge(&source, &versions)?;
129 let spec = Self::finalize(spec, expand)?;
130 Ok(vec![VersionedProjection {
131 versions: selected,
132 version: None,
133 hir: Self::project_hir(spec, &properties, version_parameter, values)?,
134 }])
135 }
136 versioning::VersionLayout::PerVersion => {
137 let mut projections = Vec::new();
138 for version in versions {
139 let spec = record.project(&source, version)?;
140 let spec = Self::finalize(spec, expand)?;
141 let definition = record.timeline().get(version).cloned();
142 let values = definition
143 .iter()
144 .filter(|_| version_parameter.is_some())
145 .map(|def| (def.ident.clone(), def.value.clone()))
146 .collect();
147 projections.push(VersionedProjection {
148 versions: definition.clone().into_iter().collect(),
149 version: definition,
150 hir: Self::project_hir(spec, &properties, version_parameter, values)?,
151 });
152 }
153 Ok(projections)
154 }
155 }
156 }
157
158 pub fn project_typed_ast_with_properties_and_path(
162 value: crate::typed_ast::Specification,
163 properties: ParserProperties,
164 path: impl AsRef<Path>,
165 ) -> crate::error::ParserResult<ProjectedHir> {
166 let mut projections = Self::project_typed_ast_versions(value, properties, path)?;
167 match projections.len() {
168 1 => Ok(projections.remove(0).hir),
169 _ => Err(crate::error::ParseError::Message(
170 "xidl versioning: per-version layout requires project_typed_ast_versions"
171 .to_string(),
172 )),
173 }
174 }
175
176 fn project_hir(
177 spec: Specification,
178 properties: &ParserProperties,
179 version_parameter: Option<&super::VersionParameter>,
180 version_values: Vec<(String, String)>,
181 ) -> crate::error::ParserResult<ProjectedHir> {
182 match hir_projection_kind(properties) {
183 HirProjectionKind::Rpc => Ok(ProjectedHir::Rpc(spec)),
184 HirProjectionKind::Http => {
185 let options = rest_hir::ProjectOptions {
186 version_parameter: version_parameter.cloned(),
187 version_values,
188 };
189 rest_hir::project_with_options(&spec, &options).map(ProjectedHir::Http)
190 }
191 HirProjectionKind::JsonRpc => jsonrpc_hir::project(&spec).map(ProjectedHir::JsonRpc),
192 }
193 }
194
195 fn lower_source(
197 value: crate::typed_ast::Specification,
198 includes: &mut VersionedIncludes<'_>,
199 ) -> crate::error::ParserResult<Self> {
200 let mut definitions = Vec::new();
201 collect_defs(value.0, &mut definitions, includes)?;
202 let spec = Self(definitions);
203 rest_hir::HttpUnion::validate_source(&spec.0)?;
206 Ok(spec)
207 }
208
209 fn finalize(mut spec: Self, expand_interfaces: bool) -> crate::error::ParserResult<Self> {
211 if expand_interfaces {
214 Self::expand_interfaces(&mut spec.0, &mut Vec::new())?;
215 }
216 semantic::analyze(&mut spec);
217 Ok(spec)
218 }
219
220 fn lower(
221 value: crate::typed_ast::Specification,
222 expand_interfaces: bool,
223 includes: &mut VersionedIncludes<'_>,
224 ) -> crate::error::ParserResult<Self> {
225 let spec = Self::lower_source(value, includes)?;
226 VersioningRecord::analyze(&spec)?;
229 Self::finalize(spec, expand_interfaces)
230 }
231
232 fn current_path(path: &Path) -> Option<String> {
234 path.to_str().map(str::to_string)
235 }
236
237 fn expand_interfaces(
238 definitions: &mut Vec<Definition>,
239 modules: &mut Vec<String>,
240 ) -> crate::error::ParserResult<()> {
241 for mut definition in std::mem::take(definitions) {
242 match &mut definition {
243 Definition::ModuleDcl(module) => {
244 modules.push(module.ident.clone());
245 Self::expand_interfaces(&mut module.definition, modules)?;
246 modules.pop();
247 }
248 Definition::InterfaceDcl(interface) => {
249 definitions.extend(interface_codegen::expand_interface(interface, modules)?);
250 }
251 _ => {}
252 }
253 definitions.push(definition);
254 }
255 Ok(())
256 }
257}
258
259pub(crate) fn spec_from_typed_ast(
260 value: crate::typed_ast::Specification,
261 expand_interfaces: bool,
262 includes: &mut VersionedIncludes<'_>,
263) -> Specification {
264 Specification::lower(value, expand_interfaces, includes)
265 .expect("HIR conversion should not fail")
266}
267
268pub(super) fn collect_defs(
269 defs: Vec<crate::typed_ast::Definition>,
270 out: &mut Vec<Definition>,
271 includes: &mut VersionedIncludes<'_>,
272) -> crate::error::ParserResult<()> {
273 for def in defs {
274 match def {
275 crate::typed_ast::Definition::ModuleDcl(module) => {
276 let ident = module.ident.0;
277 let annotations = expand_annotations(module.annotations);
278 let mut inner = Vec::new();
279 collect_defs(module.definition, &mut inner, includes)?;
280 out.push(Definition::ModuleDcl(ModuleDcl {
281 annotations,
282 ident,
283 definition: inner,
284 }));
285 }
286 crate::typed_ast::Definition::PreprocCall(call) => {
287 if let Some(pragma) = parse_xidlc_pragma(&call) {
288 out.push(Definition::Pragma(pragma));
289 }
290 }
291 crate::typed_ast::Definition::TypeDcl(value) => {
292 out.push(Definition::TypeDcl(TypeDcl::from(value)))
293 }
294 crate::typed_ast::Definition::ConstDcl(value) => {
295 out.push(Definition::ConstDcl(value.into()))
296 }
297 crate::typed_ast::Definition::ExceptDcl(value) => {
298 out.push(Definition::ExceptDcl(value.into()))
299 }
300 crate::typed_ast::Definition::InterfaceDcl(value) => {
301 out.push(Definition::InterfaceDcl(InterfaceDcl::from(value)));
302 }
303 crate::typed_ast::Definition::PreprocInclude(include) => {
304 if let Some(path) = include_path_text(&include) {
307 includes.expand(&path, out)?;
308 }
309 }
310 crate::typed_ast::Definition::ImportDcl(_) => {
311 }
314 crate::typed_ast::Definition::TemplateModuleDcl(_)
315 | crate::typed_ast::Definition::TemplateModuleInst(_)
316 | crate::typed_ast::Definition::PreprocDefine(_) => {}
317 }
318 }
319 Ok(())
320}
321
322fn include_path_text(include: &crate::typed_ast::PreprocInclude) -> Option<String> {
324 match &include.path {
325 crate::typed_ast::PreprocIncludePath::StringLiteral(text) => {
326 Some(text.trim().trim_matches('"').to_string())
327 }
328 crate::typed_ast::PreprocIncludePath::SystemLibString(_)
329 | crate::typed_ast::PreprocIncludePath::Identifier(_) => None,
330 }
331}
332
333fn expand_interface(properties: &ParserProperties) -> bool {
334 if let Some(expand) = properties.get("expand_interface").and_then(Value::as_bool) {
335 return expand;
336 }
337
338 matches!(hir_projection_kind(properties), HirProjectionKind::Rpc)
339}
340
341fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
342 match properties.get("hir_kind").and_then(Value::as_str) {
343 Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
344 Some(value)
345 if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
346 {
347 HirProjectionKind::JsonRpc
348 }
349 _ => HirProjectionKind::Rpc,
350 }
351}