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 let current = Self::current_path(path.as_ref());
38 let mut includes = VersionedIncludes::new(&mut resolver, current);
39 Self::lower(value, expand_interface(&properties), &mut includes)
40 }
41
42 pub fn from_typed_ast_with_path(
43 value: crate::typed_ast::Specification,
44 path: impl AsRef<Path>,
45 ) -> crate::error::ParserResult<Self> {
46 let mut resolver = FsIncludeResolver;
47 let current = Self::current_path(path.as_ref());
48 let mut includes = VersionedIncludes::new(&mut resolver, current);
49 Self::lower(value, true, &mut includes)
50 }
51
52 pub fn project_typed_ast_versions(
59 value: crate::typed_ast::Specification,
60 properties: ParserProperties,
61 path: impl AsRef<Path>,
62 ) -> crate::error::ParserResult<Vec<VersionedProjection>> {
63 let mut resolver = FsIncludeResolver;
64 Self::project_typed_ast_versions_with_resolver(value, properties, path, &mut resolver)
65 }
66
67 pub fn project_typed_ast_versions_with_resolver(
78 value: crate::typed_ast::Specification,
79 properties: ParserProperties,
80 path: impl AsRef<Path>,
81 resolver: &mut dyn IncludeResolver,
82 ) -> crate::error::ParserResult<Vec<VersionedProjection>> {
83 let current = Self::current_path(path.as_ref());
84 let mut includes = VersionedIncludes::new(resolver, current);
85 let layout = versioning::VersionLayout::from_properties(&properties)?;
86 let selection = versioning::selection_from_properties(&properties)?;
87 let expand = expand_interface(&properties);
88 let source = Self::lower_source(value, &mut includes)?;
89 let Some(record) = VersioningRecord::analyze(&source)? else {
90 if let Some(selection) = &selection {
91 return Err(versioning::unversioned_selection_error(selection));
92 }
93 let spec = Self::finalize(source, expand)?;
94 return Ok(vec![VersionedProjection {
95 versions: Vec::new(),
96 version: None,
97 hir: Self::project_hir(spec, &properties)?,
98 }]);
99 };
100 let versions = versioning::selected_versions(selection.as_ref(), &record)?;
101 match layout {
102 versioning::VersionLayout::Merged => {
103 let spec = record.merge(&source, &versions)?;
104 let spec = Self::finalize(spec, expand)?;
105 let selected = versions
106 .iter()
107 .filter_map(|version| record.timeline().get(*version).cloned())
108 .collect();
109 Ok(vec![VersionedProjection {
110 versions: selected,
111 version: None,
112 hir: Self::project_hir(spec, &properties)?,
113 }])
114 }
115 versioning::VersionLayout::PerVersion => {
116 let mut projections = Vec::new();
117 for version in versions {
118 let spec = record.project(&source, version)?;
119 let spec = Self::finalize(spec, expand)?;
120 let definition = record.timeline().get(version).cloned();
121 projections.push(VersionedProjection {
122 versions: definition.clone().into_iter().collect(),
123 version: definition,
124 hir: Self::project_hir(spec, &properties)?,
125 });
126 }
127 Ok(projections)
128 }
129 }
130 }
131
132 pub fn project_typed_ast_with_properties_and_path(
136 value: crate::typed_ast::Specification,
137 properties: ParserProperties,
138 path: impl AsRef<Path>,
139 ) -> crate::error::ParserResult<ProjectedHir> {
140 let mut projections = Self::project_typed_ast_versions(value, properties, path)?;
141 match projections.len() {
142 1 => Ok(projections.remove(0).hir),
143 _ => Err(crate::error::ParseError::Message(
144 "xidl versioning: per-version layout requires project_typed_ast_versions"
145 .to_string(),
146 )),
147 }
148 }
149
150 fn project_hir(
151 spec: Specification,
152 properties: &ParserProperties,
153 ) -> crate::error::ParserResult<ProjectedHir> {
154 match hir_projection_kind(properties) {
155 HirProjectionKind::Rpc => Ok(ProjectedHir::Rpc(spec)),
156 HirProjectionKind::Http => rest_hir::project(&spec).map(ProjectedHir::Http),
157 HirProjectionKind::JsonRpc => jsonrpc_hir::project(&spec).map(ProjectedHir::JsonRpc),
158 }
159 }
160
161 fn lower_source(
163 value: crate::typed_ast::Specification,
164 includes: &mut VersionedIncludes<'_>,
165 ) -> crate::error::ParserResult<Self> {
166 let mut definitions = Vec::new();
167 collect_defs(value.0, &mut definitions, includes)?;
168 let spec = Self(definitions);
169 rest_hir::HttpUnion::validate_source(&spec.0)?;
172 Ok(spec)
173 }
174
175 fn finalize(mut spec: Self, expand_interfaces: bool) -> crate::error::ParserResult<Self> {
177 if expand_interfaces {
180 Self::expand_interfaces(&mut spec.0, &mut Vec::new())?;
181 }
182 semantic::analyze(&mut spec);
183 Ok(spec)
184 }
185
186 fn lower(
187 value: crate::typed_ast::Specification,
188 expand_interfaces: bool,
189 includes: &mut VersionedIncludes<'_>,
190 ) -> crate::error::ParserResult<Self> {
191 let spec = Self::lower_source(value, includes)?;
192 VersioningRecord::analyze(&spec)?;
195 Self::finalize(spec, expand_interfaces)
196 }
197
198 fn current_path(path: &Path) -> Option<String> {
200 path.to_str().map(str::to_string)
201 }
202
203 fn expand_interfaces(
204 definitions: &mut Vec<Definition>,
205 modules: &mut Vec<String>,
206 ) -> crate::error::ParserResult<()> {
207 for mut definition in std::mem::take(definitions) {
208 match &mut definition {
209 Definition::ModuleDcl(module) => {
210 modules.push(module.ident.clone());
211 Self::expand_interfaces(&mut module.definition, modules)?;
212 modules.pop();
213 }
214 Definition::InterfaceDcl(interface) => {
215 definitions.extend(interface_codegen::expand_interface(interface, modules)?);
216 }
217 _ => {}
218 }
219 definitions.push(definition);
220 }
221 Ok(())
222 }
223}
224
225pub(crate) fn spec_from_typed_ast(
226 value: crate::typed_ast::Specification,
227 expand_interfaces: bool,
228 includes: &mut VersionedIncludes<'_>,
229) -> Specification {
230 Specification::lower(value, expand_interfaces, includes)
231 .expect("HIR conversion should not fail")
232}
233
234pub(super) fn collect_defs(
235 defs: Vec<crate::typed_ast::Definition>,
236 out: &mut Vec<Definition>,
237 includes: &mut VersionedIncludes<'_>,
238) -> crate::error::ParserResult<()> {
239 for def in defs {
240 match def {
241 crate::typed_ast::Definition::ModuleDcl(module) => {
242 let ident = module.ident.0;
243 let annotations = expand_annotations(module.annotations);
244 let mut inner = Vec::new();
245 collect_defs(module.definition, &mut inner, includes)?;
246 out.push(Definition::ModuleDcl(ModuleDcl {
247 annotations,
248 ident,
249 definition: inner,
250 }));
251 }
252 crate::typed_ast::Definition::PreprocCall(call) => {
253 if let Some(pragma) = parse_xidlc_pragma(&call) {
254 out.push(Definition::Pragma(pragma));
255 }
256 }
257 crate::typed_ast::Definition::TypeDcl(value) => {
258 out.push(Definition::TypeDcl(TypeDcl::from(value)))
259 }
260 crate::typed_ast::Definition::ConstDcl(value) => {
261 out.push(Definition::ConstDcl(value.into()))
262 }
263 crate::typed_ast::Definition::ExceptDcl(value) => {
264 out.push(Definition::ExceptDcl(value.into()))
265 }
266 crate::typed_ast::Definition::InterfaceDcl(value) => {
267 out.push(Definition::InterfaceDcl(InterfaceDcl::from(value)));
268 }
269 crate::typed_ast::Definition::PreprocInclude(include) => {
270 if let Some(path) = include_path_text(&include) {
273 includes.expand(&path, out)?;
274 }
275 }
276 crate::typed_ast::Definition::ImportDcl(_) => {
277 }
280 crate::typed_ast::Definition::TemplateModuleDcl(_)
281 | crate::typed_ast::Definition::TemplateModuleInst(_)
282 | crate::typed_ast::Definition::PreprocDefine(_) => {}
283 }
284 }
285 Ok(())
286}
287
288fn include_path_text(include: &crate::typed_ast::PreprocInclude) -> Option<String> {
290 match &include.path {
291 crate::typed_ast::PreprocIncludePath::StringLiteral(text) => {
292 Some(text.trim().trim_matches('"').to_string())
293 }
294 crate::typed_ast::PreprocIncludePath::SystemLibString(_)
295 | crate::typed_ast::PreprocIncludePath::Identifier(_) => None,
296 }
297}
298
299fn expand_interface(properties: &ParserProperties) -> bool {
300 if let Some(expand) = properties.get("expand_interface").and_then(Value::as_bool) {
301 return expand;
302 }
303
304 matches!(hir_projection_kind(properties), HirProjectionKind::Rpc)
305}
306
307fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
308 match properties.get("hir_kind").and_then(Value::as_str) {
309 Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
310 Some(value)
311 if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
312 {
313 HirProjectionKind::JsonRpc
314 }
315 _ => HirProjectionKind::Rpc,
316 }
317}