| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // The fuchsia.identity.account interface uses a common pattern of wrapped structs to provide type |
| // safety for the various integer identifiers. The FIDL bindings for these types are currently not |
| // very ergonomic since all FIDL structs potentially can grow to contain handles so prohibit |
| // useful features like cloning. This module provides convenient and full featured alternatives for |
| // the wrapped structs that implement From/Into for each conversion to the FIDL type. |
| // |
| // Longer term we aspire to annotate this wrapper pattern in FIDL, which would allow the bindings |
| // to be more ergonomic and remove the need for this module. |
| |
| use serde::de::{Deserialize, Deserializer}; |
| use serde::Serialize; |
| use std::cmp::Ordering; |
| use std::fmt::{Debug, Formatter}; |
| use std::hash::{Hash, Hasher}; |
| |
| /// Implements `$name` as a new wrapper type. |
| /// |
| /// This wrapper type implements many standard copy and comparison traits and provides conversion |
| /// in both directions to a type with the same name in `$fidl_crate`. This type must by a struct |
| /// contain a single field of type `$type` named 'id'. |
| /// |
| /// `$type` must implement the following traits: `Debug`, `Clone`, `Hash`, `Eq`, and `Ord`. |
| /// |
| /// # Examples |
| /// |
| /// ``` |
| /// wrapper_type!(LocalAccountId, u64, fidl_fuchsia_identity_account); |
| /// ``` |
| // TODO(fxbug.dev/7807): Use FIDL type aliases when exposed in the Rust bindings. |
| macro_rules! wrapper_type { |
| ($name:ident, $type:ty) => { |
| /// A simpler wrapper around the less ergonomic autogenerated FIDL type. |
| pub struct $name { |
| inner: $type, |
| } |
| |
| impl $name { |
| /// Construct a new instance from the supplied id. |
| pub fn new(id: $type) -> Self { |
| $name { inner: id } |
| } |
| } |
| |
| impl Clone for $name { |
| fn clone(&self) -> $name { |
| $name { inner: self.inner.clone() } |
| } |
| } |
| |
| impl Hash for $name { |
| fn hash<H: Hasher>(&self, state: &mut H) { |
| self.inner.hash(state); |
| } |
| } |
| |
| impl Ord for $name { |
| fn cmp(&self, other: &$name) -> Ordering { |
| self.inner.cmp(&other.inner) |
| } |
| } |
| |
| impl PartialOrd for $name { |
| fn partial_cmp(&self, other: &$name) -> Option<Ordering> { |
| Some(self.cmp(other)) |
| } |
| } |
| |
| impl PartialEq for $name { |
| fn eq(&self, other: &$name) -> bool { |
| self.inner == other.inner |
| } |
| } |
| |
| impl Eq for $name {} |
| |
| impl Debug for $name { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{} {{ id: {:?} }}", stringify!($name), self.inner) |
| } |
| } |
| |
| impl From<$type> for $name { |
| fn from(fidl_type: $type) -> Self { |
| $name { inner: fidl_type } |
| } |
| } |
| |
| impl From<$name> for $type { |
| fn from(wrapper: $name) -> Self { |
| wrapper.inner |
| } |
| } |
| |
| impl AsRef<$type> for $name { |
| fn as_ref(&self) -> &$type { |
| &self.inner |
| } |
| } |
| |
| impl AsMut<$type> for $name { |
| fn as_mut(&mut self) -> &mut $type { |
| &mut self.inner |
| } |
| } |
| |
| impl Serialize for $name { |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: ::serde::Serializer, |
| { |
| Serialize::serialize(&self.inner, serializer) |
| } |
| } |
| |
| impl<'a> Deserialize<'a> for $name { |
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
| where |
| D: Deserializer<'a>, |
| { |
| let id = Deserialize::deserialize(deserializer)?; |
| Ok($name { inner: id }) |
| } |
| } |
| }; |
| } |
| |
| wrapper_type!(LocalAccountId, u64); |
| /// Convenience type for the autogenerated FIDL LocalAccountId. |
| pub type FidlLocalAccountId = u64; |
| |
| wrapper_type!(LocalPersonaId, u64); |
| /// Convenience type for the autogenerated FIDL LocalPersonaId. |
| pub type FidlLocalPersonaId = u64; |
| |
| wrapper_type!(GlobalAccountId, Vec<u8>); |
| /// Convenience type for the autogenerated FIDL GlobalAccountId. |
| pub type FidlGlobalAccountId = Vec<u8>; |
| |
| impl LocalAccountId { |
| /// A string representing the account in a canonical way, safe for file/directory names |
| pub fn to_canonical_string(&self) -> String { |
| self.inner.to_string() |
| } |
| |
| /// Inverse of `to_canonical_string`. Returns Some(id) if &id.to_canonical_string() == s. |
| pub fn from_canonical_str(s: &str) -> Option<Self> { |
| match s.parse::<u64>() { |
| Ok(id) => { |
| let ret = Self::new(id); |
| // Double check s is the true canonical representation |
| if s == &ret.to_canonical_string() { |
| Some(ret) |
| } else { |
| None |
| } |
| } |
| Err(_) => None, |
| } |
| } |
| } |
| |
| impl LocalPersonaId { |
| /// A string representing the persona in a canonical way, safe for file/directory names |
| pub fn to_canonical_string(&self) -> String { |
| self.inner.to_string() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| // Verify a few simple properties on one of our generated types to verify the macros are |
| // working. Since the generation of these types is automated we only test one. |
| |
| #[test] |
| fn test_clonable() { |
| let id: u64 = 456; |
| let value = LocalAccountId::new(id); |
| let cloned = value.clone(); |
| assert_eq!(id, value.inner); |
| assert_eq!(id, cloned.inner); |
| } |
| |
| #[test] |
| fn test_debug() { |
| let value = LocalAccountId::new(1111); |
| assert_eq!("LocalAccountId { id: 1111 }", format!("{:?}", value)); |
| } |
| |
| #[test] |
| fn test_eq() { |
| let val_1 = LocalAccountId::new(4231); |
| let val_2 = LocalAccountId::new(4231); |
| assert_eq!(val_1, val_2); |
| } |
| |
| #[test] |
| fn test_ne() { |
| let val_1 = LocalAccountId::new(554); |
| let val_2 = LocalAccountId::new(555); |
| assert_ne!(val_1, val_2); |
| } |
| |
| #[test] |
| fn test_ord() { |
| let val_1 = LocalAccountId::new(333); |
| let val_2 = LocalAccountId::new(444); |
| assert!(val_1 < val_2); |
| } |
| |
| #[test] |
| fn test_to_primitive() { |
| let value = LocalAccountId::new(333); |
| assert_eq!(u64::from(value), 333u64); |
| } |
| |
| #[test] |
| fn test_to_canonical_string() { |
| assert_eq!(&LocalAccountId::new(0).to_canonical_string(), "0"); |
| assert_eq!(&LocalAccountId::new(333).to_canonical_string(), "333"); |
| assert_eq!( |
| &LocalAccountId::new(18446744073709551615).to_canonical_string(), |
| "18446744073709551615" // max u64 |
| ); |
| } |
| |
| #[test] |
| fn test_from_canonical_str() { |
| assert_eq!(LocalAccountId::from_canonical_str("0"), Some(LocalAccountId::from(0))); |
| assert_eq!(LocalAccountId::from_canonical_str("333"), Some(LocalAccountId::from(333))); |
| assert_eq!( |
| LocalAccountId::from_canonical_str("18446744073709551615"), // max u64 |
| Some(LocalAccountId::from(18446744073709551615)) |
| ); |
| |
| assert_eq!(LocalAccountId::from_canonical_str("-1"), None); |
| assert_eq!(LocalAccountId::from_canonical_str("18446744073709551616"), None); // max u64 + 1 |
| assert_eq!(LocalAccountId::from_canonical_str("0333"), None); |
| assert_eq!(LocalAccountId::from_canonical_str(" 333"), None); |
| assert_eq!(LocalAccountId::from_canonical_str(" "), None); |
| assert_eq!(LocalAccountId::from_canonical_str(""), None); |
| assert_eq!(LocalAccountId::from_canonical_str("333 "), None); |
| } |
| } |