1use super::{
2 Definition, InterfaceDcl, ModuleDcl, ParserProperties, Specification, TypeDcl,
3 expand_annotations, interface_codegen, parse_xidlc_pragma,
4};
5use crate::jsonrpc_hir;
6use crate::rest_hir::{self, HirProjectionKind, ProjectedHir};
7use crate::semantic;
8use crate::versioning::{self, VersionedProjection, VersioningRecord};
9use serde_json::Value;
10use std::path::Path;
11
12impl From<crate::typed_ast::Specification> for Specification {
13 fn from(value: crate::typed_ast::Specification) -> Self {
14 spec_from_typed_ast(value, true)
15 }
16}
17
18impl Specification {
19 pub fn from_typed_ast_with_properties(
20 value: crate::typed_ast::Specification,
21 properties: ParserProperties,
22 ) -> Self {
23 spec_from_typed_ast(value, expand_interface(&properties))
24 }
25
26 pub fn from_typed_ast_with_properties_and_path(
27 value: crate::typed_ast::Specification,
28 properties: ParserProperties,
29 _path: impl AsRef<Path>,
30 ) -> crate::error::ParserResult<Self> {
31 Self::lower(value, expand_interface(&properties))
32 }
33
34 pub fn from_typed_ast_with_path(
35 value: crate::typed_ast::Specification,
36 _path: impl AsRef<Path>,
37 ) -> crate::error::ParserResult<Self> {
38 Self::lower(value, true)
39 }
40
41 pub fn project_typed_ast_versions(
48 value: crate::typed_ast::Specification,
49 properties: ParserProperties,
50 _path: impl AsRef<Path>,
51 ) -> crate::error::ParserResult<Vec<VersionedProjection>> {
52 let layout = versioning::VersionLayout::from_properties(&properties)?;
53 let selection = versioning::selection_from_properties(&properties)?;
54 let expand = expand_interface(&properties);
55 let source = Self::lower_source(value)?;
56 let Some(record) = VersioningRecord::analyze(&source)? else {
57 if let Some(selection) = &selection {
58 return Err(versioning::unversioned_selection_error(selection));
59 }
60 let spec = Self::finalize(source, expand)?;
61 return Ok(vec![VersionedProjection {
62 versions: Vec::new(),
63 version: None,
64 hir: Self::project_hir(spec, &properties)?,
65 }]);
66 };
67 let versions = versioning::selected_versions(selection.as_ref(), &record)?;
68 match layout {
69 versioning::VersionLayout::Merged => {
70 let spec = record.merge(&source, &versions)?;
71 let spec = Self::finalize(spec, expand)?;
72 let selected = versions
73 .iter()
74 .filter_map(|version| record.timeline().get(*version).cloned())
75 .collect();
76 Ok(vec![VersionedProjection {
77 versions: selected,
78 version: None,
79 hir: Self::project_hir(spec, &properties)?,
80 }])
81 }
82 versioning::VersionLayout::PerVersion => {
83 let mut projections = Vec::new();
84 for version in versions {
85 let spec = record.project(&source, version)?;
86 let spec = Self::finalize(spec, expand)?;
87 let definition = record.timeline().get(version).cloned();
88 projections.push(VersionedProjection {
89 versions: definition.clone().into_iter().collect(),
90 version: definition,
91 hir: Self::project_hir(spec, &properties)?,
92 });
93 }
94 Ok(projections)
95 }
96 }
97 }
98
99 pub fn project_typed_ast_with_properties_and_path(
103 value: crate::typed_ast::Specification,
104 properties: ParserProperties,
105 path: impl AsRef<Path>,
106 ) -> crate::error::ParserResult<ProjectedHir> {
107 let mut projections = Self::project_typed_ast_versions(value, properties, path)?;
108 match projections.len() {
109 1 => Ok(projections.remove(0).hir),
110 _ => Err(crate::error::ParseError::Message(
111 "xidl versioning: per-version layout requires project_typed_ast_versions"
112 .to_string(),
113 )),
114 }
115 }
116
117 fn project_hir(
118 spec: Specification,
119 properties: &ParserProperties,
120 ) -> crate::error::ParserResult<ProjectedHir> {
121 match hir_projection_kind(properties) {
122 HirProjectionKind::Rpc => Ok(ProjectedHir::Rpc(spec)),
123 HirProjectionKind::Http => rest_hir::project(&spec).map(ProjectedHir::Http),
124 HirProjectionKind::JsonRpc => jsonrpc_hir::project(&spec).map(ProjectedHir::JsonRpc),
125 }
126 }
127
128 fn lower_source(value: crate::typed_ast::Specification) -> crate::error::ParserResult<Self> {
130 let mut definitions = Vec::new();
131 collect_defs(value.0, &mut definitions);
132 let spec = Self(definitions);
133 rest_hir::HttpUnion::validate_source(&spec.0)?;
136 Ok(spec)
137 }
138
139 fn finalize(mut spec: Self, expand_interfaces: bool) -> crate::error::ParserResult<Self> {
141 if expand_interfaces {
144 Self::expand_interfaces(&mut spec.0, &mut Vec::new())?;
145 }
146 semantic::analyze(&mut spec);
147 Ok(spec)
148 }
149
150 fn lower(
151 value: crate::typed_ast::Specification,
152 expand_interfaces: bool,
153 ) -> crate::error::ParserResult<Self> {
154 let spec = Self::lower_source(value)?;
155 VersioningRecord::analyze(&spec)?;
158 Self::finalize(spec, expand_interfaces)
159 }
160
161 fn expand_interfaces(
162 definitions: &mut Vec<Definition>,
163 modules: &mut Vec<String>,
164 ) -> crate::error::ParserResult<()> {
165 for mut definition in std::mem::take(definitions) {
166 match &mut definition {
167 Definition::ModuleDcl(module) => {
168 modules.push(module.ident.clone());
169 Self::expand_interfaces(&mut module.definition, modules)?;
170 modules.pop();
171 }
172 Definition::InterfaceDcl(interface) => {
173 definitions.extend(interface_codegen::expand_interface(interface, modules)?);
174 }
175 _ => {}
176 }
177 definitions.push(definition);
178 }
179 Ok(())
180 }
181}
182
183pub(crate) fn spec_from_typed_ast(
184 value: crate::typed_ast::Specification,
185 expand_interfaces: bool,
186) -> Specification {
187 Specification::lower(value, expand_interfaces).expect("HIR conversion should not fail")
188}
189
190pub(super) fn collect_defs(defs: Vec<crate::typed_ast::Definition>, out: &mut Vec<Definition>) {
191 for def in defs {
192 match def {
193 crate::typed_ast::Definition::ModuleDcl(module) => {
194 let ident = module.ident.0;
195 let annotations = expand_annotations(module.annotations);
196 let mut inner = Vec::new();
197 collect_defs(module.definition, &mut inner);
198 out.push(Definition::ModuleDcl(ModuleDcl {
199 annotations,
200 ident,
201 definition: inner,
202 }));
203 }
204 crate::typed_ast::Definition::PreprocCall(call) => {
205 if let Some(pragma) = parse_xidlc_pragma(&call) {
206 out.push(Definition::Pragma(pragma));
207 }
208 }
209 crate::typed_ast::Definition::TypeDcl(value) => {
210 out.push(Definition::TypeDcl(TypeDcl::from(value)))
211 }
212 crate::typed_ast::Definition::ConstDcl(value) => {
213 out.push(Definition::ConstDcl(value.into()))
214 }
215 crate::typed_ast::Definition::ExceptDcl(value) => {
216 out.push(Definition::ExceptDcl(value.into()))
217 }
218 crate::typed_ast::Definition::InterfaceDcl(value) => {
219 out.push(Definition::InterfaceDcl(InterfaceDcl::from(value)));
220 }
221 crate::typed_ast::Definition::PreprocInclude(_) => {
222 }
225 crate::typed_ast::Definition::TemplateModuleDcl(_)
226 | crate::typed_ast::Definition::TemplateModuleInst(_)
227 | crate::typed_ast::Definition::PreprocDefine(_) => {}
228 }
229 }
230}
231
232fn expand_interface(properties: &ParserProperties) -> bool {
233 if let Some(expand) = properties.get("expand_interface").and_then(Value::as_bool) {
234 return expand;
235 }
236
237 matches!(hir_projection_kind(properties), HirProjectionKind::Rpc)
238}
239
240fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
241 match properties.get("hir_kind").and_then(Value::as_str) {
242 Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
243 Some(value)
244 if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
245 {
246 HirProjectionKind::JsonRpc
247 }
248 _ => HirProjectionKind::Rpc,
249 }
250}