Skip to main content

tstring_json/
lib.rs

1use serde_json::Value;
2use std::str::FromStr;
3use tstring_syntax::{
4    BackendError, BackendResult, NormalizedDocument, NormalizedFloat, NormalizedKey,
5    NormalizedStream, NormalizedValue, SourcePosition, SourceSpan, StreamItem, TemplateInput,
6};
7
8#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9pub enum JsonProfile {
10    Rfc8259,
11}
12
13impl JsonProfile {
14    #[must_use]
15    pub const fn as_str(self) -> &'static str {
16        match self {
17            Self::Rfc8259 => "rfc8259",
18        }
19    }
20}
21
22impl Default for JsonProfile {
23    fn default() -> Self {
24        Self::Rfc8259
25    }
26}
27
28impl FromStr for JsonProfile {
29    type Err = String;
30
31    fn from_str(value: &str) -> Result<Self, Self::Err> {
32        match value {
33            "rfc8259" => Ok(Self::Rfc8259),
34            other => Err(format!(
35                "Unsupported JSON profile {other:?}. Supported profiles: \"rfc8259\"."
36            )),
37        }
38    }
39}
40
41#[derive(Clone, Debug)]
42pub struct JsonInterpolationNode {
43    pub span: SourceSpan,
44    pub interpolation_index: usize,
45    pub role: String,
46}
47
48#[derive(Clone, Debug)]
49pub struct JsonStringChunkNode {
50    pub span: SourceSpan,
51    pub value: String,
52}
53
54#[derive(Clone, Debug)]
55pub enum JsonStringPart {
56    Chunk(JsonStringChunkNode),
57    Interpolation(JsonInterpolationNode),
58}
59
60#[derive(Clone, Debug)]
61pub struct JsonStringNode {
62    pub span: SourceSpan,
63    pub chunks: Vec<JsonStringPart>,
64    pub quoted: bool,
65}
66
67#[derive(Clone, Debug)]
68pub struct JsonLiteralNode {
69    pub span: SourceSpan,
70    pub source: String,
71    pub value: Value,
72}
73
74#[derive(Clone, Debug)]
75pub struct JsonKeyNode {
76    pub span: SourceSpan,
77    pub value: JsonKeyValue,
78}
79
80#[derive(Clone, Debug)]
81pub enum JsonKeyValue {
82    String(JsonStringNode),
83    Interpolation(JsonInterpolationNode),
84}
85
86#[derive(Clone, Debug)]
87pub struct JsonMemberNode {
88    pub span: SourceSpan,
89    pub key: JsonKeyNode,
90    pub value: JsonValueNode,
91}
92
93#[derive(Clone, Debug)]
94pub struct JsonObjectNode {
95    pub span: SourceSpan,
96    pub members: Vec<JsonMemberNode>,
97}
98
99#[derive(Clone, Debug)]
100pub struct JsonArrayNode {
101    pub span: SourceSpan,
102    pub items: Vec<JsonValueNode>,
103}
104
105#[derive(Clone, Debug)]
106pub struct JsonDocumentNode {
107    pub span: SourceSpan,
108    pub value: JsonValueNode,
109}
110
111#[derive(Clone, Debug)]
112pub enum JsonValueNode {
113    String(JsonStringNode),
114    Literal(JsonLiteralNode),
115    Interpolation(JsonInterpolationNode),
116    Object(JsonObjectNode),
117    Array(JsonArrayNode),
118}
119
120pub struct JsonParser {
121    items: Vec<StreamItem>,
122    index: usize,
123}
124
125impl JsonParser {
126    #[must_use]
127    pub fn new(template: &TemplateInput) -> Self {
128        Self {
129            items: template.flatten(),
130            index: 0,
131        }
132    }
133
134    pub fn parse(&mut self) -> BackendResult<JsonDocumentNode> {
135        let start = self.mark();
136        let value = self.parse_value()?;
137        self.skip_whitespace();
138        if self.current_kind() != "eof" {
139            return Err(self.error("Unexpected trailing content in JSON template."));
140        }
141        Ok(JsonDocumentNode {
142            span: self.span_from(start),
143            value,
144        })
145    }
146
147    fn current(&self) -> &StreamItem {
148        &self.items[self.index]
149    }
150
151    fn current_kind(&self) -> &'static str {
152        self.current().kind()
153    }
154
155    fn current_char(&self) -> Option<char> {
156        self.current().char()
157    }
158
159    fn mark(&self) -> SourcePosition {
160        self.current().span().start.clone()
161    }
162
163    fn previous_end(&self) -> SourcePosition {
164        if self.index == 0 {
165            return self.current().span().start.clone();
166        }
167        self.items[self.index - 1].span().end.clone()
168    }
169
170    fn span_from(&self, start: SourcePosition) -> SourceSpan {
171        SourceSpan::between(start, self.previous_end())
172    }
173
174    fn error(&self, message: impl Into<String>) -> BackendError {
175        BackendError::parse_at("json.parse", message, Some(self.current().span().clone()))
176    }
177
178    fn advance(&mut self) {
179        if self.current_kind() != "eof" {
180            self.index += 1;
181        }
182    }
183
184    fn skip_whitespace(&mut self) {
185        while matches!(self.current_char(), Some(ch) if ch.is_whitespace()) {
186            self.advance();
187        }
188    }
189
190    fn consume_char(&mut self, expected: char) -> BackendResult<()> {
191        if self.current_char() != Some(expected) {
192            return Err(self.error(format!("Expected {expected:?} in JSON template.")));
193        }
194        self.advance();
195        Ok(())
196    }
197
198    fn parse_value(&mut self) -> BackendResult<JsonValueNode> {
199        self.skip_whitespace();
200        let start = self.mark();
201
202        if self.current_kind() == "interpolation" {
203            let interpolation = self.consume_interpolation("value")?;
204            if self.starts_value_terminator() {
205                return Ok(JsonValueNode::Interpolation(interpolation));
206            }
207            return Ok(JsonValueNode::String(self.parse_promoted_string(
208                start,
209                vec![JsonStringPart::Interpolation(interpolation)],
210            )?));
211        }
212
213        if self.current_char() == Some('{') {
214            return Ok(JsonValueNode::Object(self.parse_object()?));
215        }
216        if self.current_char() == Some('[') {
217            return Ok(JsonValueNode::Array(self.parse_array()?));
218        }
219        if self.current_char() == Some('"') {
220            return Ok(JsonValueNode::String(self.parse_string(true)?));
221        }
222        if matches!(self.current_char(), Some('-' | '0'..='9')) {
223            return Ok(JsonValueNode::Literal(self.parse_number()?));
224        }
225
226        if let Some(literal) = self.try_consume_literal("true", Value::Bool(true))? {
227            return Ok(JsonValueNode::Literal(literal));
228        }
229        if let Some(literal) = self.try_consume_literal("false", Value::Bool(false))? {
230            return Ok(JsonValueNode::Literal(literal));
231        }
232        if let Some(literal) = self.try_consume_literal("null", Value::Null)? {
233            return Ok(JsonValueNode::Literal(literal));
234        }
235
236        Ok(JsonValueNode::String(
237            self.parse_promoted_string(start, Vec::new())?,
238        ))
239    }
240
241    fn try_consume_literal(
242        &mut self,
243        text: &str,
244        value: Value,
245    ) -> BackendResult<Option<JsonLiteralNode>> {
246        let start_index = self.index;
247        let start = self.mark();
248        for expected in text.chars() {
249            if self.current_char() != Some(expected) {
250                self.index = start_index;
251                return Ok(None);
252            }
253            self.advance();
254        }
255        if !self.starts_value_terminator() {
256            self.index = start_index;
257            return Ok(None);
258        }
259        Ok(Some(JsonLiteralNode {
260            span: self.span_from(start),
261            source: text.to_owned(),
262            value,
263        }))
264    }
265
266    fn parse_number(&mut self) -> BackendResult<JsonLiteralNode> {
267        let start = self.mark();
268        let mut source = String::new();
269        while matches!(
270            self.current_char(),
271            Some('-' | '+' | '.' | 'e' | 'E' | '0'..='9')
272        ) {
273            source.push(self.current_char().unwrap_or_default());
274            self.advance();
275        }
276        if source.is_empty() {
277            return Err(self.error("Expected a JSON number."));
278        }
279        if !self.starts_value_terminator() {
280            return Err(self.error("Invalid JSON number literal."));
281        }
282        let value: Value = serde_json::from_str(&source)
283            .map_err(|err| self.error(format!("Invalid JSON number literal: {err}")))?;
284        Ok(JsonLiteralNode {
285            span: self.span_from(start),
286            source,
287            value,
288        })
289    }
290
291    fn parse_object(&mut self) -> BackendResult<JsonObjectNode> {
292        let start = self.mark();
293        self.consume_char('{')?;
294        self.skip_whitespace();
295        let mut members = Vec::new();
296        if self.current_char() == Some('}') {
297            self.advance();
298            return Ok(JsonObjectNode {
299                span: self.span_from(start),
300                members,
301            });
302        }
303
304        loop {
305            let member_start = self.mark();
306            let key = self.parse_key()?;
307            self.skip_whitespace();
308            self.consume_char(':')?;
309            let value = self.parse_value()?;
310            members.push(JsonMemberNode {
311                span: self.span_from(member_start),
312                key,
313                value,
314            });
315            self.skip_whitespace();
316            if self.current_char() == Some('}') {
317                self.advance();
318                break;
319            }
320            self.consume_char(',')?;
321            self.skip_whitespace();
322        }
323
324        Ok(JsonObjectNode {
325            span: self.span_from(start),
326            members,
327        })
328    }
329
330    fn parse_key(&mut self) -> BackendResult<JsonKeyNode> {
331        self.skip_whitespace();
332        let start = self.mark();
333        if self.current_kind() == "interpolation" {
334            return Ok(JsonKeyNode {
335                span: self.span_from(start),
336                value: JsonKeyValue::Interpolation(self.consume_interpolation("key")?),
337            });
338        }
339        if self.current_char() != Some('"') {
340            return Err(self.error("JSON object keys must be quoted strings or interpolations."));
341        }
342        Ok(JsonKeyNode {
343            span: self.span_from(start),
344            value: JsonKeyValue::String(self.parse_string(true)?),
345        })
346    }
347
348    fn parse_array(&mut self) -> BackendResult<JsonArrayNode> {
349        let start = self.mark();
350        self.consume_char('[')?;
351        self.skip_whitespace();
352        let mut items = Vec::new();
353        if self.current_char() == Some(']') {
354            self.advance();
355            return Ok(JsonArrayNode {
356                span: self.span_from(start),
357                items,
358            });
359        }
360
361        loop {
362            items.push(self.parse_value()?);
363            self.skip_whitespace();
364            if self.current_char() == Some(']') {
365                self.advance();
366                break;
367            }
368            self.consume_char(',')?;
369            self.skip_whitespace();
370        }
371
372        Ok(JsonArrayNode {
373            span: self.span_from(start),
374            items,
375        })
376    }
377
378    fn parse_string(&mut self, quoted: bool) -> BackendResult<JsonStringNode> {
379        let start = self.mark();
380        if quoted {
381            self.consume_char('"')?;
382        }
383        let mut chunks = Vec::new();
384        let mut buffer = String::new();
385
386        loop {
387            if quoted && self.current_char() == Some('"') {
388                self.advance();
389                break;
390            }
391            if quoted && self.current_kind() == "eof" {
392                return Err(self.error("Unterminated JSON string."));
393            }
394            if self.current_kind() == "interpolation" {
395                self.flush_buffer(&mut buffer, &mut chunks);
396                chunks.push(JsonStringPart::Interpolation(
397                    self.consume_interpolation("string_fragment")?,
398                ));
399                continue;
400            }
401            if self.current_char().is_none() {
402                break;
403            }
404            if quoted && self.current_char() == Some('\\') {
405                buffer.push(self.parse_escape_sequence()?);
406                continue;
407            }
408            if quoted && self.current_char().is_some_and(|ch| ch < ' ') {
409                return Err(self.error("Control characters are not allowed in JSON strings."));
410            }
411
412            buffer.push(self.current_char().unwrap_or_default());
413            self.advance();
414            if !quoted && self.starts_value_terminator() {
415                break;
416            }
417        }
418
419        self.flush_buffer(&mut buffer, &mut chunks);
420        Ok(JsonStringNode {
421            span: self.span_from(start),
422            chunks,
423            quoted,
424        })
425    }
426
427    fn parse_escape_sequence(&mut self) -> BackendResult<char> {
428        self.consume_char('\\')?;
429        let escape_char = self
430            .current_char()
431            .ok_or_else(|| self.error("Incomplete JSON escape sequence."))?;
432        self.advance();
433        let mapped = match escape_char {
434            '"' => Some('"'),
435            '\\' => Some('\\'),
436            '/' => Some('/'),
437            'b' => Some('\u{0008}'),
438            'f' => Some('\u{000c}'),
439            'n' => Some('\n'),
440            'r' => Some('\r'),
441            't' => Some('\t'),
442            _ => None,
443        };
444        if let Some(value) = mapped {
445            return Ok(value);
446        }
447        if escape_char == 'u' {
448            let codepoint = self.parse_unicode_escape_value()?;
449            if (0xD800..=0xDBFF).contains(&codepoint) {
450                self.consume_char('\\')?;
451                self.consume_char('u')?;
452                let low = self.parse_unicode_escape_value()?;
453                if !(0xDC00..=0xDFFF).contains(&low) {
454                    return Err(self.error("Invalid JSON unicode escape."));
455                }
456                let combined = 0x10000 + (((codepoint - 0xD800) << 10) | (low - 0xDC00));
457                return char::from_u32(combined)
458                    .ok_or_else(|| self.error("Invalid JSON unicode escape."));
459            }
460            if (0xDC00..=0xDFFF).contains(&codepoint) {
461                return Err(self.error("Invalid JSON unicode escape."));
462            }
463            return char::from_u32(codepoint)
464                .ok_or_else(|| self.error("Invalid JSON unicode escape."));
465        }
466        Err(self.error("Invalid JSON escape sequence."))
467    }
468
469    fn parse_unicode_escape_value(&mut self) -> BackendResult<u32> {
470        let digits = self.collect_exact_chars(4)?;
471        u32::from_str_radix(&digits, 16).map_err(|_| self.error("Invalid JSON unicode escape."))
472    }
473
474    fn collect_exact_chars(&mut self, count: usize) -> BackendResult<String> {
475        let mut digits = String::new();
476        for _ in 0..count {
477            let ch = self
478                .current_char()
479                .ok_or_else(|| self.error("Unexpected end of JSON escape sequence."))?;
480            digits.push(ch);
481            self.advance();
482        }
483        Ok(digits)
484    }
485
486    fn flush_buffer(&self, buffer: &mut String, chunks: &mut Vec<JsonStringPart>) {
487        if buffer.is_empty() {
488            return;
489        }
490        chunks.push(JsonStringPart::Chunk(JsonStringChunkNode {
491            span: SourceSpan::point(0, 0),
492            value: std::mem::take(buffer),
493        }));
494    }
495
496    fn parse_promoted_string(
497        &mut self,
498        start: SourcePosition,
499        mut chunks: Vec<JsonStringPart>,
500    ) -> BackendResult<JsonStringNode> {
501        if chunks.is_empty() && self.starts_value_terminator() {
502            return Err(self.error("Expected a JSON value."));
503        }
504
505        let mut buffer = String::new();
506        let mut saw_interpolation = chunks
507            .iter()
508            .any(|chunk| matches!(chunk, JsonStringPart::Interpolation(_)));
509
510        while self.current_kind() != "eof" && !self.starts_value_terminator() {
511            if self.current_kind() == "interpolation" {
512                self.flush_buffer(&mut buffer, &mut chunks);
513                chunks.push(JsonStringPart::Interpolation(
514                    self.consume_interpolation("string_fragment")?,
515                ));
516                saw_interpolation = true;
517                continue;
518            }
519            if matches!(self.current_char(), Some('"' | '{' | '[' | ':')) {
520                return Err(self.error("Invalid promoted JSON fragment content."));
521            }
522            if let Some(ch) = self.current_char() {
523                buffer.push(ch);
524                self.advance();
525            } else {
526                break;
527            }
528        }
529
530        self.flush_buffer(&mut buffer, &mut chunks);
531        if !saw_interpolation {
532            return Err(self.error("Expected a JSON value."));
533        }
534        self.trim_trailing_fragment_whitespace(&mut chunks);
535        Ok(JsonStringNode {
536            span: self.span_from(start),
537            chunks,
538            quoted: false,
539        })
540    }
541
542    fn trim_trailing_fragment_whitespace(&self, chunks: &mut Vec<JsonStringPart>) {
543        while let Some(last) = chunks.last_mut() {
544            let JsonStringPart::Chunk(last_chunk) = last else {
545                return;
546            };
547            let trimmed = last_chunk.value.trim_end().to_owned();
548            if trimmed.is_empty() {
549                chunks.pop();
550                continue;
551            }
552            last_chunk.value = trimmed;
553            return;
554        }
555    }
556
557    fn starts_value_terminator(&self) -> bool {
558        let mut probe = self.index;
559        while matches!(self.items[probe].char(), Some(ch) if ch.is_whitespace()) {
560            probe += 1;
561        }
562        matches!(
563            &self.items[probe],
564            StreamItem::Eof { .. }
565                | StreamItem::Char {
566                    ch: ',' | ']' | '}',
567                    ..
568                }
569        )
570    }
571
572    fn consume_interpolation(&mut self, role: &str) -> BackendResult<JsonInterpolationNode> {
573        let (interpolation_index, span) = match self.current() {
574            StreamItem::Interpolation {
575                interpolation_index,
576                span,
577                ..
578            } => (*interpolation_index, span.clone()),
579            _ => return Err(self.error("Expected an interpolation.")),
580        };
581        self.advance();
582        Ok(JsonInterpolationNode {
583            span,
584            interpolation_index,
585            role: role.to_owned(),
586        })
587    }
588}
589
590pub fn parse_template_with_profile(
591    template: &TemplateInput,
592    _profile: JsonProfile,
593) -> BackendResult<JsonDocumentNode> {
594    // JSON only exposes RFC 8259 in this phase, but the parameter is kept so
595    // the backend stays aligned with the public profile-aware API shape.
596    JsonParser::new(template).parse()
597}
598
599pub fn parse_template(template: &TemplateInput) -> BackendResult<JsonDocumentNode> {
600    parse_template_with_profile(template, JsonProfile::default())
601}
602
603pub fn parse_validated_template_with_profile(
604    template: &TemplateInput,
605    profile: JsonProfile,
606) -> BackendResult<JsonDocumentNode> {
607    // JSON does not add format-specific post-parse validation yet. Keep the
608    // validated entry point aligned with the other backends so callers can rely
609    // on one API shape as backend-specific validation rules are introduced.
610    parse_template_with_profile(template, profile)
611}
612
613pub fn parse_validated_template(template: &TemplateInput) -> BackendResult<JsonDocumentNode> {
614    parse_validated_template_with_profile(template, JsonProfile::default())
615}
616
617pub fn validate_template_with_profile(
618    template: &TemplateInput,
619    profile: JsonProfile,
620) -> BackendResult<()> {
621    parse_validated_template_with_profile(template, profile).map(|_| ())
622}
623
624pub fn validate_template(template: &TemplateInput) -> BackendResult<()> {
625    validate_template_with_profile(template, JsonProfile::default())
626}
627
628pub fn check_template_with_profile(
629    template: &TemplateInput,
630    profile: JsonProfile,
631) -> BackendResult<()> {
632    validate_template_with_profile(template, profile)
633}
634
635pub fn check_template(template: &TemplateInput) -> BackendResult<()> {
636    check_template_with_profile(template, JsonProfile::default())
637}
638
639pub fn format_template_with_profile(
640    template: &TemplateInput,
641    profile: JsonProfile,
642) -> BackendResult<String> {
643    let document = parse_validated_template_with_profile(template, profile)?;
644    format_json_value(template, &document.value)
645}
646
647pub fn format_template(template: &TemplateInput) -> BackendResult<String> {
648    format_template_with_profile(template, JsonProfile::default())
649}
650
651pub fn normalize_document_with_profile(
652    value: &Value,
653    _profile: JsonProfile,
654) -> BackendResult<NormalizedStream> {
655    // Normalization is currently profile-invariant for JSON, but it remains
656    // profile-aware so future variants do not require another API break.
657    Ok(NormalizedStream::new(vec![NormalizedDocument::Value(
658        normalize_value(value)?,
659    )]))
660}
661
662pub fn normalize_document(value: &Value) -> BackendResult<NormalizedStream> {
663    normalize_document_with_profile(value, JsonProfile::default())
664}
665
666pub fn normalize_value(value: &Value) -> BackendResult<NormalizedValue> {
667    match value {
668        Value::Null => Ok(NormalizedValue::Null),
669        Value::Bool(value) => Ok(NormalizedValue::Bool(*value)),
670        Value::String(value) => Ok(NormalizedValue::String(value.clone())),
671        Value::Array(values) => values
672            .iter()
673            .map(normalize_value)
674            .collect::<BackendResult<Vec<_>>>()
675            .map(NormalizedValue::Sequence),
676        Value::Object(values) => values
677            .iter()
678            .map(|(key, value)| {
679                Ok(tstring_syntax::NormalizedEntry {
680                    key: NormalizedKey::String(key.clone()),
681                    value: normalize_value(value)?,
682                })
683            })
684            .collect::<BackendResult<Vec<_>>>()
685            .map(NormalizedValue::Mapping),
686        Value::Number(number) => normalize_number(number),
687    }
688}
689
690fn format_json_value(template: &TemplateInput, node: &JsonValueNode) -> BackendResult<String> {
691    match node {
692        JsonValueNode::String(node) => format_json_string(template, node),
693        JsonValueNode::Literal(node) => Ok(node.source.clone()),
694        JsonValueNode::Interpolation(node) => {
695            interpolation_raw_source(template, node.interpolation_index, &node.span, "JSON value")
696        }
697        JsonValueNode::Object(node) => {
698            let mut members = Vec::with_capacity(node.members.len());
699            for member in &node.members {
700                members.push(format!(
701                    "{}: {}",
702                    format_json_key(template, &member.key)?,
703                    format_json_value(template, &member.value)?
704                ));
705            }
706            Ok(format!("{{{}}}", members.join(", ")))
707        }
708        JsonValueNode::Array(node) => {
709            let items = node
710                .items
711                .iter()
712                .map(|item| format_json_value(template, item))
713                .collect::<BackendResult<Vec<_>>>()?;
714            Ok(format!("[{}]", items.join(", ")))
715        }
716    }
717}
718
719fn format_json_key(template: &TemplateInput, node: &JsonKeyNode) -> BackendResult<String> {
720    match &node.value {
721        JsonKeyValue::String(node) => format_json_string(template, node),
722        JsonKeyValue::Interpolation(node) => interpolation_raw_source(
723            template,
724            node.interpolation_index,
725            &node.span,
726            "JSON object key",
727        ),
728    }
729}
730
731fn format_json_string(template: &TemplateInput, node: &JsonStringNode) -> BackendResult<String> {
732    let mut rendered = String::from("\"");
733    for chunk in &node.chunks {
734        match chunk {
735            JsonStringPart::Chunk(chunk) => rendered.push_str(&escape_json_fragment(&chunk.value)),
736            JsonStringPart::Interpolation(node) => rendered.push_str(&interpolation_raw_source(
737                template,
738                node.interpolation_index,
739                &node.span,
740                "JSON string fragment",
741            )?),
742        }
743    }
744    rendered.push('"');
745    Ok(rendered)
746}
747
748fn escape_json_fragment(text: &str) -> String {
749    let mut escaped = serde_json::to_string(text).unwrap_or_else(|_| "\"\"".to_owned());
750    escaped.remove(0);
751    escaped.pop();
752    escaped
753}
754
755fn interpolation_raw_source(
756    template: &TemplateInput,
757    interpolation_index: usize,
758    span: &SourceSpan,
759    context: &str,
760) -> BackendResult<String> {
761    template
762        .interpolation_raw_source(interpolation_index)
763        .map(str::to_owned)
764        .ok_or_else(|| {
765            let expression = template.interpolation(interpolation_index).map_or_else(
766                || format!("slot {interpolation_index}"),
767                |value| value.expression_label().to_owned(),
768            );
769            BackendError::semantic_at(
770                "json.format",
771                format!(
772                    "Cannot format {context} interpolation {expression:?} without raw source text."
773                ),
774                Some(span.clone()),
775            )
776        })
777}
778
779fn normalize_number(number: &serde_json::Number) -> BackendResult<NormalizedValue> {
780    let source = number.to_string();
781    if source.contains(['.', 'e', 'E']) {
782        return source
783            .parse::<f64>()
784            .map(NormalizedFloat::finite)
785            .map(NormalizedValue::Float)
786            .map_err(|err| {
787                BackendError::semantic(format!(
788                    "Validated JSON number {source} could not be normalized as a finite float: {err}"
789                ))
790            });
791    }
792
793    source.parse().map(NormalizedValue::Integer).map_err(|err| {
794        BackendError::semantic(format!(
795            "Validated JSON number {source} could not be normalized as an exact integer: {err}"
796        ))
797    })
798}
799
800#[cfg(test)]
801mod tests {
802    use super::{JsonKeyValue, JsonStringPart, JsonValueNode, parse_template};
803    use pyo3::prelude::*;
804    use serde_json::{Map, Number, Value, json};
805    use tstring_pyo3_bindings::{extract_template, json::render_document};
806    use tstring_syntax::{BackendError, BackendResult, ErrorKind};
807
808    fn parse_rendered_json(text: &str) -> BackendResult<Value> {
809        serde_json::from_str(text).map_err(|err| {
810            BackendError::parse(format!(
811                "Rendered JSON could not be reparsed during test verification: {err}"
812            ))
813        })
814    }
815
816    #[test]
817    fn parses_json_structure() {
818        Python::with_gil(|py| {
819            let module = PyModule::from_code(
820                py,
821                pyo3::ffi::c_str!(
822                    "from string.templatelib import Template\nleft='prefix'\nright='suffix'\ntemplate=t'{{\"prefix-{left}\": {left}-{right}}}'\n"
823                ),
824                pyo3::ffi::c_str!("test_json.py"),
825                pyo3::ffi::c_str!("test_json"),
826            )
827            .unwrap();
828            let template = module.getattr("template").unwrap();
829            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
830            let document = parse_template(&template).unwrap();
831            let JsonValueNode::Object(object) = document.value else {
832                panic!("expected object");
833            };
834            assert_eq!(object.members.len(), 1);
835            let JsonKeyValue::String(key) = &object.members[0].key.value else {
836                panic!("expected interpolated string key");
837            };
838            assert_eq!(key.chunks.len(), 2);
839            assert!(matches!(key.chunks[1], JsonStringPart::Interpolation(_)));
840            let JsonValueNode::String(value) = &object.members[0].value else {
841                panic!("expected promoted string value");
842            };
843            assert_eq!(value.chunks.len(), 3);
844            assert!(matches!(value.chunks[0], JsonStringPart::Interpolation(_)));
845        });
846    }
847
848    #[test]
849    fn renders_nested_collections_and_validates() {
850        Python::with_gil(|py| {
851            let module = PyModule::from_code(
852                py,
853                pyo3::ffi::c_str!(
854                    "items=[1, {'name': 'Ada'}]\nname='Ada'\ntemplate=t'{{\"items\": {items}, \"message\": \"hi-{name}\"}}'\n"
855                ),
856                pyo3::ffi::c_str!("test_json_render.py"),
857                pyo3::ffi::c_str!("test_json_render"),
858            )
859            .unwrap();
860            let template = module.getattr("template").unwrap();
861            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
862            let document = parse_template(&template).unwrap();
863            let rendered = render_document(py, &document).unwrap();
864
865            assert_eq!(rendered.data["message"], Value::String("hi-Ada".to_owned()));
866            assert_eq!(rendered.data["items"][0], Value::Number(Number::from(1)));
867            assert_eq!(
868                rendered.data["items"][1]["name"],
869                Value::String("Ada".to_owned())
870            );
871        });
872    }
873
874    #[test]
875    fn renders_top_level_scalar_text() {
876        Python::with_gil(|py| {
877            let module = PyModule::from_code(
878                py,
879                pyo3::ffi::c_str!(
880                    "template=t'{1}'\nbool_template=t'{True}'\nnull_template=t'{None}'\n"
881                ),
882                pyo3::ffi::c_str!("test_json_scalar.py"),
883                pyo3::ffi::c_str!("test_json_scalar"),
884            )
885            .unwrap();
886            let template = module.getattr("template").unwrap();
887            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
888            let document = parse_template(&template).unwrap();
889            let rendered = render_document(py, &document).unwrap();
890
891            assert_eq!(rendered.text, "1");
892            assert_eq!(rendered.data, Value::Number(Number::from(1)));
893
894            let bool_template = module.getattr("bool_template").unwrap();
895            let bool_template = extract_template(py, &bool_template, "json_t/json_t_str").unwrap();
896            let document = parse_template(&bool_template).unwrap();
897            let rendered = render_document(py, &document).unwrap();
898            assert_eq!(rendered.text, "true");
899            assert_eq!(rendered.data, Value::Bool(true));
900
901            let null_template = module.getattr("null_template").unwrap();
902            let null_template = extract_template(py, &null_template, "json_t/json_t_str").unwrap();
903            let document = parse_template(&null_template).unwrap();
904            let rendered = render_document(py, &document).unwrap();
905            assert_eq!(rendered.text, "null");
906            assert_eq!(rendered.data, Value::Null);
907        });
908    }
909
910    #[test]
911    fn renders_quoted_key_fragments_and_promoted_fragments() {
912        Python::with_gil(|py| {
913            let module = PyModule::from_code(
914                py,
915                pyo3::ffi::c_str!(
916                    "left='prefix'\nright='suffix'\nrows=[{'name': 'one'}, {'name': 'two'}]\nquoted_key=t'{{\"{left}-{right}\": {1}, \"value\": {True}}}'\nfragment=t'{{\"label\": {left}-{right}}}'\npromoted=t'{rows}'\n"
917                ),
918                pyo3::ffi::c_str!("test_json_fragments.py"),
919                pyo3::ffi::c_str!("test_json_fragments"),
920            )
921            .unwrap();
922
923            let quoted_key = module.getattr("quoted_key").unwrap();
924            let quoted_key = extract_template(py, &quoted_key, "json_t/json_t_str").unwrap();
925            let rendered = render_document(py, &parse_template(&quoted_key).unwrap()).unwrap();
926            assert_eq!(rendered.data, json!({"prefix-suffix": 1, "value": true}));
927
928            let fragment = module.getattr("fragment").unwrap();
929            let fragment = extract_template(py, &fragment, "json_t/json_t_str").unwrap();
930            let rendered = render_document(py, &parse_template(&fragment).unwrap()).unwrap();
931            assert_eq!(rendered.text, "{\"label\": \"prefix-suffix\"}");
932            assert_eq!(rendered.data, json!({"label": "prefix-suffix"}));
933
934            let promoted = module.getattr("promoted").unwrap();
935            let promoted = extract_template(py, &promoted, "json_t/json_t_str").unwrap();
936            let rendered = render_document(py, &parse_template(&promoted).unwrap()).unwrap();
937            assert_eq!(rendered.text, r#"[{"name": "one"}, {"name": "two"}]"#);
938            assert_eq!(rendered.data, json!([{"name": "one"}, {"name": "two"}]));
939        });
940    }
941
942    #[test]
943    fn rejects_non_string_key_interpolation() {
944        Python::with_gil(|py| {
945            let module = PyModule::from_code(
946                py,
947                pyo3::ffi::c_str!("key=1\ntemplate=t'{{{key}: 1}}'\n"),
948                pyo3::ffi::c_str!("test_json_error.py"),
949                pyo3::ffi::c_str!("test_json_error"),
950            )
951            .unwrap();
952            let template = module.getattr("template").unwrap();
953            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
954            let document = parse_template(&template).unwrap();
955            let err = match render_document(py, &document) {
956                Ok(_) => panic!("expected JSON render failure"),
957                Err(err) => err,
958            };
959
960            assert_eq!(err.kind, ErrorKind::Unrepresentable);
961            assert!(err.message.contains("JSON object keys must be str"));
962        });
963    }
964
965    #[test]
966    fn rejects_unrepresentable_render_value_families() {
967        Python::with_gil(|py| {
968            let module = PyModule::from_code(
969                py,
970                pyo3::ffi::c_str!(
971                    "import math\nbad_map={1: 'x'}\nbad_nan=math.nan\nbad_set={1, 2}\nmap_template=t'{bad_map}'\nnan_template=t'{bad_nan}'\nset_template=t'{bad_set}'\n"
972                ),
973                pyo3::ffi::c_str!("test_json_unrepresentable.py"),
974                pyo3::ffi::c_str!("test_json_unrepresentable"),
975            )
976            .unwrap();
977
978            for (name, expected) in [
979                ("map_template", "JSON object keys must be str"),
980                ("nan_template", "non-finite float"),
981                ("set_template", "could not be rendered as JSON"),
982            ] {
983                let template = module.getattr(name).unwrap();
984                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
985                let document = parse_template(&template).unwrap();
986                let err = match render_document(py, &document) {
987                    Ok(_) => panic!("expected JSON render failure"),
988                    Err(err) => err,
989                };
990                assert_eq!(err.kind, ErrorKind::Unrepresentable);
991                assert!(err.message.contains(expected), "{name}: {}", err.message);
992            }
993        });
994    }
995
996    #[test]
997    fn parses_unicode_surrogate_pairs() {
998        Python::with_gil(|py| {
999            let module = PyModule::from_code(
1000                py,
1001                pyo3::ffi::c_str!("template=t'\"\\\\uD834\\\\uDD1E\"'\n"),
1002                pyo3::ffi::c_str!("test_json_unicode.py"),
1003                pyo3::ffi::c_str!("test_json_unicode"),
1004            )
1005            .unwrap();
1006            let template = module.getattr("template").unwrap();
1007            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1008            let document = parse_template(&template).unwrap();
1009            let rendered = render_document(py, &document).unwrap();
1010
1011            assert_eq!(rendered.data, Value::String("𝄞".to_owned()));
1012        });
1013    }
1014
1015    #[test]
1016    fn parses_numbers_and_escape_sequences() {
1017        Python::with_gil(|py| {
1018            let module = PyModule::from_code(
1019                py,
1020                pyo3::ffi::c_str!(
1021                    "template=t'{{\"int\": -0, \"exp\": 1.5e2, \"escapes\": \"\\\\b\\\\f\\\\n\\\\r\\\\t\\\\/\", \"unicode\": \"\\\\u00DF\\\\u6771\\\\uD834\\\\uDD1E\"}}'\n"
1022                ),
1023                pyo3::ffi::c_str!("test_json_numbers.py"),
1024                pyo3::ffi::c_str!("test_json_numbers"),
1025            )
1026            .unwrap();
1027            let template = module.getattr("template").unwrap();
1028            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1029            let document = parse_template(&template).unwrap();
1030            let rendered = render_document(py, &document).unwrap();
1031
1032            assert_eq!(
1033                rendered.data["exp"],
1034                Value::Number(Number::from_f64(150.0).unwrap())
1035            );
1036            assert_eq!(rendered.data["unicode"], Value::String("ß東𝄞".to_owned()));
1037            assert_eq!(
1038                rendered.data["escapes"],
1039                Value::String("\u{0008}\u{000c}\n\r\t/".to_owned())
1040            );
1041        });
1042    }
1043
1044    #[test]
1045    fn parses_whitespace_and_escaped_solidus_cases() {
1046        Python::with_gil(|py| {
1047            let module = PyModule::from_code(
1048                py,
1049                pyo3::ffi::c_str!(
1050                    "from string.templatelib import Template\ntop_bool_ws=Template(' \\n true \\t ')\ntop_null_ws=Template(' \\r\\n null \\n')\nempty_string=t'\"\"'\nempty_object=Template('{ \\n\\t }')\nempty_array=Template('[ \\n\\t ]')\narray_empty_values=Template('[\"\", 0, false, null, {}, []]')\nempty_object_in_array=Template('[{}, {\"a\": []}]')\ntop_level_empty_object_ws=Template(' \\n { } \\t ')\nescaped_controls=t'\"\\\\b\\\\f\\\\n\\\\r\\\\t\"'\nescaped_solidus=t'\"\\\\/\"'\nescaped_backslash=t'\"\\\\\\\\\"'\nunicode_backslash_escape=t'\"\\\\u005C\"'\nreverse_solidus_u=t'\"\\\\u005C/\"'\nescaped_quote_backslash=t'\"\\\\\\\"\\\\\\\\\"'\nescaped_null_and_unit_separator=t'\"\\\\u0000\\\\u001f\"'\nnested_upper_unicode=t'\"\\\\u00DF\\\\u6771\"'\nunicode_line_sep=t'\"\\\\u2028\"'\nunicode_para_sep=t'\"\\\\u2029\"'\narray_with_line_sep=t'[\"\\\\u2028\", \"\\\\u2029\"]'\nunicode_escapes_array=Template('[\"\\\\u005C\", \"\\\\/\", \"\\\\u00DF\"]')\nunicode_mix_nested_obj=Template('{\"x\": {\"a\": \"\\\\u005C\", \"b\": \"\\\\u00DF\", \"c\": \"\\\\u2029\"}}')\nnested_unicode_object_array=Template('{\"a\": [{\"b\": \"\\\\u005C\", \"c\": \"\\\\u00DF\"}]}')\nupper_unicode_mix_array=Template('[\"\\\\u00DF\", \"\\\\u6771\", \"\\\\u2028\"]')\nescaped_slash_backslash_quote=t'\"\\\\/\\\\\\\\\\\\\\\"\"'\nescaped_reverse_solidus_solidus=t'\"\\\\\\\\/\"'\nnested_escaped_mix=Template('{\"x\":\"\\\\b\\\\u2028\\\\u2029\\\\/\"}')\nupper_exp=t'1E2'\nupper_exp_plus=t'1E+2'\nupper_exp_negative=t'-1E+2'\nupper_exp_zero_fraction=t'0E+0'\nupper_zero_negative_exp=t'-0E-0'\nnested_upper_exp=Template('{\"value\": 1E+2}')\nneg_exp_zero=t'-1e-0'\nupper_exp_negative_zero=t'1E-0'\nexp_with_fraction_zero=t'1.0e-0'\nnegative_zero_exp_upper=t'-0E0'\nnested_bool_null_mix=Template('{\"v\": [true, null, false, {\"x\": 1}]}')\nkeyword_array=Template('[true,false,null]')\nempty_name_nested_keywords=Template('{\"\": [null, true, false]}')\nnested_empty_mix=Template('{\"a\": [{}, [], \"\", 0, false, null]}')\nnested_empty_collections_mix=Template('{\"a\": {\"b\": []}, \"c\": [{}, []]}')\narray_nested_mixed_scalars=Template('[{\"a\": []}, {\"b\": {}}, \"\", 0, false, null]')\nnested_negative_exp_mix=Template('{\"x\":[-1E-2,0,\"\",{\"y\":[null]}]}')\nmixed_nested_keywords=Template('{\"a\": [true, false, null], \"b\": {\"c\": -1e-0}}')\nnested_number_combo=Template('{\"a\": [0, -0, -0.0, 1e0, -1E-0]}')\nnested_number_whitespace=Template('{\"a\": [ 0 , -0 , 1.5E-2 ] }')\nnested_empty_names=Template('{\"\": {\"\": []}}')\nnested_empty_name_array=Template('{\"\": [\"\", {\"\": 0}]}')\nnested_nulls=Template('{\"a\": null, \"b\": [null, {\"c\": null}]}')\nnested_top_ws=Template('\\r\\n {\"a\": [1, {\"b\": \"c\"}], \"\": \"\"} \\n')\ntop_ws_string=Template('\\n\\r\\t \"x\" \\n')\nzero_fraction_exp=t'0.0e+0'\nnested=Template('[\\n {\"a\": 1, \"b\": [true, false, null]}\\n]')\n"
1051                ),
1052                pyo3::ffi::c_str!("test_json_whitespace.py"),
1053                pyo3::ffi::c_str!("test_json_whitespace"),
1054            )
1055            .unwrap();
1056
1057            let top_bool_ws = module.getattr("top_bool_ws").unwrap();
1058            let top_bool_ws = extract_template(py, &top_bool_ws, "json_t/json_t_str").unwrap();
1059            let rendered = render_document(py, &parse_template(&top_bool_ws).unwrap()).unwrap();
1060            assert_eq!(rendered.data, Value::Bool(true));
1061
1062            let top_null_ws = module.getattr("top_null_ws").unwrap();
1063            let top_null_ws = extract_template(py, &top_null_ws, "json_t/json_t_str").unwrap();
1064            let rendered = render_document(py, &parse_template(&top_null_ws).unwrap()).unwrap();
1065            assert_eq!(rendered.data, Value::Null);
1066
1067            let empty_string = module.getattr("empty_string").unwrap();
1068            let empty_string = extract_template(py, &empty_string, "json_t/json_t_str").unwrap();
1069            let rendered = render_document(py, &parse_template(&empty_string).unwrap()).unwrap();
1070            assert_eq!(rendered.data, Value::String(String::new()));
1071
1072            let empty_object = module.getattr("empty_object").unwrap();
1073            let empty_object = extract_template(py, &empty_object, "json_t/json_t_str").unwrap();
1074            let rendered = render_document(py, &parse_template(&empty_object).unwrap()).unwrap();
1075            assert_eq!(rendered.data, Value::Object(Default::default()));
1076
1077            let empty_array = module.getattr("empty_array").unwrap();
1078            let empty_array = extract_template(py, &empty_array, "json_t/json_t_str").unwrap();
1079            let rendered = render_document(py, &parse_template(&empty_array).unwrap()).unwrap();
1080            assert_eq!(rendered.data, Value::Array(Vec::new()));
1081
1082            let escaped_controls = module.getattr("escaped_controls").unwrap();
1083            let escaped_controls =
1084                extract_template(py, &escaped_controls, "json_t/json_t_str").unwrap();
1085            let rendered =
1086                render_document(py, &parse_template(&escaped_controls).unwrap()).unwrap();
1087            assert_eq!(
1088                rendered.data,
1089                Value::String("\u{0008}\u{000c}\n\r\t".to_owned())
1090            );
1091
1092            let escaped_solidus = module.getattr("escaped_solidus").unwrap();
1093            let escaped_solidus =
1094                extract_template(py, &escaped_solidus, "json_t/json_t_str").unwrap();
1095            let rendered = render_document(py, &parse_template(&escaped_solidus).unwrap()).unwrap();
1096            assert_eq!(rendered.data, Value::String("/".to_owned()));
1097
1098            let escaped_backslash = module.getattr("escaped_backslash").unwrap();
1099            let escaped_backslash =
1100                extract_template(py, &escaped_backslash, "json_t/json_t_str").unwrap();
1101            let rendered =
1102                render_document(py, &parse_template(&escaped_backslash).unwrap()).unwrap();
1103            assert_eq!(rendered.data, Value::String("\\".to_owned()));
1104
1105            let unicode_backslash_escape = module.getattr("unicode_backslash_escape").unwrap();
1106            let unicode_backslash_escape =
1107                extract_template(py, &unicode_backslash_escape, "json_t/json_t_str").unwrap();
1108            let rendered =
1109                render_document(py, &parse_template(&unicode_backslash_escape).unwrap()).unwrap();
1110            assert_eq!(rendered.data, Value::String("\\".to_owned()));
1111
1112            let reverse_solidus_u = module.getattr("reverse_solidus_u").unwrap();
1113            let reverse_solidus_u =
1114                extract_template(py, &reverse_solidus_u, "json_t/json_t_str").unwrap();
1115            let rendered =
1116                render_document(py, &parse_template(&reverse_solidus_u).unwrap()).unwrap();
1117            assert_eq!(rendered.data, Value::String("\\/".to_owned()));
1118
1119            let escaped_quote_backslash = module.getattr("escaped_quote_backslash").unwrap();
1120            let escaped_quote_backslash =
1121                extract_template(py, &escaped_quote_backslash, "json_t/json_t_str").unwrap();
1122            let rendered =
1123                render_document(py, &parse_template(&escaped_quote_backslash).unwrap()).unwrap();
1124            assert_eq!(rendered.data, Value::String("\"\\".to_owned()));
1125
1126            let escaped_null_and_unit_separator =
1127                module.getattr("escaped_null_and_unit_separator").unwrap();
1128            let escaped_null_and_unit_separator =
1129                extract_template(py, &escaped_null_and_unit_separator, "json_t/json_t_str")
1130                    .unwrap();
1131            let rendered = render_document(
1132                py,
1133                &parse_template(&escaped_null_and_unit_separator).unwrap(),
1134            )
1135            .unwrap();
1136            assert_eq!(rendered.data, Value::String("\u{0000}\u{001f}".to_owned()));
1137
1138            let nested_upper_unicode = module.getattr("nested_upper_unicode").unwrap();
1139            let nested_upper_unicode =
1140                extract_template(py, &nested_upper_unicode, "json_t/json_t_str").unwrap();
1141            let rendered =
1142                render_document(py, &parse_template(&nested_upper_unicode).unwrap()).unwrap();
1143            assert_eq!(rendered.data, Value::String("ß東".to_owned()));
1144
1145            let unicode_line_sep = module.getattr("unicode_line_sep").unwrap();
1146            let unicode_line_sep =
1147                extract_template(py, &unicode_line_sep, "json_t/json_t_str").unwrap();
1148            let rendered =
1149                render_document(py, &parse_template(&unicode_line_sep).unwrap()).unwrap();
1150            assert_eq!(rendered.data, Value::String("\u{2028}".to_owned()));
1151
1152            let unicode_para_sep = module.getattr("unicode_para_sep").unwrap();
1153            let unicode_para_sep =
1154                extract_template(py, &unicode_para_sep, "json_t/json_t_str").unwrap();
1155            let rendered =
1156                render_document(py, &parse_template(&unicode_para_sep).unwrap()).unwrap();
1157            assert_eq!(rendered.data, Value::String("\u{2029}".to_owned()));
1158
1159            let array_with_line_sep = module.getattr("array_with_line_sep").unwrap();
1160            let array_with_line_sep =
1161                extract_template(py, &array_with_line_sep, "json_t/json_t_str").unwrap();
1162            let rendered =
1163                render_document(py, &parse_template(&array_with_line_sep).unwrap()).unwrap();
1164            assert_eq!(rendered.data.as_array().expect("array").len(), 2);
1165            assert_eq!(rendered.data[0], Value::String("\u{2028}".to_owned()));
1166            assert_eq!(rendered.data[1], Value::String("\u{2029}".to_owned()));
1167
1168            let unicode_escapes_array = module.getattr("unicode_escapes_array").unwrap();
1169            let unicode_escapes_array =
1170                extract_template(py, &unicode_escapes_array, "json_t/json_t_str").unwrap();
1171            let rendered =
1172                render_document(py, &parse_template(&unicode_escapes_array).unwrap()).unwrap();
1173            let values = rendered.data.as_array().expect("array");
1174            assert_eq!(values[0], Value::String("\\".to_owned()));
1175            assert_eq!(values[1], Value::String("/".to_owned()));
1176            assert_eq!(values[2], Value::String("ß".to_owned()));
1177
1178            let unicode_mix_nested_obj = module.getattr("unicode_mix_nested_obj").unwrap();
1179            let unicode_mix_nested_obj =
1180                extract_template(py, &unicode_mix_nested_obj, "json_t/json_t_str").unwrap();
1181            let rendered =
1182                render_document(py, &parse_template(&unicode_mix_nested_obj).unwrap()).unwrap();
1183            assert_eq!(rendered.data["x"]["a"], Value::String("\\".to_owned()));
1184            assert_eq!(rendered.data["x"]["b"], Value::String("ß".to_owned()));
1185            assert_eq!(
1186                rendered.data["x"]["c"],
1187                Value::String("\u{2029}".to_owned())
1188            );
1189
1190            let nested_unicode_object_array =
1191                module.getattr("nested_unicode_object_array").unwrap();
1192            let nested_unicode_object_array =
1193                extract_template(py, &nested_unicode_object_array, "json_t/json_t_str").unwrap();
1194            let rendered =
1195                render_document(py, &parse_template(&nested_unicode_object_array).unwrap())
1196                    .unwrap();
1197            assert_eq!(rendered.data["a"][0]["b"], Value::String("\\".to_owned()));
1198            assert_eq!(rendered.data["a"][0]["c"], Value::String("ß".to_owned()));
1199
1200            let upper_unicode_mix_array = module.getattr("upper_unicode_mix_array").unwrap();
1201            let upper_unicode_mix_array =
1202                extract_template(py, &upper_unicode_mix_array, "json_t/json_t_str").unwrap();
1203            let rendered =
1204                render_document(py, &parse_template(&upper_unicode_mix_array).unwrap()).unwrap();
1205            let values = rendered.data.as_array().expect("array");
1206            assert_eq!(values[0], Value::String("ß".to_owned()));
1207            assert_eq!(values[1], Value::String("東".to_owned()));
1208            assert_eq!(values[2], Value::String("\u{2028}".to_owned()));
1209
1210            let escaped_slash_backslash_quote =
1211                module.getattr("escaped_slash_backslash_quote").unwrap();
1212            let escaped_slash_backslash_quote =
1213                extract_template(py, &escaped_slash_backslash_quote, "json_t/json_t_str").unwrap();
1214            let rendered =
1215                render_document(py, &parse_template(&escaped_slash_backslash_quote).unwrap())
1216                    .unwrap();
1217            assert_eq!(rendered.data, Value::String("/\\\"".to_owned()));
1218
1219            let array_empty_values = module.getattr("array_empty_values").unwrap();
1220            let array_empty_values =
1221                extract_template(py, &array_empty_values, "json_t/json_t_str").unwrap();
1222            let rendered =
1223                render_document(py, &parse_template(&array_empty_values).unwrap()).unwrap();
1224            let values = rendered.data.as_array().expect("array");
1225            assert_eq!(values.len(), 6);
1226            assert_eq!(values[0], Value::String(String::new()));
1227            assert_eq!(values[1], Value::Number(Number::from(0)));
1228            assert_eq!(values[2], Value::Bool(false));
1229            assert_eq!(values[3], Value::Null);
1230            assert_eq!(values[4], Value::Object(Map::new()));
1231            assert_eq!(values[5], Value::Array(Vec::new()));
1232
1233            let empty_object_in_array = module.getattr("empty_object_in_array").unwrap();
1234            let empty_object_in_array =
1235                extract_template(py, &empty_object_in_array, "json_t/json_t_str").unwrap();
1236            let rendered =
1237                render_document(py, &parse_template(&empty_object_in_array).unwrap()).unwrap();
1238            let values = rendered.data.as_array().expect("array");
1239            assert_eq!(values.len(), 2);
1240            assert_eq!(values[0], Value::Object(Map::new()));
1241            assert_eq!(values[1]["a"], Value::Array(Vec::new()));
1242
1243            let top_level_empty_object_ws = module.getattr("top_level_empty_object_ws").unwrap();
1244            let top_level_empty_object_ws =
1245                extract_template(py, &top_level_empty_object_ws, "json_t/json_t_str").unwrap();
1246            let rendered =
1247                render_document(py, &parse_template(&top_level_empty_object_ws).unwrap()).unwrap();
1248            assert_eq!(rendered.data, Value::Object(Map::new()));
1249
1250            let nested_escaped_mix = module.getattr("nested_escaped_mix").unwrap();
1251            let nested_escaped_mix =
1252                extract_template(py, &nested_escaped_mix, "json_t/json_t_str").unwrap();
1253            let rendered =
1254                render_document(py, &parse_template(&nested_escaped_mix).unwrap()).unwrap();
1255            assert_eq!(
1256                rendered.data["x"],
1257                Value::String("\u{0008}\u{2028}\u{2029}/".to_owned())
1258            );
1259
1260            let escaped_reverse_solidus_solidus =
1261                module.getattr("escaped_reverse_solidus_solidus").unwrap();
1262            let escaped_reverse_solidus_solidus =
1263                extract_template(py, &escaped_reverse_solidus_solidus, "json_t/json_t_str")
1264                    .unwrap();
1265            let rendered = render_document(
1266                py,
1267                &parse_template(&escaped_reverse_solidus_solidus).unwrap(),
1268            )
1269            .unwrap();
1270            assert_eq!(rendered.data, Value::String("\\/".to_owned()));
1271
1272            let upper_exp = module.getattr("upper_exp").unwrap();
1273            let upper_exp = extract_template(py, &upper_exp, "json_t/json_t_str").unwrap();
1274            let rendered = render_document(py, &parse_template(&upper_exp).unwrap()).unwrap();
1275            assert_eq!(
1276                rendered.data,
1277                Value::Number(Number::from_f64(100.0).unwrap())
1278            );
1279
1280            let upper_exp_plus = module.getattr("upper_exp_plus").unwrap();
1281            let upper_exp_plus =
1282                extract_template(py, &upper_exp_plus, "json_t/json_t_str").unwrap();
1283            let rendered = render_document(py, &parse_template(&upper_exp_plus).unwrap()).unwrap();
1284            assert_eq!(
1285                rendered.data,
1286                Value::Number(Number::from_f64(100.0).unwrap())
1287            );
1288
1289            let upper_exp_negative = module.getattr("upper_exp_negative").unwrap();
1290            let upper_exp_negative =
1291                extract_template(py, &upper_exp_negative, "json_t/json_t_str").unwrap();
1292            let rendered =
1293                render_document(py, &parse_template(&upper_exp_negative).unwrap()).unwrap();
1294            assert_eq!(
1295                rendered.data,
1296                Value::Number(Number::from_f64(-100.0).unwrap())
1297            );
1298
1299            let upper_exp_zero_fraction = module.getattr("upper_exp_zero_fraction").unwrap();
1300            let upper_exp_zero_fraction =
1301                extract_template(py, &upper_exp_zero_fraction, "json_t/json_t_str").unwrap();
1302            let rendered =
1303                render_document(py, &parse_template(&upper_exp_zero_fraction).unwrap()).unwrap();
1304            assert_eq!(rendered.data, Value::Number(Number::from_f64(0.0).unwrap()));
1305
1306            let upper_zero_negative_exp = module.getattr("upper_zero_negative_exp").unwrap();
1307            let upper_zero_negative_exp =
1308                extract_template(py, &upper_zero_negative_exp, "json_t/json_t_str").unwrap();
1309            let rendered =
1310                render_document(py, &parse_template(&upper_zero_negative_exp).unwrap()).unwrap();
1311            assert_eq!(
1312                rendered.data,
1313                Value::Number(Number::from_f64(-0.0).unwrap())
1314            );
1315
1316            let nested_upper_exp = module.getattr("nested_upper_exp").unwrap();
1317            let nested_upper_exp =
1318                extract_template(py, &nested_upper_exp, "json_t/json_t_str").unwrap();
1319            let rendered =
1320                render_document(py, &parse_template(&nested_upper_exp).unwrap()).unwrap();
1321            assert_eq!(
1322                rendered.data["value"],
1323                Value::Number(Number::from_f64(100.0).unwrap())
1324            );
1325
1326            let neg_exp_zero = module.getattr("neg_exp_zero").unwrap();
1327            let neg_exp_zero = extract_template(py, &neg_exp_zero, "json_t/json_t_str").unwrap();
1328            let rendered = render_document(py, &parse_template(&neg_exp_zero).unwrap()).unwrap();
1329            assert_eq!(
1330                rendered.data,
1331                Value::Number(Number::from_f64(-1.0).unwrap())
1332            );
1333
1334            let upper_exp_negative_zero = module.getattr("upper_exp_negative_zero").unwrap();
1335            let upper_exp_negative_zero =
1336                extract_template(py, &upper_exp_negative_zero, "json_t/json_t_str").unwrap();
1337            let rendered =
1338                render_document(py, &parse_template(&upper_exp_negative_zero).unwrap()).unwrap();
1339            assert_eq!(rendered.data, Value::Number(Number::from_f64(1.0).unwrap()));
1340
1341            let exp_with_fraction_zero = module.getattr("exp_with_fraction_zero").unwrap();
1342            let exp_with_fraction_zero =
1343                extract_template(py, &exp_with_fraction_zero, "json_t/json_t_str").unwrap();
1344            let rendered =
1345                render_document(py, &parse_template(&exp_with_fraction_zero).unwrap()).unwrap();
1346            assert_eq!(rendered.data, Value::Number(Number::from_f64(1.0).unwrap()));
1347
1348            let negative_zero_exp_upper = module.getattr("negative_zero_exp_upper").unwrap();
1349            let negative_zero_exp_upper =
1350                extract_template(py, &negative_zero_exp_upper, "json_t/json_t_str").unwrap();
1351            let rendered =
1352                render_document(py, &parse_template(&negative_zero_exp_upper).unwrap()).unwrap();
1353            assert_eq!(
1354                rendered.data,
1355                Value::Number(Number::from_f64(-0.0).unwrap())
1356            );
1357
1358            let nested_negative_exp_mix = module.getattr("nested_negative_exp_mix").unwrap();
1359            let nested_negative_exp_mix =
1360                extract_template(py, &nested_negative_exp_mix, "json_t/json_t_str").unwrap();
1361            let rendered =
1362                render_document(py, &parse_template(&nested_negative_exp_mix).unwrap()).unwrap();
1363            assert_eq!(
1364                rendered.data["x"][0],
1365                Value::Number(Number::from_f64(-0.01).unwrap())
1366            );
1367            assert_eq!(rendered.data["x"][1], Value::Number(Number::from(0)));
1368            assert_eq!(rendered.data["x"][2], Value::String(String::new()));
1369            assert_eq!(rendered.data["x"][3]["y"][0], Value::Null);
1370
1371            let mixed_nested_keywords = module.getattr("mixed_nested_keywords").unwrap();
1372            let mixed_nested_keywords =
1373                extract_template(py, &mixed_nested_keywords, "json_t/json_t_str").unwrap();
1374            let rendered =
1375                render_document(py, &parse_template(&mixed_nested_keywords).unwrap()).unwrap();
1376            assert_eq!(rendered.data["a"][0], Value::Bool(true));
1377            assert_eq!(rendered.data["a"][1], Value::Bool(false));
1378            assert_eq!(rendered.data["a"][2], Value::Null);
1379            assert_eq!(
1380                rendered.data["b"]["c"],
1381                Value::Number(Number::from_f64(-1.0).unwrap())
1382            );
1383
1384            let nested_empty_names = module.getattr("nested_empty_names").unwrap();
1385            let nested_empty_names =
1386                extract_template(py, &nested_empty_names, "json_t/json_t_str").unwrap();
1387            let rendered =
1388                render_document(py, &parse_template(&nested_empty_names).unwrap()).unwrap();
1389            assert_eq!(rendered.data[""][""], Value::Array(Vec::new()));
1390
1391            let nested_empty_name_array = module.getattr("nested_empty_name_array").unwrap();
1392            let nested_empty_name_array =
1393                extract_template(py, &nested_empty_name_array, "json_t/json_t_str").unwrap();
1394            let rendered =
1395                render_document(py, &parse_template(&nested_empty_name_array).unwrap()).unwrap();
1396            assert_eq!(rendered.data[""][0], Value::String(String::new()));
1397            assert_eq!(rendered.data[""][1][""], Value::Number(Number::from(0)));
1398
1399            let nested_nulls = module.getattr("nested_nulls").unwrap();
1400            let nested_nulls = extract_template(py, &nested_nulls, "json_t/json_t_str").unwrap();
1401            let rendered = render_document(py, &parse_template(&nested_nulls).unwrap()).unwrap();
1402            assert_eq!(rendered.data["a"], Value::Null);
1403            assert_eq!(rendered.data["b"][0], Value::Null);
1404            assert_eq!(rendered.data["b"][1]["c"], Value::Null);
1405
1406            let nested_top_ws = module.getattr("nested_top_ws").unwrap();
1407            let nested_top_ws = extract_template(py, &nested_top_ws, "json_t/json_t_str").unwrap();
1408            let rendered = render_document(py, &parse_template(&nested_top_ws).unwrap()).unwrap();
1409            assert_eq!(rendered.data["a"][1]["b"], Value::String("c".to_owned()));
1410            assert_eq!(rendered.data[""], Value::String(String::new()));
1411
1412            let top_ws_string = module.getattr("top_ws_string").unwrap();
1413            let top_ws_string = extract_template(py, &top_ws_string, "json_t/json_t_str").unwrap();
1414            let rendered = render_document(py, &parse_template(&top_ws_string).unwrap()).unwrap();
1415            assert_eq!(rendered.data, Value::String("x".to_owned()));
1416
1417            let nested_number_whitespace = module.getattr("nested_number_whitespace").unwrap();
1418            let nested_number_whitespace =
1419                extract_template(py, &nested_number_whitespace, "json_t/json_t_str").unwrap();
1420            let rendered =
1421                render_document(py, &parse_template(&nested_number_whitespace).unwrap()).unwrap();
1422            let values = rendered.data["a"].as_array().expect("array");
1423            assert_eq!(values.len(), 3);
1424            assert_eq!(values[0], Value::Number(Number::from(0)));
1425            assert_eq!(values[1], Value::Number(Number::from_f64(-0.0).unwrap()));
1426            assert_eq!(values[2], Value::Number(Number::from_f64(0.015).unwrap()));
1427
1428            let nested_number_combo = module.getattr("nested_number_combo").unwrap();
1429            let nested_number_combo =
1430                extract_template(py, &nested_number_combo, "json_t/json_t_str").unwrap();
1431            let rendered =
1432                render_document(py, &parse_template(&nested_number_combo).unwrap()).unwrap();
1433            let values = rendered.data["a"].as_array().expect("array");
1434            assert_eq!(values[0], Value::Number(Number::from(0)));
1435            assert_eq!(values[1], Value::Number(Number::from_f64(-0.0).unwrap()));
1436            assert_eq!(values[2], Value::Number(Number::from_f64(-0.0).unwrap()));
1437            assert_eq!(values[3], Value::Number(Number::from_f64(1.0).unwrap()));
1438            assert_eq!(values[4], Value::Number(Number::from_f64(-1.0).unwrap()));
1439
1440            let nested_bool_null_mix = module.getattr("nested_bool_null_mix").unwrap();
1441            let nested_bool_null_mix =
1442                extract_template(py, &nested_bool_null_mix, "json_t/json_t_str").unwrap();
1443            let rendered =
1444                render_document(py, &parse_template(&nested_bool_null_mix).unwrap()).unwrap();
1445            assert_eq!(rendered.data["v"][0], Value::Bool(true));
1446            assert_eq!(rendered.data["v"][1], Value::Null);
1447            assert_eq!(rendered.data["v"][2], Value::Bool(false));
1448            assert_eq!(rendered.data["v"][3]["x"], Value::Number(Number::from(1)));
1449
1450            let keyword_array = module.getattr("keyword_array").unwrap();
1451            let keyword_array = extract_template(py, &keyword_array, "json_t/json_t_str").unwrap();
1452            let rendered = render_document(py, &parse_template(&keyword_array).unwrap()).unwrap();
1453            let values = rendered.data.as_array().expect("array");
1454            assert_eq!(values[0], Value::Bool(true));
1455            assert_eq!(values[1], Value::Bool(false));
1456            assert_eq!(values[2], Value::Null);
1457
1458            let empty_name_nested_keywords = module.getattr("empty_name_nested_keywords").unwrap();
1459            let empty_name_nested_keywords =
1460                extract_template(py, &empty_name_nested_keywords, "json_t/json_t_str").unwrap();
1461            let rendered =
1462                render_document(py, &parse_template(&empty_name_nested_keywords).unwrap()).unwrap();
1463            let values = rendered.data[""].as_array().expect("array");
1464            assert_eq!(values[0], Value::Null);
1465            assert_eq!(values[1], Value::Bool(true));
1466            assert_eq!(values[2], Value::Bool(false));
1467
1468            let nested_empty_mix = module.getattr("nested_empty_mix").unwrap();
1469            let nested_empty_mix =
1470                extract_template(py, &nested_empty_mix, "json_t/json_t_str").unwrap();
1471            let rendered =
1472                render_document(py, &parse_template(&nested_empty_mix).unwrap()).unwrap();
1473            let values = rendered.data["a"].as_array().expect("array");
1474            assert_eq!(values[0], Value::Object(Map::new()));
1475            assert_eq!(values[1], Value::Array(Vec::new()));
1476            assert_eq!(values[2], Value::String(String::new()));
1477            assert_eq!(values[3], Value::Number(Number::from(0)));
1478            assert_eq!(values[4], Value::Bool(false));
1479            assert_eq!(values[5], Value::Null);
1480
1481            let nested_empty_collections_mix =
1482                module.getattr("nested_empty_collections_mix").unwrap();
1483            let nested_empty_collections_mix =
1484                extract_template(py, &nested_empty_collections_mix, "json_t/json_t_str").unwrap();
1485            let rendered =
1486                render_document(py, &parse_template(&nested_empty_collections_mix).unwrap())
1487                    .unwrap();
1488            assert_eq!(rendered.data["a"]["b"], Value::Array(Vec::new()));
1489            assert_eq!(rendered.data["c"][0], Value::Object(Map::new()));
1490            assert_eq!(rendered.data["c"][1], Value::Array(Vec::new()));
1491
1492            let array_nested_mixed_scalars = module.getattr("array_nested_mixed_scalars").unwrap();
1493            let array_nested_mixed_scalars =
1494                extract_template(py, &array_nested_mixed_scalars, "json_t/json_t_str").unwrap();
1495            let rendered =
1496                render_document(py, &parse_template(&array_nested_mixed_scalars).unwrap()).unwrap();
1497            let values = rendered.data.as_array().expect("array");
1498            assert_eq!(values[0]["a"], Value::Array(Vec::new()));
1499            assert_eq!(values[1]["b"], Value::Object(Map::new()));
1500            assert_eq!(values[2], Value::String(String::new()));
1501            assert_eq!(values[3], Value::Number(Number::from(0)));
1502            assert_eq!(values[4], Value::Bool(false));
1503            assert_eq!(values[5], Value::Null);
1504
1505            let zero_fraction_exp = module.getattr("zero_fraction_exp").unwrap();
1506            let zero_fraction_exp =
1507                extract_template(py, &zero_fraction_exp, "json_t/json_t_str").unwrap();
1508            let rendered =
1509                render_document(py, &parse_template(&zero_fraction_exp).unwrap()).unwrap();
1510            assert_eq!(rendered.data, Value::Number(Number::from_f64(0.0).unwrap()));
1511
1512            let nested = module.getattr("nested").unwrap();
1513            let nested = extract_template(py, &nested, "json_t/json_t_str").unwrap();
1514            let rendered = render_document(py, &parse_template(&nested).unwrap()).unwrap();
1515            assert_eq!(rendered.data.as_array().expect("array").len(), 1);
1516            assert_eq!(rendered.data[0]["a"], Value::Number(Number::from(1)));
1517            assert_eq!(rendered.data[0]["b"][0], Value::Bool(true));
1518            assert_eq!(rendered.data[0]["b"][1], Value::Bool(false));
1519            assert_eq!(rendered.data[0]["b"][2], Value::Null);
1520        });
1521    }
1522
1523    #[test]
1524    fn rejects_invalid_unicode_and_control_sequences() {
1525        Python::with_gil(|py| {
1526            let module = PyModule::from_code(
1527                py,
1528                pyo3::ffi::c_str!(
1529                    "from string.templatelib import Template\nincomplete=Template('\"\\\\u12\"')\ncontrol=Template('\"a\\nb\"')\nlow=Template('\"\\\\uDD1E\"')\nexp=Template('1e+')\nexp2=Template('1e')\ndot=Template('1.')\nminus=Template('-')\nobject_exp=Template('{\"a\": 1e+}')\nobject_dot=Template('{\"a\": 1.}')\ntrailing_true=Template('true false')\ntrailing_string=Template('\"a\" \"b\"')\nextra_close=Template('[1]]')\nobject_extra_close=Template('{\"a\":1}}')\ndouble_trailing_close=Template('{\"a\": [1]}]')\nmissing_array_comma=Template('[1 2]')\nmissing_object_comma=Template('{\"a\":1 \"b\":2}')\nnested_missing_object_array_comma=Template('{\"a\": [1 2]}')\nextra_array_comma=Template('[1,,2]')\nleading_comma_array=Template('[,1]')\nmissing_value=Template('{\"a\":,1}')\nmissing_key=Template('{,}')\nmissing_colon=Template('{\"a\" 1}')\ntrailing_object_comma=Template('{\"a\":1,}')\ntrailing_array_comma=Template('[1,2,]')\nnested_trailing_object_comma=Template('[{\"a\":1,}]')\nplus_number=Template('+1')\nleading_decimal=Template('.1')\nleading_zero=Template('00')\nnegative_leading_zero=Template('-01')\nleading_zero_exp=Template('01e0')\nnegative_leading_zero_exp=Template('-01e0')\nmissing_exp_digits_minus=Template('1e-')\nobject_missing_exp_digits_minus=Template('{\"x\": 1e-}')\nobject_negative_leading_zero=Template('{\"a\": -01}')\narray_double_value_no_comma=Template('[true false]')\nnested_missing_comma_bool=Template('{\"a\": [true false]}')\narray_true_number_no_comma=Template('[true 1]')\narray_false_object_no_comma=Template('[false {\"a\":1}]')\nnested_missing_comma_obj_after_null=Template('[null {\"a\":1}]')\nobject_double_value_no_comma=Template('{\"a\": true false}')\nobject_keyword_number_no_comma=Template('{\"a\": true 1}')\ntruncated_true=Template('tru')\ntruncated_false=Template('fals')\ntruncated_null=Template('nul')\nobject_truncated_true=Template('{\"a\": tru}')\nobject_truncated_false=Template('{\"a\": fals}')\nobject_truncated_null=Template('{\"a\": nul}')\narray_truncated_true=Template('[tru]')\narray_truncated_false=Template('[fals]')\nnested_array_truncated_true=Template('{\"a\": [tru]}')\nnested_array_truncated_false=Template('{\"a\": [fals]}')\narray_truncated_null=Template('[nul]')\narray_bad_null_case=Template('[nulL]')\nnested_array_bad_null_case=Template('{\"a\": [nulL]}')\nnested_object_truncated_null=Template('[{\"a\": nul}]')\nkeyword_prefix=Template('[truee]')\nbad_true_case=Template('truE')\narray_bad_true_case=Template('[truE]')\nbad_false_case=Template('falsE')\nbad_null_case=Template('nulL')\nobject_bad_true_case=Template('{\"a\": truE}')\nobject_bad_false_case=Template('{\"a\": falsE}')\nobject_bad_null_case=Template('{\"a\": nulL}')\narray_bad_false_case=Template('[falsE]')\narray_missing_comma_after_string=Template('[\"a\" true]')\nobject_missing_comma_after_null=Template('{\"a\": null \"b\": 1}')\ndouble_decimal_point=Template('1.2.3')\nobject_bad_leading_zero=Template('{\"a\": 00}')\nnested_extra_close=Template('[{\"a\":1}]]')\n"
1530                ),
1531                pyo3::ffi::c_str!("test_json_invalid_unicode.py"),
1532                pyo3::ffi::c_str!("test_json_invalid_unicode"),
1533            )
1534            .unwrap();
1535
1536            for (name, expected) in [
1537                ("incomplete", "Unexpected end of JSON escape sequence"),
1538                ("control", "Control characters are not allowed"),
1539                ("low", "Invalid JSON unicode escape"),
1540                ("exp", "Invalid JSON number literal"),
1541                ("exp2", "Invalid JSON number literal"),
1542                ("dot", "Invalid JSON number literal"),
1543                ("minus", "Invalid JSON number literal"),
1544                ("object_exp", "Invalid JSON number literal"),
1545                ("object_dot", "Invalid JSON number literal"),
1546                ("trailing_true", "Expected a JSON value"),
1547                (
1548                    "trailing_string",
1549                    "Unexpected trailing content in JSON template",
1550                ),
1551                (
1552                    "extra_close",
1553                    "Unexpected trailing content in JSON template",
1554                ),
1555                (
1556                    "object_extra_close",
1557                    "Unexpected trailing content in JSON template",
1558                ),
1559                (
1560                    "double_trailing_close",
1561                    "Unexpected trailing content in JSON template",
1562                ),
1563                ("missing_array_comma", "Invalid JSON number literal"),
1564                ("missing_object_comma", "Invalid JSON number literal"),
1565                (
1566                    "nested_missing_object_array_comma",
1567                    "Invalid JSON number literal",
1568                ),
1569                ("extra_array_comma", "Expected a JSON value"),
1570                ("leading_comma_array", "Expected a JSON value"),
1571                ("missing_value", "Expected a JSON value"),
1572                (
1573                    "missing_key",
1574                    "JSON object keys must be quoted strings or interpolations",
1575                ),
1576                ("missing_colon", "Expected ':' in JSON template"),
1577                (
1578                    "trailing_object_comma",
1579                    "JSON object keys must be quoted strings or interpolations",
1580                ),
1581                ("trailing_array_comma", "Expected a JSON value"),
1582                (
1583                    "nested_trailing_object_comma",
1584                    "quoted strings or interpolations",
1585                ),
1586                ("plus_number", "Expected a JSON value"),
1587                ("leading_decimal", "Expected a JSON value"),
1588                ("leading_zero", "Invalid JSON number literal"),
1589                ("negative_leading_zero", "Invalid JSON number literal"),
1590                ("leading_zero_exp", "Invalid JSON number literal"),
1591                ("negative_leading_zero_exp", "Invalid JSON number literal"),
1592                ("missing_exp_digits_minus", "Invalid JSON number literal"),
1593                (
1594                    "object_missing_exp_digits_minus",
1595                    "Invalid JSON number literal",
1596                ),
1597                (
1598                    "object_negative_leading_zero",
1599                    "Invalid JSON number literal",
1600                ),
1601                ("array_double_value_no_comma", "Expected a JSON value"),
1602                ("nested_missing_comma_bool", "Expected a JSON value"),
1603                ("array_true_number_no_comma", "Expected a JSON value"),
1604                (
1605                    "array_false_object_no_comma",
1606                    "Invalid promoted JSON fragment content",
1607                ),
1608                (
1609                    "nested_missing_comma_obj_after_null",
1610                    "Invalid promoted JSON fragment content",
1611                ),
1612                ("object_double_value_no_comma", "Expected a JSON value"),
1613                ("object_keyword_number_no_comma", "Expected a JSON value"),
1614                ("truncated_true", "Expected a JSON value"),
1615                ("truncated_false", "Expected a JSON value"),
1616                ("truncated_null", "Expected a JSON value"),
1617                ("object_truncated_true", "Expected a JSON value"),
1618                ("object_truncated_false", "Expected a JSON value"),
1619                ("object_truncated_null", "Expected a JSON value"),
1620                ("array_truncated_true", "Expected a JSON value"),
1621                ("array_truncated_false", "Expected a JSON value"),
1622                ("nested_array_truncated_true", "Expected a JSON value"),
1623                ("nested_array_truncated_false", "Expected a JSON value"),
1624                ("array_truncated_null", "Expected a JSON value"),
1625                ("nested_object_truncated_null", "Expected a JSON value"),
1626                ("keyword_prefix", "Expected a JSON value"),
1627                ("bad_true_case", "Expected a JSON value"),
1628                ("array_bad_true_case", "Expected a JSON value"),
1629                ("bad_false_case", "Expected a JSON value"),
1630                ("bad_null_case", "Expected a JSON value"),
1631                ("object_bad_true_case", "Expected a JSON value"),
1632                ("object_bad_false_case", "Expected a JSON value"),
1633                ("object_bad_null_case", "Expected a JSON value"),
1634                ("array_bad_false_case", "Expected a JSON value"),
1635                ("array_bad_null_case", "Expected a JSON value"),
1636                ("nested_array_bad_null_case", "Expected a JSON value"),
1637                (
1638                    "array_missing_comma_after_string",
1639                    "Expected ',' in JSON template",
1640                ),
1641                (
1642                    "object_missing_comma_after_null",
1643                    "Invalid promoted JSON fragment content",
1644                ),
1645                ("double_decimal_point", "Invalid JSON number literal"),
1646                ("object_bad_leading_zero", "Invalid JSON number literal"),
1647                (
1648                    "nested_extra_close",
1649                    "Unexpected trailing content in JSON template",
1650                ),
1651            ] {
1652                let template = module.getattr(name).unwrap();
1653                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1654                let err = match parse_template(&template) {
1655                    Ok(_) => panic!("expected JSON parse failure for {name}"),
1656                    Err(err) => err,
1657                };
1658                assert_eq!(err.kind, ErrorKind::Parse);
1659                assert!(err.message.contains(expected), "{name}: {}", err.message);
1660            }
1661        });
1662    }
1663
1664    #[test]
1665    fn rejects_structural_invalid_message_families() {
1666        Python::with_gil(|py| {
1667            let module = PyModule::from_code(
1668                py,
1669                pyo3::ffi::c_str!(
1670                    "from string.templatelib import Template\nmissing_comma=Template('[\"a\" true]')\ntrailing_comma=Template('{\"a\":1,}')\ninvalid_fragment=Template('[null {\"a\":1}]')\nunexpected_trailing=Template('{\"a\":1}}')\n"
1671                ),
1672                pyo3::ffi::c_str!("test_json_structural_invalids.py"),
1673                pyo3::ffi::c_str!("test_json_structural_invalids"),
1674            )
1675            .unwrap();
1676
1677            for (name, expected) in [
1678                ("missing_comma", "Expected ',' in JSON template"),
1679                (
1680                    "trailing_comma",
1681                    "JSON object keys must be quoted strings or interpolations",
1682                ),
1683                ("invalid_fragment", "Invalid promoted JSON fragment content"),
1684                (
1685                    "unexpected_trailing",
1686                    "Unexpected trailing content in JSON template",
1687                ),
1688            ] {
1689                let template = module.getattr(name).unwrap();
1690                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1691                let err = parse_template(&template).expect_err("expected JSON parse failure");
1692                assert_eq!(err.kind, ErrorKind::Parse);
1693                assert!(err.message.contains(expected), "{name}: {}", err.message);
1694            }
1695        });
1696    }
1697
1698    #[test]
1699    fn rejects_keyword_truncation_and_collection_separator_errors() {
1700        Python::with_gil(|py| {
1701            let module = PyModule::from_code(
1702                py,
1703                pyo3::ffi::c_str!(
1704                    "from string.templatelib import Template\nbad_true=Template('[tru]')\nbad_false=Template('{\"a\": falsE}')\nbad_null=Template('[nulL]')\nmissing_comma_array=Template('[null {\"a\": 1}]')\ntrailing_array_comma=Template('[1,2,]')\ntrailing_object_comma=Template('{\"a\":1,}')\n"
1705                ),
1706                pyo3::ffi::c_str!("test_json_invalid_keywords.py"),
1707                pyo3::ffi::c_str!("test_json_invalid_keywords"),
1708            )
1709            .unwrap();
1710
1711            for (name, expected) in [
1712                ("bad_true", "Expected a JSON value"),
1713                ("bad_false", "Expected a JSON value"),
1714                ("bad_null", "Expected a JSON value"),
1715                (
1716                    "missing_comma_array",
1717                    "Invalid promoted JSON fragment content",
1718                ),
1719                ("trailing_array_comma", "Expected a JSON value"),
1720                ("trailing_object_comma", "quoted strings or interpolations"),
1721            ] {
1722                let template = module.getattr(name).unwrap();
1723                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1724                let err = parse_template(&template).expect_err("expected JSON parse failure");
1725                assert_eq!(err.kind, ErrorKind::Parse);
1726                assert!(err.message.contains(expected), "{name}: {}", err.message);
1727            }
1728        });
1729    }
1730
1731    #[test]
1732    fn rejects_additional_number_and_trailing_content_families() {
1733        Python::with_gil(|py| {
1734            let module = PyModule::from_code(
1735                py,
1736                pyo3::ffi::c_str!(
1737                    "from string.templatelib import Template\nleading_zero_exp=Template('01e0')\nleading_zero_exp_negative=Template('-01e0')\nmissing_exp_digits=Template('1e-')\nembedded_missing_exp_digits=Template('{\"x\": 1e-}')\ndouble_sign_number=Template('-+1')\nleading_plus_minus=Template('+-1')\nbad_exp_plus_minus=Template('1e+-1')\nbad_exp_minus_plus=Template('1e-+1')\nextra_decimal=Template('1.2.3')\narray_space_number=Template('[1 2]')\nobject_space_number=Template('{\"a\":1 \"b\":2}')\ntruee=Template('[truee]')\ntrue_then_number=Template('[true 1]')\nobject_true_then_number=Template('{\"a\": true 1}')\nfalse_fragment=Template('[false {\"a\":1}]')\narray_trailing_object=Template('{\"a\": [1]}]')\nobject_trailing_array=Template('[{\"a\":1}]]')\ndeep_object_trailing=Template('{\"a\": {\"b\": 1}}}')\n"
1738                ),
1739                pyo3::ffi::c_str!("test_json_additional_parse_errors.py"),
1740                pyo3::ffi::c_str!("test_json_additional_parse_errors"),
1741            )
1742            .unwrap();
1743
1744            for (name, expected) in [
1745                ("leading_zero_exp", "Invalid JSON number literal"),
1746                ("leading_zero_exp_negative", "Invalid JSON number literal"),
1747                ("missing_exp_digits", "Invalid JSON number literal"),
1748                ("embedded_missing_exp_digits", "Invalid JSON number literal"),
1749                ("double_sign_number", "Invalid JSON number literal"),
1750                ("leading_plus_minus", "Expected a JSON value"),
1751                ("bad_exp_plus_minus", "Invalid JSON number literal"),
1752                ("bad_exp_minus_plus", "Invalid JSON number literal"),
1753                ("extra_decimal", "Invalid JSON number literal"),
1754                ("array_space_number", "Invalid JSON number literal"),
1755                ("object_space_number", "Invalid JSON number literal"),
1756                ("truee", "Expected a JSON value"),
1757                ("true_then_number", "Expected a JSON value"),
1758                ("object_true_then_number", "Expected a JSON value"),
1759                ("false_fragment", "Invalid promoted JSON fragment content"),
1760                (
1761                    "array_trailing_object",
1762                    "Unexpected trailing content in JSON template",
1763                ),
1764                (
1765                    "object_trailing_array",
1766                    "Unexpected trailing content in JSON template",
1767                ),
1768                (
1769                    "deep_object_trailing",
1770                    "Unexpected trailing content in JSON template",
1771                ),
1772            ] {
1773                let template = module.getattr(name).unwrap();
1774                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1775                let err = parse_template(&template).expect_err("expected JSON parse failure");
1776                assert_eq!(err.kind, ErrorKind::Parse);
1777                assert!(err.message.contains(expected), "{name}: {}", err.message);
1778            }
1779        });
1780    }
1781
1782    #[test]
1783    fn renders_keyword_and_empty_name_collection_shapes() {
1784        Python::with_gil(|py| {
1785            let module = PyModule::from_code(
1786                py,
1787                pyo3::ffi::c_str!(
1788                    "from string.templatelib import Template\nkeyword_array=Template('[true,false,null]')\nempty_name_nested_keywords=Template('{\"\": [null, true, false]}')\nnested_empty_mix=Template('{\"a\": [{}, [], \"\", 0, false, null]}')\narray_nested_mixed_scalars=Template('[{\"a\": []}, {\"b\": {}}, \"\", 0, false, null]')\n"
1789                ),
1790                pyo3::ffi::c_str!("test_json_keyword_empty_shapes.py"),
1791                pyo3::ffi::c_str!("test_json_keyword_empty_shapes"),
1792            )
1793            .unwrap();
1794
1795            let keyword_array = module.getattr("keyword_array").unwrap();
1796            let keyword_array = extract_template(py, &keyword_array, "json_t/json_t_str").unwrap();
1797            let rendered = render_document(py, &parse_template(&keyword_array).unwrap()).unwrap();
1798            assert_eq!(rendered.text, "[true, false, null]");
1799            assert_eq!(rendered.data, json!([true, false, null]));
1800
1801            let empty_name_nested_keywords = module.getattr("empty_name_nested_keywords").unwrap();
1802            let empty_name_nested_keywords =
1803                extract_template(py, &empty_name_nested_keywords, "json_t/json_t_str").unwrap();
1804            let rendered =
1805                render_document(py, &parse_template(&empty_name_nested_keywords).unwrap()).unwrap();
1806            assert_eq!(rendered.text, "{\"\": [null, true, false]}");
1807            assert_eq!(rendered.data, json!({"": [null, true, false]}));
1808
1809            let nested_empty_mix = module.getattr("nested_empty_mix").unwrap();
1810            let nested_empty_mix =
1811                extract_template(py, &nested_empty_mix, "json_t/json_t_str").unwrap();
1812            let rendered =
1813                render_document(py, &parse_template(&nested_empty_mix).unwrap()).unwrap();
1814            assert_eq!(rendered.data, json!({"a": [{}, [], "", 0, false, null]}));
1815
1816            let array_nested_mixed_scalars = module.getattr("array_nested_mixed_scalars").unwrap();
1817            let array_nested_mixed_scalars =
1818                extract_template(py, &array_nested_mixed_scalars, "json_t/json_t_str").unwrap();
1819            let rendered =
1820                render_document(py, &parse_template(&array_nested_mixed_scalars).unwrap()).unwrap();
1821            assert_eq!(
1822                rendered.data,
1823                json!([{"a": []}, {"b": {}}, "", 0, false, null])
1824            );
1825        });
1826    }
1827
1828    #[test]
1829    fn renders_top_level_whitespace_and_nested_number_shapes() {
1830        Python::with_gil(|py| {
1831            let module = PyModule::from_code(
1832                py,
1833                pyo3::ffi::c_str!(
1834                    "from string.templatelib import Template\ntop_ws_string=Template('\\n\\r\\t \"x\" \\n')\nnested_top_ws=Template('\\r\\n {\"a\": [1, {\"b\": \"c\"}], \"\": \"\"} \\n')\nnested_number_whitespace=Template('{\"a\": [ 0 , -0 , 1.5E-2 ] }')\n"
1835                ),
1836                pyo3::ffi::c_str!("test_json_whitespace_shapes.py"),
1837                pyo3::ffi::c_str!("test_json_whitespace_shapes"),
1838            )
1839            .unwrap();
1840
1841            let top_ws_string = module.getattr("top_ws_string").unwrap();
1842            let top_ws_string = extract_template(py, &top_ws_string, "json_t/json_t_str").unwrap();
1843            let rendered = render_document(py, &parse_template(&top_ws_string).unwrap()).unwrap();
1844            assert_eq!(rendered.text, "\"x\"");
1845            assert_eq!(rendered.data, json!("x"));
1846
1847            let nested_top_ws = module.getattr("nested_top_ws").unwrap();
1848            let nested_top_ws = extract_template(py, &nested_top_ws, "json_t/json_t_str").unwrap();
1849            let rendered = render_document(py, &parse_template(&nested_top_ws).unwrap()).unwrap();
1850            assert_eq!(rendered.text, "{\"a\": [1, {\"b\": \"c\"}], \"\": \"\"}");
1851            assert_eq!(rendered.data, json!({"a": [1, {"b": "c"}], "": ""}));
1852
1853            let nested_number_whitespace = module.getattr("nested_number_whitespace").unwrap();
1854            let nested_number_whitespace =
1855                extract_template(py, &nested_number_whitespace, "json_t/json_t_str").unwrap();
1856            let rendered =
1857                render_document(py, &parse_template(&nested_number_whitespace).unwrap()).unwrap();
1858            assert_eq!(rendered.text, "{\"a\": [0, -0, 1.5E-2]}");
1859            assert_eq!(
1860                rendered.data,
1861                serde_json::from_str::<Value>("{\"a\": [0, -0, 1.5E-2]}").unwrap()
1862            );
1863        });
1864    }
1865
1866    #[test]
1867    fn renders_end_to_end_supported_positions_text_and_data() {
1868        Python::with_gil(|py| {
1869            let module = PyModule::from_code(
1870                py,
1871                pyo3::ffi::c_str!(
1872                    "key='user'\nleft='prefix'\nright='suffix'\npayload={'enabled': True, 'count': 2}\ntemplate=t'''\n{{\n  {key}: {payload},\n  \"prefix-{left}\": \"item-{right}\",\n  \"label\": {left}-{right}\n}}\n'''\n"
1873                ),
1874                pyo3::ffi::c_str!("test_json_end_to_end_positions.py"),
1875                pyo3::ffi::c_str!("test_json_end_to_end_positions"),
1876            )
1877            .unwrap();
1878
1879            let template = module.getattr("template").unwrap();
1880            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1881            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1882            assert_eq!(
1883                rendered.text,
1884                "{\"user\": {\"enabled\": true, \"count\": 2}, \"prefix-prefix\": \"item-suffix\", \"label\": \"prefix-suffix\"}"
1885            );
1886            assert_eq!(
1887                rendered.data,
1888                json!({
1889                    "user": {"enabled": true, "count": 2},
1890                    "prefix-prefix": "item-suffix",
1891                    "label": "prefix-suffix",
1892                })
1893            );
1894        });
1895    }
1896
1897    #[test]
1898    fn renders_rfc_8259_image_example_text_and_data() {
1899        Python::with_gil(|py| {
1900            let module = PyModule::from_code(
1901                py,
1902                pyo3::ffi::c_str!(
1903                    "template=t'''{{\n  \"Image\": {{\n    \"Width\": 800,\n    \"Height\": 600,\n    \"Title\": \"View from 15th Floor\",\n    \"Thumbnail\": {{\n      \"Url\": \"http://www.example.com/image/481989943\",\n      \"Height\": 125,\n      \"Width\": 100\n    }},\n    \"Animated\": false,\n    \"IDs\": [116, 943, 234, 38793]\n  }}\n}}'''\n"
1904                ),
1905                pyo3::ffi::c_str!("test_json_rfc_8259_image_example.py"),
1906                pyo3::ffi::c_str!("test_json_rfc_8259_image_example"),
1907            )
1908            .unwrap();
1909
1910            let template = module.getattr("template").unwrap();
1911            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1912            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1913            assert_eq!(
1914                rendered.text,
1915                "{\"Image\": {\"Width\": 800, \"Height\": 600, \"Title\": \"View from 15th Floor\", \"Thumbnail\": {\"Url\": \"http://www.example.com/image/481989943\", \"Height\": 125, \"Width\": 100}, \"Animated\": false, \"IDs\": [116, 943, 234, 38793]}}"
1916            );
1917            assert_eq!(
1918                rendered.data,
1919                json!({
1920                    "Image": {
1921                        "Width": 800,
1922                        "Height": 600,
1923                        "Title": "View from 15th Floor",
1924                        "Thumbnail": {
1925                            "Url": "http://www.example.com/image/481989943",
1926                            "Height": 125,
1927                            "Width": 100,
1928                        },
1929                        "Animated": false,
1930                        "IDs": [116, 943, 234, 38793],
1931                    }
1932                })
1933            );
1934        });
1935    }
1936
1937    #[test]
1938    fn renders_rfc_8259_value_examples_text_and_data() {
1939        Python::with_gil(|py| {
1940            let module = PyModule::from_code(
1941                py,
1942                pyo3::ffi::c_str!(
1943                    "array=t'''[\n  {{\n     \"precision\": \"zip\",\n     \"Latitude\":  37.7668,\n     \"Longitude\": -122.3959,\n     \"Address\":   \"\",\n     \"City\":      \"SAN FRANCISCO\",\n     \"State\":     \"CA\",\n     \"Zip\":       \"94107\",\n     \"Country\":   \"US\"\n  }},\n  {{\n     \"precision\": \"zip\",\n     \"Latitude\":  37.371991,\n     \"Longitude\": -122.026020,\n     \"Address\":   \"\",\n     \"City\":      \"SUNNYVALE\",\n     \"State\":     \"CA\",\n     \"Zip\":       \"94085\",\n     \"Country\":   \"US\"\n  }}\n]'''\nstring=t'\"Hello world!\"'\nnumber=t'42'\nboolean=t'true'\n"
1944                ),
1945                pyo3::ffi::c_str!("test_json_rfc_8259_value_examples.py"),
1946                pyo3::ffi::c_str!("test_json_rfc_8259_value_examples"),
1947            )
1948            .unwrap();
1949
1950            let array = module.getattr("array").unwrap();
1951            let array = extract_template(py, &array, "json_t/json_t_str").unwrap();
1952            let rendered = render_document(py, &parse_template(&array).unwrap()).unwrap();
1953            assert_eq!(
1954                rendered.text,
1955                "[{\"precision\": \"zip\", \"Latitude\": 37.7668, \"Longitude\": -122.3959, \"Address\": \"\", \"City\": \"SAN FRANCISCO\", \"State\": \"CA\", \"Zip\": \"94107\", \"Country\": \"US\"}, {\"precision\": \"zip\", \"Latitude\": 37.371991, \"Longitude\": -122.026020, \"Address\": \"\", \"City\": \"SUNNYVALE\", \"State\": \"CA\", \"Zip\": \"94085\", \"Country\": \"US\"}]"
1956            );
1957            assert_eq!(
1958                rendered.data,
1959                json!([
1960                    {
1961                        "precision": "zip",
1962                        "Latitude": 37.7668,
1963                        "Longitude": -122.3959,
1964                        "Address": "",
1965                        "City": "SAN FRANCISCO",
1966                        "State": "CA",
1967                        "Zip": "94107",
1968                        "Country": "US",
1969                    },
1970                    {
1971                        "precision": "zip",
1972                        "Latitude": 37.371991,
1973                        "Longitude": -122.026020,
1974                        "Address": "",
1975                        "City": "SUNNYVALE",
1976                        "State": "CA",
1977                        "Zip": "94085",
1978                        "Country": "US",
1979                    }
1980                ])
1981            );
1982
1983            for (name, expected_text, expected_value) in [
1984                ("string", "\"Hello world!\"", json!("Hello world!")),
1985                ("number", "42", json!(42)),
1986                ("boolean", "true", json!(true)),
1987            ] {
1988                let template = module.getattr(name).unwrap();
1989                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1990                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1991                assert_eq!(rendered.text, expected_text, "{name}");
1992                assert_eq!(rendered.data, expected_value, "{name}");
1993            }
1994        });
1995    }
1996
1997    #[test]
1998    fn renders_unicode_and_escape_mix_shapes() {
1999        Python::with_gil(|py| {
2000            let module = PyModule::from_code(
2001                py,
2002                pyo3::ffi::c_str!(
2003                    "unicode_array=t'[\"\\\\u2028\", \"\\\\u2029\", \"\\\\u00DF\"]'\nescape_object=t'{{\"x\":\"\\\\b\\\\u2028\\\\u2029\\\\/\"}}'\n"
2004                ),
2005                pyo3::ffi::c_str!("test_json_unicode_escape_mix.py"),
2006                pyo3::ffi::c_str!("test_json_unicode_escape_mix"),
2007            )
2008            .unwrap();
2009
2010            let unicode_array = module.getattr("unicode_array").unwrap();
2011            let unicode_array = extract_template(py, &unicode_array, "json_t/json_t_str").unwrap();
2012            let rendered = render_document(py, &parse_template(&unicode_array).unwrap()).unwrap();
2013            assert_eq!(rendered.text, "[\"\u{2028}\", \"\u{2029}\", \"ß\"]");
2014            assert_eq!(rendered.data, json!(["\u{2028}", "\u{2029}", "ß"]));
2015
2016            let escape_object = module.getattr("escape_object").unwrap();
2017            let escape_object = extract_template(py, &escape_object, "json_t/json_t_str").unwrap();
2018            let rendered = render_document(py, &parse_template(&escape_object).unwrap()).unwrap();
2019            assert_eq!(rendered.text, "{\"x\": \"\\b\u{2028}\u{2029}/\"}");
2020            assert_eq!(rendered.data, json!({"x": "\u{0008}\u{2028}\u{2029}/"}));
2021        });
2022    }
2023
2024    #[test]
2025    fn renders_control_escapes_and_reverse_solidus_variants() {
2026        Python::with_gil(|py| {
2027            let module = PyModule::from_code(
2028                py,
2029                pyo3::ffi::c_str!(
2030                    "escaped_controls=t'\"\\\\b\\\\f\\\\n\\\\r\\\\t\"'\nreverse_solidus_u=t'\"\\\\u005C/\"'\nescaped_quote_backslash=t'\"\\\\\\\"\\\\\\\\\"'\n"
2031                ),
2032                pyo3::ffi::c_str!("test_json_escape_variants.py"),
2033                pyo3::ffi::c_str!("test_json_escape_variants"),
2034            )
2035            .unwrap();
2036
2037            let escaped_controls = module.getattr("escaped_controls").unwrap();
2038            let escaped_controls =
2039                extract_template(py, &escaped_controls, "json_t/json_t_str").unwrap();
2040            let rendered =
2041                render_document(py, &parse_template(&escaped_controls).unwrap()).unwrap();
2042            assert_eq!(rendered.text, "\"\\b\\f\\n\\r\\t\"");
2043            assert_eq!(rendered.data, json!("\u{0008}\u{000c}\n\r\t"));
2044
2045            let reverse_solidus_u = module.getattr("reverse_solidus_u").unwrap();
2046            let reverse_solidus_u =
2047                extract_template(py, &reverse_solidus_u, "json_t/json_t_str").unwrap();
2048            let rendered =
2049                render_document(py, &parse_template(&reverse_solidus_u).unwrap()).unwrap();
2050            assert_eq!(rendered.text, "\"\\\\/\"");
2051            assert_eq!(rendered.data, json!("\\/"));
2052
2053            let escaped_quote_backslash = module.getattr("escaped_quote_backslash").unwrap();
2054            let escaped_quote_backslash =
2055                extract_template(py, &escaped_quote_backslash, "json_t/json_t_str").unwrap();
2056            let rendered =
2057                render_document(py, &parse_template(&escaped_quote_backslash).unwrap()).unwrap();
2058            assert_eq!(rendered.text, "\"\\\"\\\\\"");
2059            assert_eq!(rendered.data, json!("\"\\"));
2060        });
2061    }
2062
2063    #[test]
2064    fn renders_promoted_rows_and_fragment_text_shapes() {
2065        Python::with_gil(|py| {
2066            let module = PyModule::from_code(
2067                py,
2068                pyo3::ffi::c_str!(
2069                    "left='prefix'\nright='suffix'\nrows=[{'name': 'one'}, {'name': 'two'}]\nfragment=t'{{\"label\": {left}-{right}}}'\npromoted=t'{rows}'\n"
2070                ),
2071                pyo3::ffi::c_str!("test_json_promoted_rows.py"),
2072                pyo3::ffi::c_str!("test_json_promoted_rows"),
2073            )
2074            .unwrap();
2075
2076            let fragment = module.getattr("fragment").unwrap();
2077            let fragment = extract_template(py, &fragment, "json_t/json_t_str").unwrap();
2078            let rendered = render_document(py, &parse_template(&fragment).unwrap()).unwrap();
2079            assert_eq!(rendered.text, "{\"label\": \"prefix-suffix\"}");
2080            assert_eq!(rendered.data, json!({"label": "prefix-suffix"}));
2081
2082            let promoted = module.getattr("promoted").unwrap();
2083            let promoted = extract_template(py, &promoted, "json_t/json_t_str").unwrap();
2084            let rendered = render_document(py, &parse_template(&promoted).unwrap()).unwrap();
2085            assert_eq!(rendered.text, r#"[{"name": "one"}, {"name": "two"}]"#);
2086            assert_eq!(rendered.data, json!([{"name": "one"}, {"name": "two"}]));
2087        });
2088    }
2089
2090    #[test]
2091    fn renders_negative_zero_number_shapes() {
2092        Python::with_gil(|py| {
2093            let module = PyModule::from_code(
2094                py,
2095                pyo3::ffi::c_str!(
2096                    "neg_zero=t'-0'\nneg_zero_float=t'-0.0'\nneg_zero_exp=t'-0E0'\ncombo=t'{{\"a\": [0, -0, -0.0, 1e0, -1E-0]}}'\n"
2097                ),
2098                pyo3::ffi::c_str!("test_json_negative_zero_shapes.py"),
2099                pyo3::ffi::c_str!("test_json_negative_zero_shapes"),
2100            )
2101            .unwrap();
2102
2103            for (name, expected_text, expected_data) in [
2104                ("neg_zero", "-0", json!(-0.0)),
2105                ("neg_zero_float", "-0.0", json!(-0.0)),
2106                ("neg_zero_exp", "-0E0", json!(-0.0)),
2107            ] {
2108                let template = module.getattr(name).unwrap();
2109                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
2110                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
2111                assert_eq!(rendered.text, expected_text);
2112                assert_eq!(rendered.data, expected_data);
2113            }
2114
2115            let combo = module.getattr("combo").unwrap();
2116            let combo = extract_template(py, &combo, "json_t/json_t_str").unwrap();
2117            let rendered = render_document(py, &parse_template(&combo).unwrap()).unwrap();
2118            assert_eq!(rendered.text, "{\"a\": [0, -0, -0.0, 1e0, -1E-0]}");
2119            assert_eq!(
2120                rendered.data,
2121                serde_json::from_str::<Value>("{\"a\": [0, -0, -0.0, 1e0, -1E-0]}").unwrap()
2122            );
2123        });
2124    }
2125
2126    #[test]
2127    fn renders_top_level_keywords_and_empty_collections() {
2128        Python::with_gil(|py| {
2129            let module = PyModule::from_code(
2130                py,
2131                pyo3::ffi::c_str!(
2132                    "from string.templatelib import Template\ntop_bool_ws=Template(' \\n true \\t ')\ntop_null_ws=Template(' \\r\\n null \\n')\nempty_object=Template('{ \\n\\t }')\nempty_array=Template('[ \\n\\t ]')\n"
2133                ),
2134                pyo3::ffi::c_str!("test_json_top_level_keywords.py"),
2135                pyo3::ffi::c_str!("test_json_top_level_keywords"),
2136            )
2137            .unwrap();
2138
2139            for (name, expected_text, expected_data) in [
2140                ("top_bool_ws", "true", json!(true)),
2141                ("top_null_ws", "null", Value::Null),
2142                ("empty_object", "{}", json!({})),
2143                ("empty_array", "[]", json!([])),
2144            ] {
2145                let template = module.getattr(name).unwrap();
2146                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
2147                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
2148                assert_eq!(rendered.text, expected_text);
2149                assert_eq!(rendered.data, expected_data);
2150            }
2151        });
2152    }
2153
2154    #[test]
2155    fn renders_escape_unicode_and_keyword_text_shapes() {
2156        Python::with_gil(|py| {
2157            let module = PyModule::from_code(
2158                py,
2159                pyo3::ffi::c_str!(
2160                    "from string.templatelib import Template\ntop_bool_ws=Template(' \\n true \\t ')\ntop_null_ws=Template(' \\r\\n null \\n')\narray_with_line_sep=t'[\"\\\\u2028\", \"\\\\u2029\"]'\nunicode_mix_nested_obj=Template('{\"x\": {\"a\": \"\\\\u005C\", \"b\": \"\\\\u00DF\", \"c\": \"\\\\u2029\"}}')\nkeyword_array=Template('[true,false,null]')\n"
2161                ),
2162                pyo3::ffi::c_str!("test_json_escape_unicode_keyword_shapes.py"),
2163                pyo3::ffi::c_str!("test_json_escape_unicode_keyword_shapes"),
2164            )
2165            .unwrap();
2166
2167            for (name, expected_text, expected_data) in [
2168                ("top_bool_ws", "true", json!(true)),
2169                ("top_null_ws", "null", Value::Null),
2170                (
2171                    "array_with_line_sep",
2172                    "[\"\u{2028}\", \"\u{2029}\"]",
2173                    json!(["\u{2028}", "\u{2029}"]),
2174                ),
2175                (
2176                    "unicode_mix_nested_obj",
2177                    "{\"x\": {\"a\": \"\\\\\", \"b\": \"ß\", \"c\": \"\u{2029}\"}}",
2178                    json!({"x": {"a": "\\", "b": "ß", "c": "\u{2029}"}}),
2179                ),
2180                (
2181                    "keyword_array",
2182                    "[true, false, null]",
2183                    json!([true, false, null]),
2184                ),
2185            ] {
2186                let template = module.getattr(name).unwrap();
2187                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
2188                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
2189                assert_eq!(rendered.text, expected_text, "{name}");
2190                assert_eq!(rendered.data, expected_data, "{name}");
2191            }
2192        });
2193    }
2194
2195    #[test]
2196    fn test_parse_rendered_json_surfaces_parse_failures() {
2197        let err = parse_rendered_json("{\"a\":,}").expect_err("expected JSON parse failure");
2198        assert_eq!(err.kind, ErrorKind::Parse);
2199        assert!(err.message.contains("Rendered JSON could not be reparsed"));
2200    }
2201}