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