@@ -2,7 +2,7 @@ pub use crate::{Address, PackageId, ProcessId};
22use serde:: { Deserialize , Serialize } ;
33use std:: hash:: { Hash , Hasher } ;
44
5- /// Address is defined in the wit bindings , but constructors and methods here.
5+ /// Address is defined in `kinode. wit` , but constructors and methods here.
66/// An `Address` is a combination of a node ID (string) and a [`ProcessId`]. It is
77/// used in the Request/Response pattern to indicate which process on a given node
88/// in the network to direct the message to. The formatting structure for
@@ -49,36 +49,42 @@ impl std::str::FromStr for Address {
4949 /// Attempt to parse an `Address` from a string. The formatting structure for
5050 /// an Address is `node@process_name:package_name:publisher_node`.
5151 ///
52- /// TODO: clarify if `@` can be present in process name / package name / publisher name
53- ///
54- /// TODO: ensure `:` cannot sneak into first segment
52+ /// The string being parsed must contain exactly one `@` and three `:` characters.
53+ /// The `@` character separates the node ID from the rest of the address, and the
54+ /// `:` characters separate the process name, package name, and publisher node ID.
5555 fn from_str ( input : & str ) -> Result < Self , AddressParseError > {
56- // split string on colons into 4 segments,
57- // first one with @, next 3 with :
58- let mut name_rest = input. split ( '@' ) ;
59- let node = name_rest
60- . next ( )
61- . ok_or ( AddressParseError :: MissingField ) ?
62- . to_string ( ) ;
63- let mut segments = name_rest
64- . next ( )
65- . ok_or ( AddressParseError :: MissingNodeId ) ?
66- . split ( ':' ) ;
67- let process_name = segments
68- . next ( )
69- . ok_or ( AddressParseError :: MissingField ) ?
70- . to_string ( ) ;
71- let package_name = segments
72- . next ( )
73- . ok_or ( AddressParseError :: MissingField ) ?
74- . to_string ( ) ;
75- let publisher_node = segments
76- . next ( )
77- . ok_or ( AddressParseError :: MissingField ) ?
78- . to_string ( ) ;
79- if segments. next ( ) . is_some ( ) {
56+ // split string on '@' and ensure there is exactly one '@'
57+ let parts: Vec < & str > = input. split ( '@' ) . collect ( ) ;
58+ if parts. len ( ) < 2 {
59+ return Err ( AddressParseError :: MissingNodeId ) ;
60+ } else if parts. len ( ) > 2 {
61+ return Err ( AddressParseError :: TooManyAts ) ;
62+ }
63+ let node = parts[ 0 ] . to_string ( ) ;
64+ if node. is_empty ( ) {
65+ return Err ( AddressParseError :: MissingNodeId ) ;
66+ }
67+
68+ // split the rest on ':' and ensure there are exactly three ':'
69+ let segments: Vec < & str > = parts[ 1 ] . split ( ':' ) . collect ( ) ;
70+ if segments. len ( ) < 3 {
71+ return Err ( AddressParseError :: MissingField ) ;
72+ } else if segments. len ( ) > 3 {
8073 return Err ( AddressParseError :: TooManyColons ) ;
8174 }
75+ let process_name = segments[ 0 ] . to_string ( ) ;
76+ if process_name. is_empty ( ) {
77+ return Err ( AddressParseError :: MissingField ) ;
78+ }
79+ let package_name = segments[ 1 ] . to_string ( ) ;
80+ if package_name. is_empty ( ) {
81+ return Err ( AddressParseError :: MissingField ) ;
82+ }
83+ let publisher_node = segments[ 2 ] . to_string ( ) ;
84+ if publisher_node. is_empty ( ) {
85+ return Err ( AddressParseError :: MissingField ) ;
86+ }
87+
8288 Ok ( Address {
8389 node,
8490 process : ProcessId {
@@ -163,31 +169,93 @@ impl std::fmt::Display for Address {
163169/// Error type for parsing an `Address` from a string.
164170#[ derive( Debug ) ]
165171pub enum AddressParseError {
172+ TooManyAts ,
166173 TooManyColons ,
167174 MissingNodeId ,
168175 MissingField ,
169176}
170177
171178impl std:: fmt:: Display for AddressParseError {
172179 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
173- write ! (
174- f,
175- "{}" ,
176- match self {
177- AddressParseError :: TooManyColons => "Too many colons in ProcessId string" ,
178- AddressParseError :: MissingNodeId => "Node ID missing" ,
179- AddressParseError :: MissingField => "Missing field in ProcessId string" ,
180- }
181- )
180+ write ! ( f, "{self}" )
182181 }
183182}
184183
185184impl std:: error:: Error for AddressParseError {
186185 fn description ( & self ) -> & str {
187186 match self {
187+ AddressParseError :: TooManyAts => "Too many '@' chars in ProcessId string" ,
188188 AddressParseError :: TooManyColons => "Too many colons in ProcessId string" ,
189189 AddressParseError :: MissingNodeId => "Node ID missing" ,
190190 AddressParseError :: MissingField => "Missing field in ProcessId string" ,
191191 }
192192 }
193193}
194+
195+ #[ cfg( test) ]
196+ mod tests {
197+ use super :: * ;
198+ use std:: str:: FromStr ;
199+
200+ #[ test]
201+ fn test_valid_address ( ) {
202+ let input = "node123@process1:packageA:publisherB" ;
203+ let address: Address = input. parse ( ) . unwrap ( ) ;
204+ assert_eq ! ( address. node( ) , "node123" ) ;
205+ assert_eq ! ( address. process( ) , "process1" ) ;
206+ assert_eq ! ( address. package( ) , "packageA" ) ;
207+ assert_eq ! ( address. publisher( ) , "publisherB" ) ;
208+ }
209+
210+ #[ test]
211+ fn test_missing_node_id ( ) {
212+ let input = "@process1:packageA:publisherB" ;
213+ assert ! ( matches!(
214+ Address :: from_str( input) ,
215+ Err ( AddressParseError :: MissingNodeId )
216+ ) ) ;
217+ }
218+
219+ #[ test]
220+ fn test_too_many_ats ( ) {
221+ let input = "node123@process1@packageA:publisherB" ;
222+ assert ! ( matches!(
223+ Address :: from_str( input) ,
224+ Err ( AddressParseError :: TooManyAts )
225+ ) ) ;
226+ }
227+
228+ #[ test]
229+ fn test_missing_field ( ) {
230+ let input = "node123@process1:packageA" ;
231+ assert ! ( matches!(
232+ Address :: from_str( input) ,
233+ Err ( AddressParseError :: MissingField )
234+ ) ) ;
235+ }
236+
237+ #[ test]
238+ fn test_too_many_colons ( ) {
239+ let input = "node123@process1:packageA:publisherB:extra" ;
240+ assert ! ( matches!(
241+ Address :: from_str( input) ,
242+ Err ( AddressParseError :: TooManyColons )
243+ ) ) ;
244+ }
245+
246+ #[ test]
247+ fn test_empty_input ( ) {
248+ let input = "" ;
249+ assert ! ( matches!(
250+ Address :: from_str( input) ,
251+ Err ( AddressParseError :: MissingNodeId )
252+ ) ) ;
253+ }
254+
255+ #[ test]
256+ fn test_display ( ) {
257+ let input = "node123@process1:packageA:publisherB" ;
258+ let address: Address = input. parse ( ) . unwrap ( ) ;
259+ assert_eq ! ( format!( "{}" , address) , input) ;
260+ }
261+ }
0 commit comments