1#![allow(
4 clippy::cast_lossless,
5 clippy::cast_possible_truncation,
6 clippy::cast_possible_wrap,
7 clippy::cast_sign_loss,
8 clippy::doc_markdown,
9 clippy::format_push_string,
10 clippy::needless_continue,
11 clippy::needless_range_loop,
12 clippy::single_match,
13 clippy::uninlined_format_args
14)]
15
16use alloc::string::{String, ToString};
34use alloc::vec::Vec;
35
36use spg_storage::Value;
37
38use crate::eval::EvalError;
39
40#[derive(Debug, Clone, PartialEq)]
41pub enum JsonValue {
42 Null,
43 Bool(bool),
44 Number(f64),
45 NumberText(String),
49 String(String),
50 Array(Vec<JsonValue>),
51 Object(Vec<(String, JsonValue)>),
52}
53
54impl JsonValue {
55 fn as_text(&self) -> String {
56 match self {
57 Self::Null => "null".into(),
58 Self::Bool(b) => if *b { "true" } else { "false" }.into(),
59 Self::Number(x) => alloc::format!("{x}"),
60 Self::NumberText(s) | Self::String(s) => s.clone(),
61 Self::Array(_) | Self::Object(_) => self.to_json_text(),
62 }
63 }
64
65 fn to_json_text(&self) -> String {
66 let mut out = String::new();
67 write_json(self, &mut out);
68 out
69 }
70}
71
72fn write_json(v: &JsonValue, out: &mut String) {
73 match v {
74 JsonValue::Null => out.push_str("null"),
75 JsonValue::Bool(true) => out.push_str("true"),
76 JsonValue::Bool(false) => out.push_str("false"),
77 JsonValue::Number(x) => out.push_str(&alloc::format!("{x}")),
78 JsonValue::NumberText(s) => out.push_str(s),
79 JsonValue::String(s) => {
80 out.push('"');
81 for c in s.chars() {
82 match c {
83 '"' => out.push_str("\\\""),
84 '\\' => out.push_str("\\\\"),
85 '\n' => out.push_str("\\n"),
86 '\r' => out.push_str("\\r"),
87 '\t' => out.push_str("\\t"),
88 c if (c as u32) < 0x20 => {
89 out.push_str(&alloc::format!("\\u{:04x}", c as u32));
90 }
91 c => out.push(c),
92 }
93 }
94 out.push('"');
95 }
96 JsonValue::Array(items) => {
97 out.push('[');
98 for (i, it) in items.iter().enumerate() {
99 if i > 0 {
100 out.push(',');
101 }
102 write_json(it, out);
103 }
104 out.push(']');
105 }
106 JsonValue::Object(entries) => {
107 out.push('{');
108 for (i, (k, val)) in entries.iter().enumerate() {
109 if i > 0 {
110 out.push(',');
111 }
112 write_json(&JsonValue::String(k.clone()), out);
113 out.push(':');
114 write_json(val, out);
115 }
116 out.push('}');
117 }
118 }
119}
120
121pub fn path_walk(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value, EvalError> {
127 let src = match lhs {
128 Value::Json(s) | Value::Text(s) => s.as_str(),
129 Value::Null => return Ok(Value::Null),
130 other => {
131 return Err(EvalError::TypeMismatch {
132 detail: alloc::format!(
133 "JSON path walk: left side must be JSON or TEXT, got {:?}",
134 other.data_type()
135 ),
136 });
137 }
138 };
139 let path_text = match rhs {
140 Value::Text(s) | Value::Json(s) => s.as_str(),
141 Value::Null => return Ok(Value::Null),
142 other => {
143 return Err(EvalError::TypeMismatch {
144 detail: alloc::format!(
145 "JSON path walk: right side must be TEXT, got {:?}",
146 other.data_type()
147 ),
148 });
149 }
150 };
151 let path = parse_text_array(path_text)?;
152 let mut cur = parse(src).map_err(|e| EvalError::TypeMismatch {
153 detail: alloc::format!("invalid JSON for path walk: {e}"),
154 })?;
155 for step in &path {
156 let next = match (&cur, step.as_str()) {
157 (JsonValue::Object(entries), key) => entries
158 .iter()
159 .find(|(k, _)| k == key)
160 .map(|(_, v)| v.clone()),
161 (JsonValue::Array(items), key) => {
162 let Ok(idx) = key.parse::<i64>() else {
163 return Ok(Value::Null);
164 };
165 if idx >= 0 {
166 items.get(idx as usize).cloned()
167 } else {
168 let from_end = items.len() as i64 + idx;
169 if from_end >= 0 {
170 items.get(from_end as usize).cloned()
171 } else {
172 None
173 }
174 }
175 }
176 _ => return Ok(Value::Null),
177 };
178 cur = match next {
179 None => return Ok(Value::Null),
180 Some(v) => v,
181 };
182 }
183 if matches!(cur, JsonValue::Null) {
184 return Ok(Value::Null);
185 }
186 if as_text {
187 Ok(Value::Text(cur.as_text()))
188 } else {
189 Ok(Value::Json(cur.to_json_text()))
190 }
191}
192
193pub fn contains(lhs: &Value, rhs: &Value) -> Result<Value, EvalError> {
201 let lhs_text = match lhs {
202 Value::Json(s) | Value::Text(s) => s.as_str(),
203 Value::Null => return Ok(Value::Null),
204 other => {
205 return Err(EvalError::TypeMismatch {
206 detail: alloc::format!(
207 "JSON @>: left side must be JSON or TEXT, got {:?}",
208 other.data_type()
209 ),
210 });
211 }
212 };
213 let rhs_text = match rhs {
214 Value::Json(s) | Value::Text(s) => s.as_str(),
215 Value::Null => return Ok(Value::Null),
216 other => {
217 return Err(EvalError::TypeMismatch {
218 detail: alloc::format!(
219 "JSON @>: right side must be JSON or TEXT, got {:?}",
220 other.data_type()
221 ),
222 });
223 }
224 };
225 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
226 detail: alloc::format!("invalid JSON on left of @>: {e}"),
227 })?;
228 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
229 detail: alloc::format!("invalid JSON on right of @>: {e}"),
230 })?;
231 Ok(Value::Bool(json_contains(&lhs_doc, &rhs_doc)))
232}
233
234fn json_contains(lhs: &JsonValue, rhs: &JsonValue) -> bool {
235 match (lhs, rhs) {
236 (JsonValue::Object(l), JsonValue::Object(r)) => r
237 .iter()
238 .all(|(rk, rv)| l.iter().any(|(lk, lv)| lk == rk && json_contains(lv, rv))),
239 (JsonValue::Array(l), JsonValue::Array(r)) => {
240 r.iter().all(|rv| l.iter().any(|lv| json_contains(lv, rv)))
241 }
242 _ => json_eq(lhs, rhs),
243 }
244}
245
246fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
247 match (a, b) {
248 (JsonValue::Null, JsonValue::Null) => true,
249 (JsonValue::Bool(x), JsonValue::Bool(y)) => x == y,
250 (JsonValue::String(x), JsonValue::String(y)) => x == y,
251 (JsonValue::Number(x), JsonValue::Number(y)) => (x - y).abs() < 1e-12,
252 (JsonValue::NumberText(x), JsonValue::NumberText(y)) => x == y,
253 (JsonValue::NumberText(x), JsonValue::Number(y))
254 | (JsonValue::Number(y), JsonValue::NumberText(x)) => {
255 x.parse::<f64>().is_ok_and(|xn| (xn - y).abs() < 1e-12)
256 }
257 (JsonValue::Array(x), JsonValue::Array(y)) => {
258 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| json_eq(a, b))
259 }
260 (JsonValue::Object(x), JsonValue::Object(y)) => {
261 x.len() == y.len()
262 && x.iter()
263 .all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && json_eq(v, v2)))
264 }
265 _ => false,
266 }
267}
268
269fn parse_text_array(s: &str) -> Result<Vec<String>, EvalError> {
274 let trimmed = s.trim();
275 let inner = if let Some(stripped) = trimmed.strip_prefix('{').and_then(|s| s.strip_suffix('}'))
276 {
277 stripped
278 } else {
279 return Err(EvalError::TypeMismatch {
280 detail: alloc::format!("path walk: expected PG array literal `{{…}}`, got {s:?}"),
281 });
282 };
283 if inner.trim().is_empty() {
284 return Ok(Vec::new());
285 }
286 let mut out = Vec::new();
287 let mut cur = String::new();
288 let mut in_quotes = false;
289 let mut chars = inner.chars().peekable();
290 while let Some(c) = chars.next() {
291 match c {
292 '"' => in_quotes = !in_quotes,
293 ',' if !in_quotes => {
294 out.push(cur.trim().to_string());
295 cur = String::new();
296 }
297 '\\' => {
298 if let Some(&next) = chars.peek() {
299 cur.push(next);
300 chars.next();
301 }
302 }
303 _ => cur.push(c),
304 }
305 }
306 out.push(cur.trim().to_string());
307 Ok(out)
308}
309
310pub fn path_get(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value, EvalError> {
315 let src = match lhs {
316 Value::Json(s) | Value::Text(s) => s.as_str(),
317 Value::Null => return Ok(Value::Null),
318 other => {
319 return Err(EvalError::TypeMismatch {
320 detail: alloc::format!(
321 "JSON path operator: left side must be JSON or TEXT, got {:?}",
322 other.data_type()
323 ),
324 });
325 }
326 };
327 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
328 detail: alloc::format!("invalid JSON for path access: {e}"),
329 })?;
330 let inner = match (&doc, rhs) {
331 (JsonValue::Object(entries), Value::Text(k)) => entries
332 .iter()
333 .find(|(name, _)| name == k)
334 .map(|(_, v)| v.clone()),
335 (JsonValue::Array(items), Value::Int(idx)) => {
336 let n = *idx;
337 if n >= 0 {
338 items.get(n as usize).cloned()
339 } else {
340 let from_end = items.len() as i64 + i64::from(n);
341 if from_end >= 0 {
342 items.get(from_end as usize).cloned()
343 } else {
344 None
345 }
346 }
347 }
348 (JsonValue::Array(items), Value::BigInt(idx)) => {
349 let n = *idx;
350 if n >= 0 {
351 items.get(n as usize).cloned()
352 } else {
353 let from_end = items.len() as i64 + n;
354 if from_end >= 0 {
355 items.get(from_end as usize).cloned()
356 } else {
357 None
358 }
359 }
360 }
361 (_, Value::Null) => return Ok(Value::Null),
362 _ => None,
363 };
364 match inner {
365 None | Some(JsonValue::Null) => Ok(Value::Null),
366 Some(v) => {
367 if as_text {
368 Ok(Value::Text(v.as_text()))
369 } else {
370 Ok(Value::Json(v.to_json_text()))
371 }
372 }
373 }
374}
375
376#[derive(Debug)]
379pub enum ParseError {
380 Unexpected(char, usize),
381 Truncated,
382 InvalidEscape(usize),
383 InvalidNumber(usize),
384}
385
386impl core::fmt::Display for ParseError {
387 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388 match self {
389 Self::Unexpected(c, p) => write!(f, "unexpected {c:?} at offset {p}"),
390 Self::Truncated => f.write_str("unexpected end of JSON input"),
391 Self::InvalidEscape(p) => write!(f, "invalid string escape at offset {p}"),
392 Self::InvalidNumber(p) => write!(f, "invalid number at offset {p}"),
393 }
394 }
395}
396
397pub fn parse(src: &str) -> Result<JsonValue, ParseError> {
398 let bytes = src.as_bytes();
399 let mut p = 0;
400 skip_ws(bytes, &mut p);
401 let value = parse_value(bytes, &mut p)?;
402 skip_ws(bytes, &mut p);
403 if p != bytes.len() {
404 return Err(ParseError::Unexpected(bytes[p] as char, p));
405 }
406 Ok(value)
407}
408
409fn skip_ws(bytes: &[u8], p: &mut usize) {
410 while *p < bytes.len() && matches!(bytes[*p], b' ' | b'\t' | b'\n' | b'\r') {
411 *p += 1;
412 }
413}
414
415fn parse_value(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
416 skip_ws(bytes, p);
417 if *p >= bytes.len() {
418 return Err(ParseError::Truncated);
419 }
420 match bytes[*p] {
421 b'{' => parse_object(bytes, p),
422 b'[' => parse_array(bytes, p),
423 b'"' => parse_string(bytes, p).map(JsonValue::String),
424 b't' | b'f' => parse_bool(bytes, p),
425 b'n' => parse_null(bytes, p),
426 b'-' | b'0'..=b'9' => parse_number(bytes, p),
427 c => Err(ParseError::Unexpected(c as char, *p)),
428 }
429}
430
431fn parse_object(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
432 debug_assert_eq!(bytes[*p], b'{');
433 *p += 1;
434 let mut entries = Vec::new();
435 skip_ws(bytes, p);
436 if *p < bytes.len() && bytes[*p] == b'}' {
437 *p += 1;
438 return Ok(JsonValue::Object(entries));
439 }
440 loop {
441 skip_ws(bytes, p);
442 if *p >= bytes.len() || bytes[*p] != b'"' {
443 return Err(ParseError::Unexpected(
444 bytes.get(*p).copied().unwrap_or(0) as char,
445 *p,
446 ));
447 }
448 let key = parse_string(bytes, p)?;
449 skip_ws(bytes, p);
450 if *p >= bytes.len() || bytes[*p] != b':' {
451 return Err(ParseError::Unexpected(
452 bytes.get(*p).copied().unwrap_or(0) as char,
453 *p,
454 ));
455 }
456 *p += 1;
457 let value = parse_value(bytes, p)?;
458 entries.push((key, value));
459 skip_ws(bytes, p);
460 if *p >= bytes.len() {
461 return Err(ParseError::Truncated);
462 }
463 match bytes[*p] {
464 b',' => {
465 *p += 1;
466 continue;
467 }
468 b'}' => {
469 *p += 1;
470 return Ok(JsonValue::Object(entries));
471 }
472 c => return Err(ParseError::Unexpected(c as char, *p)),
473 }
474 }
475}
476
477fn parse_array(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
478 debug_assert_eq!(bytes[*p], b'[');
479 *p += 1;
480 let mut items = Vec::new();
481 skip_ws(bytes, p);
482 if *p < bytes.len() && bytes[*p] == b']' {
483 *p += 1;
484 return Ok(JsonValue::Array(items));
485 }
486 loop {
487 items.push(parse_value(bytes, p)?);
488 skip_ws(bytes, p);
489 if *p >= bytes.len() {
490 return Err(ParseError::Truncated);
491 }
492 match bytes[*p] {
493 b',' => {
494 *p += 1;
495 continue;
496 }
497 b']' => {
498 *p += 1;
499 return Ok(JsonValue::Array(items));
500 }
501 c => return Err(ParseError::Unexpected(c as char, *p)),
502 }
503 }
504}
505
506fn parse_string(bytes: &[u8], p: &mut usize) -> Result<String, ParseError> {
507 debug_assert_eq!(bytes[*p], b'"');
508 *p += 1;
509 let mut out = String::new();
510 while *p < bytes.len() {
511 match bytes[*p] {
512 b'"' => {
513 *p += 1;
514 return Ok(out);
515 }
516 b'\\' => {
517 let start = *p;
518 *p += 1;
519 if *p >= bytes.len() {
520 return Err(ParseError::Truncated);
521 }
522 match bytes[*p] {
523 b'"' => {
524 out.push('"');
525 *p += 1;
526 }
527 b'\\' => {
528 out.push('\\');
529 *p += 1;
530 }
531 b'/' => {
532 out.push('/');
533 *p += 1;
534 }
535 b'b' => {
536 out.push('\u{08}');
537 *p += 1;
538 }
539 b'f' => {
540 out.push('\u{0c}');
541 *p += 1;
542 }
543 b'n' => {
544 out.push('\n');
545 *p += 1;
546 }
547 b'r' => {
548 out.push('\r');
549 *p += 1;
550 }
551 b't' => {
552 out.push('\t');
553 *p += 1;
554 }
555 b'u' => {
556 if *p + 5 > bytes.len() {
557 return Err(ParseError::Truncated);
558 }
559 let hex = &bytes[*p + 1..*p + 5];
560 let n = u32::from_str_radix(
561 core::str::from_utf8(hex)
562 .map_err(|_| ParseError::InvalidEscape(start))?,
563 16,
564 )
565 .map_err(|_| ParseError::InvalidEscape(start))?;
566 out.push(char::from_u32(n).ok_or(ParseError::InvalidEscape(start))?);
567 *p += 5;
568 }
569 _ => return Err(ParseError::InvalidEscape(start)),
570 }
571 }
572 c if c < 0x20 => return Err(ParseError::Unexpected(c as char, *p)),
573 _ => {
574 let s = core::str::from_utf8(&bytes[*p..])
576 .map_err(|_| ParseError::Unexpected(bytes[*p] as char, *p))?;
577 let c = s.chars().next().unwrap();
578 out.push(c);
579 *p += c.len_utf8();
580 }
581 }
582 }
583 Err(ParseError::Truncated)
584}
585
586fn parse_bool(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
587 if bytes[*p..].starts_with(b"true") {
588 *p += 4;
589 Ok(JsonValue::Bool(true))
590 } else if bytes[*p..].starts_with(b"false") {
591 *p += 5;
592 Ok(JsonValue::Bool(false))
593 } else {
594 Err(ParseError::Unexpected(bytes[*p] as char, *p))
595 }
596}
597
598fn parse_null(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
599 if bytes[*p..].starts_with(b"null") {
600 *p += 4;
601 Ok(JsonValue::Null)
602 } else {
603 Err(ParseError::Unexpected(bytes[*p] as char, *p))
604 }
605}
606
607fn parse_number(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
608 let start = *p;
609 if bytes[*p] == b'-' {
610 *p += 1;
611 }
612 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
613 *p += 1;
614 }
615 if *p < bytes.len() && bytes[*p] == b'.' {
616 *p += 1;
617 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
618 *p += 1;
619 }
620 }
621 if *p < bytes.len() && matches!(bytes[*p], b'e' | b'E') {
622 *p += 1;
623 if *p < bytes.len() && matches!(bytes[*p], b'+' | b'-') {
624 *p += 1;
625 }
626 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
627 *p += 1;
628 }
629 }
630 let text = core::str::from_utf8(&bytes[start..*p])
631 .map_err(|_| ParseError::InvalidNumber(start))?
632 .to_string();
633 if text.parse::<f64>().is_err() {
635 return Err(ParseError::InvalidNumber(start));
636 }
637 Ok(JsonValue::NumberText(text))
638}
639
640#[derive(Debug, Clone)]
659enum PathStep {
660 Field(String),
661 Index(usize),
662 Wildcard,
663}
664
665fn parse_jsonpath(p: &str) -> Result<Vec<PathStep>, EvalError> {
666 let chars: Vec<char> = p.chars().collect();
667 let mut i = 0;
668 if i >= chars.len() || chars[i] != '$' {
669 return Err(EvalError::TypeMismatch {
670 detail: alloc::format!("jsonpath must start with '$', got {p:?}"),
671 });
672 }
673 i += 1;
674 let mut steps: Vec<PathStep> = Vec::new();
675 while i < chars.len() {
676 match chars[i] {
677 '.' => {
678 i += 1;
679 if i < chars.len() && chars[i] == '"' {
680 i += 1;
681 let start = i;
682 while i < chars.len() && chars[i] != '"' {
683 i += 1;
684 }
685 if i >= chars.len() {
686 return Err(EvalError::TypeMismatch {
687 detail: "jsonpath: unterminated quoted field".into(),
688 });
689 }
690 steps.push(PathStep::Field(chars[start..i].iter().collect()));
691 i += 1;
692 } else {
693 let start = i;
694 while i < chars.len()
695 && chars[i] != '.'
696 && chars[i] != '['
697 && !chars[i].is_whitespace()
698 {
699 i += 1;
700 }
701 if start == i {
702 return Err(EvalError::TypeMismatch {
703 detail: "jsonpath: missing field name after '.'".into(),
704 });
705 }
706 steps.push(PathStep::Field(chars[start..i].iter().collect()));
707 }
708 }
709 '[' => {
710 i += 1;
711 if i < chars.len() && chars[i] == '*' {
712 i += 1;
713 if i >= chars.len() || chars[i] != ']' {
714 return Err(EvalError::TypeMismatch {
715 detail: "jsonpath: expected ']' after '[*'".into(),
716 });
717 }
718 i += 1;
719 steps.push(PathStep::Wildcard);
720 } else {
721 let start = i;
722 while i < chars.len() && chars[i].is_ascii_digit() {
723 i += 1;
724 }
725 if start == i {
726 return Err(EvalError::TypeMismatch {
727 detail:
728 "jsonpath: only `[N]` (non-negative) or `[*]` supported in v7.17"
729 .into(),
730 });
731 }
732 let idx: usize =
733 chars[start..i]
734 .iter()
735 .collect::<String>()
736 .parse()
737 .map_err(|_| EvalError::TypeMismatch {
738 detail: "jsonpath: invalid array index".into(),
739 })?;
740 if i >= chars.len() || chars[i] != ']' {
741 return Err(EvalError::TypeMismatch {
742 detail: "jsonpath: expected ']' after array index".into(),
743 });
744 }
745 i += 1;
746 steps.push(PathStep::Index(idx));
747 }
748 }
749 c if c.is_whitespace() => {
750 i += 1;
751 }
752 c => {
753 return Err(EvalError::TypeMismatch {
754 detail: alloc::format!(
755 "jsonpath: unexpected char '{c}' (v7.17 supports `$.field`, `[N]`, `[*]` only)"
756 ),
757 });
758 }
759 }
760 }
761 Ok(steps)
762}
763
764fn apply_jsonpath(root: &JsonValue, steps: &[PathStep]) -> Vec<JsonValue> {
765 let mut cur: Vec<JsonValue> = alloc::vec![root.clone()];
766 for step in steps {
767 let mut next: Vec<JsonValue> = Vec::new();
768 for node in &cur {
769 match (step, node) {
770 (PathStep::Field(k), JsonValue::Object(entries)) => {
771 if let Some((_, v)) = entries.iter().find(|(name, _)| name == k) {
772 next.push(v.clone());
773 }
774 }
775 (PathStep::Index(idx), JsonValue::Array(items)) => {
776 if let Some(v) = items.get(*idx) {
777 next.push(v.clone());
778 }
779 }
780 (PathStep::Wildcard, JsonValue::Array(items)) => {
781 next.extend(items.iter().cloned());
782 }
783 _ => {} }
785 }
786 cur = next;
787 if cur.is_empty() {
788 return Vec::new();
789 }
790 }
791 cur
792}
793
794pub fn path_query(doc: &Value, path: &Value) -> Result<Value, EvalError> {
798 let (src, path_text) = match (doc, path) {
799 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
800 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
801 _ => {
802 return Err(EvalError::TypeMismatch {
803 detail: "jsonb_path_query() expects (JSON, TEXT)".into(),
804 });
805 }
806 };
807 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
808 detail: alloc::format!("invalid JSON for jsonb_path_query: {e}"),
809 })?;
810 let steps = parse_jsonpath(path_text)?;
811 let matches = apply_jsonpath(&root, &steps);
812 let arr: Vec<Option<String>> = matches
813 .into_iter()
814 .map(|v| Some(v.to_json_text()))
815 .collect();
816 Ok(Value::TextArray(arr))
817}
818
819pub fn path_query_first(doc: &Value, path: &Value) -> Result<Value, EvalError> {
822 let q = path_query(doc, path)?;
823 match q {
824 Value::TextArray(items) => {
825 if let Some(Some(first)) = items.into_iter().next() {
826 Ok(Value::Json(first))
827 } else {
828 Ok(Value::Null)
829 }
830 }
831 other => Ok(other),
832 }
833}
834
835pub fn path_query_array(doc: &Value, path: &Value) -> Result<Value, EvalError> {
838 let q = path_query(doc, path)?;
839 match q {
840 Value::TextArray(items) => {
841 let mut buf = String::from("[");
842 let mut first = true;
843 for s in items.into_iter().flatten() {
844 if !first {
845 buf.push(',');
846 }
847 buf.push_str(&s);
848 first = false;
849 }
850 buf.push(']');
851 Ok(Value::Json(buf))
852 }
853 other => Ok(other),
854 }
855}
856
857pub fn value_to_json_text(v: &Value) -> String {
878 let mut out = String::new();
879 encode_value_into(v, &mut out);
880 out
881}
882
883fn encode_value_into(v: &Value, out: &mut String) {
884 match v {
885 Value::Null => out.push_str("null"),
886 Value::Bool(true) => out.push_str("true"),
887 Value::Bool(false) => out.push_str("false"),
888 Value::SmallInt(n) => out.push_str(&alloc::format!("{n}")),
889 Value::Int(n) => out.push_str(&alloc::format!("{n}")),
890 Value::BigInt(n) => out.push_str(&alloc::format!("{n}")),
891 Value::Float(x) => out.push_str(&alloc::format!("{x}")),
892 Value::Numeric { scaled, scale } => {
893 out.push_str(&render_numeric(*scaled, *scale));
895 }
896 Value::Text(s) => write_json(&JsonValue::String(s.clone()), out),
897 Value::Json(s) => {
898 out.push_str(s);
902 }
903 Value::TextArray(items) => {
904 out.push('[');
905 for (i, it) in items.iter().enumerate() {
906 if i > 0 {
907 out.push(',');
908 }
909 match it {
910 Some(s) => write_json(&JsonValue::String(s.clone()), out),
911 None => out.push_str("null"),
912 }
913 }
914 out.push(']');
915 }
916 Value::IntArray(items) => {
917 out.push('[');
918 for (i, it) in items.iter().enumerate() {
919 if i > 0 {
920 out.push(',');
921 }
922 match it {
923 Some(n) => out.push_str(&alloc::format!("{n}")),
924 None => out.push_str("null"),
925 }
926 }
927 out.push(']');
928 }
929 Value::BigIntArray(items) => {
930 out.push('[');
931 for (i, it) in items.iter().enumerate() {
932 if i > 0 {
933 out.push(',');
934 }
935 match it {
936 Some(n) => out.push_str(&alloc::format!("{n}")),
937 None => out.push_str("null"),
938 }
939 }
940 out.push(']');
941 }
942 other => {
946 let txt = alloc::format!("{other:?}");
947 write_json(&JsonValue::String(txt), out);
948 }
949 }
950}
951
952fn render_numeric(scaled: i128, scale: u8) -> String {
953 let neg = scaled < 0;
954 let mag_str = alloc::format!("{}", scaled.unsigned_abs());
955 let s = scale as usize;
956 let body = if s == 0 {
957 mag_str
958 } else if mag_str.len() > s {
959 let p = mag_str.len() - s;
960 alloc::format!("{}.{}", &mag_str[..p], &mag_str[p..])
961 } else {
962 let pad = s - mag_str.len();
963 alloc::format!("0.{}{}", "0".repeat(pad), mag_str)
964 };
965 if neg { alloc::format!("-{body}") } else { body }
966}
967
968pub fn build_object(args: &[Value]) -> Result<Value, EvalError> {
972 if !args.len().is_multiple_of(2) {
973 return Err(EvalError::TypeMismatch {
974 detail: alloc::format!(
975 "json_build_object() needs an even number of args, got {}",
976 args.len()
977 ),
978 });
979 }
980 let mut out = String::from("{");
981 let mut first = true;
982 for pair in args.chunks_exact(2) {
983 if !first {
984 out.push(',');
985 }
986 first = false;
987 let key = match &pair[0] {
988 Value::Null => {
989 return Err(EvalError::TypeMismatch {
990 detail: "json_build_object() key cannot be NULL".into(),
991 });
992 }
993 Value::Text(s) | Value::Json(s) => s.clone(),
994 other => format_value_as_text(other),
995 };
996 write_json(&JsonValue::String(key), &mut out);
997 out.push(':');
998 encode_value_into(&pair[1], &mut out);
999 }
1000 out.push('}');
1001 Ok(Value::Json(out))
1002}
1003
1004pub fn build_array(args: &[Value]) -> Result<Value, EvalError> {
1007 let mut out = String::from("[");
1008 for (i, v) in args.iter().enumerate() {
1009 if i > 0 {
1010 out.push(',');
1011 }
1012 encode_value_into(v, &mut out);
1013 }
1014 out.push(']');
1015 Ok(Value::Json(out))
1016}
1017
1018fn format_value_as_text(v: &Value) -> String {
1019 match v {
1020 Value::SmallInt(n) => alloc::format!("{n}"),
1021 Value::Int(n) => alloc::format!("{n}"),
1022 Value::BigInt(n) => alloc::format!("{n}"),
1023 Value::Float(x) => alloc::format!("{x}"),
1024 Value::Bool(b) => alloc::format!("{b}"),
1025 other => alloc::format!("{other:?}"),
1026 }
1027}
1028
1029pub fn set(args: &[Value]) -> Result<Value, EvalError> {
1038 if !(3..=4).contains(&args.len()) {
1039 return Err(EvalError::TypeMismatch {
1040 detail: alloc::format!("jsonb_set() takes 3 or 4 args, got {}", args.len()),
1041 });
1042 }
1043 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
1044 return Ok(Value::Null);
1045 }
1046 let create_missing = match args.get(3) {
1047 None | Some(Value::Null) => true,
1048 Some(Value::Bool(b)) => *b,
1049 Some(other) => {
1050 return Err(EvalError::TypeMismatch {
1051 detail: alloc::format!(
1052 "jsonb_set() create_missing must be BOOL, got {:?}",
1053 other.data_type()
1054 ),
1055 });
1056 }
1057 };
1058 let doc_text = json_text_arg(&args[0], "jsonb_set", "target")?;
1059 let path = path_text_arg(&args[1], "jsonb_set")?;
1060 let new_text = json_text_arg(&args[2], "jsonb_set", "new_value")?;
1061 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
1062 detail: alloc::format!("jsonb_set(): invalid JSON target — {e}"),
1063 })?;
1064 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
1065 detail: alloc::format!("jsonb_set(): invalid JSON new_value — {e}"),
1066 })?;
1067 set_at_path(&mut root, &path, new_val, create_missing);
1068 Ok(Value::Json(root.to_json_text()))
1069}
1070
1071fn set_at_path(node: &mut JsonValue, path: &[String], new_val: JsonValue, create_missing: bool) {
1072 if path.is_empty() {
1073 *node = new_val;
1074 return;
1075 }
1076 let step = &path[0];
1077 let rest = &path[1..];
1078 match node {
1079 JsonValue::Object(entries) => {
1080 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
1081 if rest.is_empty() {
1082 entries[pos].1 = new_val;
1083 } else {
1084 set_at_path(&mut entries[pos].1, rest, new_val, create_missing);
1085 }
1086 } else if create_missing && rest.is_empty() {
1087 entries.push((step.clone(), new_val));
1088 }
1089 }
1092 JsonValue::Array(items) => {
1093 let Some(idx) = resolve_array_index(step, items.len()) else {
1094 if create_missing && rest.is_empty() {
1095 if let Ok(n) = step.parse::<i64>() {
1097 if n < 0 {
1098 items.insert(0, new_val);
1099 } else {
1100 items.push(new_val);
1101 }
1102 }
1103 }
1104 return;
1105 };
1106 if rest.is_empty() {
1107 items[idx] = new_val;
1108 } else {
1109 set_at_path(&mut items[idx], rest, new_val, create_missing);
1110 }
1111 }
1112 _ => {
1113 }
1115 }
1116}
1117
1118fn resolve_array_index(step: &str, len: usize) -> Option<usize> {
1119 let n = step.parse::<i64>().ok()?;
1120 if n >= 0 {
1121 let i = n as usize;
1122 if i < len { Some(i) } else { None }
1123 } else {
1124 let from_end = len as i64 + n;
1125 if from_end >= 0 {
1126 Some(from_end as usize)
1127 } else {
1128 None
1129 }
1130 }
1131}
1132
1133pub fn insert(args: &[Value]) -> Result<Value, EvalError> {
1141 if !(3..=4).contains(&args.len()) {
1142 return Err(EvalError::TypeMismatch {
1143 detail: alloc::format!("jsonb_insert() takes 3 or 4 args, got {}", args.len()),
1144 });
1145 }
1146 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
1147 return Ok(Value::Null);
1148 }
1149 let insert_after = match args.get(3) {
1150 None | Some(Value::Null) => false,
1151 Some(Value::Bool(b)) => *b,
1152 Some(other) => {
1153 return Err(EvalError::TypeMismatch {
1154 detail: alloc::format!(
1155 "jsonb_insert() insert_after must be BOOL, got {:?}",
1156 other.data_type()
1157 ),
1158 });
1159 }
1160 };
1161 let doc_text = json_text_arg(&args[0], "jsonb_insert", "target")?;
1162 let path = path_text_arg(&args[1], "jsonb_insert")?;
1163 let new_text = json_text_arg(&args[2], "jsonb_insert", "new_value")?;
1164 if path.is_empty() {
1165 return Err(EvalError::TypeMismatch {
1166 detail: "jsonb_insert(): path cannot be empty".into(),
1167 });
1168 }
1169 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
1170 detail: alloc::format!("jsonb_insert(): invalid JSON target — {e}"),
1171 })?;
1172 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
1173 detail: alloc::format!("jsonb_insert(): invalid JSON new_value — {e}"),
1174 })?;
1175 insert_at_path(&mut root, &path, new_val, insert_after)?;
1176 Ok(Value::Json(root.to_json_text()))
1177}
1178
1179fn insert_at_path(
1180 node: &mut JsonValue,
1181 path: &[String],
1182 new_val: JsonValue,
1183 insert_after: bool,
1184) -> Result<(), EvalError> {
1185 debug_assert!(!path.is_empty());
1186 if path.len() == 1 {
1187 let step = &path[0];
1188 match node {
1189 JsonValue::Object(entries) => {
1190 if entries.iter().any(|(k, _)| k == step) {
1191 return Err(EvalError::TypeMismatch {
1192 detail: alloc::format!(
1193 "jsonb_insert(): cannot replace existing key {step:?}"
1194 ),
1195 });
1196 }
1197 entries.push((step.clone(), new_val));
1198 Ok(())
1199 }
1200 JsonValue::Array(items) => {
1201 let Ok(n) = step.parse::<i64>() else {
1202 return Err(EvalError::TypeMismatch {
1203 detail: alloc::format!(
1204 "jsonb_insert(): array step must be integer, got {step:?}"
1205 ),
1206 });
1207 };
1208 let mut idx = if n >= 0 {
1209 let i = n as usize;
1210 if i > items.len() { items.len() } else { i }
1211 } else {
1212 let from_end = items.len() as i64 + n;
1213 if from_end < 0 { 0 } else { from_end as usize }
1214 };
1215 if insert_after && idx < items.len() {
1216 idx += 1;
1217 }
1218 items.insert(idx, new_val);
1219 Ok(())
1220 }
1221 _ => Err(EvalError::TypeMismatch {
1222 detail: "jsonb_insert(): parent at path is a scalar".into(),
1223 }),
1224 }
1225 } else {
1226 let step = &path[0];
1227 let rest = &path[1..];
1228 match node {
1229 JsonValue::Object(entries) => {
1230 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
1231 insert_at_path(&mut entries[pos].1, rest, new_val, insert_after)
1232 } else {
1233 Err(EvalError::TypeMismatch {
1234 detail: alloc::format!("jsonb_insert(): path {step:?} does not exist"),
1235 })
1236 }
1237 }
1238 JsonValue::Array(items) => {
1239 let Some(idx) = resolve_array_index(step, items.len()) else {
1240 return Err(EvalError::TypeMismatch {
1241 detail: alloc::format!("jsonb_insert(): array index {step:?} out of range"),
1242 });
1243 };
1244 insert_at_path(&mut items[idx], rest, new_val, insert_after)
1245 }
1246 _ => Err(EvalError::TypeMismatch {
1247 detail: "jsonb_insert(): parent at path is a scalar".into(),
1248 }),
1249 }
1250 }
1251}
1252
1253fn json_text_arg<'a>(v: &'a Value, fname: &str, role: &str) -> Result<&'a str, EvalError> {
1254 match v {
1255 Value::Json(s) | Value::Text(s) => Ok(s.as_str()),
1256 other => Err(EvalError::TypeMismatch {
1257 detail: alloc::format!(
1258 "{fname}() {role} must be JSON or TEXT, got {:?}",
1259 other.data_type()
1260 ),
1261 }),
1262 }
1263}
1264
1265fn path_text_arg(v: &Value, fname: &str) -> Result<Vec<String>, EvalError> {
1266 match v {
1267 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_str()),
1268 Value::TextArray(items) => Ok(items
1269 .iter()
1270 .map(|o| o.clone().unwrap_or_default())
1271 .collect()),
1272 other => Err(EvalError::TypeMismatch {
1273 detail: alloc::format!(
1274 "{fname}() path must be TEXT[] or TEXT, got {:?}",
1275 other.data_type()
1276 ),
1277 }),
1278 }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283 use super::*;
1284
1285 #[test]
1286 fn parse_atoms() {
1287 assert_eq!(parse("null").unwrap(), JsonValue::Null);
1288 assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
1289 assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
1290 assert_eq!(
1291 parse("\"hello\"").unwrap(),
1292 JsonValue::String("hello".into())
1293 );
1294 assert!(matches!(
1295 parse("42").unwrap(),
1296 JsonValue::NumberText(ref s) if s == "42"
1297 ));
1298 }
1299
1300 #[test]
1301 fn parse_nested() {
1302 let doc = parse(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
1303 let JsonValue::Object(entries) = doc else {
1304 panic!("expected object");
1305 };
1306 assert_eq!(entries.len(), 2);
1307 assert_eq!(entries[0].0, "a");
1308 assert_eq!(entries[1].0, "b");
1309 }
1310
1311 #[test]
1312 fn parse_string_escapes() {
1313 let s = parse(r#""he said \"hi\" and\\then\n""#).unwrap();
1314 assert_eq!(s, JsonValue::String("he said \"hi\" and\\then\n".into()));
1315 }
1316
1317 #[test]
1318 fn parse_unicode_escape() {
1319 assert_eq!(parse(r#""é""#).unwrap(), JsonValue::String("é".into()));
1320 }
1321
1322 #[test]
1323 fn path_object_key_returns_value() {
1324 let doc = Value::Json(r#"{"name":"alice","age":30}"#.into());
1325 let key = Value::Text("name".into());
1326 let v = path_get(&doc, &key, true).unwrap();
1327 assert_eq!(v, Value::Text("alice".into()));
1328 let v = path_get(&doc, &key, false).unwrap();
1329 assert_eq!(v, Value::Json("\"alice\"".into()));
1330 }
1331
1332 #[test]
1333 fn path_array_index_supports_negative() {
1334 let doc = Value::Json("[10,20,30]".into());
1335 let v = path_get(&doc, &Value::Int(1), true).unwrap();
1336 assert_eq!(v, Value::Text("20".into()));
1337 let v = path_get(&doc, &Value::Int(-1), true).unwrap();
1338 assert_eq!(v, Value::Text("30".into()));
1339 }
1340
1341 #[test]
1342 fn path_missing_key_returns_null() {
1343 let doc = Value::Json(r#"{"a":1}"#.into());
1344 let v = path_get(&doc, &Value::Text("missing".into()), true).unwrap();
1345 assert_eq!(v, Value::Null);
1346 }
1347
1348 #[test]
1349 fn path_get_nested_subtree_renders_back() {
1350 let doc = Value::Json(r#"{"k":{"x":[1,2]}}"#.into());
1351 let v = path_get(&doc, &Value::Text("k".into()), false).unwrap();
1352 assert_eq!(v, Value::Json("{\"x\":[1,2]}".into()));
1353 }
1354}