use std::collections::BTreeMap;
use chrono::{TimeZone, Utc};
use newton_chainio::{
confidential_data::ConfidentialDomain,
identity_data::{deserialize_identity_data, IdentityDomain},
};
use newton_core::rego::{AllowlistData, BlacklistData, PolicyDomainData, Value};
use crate::{decrypt::DecryptedEnvelope, error::EnclaveError};
pub(crate) fn merge_additional(
additional_data: Option<serde_json::Value>,
ephemeral_data: Option<serde_json::Value>,
) -> Option<serde_json::Value> {
let mut obj = match additional_data {
Some(serde_json::Value::Object(map)) => map,
_ => serde_json::Map::new(),
};
if let Some(ephemeral) = ephemeral_data {
obj.insert("privacy".to_string(), ephemeral);
}
(!obj.is_empty()).then_some(serde_json::Value::Object(obj))
}
pub(crate) fn identity_data(
values: &[DecryptedEnvelope],
timestamp: u64,
) -> Result<Vec<Box<dyn PolicyDomainData>>, EnclaveError> {
let date = Utc
.timestamp_opt(
timestamp
.try_into()
.map_err(|_| EnclaveError::InvalidRequest("timestamp".to_string()))?,
0,
)
.single()
.ok_or_else(|| EnclaveError::InvalidRequest("timestamp".to_string()))?
.format("%Y-%m-%d")
.to_string();
values
.iter()
.enumerate()
.map(|(index, item)| {
let json = item.value.to_string();
if let Some(domain) = item.domain {
match deserialize_identity_data(&domain, &json, date.clone()) {
Ok(data) => Ok(data),
Err(_) => {
let domain_name = IdentityDomain::from_bytes32(&domain)
.map(|d| d.name().to_string())
.unwrap_or_else(|| format!("0x{}", hex::encode(&domain[..8])));
Ok(Box::new(GenericDomainData {
domain: domain_name,
prefix: "identity".to_string(),
fields: item.value.clone(),
}) as Box<dyn PolicyDomainData>)
}
}
} else {
Ok(Box::new(GenericDomainData {
domain: format!("identity_domain_{index}"),
prefix: "identity".to_string(),
fields: item.value.clone(),
}) as Box<dyn PolicyDomainData>)
}
})
.collect()
}
pub(crate) fn confidential_data(values: &[DecryptedEnvelope]) -> Result<Vec<Box<dyn PolicyDomainData>>, EnclaveError> {
values
.iter()
.enumerate()
.map(|(index, item)| {
let rego_ns = item
.domain
.and_then(|domain| ConfidentialDomain::from_bytes32(&domain).map(|d| d.rego_namespace().to_string()))
.unwrap_or_else(|| format!("confidential_domain_{index}"));
let data: Box<dyn PolicyDomainData> = match rego_ns.as_str() {
"blacklist" => Box::new(BlacklistData {
addresses: string_array(&item.value, "addresses"),
}),
"allowlist" => Box::new(AllowlistData {
addresses: string_array(&item.value, "addresses"),
}),
_ => Box::new(GenericDomainData {
domain: rego_ns,
prefix: "confidential".to_string(),
fields: item.value.clone(),
}),
};
Ok(data)
})
.collect()
}
fn string_array(value: &serde_json::Value, key: &str) -> Vec<String> {
value
.get(key)
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default()
}
#[derive(Debug)]
struct GenericDomainData {
domain: String,
prefix: String,
fields: serde_json::Value,
}
impl PolicyDomainData for GenericDomainData {
fn domain_name(&self) -> &str {
&self.domain
}
fn rego_prefix(&self) -> &str {
&self.prefix
}
fn to_field_map(&self) -> BTreeMap<String, Value> {
let mut map = BTreeMap::new();
if let Some(obj) = self.fields.as_object() {
for (k, v) in obj {
map.insert(k.clone(), json_to_regorus_value(v));
}
}
map
}
}
fn json_to_regorus_value(v: &serde_json::Value) -> Value {
newton_core::rego::json_to_rego_value(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nested_json_objects_are_preserved() {
let value = serde_json::json!({
"profile": {
"country": "us",
"flags": ["kyc", "accredited"]
}
});
let rego = json_to_regorus_value(&value);
let json = newton_core::rego::value_to_json(®o);
assert_eq!(json, value);
}
}