1use std::{collections::HashMap, sync::Arc};
13
14use cel::{
15 Value,
16 objects::{Key, Map},
17};
18
19use crate::validation::{compilation::CompiledSchema, escaping::escape_field_name};
20
21#[derive(Clone, Debug, Default, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum SchemaFormat {
25 DateTime,
27 Duration,
29 IntOrString,
32 #[default]
34 None,
35}
36
37impl SchemaFormat {
38 pub(crate) fn from_schema(schema: &serde_json::Value) -> Self {
40 if schema.get("x-kubernetes-int-or-string").and_then(|v| v.as_bool()) == Some(true) {
41 return SchemaFormat::IntOrString;
42 }
43 match schema.get("format").and_then(|f| f.as_str()) {
44 Some("date-time") => SchemaFormat::DateTime,
45 Some("duration") => SchemaFormat::Duration,
46 _ => SchemaFormat::None,
47 }
48 }
49}
50
51#[must_use]
63pub(crate) fn json_to_cel(value: &serde_json::Value) -> Value {
64 match value {
65 serde_json::Value::Null => Value::Null,
66 serde_json::Value::Bool(b) => Value::Bool(*b),
67 serde_json::Value::Number(n) => convert_number(n),
68 serde_json::Value::String(s) => Value::String(Arc::new(s.clone())),
69 serde_json::Value::Array(arr) => {
70 let items: Vec<Value> = arr.iter().map(json_to_cel).collect();
71 Value::List(Arc::new(items))
72 }
73 serde_json::Value::Object(obj) => {
74 let mut map = HashMap::with_capacity(obj.len());
75 for (k, v) in obj {
76 map.insert(Key::String(Arc::new(escape_field_name(k))), json_to_cel(v));
77 }
78 Value::Map(Map { map: Arc::new(map) })
79 }
80 }
81}
82
83#[inline]
84fn convert_number(n: &serde_json::Number) -> Value {
85 if let Some(i) = n.as_i64() {
86 Value::Int(i)
87 } else if let Some(u) = n.as_u64() {
88 Value::UInt(u)
89 } else {
90 Value::Float(n.as_f64().unwrap_or(f64::NAN))
91 }
92}
93
94#[must_use]
102pub(crate) fn json_to_cel_with_schema(value: &serde_json::Value, schema: &serde_json::Value) -> Value {
103 let format = SchemaFormat::from_schema(schema);
104 match value {
105 serde_json::Value::Null => Value::Null,
106 serde_json::Value::Bool(b) => Value::Bool(*b),
107 serde_json::Value::Number(n) => convert_number(n),
108 serde_json::Value::String(s) => convert_string_with_format(s, &format),
109 serde_json::Value::Array(arr) => {
110 let items_schema = schema.get("items");
111 let items: Vec<Value> = arr
112 .iter()
113 .map(|item| match items_schema {
114 Some(s) => json_to_cel_with_schema(item, s),
115 None => json_to_cel(item),
116 })
117 .collect();
118 Value::List(Arc::new(items))
119 }
120 serde_json::Value::Object(obj) => {
121 let props = schema.get("properties").and_then(|p| p.as_object());
122 let additional = schema.get("additionalProperties").filter(|a| a.is_object());
123
124 let mut map = HashMap::with_capacity(obj.len());
125 for (k, v) in obj {
126 let child_val = if let Some(prop_schema) = props.and_then(|p| p.get(k)) {
127 json_to_cel_with_schema(v, prop_schema)
128 } else if let Some(additional_schema) = additional {
129 json_to_cel_with_schema(v, additional_schema)
130 } else {
131 json_to_cel(v)
132 };
133 map.insert(Key::String(Arc::new(escape_field_name(k))), child_val);
134 }
135
136 let is_embedded = schema
137 .get("x-kubernetes-embedded-resource")
138 .and_then(|v| v.as_bool())
139 == Some(true);
140 if is_embedded {
141 map.entry(Key::String(Arc::new("apiVersion".into())))
142 .or_insert_with(|| Value::String(Arc::new(String::new())));
143 map.entry(Key::String(Arc::new("kind".into())))
144 .or_insert_with(|| Value::String(Arc::new(String::new())));
145 map.entry(Key::String(Arc::new("metadata".into())))
146 .or_insert_with(|| {
147 Value::Map(Map {
148 map: Arc::new(HashMap::new()),
149 })
150 });
151 }
152
153 Value::Map(Map { map: Arc::new(map) })
154 }
155 }
156}
157
158#[must_use]
163pub(crate) fn json_to_cel_with_compiled(value: &serde_json::Value, compiled: &CompiledSchema) -> Value {
164 match value {
165 serde_json::Value::Null => Value::Null,
166 serde_json::Value::Bool(b) => Value::Bool(*b),
167 serde_json::Value::Number(n) => convert_number(n),
168 serde_json::Value::String(s) => convert_string_with_format(s, &compiled.format),
169 serde_json::Value::Array(arr) => {
170 let items: Vec<Value> = arr
171 .iter()
172 .map(|item| match &compiled.items {
173 Some(items_compiled) => json_to_cel_with_compiled(item, items_compiled),
174 None => json_to_cel(item),
175 })
176 .collect();
177 Value::List(Arc::new(items))
178 }
179 serde_json::Value::Object(obj) => {
180 let mut map = HashMap::with_capacity(obj.len());
181 for (k, v) in obj {
182 let child_val = if let Some(prop_compiled) = compiled.properties.get(k) {
183 json_to_cel_with_compiled(v, prop_compiled)
184 } else if let Some(ref additional) = compiled.additional_properties {
185 json_to_cel_with_compiled(v, additional)
186 } else {
187 json_to_cel(v)
188 };
189 map.insert(Key::String(Arc::new(escape_field_name(k))), child_val);
190 }
191
192 if compiled.embedded_resource {
193 map.entry(Key::String(Arc::new("apiVersion".into())))
194 .or_insert_with(|| Value::String(Arc::new(String::new())));
195 map.entry(Key::String(Arc::new("kind".into())))
196 .or_insert_with(|| Value::String(Arc::new(String::new())));
197 map.entry(Key::String(Arc::new("metadata".into())))
198 .or_insert_with(|| {
199 Value::Map(Map {
200 map: Arc::new(HashMap::new()),
201 })
202 });
203 }
204
205 Value::Map(Map { map: Arc::new(map) })
206 }
207 }
208}
209
210#[inline]
212fn convert_string_with_format(s: &str, format: &SchemaFormat) -> Value {
213 match format {
214 SchemaFormat::DateTime => {
215 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
216 return Value::Timestamp(dt);
217 }
218 Value::String(Arc::new(s.to_string()))
219 }
220 SchemaFormat::Duration => {
221 if let Some(d) = parse_go_duration(s) {
222 return Value::Duration(d);
223 }
224 Value::String(Arc::new(s.to_string()))
225 }
226 SchemaFormat::IntOrString => Value::String(Arc::new(s.to_string())),
227 SchemaFormat::None => Value::String(Arc::new(s.to_string())),
228 }
229}
230
231pub(crate) fn parse_go_duration(input: &str) -> Option<chrono::Duration> {
240 let (input, negative) = if let Some(rest) = input.strip_prefix('-') {
241 (rest, true)
242 } else {
243 (input, false)
244 };
245
246 if input == "0" {
247 return Some(chrono::Duration::zero());
248 }
249
250 let mut remaining = input;
251 let mut total_nanos: i64 = 0;
252 let mut parsed_any = false;
253
254 while !remaining.is_empty() {
255 let num_end = remaining
257 .find(|c: char| !c.is_ascii_digit() && c != '.')
258 .unwrap_or(0);
259 if num_end == 0 {
260 return None; }
262 let num_str = &remaining[..num_end];
263 let num: f64 = num_str.parse().ok()?;
264 remaining = &remaining[num_end..];
265
266 let (unit_nanos, unit_len) = if remaining.starts_with("ms") {
268 (1_000_000i64, 2)
269 } else if remaining.starts_with("us") {
270 (1_000i64, 2)
271 } else if remaining.starts_with("ns") {
272 (1i64, 2)
273 } else if remaining.starts_with('h') {
274 (3_600_000_000_000i64, 1)
275 } else if remaining.starts_with('m') {
276 (60_000_000_000i64, 1)
277 } else if remaining.starts_with('s') {
278 (1_000_000_000i64, 1)
279 } else {
280 return None; };
282
283 remaining = &remaining[unit_len..];
284 total_nanos += (num * unit_nanos as f64).trunc() as i64;
285 parsed_any = true;
286 }
287
288 if !parsed_any {
289 return None;
290 }
291
292 if negative {
293 total_nanos = -total_nanos;
294 }
295 Some(chrono::Duration::nanoseconds(total_nanos))
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use serde_json::json;
302
303 #[test]
304 fn test_null() {
305 assert_eq!(json_to_cel(&json!(null)), Value::Null);
306 }
307
308 #[test]
309 fn test_bool() {
310 assert_eq!(json_to_cel(&json!(true)), Value::Bool(true));
311 assert_eq!(json_to_cel(&json!(false)), Value::Bool(false));
312 }
313
314 #[test]
315 fn test_i64() {
316 assert_eq!(json_to_cel(&json!(42)), Value::Int(42));
317 assert_eq!(json_to_cel(&json!(-1)), Value::Int(-1));
318 assert_eq!(json_to_cel(&json!(0)), Value::Int(0));
319 }
320
321 #[test]
322 fn test_u64_beyond_i64() {
323 let big: u64 = (i64::MAX as u64) + 1;
324 let v = json_to_cel(&serde_json::Value::Number(serde_json::Number::from(big)));
325 assert_eq!(v, Value::UInt(big));
326 }
327
328 #[test]
329 fn test_float() {
330 assert_eq!(json_to_cel(&json!(2.5)), Value::Float(2.5));
331 assert_eq!(json_to_cel(&json!(0.0)), Value::Float(0.0));
332 }
333
334 #[test]
335 fn test_string() {
336 assert_eq!(
337 json_to_cel(&json!("hello")),
338 Value::String(Arc::new("hello".into()))
339 );
340 }
341
342 #[test]
343 fn test_empty_string() {
344 assert_eq!(json_to_cel(&json!("")), Value::String(Arc::new(String::new())));
345 }
346
347 #[test]
348 fn test_array_mixed() {
349 let v = json_to_cel(&json!([1, "two", true, null]));
350 let expected = Value::List(Arc::new(vec![
351 Value::Int(1),
352 Value::String(Arc::new("two".into())),
353 Value::Bool(true),
354 Value::Null,
355 ]));
356 assert_eq!(v, expected);
357 }
358
359 #[test]
360 fn test_empty_array() {
361 assert_eq!(json_to_cel(&json!([])), Value::List(Arc::new(vec![])));
362 }
363
364 #[test]
365 fn test_object() {
366 let v = json_to_cel(&json!({"name": "test", "count": 5}));
367 if let Value::Map(map) = v {
368 assert_eq!(
369 map.map.get(&Key::String(Arc::new("name".into()))),
370 Some(&Value::String(Arc::new("test".into())))
371 );
372 assert_eq!(
373 map.map.get(&Key::String(Arc::new("count".into()))),
374 Some(&Value::Int(5))
375 );
376 } else {
377 panic!("expected Map");
378 }
379 }
380
381 #[test]
382 fn test_empty_object() {
383 let v = json_to_cel(&json!({}));
384 if let Value::Map(map) = v {
385 assert!(map.map.is_empty());
386 } else {
387 panic!("expected Map");
388 }
389 }
390
391 #[test]
392 fn test_nested_structure() {
393 let v = json_to_cel(&json!({
394 "spec": {
395 "replicas": 3,
396 "items": [1, 2, 3]
397 }
398 }));
399 if let Value::Map(outer) = v {
400 let spec = outer.map.get(&Key::String(Arc::new("spec".into()))).unwrap();
401 if let Value::Map(inner) = spec {
402 assert_eq!(
403 inner.map.get(&Key::String(Arc::new("replicas".into()))),
404 Some(&Value::Int(3))
405 );
406 assert_eq!(
407 inner.map.get(&Key::String(Arc::new("items".into()))),
408 Some(&Value::List(Arc::new(vec![
409 Value::Int(1),
410 Value::Int(2),
411 Value::Int(3),
412 ])))
413 );
414 } else {
415 panic!("expected inner Map");
416 }
417 } else {
418 panic!("expected outer Map");
419 }
420 }
421
422 #[test]
423 fn test_number_priority() {
424 assert_eq!(json_to_cel(&json!(42)), Value::Int(42));
426 let big: u64 = (i64::MAX as u64) + 1;
428 assert_eq!(
429 json_to_cel(&serde_json::Value::Number(serde_json::Number::from(big))),
430 Value::UInt(big)
431 );
432 assert_eq!(json_to_cel(&json!(1.5)), Value::Float(1.5));
434 }
435
436 #[test]
439 fn parse_duration_hours() {
440 assert_eq!(parse_go_duration("1h"), Some(chrono::Duration::hours(1)));
441 }
442
443 #[test]
444 fn parse_duration_minutes() {
445 assert_eq!(parse_go_duration("30m"), Some(chrono::Duration::minutes(30)));
446 }
447
448 #[test]
449 fn parse_duration_seconds() {
450 assert_eq!(parse_go_duration("45s"), Some(chrono::Duration::seconds(45)));
451 }
452
453 #[test]
454 fn parse_duration_milliseconds() {
455 assert_eq!(
456 parse_go_duration("500ms"),
457 Some(chrono::Duration::milliseconds(500))
458 );
459 }
460
461 #[test]
462 fn parse_duration_microseconds() {
463 assert_eq!(
464 parse_go_duration("100us"),
465 Some(chrono::Duration::microseconds(100))
466 );
467 }
468
469 #[test]
470 fn parse_duration_nanoseconds() {
471 assert_eq!(
472 parse_go_duration("999ns"),
473 Some(chrono::Duration::nanoseconds(999))
474 );
475 }
476
477 #[test]
478 fn parse_duration_compound() {
479 assert_eq!(
480 parse_go_duration("1h30m"),
481 Some(chrono::Duration::hours(1) + chrono::Duration::minutes(30))
482 );
483 assert_eq!(
484 parse_go_duration("1h30m10s"),
485 Some(chrono::Duration::hours(1) + chrono::Duration::minutes(30) + chrono::Duration::seconds(10))
486 );
487 }
488
489 #[test]
490 fn parse_duration_negative() {
491 assert_eq!(parse_go_duration("-1h"), Some(chrono::Duration::hours(-1)));
492 assert_eq!(parse_go_duration("-30s"), Some(chrono::Duration::seconds(-30)));
493 }
494
495 #[test]
496 fn parse_duration_zero() {
497 assert_eq!(parse_go_duration("0"), Some(chrono::Duration::zero()));
498 }
499
500 #[test]
501 fn parse_duration_invalid() {
502 assert_eq!(parse_go_duration(""), None);
503 assert_eq!(parse_go_duration("abc"), None);
504 assert_eq!(parse_go_duration("1x"), None);
505 assert_eq!(parse_go_duration("h"), None);
506 }
507
508 #[test]
511 fn timestamp_parsed_from_schema() {
512 let schema = json!({
513 "type": "string",
514 "format": "date-time"
515 });
516 let value = json!("2024-01-01T00:00:00Z");
517 let result = json_to_cel_with_schema(&value, &schema);
518 assert!(matches!(result, Value::Timestamp(_)));
519 }
520
521 #[test]
522 fn timestamp_parse_failure_falls_back_to_string() {
523 let schema = json!({
524 "type": "string",
525 "format": "date-time"
526 });
527 let value = json!("not-a-date");
528 let result = json_to_cel_with_schema(&value, &schema);
529 assert_eq!(result, Value::String(Arc::new("not-a-date".into())));
530 }
531
532 #[test]
533 fn duration_parsed_from_schema() {
534 let schema = json!({
535 "type": "string",
536 "format": "duration"
537 });
538 let value = json!("1h30m");
539 let result = json_to_cel_with_schema(&value, &schema);
540 assert!(matches!(result, Value::Duration(_)));
541 }
542
543 #[test]
544 fn duration_parse_failure_falls_back_to_string() {
545 let schema = json!({
546 "type": "string",
547 "format": "duration"
548 });
549 let value = json!("not-a-duration");
550 let result = json_to_cel_with_schema(&value, &schema);
551 assert_eq!(result, Value::String(Arc::new("not-a-duration".into())));
552 }
553
554 #[test]
555 fn nested_object_properties_format() {
556 let schema = json!({
557 "type": "object",
558 "properties": {
559 "createdAt": {
560 "type": "string",
561 "format": "date-time"
562 },
563 "name": {
564 "type": "string"
565 },
566 "timeout": {
567 "type": "string",
568 "format": "duration"
569 }
570 }
571 });
572 let value = json!({
573 "createdAt": "2024-06-15T10:30:00Z",
574 "name": "test",
575 "timeout": "30s"
576 });
577 let result = json_to_cel_with_schema(&value, &schema);
578 if let Value::Map(map) = result {
579 assert!(matches!(
580 map.map.get(&Key::String(Arc::new("createdAt".into()))),
581 Some(Value::Timestamp(_))
582 ));
583 assert!(matches!(
584 map.map.get(&Key::String(Arc::new("name".into()))),
585 Some(Value::String(_))
586 ));
587 assert!(matches!(
588 map.map.get(&Key::String(Arc::new("timeout".into()))),
589 Some(Value::Duration(_))
590 ));
591 } else {
592 panic!("expected Map");
593 }
594 }
595
596 #[test]
597 fn array_items_format() {
598 let schema = json!({
599 "type": "array",
600 "items": {
601 "type": "string",
602 "format": "date-time"
603 }
604 });
605 let value = json!(["2024-01-01T00:00:00Z", "2024-06-15T12:00:00+09:00"]);
606 let result = json_to_cel_with_schema(&value, &schema);
607 if let Value::List(items) = result {
608 assert!(matches!(items[0], Value::Timestamp(_)));
609 assert!(matches!(items[1], Value::Timestamp(_)));
610 } else {
611 panic!("expected List");
612 }
613 }
614
615 #[test]
616 fn no_format_leaves_string_unchanged() {
617 let schema = json!({
618 "type": "string"
619 });
620 let value = json!("2024-01-01T00:00:00Z");
621 let result = json_to_cel_with_schema(&value, &schema);
622 assert_eq!(result, Value::String(Arc::new("2024-01-01T00:00:00Z".into())));
623 }
624
625 #[test]
626 fn json_to_cel_unchanged_with_no_schema() {
627 let value = json!("2024-01-01T00:00:00Z");
629 let result = json_to_cel(&value);
630 assert_eq!(result, Value::String(Arc::new("2024-01-01T00:00:00Z".into())));
631 }
632
633 #[test]
634 fn int_or_string_schema_format_detected() {
635 let schema = json!({"x-kubernetes-int-or-string": true});
636 assert_eq!(SchemaFormat::from_schema(&schema), SchemaFormat::IntOrString);
637 }
638
639 #[test]
640 fn int_or_string_int_value_preserved() {
641 let schema = json!({"x-kubernetes-int-or-string": true});
642 let result = json_to_cel_with_schema(&json!(8080), &schema);
643 assert_eq!(result, Value::Int(8080));
644 }
645
646 #[test]
647 fn int_or_string_string_value_preserved() {
648 let schema = json!({"x-kubernetes-int-or-string": true});
649 let result = json_to_cel_with_schema(&json!("http"), &schema);
650 assert_eq!(result, Value::String(Arc::new("http".into())));
651 }
652
653 #[test]
654 fn int_or_string_overrides_format() {
655 let schema = json!({"x-kubernetes-int-or-string": true, "format": "date-time"});
657 assert_eq!(SchemaFormat::from_schema(&schema), SchemaFormat::IntOrString);
658 }
659}
660
661#[cfg(test)]
666mod cel_evaluation_tests {
667 use std::sync::Arc;
668
669 use cel::{Context, Program, Value};
670 use serde_json::json;
671
672 use super::json_to_cel;
673 use crate::register_all;
674
675 fn eval_self(json_val: serde_json::Value, expr: &str) -> Value {
678 let mut ctx = Context::default();
679 register_all(&mut ctx);
680 ctx.add_variable_from_value("self", json_to_cel(&json_val));
681 Program::compile(expr).unwrap().execute(&ctx).unwrap()
682 }
683
684 fn eval_transition(json_self: serde_json::Value, json_old: serde_json::Value, expr: &str) -> Value {
686 let mut ctx = Context::default();
687 register_all(&mut ctx);
688 ctx.add_variable_from_value("self", json_to_cel(&json_self));
689 ctx.add_variable_from_value("oldSelf", json_to_cel(&json_old));
690 Program::compile(expr).unwrap().execute(&ctx).unwrap()
691 }
692
693 #[test]
694 fn scalar_comparison() {
695 assert_eq!(eval_self(json!(10), "self >= 0"), Value::Bool(true));
696 assert_eq!(eval_self(json!(-1), "self >= 0"), Value::Bool(false));
697 }
698
699 #[test]
700 fn field_access_int() {
701 let obj = json!({"replicas": 3});
702 assert_eq!(eval_self(obj, "self.replicas"), Value::Int(3));
703 }
704
705 #[test]
706 fn field_access_string() {
707 let obj = json!({"name": "my-app"});
708 assert_eq!(
709 eval_self(obj, "self.name"),
710 Value::String(Arc::new("my-app".into()))
711 );
712 }
713
714 #[test]
715 fn nested_field_comparison() {
716 let obj = json!({"spec": {"replicas": 5, "minReplicas": 2}});
717 assert_eq!(
718 eval_self(obj, "self.spec.replicas >= self.spec.minReplicas"),
719 Value::Bool(true)
720 );
721 }
722
723 #[test]
724 fn transition_rule_oldself() {
725 let new = json!({"replicas": 5});
726 let old = json!({"replicas": 3});
727 assert_eq!(
728 eval_transition(new, old, "self.replicas >= oldSelf.replicas"),
729 Value::Bool(true)
730 );
731 }
732
733 #[test]
734 fn transition_rule_downscale_rejected() {
735 let new = json!({"replicas": 1});
736 let old = json!({"replicas": 3});
737 assert_eq!(
738 eval_transition(new, old, "self.replicas >= oldSelf.replicas"),
739 Value::Bool(false)
740 );
741 }
742
743 #[test]
744 fn detect_oldself_reference() {
745 let prog1 = Program::compile("self.replicas >= oldSelf.replicas").unwrap();
746 assert!(prog1.references().has_variable("oldSelf"));
747 assert!(prog1.references().has_variable("self"));
748
749 let prog2 = Program::compile("self.replicas >= 0").unwrap();
750 assert!(!prog2.references().has_variable("oldSelf"));
751 assert!(prog2.references().has_variable("self"));
752 }
753
754 #[test]
755 #[cfg(feature = "strings")]
756 fn extension_trim_lower_ascii() {
757 let obj = json!({"name": " Hello World "});
758 assert_eq!(
759 eval_self(obj, "self.name.trim().lowerAscii()"),
760 Value::String(Arc::new("hello world".into()))
761 );
762 }
763
764 #[test]
765 #[cfg(feature = "lists")]
766 fn extension_is_sorted() {
767 let obj = json!({"items": [1, 2, 3, 4]});
768 assert_eq!(eval_self(obj, "self.items.isSorted()"), Value::Bool(true));
769
770 let obj2 = json!({"items": [3, 1, 2]});
771 assert_eq!(eval_self(obj2, "self.items.isSorted()"), Value::Bool(false));
772 }
773
774 #[test]
775 fn array_indexing() {
776 let obj = json!({"containers": [{"name": "nginx"}, {"name": "sidecar"}]});
777 assert_eq!(
778 eval_self(obj, "self.containers[0].name"),
779 Value::String(Arc::new("nginx".into()))
780 );
781 }
782
783 #[test]
784 fn null_comparison() {
785 let obj = json!({"extra": null});
786 assert_eq!(eval_self(obj, "self.extra == null"), Value::Bool(true));
787 }
788
789 #[test]
790 fn non_null_comparison() {
791 let obj = json!({"extra": "present"});
792 assert_eq!(eval_self(obj, "self.extra == null"), Value::Bool(false));
793 }
794
795 #[test]
796 fn has_macro_present() {
797 let obj = json!({"name": "test"});
798 assert_eq!(eval_self(obj, "has(self.name)"), Value::Bool(true));
799 }
800
801 #[test]
802 fn has_macro_missing() {
803 let obj = json!({"name": "test"});
804 assert_eq!(eval_self(obj, "has(self.missing)"), Value::Bool(false));
805 }
806
807 #[test]
808 fn size_of_list() {
809 let obj = json!({"items": [1, 2, 3]});
810 assert_eq!(eval_self(obj, "size(self.items)"), Value::Int(3));
811 }
812
813 #[test]
814 fn size_of_string() {
815 let obj = json!({"name": "hello"});
816 assert_eq!(eval_self(obj, "size(self.name)"), Value::Int(5));
817 }
818}