use serde_json::Value;
use std::collections::HashMap;
use super::data::PEPJSONValue;
#[cfg(feature = "long")]
use crate::data::long::LongPseudonym;
pub struct PEPJSONBuilder {
fields: HashMap<String, PEPJSONValue>,
}
impl PEPJSONBuilder {
pub fn new() -> Self {
Self {
fields: HashMap::new(),
}
}
pub fn from_json(json: &Value, pseudonyms: &[&str]) -> Option<Self> {
let obj = json.as_object()?;
let mut builder = Self::new();
for (key, value) in obj {
if pseudonyms.contains(&key.as_str()) {
let string_value = value.as_str()?;
builder = builder.pseudonym(key, string_value);
} else {
builder = builder.attribute(key, value.clone());
}
}
Some(builder)
}
pub fn attribute(mut self, key: &str, value: Value) -> Self {
let pep_value = PEPJSONValue::from_value(&value);
self.fields.insert(key.to_string(), pep_value);
self
}
pub fn pseudonym(mut self, key: &str, value: &str) -> Self {
use crate::data::padding::Padded;
use crate::data::simple::{ElGamalEncryptable, Pseudonym};
if let Some(pseudo) = Pseudonym::from_hex(value) {
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
return self;
}
if value.len() > 64 && value.len().is_multiple_of(64) {
let num_blocks = value.len() / 64;
let mut blocks = Vec::with_capacity(num_blocks);
let mut all_decoded = true;
for i in 0..num_blocks {
let start = i * 64;
let end = start + 64;
if let Some(block) = Pseudonym::from_hex(&value[start..end]) {
blocks.push(block);
} else {
all_decoded = false;
break;
}
}
if all_decoded {
self.fields.insert(
key.to_string(),
PEPJSONValue::LongPseudonym(LongPseudonym(blocks)),
);
return self;
}
}
if value.len() == 32 {
if let Some(pseudo) = Pseudonym::from_slice(value.as_bytes()) {
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
return self;
}
}
let bytes = value.as_bytes();
if bytes.len() > 32 && bytes.len().is_multiple_of(32) {
let num_blocks = bytes.len() / 32;
let mut blocks = Vec::with_capacity(num_blocks);
let mut all_decoded = true;
for i in 0..num_blocks {
let start = i * 32;
let end = start + 32;
if let Some(block) = Pseudonym::from_slice(&bytes[start..end]) {
blocks.push(block);
} else {
all_decoded = false;
break;
}
}
if all_decoded {
self.fields.insert(
key.to_string(),
PEPJSONValue::LongPseudonym(LongPseudonym(blocks)),
);
return self;
}
}
if bytes.len() <= 15 {
match Pseudonym::from_string_padded(value) {
Ok(pseudo) => {
self.fields
.insert(key.to_string(), PEPJSONValue::Pseudonym(pseudo));
}
Err(_) => {
let pseudo = LongPseudonym::from_string_padded(value);
self.fields
.insert(key.to_string(), PEPJSONValue::LongPseudonym(pseudo));
}
}
} else {
let pseudo = LongPseudonym::from_string_padded(value);
self.fields
.insert(key.to_string(), PEPJSONValue::LongPseudonym(pseudo));
}
self
}
pub fn build(self) -> PEPJSONValue {
PEPJSONValue::Object(self.fields)
}
}
impl Default for PEPJSONBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::client::{decrypt, encrypt};
use crate::factors::contexts::EncryptionContext;
use crate::factors::EncryptionSecret;
use crate::keys::{
make_attribute_global_keys, make_attribute_session_keys, make_pseudonym_global_keys,
make_pseudonym_session_keys, AttributeSessionKeys, PseudonymSessionKeys, SessionKeys,
};
use serde_json::json;
fn make_test_keys() -> SessionKeys {
let mut rng = rand::rng();
let (_, attr_global_secret) = make_attribute_global_keys(&mut rng);
let (_, pseudo_global_secret) = make_pseudonym_global_keys(&mut rng);
let enc_secret = EncryptionSecret::from("test-secret".as_bytes().to_vec());
let session = EncryptionContext::from("session-1");
let (attr_public, attr_secret) =
make_attribute_session_keys(&attr_global_secret, &session, &enc_secret);
let (pseudo_public, pseudo_secret) =
make_pseudonym_session_keys(&pseudo_global_secret, &session, &enc_secret);
SessionKeys {
attribute: AttributeSessionKeys {
public: attr_public,
secret: attr_secret,
},
pseudonym: PseudonymSessionKeys {
public: pseudo_public,
secret: pseudo_secret,
},
}
}
#[test]
fn builder_with_pseudonym_id() {
let mut rng = rand::rng();
let keys = make_test_keys();
let pep_value = PEPJSONBuilder::new()
.pseudonym("id", "user1@example.com")
.attribute("age", json!(16))
.attribute("verified", json!(true))
.attribute("scores", json!([88, 91, 85]))
.build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
let expected = json!({
"id": "user1@example.com",
"age": 16,
"verified": true,
"scores": [88, 91, 85]
});
assert_eq!(expected, decrypted.to_value().unwrap());
}
#[test]
fn builder_empty_object() {
let mut rng = rand::rng();
let keys = make_test_keys();
let pep_value = PEPJSONBuilder::new().build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
assert_eq!(json!({}), decrypted.to_value().unwrap());
}
#[test]
fn builder_only_attributes() {
let mut rng = rand::rng();
let keys = make_test_keys();
let pep_value = PEPJSONBuilder::new()
.attribute("name", json!("Alice"))
.attribute("age", json!(30))
.build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
let expected = json!({
"name": "Alice",
"age": 30
});
assert_eq!(expected, decrypted.to_value().unwrap());
}
#[test]
fn builder_only_pseudonyms() {
let mut rng = rand::rng();
let keys = make_test_keys();
let pep_value = PEPJSONBuilder::new()
.pseudonym("id1", "user1@example.com")
.pseudonym("id2", "user2@example.com")
.build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
let expected = json!({
"id1": "user1@example.com",
"id2": "user2@example.com"
});
assert_eq!(expected, decrypted.to_value().unwrap());
}
#[test]
fn from_json_with_pseudonyms() {
let mut rng = rand::rng();
let keys = make_test_keys();
let data = json!({
"id": "user@example.com",
"name": "Alice",
"age": 30,
"verified": true
});
let pep_value = PEPJSONBuilder::from_json(&data, &["id"]).unwrap().build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
assert_eq!(data, decrypted.to_value().unwrap());
}
#[test]
fn from_json_multiple_pseudonyms() {
let mut rng = rand::rng();
let keys = make_test_keys();
let data = json!({
"user_id": "user@example.com",
"email": "user@example.com",
"name": "Alice",
"age": 30
});
let pep_value = PEPJSONBuilder::from_json(&data, &["user_id", "email"])
.unwrap()
.build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
assert_eq!(data, decrypted.to_value().unwrap());
}
#[test]
fn from_json_no_pseudonyms() {
let mut rng = rand::rng();
let keys = make_test_keys();
let data = json!({
"name": "Alice",
"age": 30,
"scores": [88, 91, 85]
});
let pep_value = PEPJSONBuilder::from_json(&data, &[]).unwrap().build();
let encrypted = encrypt(&pep_value, &keys, &mut rng);
#[cfg(feature = "elgamal3")]
let decrypted = decrypt(&encrypted, &keys).unwrap();
#[cfg(not(feature = "elgamal3"))]
let decrypted = decrypt(&encrypted, &keys);
assert_eq!(data, decrypted.to_value().unwrap());
}
#[test]
fn from_json_empty_object() {
let data = json!({});
let pep_value = PEPJSONBuilder::from_json(&data, &[]).unwrap().build();
assert!(matches!(pep_value, PEPJSONValue::Object(_)));
}
#[test]
fn from_json_non_object_returns_none() {
let data = json!([1, 2, 3]);
assert!(PEPJSONBuilder::from_json(&data, &[]).is_none());
let data = json!("string");
assert!(PEPJSONBuilder::from_json(&data, &[]).is_none());
let data = json!(42);
assert!(PEPJSONBuilder::from_json(&data, &[]).is_none());
}
#[test]
fn from_json_pseudonym_not_string_returns_none() {
let data = json!({
"id": 123,
"name": "Alice"
});
assert!(PEPJSONBuilder::from_json(&data, &["id"]).is_none());
}
}