use alloc::string::String;
use alloc::vec::Vec;
use zerodds_idl::ast::{
Export, Identifier, InitDcl, OpDecl, ParamAttribute, ParamDecl, ScopedName, ValueDef,
ValueElement, ValueKind,
};
use zerodds_idl::errors::Span;
use crate::transform::{HomeEquivalent, scoped_name};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrimaryKeyError {
NotValueType(String),
NotDerivedFromPrimaryKeyBase(String),
HasPrivateStateMembers(String),
HasInterfaceReference(String),
}
pub fn validate_primary_key(pk_type: &ValueDef) -> Result<(), PrimaryKeyError> {
if pk_type.kind == ValueKind::Abstract {
return Err(PrimaryKeyError::NotValueType(pk_type.name.text.clone()));
}
let derives_from_pk_base = pk_type
.inheritance
.as_ref()
.map(|i| {
i.bases.iter().any(|b| {
matches!(
(b.parts.len(), b.parts.first(), b.parts.get(1)),
(2, Some(c), Some(p)) if c.text == "Components" && p.text == "PrimaryKeyBase"
)
})
})
.unwrap_or(false);
if !derives_from_pk_base {
return Err(PrimaryKeyError::NotDerivedFromPrimaryKeyBase(
pk_type.name.text.clone(),
));
}
for el in &pk_type.elements {
if let ValueElement::State(sm) = el {
if matches!(sm.visibility, zerodds_idl::ast::StateVisibility::Private) {
return Err(PrimaryKeyError::HasPrivateStateMembers(
pk_type.name.text.clone(),
));
}
if matches!(sm.type_spec, zerodds_idl::ast::TypeSpec::Scoped(_)) {
return Err(PrimaryKeyError::HasInterfaceReference(
pk_type.name.text.clone(),
));
}
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct InitOp {
pub name: Identifier,
pub params: Vec<ParamDecl>,
pub raises: Vec<ScopedName>,
}
impl From<InitDcl> for InitOp {
fn from(d: InitDcl) -> Self {
Self {
name: d.name,
params: d.params,
raises: d.raises,
}
}
}
pub fn apply_factory_finder_body(
home: &mut HomeEquivalent,
factories: &[InitOp],
finders: &[InitOp],
) {
let span = Span::SYNTHETIC;
let component_type = zerodds_idl::ast::TypeSpec::Scoped(
home.equivalent.bases.first().cloned().unwrap_or_else(|| {
ScopedName::single(home.equivalent.name.clone())
}),
);
let _ = component_type;
for f in factories {
let mut raises = alloc::vec![scoped_name(&["Components", "CreateFailure"], span)];
raises.extend(f.raises.clone());
let op = OpDecl {
name: f.name.clone(),
oneway: false,
return_type: Some(zerodds_idl::ast::TypeSpec::Scoped(ScopedName::single(
home.equivalent.name.clone(),
))),
params: ensure_in_params(&f.params),
raises,
annotations: Vec::new(),
span,
};
home.explicit.exports.push(Export::Op(op));
}
for fi in finders {
let mut raises = alloc::vec![scoped_name(&["Components", "FinderFailure"], span)];
raises.extend(fi.raises.clone());
let op = OpDecl {
name: fi.name.clone(),
oneway: false,
return_type: Some(zerodds_idl::ast::TypeSpec::Scoped(ScopedName::single(
home.equivalent.name.clone(),
))),
params: ensure_in_params(&fi.params),
raises,
annotations: Vec::new(),
span,
};
home.explicit.exports.push(Export::Op(op));
}
}
fn ensure_in_params(params: &[ParamDecl]) -> Vec<ParamDecl> {
let span = Span::SYNTHETIC;
params
.iter()
.map(|p| ParamDecl {
attribute: ParamAttribute::In,
type_spec: p.type_spec.clone(),
name: p.name.clone(),
annotations: Vec::new(),
span,
})
.collect()
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic, clippy::unreachable)]
mod tests {
use super::*;
use zerodds_idl::ast::{
FloatingType, IntegerType, PrimitiveType, StateMember, StateVisibility, StringType,
TypeSpec, ValueElement, ValueInheritanceSpec, ValueKind,
};
fn ident(name: &str) -> Identifier {
Identifier::new(name, Span::SYNTHETIC)
}
fn scoped(parts: &[&str]) -> ScopedName {
ScopedName {
absolute: false,
parts: parts.iter().map(|p| ident(p)).collect(),
span: Span::SYNTHETIC,
}
}
fn pk_value(
kind: ValueKind,
inheritance: Option<ValueInheritanceSpec>,
elements: Vec<ValueElement>,
) -> ValueDef {
ValueDef {
name: ident("CKey"),
kind,
inheritance,
elements,
annotations: Vec::new(),
span: Span::SYNTHETIC,
}
}
fn public_state(ty: TypeSpec) -> ValueElement {
ValueElement::State(StateMember {
visibility: StateVisibility::Public,
type_spec: ty,
declarators: alloc::vec![zerodds_idl::ast::Declarator::Simple(ident("v"))],
annotations: Vec::new(),
span: Span::SYNTHETIC,
})
}
fn private_state(ty: TypeSpec) -> ValueElement {
ValueElement::State(StateMember {
visibility: StateVisibility::Private,
type_spec: ty,
declarators: alloc::vec![zerodds_idl::ast::Declarator::Simple(ident("v"))],
annotations: Vec::new(),
span: Span::SYNTHETIC,
})
}
fn long_ty() -> TypeSpec {
TypeSpec::Primitive(PrimitiveType::Integer(IntegerType::Long))
}
fn string_ty() -> TypeSpec {
TypeSpec::String(StringType {
wide: false,
bound: None,
span: Span::SYNTHETIC,
})
}
fn double_ty() -> TypeSpec {
TypeSpec::Primitive(PrimitiveType::Floating(FloatingType::Double))
}
fn pk_inheritance() -> ValueInheritanceSpec {
ValueInheritanceSpec {
truncatable: false,
bases: alloc::vec![scoped(&["Components", "PrimaryKeyBase"])],
supports: Vec::new(),
span: Span::SYNTHETIC,
}
}
#[test]
fn pk_with_correct_inheritance_and_public_long_member_ok() {
let v = pk_value(
ValueKind::Concrete,
Some(pk_inheritance()),
alloc::vec![public_state(long_ty())],
);
assert!(validate_primary_key(&v).is_ok());
}
#[test]
fn pk_without_inheritance_yields_error() {
let v = pk_value(
ValueKind::Concrete,
None,
alloc::vec![public_state(long_ty())],
);
let err = validate_primary_key(&v).expect_err("error");
assert!(matches!(
err,
PrimaryKeyError::NotDerivedFromPrimaryKeyBase(_)
));
}
#[test]
fn pk_with_wrong_base_yields_error() {
let inh = ValueInheritanceSpec {
truncatable: false,
bases: alloc::vec![scoped(&["Other", "Base"])],
supports: Vec::new(),
span: Span::SYNTHETIC,
};
let v = pk_value(
ValueKind::Concrete,
Some(inh),
alloc::vec![public_state(long_ty())],
);
let err = validate_primary_key(&v).expect_err("error");
assert!(matches!(
err,
PrimaryKeyError::NotDerivedFromPrimaryKeyBase(_)
));
}
#[test]
fn pk_with_private_state_member_yields_error() {
let v = pk_value(
ValueKind::Concrete,
Some(pk_inheritance()),
alloc::vec![private_state(long_ty())],
);
let err = validate_primary_key(&v).expect_err("error");
assert!(matches!(err, PrimaryKeyError::HasPrivateStateMembers(_)));
}
#[test]
fn pk_with_string_member_ok() {
let v = pk_value(
ValueKind::Concrete,
Some(pk_inheritance()),
alloc::vec![public_state(string_ty())],
);
assert!(validate_primary_key(&v).is_ok());
}
#[test]
fn pk_with_double_member_ok() {
let v = pk_value(
ValueKind::Concrete,
Some(pk_inheritance()),
alloc::vec![public_state(double_ty())],
);
assert!(validate_primary_key(&v).is_ok());
}
#[test]
fn pk_abstract_yields_error() {
let v = pk_value(
ValueKind::Abstract,
Some(pk_inheritance()),
alloc::vec![public_state(long_ty())],
);
let err = validate_primary_key(&v).expect_err("error");
assert!(matches!(err, PrimaryKeyError::NotValueType(_)));
}
#[test]
fn pk_with_scoped_member_yields_interface_reference_error() {
let v = pk_value(
ValueKind::Concrete,
Some(pk_inheritance()),
alloc::vec![public_state(TypeSpec::Scoped(scoped(&["IFoo"])))],
);
let err = validate_primary_key(&v).expect_err("error");
assert!(matches!(err, PrimaryKeyError::HasInterfaceReference(_)));
}
use crate::transform::{HomeEquivalent, transform_home};
use zerodds_idl::ast::HomeDef;
fn home_with_pk() -> HomeEquivalent {
let h = HomeDef {
name: ident("CManager"),
base: None,
supports: Vec::new(),
manages: scoped(&["CWidget"]),
primary_key: Some(scoped(&["CKey"])),
annotations: Vec::new(),
span: Span::SYNTHETIC,
};
transform_home(&h)
}
fn long_param(n: &str) -> ParamDecl {
ParamDecl {
attribute: ParamAttribute::In,
type_spec: long_ty(),
name: ident(n),
annotations: Vec::new(),
span: Span::SYNTHETIC,
}
}
#[test]
fn factory_op_emitted_with_create_failure_raises() {
let mut h = home_with_pk();
let factory = InitOp {
name: ident("create_widget"),
params: alloc::vec![long_param("size")],
raises: Vec::new(),
};
apply_factory_finder_body(&mut h, &[factory], &[]);
let names: Vec<String> = h
.explicit
.exports
.iter()
.filter_map(|e| match e {
Export::Op(o) => Some(o.name.text.clone()),
_ => None,
})
.collect();
assert!(names.contains(&String::from("create_widget")));
let op = h
.explicit
.exports
.iter()
.find_map(|e| match e {
Export::Op(o) if o.name.text == "create_widget" => Some(o),
_ => None,
})
.expect("op present");
let raises_first = op.raises[0]
.parts
.iter()
.map(|i| i.text.as_str())
.collect::<Vec<_>>();
assert_eq!(raises_first, alloc::vec!["Components", "CreateFailure"]);
}
#[test]
fn finder_op_emitted_with_finder_failure_raises() {
let mut h = home_with_pk();
let finder = InitOp {
name: ident("find_by_size"),
params: alloc::vec![long_param("size")],
raises: Vec::new(),
};
apply_factory_finder_body(&mut h, &[], &[finder]);
let op = h
.explicit
.exports
.iter()
.find_map(|e| match e {
Export::Op(o) if o.name.text == "find_by_size" => Some(o),
_ => None,
})
.expect("op present");
let raises_first = op.raises[0]
.parts
.iter()
.map(|i| i.text.as_str())
.collect::<Vec<_>>();
assert_eq!(raises_first, alloc::vec!["Components", "FinderFailure"]);
}
#[test]
fn caller_raises_are_appended_to_create_failure() {
let mut h = home_with_pk();
let f = InitOp {
name: ident("create_widget"),
params: alloc::vec![],
raises: alloc::vec![scoped(&["MyExcep"])],
};
apply_factory_finder_body(&mut h, &[f], &[]);
let op = h
.explicit
.exports
.iter()
.find_map(|e| match e {
Export::Op(o) if o.name.text == "create_widget" => Some(o),
_ => None,
})
.expect("op present");
assert_eq!(op.raises.len(), 2);
assert_eq!(op.raises[1].parts[0].text, "MyExcep");
}
#[test]
fn factory_op_returns_home_equivalent_type() {
let mut h = home_with_pk();
let f = InitOp {
name: ident("create_default"),
params: alloc::vec![],
raises: Vec::new(),
};
apply_factory_finder_body(&mut h, &[f], &[]);
let op = h
.explicit
.exports
.iter()
.find_map(|e| match e {
Export::Op(o) if o.name.text == "create_default" => Some(o),
_ => None,
})
.expect("op present");
if let Some(TypeSpec::Scoped(s)) = &op.return_type {
assert_eq!(s.parts[0].text, "CManager");
} else {
panic!("expected scoped return type");
}
}
#[test]
fn init_dcl_into_init_op_conversion_preserves_fields() {
let init = InitDcl {
name: ident("create_x"),
params: alloc::vec![long_param("a")],
raises: alloc::vec![scoped(&["Excp"])],
span: Span::SYNTHETIC,
};
let op: InitOp = init.into();
assert_eq!(op.name.text, "create_x");
assert_eq!(op.params.len(), 1);
assert_eq!(op.raises.len(), 1);
}
}