1use crate::Path;
2use serde::{Deserialize, Serialize};
3
4#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
5#[serde(rename_all = "snake_case")]
6pub(crate) enum ScanArea {
7 #[default]
8 Everything,
9 Only(Vec<Path<'static>>),
11}
12
13#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
26#[serde(deny_unknown_fields)]
29pub struct Scope {
30 #[serde(default)]
31 pub(crate) include: ScanArea,
32 #[serde(default)]
34 pub(crate) exclude: Vec<Path<'static>>,
35}
36
37impl Scope {
38 pub fn all() -> Self {
40 Self {
41 include: ScanArea::Everything,
42 exclude: vec![],
43 }
44 }
45
46 pub fn include_and_exclude(include: Vec<Path<'static>>, exclude: Vec<Path<'static>>) -> Self {
48 Self {
49 include: ScanArea::Only(include),
50 exclude,
51 }
52 }
53
54 pub fn include(include: Vec<Path<'static>>) -> Self {
56 Self {
57 include: ScanArea::Only(include),
58 exclude: vec![],
59 }
60 }
61
62 pub fn exclude(exclude: Vec<Path<'static>>) -> Self {
64 Self {
65 include: ScanArea::Everything,
66 exclude,
67 }
68 }
69}
70
71impl Default for Scope {
72 fn default() -> Self {
73 Self::all()
74 }
75}
76
77#[cfg(test)]
78mod test {
79 use super::*;
80 use serde_json::{Value, json};
81
82 fn field_path(name: &'static str) -> Path<'static> {
83 Path::from(vec![name.into()])
84 }
85
86 fn field_path_json(name: &str) -> Value {
87 json!({"segments": [{"type": "Field", "value": name}]})
88 }
89
90 fn assert_json_round_trip(scope: Scope, expected: Value) {
91 assert_eq!(serde_json::to_value(&scope).unwrap(), expected);
92 assert_eq!(serde_json::from_value::<Scope>(expected).unwrap(), scope);
93 }
94
95 #[test]
96 fn test_all_scope_json() {
97 assert_json_round_trip(
98 Scope::all(),
99 json!({"include": "everything", "exclude": []}),
100 );
101 }
102
103 #[test]
104 fn test_default_scope_is_all() {
105 assert_eq!(Scope::default(), Scope::all());
106 }
107
108 #[test]
109 fn test_include_scope_json() {
110 assert_json_round_trip(
111 Scope::include(vec![field_path("a")]),
112 json!({"include": {"only": [field_path_json("a")]}, "exclude": []}),
113 );
114 }
115
116 #[test]
117 fn test_include_nothing_scope_json_differs_from_all() {
118 assert_json_round_trip(
119 Scope::include(vec![]),
120 json!({"include": {"only": []}, "exclude": []}),
121 );
122 }
123
124 #[test]
125 fn test_exclude_scope_json() {
126 assert_json_round_trip(
127 Scope::exclude(vec![field_path("a")]),
128 json!({"include": "everything", "exclude": [field_path_json("a")]}),
129 );
130 }
131
132 #[test]
133 fn test_include_and_exclude_scope_json() {
134 assert_json_round_trip(
135 Scope::include_and_exclude(vec![field_path("a")], vec![field_path("b")]),
136 json!({"include": {"only": [field_path_json("a")]}, "exclude": [field_path_json("b")]}),
137 );
138 }
139
140 #[test]
141 fn test_missing_scope_fields_default_to_all() {
142 assert_eq!(
143 serde_json::from_value::<Scope>(json!({})).unwrap(),
144 Scope::all()
145 );
146 }
147
148 #[test]
151 fn test_old_scope_json_format_is_rejected() {
152 assert!(serde_json::from_value::<Scope>(json!({"type": "Exclude", "paths": []})).is_err());
153 assert!(
154 serde_json::from_value::<Scope>(
155 json!({"type": "Include", "paths": {"include": [], "exclude": []}})
156 )
157 .is_err()
158 );
159 }
160}