diff --git a/Cargo.lock b/Cargo.lock index 7c152e3..56253cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,7 +1010,7 @@ dependencies = [ [[package]] name = "kinode_process_lib" -version = "0.5.8" +version = "0.6.0" dependencies = [ "alloy-rpc-types", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 2adda33..d72bafb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "kinode_process_lib" description = "A library for writing Kinode processes in Rust." -version = "0.5.9" +version = "0.6.0" edition = "2021" license-file = "LICENSE" homepage = "https://kinode.org" repository = "https://github.com/kinode-dao/process_lib" [features] -eth = [ "ethers-core", "alloy-rpc-types" ] +eth = ["ethers-core", "alloy-rpc-types"] [dependencies] alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy.git", rev = "3b1c310", optional = true } diff --git a/src/capability.rs b/src/capability.rs index ca0a555..d43e4e1 100644 --- a/src/capability.rs +++ b/src/capability.rs @@ -1,5 +1,6 @@ -pub use crate::{Address, Capability, ProcessId}; -use serde::{Deserialize, Serialize}; +pub use crate::{Address, Capability}; +use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct}; use std::hash::{Hash, Hasher}; /// Capability is defined in the wit bindings, but constructors and methods here. @@ -29,66 +30,15 @@ impl Capability { } } -impl std::str::FromStr for Capability { - type Err = CapabilityParseError; - /// Attempt to parse a `Capability` from a string. The formatting structure for - /// a Capability is `issuer^params`. - /// TODO not tested - fn from_str(input: &str) -> Result { - // split string on colons into 4 segments, - // first one with @, next 3 with : - let mut name_rest = input.split('@'); - let node = name_rest - .next() - .ok_or(CapabilityParseError::MissingField)? - .to_string(); - let mut param_segments = name_rest - .next() - .ok_or(CapabilityParseError::MissingNodeId)? - .split('^'); - let mut segments = param_segments - .next() - .ok_or(CapabilityParseError::MissingNodeId)? - .split(':'); - let process_name = segments - .next() - .ok_or(CapabilityParseError::MissingField)? - .to_string(); - let package_name = segments - .next() - .ok_or(CapabilityParseError::MissingField)? - .to_string(); - let publisher_node = segments - .next() - .ok_or(CapabilityParseError::MissingField)? - .to_string(); - let params = param_segments - .next() - .ok_or(CapabilityParseError::MissingParams)? - .to_string(); - if segments.next().is_some() { - return Err(CapabilityParseError::TooManyColons); - } - Ok(Capability { - issuer: Address { - node, - process: ProcessId { - process_name, - package_name, - publisher_node, - }, - }, - params, - }) - } -} - impl Serialize for Capability { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { - format!("{}", self).serialize(serializer) + let mut state = serializer.serialize_struct("Capability", 2)?; + state.serialize_field("issuer", &self.issuer)?; + state.serialize_field("params", &self.params)?; + state.end() } } @@ -97,8 +47,97 @@ impl<'a> Deserialize<'a> for Capability { where D: serde::de::Deserializer<'a>, { - let s = String::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) + enum Field { + Issuer, + Params, + } + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("`issuer` or `params`") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "issuer" => Ok(Field::Issuer), + "params" => Ok(Field::Params), + _ => Err(de::Error::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct CapabilityVisitor; + + impl<'de> Visitor<'de> for CapabilityVisitor { + type Value = Capability; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct Capability") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let issuer: Address = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let params: String = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + Ok(Capability::new(issuer, params)) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut issuer: Option
= None; + let mut params: Option = None; + while let Some(key) = map.next_key()? { + match key { + Field::Issuer => { + if issuer.is_some() { + return Err(de::Error::duplicate_field("issuer")); + } + issuer = Some(map.next_value()?); + } + Field::Params => { + if params.is_some() { + return Err(de::Error::duplicate_field("params")); + } + params = Some(map.next_value()?); + } + } + } + let issuer: Address = issuer + .ok_or_else(|| de::Error::missing_field("issuer"))? + .into(); + let params: String = params + .ok_or_else(|| de::Error::missing_field("params"))? + .into(); + Ok(Capability::new(issuer, params)) + } + } + + const FIELDS: &'static [&'static str] = &["issuer", "params"]; + deserializer.deserialize_struct("Capability", FIELDS, CapabilityVisitor) } } @@ -134,41 +173,6 @@ where impl std::fmt::Display for Capability { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}^{}", self.issuer, self.params) - } -} - -/// Error type for parsing an `Address` from a string. -#[derive(Debug)] -pub enum CapabilityParseError { - TooManyColons, - MissingNodeId, - MissingField, - MissingParams, -} - -impl std::fmt::Display for CapabilityParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - CapabilityParseError::TooManyColons => "Too many colons in ProcessId string", - CapabilityParseError::MissingNodeId => "Node ID missing", - CapabilityParseError::MissingField => "Missing field in ProcessId string", - CapabilityParseError::MissingParams => "Missing params in Capability string", - } - ) - } -} - -impl std::error::Error for CapabilityParseError { - fn description(&self) -> &str { - match self { - CapabilityParseError::TooManyColons => "Too many colons in ProcessId string", - CapabilityParseError::MissingNodeId => "Node ID missing", - CapabilityParseError::MissingField => "Missing field in ProcessId string", - CapabilityParseError::MissingParams => "Missing params in Capability string", - } + write!(f, "{}({})", self.issuer, self.params) } } diff --git a/src/kernel_types.rs b/src/kernel_types.rs index 758af9a..1d73be8 100644 --- a/src/kernel_types.rs +++ b/src/kernel_types.rs @@ -106,6 +106,11 @@ pub enum KernelCommand { target: ProcessId, capabilities: Vec, }, + /// Drop capabilities. Does nothing if process doesn't have these caps + DropCapabilities { + target: ProcessId, + capabilities: Vec, + }, /// Tell the kernel to run a process that has already been installed. /// TODO: in the future, this command could be extended to allow for /// resource provision. @@ -219,7 +224,7 @@ pub struct PackageManifestEntry { } /// the type that gets deserialized from a `scripts.json` object -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct DotScriptsEntry { pub root: bool, pub public: bool,