From 169abde281c3e5ee9a459d80bd5408fc163099ba Mon Sep 17 00:00:00 2001 From: Kendall Weihe Date: Wed, 24 Apr 2024 16:48:35 -0400 Subject: [PATCH] Add 'Any' type for JSON serializable types --- bindings/uniffi/Cargo.toml | 1 + bindings/uniffi/src/lib.rs | 1 + bindings/uniffi/src/web5.udl | 10 +++ crates/any/Cargo.toml | 12 ++++ crates/any/src/lib.rs | 127 +++++++++++++++++++++++++++++++++++ web5-rs.code-workspace | 3 + 6 files changed, 154 insertions(+) create mode 100644 crates/any/Cargo.toml create mode 100644 crates/any/src/lib.rs diff --git a/bindings/uniffi/Cargo.toml b/bindings/uniffi/Cargo.toml index d6dc684d..2a7c8764 100644 --- a/bindings/uniffi/Cargo.toml +++ b/bindings/uniffi/Cargo.toml @@ -7,6 +7,7 @@ repository.workspace = true license-file.workspace = true [dependencies] +any = { path = "../../crates/any" } credentials = { path = "../../crates/credentials" } crypto = { path = "../../crates/crypto" } dids = { path = "../../crates/dids" } diff --git a/bindings/uniffi/src/lib.rs b/bindings/uniffi/src/lib.rs index 0069364a..e10ad1c1 100644 --- a/bindings/uniffi/src/lib.rs +++ b/bindings/uniffi/src/lib.rs @@ -1,3 +1,4 @@ +use ::any::{Any, AnyError}; use ::credentials::vc::{verify_vcjwt, CredentialError, CredentialSubject, VerifiableCredential}; use ::crypto::Curve; use ::dids::{ diff --git a/bindings/uniffi/src/web5.udl b/bindings/uniffi/src/web5.udl index 3bba1128..8ce43e22 100644 --- a/bindings/uniffi/src/web5.udl +++ b/bindings/uniffi/src/web5.udl @@ -11,6 +11,16 @@ namespace web5 { VerifiableCredential verify_vcjwt([ByRef] string jwt); }; +[Error] +enum AnyError { + "SerdeError", +}; + +interface Any { + [Name=from_json_string, Throws=AnyError] + constructor(string json_string); +}; + [Error] enum KeyError { "KeyGenerationFailed", diff --git a/crates/any/Cargo.toml b/crates/any/Cargo.toml new file mode 100644 index 00000000..1341ebbe --- /dev/null +++ b/crates/any/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "any" +version = "0.1.0" +edition = "2021" +homepage.workspace = true +repository.workspace = true +license-file.workspace = true + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } diff --git a/crates/any/src/lib.rs b/crates/any/src/lib.rs new file mode 100644 index 00000000..c3b83a7e --- /dev/null +++ b/crates/any/src/lib.rs @@ -0,0 +1,127 @@ +use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; +use serde_json::Value; + +#[derive(thiserror::Error, Debug)] +pub enum AnyError { + #[error(transparent)] + SerdeError(#[from] serde_json::Error), +} + +#[derive(Debug, Default, Clone)] +pub struct Any { + pub value: Value, +} + +impl Serialize for Any { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.value.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Any { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Value::deserialize(deserializer).map(|value| Any { value }) + } +} + +impl Any { + pub fn from_json_string(json_string: String) -> Result { + let value: Value = serde_json::from_str(&json_string)?; + Ok(Self { value }) + } + + pub fn from_generic(any: T) -> Result { + let json_str = serde_json::to_string(&any)?; + let value = serde_json::from_str(&json_str)?; + Ok(Self { value }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_serialize_simple_values() { + let any = Any { + value: json!("hello"), + }; + let serialized = serde_json::to_string(&any).unwrap(); + assert_eq!(serialized, "\"hello\""); + } + + #[test] + fn test_serialize_complex_object() { + let any = Any { + value: json!({"key": "value", "number": 10}), + }; + let serialized = serde_json::to_string(&any).unwrap(); + assert_eq!(serialized, "{\"key\":\"value\",\"number\":10}"); + } + + #[test] + fn test_deserialize_simple_values() { + let json_str = "\"hello\""; + let any: Any = serde_json::from_str(json_str).unwrap(); + assert_eq!(any.value, json!("hello")); + } + + #[test] + fn test_deserialize_complex_object() { + let json_str = "{\"key\":\"value\",\"number\":10}"; + let any: Any = serde_json::from_str(json_str).unwrap(); + assert_eq!(any.value, json!({"key": "value", "number": 10})); + } + + #[test] + fn test_from_json_string_error_handling() { + let json_str = "not a valid json"; + let result = Any::from_json_string(json_str.to_string()); + assert!(result.is_err()); + } + + #[test] + fn test_from_generic_conversion() { + let data = vec![1, 2, 3]; + let any = Any::from_generic(data).unwrap(); + assert_eq!(any.value, json!([1, 2, 3])); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestStruct { + id: u32, + name: String, + } + + #[test] + fn test_serialize_custom_struct() { + let test_struct = TestStruct { + id: 123, + name: "Alice".to_string(), + }; + let any = Any::from_generic(test_struct).unwrap(); + let serialized = serde_json::to_string(&any).unwrap(); + assert_eq!(serialized, "{\"id\":123,\"name\":\"Alice\"}"); + } + + #[test] + fn test_deserialize_custom_struct() { + let json_str = "{\"id\":123,\"name\":\"Alice\"}"; + let any: Any = serde_json::from_str(json_str).unwrap(); + let deserialized: TestStruct = serde_json::from_value(any.value).unwrap(); + assert_eq!( + deserialized, + TestStruct { + id: 123, + name: "Alice".to_string() + } + ); + } +} diff --git a/web5-rs.code-workspace b/web5-rs.code-workspace index 89d89bee..4080ff62 100644 --- a/web5-rs.code-workspace +++ b/web5-rs.code-workspace @@ -13,6 +13,9 @@ { "path": "examples" }, + { + "path": "crates/any" + }, { "path": "crates/credentials" },