Skip to content

Commit 60c8986

Browse files
authored
Add @Date, @array and @Format directives (#20)
Add @Date, @array and @Format directives
2 parents 4fd5271 + ab28044 commit 60c8986

File tree

6 files changed

+977
-0
lines changed

6 files changed

+977
-0
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

aiscript-directive/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ readme.workspace = true
1414
[dependencies]
1515
aiscript-lexer = { path = "../aiscript-lexer", version = "0.1.0" }
1616
serde_json = "1.0"
17+
chrono = "0.4"
18+
regex = "1.11"
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
use serde_json::Value;
2+
use std::any::Any;
3+
use std::collections::HashSet;
4+
5+
use crate::{Directive, DirectiveParams, FromDirective};
6+
7+
use super::Validator;
8+
9+
pub struct ArrayValidator {
10+
pub min_len: Option<usize>,
11+
pub max_len: Option<usize>,
12+
pub unique: bool,
13+
}
14+
15+
impl Validator for ArrayValidator {
16+
fn name(&self) -> &'static str {
17+
"@array"
18+
}
19+
20+
fn validate(&self, value: &Value) -> Result<(), String> {
21+
let array = match value.as_array() {
22+
Some(a) => a,
23+
None => return Err("Value must be an array".into()),
24+
};
25+
26+
// Check minimum length
27+
if let Some(min_len) = self.min_len {
28+
if array.len() < min_len {
29+
return Err(format!(
30+
"Array length {} is less than the minimum length of {}",
31+
array.len(),
32+
min_len
33+
));
34+
}
35+
}
36+
37+
// Check maximum length
38+
if let Some(max_len) = self.max_len {
39+
if array.len() > max_len {
40+
return Err(format!(
41+
"Array length {} is greater than the maximum length of {}",
42+
array.len(),
43+
max_len
44+
));
45+
}
46+
}
47+
48+
// Check uniqueness if required
49+
if self.unique && array.len() > 1 {
50+
let mut seen = HashSet::new();
51+
for item in array {
52+
// Convert to string for comparison since not all Value types implement Hash
53+
let item_str = item.to_string();
54+
if !seen.insert(item_str) {
55+
return Err("Array contains duplicate values".into());
56+
}
57+
}
58+
}
59+
60+
Ok(())
61+
}
62+
63+
fn as_any(&self) -> &dyn Any {
64+
self
65+
}
66+
}
67+
68+
impl FromDirective for ArrayValidator {
69+
fn from_directive(directive: Directive) -> Result<Self, String> {
70+
match directive.params {
71+
DirectiveParams::KeyValue(params) => {
72+
let min_len = params
73+
.get("min_len")
74+
.and_then(|v| v.as_u64())
75+
.map(|v| v as usize);
76+
77+
let max_len = params
78+
.get("max_len")
79+
.and_then(|v| v.as_u64())
80+
.map(|v| v as usize);
81+
82+
let unique = params
83+
.get("unique")
84+
.and_then(|v| v.as_bool())
85+
.unwrap_or(false);
86+
87+
// Validate that min_len <= max_len if both are provided
88+
if let (Some(min), Some(max)) = (min_len, max_len) {
89+
if min > max {
90+
return Err(format!(
91+
"min_len ({}) cannot be greater than max_len ({})",
92+
min, max
93+
));
94+
}
95+
}
96+
97+
Ok(Self {
98+
min_len,
99+
max_len,
100+
unique,
101+
})
102+
}
103+
_ => Err("Invalid params for @array directive".into()),
104+
}
105+
}
106+
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
use crate::{Directive, DirectiveParams};
112+
use serde_json::json;
113+
use std::collections::HashMap;
114+
115+
fn create_directive(params: HashMap<String, Value>) -> Directive {
116+
Directive {
117+
name: "array".into(),
118+
params: DirectiveParams::KeyValue(params),
119+
line: 1,
120+
}
121+
}
122+
123+
#[test]
124+
fn test_array_validator_basic() {
125+
let params = HashMap::new();
126+
let directive = create_directive(params);
127+
let validator = ArrayValidator::from_directive(directive).unwrap();
128+
129+
assert!(validator.validate(&json!([])).is_ok());
130+
assert!(validator.validate(&json!([1, 2, 3])).is_ok());
131+
assert!(validator.validate(&json!("not-an-array")).is_err());
132+
}
133+
134+
#[test]
135+
fn test_array_validator_min_length() {
136+
let mut params = HashMap::new();
137+
params.insert("min_len".into(), json!(2));
138+
let directive = create_directive(params);
139+
let validator = ArrayValidator::from_directive(directive).unwrap();
140+
141+
assert!(validator.validate(&json!([1, 2])).is_ok());
142+
assert!(validator.validate(&json!([1, 2, 3])).is_ok());
143+
assert!(validator.validate(&json!([1])).is_err());
144+
assert!(validator.validate(&json!([])).is_err());
145+
}
146+
147+
#[test]
148+
fn test_array_validator_max_length() {
149+
let mut params = HashMap::new();
150+
params.insert("max_len".into(), json!(3));
151+
let directive = create_directive(params);
152+
let validator = ArrayValidator::from_directive(directive).unwrap();
153+
154+
assert!(validator.validate(&json!([])).is_ok());
155+
assert!(validator.validate(&json!([1, 2, 3])).is_ok());
156+
assert!(validator.validate(&json!([1, 2, 3, 4])).is_err());
157+
}
158+
159+
#[test]
160+
fn test_array_validator_min_max_length() {
161+
let mut params = HashMap::new();
162+
params.insert("min_len".into(), json!(2));
163+
params.insert("max_len".into(), json!(4));
164+
let directive = create_directive(params);
165+
let validator = ArrayValidator::from_directive(directive).unwrap();
166+
167+
assert!(validator.validate(&json!([1, 2])).is_ok());
168+
assert!(validator.validate(&json!([1, 2, 3, 4])).is_ok());
169+
assert!(validator.validate(&json!([1])).is_err());
170+
assert!(validator.validate(&json!([1, 2, 3, 4, 5])).is_err());
171+
}
172+
173+
#[test]
174+
fn test_array_validator_uniqueness() {
175+
let mut params = HashMap::new();
176+
params.insert("unique".into(), json!(true));
177+
let directive = create_directive(params);
178+
let validator = ArrayValidator::from_directive(directive).unwrap();
179+
180+
assert!(validator.validate(&json!([1, 2, 3])).is_ok());
181+
assert!(validator.validate(&json!(["a", "b", "c"])).is_ok());
182+
assert!(validator.validate(&json!([1, 2, 1])).is_err());
183+
assert!(validator.validate(&json!(["a", "b", "a"])).is_err());
184+
185+
// Different types should be considered unique
186+
assert!(validator.validate(&json!([1, "1", true])).is_ok());
187+
188+
// Empty array and single element array are always unique
189+
assert!(validator.validate(&json!([])).is_ok());
190+
assert!(validator.validate(&json!([1])).is_ok());
191+
}
192+
193+
#[test]
194+
fn test_array_validator_all_constraints() {
195+
let mut params = HashMap::new();
196+
params.insert("min_len".into(), json!(2));
197+
params.insert("max_len".into(), json!(4));
198+
params.insert("unique".into(), json!(true));
199+
let directive = create_directive(params);
200+
let validator = ArrayValidator::from_directive(directive).unwrap();
201+
202+
assert!(validator.validate(&json!([1, 2])).is_ok());
203+
assert!(validator.validate(&json!([1, 2, 3, 4])).is_ok());
204+
assert!(validator.validate(&json!([1])).is_err()); // Too short
205+
assert!(validator.validate(&json!([1, 2, 3, 4, 5])).is_err()); // Too long
206+
assert!(validator.validate(&json!([1, 2, 1])).is_err()); // Not unique
207+
}
208+
209+
#[test]
210+
fn test_invalid_directive_params() {
211+
// Test with invalid min_len > max_len
212+
let mut params = HashMap::new();
213+
params.insert("min_len".into(), json!(5));
214+
params.insert("max_len".into(), json!(2));
215+
let directive = create_directive(params);
216+
assert!(ArrayValidator::from_directive(directive).is_err());
217+
}
218+
219+
#[test]
220+
fn test_validator_name() {
221+
let params = HashMap::new();
222+
let directive = create_directive(params);
223+
let validator = ArrayValidator::from_directive(directive).unwrap();
224+
225+
assert_eq!(validator.name(), "@array");
226+
}
227+
228+
#[test]
229+
fn test_downcast_ref() {
230+
let params = HashMap::new();
231+
let directive = create_directive(params);
232+
let validator: Box<dyn Validator> =
233+
Box::new(ArrayValidator::from_directive(directive).unwrap());
234+
235+
let downcast = validator.downcast_ref::<ArrayValidator>();
236+
assert!(downcast.is_some());
237+
}
238+
}

0 commit comments

Comments
 (0)