| 1 | //! Bus match rule API. |
| 2 | |
| 3 | use std::{ |
| 4 | fmt::{Display, Write}, |
| 5 | ops::Deref, |
| 6 | }; |
| 7 | |
| 8 | use serde::{de, Deserialize, Serialize}; |
| 9 | use static_assertions::assert_impl_all; |
| 10 | use zvariant::Structure; |
| 11 | |
| 12 | use crate::{ |
| 13 | message::Type, |
| 14 | names::{BusName, InterfaceName, MemberName, UniqueName}, |
| 15 | zvariant::{ObjectPath, Str, Type as VariantType}, |
| 16 | Error, Result, |
| 17 | }; |
| 18 | |
| 19 | mod builder; |
| 20 | pub use builder::Builder; |
| 21 | |
| 22 | /// A bus match rule for subscribing to specific messages. |
| 23 | /// |
| 24 | /// This is mainly used by peer to subscribe to specific signals as by default the bus will not |
| 25 | /// send out most broadcasted signals. This API is intended to make it easy to create and parse |
| 26 | /// match rules. See the [match rules section of the D-Bus specification][mrs] for a description of |
| 27 | /// each possible element of a match rule. |
| 28 | /// |
| 29 | /// # Examples |
| 30 | /// |
| 31 | /// ``` |
| 32 | /// # fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 33 | /// # use zbus::MatchRule; |
| 34 | /// |
| 35 | /// // Let's take the most typical example of match rule to subscribe to properties' changes: |
| 36 | /// let rule = MatchRule::builder() |
| 37 | /// .msg_type(zbus::message::Type::Signal) |
| 38 | /// .sender("org.freedesktop.DBus" )? |
| 39 | /// .interface("org.freedesktop.DBus.Properties" )? |
| 40 | /// .member("PropertiesChanged" )? |
| 41 | /// .add_arg("org.zbus" )? |
| 42 | /// // Sometimes it's useful to match empty strings (null check). |
| 43 | /// .add_arg("" )? |
| 44 | /// .build(); |
| 45 | /// let rule_str = rule.to_string(); |
| 46 | /// assert_eq!( |
| 47 | /// rule_str, |
| 48 | /// "type='signal',\ |
| 49 | /// sender='org.freedesktop.DBus',\ |
| 50 | /// interface='org.freedesktop.DBus.Properties',\ |
| 51 | /// member='PropertiesChanged',\ |
| 52 | /// arg0='org.zbus',\ |
| 53 | /// arg1=''" , |
| 54 | /// ); |
| 55 | /// |
| 56 | /// // Let's parse it back. |
| 57 | /// let parsed_rule = MatchRule::try_from(rule_str.as_str())?; |
| 58 | /// assert_eq!(rule, parsed_rule); |
| 59 | /// |
| 60 | /// // Now for `ObjectManager::InterfacesAdded` signal. |
| 61 | /// let rule = MatchRule::builder() |
| 62 | /// .msg_type(zbus::message::Type::Signal) |
| 63 | /// .sender("org.zbus" )? |
| 64 | /// .interface("org.freedesktop.DBus.ObjectManager" )? |
| 65 | /// .member("InterfacesAdded" )? |
| 66 | /// .arg_path(0, "/org/zbus/NewPath" )? |
| 67 | /// .build(); |
| 68 | /// let rule_str = rule.to_string(); |
| 69 | /// assert_eq!( |
| 70 | /// rule_str, |
| 71 | /// "type='signal',\ |
| 72 | /// sender='org.zbus',\ |
| 73 | /// interface='org.freedesktop.DBus.ObjectManager',\ |
| 74 | /// member='InterfacesAdded',\ |
| 75 | /// arg0path='/org/zbus/NewPath'" , |
| 76 | /// ); |
| 77 | /// |
| 78 | /// // Let's parse it back. |
| 79 | /// let parsed_rule = MatchRule::try_from(rule_str.as_str())?; |
| 80 | /// assert_eq!(rule, parsed_rule); |
| 81 | /// |
| 82 | /// # Ok(()) |
| 83 | /// # } |
| 84 | /// ``` |
| 85 | /// |
| 86 | /// # Caveats |
| 87 | /// |
| 88 | /// The `PartialEq` implementation assumes arguments in both rules are in the same order. |
| 89 | /// |
| 90 | /// [mrs]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules |
| 91 | #[derive (Clone, Debug, PartialEq, Eq, Hash, VariantType)] |
| 92 | #[zvariant(signature = "s" )] |
| 93 | pub struct MatchRule<'m> { |
| 94 | pub(crate) msg_type: Option<Type>, |
| 95 | pub(crate) sender: Option<BusName<'m>>, |
| 96 | pub(crate) interface: Option<InterfaceName<'m>>, |
| 97 | pub(crate) member: Option<MemberName<'m>>, |
| 98 | pub(crate) path_spec: Option<PathSpec<'m>>, |
| 99 | pub(crate) destination: Option<UniqueName<'m>>, |
| 100 | pub(crate) args: Vec<(u8, Str<'m>)>, |
| 101 | pub(crate) arg_paths: Vec<(u8, ObjectPath<'m>)>, |
| 102 | pub(crate) arg0ns: Option<Str<'m>>, |
| 103 | } |
| 104 | |
| 105 | assert_impl_all!(MatchRule<'_>: Send, Sync, Unpin); |
| 106 | |
| 107 | impl<'m> MatchRule<'m> { |
| 108 | /// Create a builder for `MatchRule`. |
| 109 | pub fn builder() -> Builder<'m> { |
| 110 | Builder::new() |
| 111 | } |
| 112 | |
| 113 | /// The sender, if set. |
| 114 | pub fn sender(&self) -> Option<&BusName<'_>> { |
| 115 | self.sender.as_ref() |
| 116 | } |
| 117 | |
| 118 | /// The message type, if set. |
| 119 | pub fn msg_type(&self) -> Option<Type> { |
| 120 | self.msg_type |
| 121 | } |
| 122 | |
| 123 | /// The interfac, if set. |
| 124 | pub fn interface(&self) -> Option<&InterfaceName<'_>> { |
| 125 | self.interface.as_ref() |
| 126 | } |
| 127 | |
| 128 | /// The member name if set. |
| 129 | pub fn member(&self) -> Option<&MemberName<'_>> { |
| 130 | self.member.as_ref() |
| 131 | } |
| 132 | |
| 133 | /// The path or path namespace, if set. |
| 134 | pub fn path_spec(&self) -> Option<&PathSpec<'_>> { |
| 135 | self.path_spec.as_ref() |
| 136 | } |
| 137 | |
| 138 | /// The destination, if set. |
| 139 | pub fn destination(&self) -> Option<&UniqueName<'_>> { |
| 140 | self.destination.as_ref() |
| 141 | } |
| 142 | |
| 143 | /// The arguments. |
| 144 | pub fn args(&self) -> &[(u8, Str<'_>)] { |
| 145 | self.args.as_ref() |
| 146 | } |
| 147 | |
| 148 | /// The argument paths. |
| 149 | pub fn arg_paths(&self) -> &[(u8, ObjectPath<'_>)] { |
| 150 | self.arg_paths.as_ref() |
| 151 | } |
| 152 | |
| 153 | /// Match messages whose first argument is within the specified namespace. |
| 154 | pub fn arg0ns(&self) -> Option<&Str<'m>> { |
| 155 | self.arg0ns.as_ref() |
| 156 | } |
| 157 | |
| 158 | /// Creates an owned clone of `self`. |
| 159 | pub fn to_owned(&self) -> MatchRule<'static> { |
| 160 | MatchRule { |
| 161 | msg_type: self.msg_type, |
| 162 | sender: self.sender.as_ref().map(|s| s.to_owned()), |
| 163 | interface: self.interface.as_ref().map(|i| i.to_owned()), |
| 164 | member: self.member.as_ref().map(|m| m.to_owned()), |
| 165 | path_spec: self.path_spec.as_ref().map(|p| p.to_owned()), |
| 166 | destination: self.destination.as_ref().map(|d| d.to_owned()), |
| 167 | args: self.args.iter().map(|(i, s)| (*i, s.to_owned())).collect(), |
| 168 | arg_paths: self |
| 169 | .arg_paths |
| 170 | .iter() |
| 171 | .map(|(i, p)| (*i, p.to_owned())) |
| 172 | .collect(), |
| 173 | arg0ns: self.arg0ns.as_ref().map(|a| a.to_owned()), |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /// Creates an owned clone of `self`. |
| 178 | pub fn into_owned(self) -> MatchRule<'static> { |
| 179 | MatchRule { |
| 180 | msg_type: self.msg_type, |
| 181 | sender: self.sender.map(|s| s.into_owned()), |
| 182 | interface: self.interface.map(|i| i.into_owned()), |
| 183 | member: self.member.map(|m| m.into_owned()), |
| 184 | path_spec: self.path_spec.map(|p| p.into_owned()), |
| 185 | destination: self.destination.map(|d| d.into_owned()), |
| 186 | args: self |
| 187 | .args |
| 188 | .into_iter() |
| 189 | .map(|(i, s)| (i, s.into_owned())) |
| 190 | .collect(), |
| 191 | arg_paths: self |
| 192 | .arg_paths |
| 193 | .into_iter() |
| 194 | .map(|(i, p)| (i, p.into_owned())) |
| 195 | .collect(), |
| 196 | arg0ns: self.arg0ns.map(|a| a.into_owned()), |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /// Match the given message against this rule. |
| 201 | /// |
| 202 | /// # Caveats |
| 203 | /// |
| 204 | /// Since this method doesn't have any knowledge of names on the bus (or even connection to a |
| 205 | /// bus) matching always succeeds for: |
| 206 | /// |
| 207 | /// * `sender` in the rule (if set) that is a well-known name. The `sender` on a message is |
| 208 | /// always a unique name. |
| 209 | /// * `destination` in the rule when `destination` on the `msg` is a well-known name. The |
| 210 | /// `destination` on match rule is always a unique name. |
| 211 | pub fn matches(&self, msg: &zbus::message::Message) -> Result<bool> { |
| 212 | let hdr = msg.header(); |
| 213 | |
| 214 | // Start with message type. |
| 215 | if let Some(msg_type) = self.msg_type() { |
| 216 | if msg_type != msg.message_type() { |
| 217 | return Ok(false); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | // Then check sender. |
| 222 | if let Some(sender) = self.sender() { |
| 223 | match sender { |
| 224 | BusName::Unique(name) if Some(name) != hdr.sender() => { |
| 225 | return Ok(false); |
| 226 | } |
| 227 | BusName::Unique(_) => (), |
| 228 | // We can't match against a well-known name. |
| 229 | BusName::WellKnown(_) => (), |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // The interface. |
| 234 | if let Some(interface) = self.interface() { |
| 235 | match hdr.interface() { |
| 236 | Some(msg_interface) if interface != msg_interface => return Ok(false), |
| 237 | Some(_) => (), |
| 238 | None => return Ok(false), |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | // The member. |
| 243 | if let Some(member) = self.member() { |
| 244 | match hdr.member() { |
| 245 | Some(msg_member) if member != msg_member => return Ok(false), |
| 246 | Some(_) => (), |
| 247 | None => return Ok(false), |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | // The destination. |
| 252 | if let Some(destination) = self.destination() { |
| 253 | match hdr.destination() { |
| 254 | Some(BusName::Unique(name)) if destination != name => { |
| 255 | return Ok(false); |
| 256 | } |
| 257 | Some(BusName::Unique(_)) | None => (), |
| 258 | // We can't match against a well-known name. |
| 259 | Some(BusName::WellKnown(_)) => (), |
| 260 | }; |
| 261 | } |
| 262 | |
| 263 | // The path. |
| 264 | if let Some(path_spec) = self.path_spec() { |
| 265 | let msg_path = match hdr.path() { |
| 266 | Some(p) => p, |
| 267 | None => return Ok(false), |
| 268 | }; |
| 269 | match path_spec { |
| 270 | PathSpec::Path(path) if path != msg_path => return Ok(false), |
| 271 | PathSpec::PathNamespace(path_ns) if !msg_path.starts_with(path_ns.as_str()) => { |
| 272 | return Ok(false); |
| 273 | } |
| 274 | PathSpec::Path(_) | PathSpec::PathNamespace(_) => (), |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | // The arg0 namespace. |
| 279 | if let Some(arg0_ns) = self.arg0ns() { |
| 280 | if let Ok(arg0) = msg.body().deserialize_unchecked::<BusName<'_>>() { |
| 281 | match arg0.strip_prefix(arg0_ns.as_str()) { |
| 282 | None => return Ok(false), |
| 283 | Some(s) if !s.is_empty() && !s.starts_with('.' ) => return Ok(false), |
| 284 | _ => (), |
| 285 | } |
| 286 | } else { |
| 287 | return Ok(false); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | // Args |
| 292 | if self.args().is_empty() && self.arg_paths().is_empty() { |
| 293 | return Ok(true); |
| 294 | } |
| 295 | let body = msg.body(); |
| 296 | let structure = match body.deserialize::<Structure<'_>>() { |
| 297 | Ok(s) => s, |
| 298 | Err(_) => return Ok(false), |
| 299 | }; |
| 300 | let args = structure.fields(); |
| 301 | |
| 302 | for (i, arg) in self.args() { |
| 303 | match args.get(*i as usize) { |
| 304 | Some(msg_arg) => match <&str>::try_from(msg_arg) { |
| 305 | Ok(msg_arg) if arg != msg_arg => return Ok(false), |
| 306 | Ok(_) => (), |
| 307 | Err(_) => return Ok(false), |
| 308 | }, |
| 309 | None => return Ok(false), |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | // Path args |
| 314 | for (i, path) in self.arg_paths() { |
| 315 | match args.get(*i as usize) { |
| 316 | Some(msg_arg) => match <ObjectPath<'_>>::try_from(msg_arg) { |
| 317 | Ok(msg_arg) if *path != msg_arg => return Ok(false), |
| 318 | Ok(_) => (), |
| 319 | Err(_) => return Ok(false), |
| 320 | }, |
| 321 | None => return Ok(false), |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | Ok(true) |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | impl Display for MatchRule<'_> { |
| 330 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 331 | let mut first_component = true; |
| 332 | if let Some(msg_type) = self.msg_type() { |
| 333 | let type_str = match msg_type { |
| 334 | Type::Error => "error" , |
| 335 | Type::MethodCall => "method_call" , |
| 336 | Type::MethodReturn => "method_return" , |
| 337 | Type::Signal => "signal" , |
| 338 | }; |
| 339 | write_match_rule_string_component(f, "type" , type_str, &mut first_component)?; |
| 340 | } |
| 341 | if let Some(sender) = self.sender() { |
| 342 | write_match_rule_string_component(f, "sender" , sender, &mut first_component)?; |
| 343 | } |
| 344 | if let Some(interface) = self.interface() { |
| 345 | write_match_rule_string_component(f, "interface" , interface, &mut first_component)?; |
| 346 | } |
| 347 | if let Some(member) = self.member() { |
| 348 | write_match_rule_string_component(f, "member" , member, &mut first_component)?; |
| 349 | } |
| 350 | if let Some(destination) = self.destination() { |
| 351 | write_match_rule_string_component(f, "destination" , destination, &mut first_component)?; |
| 352 | } |
| 353 | if let Some(path_spec) = self.path_spec() { |
| 354 | let (key, value) = match path_spec { |
| 355 | PathSpec::Path(path) => ("path" , path), |
| 356 | PathSpec::PathNamespace(ns) => ("path_namespace" , ns), |
| 357 | }; |
| 358 | write_match_rule_string_component(f, key, value, &mut first_component)?; |
| 359 | } |
| 360 | for (i, arg) in self.args() { |
| 361 | write_comma(f, &mut first_component)?; |
| 362 | write!(f, "arg {i}=' {arg}'" )?; |
| 363 | } |
| 364 | for (i, arg_path) in self.arg_paths() { |
| 365 | write_comma(f, &mut first_component)?; |
| 366 | write!(f, "arg {i}path=' {arg_path}'" )?; |
| 367 | } |
| 368 | if let Some(arg0namespace) = self.arg0ns() { |
| 369 | write_comma(f, &mut first_component)?; |
| 370 | write!(f, "arg0namespace=' {arg0namespace}'" )?; |
| 371 | } |
| 372 | |
| 373 | Ok(()) |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | fn write_match_rule_string_component( |
| 378 | f: &mut std::fmt::Formatter<'_>, |
| 379 | key: &str, |
| 380 | value: &str, |
| 381 | first_component: &mut bool, |
| 382 | ) -> std::fmt::Result { |
| 383 | write_comma(f, first_component)?; |
| 384 | f.write_str(data:key)?; |
| 385 | f.write_str(data:"='" )?; |
| 386 | f.write_str(data:value)?; |
| 387 | f.write_char(' \'' )?; |
| 388 | |
| 389 | Ok(()) |
| 390 | } |
| 391 | |
| 392 | fn write_comma(f: &mut std::fmt::Formatter<'_>, first_component: &mut bool) -> std::fmt::Result { |
| 393 | if *first_component { |
| 394 | *first_component = false; |
| 395 | } else { |
| 396 | f.write_char(',' )?; |
| 397 | } |
| 398 | |
| 399 | Ok(()) |
| 400 | } |
| 401 | |
| 402 | impl<'m> TryFrom<&'m str> for MatchRule<'m> { |
| 403 | type Error = Error; |
| 404 | |
| 405 | fn try_from(s: &'m str) -> Result<Self> { |
| 406 | let components = s.split(',' ); |
| 407 | if components.clone().peekable().peek().is_none() { |
| 408 | return Err(Error::InvalidMatchRule); |
| 409 | } |
| 410 | let mut builder = MatchRule::builder(); |
| 411 | for component in components { |
| 412 | let (key, value) = component.split_once('=' ).ok_or(Error::InvalidMatchRule)?; |
| 413 | if key.is_empty() |
| 414 | || value.len() < 2 |
| 415 | || !value.starts_with(' \'' ) |
| 416 | || !value.ends_with(' \'' ) |
| 417 | { |
| 418 | return Err(Error::InvalidMatchRule); |
| 419 | } |
| 420 | let value = &value[1..value.len() - 1]; |
| 421 | builder = match key { |
| 422 | "type" => { |
| 423 | let msg_type = match value { |
| 424 | "error" => Type::Error, |
| 425 | "method_call" => Type::MethodCall, |
| 426 | "method_return" => Type::MethodReturn, |
| 427 | "signal" => Type::Signal, |
| 428 | _ => return Err(Error::InvalidMatchRule), |
| 429 | }; |
| 430 | builder.msg_type(msg_type) |
| 431 | } |
| 432 | "sender" => builder.sender(value)?, |
| 433 | "interface" => builder.interface(value)?, |
| 434 | "member" => builder.member(value)?, |
| 435 | "path" => builder.path(value)?, |
| 436 | "path_namespace" => builder.path_namespace(value)?, |
| 437 | "destination" => builder.destination(value)?, |
| 438 | "arg0namespace" => builder.arg0ns(value)?, |
| 439 | key if key.starts_with("arg" ) => { |
| 440 | if let Some(trailing_idx) = key.find("path" ) { |
| 441 | let idx = key[3..trailing_idx] |
| 442 | .parse::<u8>() |
| 443 | .map_err(|_| Error::InvalidMatchRule)?; |
| 444 | builder.arg_path(idx, value)? |
| 445 | } else { |
| 446 | let idx = key[3..] |
| 447 | .parse::<u8>() |
| 448 | .map_err(|_| Error::InvalidMatchRule)?; |
| 449 | builder.arg(idx, value)? |
| 450 | } |
| 451 | } |
| 452 | _ => return Err(Error::InvalidMatchRule), |
| 453 | }; |
| 454 | } |
| 455 | |
| 456 | Ok(builder.build()) |
| 457 | } |
| 458 | } |
| 459 | |
| 460 | impl<'de: 'm, 'm> Deserialize<'de> for MatchRule<'m> { |
| 461 | fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error> |
| 462 | where |
| 463 | D: serde::Deserializer<'de>, |
| 464 | { |
| 465 | let name: &str = <&str>::deserialize(deserializer)?; |
| 466 | |
| 467 | Self::try_from(name).map_err(|e: Error| de::Error::custom(msg:e.to_string())) |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | impl Serialize for MatchRule<'_> { |
| 472 | fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error> |
| 473 | where |
| 474 | S: serde::Serializer, |
| 475 | { |
| 476 | serializer.serialize_str(&self.to_string()) |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | /// The path or path namespace. |
| 481 | #[derive (Clone, Debug, PartialEq, Eq, Hash)] |
| 482 | pub enum PathSpec<'m> { |
| 483 | Path(ObjectPath<'m>), |
| 484 | PathNamespace(ObjectPath<'m>), |
| 485 | } |
| 486 | |
| 487 | assert_impl_all!(PathSpec<'_>: Send, Sync, Unpin); |
| 488 | |
| 489 | impl<'m> PathSpec<'m> { |
| 490 | /// Creates an owned clone of `self`. |
| 491 | fn to_owned(&self) -> PathSpec<'static> { |
| 492 | match self { |
| 493 | PathSpec::Path(path: &ObjectPath<'_>) => PathSpec::Path(path.to_owned()), |
| 494 | PathSpec::PathNamespace(ns: &ObjectPath<'_>) => PathSpec::PathNamespace(ns.to_owned()), |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | /// Creates an owned clone of `self`. |
| 499 | pub fn into_owned(self) -> PathSpec<'static> { |
| 500 | match self { |
| 501 | PathSpec::Path(path: ObjectPath<'_>) => PathSpec::Path(path.into_owned()), |
| 502 | PathSpec::PathNamespace(ns: ObjectPath<'_>) => PathSpec::PathNamespace(ns.into_owned()), |
| 503 | } |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | /// Owned sibling of [`MatchRule`]. |
| 508 | #[derive (Clone, Debug, Hash, PartialEq, Eq, Serialize, VariantType)] |
| 509 | pub struct OwnedMatchRule(#[serde(borrow)] MatchRule<'static>); |
| 510 | |
| 511 | assert_impl_all!(OwnedMatchRule: Send, Sync, Unpin); |
| 512 | |
| 513 | impl OwnedMatchRule { |
| 514 | /// Convert to the inner `MatchRule`, consuming `self`. |
| 515 | pub fn into_inner(self) -> MatchRule<'static> { |
| 516 | self.0 |
| 517 | } |
| 518 | |
| 519 | /// Get a reference to the inner `MatchRule`. |
| 520 | pub fn inner(&self) -> &MatchRule<'static> { |
| 521 | &self.0 |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | impl Deref for OwnedMatchRule { |
| 526 | type Target = MatchRule<'static>; |
| 527 | |
| 528 | fn deref(&self) -> &Self::Target { |
| 529 | &self.0 |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | impl From<OwnedMatchRule> for MatchRule<'_> { |
| 534 | fn from(o: OwnedMatchRule) -> Self { |
| 535 | o.into_inner() |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | impl<'unowned, 'owned: 'unowned> From<&'owned OwnedMatchRule> for MatchRule<'unowned> { |
| 540 | fn from(rule: &'owned OwnedMatchRule) -> Self { |
| 541 | rule.inner().clone() |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | impl From<MatchRule<'_>> for OwnedMatchRule { |
| 546 | fn from(rule: MatchRule<'_>) -> Self { |
| 547 | OwnedMatchRule(rule.into_owned()) |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | impl TryFrom<&'_ str> for OwnedMatchRule { |
| 552 | type Error = Error; |
| 553 | |
| 554 | fn try_from(value: &str) -> Result<Self> { |
| 555 | Ok(Self::from(MatchRule::try_from(value)?)) |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | impl<'de> Deserialize<'de> for OwnedMatchRule { |
| 560 | fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> |
| 561 | where |
| 562 | D: de::Deserializer<'de>, |
| 563 | { |
| 564 | String::deserialize(deserializer) |
| 565 | .and_then(|r| { |
| 566 | MatchRule::try_from(r.as_str()) |
| 567 | .map(|r| r.to_owned()) |
| 568 | .map_err(|e| de::Error::custom(e.to_string())) |
| 569 | }) |
| 570 | .map(Self) |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | impl PartialEq<MatchRule<'_>> for OwnedMatchRule { |
| 575 | fn eq(&self, other: &MatchRule<'_>) -> bool { |
| 576 | self.0 == *other |
| 577 | } |
| 578 | } |
| 579 | |