Skip to main content

reddb_server/storage/query/parser/
dml.rs

1//! DML SQL Parser: INSERT, UPDATE, DELETE
2
3use super::super::ast::{
4    AskQuery, DeleteQuery, Expr, Filter, InsertEntityType, InsertQuery, QueryExpr, ReturningItem,
5    UpdateQuery,
6};
7use super::super::lexer::Token;
8use super::error::ParseError;
9use super::Parser;
10use crate::storage::query::sql_lowering::{filter_to_expr, fold_expr_to_value};
11use crate::storage::schema::Value;
12
13/// DoS guard: maximum JSON nesting depth accepted by the parser.
14/// Mirrors typical web-server JSON limits and bails out before stack
15/// usage gets dangerous in downstream traversals.
16pub(crate) const JSON_LITERAL_MAX_DEPTH: u32 = 128;
17
18/// Walk a parsed `JsonValue` tree and bail out if nesting exceeds
19/// `JSON_LITERAL_MAX_DEPTH`. Iterative to avoid the very stack
20/// overflow we're trying to prevent.
21pub(crate) fn json_literal_depth_check(
22    value: &crate::utils::json::JsonValue,
23) -> Result<(), String> {
24    use crate::utils::json::JsonValue;
25    let mut stack: Vec<(&JsonValue, u32)> = vec![(value, 1)];
26    while let Some((node, depth)) = stack.pop() {
27        if depth > JSON_LITERAL_MAX_DEPTH {
28            return Err(format!(
29                "JSON object literal exceeds JSON_LITERAL_MAX_DEPTH ({})",
30                JSON_LITERAL_MAX_DEPTH
31            ));
32        }
33        match node {
34            JsonValue::Object(entries) => {
35                for (_, v) in entries {
36                    stack.push((v, depth + 1));
37                }
38            }
39            JsonValue::Array(items) => {
40                for v in items {
41                    stack.push((v, depth + 1));
42                }
43            }
44            _ => {}
45        }
46    }
47    Ok(())
48}
49
50impl<'a> Parser<'a> {
51    /// Parse: INSERT INTO table [NODE|EDGE|VECTOR|DOCUMENT|KV] (col1, col2) VALUES (val1, val2), (val3, val4) [RETURNING]
52    pub fn parse_insert_query(&mut self) -> Result<QueryExpr, ParseError> {
53        self.expect(Token::Insert)?;
54        self.expect(Token::Into)?;
55        let table = self.expect_ident()?;
56
57        // Check for entity type keyword
58        let entity_type = match self.peek().clone() {
59            Token::Node => {
60                self.advance()?;
61                InsertEntityType::Node
62            }
63            Token::Edge => {
64                self.advance()?;
65                InsertEntityType::Edge
66            }
67            Token::Vector => {
68                self.advance()?;
69                InsertEntityType::Vector
70            }
71            Token::Document => {
72                self.advance()?;
73                InsertEntityType::Document
74            }
75            Token::Kv => {
76                self.advance()?;
77                InsertEntityType::Kv
78            }
79            _ => InsertEntityType::Row,
80        };
81
82        // Parse column list
83        self.expect(Token::LParen)?;
84        let columns = self.parse_ident_list()?;
85        self.expect(Token::RParen)?;
86
87        // Parse VALUES
88        self.expect(Token::Values)?;
89        let mut all_values = Vec::new();
90        let mut all_value_exprs = Vec::new();
91        loop {
92            self.expect(Token::LParen)?;
93            let row_exprs = self.parse_dml_expr_list()?;
94            self.expect(Token::RParen)?;
95            // Tolerate `$N` / `?` placeholders in VALUES rows: fold to
96            // Value::Null and rely on `user_params::bind` to substitute
97            // the caller's values before execution. Issue #355.
98            // Tolerate `$N` / `?` placeholders in VALUES rows: if fold
99            // fails on an expression that contains `Expr::Parameter`,
100            // emit a `Value::Null` placeholder. `user_params::bind`
101            // substitutes the caller-supplied value before execution.
102            // Issue #355.
103            let row_values = row_exprs
104                .iter()
105                .cloned()
106                .map(|expr| match fold_expr_to_value(expr.clone()) {
107                    Ok(value) => Ok(value),
108                    Err(msg) => {
109                        if crate::storage::query::user_params::expr_contains_parameter(&expr) {
110                            Ok(Value::Null)
111                        } else {
112                            Err(msg)
113                        }
114                    }
115                })
116                .collect::<Result<Vec<_>, _>>()
117                .map_err(|msg| ParseError::new(msg, self.position()))?;
118            all_value_exprs.push(row_exprs);
119            all_values.push(row_values);
120            if !self.consume(&Token::Comma)? {
121                break;
122            }
123        }
124
125        // Parse optional WITH clauses
126        let (ttl_ms, expires_at_ms, with_metadata, auto_embed) = self.parse_with_clauses()?;
127
128        let returning = self.parse_returning_clause()?;
129
130        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
131            self.expect_ident_ci("EVENTS")?;
132            true
133        } else {
134            false
135        };
136
137        Ok(QueryExpr::Insert(InsertQuery {
138            table,
139            entity_type,
140            columns,
141            value_exprs: all_value_exprs,
142            values: all_values,
143            returning,
144            ttl_ms,
145            expires_at_ms,
146            with_metadata,
147            auto_embed,
148            suppress_events,
149        }))
150    }
151
152    /// Parse TTL duration value using the same logic as CREATE TABLE ... WITH TTL.
153    fn parse_ttl_duration(&mut self) -> Result<u64, ParseError> {
154        // Reuse the DDL TTL parser: expects a number followed by optional unit
155        let ttl_value = self.parse_float()?;
156        let ttl_unit = match self.peek() {
157            Token::Ident(unit) => {
158                let unit = unit.clone();
159                self.advance()?;
160                unit
161            }
162            _ => "s".to_string(),
163        };
164
165        let multiplier_ms = match ttl_unit.to_ascii_lowercase().as_str() {
166            "ms" | "msec" | "millisecond" | "milliseconds" => 1.0,
167            "s" | "sec" | "secs" | "second" | "seconds" => 1_000.0,
168            "m" | "min" | "mins" | "minute" | "minutes" => 60_000.0,
169            "h" | "hr" | "hrs" | "hour" | "hours" => 3_600_000.0,
170            "d" | "day" | "days" => 86_400_000.0,
171            other => {
172                return Err(ParseError::new(
173                    // F-05: render `other` via `{:?}` so caller-controlled
174                    // bytes (CR / LF / NUL / quotes) are escaped before
175                    // landing in the JSON/audit/log/gRPC error sinks.
176                    format!("unsupported TTL unit {other:?}"),
177                    self.position(),
178                ));
179            }
180        };
181
182        Ok((ttl_value * multiplier_ms) as u64)
183    }
184
185    /// Parse WITH clauses: WITH TTL | EXPIRES AT | METADATA | AUTO EMBED
186    /// Returns (ttl_ms, expires_at_ms, metadata, auto_embed)
187    pub fn parse_with_clauses(
188        &mut self,
189    ) -> Result<
190        (
191            Option<u64>,
192            Option<u64>,
193            Vec<(String, Value)>,
194            Option<crate::storage::query::ast::AutoEmbedConfig>,
195        ),
196        ParseError,
197    > {
198        let mut ttl_ms = None;
199        let mut expires_at_ms = None;
200        let mut with_metadata = Vec::new();
201        let mut auto_embed = None;
202
203        while self.consume(&Token::With)? {
204            if self.consume_ident_ci("TTL")? {
205                ttl_ms = Some(self.parse_ttl_duration()?);
206            } else if self.consume_ident_ci("EXPIRES")? {
207                self.expect_ident_ci("AT")?;
208                let ts = self.parse_expires_at_value()?;
209                expires_at_ms = Some(ts);
210            } else if self.consume(&Token::Metadata)? || self.consume_ident_ci("METADATA")? {
211                with_metadata = self.parse_with_metadata_pairs()?;
212            } else if self.consume_ident_ci("AUTO")? {
213                // WITH AUTO EMBED (field1, field2) [USING provider] [MODEL 'model']
214                self.consume_ident_ci("EMBED")?;
215                self.expect(Token::LParen)?;
216                let mut fields = Vec::new();
217                loop {
218                    fields.push(self.expect_ident()?);
219                    if !self.consume(&Token::Comma)? {
220                        break;
221                    }
222                }
223                self.expect(Token::RParen)?;
224                // `USING` is a reserved keyword (`Token::Using`), so
225                // `consume_ident_ci` would never match. Use the typed
226                // consumer instead. See bug #108 (mirrors the #92 fix
227                // for migration `DEPENDS ON`).
228                let provider = if self.consume(&Token::Using)? {
229                    self.expect_ident()?
230                } else {
231                    "openai".to_string()
232                };
233                let model = if self.consume_ident_ci("MODEL")? {
234                    Some(self.parse_string()?)
235                } else {
236                    None
237                };
238                auto_embed = Some(crate::storage::query::ast::AutoEmbedConfig {
239                    fields,
240                    provider,
241                    model,
242                });
243            } else {
244                return Err(ParseError::expected(
245                    vec!["TTL", "EXPIRES AT", "METADATA", "AUTO EMBED"],
246                    self.peek(),
247                    self.position(),
248                ));
249            }
250        }
251
252        Ok((ttl_ms, expires_at_ms, with_metadata, auto_embed))
253    }
254
255    /// Expect a case-insensitive identifier (error if not found)
256    fn expect_ident_ci(&mut self, expected: &str) -> Result<(), ParseError> {
257        if self.consume_ident_ci(expected)? {
258            Ok(())
259        } else {
260            Err(ParseError::expected(
261                vec![expected],
262                self.peek(),
263                self.position(),
264            ))
265        }
266    }
267
268    /// Parse an absolute expiration timestamp (unix ms or string date)
269    fn parse_expires_at_value(&mut self) -> Result<u64, ParseError> {
270        // Try integer (unix timestamp in ms)
271        if let Ok(value) = self.parse_integer() {
272            return Ok(value as u64);
273        }
274        // Try string like '2026-12-31' — convert to unix ms
275        if let Ok(text) = self.parse_string() {
276            // Simple ISO date parsing: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
277            let trimmed = text.trim();
278            if let Ok(ts) = trimmed.parse::<u64>() {
279                return Ok(ts);
280            }
281            // Basic date parsing — delegate to chrono if available, or simple heuristic
282            return Err(ParseError::new(
283                // F-05: `trimmed` is caller-controlled string-literal bytes.
284                // Render via `{:?}` so CR/LF/NUL/quotes are escaped before
285                // the message reaches the JSON / audit / log / gRPC sinks.
286                format!("EXPIRES AT requires a unix timestamp in milliseconds, got {trimmed:?}"),
287                self.position(),
288            ));
289        }
290        Err(ParseError::expected(
291            vec!["timestamp (unix ms) or 'YYYY-MM-DD'"],
292            self.peek(),
293            self.position(),
294        ))
295    }
296
297    /// Parse WITH METADATA (key1 = 'value1', key2 = 42)
298    fn parse_with_metadata_pairs(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
299        self.expect(Token::LParen)?;
300        let mut pairs = Vec::new();
301        if !self.check(&Token::RParen) {
302            loop {
303                let key = self.expect_ident_or_keyword()?.to_ascii_lowercase();
304                self.expect(Token::Eq)?;
305                let value = self.parse_literal_value()?;
306                pairs.push((key, value));
307                if !self.consume(&Token::Comma)? {
308                    break;
309                }
310            }
311        }
312        self.expect(Token::RParen)?;
313        Ok(pairs)
314    }
315
316    /// Parse: UPDATE table SET col1=val1, col2=val2 [WHERE filter] [WITH TTL|EXPIRES AT|METADATA]
317    pub fn parse_update_query(&mut self) -> Result<QueryExpr, ParseError> {
318        self.expect(Token::Update)?;
319        let table = self.expect_ident()?;
320        self.expect(Token::Set)?;
321
322        let mut assignments = Vec::new();
323        let mut assignment_exprs = Vec::new();
324        loop {
325            let col = self.expect_ident()?;
326            self.expect(Token::Eq)?;
327            let expr = self.parse_expr()?;
328            let folded = fold_expr_to_value(expr.clone()).ok();
329            assignment_exprs.push((col.clone(), expr));
330            if let Some(val) = folded {
331                assignments.push((col.clone(), val));
332            }
333            if !self.consume(&Token::Comma)? {
334                break;
335            }
336        }
337
338        let filter = if self.consume(&Token::Where)? {
339            Some(self.parse_filter()?)
340        } else {
341            None
342        };
343        let where_expr = filter.as_ref().map(filter_to_expr);
344
345        let (ttl_ms, expires_at_ms, with_metadata, _auto_embed) = self.parse_with_clauses()?;
346
347        // Optional `LIMIT N` — used by `BATCH N ROWS` data migrations
348        // to cap a single batch. Must come after WHERE / WITH because
349        // those have their own keyword tokens that the LIMIT branch
350        // would otherwise mis-consume.
351        let limit = if self.consume(&Token::Limit)? {
352            Some(self.parse_integer()? as u64)
353        } else {
354            None
355        };
356
357        let returning = self.parse_returning_clause()?;
358
359        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
360            self.expect_ident_ci("EVENTS")?;
361            true
362        } else {
363            false
364        };
365
366        Ok(QueryExpr::Update(UpdateQuery {
367            table,
368            assignment_exprs,
369            assignments,
370            where_expr,
371            filter,
372            ttl_ms,
373            expires_at_ms,
374            with_metadata,
375            returning,
376            limit,
377            suppress_events,
378        }))
379    }
380
381    /// Parse: DELETE FROM table [WHERE filter]
382    pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
383        self.expect(Token::Delete)?;
384        self.expect(Token::From)?;
385        let table = self.expect_ident()?;
386
387        let filter = if self.consume(&Token::Where)? {
388            Some(self.parse_filter()?)
389        } else {
390            None
391        };
392
393        let where_expr = filter.as_ref().map(filter_to_expr);
394
395        let returning = self.parse_returning_clause()?;
396
397        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
398            self.expect_ident_ci("EVENTS")?;
399            true
400        } else {
401            false
402        };
403
404        Ok(QueryExpr::Delete(DeleteQuery {
405            table,
406            where_expr,
407            filter,
408            returning,
409            suppress_events,
410        }))
411    }
412
413    /// Parse optional `RETURNING (* | col [, col ...])` clause.
414    /// Returns `None` if no RETURNING token, errors if RETURNING is present
415    /// but not followed by `*` or a non-empty column list.
416    fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
417        if !self.consume(&Token::Returning)? {
418            return Ok(None);
419        }
420        if self.consume(&Token::Star)? {
421            return Ok(Some(vec![ReturningItem::All]));
422        }
423        let mut items = Vec::new();
424        loop {
425            let col = self.expect_ident_or_keyword()?;
426            items.push(ReturningItem::Column(col));
427            if !self.consume(&Token::Comma)? {
428                break;
429            }
430        }
431        if items.is_empty() {
432            return Err(ParseError::expected(
433                vec!["*", "column name"],
434                self.peek(),
435                self.position(),
436            ));
437        }
438        Ok(Some(items))
439    }
440
441    /// Parse: ASK 'question' [USING provider] [MODEL 'model'] [DEPTH n] [LIMIT n] [COLLECTION col]
442    pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
443        self.advance()?; // consume ASK
444
445        let question = self.parse_string()?;
446
447        let mut provider = None;
448        let mut model = None;
449        let mut depth = None;
450        let mut limit = None;
451        let mut collection = None;
452
453        // Parse optional clauses in any order
454        for _ in 0..5 {
455            if self.consume(&Token::Using)? {
456                provider = Some(self.expect_ident()?);
457            } else if self.consume_ident_ci("MODEL")? {
458                model = Some(self.parse_string()?);
459            } else if self.consume(&Token::Depth)? {
460                depth = Some(self.parse_integer()? as usize);
461            } else if self.consume(&Token::Limit)? {
462                limit = Some(self.parse_integer()? as usize);
463            } else if self.consume(&Token::Collection)? {
464                collection = Some(self.expect_ident()?);
465            } else {
466                break;
467            }
468        }
469
470        Ok(QueryExpr::Ask(AskQuery {
471            question,
472            provider,
473            model,
474            depth,
475            limit,
476            collection,
477        }))
478    }
479
480    /// Parse comma-separated identifiers (accepts keywords as column names in DML context)
481    fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
482        let mut idents = Vec::new();
483        loop {
484            idents.push(self.expect_ident_or_keyword()?);
485            if !self.consume(&Token::Comma)? {
486                break;
487            }
488        }
489        Ok(idents)
490    }
491
492    /// Parse comma-separated literal values for DML statements
493    fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
494        self.parse_dml_expr_list()?
495            .into_iter()
496            .map(fold_expr_to_value)
497            .collect::<Result<Vec<_>, _>>()
498            .map_err(|msg| ParseError::new(msg, self.position()))
499    }
500
501    fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
502        let mut values = Vec::new();
503        loop {
504            values.push(self.parse_expr()?);
505            if !self.consume(&Token::Comma)? {
506                break;
507            }
508        }
509        Ok(values)
510    }
511
512    /// Parse a single literal value (string, number, true, false, null, array)
513    pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
514        // Depth guard: this function recurses for nested array `[…]`
515        // and object `{…}` literals (see the LBracket / LBrace arms
516        // below). Without entering the depth counter, an adversarial
517        // payload like `[[[[…(10k×)…]]]]` would overflow the Rust
518        // stack BEFORE `ParserLimits::max_depth` fires. The
519        // `JsonLiteral` token path uses `json_literal_depth_check`
520        // (iterative) — the bare `[`/`{` path needs the recursion
521        // counter explicitly.
522        self.enter_depth()?;
523        let result = self.parse_literal_value_inner();
524        self.exit_depth();
525        result
526    }
527
528    fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
529        // Recognize PASSWORD('plaintext') and SECRET('plaintext') as
530        // typed literal constructors. The parser stores them as
531        // sentinel-prefixed values so that the INSERT executor can
532        // apply the crypto transform (argon2id hash / AES-256-GCM
533        // encrypt) without the parser depending on auth or crypto
534        // subsystems.
535        if let Token::Ident(name) = self.peek().clone() {
536            let upper = name.to_uppercase();
537            if upper == "PASSWORD" || upper == "SECRET" {
538                self.advance()?; // consume ident
539                self.expect(Token::LParen)?;
540                let plaintext = self.parse_string()?;
541                self.expect(Token::RParen)?;
542                return Ok(match upper.as_str() {
543                    "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
544                    "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
545                    _ => unreachable!(),
546                });
547            }
548            if upper == "SECRET_REF" {
549                self.advance()?; // consume ident
550                self.expect(Token::LParen)?;
551                let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
552                if store != "vault" {
553                    return Err(ParseError::expected(
554                        vec!["vault"],
555                        self.peek(),
556                        self.position(),
557                    ));
558                }
559                self.expect(Token::Comma)?;
560                let (collection, key) =
561                    self.parse_kv_key(crate::catalog::CollectionModel::Vault)?;
562                self.expect(Token::RParen)?;
563                return Ok(secret_ref_value(&store, &collection, &key));
564            }
565        }
566
567        match self.peek().clone() {
568            Token::String(s) => {
569                let s = s.clone();
570                self.advance()?;
571                Ok(Value::text(s))
572            }
573            Token::JsonLiteral(raw) => {
574                // The lexer already validated brace balance and the
575                // 16 MiB payload ceiling. Parse the raw text into a
576                // canonical JsonValue then re-encode via `to_vec` so
577                // the on-disk bytes match the quoted form.
578                self.advance()?;
579                let json_value = crate::utils::json::parse_json(&raw).map_err(|err| {
580                    ParseError::new(
581                        // F-05: render the underlying parse-error string
582                        // via `{:?}` so any user fragment serde echoed
583                        // back (unexpected character, key text, …) is
584                        // Debug-escaped before reaching the downstream
585                        // JSON / audit / log / gRPC sinks.
586                        format!("invalid JSON object literal: {:?}", err.to_string()),
587                        self.position(),
588                    )
589                })?;
590                json_literal_depth_check(&json_value)
591                    .map_err(|err| ParseError::new(err, self.position()))?;
592                let canonical = crate::serde_json::Value::from(json_value);
593                let bytes = crate::json::to_vec(&canonical).map_err(|err| {
594                    ParseError::new(
595                        // F-05: escape the encoder error via `{:?}` so any
596                        // user fragment it carries cannot smuggle control
597                        // bytes through downstream serialization sinks.
598                        format!("failed to encode JSON literal: {:?}", err.to_string()),
599                        self.position(),
600                    )
601                })?;
602                Ok(Value::Json(bytes))
603            }
604            Token::Integer(n) => {
605                self.advance()?;
606                Ok(Value::Integer(n))
607            }
608            Token::Float(n) => {
609                self.advance()?;
610                Ok(Value::Float(n))
611            }
612            Token::True => {
613                self.advance()?;
614                Ok(Value::Boolean(true))
615            }
616            Token::False => {
617                self.advance()?;
618                Ok(Value::Boolean(false))
619            }
620            Token::Null => {
621                self.advance()?;
622                Ok(Value::Null)
623            }
624            Token::LBracket => {
625                // Parse array literal [val1, val2, ...]
626                // For numeric arrays, produce Value::Vector; for others, produce Value::Json
627                self.advance()?; // consume '['
628                let mut items = Vec::new();
629                if !self.check(&Token::RBracket) {
630                    loop {
631                        items.push(self.parse_literal_value()?);
632                        if !self.consume(&Token::Comma)? {
633                            break;
634                        }
635                    }
636                }
637                self.expect(Token::RBracket)?;
638
639                // Check if all items are numeric (Integer or Float) -> Value::Vector
640                let all_numeric = items
641                    .iter()
642                    .all(|v| matches!(v, Value::Integer(_) | Value::Float(_)));
643                if all_numeric && !items.is_empty() {
644                    let floats: Vec<f32> = items
645                        .iter()
646                        .map(|v| match v {
647                            Value::Float(f) => *f as f32,
648                            Value::Integer(i) => *i as f32,
649                            _ => 0.0,
650                        })
651                        .collect();
652                    Ok(Value::Vector(floats))
653                } else {
654                    // Encode as JSON bytes
655                    let json_arr: Vec<crate::json::Value> = items
656                        .iter()
657                        .map(|v| match v {
658                            Value::Null => crate::json::Value::Null,
659                            Value::Boolean(b) => crate::json::Value::Bool(*b),
660                            Value::Integer(i) => crate::json::Value::Number(*i as f64),
661                            Value::Float(f) => crate::json::Value::Number(*f),
662                            Value::Text(s) => crate::json::Value::String(s.to_string()),
663                            _ => crate::json::Value::Null,
664                        })
665                        .collect();
666                    let json_val = crate::json::Value::Array(json_arr);
667                    let bytes = crate::json::to_vec(&json_val).unwrap_or_default();
668                    Ok(Value::Json(bytes))
669                }
670            }
671            Token::LBrace => {
672                // Parse JSON object literal {key: value, ...}
673                self.advance()?; // consume '{'
674                let mut map = crate::json::Map::new();
675                if !self.check(&Token::RBrace) {
676                    loop {
677                        // Key: string or identifier. Reserved-word
678                        // keys (`level`, `msg`, `type`, …) fall through
679                        // to `expect_ident_or_keyword`, which returns
680                        // the canonical UPPERCASE spelling; lowercase
681                        // that path so the JSON object preserves the
682                        // source casing.
683                        let key = match self.peek().clone() {
684                            Token::String(s) => {
685                                self.advance()?;
686                                s
687                            }
688                            Token::Ident(s) => {
689                                self.advance()?;
690                                s
691                            }
692                            _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
693                        };
694                        // Separator: ':' or '='
695                        if !self.consume(&Token::Colon)? {
696                            self.expect(Token::Eq)?;
697                        }
698                        // Value: recursive
699                        let val = self.parse_literal_value()?;
700                        let json_val = match val {
701                            Value::Null => crate::json::Value::Null,
702                            Value::Boolean(b) => crate::json::Value::Bool(b),
703                            Value::Integer(i) => crate::json::Value::Number(i as f64),
704                            Value::Float(f) => crate::json::Value::Number(f),
705                            Value::Text(s) => crate::json::Value::String(s.to_string()),
706                            Value::Json(ref bytes) => {
707                                crate::json::from_slice(bytes).unwrap_or(crate::json::Value::Null)
708                            }
709                            _ => crate::json::Value::Null,
710                        };
711                        map.insert(key, json_val);
712                        if !self.consume(&Token::Comma)? {
713                            break;
714                        }
715                    }
716                }
717                self.expect(Token::RBrace)?;
718                let json_val = crate::json::Value::Object(map);
719                let bytes = crate::json::to_vec(&json_val).unwrap_or_default();
720                Ok(Value::Json(bytes))
721            }
722            ref other => Err(ParseError::expected(
723                vec!["string", "number", "true", "false", "null", "[", "{"],
724                other,
725                self.position(),
726            )),
727        }
728    }
729}
730
731fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
732    let mut map = crate::json::Map::new();
733    map.insert(
734        "type".to_string(),
735        crate::json::Value::String("secret_ref".to_string()),
736    );
737    map.insert(
738        "store".to_string(),
739        crate::json::Value::String(store.to_string()),
740    );
741    map.insert(
742        "collection".to_string(),
743        crate::json::Value::String(collection.to_string()),
744    );
745    map.insert(
746        "key".to_string(),
747        crate::json::Value::String(key.to_string()),
748    );
749    Value::Json(crate::json::to_vec(&crate::json::Value::Object(map)).unwrap_or_default())
750}