Skip to content

Commit c230443

Browse files
committed
refactor basic types for cleaner code, better parsing
1 parent 7eb3a04 commit c230443

File tree

14 files changed

+294
-183
lines changed

14 files changed

+294
-183
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Library of functions for more ergonomic kinode process development.
55
To develop/build:
66
```
77
git submodule update --init
8+
cargo build
89
```
910

10-
Docs: (TODO link)
11+
Docs: waiting on other crates to be published.

src/eth.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ pub enum EthError {
9292

9393
/// The action type used for configuring eth:distro:sys. Only processes which have the "root"
9494
/// capability from eth:distro:sys can successfully send this action.
95-
///
96-
/// NOTE: changes to config will not be persisted between boots, they must be saved in .env
97-
/// to be reflected between boots. TODO: can change this
9895
#[derive(Debug, Serialize, Deserialize)]
9996
pub enum EthConfigAction {
10097
/// Add a new provider to the list of providers.

src/lib.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ pub use types::{
5353
address::{Address, AddressParseError},
5454
capability::Capability,
5555
lazy_load_blob::LazyLoadBlob,
56-
message::wit_message_to_message,
57-
message::{Message, SendError, SendErrorKind},
56+
message::{Message, _wit_message_to_message},
5857
on_exit::OnExit,
5958
package_id::PackageId,
6059
process_id::{ProcessId, ProcessIdParseError},
6160
request::Request,
6261
response::Response,
62+
send_error::{SendError, SendErrorKind, _wit_send_error_to_send_error},
6363
};
6464

6565
/// Implement the wit-bindgen specific code that the kernel uses to hook into
@@ -98,20 +98,24 @@ macro_rules! println {
9898
/// attempts to send a message to another node, that message may bounce back with
9999
/// a `SendError`. Those should be handled here.
100100
///
101-
/// TODO: example of usage
101+
/// Example:
102+
/// ```
103+
/// loop {
104+
/// match await_message() {
105+
/// Ok(msg) => {
106+
/// println!("Received message: {:?}", msg);
107+
/// // Do something with the message
108+
/// }
109+
/// Err(send_error) => {
110+
/// println!("Error sending message: {:?}", send_error);
111+
/// }
112+
/// }
113+
/// }
114+
/// ```
102115
pub fn await_message() -> Result<Message, SendError> {
103116
match crate::receive() {
104-
Ok((source, message)) => Ok(wit_message_to_message(source, message)),
105-
Err((send_err, context)) => Err(SendError {
106-
kind: match send_err.kind {
107-
crate::kinode::process::standard::SendErrorKind::Offline => SendErrorKind::Offline,
108-
crate::kinode::process::standard::SendErrorKind::Timeout => SendErrorKind::Timeout,
109-
},
110-
target: send_err.target.clone(),
111-
message: wit_message_to_message(send_err.target, send_err.message),
112-
lazy_load_blob: send_err.lazy_load_blob,
113-
context,
114-
}),
117+
Ok((source, message)) => Ok(_wit_message_to_message(source, message)),
118+
Err((send_err, context)) => Err(_wit_send_error_to_send_error(send_err, context)),
115119
}
116120
}
117121

@@ -137,7 +141,10 @@ pub fn spawn(
137141
/// Create a blob with no MIME type and a generic type, plus a serializer
138142
/// function that turns that type into bytes.
139143
///
140-
/// Example: TODO
144+
/// Example usage:
145+
/// ```
146+
/// make_blob(&my_type, |t| Ok(bincode::serialize(t)?));
147+
/// ```
141148
pub fn make_blob<T, F>(blob: &T, serializer: F) -> anyhow::Result<LazyLoadBlob>
142149
where
143150
F: Fn(&T) -> anyhow::Result<Vec<u8>>,

src/types/address.rs

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pub use crate::{Address, PackageId, ProcessId};
22
use serde::{Deserialize, Serialize};
33
use 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)]
165171
pub enum AddressParseError {
172+
TooManyAts,
166173
TooManyColons,
167174
MissingNodeId,
168175
MissingField,
169176
}
170177

171178
impl 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

185184
impl 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+
}

src/types/capability.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::hash::{Hash, Hasher};
55

66
/// Capability is defined in the wit bindings, but constructors and methods here.
77
/// A `Capability` is a combination of an Address and a set of Params (a serialized
8-
/// json string). Capabilities are attached to messages to either share that capability
8+
/// JSON string). Capabilities are attached to messages to either share that capability
99
/// with the receiving process, or to prove that a process has authority to perform a
1010
/// certain action.
1111
impl Capability {
@@ -28,6 +28,15 @@ impl Capability {
2828
pub fn params(&self) -> &str {
2929
&self.params
3030
}
31+
/// Read the params from a `Capability` as a `serde_json::Value`.
32+
pub fn params_json(&self) -> Result<serde_json::Value, serde_json::Error> {
33+
serde_json::from_str(&self.params)
34+
}
35+
/// Set the params for a `Capability` from a `serde_json::Value`.
36+
pub fn set_params_json(&mut self, value: serde_json::Value) -> Result<(), serde_json::Error> {
37+
self.params = serde_json::to_string(&value)?;
38+
Ok(())
39+
}
3140
}
3241

3342
impl Serialize for Capability {

src/types/lazy_load_blob.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
pub use crate::LazyLoadBlob;
22

3+
impl LazyLoadBlob {
4+
/// Create a new `LazyLoadBlob`. Takes a mime type and a byte vector.
5+
pub fn new<T, U>(mime: Option<T>, bytes: U) -> LazyLoadBlob
6+
where
7+
T: Into<String>,
8+
U: Into<Vec<u8>>,
9+
{
10+
LazyLoadBlob {
11+
mime: mime.map(|mime| mime.into()),
12+
bytes: bytes.into(),
13+
}
14+
}
15+
/// Read the mime type from a `LazyLoadBlob`.
16+
pub fn mime(&self) -> Option<&str> {
17+
self.mime.as_ref().map(|mime| mime.as_str())
18+
}
19+
/// Read the bytes from a `LazyLoadBlob`.
20+
pub fn bytes(&self) -> &[u8] {
21+
&self.bytes
22+
}
23+
}
24+
325
impl std::default::Default for LazyLoadBlob {
426
fn default() -> Self {
527
LazyLoadBlob {

0 commit comments

Comments
 (0)