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