Skip to content

Commit ca9c9cd

Browse files
client: Add missing attributes_as in wrapper keyring
1 parent 7b001a6 commit ca9c9cd

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

client/src/keyring.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,40 @@ impl Item {
375375
Ok(attributes)
376376
}
377377

378+
/// Retrieve the item attributes as a typed schema.
379+
///
380+
/// # Example
381+
///
382+
/// ```no_run
383+
/// # use oo7::{SecretSchema, Item};
384+
/// # #[derive(SecretSchema, Debug)]
385+
/// # #[schema(name = "org.example.Password")]
386+
/// # struct PasswordSchema {
387+
/// # username: String,
388+
/// # server: String,
389+
/// # }
390+
/// # async fn example(item: &Item) -> Result<(), oo7::Error> {
391+
/// let schema = item.attributes_as::<PasswordSchema>().await?;
392+
/// println!("Username: {}", schema.username);
393+
/// # Ok(())
394+
/// # }
395+
/// ```
396+
#[cfg(feature = "schema")]
397+
#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
398+
pub async fn attributes_as<T>(&self) -> Result<T>
399+
where
400+
T: for<'a> std::convert::TryFrom<&'a HashMap<String, String>, Error = crate::SchemaError>,
401+
{
402+
match self {
403+
Self::File(_, _) => T::try_from(&self.attributes().await?)
404+
.map_err(crate::file::Error::Schema)
405+
.map_err(Into::into),
406+
Self::DBus(_) => T::try_from(&self.attributes().await?)
407+
.map_err(crate::dbus::Error::Schema)
408+
.map_err(Into::into),
409+
}
410+
}
411+
378412
/// Sets the item attributes.
379413
pub async fn set_attributes(&self, attributes: &impl AsAttributes) -> Result<()> {
380414
match self {

client/tests/keyring.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(feature = "schema")]
2+
use oo7::{ContentType, SecretSchema};
13
use oo7::{Keyring, Secret, file};
24
use tempfile::tempdir;
35

@@ -612,3 +614,132 @@ async fn item_lock_with_locked_keyring_fails() {
612614
keyring.delete(&[("app", "test")]).await.unwrap();
613615
}
614616
}
617+
618+
#[tokio::test]
619+
#[cfg(all(feature = "tokio", feature = "schema"))]
620+
async fn attributes_as() {
621+
#[derive(SecretSchema, Debug, Default, PartialEq)]
622+
#[schema(name = "org.example.Test")]
623+
struct TestSchema {
624+
username: String,
625+
port: Option<u16>,
626+
}
627+
628+
let temp_dir = tempdir().unwrap();
629+
let (_setup, backends) = all_backends(&temp_dir).await;
630+
631+
for (idx, keyring) in backends.iter().enumerate() {
632+
println!("Testing attributes_as on backend {}", idx);
633+
634+
// Create an item with text content
635+
keyring
636+
.create_item(
637+
"Text Item",
638+
&TestSchema {
639+
username: "alice".to_string(),
640+
port: Some(8080),
641+
},
642+
Secret::text("my-password"),
643+
true,
644+
)
645+
.await
646+
.unwrap();
647+
648+
// Create an item with blob content
649+
keyring
650+
.create_item(
651+
"Blob Item",
652+
&TestSchema {
653+
username: "bob".to_string(),
654+
port: None,
655+
},
656+
Secret::blob(b"binary data"),
657+
true,
658+
)
659+
.await
660+
.unwrap();
661+
662+
// Search for the text item
663+
let text_items = keyring
664+
.search_items(&TestSchema {
665+
username: "alice".to_string(),
666+
..Default::default()
667+
})
668+
.await
669+
.unwrap();
670+
671+
assert_eq!(text_items.len(), 1);
672+
let text_item = &text_items[0];
673+
674+
// Verify content type
675+
let attrs = text_item.attributes().await.unwrap();
676+
assert_eq!(attrs.get("xdg:content-type").unwrap(), "text/plain");
677+
assert_eq!(
678+
text_item.secret().await.unwrap().content_type(),
679+
ContentType::Text
680+
);
681+
682+
// Test attributes_as
683+
let schema = text_item.attributes_as::<TestSchema>().await.unwrap();
684+
assert_eq!(
685+
schema,
686+
TestSchema {
687+
username: "alice".to_string(),
688+
port: Some(8080)
689+
}
690+
);
691+
assert_eq!(schema.username, "alice");
692+
assert_eq!(schema.port, Some(8080));
693+
694+
// Search for the blob item
695+
let blob_items = keyring
696+
.search_items(&TestSchema {
697+
username: "bob".to_string(),
698+
..Default::default()
699+
})
700+
.await
701+
.unwrap();
702+
703+
assert_eq!(blob_items.len(), 1);
704+
let blob_item = &blob_items[0];
705+
706+
// Verify content type
707+
let attrs = blob_item.attributes().await.unwrap();
708+
assert_eq!(
709+
attrs.get("xdg:content-type").unwrap(),
710+
"application/octet-stream"
711+
);
712+
assert_eq!(
713+
blob_item.secret().await.unwrap().content_type(),
714+
ContentType::Blob
715+
);
716+
717+
// Test attributes_as
718+
let schema = blob_item.attributes_as::<TestSchema>().await.unwrap();
719+
assert_eq!(
720+
schema,
721+
TestSchema {
722+
username: "bob".to_string(),
723+
port: None
724+
}
725+
);
726+
assert_eq!(schema.username, "bob");
727+
assert_eq!(schema.port, None);
728+
729+
// Cleanup
730+
keyring
731+
.delete(&TestSchema {
732+
username: "alice".to_string(),
733+
..Default::default()
734+
})
735+
.await
736+
.unwrap();
737+
keyring
738+
.delete(&TestSchema {
739+
username: "bob".to_string(),
740+
..Default::default()
741+
})
742+
.await
743+
.unwrap();
744+
}
745+
}

0 commit comments

Comments
 (0)