icarus_core/
protocol.rs

1//! Metadata types for tool discovery and canister introspection
2
3use candid::{CandidType, Principal};
4use serde::{Deserialize, Serialize};
5
6/// Metadata about the Icarus canister for tool discovery
7#[derive(Debug, Clone, Serialize, Deserialize, CandidType)]
8pub struct IcarusMetadata {
9    /// Version of the Icarus protocol
10    pub version: String,
11    /// Canister ID where this server is deployed
12    pub canister_id: Principal,
13    /// List of available tools in this canister
14    pub tools: Vec<ToolMetadata>,
15}
16
17/// Metadata about a single tool
18#[derive(Debug, Clone, Serialize, Deserialize, CandidType)]
19pub struct ToolMetadata {
20    /// Name of the tool
21    pub name: String,
22    /// Candid method name to call on the canister
23    pub candid_method: String,
24    /// Whether this is a query call (read-only)
25    pub is_query: bool,
26    /// Human-readable description of what the tool does
27    pub description: String,
28    /// Parameters accepted by this tool
29    pub parameters: Vec<ParameterMetadata>,
30}
31
32/// Metadata about a tool parameter
33#[derive(Debug, Clone, Serialize, Deserialize, CandidType)]
34pub struct ParameterMetadata {
35    /// Parameter name
36    pub name: String,
37    /// Candid type of the parameter
38    pub candid_type: String,
39    /// Whether this parameter is required
40    pub required: bool,
41    /// Description of the parameter
42    pub description: String,
43}
44
45/// Canister configuration
46#[derive(Debug, Clone, Serialize, Deserialize, CandidType)]
47pub struct CanisterConfig {
48    pub name: String,
49    pub version: String,
50    pub canister_id: Principal,
51}
52
53/// Types of ICP subnets (kept for canister info)
54#[derive(Debug, Clone, Serialize, Deserialize, CandidType, PartialEq)]
55pub enum SubnetType {
56    Application,
57    System,
58    Fiduciary,
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_tool_metadata_creation() {
67        let params = vec![ParameterMetadata {
68            name: "input".to_string(),
69            candid_type: "text".to_string(),
70            required: true,
71            description: "Input text".to_string(),
72        }];
73
74        let tool = ToolMetadata {
75            name: "process".to_string(),
76            candid_method: "process_text".to_string(),
77            is_query: false,
78            description: "Process text input".to_string(),
79            parameters: params,
80        };
81
82        assert_eq!(tool.name, "process");
83        assert_eq!(tool.candid_method, "process_text");
84        assert!(!tool.is_query);
85        assert_eq!(tool.parameters.len(), 1);
86        assert_eq!(tool.parameters[0].name, "input");
87    }
88
89    #[test]
90    fn test_icarus_metadata_serialization() {
91        let metadata = IcarusMetadata {
92            version: "0.2.6".to_string(),
93            canister_id: Principal::from_text("rdmx6-jaaaa-aaaaa-aaadq-cai").unwrap(),
94            tools: vec![ToolMetadata {
95                name: "tool1".to_string(),
96                candid_method: "method1".to_string(),
97                is_query: true,
98                description: "First tool".to_string(),
99                parameters: vec![],
100            }],
101        };
102
103        // Test serialization round-trip
104        let serialized = serde_json::to_string(&metadata).unwrap();
105        let deserialized: IcarusMetadata = serde_json::from_str(&serialized).unwrap();
106
107        assert_eq!(deserialized.version, metadata.version);
108        assert_eq!(deserialized.canister_id, metadata.canister_id);
109        assert_eq!(deserialized.tools.len(), metadata.tools.len());
110        assert_eq!(deserialized.tools[0].name, "tool1");
111    }
112
113    #[test]
114    fn test_parameter_metadata() {
115        let param = ParameterMetadata {
116            name: "count".to_string(),
117            candid_type: "nat32".to_string(),
118            required: false,
119            description: "Number of items".to_string(),
120        };
121
122        assert_eq!(param.name, "count");
123        assert_eq!(param.candid_type, "nat32");
124        assert!(!param.required);
125        assert_eq!(param.description, "Number of items");
126    }
127
128    #[test]
129    fn test_canister_config() {
130        let config = CanisterConfig {
131            name: "test_canister".to_string(),
132            version: "1.0.0".to_string(),
133            canister_id: Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(),
134        };
135
136        assert_eq!(config.name, "test_canister");
137        assert_eq!(config.version, "1.0.0");
138        assert_eq!(config.canister_id.to_text(), "ryjl3-tyaaa-aaaaa-aaaba-cai");
139    }
140
141    #[test]
142    fn test_subnet_type() {
143        let app = SubnetType::Application;
144        let sys = SubnetType::System;
145        let fid = SubnetType::Fiduciary;
146
147        // Test serialization
148        assert_eq!(serde_json::to_string(&app).unwrap(), "\"Application\"");
149        assert_eq!(serde_json::to_string(&sys).unwrap(), "\"System\"");
150        assert_eq!(serde_json::to_string(&fid).unwrap(), "\"Fiduciary\"");
151
152        // Test deserialization
153        let deserialized: SubnetType = serde_json::from_str("\"Application\"").unwrap();
154        assert_eq!(deserialized, SubnetType::Application);
155    }
156
157    #[test]
158    fn test_metadata_with_multiple_tools() {
159        let tools = vec![
160            ToolMetadata {
161                name: "read".to_string(),
162                candid_method: "read_data".to_string(),
163                is_query: true,
164                description: "Read data".to_string(),
165                parameters: vec![],
166            },
167            ToolMetadata {
168                name: "write".to_string(),
169                candid_method: "write_data".to_string(),
170                is_query: false,
171                description: "Write data".to_string(),
172                parameters: vec![ParameterMetadata {
173                    name: "data".to_string(),
174                    candid_type: "text".to_string(),
175                    required: true,
176                    description: "Data to write".to_string(),
177                }],
178            },
179        ];
180
181        let metadata = IcarusMetadata {
182            version: "0.2.6".to_string(),
183            canister_id: Principal::anonymous(),
184            tools,
185        };
186
187        assert_eq!(metadata.tools.len(), 2);
188        assert!(metadata.tools[0].is_query);
189        assert!(!metadata.tools[1].is_query);
190        assert_eq!(metadata.tools[1].parameters.len(), 1);
191    }
192}