Skip to content

Commit

Permalink
Enable serde::expose_secret to deserialize Option<Secret<T>> (#24)
Browse files Browse the repository at this point in the history
* Experiment with more flexiable `expose_secret` that supports `Option`

* `SerializableSecret`

* Add test
  • Loading branch information
eopb authored Jul 24, 2023
1 parent bc15476 commit f8bd35f
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ std = []
fake = ["dep:fake", "dep:rand"]

[dependencies]
serde = { version = "1.0", optional = true, default-features = false }
fake = { version = "2.5", optional = true, default-features = false }
rand = { version = "0.8", optional = true, default-features = false }
serde = { version = "1.0", optional = true, default-features = false }

[dev-dependencies]
fake = { version = "2.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[package.metadata.docs.rs]
all-features = true
Expand Down
62 changes: 60 additions & 2 deletions src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,36 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Secret<T> {
}
}

/// A serializable type that contains a secret.
///
/// This abstraction enables [expose_secret] to be used to serialize both `Secret<T>` and
/// `Option<Secret<T>`.
pub trait SerializableSecret<T> {
type Exposed<'a>: Serialize
where
Self: 'a;
/// To reduce the number of functions that are able to expose secrets we require
/// that the [`Secret::expose_secret`] function is passed in here.
fn expose_via<'a>(&'a self, expose: impl Fn(&Secret<T>) -> &T) -> Self::Exposed<'a>;
}

impl<T: Serialize> SerializableSecret<T> for Secret<T> {
type Exposed<'a> = &'a T where T: 'a;

fn expose_via<'a>(&'a self, expose: impl Fn(&Secret<T>) -> &T) -> Self::Exposed<'a> {
expose(self)
}
}

impl<T: Serialize> SerializableSecret<T> for Option<Secret<T>> {
type Exposed<'a> = Option<&'a T> where T: 'a;

fn expose_via<'a>(&'a self, expose: impl Fn(&Secret<T>) -> &T) -> Self::Exposed<'a> {
self.as_ref().map(expose)
}
}

#[cfg(feature = "serde")]
/// Exposes a [Secret] for serialization.
///
/// For general-purpose secret exposing see [Secret::expose_secret].
Expand All @@ -19,8 +49,36 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Secret<T> {
/// *This API requires the following crate features to be activated: `serde`*
#[inline]
pub fn expose_secret<S: Serializer, T: Serialize>(
secret: &Secret<T>,
secret: &impl SerializableSecret<T>,
serializer: S,
) -> Result<S::Ok, S::Error> {
secret.expose_secret().serialize(serializer)
secret
.expose_via(Secret::expose_secret)
.serialize(serializer)
}

#[cfg(test)]
mod tests {
use super::*;
use fake::{Fake, Faker};
use serde::{Deserialize, Serialize};

#[test]
fn deserialize_the_serialized() {
#[derive(Serialize, Deserialize, fake::Dummy, PartialEq, Debug)]
struct Test {
#[serde(serialize_with = "expose_secret")]
one: Secret<String>,
#[serde(serialize_with = "expose_secret")]
two: Option<Secret<String>>,
}

let to_serialize: Test = Faker.fake();

let serialized = serde_json::to_string(&to_serialize).unwrap();

let deserialized = serde_json::from_str(&serialized).unwrap();

assert_eq!(to_serialize, deserialized)
}
}

0 comments on commit f8bd35f

Please sign in to comment.