1use alloc::string::String;
26use alloc::vec::Vec;
27
28use zerodds_idl::ast::{
29 Export, Identifier, InitDcl, OpDecl, ParamAttribute, ParamDecl, ScopedName, ValueDef,
30 ValueElement, ValueKind,
31};
32use zerodds_idl::errors::Span;
33
34use crate::transform::{HomeEquivalent, scoped_name};
35
36#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum PrimaryKeyError {
43 NotValueType(String),
45 NotDerivedFromPrimaryKeyBase(String),
47 HasPrivateStateMembers(String),
49 HasInterfaceReference(String),
51}
52
53pub fn validate_primary_key(pk_type: &ValueDef) -> Result<(), PrimaryKeyError> {
64 if pk_type.kind == ValueKind::Abstract {
66 return Err(PrimaryKeyError::NotValueType(pk_type.name.text.clone()));
67 }
68
69 let derives_from_pk_base = pk_type
71 .inheritance
72 .as_ref()
73 .map(|i| {
74 i.bases.iter().any(|b| {
75 matches!(
76 (b.parts.len(), b.parts.first(), b.parts.get(1)),
77 (2, Some(c), Some(p)) if c.text == "Components" && p.text == "PrimaryKeyBase"
78 )
79 })
80 })
81 .unwrap_or(false);
82 if !derives_from_pk_base {
83 return Err(PrimaryKeyError::NotDerivedFromPrimaryKeyBase(
84 pk_type.name.text.clone(),
85 ));
86 }
87
88 for el in &pk_type.elements {
90 if let ValueElement::State(sm) = el {
91 if matches!(sm.visibility, zerodds_idl::ast::StateVisibility::Private) {
92 return Err(PrimaryKeyError::HasPrivateStateMembers(
93 pk_type.name.text.clone(),
94 ));
95 }
96 if matches!(sm.type_spec, zerodds_idl::ast::TypeSpec::Scoped(_)) {
97 return Err(PrimaryKeyError::HasInterfaceReference(
108 pk_type.name.text.clone(),
109 ));
110 }
111 }
112 }
113 Ok(())
114}
115
116#[derive(Debug, Clone)]
123pub struct InitOp {
124 pub name: Identifier,
126 pub params: Vec<ParamDecl>,
128 pub raises: Vec<ScopedName>,
131}
132
133impl From<InitDcl> for InitOp {
134 fn from(d: InitDcl) -> Self {
135 Self {
136 name: d.name,
137 params: d.params,
138 raises: d.raises,
139 }
140 }
141}
142
143pub fn apply_factory_finder_body(
154 home: &mut HomeEquivalent,
155 factories: &[InitOp],
156 finders: &[InitOp],
157) {
158 let span = Span::SYNTHETIC;
159 let component_type = zerodds_idl::ast::TypeSpec::Scoped(
160 home.equivalent.bases.first().cloned().unwrap_or_else(|| {
161 ScopedName::single(home.equivalent.name.clone())
163 }),
164 );
165 let _ = component_type; for f in factories {
173 let mut raises = alloc::vec![scoped_name(&["Components", "CreateFailure"], span)];
174 raises.extend(f.raises.clone());
175 let op = OpDecl {
176 name: f.name.clone(),
177 oneway: false,
178 return_type: Some(zerodds_idl::ast::TypeSpec::Scoped(ScopedName::single(
179 home.equivalent.name.clone(),
180 ))),
181 params: ensure_in_params(&f.params),
182 raises,
183 annotations: Vec::new(),
184 span,
185 };
186 home.explicit.exports.push(Export::Op(op));
187 }
188 for fi in finders {
189 let mut raises = alloc::vec![scoped_name(&["Components", "FinderFailure"], span)];
190 raises.extend(fi.raises.clone());
191 let op = OpDecl {
192 name: fi.name.clone(),
193 oneway: false,
194 return_type: Some(zerodds_idl::ast::TypeSpec::Scoped(ScopedName::single(
195 home.equivalent.name.clone(),
196 ))),
197 params: ensure_in_params(&fi.params),
198 raises,
199 annotations: Vec::new(),
200 span,
201 };
202 home.explicit.exports.push(Export::Op(op));
203 }
204}
205
206fn ensure_in_params(params: &[ParamDecl]) -> Vec<ParamDecl> {
207 let span = Span::SYNTHETIC;
208 params
209 .iter()
210 .map(|p| ParamDecl {
211 attribute: ParamAttribute::In,
212 type_spec: p.type_spec.clone(),
213 name: p.name.clone(),
214 annotations: Vec::new(),
215 span,
216 })
217 .collect()
218}
219
220#[cfg(test)]
225#[allow(clippy::expect_used, clippy::panic, clippy::unreachable)]
226mod tests {
227 use super::*;
228 use zerodds_idl::ast::{
229 FloatingType, IntegerType, PrimitiveType, StateMember, StateVisibility, StringType,
230 TypeSpec, ValueElement, ValueInheritanceSpec, ValueKind,
231 };
232
233 fn ident(name: &str) -> Identifier {
234 Identifier::new(name, Span::SYNTHETIC)
235 }
236
237 fn scoped(parts: &[&str]) -> ScopedName {
238 ScopedName {
239 absolute: false,
240 parts: parts.iter().map(|p| ident(p)).collect(),
241 span: Span::SYNTHETIC,
242 }
243 }
244
245 fn pk_value(
246 kind: ValueKind,
247 inheritance: Option<ValueInheritanceSpec>,
248 elements: Vec<ValueElement>,
249 ) -> ValueDef {
250 ValueDef {
251 name: ident("CKey"),
252 kind,
253 inheritance,
254 elements,
255 annotations: Vec::new(),
256 span: Span::SYNTHETIC,
257 }
258 }
259
260 fn public_state(ty: TypeSpec) -> ValueElement {
261 ValueElement::State(StateMember {
262 visibility: StateVisibility::Public,
263 type_spec: ty,
264 declarators: alloc::vec![zerodds_idl::ast::Declarator::Simple(ident("v"))],
265 annotations: Vec::new(),
266 span: Span::SYNTHETIC,
267 })
268 }
269
270 fn private_state(ty: TypeSpec) -> ValueElement {
271 ValueElement::State(StateMember {
272 visibility: StateVisibility::Private,
273 type_spec: ty,
274 declarators: alloc::vec![zerodds_idl::ast::Declarator::Simple(ident("v"))],
275 annotations: Vec::new(),
276 span: Span::SYNTHETIC,
277 })
278 }
279
280 fn long_ty() -> TypeSpec {
281 TypeSpec::Primitive(PrimitiveType::Integer(IntegerType::Long))
282 }
283
284 fn string_ty() -> TypeSpec {
285 TypeSpec::String(StringType {
286 wide: false,
287 bound: None,
288 span: Span::SYNTHETIC,
289 })
290 }
291
292 fn double_ty() -> TypeSpec {
293 TypeSpec::Primitive(PrimitiveType::Floating(FloatingType::Double))
294 }
295
296 fn pk_inheritance() -> ValueInheritanceSpec {
297 ValueInheritanceSpec {
298 truncatable: false,
299 bases: alloc::vec![scoped(&["Components", "PrimaryKeyBase"])],
300 supports: Vec::new(),
301 span: Span::SYNTHETIC,
302 }
303 }
304
305 #[test]
306 fn pk_with_correct_inheritance_and_public_long_member_ok() {
307 let v = pk_value(
308 ValueKind::Concrete,
309 Some(pk_inheritance()),
310 alloc::vec![public_state(long_ty())],
311 );
312 assert!(validate_primary_key(&v).is_ok());
313 }
314
315 #[test]
316 fn pk_without_inheritance_yields_error() {
317 let v = pk_value(
318 ValueKind::Concrete,
319 None,
320 alloc::vec![public_state(long_ty())],
321 );
322 let err = validate_primary_key(&v).expect_err("error");
323 assert!(matches!(
324 err,
325 PrimaryKeyError::NotDerivedFromPrimaryKeyBase(_)
326 ));
327 }
328
329 #[test]
330 fn pk_with_wrong_base_yields_error() {
331 let inh = ValueInheritanceSpec {
332 truncatable: false,
333 bases: alloc::vec![scoped(&["Other", "Base"])],
334 supports: Vec::new(),
335 span: Span::SYNTHETIC,
336 };
337 let v = pk_value(
338 ValueKind::Concrete,
339 Some(inh),
340 alloc::vec![public_state(long_ty())],
341 );
342 let err = validate_primary_key(&v).expect_err("error");
343 assert!(matches!(
344 err,
345 PrimaryKeyError::NotDerivedFromPrimaryKeyBase(_)
346 ));
347 }
348
349 #[test]
350 fn pk_with_private_state_member_yields_error() {
351 let v = pk_value(
352 ValueKind::Concrete,
353 Some(pk_inheritance()),
354 alloc::vec![private_state(long_ty())],
355 );
356 let err = validate_primary_key(&v).expect_err("error");
357 assert!(matches!(err, PrimaryKeyError::HasPrivateStateMembers(_)));
358 }
359
360 #[test]
361 fn pk_with_string_member_ok() {
362 let v = pk_value(
363 ValueKind::Concrete,
364 Some(pk_inheritance()),
365 alloc::vec![public_state(string_ty())],
366 );
367 assert!(validate_primary_key(&v).is_ok());
368 }
369
370 #[test]
371 fn pk_with_double_member_ok() {
372 let v = pk_value(
373 ValueKind::Concrete,
374 Some(pk_inheritance()),
375 alloc::vec![public_state(double_ty())],
376 );
377 assert!(validate_primary_key(&v).is_ok());
378 }
379
380 #[test]
381 fn pk_abstract_yields_error() {
382 let v = pk_value(
383 ValueKind::Abstract,
384 Some(pk_inheritance()),
385 alloc::vec![public_state(long_ty())],
386 );
387 let err = validate_primary_key(&v).expect_err("error");
388 assert!(matches!(err, PrimaryKeyError::NotValueType(_)));
389 }
390
391 #[test]
392 fn pk_with_scoped_member_yields_interface_reference_error() {
393 let v = pk_value(
394 ValueKind::Concrete,
395 Some(pk_inheritance()),
396 alloc::vec![public_state(TypeSpec::Scoped(scoped(&["IFoo"])))],
397 );
398 let err = validate_primary_key(&v).expect_err("error");
399 assert!(matches!(err, PrimaryKeyError::HasInterfaceReference(_)));
400 }
401
402 use crate::transform::{HomeEquivalent, transform_home};
405 use zerodds_idl::ast::HomeDef;
406
407 fn home_with_pk() -> HomeEquivalent {
408 let h = HomeDef {
409 name: ident("CManager"),
410 base: None,
411 supports: Vec::new(),
412 manages: scoped(&["CWidget"]),
413 primary_key: Some(scoped(&["CKey"])),
414 annotations: Vec::new(),
415 span: Span::SYNTHETIC,
416 };
417 transform_home(&h)
418 }
419
420 fn long_param(n: &str) -> ParamDecl {
421 ParamDecl {
422 attribute: ParamAttribute::In,
423 type_spec: long_ty(),
424 name: ident(n),
425 annotations: Vec::new(),
426 span: Span::SYNTHETIC,
427 }
428 }
429
430 #[test]
431 fn factory_op_emitted_with_create_failure_raises() {
432 let mut h = home_with_pk();
433 let factory = InitOp {
434 name: ident("create_widget"),
435 params: alloc::vec![long_param("size")],
436 raises: Vec::new(),
437 };
438 apply_factory_finder_body(&mut h, &[factory], &[]);
439 let names: Vec<String> = h
440 .explicit
441 .exports
442 .iter()
443 .filter_map(|e| match e {
444 Export::Op(o) => Some(o.name.text.clone()),
445 _ => None,
446 })
447 .collect();
448 assert!(names.contains(&String::from("create_widget")));
449 let op = h
450 .explicit
451 .exports
452 .iter()
453 .find_map(|e| match e {
454 Export::Op(o) if o.name.text == "create_widget" => Some(o),
455 _ => None,
456 })
457 .expect("op present");
458 let raises_first = op.raises[0]
459 .parts
460 .iter()
461 .map(|i| i.text.as_str())
462 .collect::<Vec<_>>();
463 assert_eq!(raises_first, alloc::vec!["Components", "CreateFailure"]);
464 }
465
466 #[test]
467 fn finder_op_emitted_with_finder_failure_raises() {
468 let mut h = home_with_pk();
469 let finder = InitOp {
470 name: ident("find_by_size"),
471 params: alloc::vec![long_param("size")],
472 raises: Vec::new(),
473 };
474 apply_factory_finder_body(&mut h, &[], &[finder]);
475 let op = h
476 .explicit
477 .exports
478 .iter()
479 .find_map(|e| match e {
480 Export::Op(o) if o.name.text == "find_by_size" => Some(o),
481 _ => None,
482 })
483 .expect("op present");
484 let raises_first = op.raises[0]
485 .parts
486 .iter()
487 .map(|i| i.text.as_str())
488 .collect::<Vec<_>>();
489 assert_eq!(raises_first, alloc::vec!["Components", "FinderFailure"]);
490 }
491
492 #[test]
493 fn caller_raises_are_appended_to_create_failure() {
494 let mut h = home_with_pk();
495 let f = InitOp {
496 name: ident("create_widget"),
497 params: alloc::vec![],
498 raises: alloc::vec![scoped(&["MyExcep"])],
499 };
500 apply_factory_finder_body(&mut h, &[f], &[]);
501 let op = h
502 .explicit
503 .exports
504 .iter()
505 .find_map(|e| match e {
506 Export::Op(o) if o.name.text == "create_widget" => Some(o),
507 _ => None,
508 })
509 .expect("op present");
510 assert_eq!(op.raises.len(), 2);
512 assert_eq!(op.raises[1].parts[0].text, "MyExcep");
513 }
514
515 #[test]
516 fn factory_op_returns_home_equivalent_type() {
517 let mut h = home_with_pk();
518 let f = InitOp {
519 name: ident("create_default"),
520 params: alloc::vec![],
521 raises: Vec::new(),
522 };
523 apply_factory_finder_body(&mut h, &[f], &[]);
524 let op = h
525 .explicit
526 .exports
527 .iter()
528 .find_map(|e| match e {
529 Export::Op(o) if o.name.text == "create_default" => Some(o),
530 _ => None,
531 })
532 .expect("op present");
533 if let Some(TypeSpec::Scoped(s)) = &op.return_type {
535 assert_eq!(s.parts[0].text, "CManager");
536 } else {
537 panic!("expected scoped return type");
538 }
539 }
540
541 #[test]
542 fn init_dcl_into_init_op_conversion_preserves_fields() {
543 let init = InitDcl {
544 name: ident("create_x"),
545 params: alloc::vec![long_param("a")],
546 raises: alloc::vec![scoped(&["Excp"])],
547 span: Span::SYNTHETIC,
548 };
549 let op: InitOp = init.into();
550 assert_eq!(op.name.text, "create_x");
551 assert_eq!(op.params.len(), 1);
552 assert_eq!(op.raises.len(), 1);
553 }
554}