1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum VisibilityOperator {
13 Exists,
14 NotExists,
15 Eq,
16 NotEq,
17 Gt,
18 Lt,
19 Gte,
20 Lte,
21 Contains,
22 NotEmpty,
23 Empty,
24}
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct VisibilityCondition {
29 pub path: String,
31 pub operator: VisibilityOperator,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub value: Option<serde_json::Value>,
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[serde(untagged)]
44pub enum Visibility {
45 And { and: Vec<Visibility> },
46 Or { or: Vec<Visibility> },
47 Not { not: Box<Visibility> },
48 Condition(VisibilityCondition),
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn simple_condition_round_trips() {
57 let json = r#"{"path": "/data/users", "operator": "not_empty"}"#;
58 let vis: Visibility = serde_json::from_str(json).unwrap();
59 match &vis {
60 Visibility::Condition(c) => {
61 assert_eq!(c.path, "/data/users");
62 assert_eq!(c.operator, VisibilityOperator::NotEmpty);
63 assert!(c.value.is_none());
64 }
65 _ => panic!("expected Condition variant"),
66 }
67 let serialized = serde_json::to_string(&vis).unwrap();
68 let reparsed: Visibility = serde_json::from_str(&serialized).unwrap();
69 assert_eq!(vis, reparsed);
70 }
71
72 #[test]
73 fn condition_with_value() {
74 let json = r#"{"path": "/auth/user/role", "operator": "eq", "value": "admin"}"#;
75 let vis: Visibility = serde_json::from_str(json).unwrap();
76 match &vis {
77 Visibility::Condition(c) => {
78 assert_eq!(c.operator, VisibilityOperator::Eq);
79 assert_eq!(
80 c.value,
81 Some(serde_json::Value::String("admin".to_string()))
82 );
83 }
84 _ => panic!("expected Condition variant"),
85 }
86 }
87
88 #[test]
89 fn compound_and_condition() {
90 let json = r#"{
91 "and": [
92 {"path": "/auth/user", "operator": "exists"},
93 {"path": "/auth/user/role", "operator": "eq", "value": "admin"}
94 ]
95 }"#;
96 let vis: Visibility = serde_json::from_str(json).unwrap();
97 match &vis {
98 Visibility::And { and } => {
99 assert_eq!(and.len(), 2);
100 }
101 _ => panic!("expected And variant"),
102 }
103 let serialized = serde_json::to_string(&vis).unwrap();
104 let reparsed: Visibility = serde_json::from_str(&serialized).unwrap();
105 assert_eq!(vis, reparsed);
106 }
107
108 #[test]
109 fn compound_or_condition() {
110 let json = r#"{
111 "or": [
112 {"path": "/data/status", "operator": "eq", "value": "active"},
113 {"path": "/data/status", "operator": "eq", "value": "pending"}
114 ]
115 }"#;
116 let vis: Visibility = serde_json::from_str(json).unwrap();
117 assert!(matches!(vis, Visibility::Or { .. }));
118 }
119
120 #[test]
121 fn nested_not_condition() {
122 let json = r#"{"not": {"path": "/data/deleted", "operator": "exists"}}"#;
123 let vis: Visibility = serde_json::from_str(json).unwrap();
124 match &vis {
125 Visibility::Not { not } => match not.as_ref() {
126 Visibility::Condition(c) => {
127 assert_eq!(c.path, "/data/deleted");
128 assert_eq!(c.operator, VisibilityOperator::Exists);
129 }
130 _ => panic!("expected Condition inside Not"),
131 },
132 _ => panic!("expected Not variant"),
133 }
134 }
135
136 #[test]
137 fn all_operators_serialize() {
138 let operators = vec![
139 (VisibilityOperator::Exists, "exists"),
140 (VisibilityOperator::NotExists, "not_exists"),
141 (VisibilityOperator::Eq, "eq"),
142 (VisibilityOperator::NotEq, "not_eq"),
143 (VisibilityOperator::Gt, "gt"),
144 (VisibilityOperator::Lt, "lt"),
145 (VisibilityOperator::Gte, "gte"),
146 (VisibilityOperator::Lte, "lte"),
147 (VisibilityOperator::Contains, "contains"),
148 (VisibilityOperator::NotEmpty, "not_empty"),
149 (VisibilityOperator::Empty, "empty"),
150 ];
151 for (op, expected) in operators {
152 let json = serde_json::to_value(&op).unwrap();
153 assert_eq!(
154 json, expected,
155 "operator {op:?} should serialize to {expected}"
156 );
157 }
158 }
159}