use super::{
Definition, FsIncludeResolver, InterfaceDcl, ModuleDcl, ParserProperties, Specification,
TypeDcl, VersionedIncludes, expand_annotations, interface_codegen, parse_xidlc_pragma,
};
use crate::jsonrpc_hir;
use crate::parser::IncludeResolver;
use crate::rest_hir::{self, HirProjectionKind, ProjectedHir};
use crate::semantic;
use crate::versioning::{self, VersionedProjection, VersioningRecord};
use serde_json::Value;
use std::path::Path;
impl From<crate::typed_ast::Specification> for Specification {
fn from(value: crate::typed_ast::Specification) -> Self {
let mut resolver = FsIncludeResolver;
let mut includes = VersionedIncludes::new(&mut resolver, None);
spec_from_typed_ast(value, true, &mut includes)
}
}
impl Specification {
pub fn from_typed_ast_with_properties(
value: crate::typed_ast::Specification,
properties: ParserProperties,
) -> Self {
let mut resolver = FsIncludeResolver;
let mut includes = VersionedIncludes::new(&mut resolver, None);
spec_from_typed_ast(value, expand_interface(&properties), &mut includes)
}
pub fn from_typed_ast_with_properties_and_path(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<Self> {
let mut resolver = FsIncludeResolver;
Self::from_typed_ast_with_resolver(value, properties, path, &mut resolver)
}
pub fn from_typed_ast_with_resolver(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
resolver: &mut dyn IncludeResolver,
) -> crate::error::ParserResult<Self> {
let current = Self::current_path(path.as_ref());
let mut includes = VersionedIncludes::new(resolver, current);
Self::lower(value, expand_interface(&properties), &mut includes)
}
pub fn from_typed_ast_with_path(
value: crate::typed_ast::Specification,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<Self> {
let mut resolver = FsIncludeResolver;
let current = Self::current_path(path.as_ref());
let mut includes = VersionedIncludes::new(&mut resolver, current);
Self::lower(value, true, &mut includes)
}
pub fn project_typed_ast_versions(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<Vec<VersionedProjection>> {
let mut resolver = FsIncludeResolver;
Self::project_typed_ast_versions_with_resolver(value, properties, path, &mut resolver)
}
pub fn project_typed_ast_versions_with_resolver(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
resolver: &mut dyn IncludeResolver,
) -> crate::error::ParserResult<Vec<VersionedProjection>> {
let current = Self::current_path(path.as_ref());
let mut includes = VersionedIncludes::new(resolver, current);
let layout = versioning::VersionLayout::from_properties(&properties)?;
let selection = versioning::selection_from_properties(&properties)?;
let expand = expand_interface(&properties);
let source = Self::lower_source(value, &mut includes)?;
let Some(record) = VersioningRecord::analyze(&source)? else {
if let Some(selection) = &selection {
return Err(versioning::unversioned_selection_error(selection));
}
let spec = Self::finalize(source, expand)?;
return Ok(vec![VersionedProjection {
versions: Vec::new(),
version: None,
hir: Self::project_hir(spec, &properties, None, Vec::new())?,
}]);
};
let versions = versioning::selected_versions(selection.as_ref(), &record)?;
let version_parameter = record.parameter();
let version_values = |defs: &[versioning::VersionDef]| {
defs.iter()
.filter(|_| version_parameter.is_some())
.map(|def| (def.ident.clone(), def.value.clone()))
.collect::<Vec<_>>()
};
match layout {
versioning::VersionLayout::Merged => {
let selected = versions
.iter()
.filter_map(|version| record.timeline().get(*version).cloned())
.collect::<Vec<_>>();
let values = version_values(&selected);
let spec = record.merge(&source, &versions)?;
let spec = Self::finalize(spec, expand)?;
Ok(vec![VersionedProjection {
versions: selected,
version: None,
hir: Self::project_hir(spec, &properties, version_parameter, values)?,
}])
}
versioning::VersionLayout::PerVersion => {
let mut projections = Vec::new();
for version in versions {
let spec = record.project(&source, version)?;
let spec = Self::finalize(spec, expand)?;
let definition = record.timeline().get(version).cloned();
let values = definition
.iter()
.filter(|_| version_parameter.is_some())
.map(|def| (def.ident.clone(), def.value.clone()))
.collect();
projections.push(VersionedProjection {
versions: definition.clone().into_iter().collect(),
version: definition,
hir: Self::project_hir(spec, &properties, version_parameter, values)?,
});
}
Ok(projections)
}
}
}
pub fn project_typed_ast_with_properties_and_path(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<ProjectedHir> {
let mut projections = Self::project_typed_ast_versions(value, properties, path)?;
match projections.len() {
1 => Ok(projections.remove(0).hir),
_ => Err(crate::error::ParseError::Message(
"xidl versioning: per-version layout requires project_typed_ast_versions"
.to_string(),
)),
}
}
fn project_hir(
spec: Specification,
properties: &ParserProperties,
version_parameter: Option<&super::VersionParameter>,
version_values: Vec<(String, String)>,
) -> crate::error::ParserResult<ProjectedHir> {
match hir_projection_kind(properties) {
HirProjectionKind::Rpc => Ok(ProjectedHir::Rpc(spec)),
HirProjectionKind::Http => {
let options = rest_hir::ProjectOptions {
version_parameter: version_parameter.cloned(),
version_values,
};
rest_hir::project_with_options(&spec, &options).map(ProjectedHir::Http)
}
HirProjectionKind::JsonRpc => jsonrpc_hir::project(&spec).map(ProjectedHir::JsonRpc),
}
}
fn lower_source(
value: crate::typed_ast::Specification,
includes: &mut VersionedIncludes<'_>,
) -> crate::error::ParserResult<Self> {
let mut definitions = Vec::new();
collect_defs(value.0, &mut definitions, includes)?;
let spec = Self(definitions);
rest_hir::HttpUnion::validate_source(&spec.0)?;
Ok(spec)
}
fn finalize(mut spec: Self, expand_interfaces: bool) -> crate::error::ParserResult<Self> {
if expand_interfaces {
Self::expand_interfaces(&mut spec.0, &mut Vec::new())?;
}
semantic::analyze(&mut spec);
Ok(spec)
}
fn lower(
value: crate::typed_ast::Specification,
expand_interfaces: bool,
includes: &mut VersionedIncludes<'_>,
) -> crate::error::ParserResult<Self> {
let spec = Self::lower_source(value, includes)?;
VersioningRecord::analyze(&spec)?;
Self::finalize(spec, expand_interfaces)
}
fn current_path(path: &Path) -> Option<String> {
path.to_str().map(str::to_string)
}
fn expand_interfaces(
definitions: &mut Vec<Definition>,
modules: &mut Vec<String>,
) -> crate::error::ParserResult<()> {
for mut definition in std::mem::take(definitions) {
match &mut definition {
Definition::ModuleDcl(module) => {
modules.push(module.ident.clone());
Self::expand_interfaces(&mut module.definition, modules)?;
modules.pop();
}
Definition::InterfaceDcl(interface) => {
definitions.extend(interface_codegen::expand_interface(interface, modules)?);
}
_ => {}
}
definitions.push(definition);
}
Ok(())
}
}
pub(crate) fn spec_from_typed_ast(
value: crate::typed_ast::Specification,
expand_interfaces: bool,
includes: &mut VersionedIncludes<'_>,
) -> Specification {
Specification::lower(value, expand_interfaces, includes)
.expect("HIR conversion should not fail")
}
pub(super) fn collect_defs(
defs: Vec<crate::typed_ast::Definition>,
out: &mut Vec<Definition>,
includes: &mut VersionedIncludes<'_>,
) -> crate::error::ParserResult<()> {
for def in defs {
match def {
crate::typed_ast::Definition::ModuleDcl(module) => {
let ident = module.ident.0;
let annotations = expand_annotations(module.annotations);
let mut inner = Vec::new();
collect_defs(module.definition, &mut inner, includes)?;
out.push(Definition::ModuleDcl(ModuleDcl {
annotations,
ident,
definition: inner,
}));
}
crate::typed_ast::Definition::PreprocCall(call) => {
if let Some(pragma) = parse_xidlc_pragma(&call) {
out.push(Definition::Pragma(pragma));
}
}
crate::typed_ast::Definition::TypeDcl(value) => {
out.push(Definition::TypeDcl(TypeDcl::from(value)))
}
crate::typed_ast::Definition::ConstDcl(value) => {
out.push(Definition::ConstDcl(value.into()))
}
crate::typed_ast::Definition::ExceptDcl(value) => {
out.push(Definition::ExceptDcl(value.into()))
}
crate::typed_ast::Definition::InterfaceDcl(value) => {
out.push(Definition::InterfaceDcl(InterfaceDcl::from(value)));
}
crate::typed_ast::Definition::PreprocInclude(include) => {
if let Some(path) = include_path_text(&include) {
includes.expand(&path, out)?;
}
}
crate::typed_ast::Definition::ImportDcl(_) => {
}
crate::typed_ast::Definition::TemplateModuleDcl(_)
| crate::typed_ast::Definition::TemplateModuleInst(_)
| crate::typed_ast::Definition::PreprocDefine(_) => {}
}
}
Ok(())
}
fn include_path_text(include: &crate::typed_ast::PreprocInclude) -> Option<String> {
match &include.path {
crate::typed_ast::PreprocIncludePath::StringLiteral(text) => {
Some(text.trim().trim_matches('"').to_string())
}
crate::typed_ast::PreprocIncludePath::SystemLibString(_)
| crate::typed_ast::PreprocIncludePath::Identifier(_) => None,
}
}
fn expand_interface(properties: &ParserProperties) -> bool {
if let Some(expand) = properties.get("expand_interface").and_then(Value::as_bool) {
return expand;
}
matches!(hir_projection_kind(properties), HirProjectionKind::Rpc)
}
fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
match properties.get("hir_kind").and_then(Value::as_str) {
Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
Some(value)
if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
{
HirProjectionKind::JsonRpc
}
_ => HirProjectionKind::Rpc,
}
}