1use super::tables::Value;
5use crate::RpcTarget;
6use std::collections::HashMap;
7use std::sync::Arc;
8use tokio::sync::RwLock;
9use uuid::Uuid;
10
11#[async_trait::async_trait]
13pub trait CapabilityFactory: Send + Sync + std::fmt::Debug {
14 async fn create_capability(
16 &self,
17 capability_type: &str,
18 config: Value,
19 ) -> Result<Arc<dyn RpcTarget>, CapabilityError>;
20
21 fn list_capability_types(&self) -> Vec<String>;
23
24 fn get_capability_metadata(&self, capability_type: &str) -> Option<CapabilityMetadata>;
26}
27
28#[derive(Debug, Clone)]
30pub struct CapabilityMetadata {
31 pub name: String,
32 pub description: String,
33 pub version: String,
34 pub methods: Vec<MethodMetadata>,
35 pub config_schema: Option<Value>, }
37
38#[derive(Debug, Clone)]
40pub struct MethodMetadata {
41 pub name: String,
42 pub description: String,
43 pub parameters: Vec<ParameterMetadata>,
44 pub return_type: String,
45}
46
47#[derive(Debug, Clone)]
49pub struct ParameterMetadata {
50 pub name: String,
51 pub type_name: String,
52 pub required: bool,
53 pub description: String,
54}
55
56#[async_trait::async_trait]
58pub trait NestedCapableRpcTarget: RpcTarget {
59 async fn create_sub_capability(
61 &self,
62 capability_type: &str,
63 config: Value,
64 ) -> Result<Value, crate::RpcError>;
65
66 async fn list_capability_types(&self) -> Result<Value, crate::RpcError>;
68
69 async fn get_capability_metadata(
71 &self,
72 capability_type: &str,
73 ) -> Result<Value, crate::RpcError>;
74
75 async fn list_child_capabilities(&self) -> Result<Value, crate::RpcError>;
77
78 async fn dispose_child_capability(&self, capability_id: &str)
80 -> Result<Value, crate::RpcError>;
81}
82
83#[derive(Debug)]
85pub struct CapabilityGraph {
86 nodes: Arc<RwLock<HashMap<String, CapabilityNode>>>,
88 edges: Arc<RwLock<HashMap<String, Vec<String>>>>, reference_counts: Arc<RwLock<HashMap<String, usize>>>,
92}
93
94#[derive(Debug, Clone)]
96pub struct CapabilityNode {
97 pub id: String,
98 pub capability_type: String,
99 pub parent_id: Option<String>,
100 pub created_at: u64,
101 pub config: Value,
102 pub metadata: CapabilityMetadata,
103}
104
105impl CapabilityGraph {
106 pub fn new() -> Self {
108 Self {
109 nodes: Arc::new(RwLock::new(HashMap::new())),
110 edges: Arc::new(RwLock::new(HashMap::new())),
111 reference_counts: Arc::new(RwLock::new(HashMap::new())),
112 }
113 }
114
115 pub async fn add_capability(&self, node: CapabilityNode) -> Result<(), CapabilityError> {
117 let mut nodes = self.nodes.write().await;
118 let mut edges = self.edges.write().await;
119 let mut ref_counts = self.reference_counts.write().await;
120
121 tracing::debug!(
122 capability_id = %node.id,
123 capability_type = %node.capability_type,
124 parent_id = ?node.parent_id,
125 "Adding capability to graph"
126 );
127
128 nodes.insert(node.id.clone(), node.clone());
130
131 ref_counts.insert(node.id.clone(), 1);
133
134 if let Some(parent_id) = &node.parent_id {
136 edges
137 .entry(parent_id.clone())
138 .or_insert_with(Vec::new)
139 .push(node.id.clone());
140 }
141
142 Ok(())
143 }
144
145 pub async fn get_capability(&self, id: &str) -> Option<CapabilityNode> {
147 let nodes = self.nodes.read().await;
148 nodes.get(id).cloned()
149 }
150
151 pub async fn get_children(&self, parent_id: &str) -> Vec<String> {
153 let edges = self.edges.read().await;
154 edges.get(parent_id).cloned().unwrap_or_default()
155 }
156
157 pub async fn get_descendants(&self, parent_id: &str) -> Vec<String> {
159 let mut descendants = Vec::new();
160 let mut to_visit = vec![parent_id.to_string()];
161
162 while let Some(current_id) = to_visit.pop() {
163 let children = self.get_children(¤t_id).await;
164 for child_id in children {
165 descendants.push(child_id.clone());
166 to_visit.push(child_id);
167 }
168 }
169
170 descendants
171 }
172
173 pub async fn add_reference(&self, id: &str) -> Result<usize, CapabilityError> {
175 let mut ref_counts = self.reference_counts.write().await;
176 match ref_counts.get_mut(id) {
177 Some(count) => {
178 *count += 1;
179 Ok(*count)
180 }
181 None => Err(CapabilityError::CapabilityNotFound(id.to_string())),
182 }
183 }
184
185 pub async fn remove_reference(&self, id: &str) -> Result<bool, CapabilityError> {
187 let mut should_dispose = false;
188
189 {
190 let mut ref_counts = self.reference_counts.write().await;
191 match ref_counts.get_mut(id) {
192 Some(count) => {
193 *count -= 1;
194 if *count == 0 {
195 should_dispose = true;
196 ref_counts.remove(id);
197 }
198 }
199 None => return Err(CapabilityError::CapabilityNotFound(id.to_string())),
200 }
201 }
202
203 if should_dispose {
204 self.remove_capability(id).await?;
205 }
206
207 Ok(should_dispose)
208 }
209
210 pub async fn remove_capability(&self, id: &str) -> Result<(), CapabilityError> {
212 tracing::debug!(capability_id = %id, "Removing capability from graph");
213
214 let descendants = self.get_descendants(id).await;
216
217 {
219 let mut nodes = self.nodes.write().await;
220 let mut edges = self.edges.write().await;
221
222 for desc_id in &descendants {
223 nodes.remove(desc_id);
224 edges.remove(desc_id);
225 }
226
227 nodes.remove(id);
229 edges.remove(id);
230 }
231
232 {
234 let nodes = self.nodes.read().await;
235 if let Some(node) = nodes.get(id) {
236 if let Some(parent_id) = &node.parent_id {
237 let mut edges = self.edges.write().await;
238 if let Some(children) = edges.get_mut(parent_id) {
239 children.retain(|child_id| child_id != id);
240 }
241 }
242 }
243 }
244
245 tracing::debug!(
246 capability_id = %id,
247 descendants_count = descendants.len(),
248 "Capability and descendants removed from graph"
249 );
250
251 Ok(())
252 }
253
254 pub async fn get_stats(&self) -> CapabilityGraphStats {
256 let nodes = self.nodes.read().await;
257 let edges = self.edges.read().await;
258
259 let total_capabilities = nodes.len();
260 let root_capabilities = nodes
261 .values()
262 .filter(|node| node.parent_id.is_none())
263 .count();
264 let max_depth = self.calculate_max_depth(&nodes, &edges).await;
265
266 CapabilityGraphStats {
267 total_capabilities,
268 root_capabilities,
269 max_depth,
270 total_edges: edges.values().map(|children| children.len()).sum(),
271 }
272 }
273
274 async fn calculate_max_depth(
275 &self,
276 nodes: &HashMap<String, CapabilityNode>,
277 edges: &HashMap<String, Vec<String>>,
278 ) -> usize {
279 let mut max_depth = 0;
280
281 for (id, node) in nodes {
282 if node.parent_id.is_none() {
283 let depth = Self::calculate_depth_recursive(id, edges, 0);
284 max_depth = max_depth.max(depth);
285 }
286 }
287
288 max_depth
289 }
290
291 fn calculate_depth_recursive(
292 node_id: &str,
293 edges: &HashMap<String, Vec<String>>,
294 current_depth: usize,
295 ) -> usize {
296 let mut max_child_depth = current_depth;
297
298 if let Some(children) = edges.get(node_id) {
299 for child_id in children {
300 let child_depth =
301 Self::calculate_depth_recursive(child_id, edges, current_depth + 1);
302 max_child_depth = max_child_depth.max(child_depth);
303 }
304 }
305
306 max_child_depth
307 }
308}
309
310impl Default for CapabilityGraph {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316#[derive(Debug, Clone)]
318pub struct CapabilityGraphStats {
319 pub total_capabilities: usize,
320 pub root_capabilities: usize,
321 pub max_depth: usize,
322 pub total_edges: usize,
323}
324
325#[derive(Debug)]
327pub struct DefaultNestedCapableTarget {
328 id: String,
329 capability_type: String,
330 factory: Arc<dyn CapabilityFactory>,
331 graph: Arc<CapabilityGraph>,
332 children: Arc<RwLock<HashMap<String, Arc<dyn RpcTarget>>>>,
333 delegate: Arc<dyn RpcTarget>, }
335
336impl DefaultNestedCapableTarget {
337 pub fn new(
339 capability_type: String,
340 factory: Arc<dyn CapabilityFactory>,
341 graph: Arc<CapabilityGraph>,
342 delegate: Arc<dyn RpcTarget>,
343 ) -> Self {
344 Self {
345 id: Uuid::new_v4().to_string(),
346 capability_type,
347 factory,
348 graph,
349 children: Arc::new(RwLock::new(HashMap::new())),
350 delegate,
351 }
352 }
353
354 pub fn id(&self) -> &str {
356 &self.id
357 }
358}
359
360#[async_trait::async_trait]
361impl NestedCapableRpcTarget for DefaultNestedCapableTarget {
362 async fn create_sub_capability(
363 &self,
364 capability_type: &str,
365 config: Value,
366 ) -> Result<Value, crate::RpcError> {
367 tracing::debug!(
368 parent_id = %self.id,
369 capability_type = %capability_type,
370 "Creating sub-capability"
371 );
372
373 let capability = self
375 .factory
376 .create_capability(capability_type, config.clone())
377 .await
378 .map_err(|e| crate::RpcError::internal(e.to_string()))?;
379
380 let capability_id = Uuid::new_v4().to_string();
381
382 let metadata = self
384 .factory
385 .get_capability_metadata(capability_type)
386 .unwrap_or_else(|| CapabilityMetadata {
387 name: capability_type.to_string(),
388 description: format!("Dynamically created {} capability", capability_type),
389 version: "1.0.0".to_string(),
390 methods: Vec::new(),
391 config_schema: None,
392 });
393
394 let node = CapabilityNode {
396 id: capability_id.clone(),
397 capability_type: capability_type.to_string(),
398 parent_id: Some(self.id.clone()),
399 created_at: std::time::SystemTime::now()
400 .duration_since(std::time::UNIX_EPOCH)
401 .unwrap()
402 .as_secs(),
403 config,
404 metadata,
405 };
406
407 self.graph
409 .add_capability(node)
410 .await
411 .map_err(|e| crate::RpcError::internal(e.to_string()))?;
412
413 self.children
415 .write()
416 .await
417 .insert(capability_id.clone(), capability);
418
419 tracing::debug!(
420 parent_id = %self.id,
421 child_id = %capability_id,
422 capability_type = %capability_type,
423 "Sub-capability created successfully"
424 );
425
426 Ok(Value::Object({
428 let mut obj = std::collections::HashMap::new();
429 obj.insert(
430 "capability_id".to_string(),
431 Box::new(Value::String(capability_id)),
432 );
433 obj.insert(
434 "capability_type".to_string(),
435 Box::new(Value::String(capability_type.to_string())),
436 );
437 obj.insert(
438 "parent_id".to_string(),
439 Box::new(Value::String(self.id.clone())),
440 );
441 obj
442 }))
443 }
444
445 async fn list_capability_types(&self) -> Result<Value, crate::RpcError> {
446 let types = self.factory.list_capability_types();
447 let values: Vec<Value> = types.into_iter().map(Value::String).collect();
448 Ok(Value::Array(values))
449 }
450
451 async fn get_capability_metadata(
452 &self,
453 capability_type: &str,
454 ) -> Result<Value, crate::RpcError> {
455 match self.factory.get_capability_metadata(capability_type) {
456 Some(metadata) => {
457 let mut obj = std::collections::HashMap::new();
458 obj.insert("name".to_string(), Box::new(Value::String(metadata.name)));
459 obj.insert(
460 "description".to_string(),
461 Box::new(Value::String(metadata.description)),
462 );
463 obj.insert(
464 "version".to_string(),
465 Box::new(Value::String(metadata.version)),
466 );
467
468 let methods: Vec<Value> = metadata
469 .methods
470 .into_iter()
471 .map(|method| {
472 let mut method_obj = std::collections::HashMap::new();
473 method_obj.insert("name".to_string(), Box::new(Value::String(method.name)));
474 method_obj.insert(
475 "description".to_string(),
476 Box::new(Value::String(method.description)),
477 );
478 method_obj.insert(
479 "return_type".to_string(),
480 Box::new(Value::String(method.return_type)),
481 );
482
483 let params: Vec<Value> = method
484 .parameters
485 .into_iter()
486 .map(|param| {
487 let mut param_obj = std::collections::HashMap::new();
488 param_obj.insert(
489 "name".to_string(),
490 Box::new(Value::String(param.name)),
491 );
492 param_obj.insert(
493 "type".to_string(),
494 Box::new(Value::String(param.type_name)),
495 );
496 param_obj.insert(
497 "required".to_string(),
498 Box::new(Value::Bool(param.required)),
499 );
500 param_obj.insert(
501 "description".to_string(),
502 Box::new(Value::String(param.description)),
503 );
504 Value::Object(param_obj)
505 })
506 .collect();
507
508 method_obj.insert("parameters".to_string(), Box::new(Value::Array(params)));
509 Value::Object(method_obj)
510 })
511 .collect();
512
513 obj.insert("methods".to_string(), Box::new(Value::Array(methods)));
514
515 if let Some(schema) = metadata.config_schema {
516 obj.insert("config_schema".to_string(), Box::new(schema));
517 }
518
519 Ok(Value::Object(obj))
520 }
521 None => Err(crate::RpcError::not_found(format!(
522 "Capability type not found: {}",
523 capability_type
524 ))),
525 }
526 }
527
528 async fn list_child_capabilities(&self) -> Result<Value, crate::RpcError> {
529 let children_ids = self.graph.get_children(&self.id).await;
530
531 let mut children_info = Vec::new();
532
533 for child_id in children_ids {
534 if let Some(node) = self.graph.get_capability(&child_id).await {
535 let mut child_obj = std::collections::HashMap::new();
536 child_obj.insert("id".to_string(), Box::new(Value::String(node.id)));
537 child_obj.insert(
538 "type".to_string(),
539 Box::new(Value::String(node.capability_type)),
540 );
541 child_obj.insert(
542 "created_at".to_string(),
543 Box::new(Value::Number(serde_json::Number::from(node.created_at))),
544 );
545 children_info.push(Value::Object(child_obj));
546 }
547 }
548
549 Ok(Value::Array(children_info))
550 }
551
552 async fn dispose_child_capability(
553 &self,
554 capability_id: &str,
555 ) -> Result<Value, crate::RpcError> {
556 tracing::debug!(
557 parent_id = %self.id,
558 child_id = %capability_id,
559 "Disposing child capability"
560 );
561
562 let removed = self.children.write().await.remove(capability_id).is_some();
564
565 if removed {
566 self.graph
568 .remove_reference(capability_id)
569 .await
570 .map_err(|e| crate::RpcError::internal(e.to_string()))?;
571
572 tracing::debug!(
573 parent_id = %self.id,
574 child_id = %capability_id,
575 "Child capability disposed successfully"
576 );
577
578 Ok(Value::Bool(true))
579 } else {
580 Err(crate::RpcError::not_found(format!(
581 "Child capability not found: {}",
582 capability_id
583 )))
584 }
585 }
586}
587
588#[async_trait::async_trait]
589impl RpcTarget for DefaultNestedCapableTarget {
590 async fn call(&self, method: &str, args: Vec<Value>) -> Result<Value, crate::RpcError> {
591 match method {
592 "createSubCapability" => {
593 if args.len() != 2 {
594 return Err(crate::RpcError::bad_request(
595 "createSubCapability requires 2 arguments: capability_type, config",
596 ));
597 }
598
599 let capability_type = match &args[0] {
600 Value::String(s) => s.clone(),
601 _ => {
602 return Err(crate::RpcError::bad_request(
603 "capability_type must be a string",
604 ))
605 }
606 };
607
608 self.create_sub_capability(&capability_type, args[1].clone())
609 .await
610 }
611
612 "listCapabilityTypes" => {
613 if !args.is_empty() {
614 return Err(crate::RpcError::bad_request(
615 "listCapabilityTypes takes no arguments",
616 ));
617 }
618 self.list_capability_types().await
619 }
620
621 "getCapabilityMetadata" => {
622 if args.len() != 1 {
623 return Err(crate::RpcError::bad_request(
624 "getCapabilityMetadata requires 1 argument: capability_type",
625 ));
626 }
627
628 let capability_type = match &args[0] {
629 Value::String(s) => s.clone(),
630 _ => {
631 return Err(crate::RpcError::bad_request(
632 "capability_type must be a string",
633 ))
634 }
635 };
636
637 self.get_capability_metadata(&capability_type).await
638 }
639
640 "listChildCapabilities" => {
641 if !args.is_empty() {
642 return Err(crate::RpcError::bad_request(
643 "listChildCapabilities takes no arguments",
644 ));
645 }
646 self.list_child_capabilities().await
647 }
648
649 "disposeChildCapability" => {
650 if args.len() != 1 {
651 return Err(crate::RpcError::bad_request(
652 "disposeChildCapability requires 1 argument: capability_id",
653 ));
654 }
655
656 let capability_id = match &args[0] {
657 Value::String(s) => s.clone(),
658 _ => {
659 return Err(crate::RpcError::bad_request(
660 "capability_id must be a string",
661 ))
662 }
663 };
664
665 self.dispose_child_capability(&capability_id).await
666 }
667
668 _ if method.starts_with("child:") => {
670 let parts: Vec<&str> = method.splitn(3, ':').collect();
671 if parts.len() == 3 {
672 let child_id = parts[1];
673 let child_method = parts[2];
674
675 let children = self.children.read().await;
676 if let Some(child) = children.get(child_id) {
677 child.call(child_method, args).await
678 } else {
679 Err(crate::RpcError::not_found(format!(
680 "Child capability not found: {}",
681 child_id
682 )))
683 }
684 } else {
685 Err(crate::RpcError::bad_request(
686 "Invalid child method format: use 'child:id:method'",
687 ))
688 }
689 }
690
691 _ => self.delegate.call(method, args).await,
693 }
694 }
695
696 async fn get_property(&self, property: &str) -> Result<Value, crate::RpcError> {
697 match property {
698 "capability_id" => Ok(Value::String(self.id.clone())),
699 "capability_type" => Ok(Value::String(self.capability_type.clone())),
700 _ => self.delegate.get_property(property).await,
701 }
702 }
703}
704
705#[derive(Debug, thiserror::Error)]
707pub enum CapabilityError {
708 #[error("Capability not found: {0}")]
709 CapabilityNotFound(String),
710
711 #[error("Invalid capability type: {0}")]
712 InvalidCapabilityType(String),
713
714 #[error("Capability creation failed: {0}")]
715 CreationFailed(String),
716
717 #[error("Invalid configuration: {0}")]
718 InvalidConfiguration(String),
719
720 #[error("Graph operation failed: {0}")]
721 GraphOperationFailed(String),
722}
723
724#[cfg(test)]
725mod tests {
726 use super::*;
727 use serde_json::Number;
728
729 #[derive(Debug)]
731 struct MockCapabilityFactory;
732
733 #[async_trait::async_trait]
734 impl CapabilityFactory for MockCapabilityFactory {
735 async fn create_capability(
736 &self,
737 capability_type: &str,
738 _config: Value,
739 ) -> Result<Arc<dyn RpcTarget>, CapabilityError> {
740 match capability_type {
741 "calculator" => Ok(Arc::new(crate::MockRpcTarget::new())),
742 _ => Err(CapabilityError::InvalidCapabilityType(
743 capability_type.to_string(),
744 )),
745 }
746 }
747
748 fn list_capability_types(&self) -> Vec<String> {
749 vec!["calculator".to_string()]
750 }
751
752 fn get_capability_metadata(&self, capability_type: &str) -> Option<CapabilityMetadata> {
753 match capability_type {
754 "calculator" => Some(CapabilityMetadata {
755 name: "Calculator".to_string(),
756 description: "Basic calculator capability".to_string(),
757 version: "1.0.0".to_string(),
758 methods: vec![MethodMetadata {
759 name: "add".to_string(),
760 description: "Add two numbers".to_string(),
761 parameters: vec![
762 ParameterMetadata {
763 name: "a".to_string(),
764 type_name: "number".to_string(),
765 required: true,
766 description: "First number".to_string(),
767 },
768 ParameterMetadata {
769 name: "b".to_string(),
770 type_name: "number".to_string(),
771 required: true,
772 description: "Second number".to_string(),
773 },
774 ],
775 return_type: "number".to_string(),
776 }],
777 config_schema: None,
778 }),
779 _ => None,
780 }
781 }
782 }
783
784 #[tokio::test]
785 async fn test_capability_graph() {
786 let graph = CapabilityGraph::new();
787
788 let node = CapabilityNode {
789 id: "test-cap-1".to_string(),
790 capability_type: "calculator".to_string(),
791 parent_id: None,
792 created_at: 1234567890,
793 config: Value::Object(HashMap::new()),
794 metadata: CapabilityMetadata {
795 name: "Test Calculator".to_string(),
796 description: "Test capability".to_string(),
797 version: "1.0.0".to_string(),
798 methods: Vec::new(),
799 config_schema: None,
800 },
801 };
802
803 graph.add_capability(node.clone()).await.unwrap();
804
805 let retrieved = graph.get_capability("test-cap-1").await;
806 assert!(retrieved.is_some());
807 assert_eq!(retrieved.unwrap().id, "test-cap-1");
808
809 let stats = graph.get_stats().await;
810 assert_eq!(stats.total_capabilities, 1);
811 assert_eq!(stats.root_capabilities, 1);
812 assert_eq!(stats.max_depth, 0);
813 }
814
815 #[tokio::test]
816 async fn test_nested_capability_creation() {
817 let factory = Arc::new(MockCapabilityFactory);
818 let graph = Arc::new(CapabilityGraph::new());
819 let delegate = Arc::new(crate::MockRpcTarget::new());
820
821 let nested_target =
822 DefaultNestedCapableTarget::new("parent".to_string(), factory, graph.clone(), delegate);
823
824 let types = nested_target.list_capability_types().await.unwrap();
826 match types {
827 Value::Array(arr) => {
828 assert_eq!(arr.len(), 1);
829 match &arr[0] {
830 Value::String(s) => assert_eq!(s, "calculator"),
831 _ => panic!("Expected string"),
832 }
833 }
834 _ => panic!("Expected array"),
835 }
836
837 let config = Value::Object({
839 let mut obj = HashMap::new();
840 obj.insert(
841 "precision".to_string(),
842 Box::new(Value::Number(Number::from(2))),
843 );
844 obj
845 });
846
847 let result = nested_target
848 .create_sub_capability("calculator", config)
849 .await
850 .unwrap();
851
852 match result {
854 Value::Object(obj) => {
855 assert!(obj.contains_key("capability_id"));
856 assert!(obj.contains_key("capability_type"));
857 assert!(obj.contains_key("parent_id"));
858 }
859 _ => panic!("Expected object result"),
860 }
861
862 let children = nested_target.list_child_capabilities().await.unwrap();
864 match children {
865 Value::Array(arr) => {
866 assert_eq!(arr.len(), 1);
867 }
868 _ => panic!("Expected array of children"),
869 }
870
871 let stats = graph.get_stats().await;
873 assert_eq!(stats.total_capabilities, 1); assert_eq!(stats.root_capabilities, 0); assert_eq!(stats.max_depth, 0); }
877
878 #[tokio::test]
879 async fn test_capability_disposal() {
880 let factory = Arc::new(MockCapabilityFactory);
881 let graph = Arc::new(CapabilityGraph::new());
882 let delegate = Arc::new(crate::MockRpcTarget::new());
883
884 let nested_target =
885 DefaultNestedCapableTarget::new("parent".to_string(), factory, graph.clone(), delegate);
886
887 let config = Value::Object(HashMap::new());
889 let result = nested_target
890 .create_sub_capability("calculator", config)
891 .await
892 .unwrap();
893
894 let capability_id = match result {
895 Value::Object(ref obj) => match obj.get("capability_id").unwrap().as_ref() {
896 Value::String(id) => id.clone(),
897 _ => panic!("Expected string capability_id"),
898 },
899 _ => panic!("Expected object result"),
900 };
901
902 let children_before = nested_target.list_child_capabilities().await.unwrap();
904 match children_before {
905 Value::Array(arr) => assert_eq!(arr.len(), 1),
906 _ => panic!("Expected array"),
907 }
908
909 let disposed = nested_target
911 .dispose_child_capability(&capability_id)
912 .await
913 .unwrap();
914 match disposed {
915 Value::Bool(true) => {}
916 _ => panic!("Expected true"),
917 }
918
919 let children_after = nested_target.list_child_capabilities().await.unwrap();
921 match children_after {
922 Value::Array(arr) => assert_eq!(arr.len(), 0),
923 _ => panic!("Expected empty array"),
924 }
925 }
926}