use core_storage::Value;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
pub struct WriteScope {
#[serde(default)]
pub create_labels: Vec<String>,
#[serde(default)]
pub update_labels: Vec<String>,
#[serde(default)]
pub delete_labels: Vec<String>,
#[serde(default)]
pub create_edge_types: Vec<String>,
#[serde(default)]
pub delete_edge_types: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct PropPredicate {
pub field: String,
#[serde(
default,
deserialize_with = "de_value_opt",
skip_serializing_if = "Option::is_none"
)]
pub eq: Option<Value>,
#[serde(
default,
rename = "in",
deserialize_with = "de_value_vec_opt",
skip_serializing_if = "Option::is_none"
)]
pub in_: Option<Vec<Value>>,
}
fn value_from_json(j: serde_json::Value) -> std::result::Result<Value, String> {
if let Ok(v) = serde_json::from_value::<Value>(j.clone()) {
return Ok(v);
}
match j {
serde_json::Value::String(s) => Ok(Value::Str(s)),
serde_json::Value::Bool(b) => Ok(Value::Bool(b)),
serde_json::Value::Number(n) => n
.as_i64()
.map(Value::Int)
.or_else(|| n.as_f64().map(Value::Float))
.ok_or_else(|| format!("visible_where: {n} is not a representable number")),
other => Err(format!(
"visible_where: {other} is not a value — use a string, number or boolean, \
or the tagged form such as {{\"Str\": \"published\"}}"
)),
}
}
fn de_value_opt<'de, D>(d: D) -> std::result::Result<Option<Value>, D::Error>
where
D: serde::Deserializer<'de>,
{
match Option::<serde_json::Value>::deserialize(d)? {
None => Ok(None),
Some(j) => value_from_json(j)
.map(Some)
.map_err(serde::de::Error::custom),
}
}
fn de_value_vec_opt<'de, D>(d: D) -> std::result::Result<Option<Vec<Value>>, D::Error>
where
D: serde::Deserializer<'de>,
{
match Option::<Vec<serde_json::Value>>::deserialize(d)? {
None => Ok(None),
Some(items) => items
.into_iter()
.map(value_from_json)
.collect::<std::result::Result<Vec<_>, _>>()
.map(Some)
.map_err(serde::de::Error::custom),
}
}
impl PropPredicate {
pub fn holds(&self, value: Option<&Value>) -> bool {
let Some(value) = value else {
return false;
};
match (&self.eq, &self.in_) {
(Some(expected), None) => value == expected,
(None, Some(allowed)) => allowed.iter().any(|a| a == value),
_ => false,
}
}
pub fn validate_named(&self, what: &str) -> std::result::Result<(), String> {
if self.field.is_empty() {
return Err(format!("{what}.field must not be empty"));
}
match (&self.eq, &self.in_) {
(Some(_), None) | (None, Some(_)) => Ok(()),
(None, None) => Err(format!(
"{what} on field '{}' sets neither 'eq' nor 'in'",
self.field
)),
(Some(_), Some(_)) => Err(format!(
"{what} on field '{}' sets both 'eq' and 'in'; use one",
self.field
)),
}
}
pub fn validate(&self) -> std::result::Result<(), String> {
self.validate_named("visible_where")
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct RoleDef {
pub name: String,
#[serde(default)]
pub keys: Vec<String>,
#[serde(default)]
pub labels: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visible_where: Option<PropPredicate>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub namespaces: Option<Vec<String>>,
#[serde(default)]
pub write: Option<WriteScope>,
}
impl RoleDef {
pub fn sees_namespace(&self, namespace: &str) -> bool {
match &self.namespaces {
None => true,
Some(list) => list.iter().any(|n| n == namespace),
}
}
}
#[derive(Serialize, Deserialize)]
pub(crate) struct RolesFile {
pub version: u32,
pub roles: Vec<RoleDef>,
}
impl RolesFile {
pub(crate) fn new_versioned(roles: Vec<RoleDef>) -> Self {
let version = if roles.iter().any(|r| r.namespaces.is_some()) {
4
} else if roles.iter().any(|r| r.visible_where.is_some()) {
3
} else if roles.iter().any(|r| r.write.is_some()) {
2
} else {
1
};
RolesFile { version, roles }
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn v1(roles: Vec<RoleDef>) -> Self {
RolesFile { version: 1, roles }
}
}