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 normalize_document_with_profile(
604    value: &Value,
605    _profile: JsonProfile,
606) -> BackendResult<NormalizedStream> {
607    // Normalization is currently profile-invariant for JSON, but it remains
608    // profile-aware so future variants do not require another API break.
609    Ok(NormalizedStream::new(vec![NormalizedDocument::Value(
610        normalize_value(value)?,
611    )]))
612}
613
614pub fn normalize_document(value: &Value) -> BackendResult<NormalizedStream> {
615    normalize_document_with_profile(value, JsonProfile::default())
616}
617
618pub fn normalize_value(value: &Value) -> BackendResult<NormalizedValue> {
619    match value {
620        Value::Null => Ok(NormalizedValue::Null),
621        Value::Bool(value) => Ok(NormalizedValue::Bool(*value)),
622        Value::String(value) => Ok(NormalizedValue::String(value.clone())),
623        Value::Array(values) => values
624            .iter()
625            .map(normalize_value)
626            .collect::<BackendResult<Vec<_>>>()
627            .map(NormalizedValue::Sequence),
628        Value::Object(values) => values
629            .iter()
630            .map(|(key, value)| {
631                Ok(tstring_syntax::NormalizedEntry {
632                    key: NormalizedKey::String(key.clone()),
633                    value: normalize_value(value)?,
634                })
635            })
636            .collect::<BackendResult<Vec<_>>>()
637            .map(NormalizedValue::Mapping),
638        Value::Number(number) => normalize_number(number),
639    }
640}
641
642fn normalize_number(number: &serde_json::Number) -> BackendResult<NormalizedValue> {
643    let source = number.to_string();
644    if source.contains(['.', 'e', 'E']) {
645        return source
646            .parse::<f64>()
647            .map(NormalizedFloat::finite)
648            .map(NormalizedValue::Float)
649            .map_err(|err| {
650                BackendError::semantic(format!(
651                    "Validated JSON number {source} could not be normalized as a finite float: {err}"
652                ))
653            });
654    }
655
656    source.parse().map(NormalizedValue::Integer).map_err(|err| {
657        BackendError::semantic(format!(
658            "Validated JSON number {source} could not be normalized as an exact integer: {err}"
659        ))
660    })
661}
662
663#[cfg(test)]
664mod tests {
665    use super::{JsonKeyValue, JsonStringPart, JsonValueNode, parse_template};
666    use pyo3::prelude::*;
667    use serde_json::{Map, Number, Value, json};
668    use tstring_pyo3_bindings::{extract_template, json::render_document};
669    use tstring_syntax::{BackendError, BackendResult, ErrorKind};
670
671    fn parse_rendered_json(text: &str) -> BackendResult<Value> {
672        serde_json::from_str(text).map_err(|err| {
673            BackendError::parse(format!(
674                "Rendered JSON could not be reparsed during test verification: {err}"
675            ))
676        })
677    }
678
679    #[test]
680    fn parses_json_structure() {
681        Python::with_gil(|py| {
682            let module = PyModule::from_code(
683                py,
684                pyo3::ffi::c_str!(
685                    "from string.templatelib import Template\nleft='prefix'\nright='suffix'\ntemplate=t'{{\"prefix-{left}\": {left}-{right}}}'\n"
686                ),
687                pyo3::ffi::c_str!("test_json.py"),
688                pyo3::ffi::c_str!("test_json"),
689            )
690            .unwrap();
691            let template = module.getattr("template").unwrap();
692            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
693            let document = parse_template(&template).unwrap();
694            let JsonValueNode::Object(object) = document.value else {
695                panic!("expected object");
696            };
697            assert_eq!(object.members.len(), 1);
698            let JsonKeyValue::String(key) = &object.members[0].key.value else {
699                panic!("expected interpolated string key");
700            };
701            assert_eq!(key.chunks.len(), 2);
702            assert!(matches!(key.chunks[1], JsonStringPart::Interpolation(_)));
703            let JsonValueNode::String(value) = &object.members[0].value else {
704                panic!("expected promoted string value");
705            };
706            assert_eq!(value.chunks.len(), 3);
707            assert!(matches!(value.chunks[0], JsonStringPart::Interpolation(_)));
708        });
709    }
710
711    #[test]
712    fn renders_nested_collections_and_validates() {
713        Python::with_gil(|py| {
714            let module = PyModule::from_code(
715                py,
716                pyo3::ffi::c_str!(
717                    "items=[1, {'name': 'Ada'}]\nname='Ada'\ntemplate=t'{{\"items\": {items}, \"message\": \"hi-{name}\"}}'\n"
718                ),
719                pyo3::ffi::c_str!("test_json_render.py"),
720                pyo3::ffi::c_str!("test_json_render"),
721            )
722            .unwrap();
723            let template = module.getattr("template").unwrap();
724            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
725            let document = parse_template(&template).unwrap();
726            let rendered = render_document(py, &document).unwrap();
727
728            assert_eq!(rendered.data["message"], Value::String("hi-Ada".to_owned()));
729            assert_eq!(rendered.data["items"][0], Value::Number(Number::from(1)));
730            assert_eq!(
731                rendered.data["items"][1]["name"],
732                Value::String("Ada".to_owned())
733            );
734        });
735    }
736
737    #[test]
738    fn renders_top_level_scalar_text() {
739        Python::with_gil(|py| {
740            let module = PyModule::from_code(
741                py,
742                pyo3::ffi::c_str!(
743                    "template=t'{1}'\nbool_template=t'{True}'\nnull_template=t'{None}'\n"
744                ),
745                pyo3::ffi::c_str!("test_json_scalar.py"),
746                pyo3::ffi::c_str!("test_json_scalar"),
747            )
748            .unwrap();
749            let template = module.getattr("template").unwrap();
750            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
751            let document = parse_template(&template).unwrap();
752            let rendered = render_document(py, &document).unwrap();
753
754            assert_eq!(rendered.text, "1");
755            assert_eq!(rendered.data, Value::Number(Number::from(1)));
756
757            let bool_template = module.getattr("bool_template").unwrap();
758            let bool_template = extract_template(py, &bool_template, "json_t/json_t_str").unwrap();
759            let document = parse_template(&bool_template).unwrap();
760            let rendered = render_document(py, &document).unwrap();
761            assert_eq!(rendered.text, "true");
762            assert_eq!(rendered.data, Value::Bool(true));
763
764            let null_template = module.getattr("null_template").unwrap();
765            let null_template = extract_template(py, &null_template, "json_t/json_t_str").unwrap();
766            let document = parse_template(&null_template).unwrap();
767            let rendered = render_document(py, &document).unwrap();
768            assert_eq!(rendered.text, "null");
769            assert_eq!(rendered.data, Value::Null);
770        });
771    }
772
773    #[test]
774    fn renders_quoted_key_fragments_and_promoted_fragments() {
775        Python::with_gil(|py| {
776            let module = PyModule::from_code(
777                py,
778                pyo3::ffi::c_str!(
779                    "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"
780                ),
781                pyo3::ffi::c_str!("test_json_fragments.py"),
782                pyo3::ffi::c_str!("test_json_fragments"),
783            )
784            .unwrap();
785
786            let quoted_key = module.getattr("quoted_key").unwrap();
787            let quoted_key = extract_template(py, &quoted_key, "json_t/json_t_str").unwrap();
788            let rendered = render_document(py, &parse_template(&quoted_key).unwrap()).unwrap();
789            assert_eq!(rendered.data, json!({"prefix-suffix": 1, "value": true}));
790
791            let fragment = module.getattr("fragment").unwrap();
792            let fragment = extract_template(py, &fragment, "json_t/json_t_str").unwrap();
793            let rendered = render_document(py, &parse_template(&fragment).unwrap()).unwrap();
794            assert_eq!(rendered.text, "{\"label\": \"prefix-suffix\"}");
795            assert_eq!(rendered.data, json!({"label": "prefix-suffix"}));
796
797            let promoted = module.getattr("promoted").unwrap();
798            let promoted = extract_template(py, &promoted, "json_t/json_t_str").unwrap();
799            let rendered = render_document(py, &parse_template(&promoted).unwrap()).unwrap();
800            assert_eq!(rendered.text, r#"[{"name": "one"}, {"name": "two"}]"#);
801            assert_eq!(rendered.data, json!([{"name": "one"}, {"name": "two"}]));
802        });
803    }
804
805    #[test]
806    fn rejects_non_string_key_interpolation() {
807        Python::with_gil(|py| {
808            let module = PyModule::from_code(
809                py,
810                pyo3::ffi::c_str!("key=1\ntemplate=t'{{{key}: 1}}'\n"),
811                pyo3::ffi::c_str!("test_json_error.py"),
812                pyo3::ffi::c_str!("test_json_error"),
813            )
814            .unwrap();
815            let template = module.getattr("template").unwrap();
816            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
817            let document = parse_template(&template).unwrap();
818            let err = match render_document(py, &document) {
819                Ok(_) => panic!("expected JSON render failure"),
820                Err(err) => err,
821            };
822
823            assert_eq!(err.kind, ErrorKind::Unrepresentable);
824            assert!(err.message.contains("JSON object keys must be str"));
825        });
826    }
827
828    #[test]
829    fn rejects_unrepresentable_render_value_families() {
830        Python::with_gil(|py| {
831            let module = PyModule::from_code(
832                py,
833                pyo3::ffi::c_str!(
834                    "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"
835                ),
836                pyo3::ffi::c_str!("test_json_unrepresentable.py"),
837                pyo3::ffi::c_str!("test_json_unrepresentable"),
838            )
839            .unwrap();
840
841            for (name, expected) in [
842                ("map_template", "JSON object keys must be str"),
843                ("nan_template", "non-finite float"),
844                ("set_template", "could not be rendered as JSON"),
845            ] {
846                let template = module.getattr(name).unwrap();
847                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
848                let document = parse_template(&template).unwrap();
849                let err = match render_document(py, &document) {
850                    Ok(_) => panic!("expected JSON render failure"),
851                    Err(err) => err,
852                };
853                assert_eq!(err.kind, ErrorKind::Unrepresentable);
854                assert!(err.message.contains(expected), "{name}: {}", err.message);
855            }
856        });
857    }
858
859    #[test]
860    fn parses_unicode_surrogate_pairs() {
861        Python::with_gil(|py| {
862            let module = PyModule::from_code(
863                py,
864                pyo3::ffi::c_str!("template=t'\"\\\\uD834\\\\uDD1E\"'\n"),
865                pyo3::ffi::c_str!("test_json_unicode.py"),
866                pyo3::ffi::c_str!("test_json_unicode"),
867            )
868            .unwrap();
869            let template = module.getattr("template").unwrap();
870            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
871            let document = parse_template(&template).unwrap();
872            let rendered = render_document(py, &document).unwrap();
873
874            assert_eq!(rendered.data, Value::String("𝄞".to_owned()));
875        });
876    }
877
878    #[test]
879    fn parses_numbers_and_escape_sequences() {
880        Python::with_gil(|py| {
881            let module = PyModule::from_code(
882                py,
883                pyo3::ffi::c_str!(
884                    "template=t'{{\"int\": -0, \"exp\": 1.5e2, \"escapes\": \"\\\\b\\\\f\\\\n\\\\r\\\\t\\\\/\", \"unicode\": \"\\\\u00DF\\\\u6771\\\\uD834\\\\uDD1E\"}}'\n"
885                ),
886                pyo3::ffi::c_str!("test_json_numbers.py"),
887                pyo3::ffi::c_str!("test_json_numbers"),
888            )
889            .unwrap();
890            let template = module.getattr("template").unwrap();
891            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
892            let document = parse_template(&template).unwrap();
893            let rendered = render_document(py, &document).unwrap();
894
895            assert_eq!(
896                rendered.data["exp"],
897                Value::Number(Number::from_f64(150.0).unwrap())
898            );
899            assert_eq!(rendered.data["unicode"], Value::String("ß東𝄞".to_owned()));
900            assert_eq!(
901                rendered.data["escapes"],
902                Value::String("\u{0008}\u{000c}\n\r\t/".to_owned())
903            );
904        });
905    }
906
907    #[test]
908    fn parses_whitespace_and_escaped_solidus_cases() {
909        Python::with_gil(|py| {
910            let module = PyModule::from_code(
911                py,
912                pyo3::ffi::c_str!(
913                    "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"
914                ),
915                pyo3::ffi::c_str!("test_json_whitespace.py"),
916                pyo3::ffi::c_str!("test_json_whitespace"),
917            )
918            .unwrap();
919
920            let top_bool_ws = module.getattr("top_bool_ws").unwrap();
921            let top_bool_ws = extract_template(py, &top_bool_ws, "json_t/json_t_str").unwrap();
922            let rendered = render_document(py, &parse_template(&top_bool_ws).unwrap()).unwrap();
923            assert_eq!(rendered.data, Value::Bool(true));
924
925            let top_null_ws = module.getattr("top_null_ws").unwrap();
926            let top_null_ws = extract_template(py, &top_null_ws, "json_t/json_t_str").unwrap();
927            let rendered = render_document(py, &parse_template(&top_null_ws).unwrap()).unwrap();
928            assert_eq!(rendered.data, Value::Null);
929
930            let empty_string = module.getattr("empty_string").unwrap();
931            let empty_string = extract_template(py, &empty_string, "json_t/json_t_str").unwrap();
932            let rendered = render_document(py, &parse_template(&empty_string).unwrap()).unwrap();
933            assert_eq!(rendered.data, Value::String(String::new()));
934
935            let empty_object = module.getattr("empty_object").unwrap();
936            let empty_object = extract_template(py, &empty_object, "json_t/json_t_str").unwrap();
937            let rendered = render_document(py, &parse_template(&empty_object).unwrap()).unwrap();
938            assert_eq!(rendered.data, Value::Object(Default::default()));
939
940            let empty_array = module.getattr("empty_array").unwrap();
941            let empty_array = extract_template(py, &empty_array, "json_t/json_t_str").unwrap();
942            let rendered = render_document(py, &parse_template(&empty_array).unwrap()).unwrap();
943            assert_eq!(rendered.data, Value::Array(Vec::new()));
944
945            let escaped_controls = module.getattr("escaped_controls").unwrap();
946            let escaped_controls =
947                extract_template(py, &escaped_controls, "json_t/json_t_str").unwrap();
948            let rendered =
949                render_document(py, &parse_template(&escaped_controls).unwrap()).unwrap();
950            assert_eq!(
951                rendered.data,
952                Value::String("\u{0008}\u{000c}\n\r\t".to_owned())
953            );
954
955            let escaped_solidus = module.getattr("escaped_solidus").unwrap();
956            let escaped_solidus =
957                extract_template(py, &escaped_solidus, "json_t/json_t_str").unwrap();
958            let rendered = render_document(py, &parse_template(&escaped_solidus).unwrap()).unwrap();
959            assert_eq!(rendered.data, Value::String("/".to_owned()));
960
961            let escaped_backslash = module.getattr("escaped_backslash").unwrap();
962            let escaped_backslash =
963                extract_template(py, &escaped_backslash, "json_t/json_t_str").unwrap();
964            let rendered =
965                render_document(py, &parse_template(&escaped_backslash).unwrap()).unwrap();
966            assert_eq!(rendered.data, Value::String("\\".to_owned()));
967
968            let unicode_backslash_escape = module.getattr("unicode_backslash_escape").unwrap();
969            let unicode_backslash_escape =
970                extract_template(py, &unicode_backslash_escape, "json_t/json_t_str").unwrap();
971            let rendered =
972                render_document(py, &parse_template(&unicode_backslash_escape).unwrap()).unwrap();
973            assert_eq!(rendered.data, Value::String("\\".to_owned()));
974
975            let reverse_solidus_u = module.getattr("reverse_solidus_u").unwrap();
976            let reverse_solidus_u =
977                extract_template(py, &reverse_solidus_u, "json_t/json_t_str").unwrap();
978            let rendered =
979                render_document(py, &parse_template(&reverse_solidus_u).unwrap()).unwrap();
980            assert_eq!(rendered.data, Value::String("\\/".to_owned()));
981
982            let escaped_quote_backslash = module.getattr("escaped_quote_backslash").unwrap();
983            let escaped_quote_backslash =
984                extract_template(py, &escaped_quote_backslash, "json_t/json_t_str").unwrap();
985            let rendered =
986                render_document(py, &parse_template(&escaped_quote_backslash).unwrap()).unwrap();
987            assert_eq!(rendered.data, Value::String("\"\\".to_owned()));
988
989            let escaped_null_and_unit_separator =
990                module.getattr("escaped_null_and_unit_separator").unwrap();
991            let escaped_null_and_unit_separator =
992                extract_template(py, &escaped_null_and_unit_separator, "json_t/json_t_str")
993                    .unwrap();
994            let rendered = render_document(
995                py,
996                &parse_template(&escaped_null_and_unit_separator).unwrap(),
997            )
998            .unwrap();
999            assert_eq!(rendered.data, Value::String("\u{0000}\u{001f}".to_owned()));
1000
1001            let nested_upper_unicode = module.getattr("nested_upper_unicode").unwrap();
1002            let nested_upper_unicode =
1003                extract_template(py, &nested_upper_unicode, "json_t/json_t_str").unwrap();
1004            let rendered =
1005                render_document(py, &parse_template(&nested_upper_unicode).unwrap()).unwrap();
1006            assert_eq!(rendered.data, Value::String("ß東".to_owned()));
1007
1008            let unicode_line_sep = module.getattr("unicode_line_sep").unwrap();
1009            let unicode_line_sep =
1010                extract_template(py, &unicode_line_sep, "json_t/json_t_str").unwrap();
1011            let rendered =
1012                render_document(py, &parse_template(&unicode_line_sep).unwrap()).unwrap();
1013            assert_eq!(rendered.data, Value::String("\u{2028}".to_owned()));
1014
1015            let unicode_para_sep = module.getattr("unicode_para_sep").unwrap();
1016            let unicode_para_sep =
1017                extract_template(py, &unicode_para_sep, "json_t/json_t_str").unwrap();
1018            let rendered =
1019                render_document(py, &parse_template(&unicode_para_sep).unwrap()).unwrap();
1020            assert_eq!(rendered.data, Value::String("\u{2029}".to_owned()));
1021
1022            let array_with_line_sep = module.getattr("array_with_line_sep").unwrap();
1023            let array_with_line_sep =
1024                extract_template(py, &array_with_line_sep, "json_t/json_t_str").unwrap();
1025            let rendered =
1026                render_document(py, &parse_template(&array_with_line_sep).unwrap()).unwrap();
1027            assert_eq!(rendered.data.as_array().expect("array").len(), 2);
1028            assert_eq!(rendered.data[0], Value::String("\u{2028}".to_owned()));
1029            assert_eq!(rendered.data[1], Value::String("\u{2029}".to_owned()));
1030
1031            let unicode_escapes_array = module.getattr("unicode_escapes_array").unwrap();
1032            let unicode_escapes_array =
1033                extract_template(py, &unicode_escapes_array, "json_t/json_t_str").unwrap();
1034            let rendered =
1035                render_document(py, &parse_template(&unicode_escapes_array).unwrap()).unwrap();
1036            let values = rendered.data.as_array().expect("array");
1037            assert_eq!(values[0], Value::String("\\".to_owned()));
1038            assert_eq!(values[1], Value::String("/".to_owned()));
1039            assert_eq!(values[2], Value::String("ß".to_owned()));
1040
1041            let unicode_mix_nested_obj = module.getattr("unicode_mix_nested_obj").unwrap();
1042            let unicode_mix_nested_obj =
1043                extract_template(py, &unicode_mix_nested_obj, "json_t/json_t_str").unwrap();
1044            let rendered =
1045                render_document(py, &parse_template(&unicode_mix_nested_obj).unwrap()).unwrap();
1046            assert_eq!(rendered.data["x"]["a"], Value::String("\\".to_owned()));
1047            assert_eq!(rendered.data["x"]["b"], Value::String("ß".to_owned()));
1048            assert_eq!(
1049                rendered.data["x"]["c"],
1050                Value::String("\u{2029}".to_owned())
1051            );
1052
1053            let nested_unicode_object_array =
1054                module.getattr("nested_unicode_object_array").unwrap();
1055            let nested_unicode_object_array =
1056                extract_template(py, &nested_unicode_object_array, "json_t/json_t_str").unwrap();
1057            let rendered =
1058                render_document(py, &parse_template(&nested_unicode_object_array).unwrap())
1059                    .unwrap();
1060            assert_eq!(rendered.data["a"][0]["b"], Value::String("\\".to_owned()));
1061            assert_eq!(rendered.data["a"][0]["c"], Value::String("ß".to_owned()));
1062
1063            let upper_unicode_mix_array = module.getattr("upper_unicode_mix_array").unwrap();
1064            let upper_unicode_mix_array =
1065                extract_template(py, &upper_unicode_mix_array, "json_t/json_t_str").unwrap();
1066            let rendered =
1067                render_document(py, &parse_template(&upper_unicode_mix_array).unwrap()).unwrap();
1068            let values = rendered.data.as_array().expect("array");
1069            assert_eq!(values[0], Value::String("ß".to_owned()));
1070            assert_eq!(values[1], Value::String("東".to_owned()));
1071            assert_eq!(values[2], Value::String("\u{2028}".to_owned()));
1072
1073            let escaped_slash_backslash_quote =
1074                module.getattr("escaped_slash_backslash_quote").unwrap();
1075            let escaped_slash_backslash_quote =
1076                extract_template(py, &escaped_slash_backslash_quote, "json_t/json_t_str").unwrap();
1077            let rendered =
1078                render_document(py, &parse_template(&escaped_slash_backslash_quote).unwrap())
1079                    .unwrap();
1080            assert_eq!(rendered.data, Value::String("/\\\"".to_owned()));
1081
1082            let array_empty_values = module.getattr("array_empty_values").unwrap();
1083            let array_empty_values =
1084                extract_template(py, &array_empty_values, "json_t/json_t_str").unwrap();
1085            let rendered =
1086                render_document(py, &parse_template(&array_empty_values).unwrap()).unwrap();
1087            let values = rendered.data.as_array().expect("array");
1088            assert_eq!(values.len(), 6);
1089            assert_eq!(values[0], Value::String(String::new()));
1090            assert_eq!(values[1], Value::Number(Number::from(0)));
1091            assert_eq!(values[2], Value::Bool(false));
1092            assert_eq!(values[3], Value::Null);
1093            assert_eq!(values[4], Value::Object(Map::new()));
1094            assert_eq!(values[5], Value::Array(Vec::new()));
1095
1096            let empty_object_in_array = module.getattr("empty_object_in_array").unwrap();
1097            let empty_object_in_array =
1098                extract_template(py, &empty_object_in_array, "json_t/json_t_str").unwrap();
1099            let rendered =
1100                render_document(py, &parse_template(&empty_object_in_array).unwrap()).unwrap();
1101            let values = rendered.data.as_array().expect("array");
1102            assert_eq!(values.len(), 2);
1103            assert_eq!(values[0], Value::Object(Map::new()));
1104            assert_eq!(values[1]["a"], Value::Array(Vec::new()));
1105
1106            let top_level_empty_object_ws = module.getattr("top_level_empty_object_ws").unwrap();
1107            let top_level_empty_object_ws =
1108                extract_template(py, &top_level_empty_object_ws, "json_t/json_t_str").unwrap();
1109            let rendered =
1110                render_document(py, &parse_template(&top_level_empty_object_ws).unwrap()).unwrap();
1111            assert_eq!(rendered.data, Value::Object(Map::new()));
1112
1113            let nested_escaped_mix = module.getattr("nested_escaped_mix").unwrap();
1114            let nested_escaped_mix =
1115                extract_template(py, &nested_escaped_mix, "json_t/json_t_str").unwrap();
1116            let rendered =
1117                render_document(py, &parse_template(&nested_escaped_mix).unwrap()).unwrap();
1118            assert_eq!(
1119                rendered.data["x"],
1120                Value::String("\u{0008}\u{2028}\u{2029}/".to_owned())
1121            );
1122
1123            let escaped_reverse_solidus_solidus =
1124                module.getattr("escaped_reverse_solidus_solidus").unwrap();
1125            let escaped_reverse_solidus_solidus =
1126                extract_template(py, &escaped_reverse_solidus_solidus, "json_t/json_t_str")
1127                    .unwrap();
1128            let rendered = render_document(
1129                py,
1130                &parse_template(&escaped_reverse_solidus_solidus).unwrap(),
1131            )
1132            .unwrap();
1133            assert_eq!(rendered.data, Value::String("\\/".to_owned()));
1134
1135            let upper_exp = module.getattr("upper_exp").unwrap();
1136            let upper_exp = extract_template(py, &upper_exp, "json_t/json_t_str").unwrap();
1137            let rendered = render_document(py, &parse_template(&upper_exp).unwrap()).unwrap();
1138            assert_eq!(
1139                rendered.data,
1140                Value::Number(Number::from_f64(100.0).unwrap())
1141            );
1142
1143            let upper_exp_plus = module.getattr("upper_exp_plus").unwrap();
1144            let upper_exp_plus =
1145                extract_template(py, &upper_exp_plus, "json_t/json_t_str").unwrap();
1146            let rendered = render_document(py, &parse_template(&upper_exp_plus).unwrap()).unwrap();
1147            assert_eq!(
1148                rendered.data,
1149                Value::Number(Number::from_f64(100.0).unwrap())
1150            );
1151
1152            let upper_exp_negative = module.getattr("upper_exp_negative").unwrap();
1153            let upper_exp_negative =
1154                extract_template(py, &upper_exp_negative, "json_t/json_t_str").unwrap();
1155            let rendered =
1156                render_document(py, &parse_template(&upper_exp_negative).unwrap()).unwrap();
1157            assert_eq!(
1158                rendered.data,
1159                Value::Number(Number::from_f64(-100.0).unwrap())
1160            );
1161
1162            let upper_exp_zero_fraction = module.getattr("upper_exp_zero_fraction").unwrap();
1163            let upper_exp_zero_fraction =
1164                extract_template(py, &upper_exp_zero_fraction, "json_t/json_t_str").unwrap();
1165            let rendered =
1166                render_document(py, &parse_template(&upper_exp_zero_fraction).unwrap()).unwrap();
1167            assert_eq!(rendered.data, Value::Number(Number::from_f64(0.0).unwrap()));
1168
1169            let upper_zero_negative_exp = module.getattr("upper_zero_negative_exp").unwrap();
1170            let upper_zero_negative_exp =
1171                extract_template(py, &upper_zero_negative_exp, "json_t/json_t_str").unwrap();
1172            let rendered =
1173                render_document(py, &parse_template(&upper_zero_negative_exp).unwrap()).unwrap();
1174            assert_eq!(
1175                rendered.data,
1176                Value::Number(Number::from_f64(-0.0).unwrap())
1177            );
1178
1179            let nested_upper_exp = module.getattr("nested_upper_exp").unwrap();
1180            let nested_upper_exp =
1181                extract_template(py, &nested_upper_exp, "json_t/json_t_str").unwrap();
1182            let rendered =
1183                render_document(py, &parse_template(&nested_upper_exp).unwrap()).unwrap();
1184            assert_eq!(
1185                rendered.data["value"],
1186                Value::Number(Number::from_f64(100.0).unwrap())
1187            );
1188
1189            let neg_exp_zero = module.getattr("neg_exp_zero").unwrap();
1190            let neg_exp_zero = extract_template(py, &neg_exp_zero, "json_t/json_t_str").unwrap();
1191            let rendered = render_document(py, &parse_template(&neg_exp_zero).unwrap()).unwrap();
1192            assert_eq!(
1193                rendered.data,
1194                Value::Number(Number::from_f64(-1.0).unwrap())
1195            );
1196
1197            let upper_exp_negative_zero = module.getattr("upper_exp_negative_zero").unwrap();
1198            let upper_exp_negative_zero =
1199                extract_template(py, &upper_exp_negative_zero, "json_t/json_t_str").unwrap();
1200            let rendered =
1201                render_document(py, &parse_template(&upper_exp_negative_zero).unwrap()).unwrap();
1202            assert_eq!(rendered.data, Value::Number(Number::from_f64(1.0).unwrap()));
1203
1204            let exp_with_fraction_zero = module.getattr("exp_with_fraction_zero").unwrap();
1205            let exp_with_fraction_zero =
1206                extract_template(py, &exp_with_fraction_zero, "json_t/json_t_str").unwrap();
1207            let rendered =
1208                render_document(py, &parse_template(&exp_with_fraction_zero).unwrap()).unwrap();
1209            assert_eq!(rendered.data, Value::Number(Number::from_f64(1.0).unwrap()));
1210
1211            let negative_zero_exp_upper = module.getattr("negative_zero_exp_upper").unwrap();
1212            let negative_zero_exp_upper =
1213                extract_template(py, &negative_zero_exp_upper, "json_t/json_t_str").unwrap();
1214            let rendered =
1215                render_document(py, &parse_template(&negative_zero_exp_upper).unwrap()).unwrap();
1216            assert_eq!(
1217                rendered.data,
1218                Value::Number(Number::from_f64(-0.0).unwrap())
1219            );
1220
1221            let nested_negative_exp_mix = module.getattr("nested_negative_exp_mix").unwrap();
1222            let nested_negative_exp_mix =
1223                extract_template(py, &nested_negative_exp_mix, "json_t/json_t_str").unwrap();
1224            let rendered =
1225                render_document(py, &parse_template(&nested_negative_exp_mix).unwrap()).unwrap();
1226            assert_eq!(
1227                rendered.data["x"][0],
1228                Value::Number(Number::from_f64(-0.01).unwrap())
1229            );
1230            assert_eq!(rendered.data["x"][1], Value::Number(Number::from(0)));
1231            assert_eq!(rendered.data["x"][2], Value::String(String::new()));
1232            assert_eq!(rendered.data["x"][3]["y"][0], Value::Null);
1233
1234            let mixed_nested_keywords = module.getattr("mixed_nested_keywords").unwrap();
1235            let mixed_nested_keywords =
1236                extract_template(py, &mixed_nested_keywords, "json_t/json_t_str").unwrap();
1237            let rendered =
1238                render_document(py, &parse_template(&mixed_nested_keywords).unwrap()).unwrap();
1239            assert_eq!(rendered.data["a"][0], Value::Bool(true));
1240            assert_eq!(rendered.data["a"][1], Value::Bool(false));
1241            assert_eq!(rendered.data["a"][2], Value::Null);
1242            assert_eq!(
1243                rendered.data["b"]["c"],
1244                Value::Number(Number::from_f64(-1.0).unwrap())
1245            );
1246
1247            let nested_empty_names = module.getattr("nested_empty_names").unwrap();
1248            let nested_empty_names =
1249                extract_template(py, &nested_empty_names, "json_t/json_t_str").unwrap();
1250            let rendered =
1251                render_document(py, &parse_template(&nested_empty_names).unwrap()).unwrap();
1252            assert_eq!(rendered.data[""][""], Value::Array(Vec::new()));
1253
1254            let nested_empty_name_array = module.getattr("nested_empty_name_array").unwrap();
1255            let nested_empty_name_array =
1256                extract_template(py, &nested_empty_name_array, "json_t/json_t_str").unwrap();
1257            let rendered =
1258                render_document(py, &parse_template(&nested_empty_name_array).unwrap()).unwrap();
1259            assert_eq!(rendered.data[""][0], Value::String(String::new()));
1260            assert_eq!(rendered.data[""][1][""], Value::Number(Number::from(0)));
1261
1262            let nested_nulls = module.getattr("nested_nulls").unwrap();
1263            let nested_nulls = extract_template(py, &nested_nulls, "json_t/json_t_str").unwrap();
1264            let rendered = render_document(py, &parse_template(&nested_nulls).unwrap()).unwrap();
1265            assert_eq!(rendered.data["a"], Value::Null);
1266            assert_eq!(rendered.data["b"][0], Value::Null);
1267            assert_eq!(rendered.data["b"][1]["c"], Value::Null);
1268
1269            let nested_top_ws = module.getattr("nested_top_ws").unwrap();
1270            let nested_top_ws = extract_template(py, &nested_top_ws, "json_t/json_t_str").unwrap();
1271            let rendered = render_document(py, &parse_template(&nested_top_ws).unwrap()).unwrap();
1272            assert_eq!(rendered.data["a"][1]["b"], Value::String("c".to_owned()));
1273            assert_eq!(rendered.data[""], Value::String(String::new()));
1274
1275            let top_ws_string = module.getattr("top_ws_string").unwrap();
1276            let top_ws_string = extract_template(py, &top_ws_string, "json_t/json_t_str").unwrap();
1277            let rendered = render_document(py, &parse_template(&top_ws_string).unwrap()).unwrap();
1278            assert_eq!(rendered.data, Value::String("x".to_owned()));
1279
1280            let nested_number_whitespace = module.getattr("nested_number_whitespace").unwrap();
1281            let nested_number_whitespace =
1282                extract_template(py, &nested_number_whitespace, "json_t/json_t_str").unwrap();
1283            let rendered =
1284                render_document(py, &parse_template(&nested_number_whitespace).unwrap()).unwrap();
1285            let values = rendered.data["a"].as_array().expect("array");
1286            assert_eq!(values.len(), 3);
1287            assert_eq!(values[0], Value::Number(Number::from(0)));
1288            assert_eq!(values[1], Value::Number(Number::from_f64(-0.0).unwrap()));
1289            assert_eq!(values[2], Value::Number(Number::from_f64(0.015).unwrap()));
1290
1291            let nested_number_combo = module.getattr("nested_number_combo").unwrap();
1292            let nested_number_combo =
1293                extract_template(py, &nested_number_combo, "json_t/json_t_str").unwrap();
1294            let rendered =
1295                render_document(py, &parse_template(&nested_number_combo).unwrap()).unwrap();
1296            let values = rendered.data["a"].as_array().expect("array");
1297            assert_eq!(values[0], Value::Number(Number::from(0)));
1298            assert_eq!(values[1], Value::Number(Number::from_f64(-0.0).unwrap()));
1299            assert_eq!(values[2], Value::Number(Number::from_f64(-0.0).unwrap()));
1300            assert_eq!(values[3], Value::Number(Number::from_f64(1.0).unwrap()));
1301            assert_eq!(values[4], Value::Number(Number::from_f64(-1.0).unwrap()));
1302
1303            let nested_bool_null_mix = module.getattr("nested_bool_null_mix").unwrap();
1304            let nested_bool_null_mix =
1305                extract_template(py, &nested_bool_null_mix, "json_t/json_t_str").unwrap();
1306            let rendered =
1307                render_document(py, &parse_template(&nested_bool_null_mix).unwrap()).unwrap();
1308            assert_eq!(rendered.data["v"][0], Value::Bool(true));
1309            assert_eq!(rendered.data["v"][1], Value::Null);
1310            assert_eq!(rendered.data["v"][2], Value::Bool(false));
1311            assert_eq!(rendered.data["v"][3]["x"], Value::Number(Number::from(1)));
1312
1313            let keyword_array = module.getattr("keyword_array").unwrap();
1314            let keyword_array = extract_template(py, &keyword_array, "json_t/json_t_str").unwrap();
1315            let rendered = render_document(py, &parse_template(&keyword_array).unwrap()).unwrap();
1316            let values = rendered.data.as_array().expect("array");
1317            assert_eq!(values[0], Value::Bool(true));
1318            assert_eq!(values[1], Value::Bool(false));
1319            assert_eq!(values[2], Value::Null);
1320
1321            let empty_name_nested_keywords = module.getattr("empty_name_nested_keywords").unwrap();
1322            let empty_name_nested_keywords =
1323                extract_template(py, &empty_name_nested_keywords, "json_t/json_t_str").unwrap();
1324            let rendered =
1325                render_document(py, &parse_template(&empty_name_nested_keywords).unwrap()).unwrap();
1326            let values = rendered.data[""].as_array().expect("array");
1327            assert_eq!(values[0], Value::Null);
1328            assert_eq!(values[1], Value::Bool(true));
1329            assert_eq!(values[2], Value::Bool(false));
1330
1331            let nested_empty_mix = module.getattr("nested_empty_mix").unwrap();
1332            let nested_empty_mix =
1333                extract_template(py, &nested_empty_mix, "json_t/json_t_str").unwrap();
1334            let rendered =
1335                render_document(py, &parse_template(&nested_empty_mix).unwrap()).unwrap();
1336            let values = rendered.data["a"].as_array().expect("array");
1337            assert_eq!(values[0], Value::Object(Map::new()));
1338            assert_eq!(values[1], Value::Array(Vec::new()));
1339            assert_eq!(values[2], Value::String(String::new()));
1340            assert_eq!(values[3], Value::Number(Number::from(0)));
1341            assert_eq!(values[4], Value::Bool(false));
1342            assert_eq!(values[5], Value::Null);
1343
1344            let nested_empty_collections_mix =
1345                module.getattr("nested_empty_collections_mix").unwrap();
1346            let nested_empty_collections_mix =
1347                extract_template(py, &nested_empty_collections_mix, "json_t/json_t_str").unwrap();
1348            let rendered =
1349                render_document(py, &parse_template(&nested_empty_collections_mix).unwrap())
1350                    .unwrap();
1351            assert_eq!(rendered.data["a"]["b"], Value::Array(Vec::new()));
1352            assert_eq!(rendered.data["c"][0], Value::Object(Map::new()));
1353            assert_eq!(rendered.data["c"][1], Value::Array(Vec::new()));
1354
1355            let array_nested_mixed_scalars = module.getattr("array_nested_mixed_scalars").unwrap();
1356            let array_nested_mixed_scalars =
1357                extract_template(py, &array_nested_mixed_scalars, "json_t/json_t_str").unwrap();
1358            let rendered =
1359                render_document(py, &parse_template(&array_nested_mixed_scalars).unwrap()).unwrap();
1360            let values = rendered.data.as_array().expect("array");
1361            assert_eq!(values[0]["a"], Value::Array(Vec::new()));
1362            assert_eq!(values[1]["b"], Value::Object(Map::new()));
1363            assert_eq!(values[2], Value::String(String::new()));
1364            assert_eq!(values[3], Value::Number(Number::from(0)));
1365            assert_eq!(values[4], Value::Bool(false));
1366            assert_eq!(values[5], Value::Null);
1367
1368            let zero_fraction_exp = module.getattr("zero_fraction_exp").unwrap();
1369            let zero_fraction_exp =
1370                extract_template(py, &zero_fraction_exp, "json_t/json_t_str").unwrap();
1371            let rendered =
1372                render_document(py, &parse_template(&zero_fraction_exp).unwrap()).unwrap();
1373            assert_eq!(rendered.data, Value::Number(Number::from_f64(0.0).unwrap()));
1374
1375            let nested = module.getattr("nested").unwrap();
1376            let nested = extract_template(py, &nested, "json_t/json_t_str").unwrap();
1377            let rendered = render_document(py, &parse_template(&nested).unwrap()).unwrap();
1378            assert_eq!(rendered.data.as_array().expect("array").len(), 1);
1379            assert_eq!(rendered.data[0]["a"], Value::Number(Number::from(1)));
1380            assert_eq!(rendered.data[0]["b"][0], Value::Bool(true));
1381            assert_eq!(rendered.data[0]["b"][1], Value::Bool(false));
1382            assert_eq!(rendered.data[0]["b"][2], Value::Null);
1383        });
1384    }
1385
1386    #[test]
1387    fn rejects_invalid_unicode_and_control_sequences() {
1388        Python::with_gil(|py| {
1389            let module = PyModule::from_code(
1390                py,
1391                pyo3::ffi::c_str!(
1392                    "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"
1393                ),
1394                pyo3::ffi::c_str!("test_json_invalid_unicode.py"),
1395                pyo3::ffi::c_str!("test_json_invalid_unicode"),
1396            )
1397            .unwrap();
1398
1399            for (name, expected) in [
1400                ("incomplete", "Unexpected end of JSON escape sequence"),
1401                ("control", "Control characters are not allowed"),
1402                ("low", "Invalid JSON unicode escape"),
1403                ("exp", "Invalid JSON number literal"),
1404                ("exp2", "Invalid JSON number literal"),
1405                ("dot", "Invalid JSON number literal"),
1406                ("minus", "Invalid JSON number literal"),
1407                ("object_exp", "Invalid JSON number literal"),
1408                ("object_dot", "Invalid JSON number literal"),
1409                ("trailing_true", "Expected a JSON value"),
1410                (
1411                    "trailing_string",
1412                    "Unexpected trailing content in JSON template",
1413                ),
1414                (
1415                    "extra_close",
1416                    "Unexpected trailing content in JSON template",
1417                ),
1418                (
1419                    "object_extra_close",
1420                    "Unexpected trailing content in JSON template",
1421                ),
1422                (
1423                    "double_trailing_close",
1424                    "Unexpected trailing content in JSON template",
1425                ),
1426                ("missing_array_comma", "Invalid JSON number literal"),
1427                ("missing_object_comma", "Invalid JSON number literal"),
1428                (
1429                    "nested_missing_object_array_comma",
1430                    "Invalid JSON number literal",
1431                ),
1432                ("extra_array_comma", "Expected a JSON value"),
1433                ("leading_comma_array", "Expected a JSON value"),
1434                ("missing_value", "Expected a JSON value"),
1435                (
1436                    "missing_key",
1437                    "JSON object keys must be quoted strings or interpolations",
1438                ),
1439                ("missing_colon", "Expected ':' in JSON template"),
1440                (
1441                    "trailing_object_comma",
1442                    "JSON object keys must be quoted strings or interpolations",
1443                ),
1444                ("trailing_array_comma", "Expected a JSON value"),
1445                (
1446                    "nested_trailing_object_comma",
1447                    "quoted strings or interpolations",
1448                ),
1449                ("plus_number", "Expected a JSON value"),
1450                ("leading_decimal", "Expected a JSON value"),
1451                ("leading_zero", "Invalid JSON number literal"),
1452                ("negative_leading_zero", "Invalid JSON number literal"),
1453                ("leading_zero_exp", "Invalid JSON number literal"),
1454                ("negative_leading_zero_exp", "Invalid JSON number literal"),
1455                ("missing_exp_digits_minus", "Invalid JSON number literal"),
1456                (
1457                    "object_missing_exp_digits_minus",
1458                    "Invalid JSON number literal",
1459                ),
1460                (
1461                    "object_negative_leading_zero",
1462                    "Invalid JSON number literal",
1463                ),
1464                ("array_double_value_no_comma", "Expected a JSON value"),
1465                ("nested_missing_comma_bool", "Expected a JSON value"),
1466                ("array_true_number_no_comma", "Expected a JSON value"),
1467                (
1468                    "array_false_object_no_comma",
1469                    "Invalid promoted JSON fragment content",
1470                ),
1471                (
1472                    "nested_missing_comma_obj_after_null",
1473                    "Invalid promoted JSON fragment content",
1474                ),
1475                ("object_double_value_no_comma", "Expected a JSON value"),
1476                ("object_keyword_number_no_comma", "Expected a JSON value"),
1477                ("truncated_true", "Expected a JSON value"),
1478                ("truncated_false", "Expected a JSON value"),
1479                ("truncated_null", "Expected a JSON value"),
1480                ("object_truncated_true", "Expected a JSON value"),
1481                ("object_truncated_false", "Expected a JSON value"),
1482                ("object_truncated_null", "Expected a JSON value"),
1483                ("array_truncated_true", "Expected a JSON value"),
1484                ("array_truncated_false", "Expected a JSON value"),
1485                ("nested_array_truncated_true", "Expected a JSON value"),
1486                ("nested_array_truncated_false", "Expected a JSON value"),
1487                ("array_truncated_null", "Expected a JSON value"),
1488                ("nested_object_truncated_null", "Expected a JSON value"),
1489                ("keyword_prefix", "Expected a JSON value"),
1490                ("bad_true_case", "Expected a JSON value"),
1491                ("array_bad_true_case", "Expected a JSON value"),
1492                ("bad_false_case", "Expected a JSON value"),
1493                ("bad_null_case", "Expected a JSON value"),
1494                ("object_bad_true_case", "Expected a JSON value"),
1495                ("object_bad_false_case", "Expected a JSON value"),
1496                ("object_bad_null_case", "Expected a JSON value"),
1497                ("array_bad_false_case", "Expected a JSON value"),
1498                ("array_bad_null_case", "Expected a JSON value"),
1499                ("nested_array_bad_null_case", "Expected a JSON value"),
1500                (
1501                    "array_missing_comma_after_string",
1502                    "Expected ',' in JSON template",
1503                ),
1504                (
1505                    "object_missing_comma_after_null",
1506                    "Invalid promoted JSON fragment content",
1507                ),
1508                ("double_decimal_point", "Invalid JSON number literal"),
1509                ("object_bad_leading_zero", "Invalid JSON number literal"),
1510                (
1511                    "nested_extra_close",
1512                    "Unexpected trailing content in JSON template",
1513                ),
1514            ] {
1515                let template = module.getattr(name).unwrap();
1516                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1517                let err = match parse_template(&template) {
1518                    Ok(_) => panic!("expected JSON parse failure for {name}"),
1519                    Err(err) => err,
1520                };
1521                assert_eq!(err.kind, ErrorKind::Parse);
1522                assert!(err.message.contains(expected), "{name}: {}", err.message);
1523            }
1524        });
1525    }
1526
1527    #[test]
1528    fn rejects_structural_invalid_message_families() {
1529        Python::with_gil(|py| {
1530            let module = PyModule::from_code(
1531                py,
1532                pyo3::ffi::c_str!(
1533                    "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"
1534                ),
1535                pyo3::ffi::c_str!("test_json_structural_invalids.py"),
1536                pyo3::ffi::c_str!("test_json_structural_invalids"),
1537            )
1538            .unwrap();
1539
1540            for (name, expected) in [
1541                ("missing_comma", "Expected ',' in JSON template"),
1542                (
1543                    "trailing_comma",
1544                    "JSON object keys must be quoted strings or interpolations",
1545                ),
1546                ("invalid_fragment", "Invalid promoted JSON fragment content"),
1547                (
1548                    "unexpected_trailing",
1549                    "Unexpected trailing content in JSON template",
1550                ),
1551            ] {
1552                let template = module.getattr(name).unwrap();
1553                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1554                let err = parse_template(&template).expect_err("expected JSON parse failure");
1555                assert_eq!(err.kind, ErrorKind::Parse);
1556                assert!(err.message.contains(expected), "{name}: {}", err.message);
1557            }
1558        });
1559    }
1560
1561    #[test]
1562    fn rejects_keyword_truncation_and_collection_separator_errors() {
1563        Python::with_gil(|py| {
1564            let module = PyModule::from_code(
1565                py,
1566                pyo3::ffi::c_str!(
1567                    "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"
1568                ),
1569                pyo3::ffi::c_str!("test_json_invalid_keywords.py"),
1570                pyo3::ffi::c_str!("test_json_invalid_keywords"),
1571            )
1572            .unwrap();
1573
1574            for (name, expected) in [
1575                ("bad_true", "Expected a JSON value"),
1576                ("bad_false", "Expected a JSON value"),
1577                ("bad_null", "Expected a JSON value"),
1578                (
1579                    "missing_comma_array",
1580                    "Invalid promoted JSON fragment content",
1581                ),
1582                ("trailing_array_comma", "Expected a JSON value"),
1583                ("trailing_object_comma", "quoted strings or interpolations"),
1584            ] {
1585                let template = module.getattr(name).unwrap();
1586                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1587                let err = parse_template(&template).expect_err("expected JSON parse failure");
1588                assert_eq!(err.kind, ErrorKind::Parse);
1589                assert!(err.message.contains(expected), "{name}: {}", err.message);
1590            }
1591        });
1592    }
1593
1594    #[test]
1595    fn rejects_additional_number_and_trailing_content_families() {
1596        Python::with_gil(|py| {
1597            let module = PyModule::from_code(
1598                py,
1599                pyo3::ffi::c_str!(
1600                    "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"
1601                ),
1602                pyo3::ffi::c_str!("test_json_additional_parse_errors.py"),
1603                pyo3::ffi::c_str!("test_json_additional_parse_errors"),
1604            )
1605            .unwrap();
1606
1607            for (name, expected) in [
1608                ("leading_zero_exp", "Invalid JSON number literal"),
1609                ("leading_zero_exp_negative", "Invalid JSON number literal"),
1610                ("missing_exp_digits", "Invalid JSON number literal"),
1611                ("embedded_missing_exp_digits", "Invalid JSON number literal"),
1612                ("double_sign_number", "Invalid JSON number literal"),
1613                ("leading_plus_minus", "Expected a JSON value"),
1614                ("bad_exp_plus_minus", "Invalid JSON number literal"),
1615                ("bad_exp_minus_plus", "Invalid JSON number literal"),
1616                ("extra_decimal", "Invalid JSON number literal"),
1617                ("array_space_number", "Invalid JSON number literal"),
1618                ("object_space_number", "Invalid JSON number literal"),
1619                ("truee", "Expected a JSON value"),
1620                ("true_then_number", "Expected a JSON value"),
1621                ("object_true_then_number", "Expected a JSON value"),
1622                ("false_fragment", "Invalid promoted JSON fragment content"),
1623                (
1624                    "array_trailing_object",
1625                    "Unexpected trailing content in JSON template",
1626                ),
1627                (
1628                    "object_trailing_array",
1629                    "Unexpected trailing content in JSON template",
1630                ),
1631                (
1632                    "deep_object_trailing",
1633                    "Unexpected trailing content in JSON template",
1634                ),
1635            ] {
1636                let template = module.getattr(name).unwrap();
1637                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1638                let err = parse_template(&template).expect_err("expected JSON parse failure");
1639                assert_eq!(err.kind, ErrorKind::Parse);
1640                assert!(err.message.contains(expected), "{name}: {}", err.message);
1641            }
1642        });
1643    }
1644
1645    #[test]
1646    fn renders_keyword_and_empty_name_collection_shapes() {
1647        Python::with_gil(|py| {
1648            let module = PyModule::from_code(
1649                py,
1650                pyo3::ffi::c_str!(
1651                    "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"
1652                ),
1653                pyo3::ffi::c_str!("test_json_keyword_empty_shapes.py"),
1654                pyo3::ffi::c_str!("test_json_keyword_empty_shapes"),
1655            )
1656            .unwrap();
1657
1658            let keyword_array = module.getattr("keyword_array").unwrap();
1659            let keyword_array = extract_template(py, &keyword_array, "json_t/json_t_str").unwrap();
1660            let rendered = render_document(py, &parse_template(&keyword_array).unwrap()).unwrap();
1661            assert_eq!(rendered.text, "[true, false, null]");
1662            assert_eq!(rendered.data, json!([true, false, null]));
1663
1664            let empty_name_nested_keywords = module.getattr("empty_name_nested_keywords").unwrap();
1665            let empty_name_nested_keywords =
1666                extract_template(py, &empty_name_nested_keywords, "json_t/json_t_str").unwrap();
1667            let rendered =
1668                render_document(py, &parse_template(&empty_name_nested_keywords).unwrap()).unwrap();
1669            assert_eq!(rendered.text, "{\"\": [null, true, false]}");
1670            assert_eq!(rendered.data, json!({"": [null, true, false]}));
1671
1672            let nested_empty_mix = module.getattr("nested_empty_mix").unwrap();
1673            let nested_empty_mix =
1674                extract_template(py, &nested_empty_mix, "json_t/json_t_str").unwrap();
1675            let rendered =
1676                render_document(py, &parse_template(&nested_empty_mix).unwrap()).unwrap();
1677            assert_eq!(rendered.data, json!({"a": [{}, [], "", 0, false, null]}));
1678
1679            let array_nested_mixed_scalars = module.getattr("array_nested_mixed_scalars").unwrap();
1680            let array_nested_mixed_scalars =
1681                extract_template(py, &array_nested_mixed_scalars, "json_t/json_t_str").unwrap();
1682            let rendered =
1683                render_document(py, &parse_template(&array_nested_mixed_scalars).unwrap()).unwrap();
1684            assert_eq!(
1685                rendered.data,
1686                json!([{"a": []}, {"b": {}}, "", 0, false, null])
1687            );
1688        });
1689    }
1690
1691    #[test]
1692    fn renders_top_level_whitespace_and_nested_number_shapes() {
1693        Python::with_gil(|py| {
1694            let module = PyModule::from_code(
1695                py,
1696                pyo3::ffi::c_str!(
1697                    "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"
1698                ),
1699                pyo3::ffi::c_str!("test_json_whitespace_shapes.py"),
1700                pyo3::ffi::c_str!("test_json_whitespace_shapes"),
1701            )
1702            .unwrap();
1703
1704            let top_ws_string = module.getattr("top_ws_string").unwrap();
1705            let top_ws_string = extract_template(py, &top_ws_string, "json_t/json_t_str").unwrap();
1706            let rendered = render_document(py, &parse_template(&top_ws_string).unwrap()).unwrap();
1707            assert_eq!(rendered.text, "\"x\"");
1708            assert_eq!(rendered.data, json!("x"));
1709
1710            let nested_top_ws = module.getattr("nested_top_ws").unwrap();
1711            let nested_top_ws = extract_template(py, &nested_top_ws, "json_t/json_t_str").unwrap();
1712            let rendered = render_document(py, &parse_template(&nested_top_ws).unwrap()).unwrap();
1713            assert_eq!(rendered.text, "{\"a\": [1, {\"b\": \"c\"}], \"\": \"\"}");
1714            assert_eq!(rendered.data, json!({"a": [1, {"b": "c"}], "": ""}));
1715
1716            let nested_number_whitespace = module.getattr("nested_number_whitespace").unwrap();
1717            let nested_number_whitespace =
1718                extract_template(py, &nested_number_whitespace, "json_t/json_t_str").unwrap();
1719            let rendered =
1720                render_document(py, &parse_template(&nested_number_whitespace).unwrap()).unwrap();
1721            assert_eq!(rendered.text, "{\"a\": [0, -0, 1.5E-2]}");
1722            assert_eq!(
1723                rendered.data,
1724                serde_json::from_str::<Value>("{\"a\": [0, -0, 1.5E-2]}").unwrap()
1725            );
1726        });
1727    }
1728
1729    #[test]
1730    fn renders_end_to_end_supported_positions_text_and_data() {
1731        Python::with_gil(|py| {
1732            let module = PyModule::from_code(
1733                py,
1734                pyo3::ffi::c_str!(
1735                    "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"
1736                ),
1737                pyo3::ffi::c_str!("test_json_end_to_end_positions.py"),
1738                pyo3::ffi::c_str!("test_json_end_to_end_positions"),
1739            )
1740            .unwrap();
1741
1742            let template = module.getattr("template").unwrap();
1743            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1744            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1745            assert_eq!(
1746                rendered.text,
1747                "{\"user\": {\"enabled\": true, \"count\": 2}, \"prefix-prefix\": \"item-suffix\", \"label\": \"prefix-suffix\"}"
1748            );
1749            assert_eq!(
1750                rendered.data,
1751                json!({
1752                    "user": {"enabled": true, "count": 2},
1753                    "prefix-prefix": "item-suffix",
1754                    "label": "prefix-suffix",
1755                })
1756            );
1757        });
1758    }
1759
1760    #[test]
1761    fn renders_rfc_8259_image_example_text_and_data() {
1762        Python::with_gil(|py| {
1763            let module = PyModule::from_code(
1764                py,
1765                pyo3::ffi::c_str!(
1766                    "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"
1767                ),
1768                pyo3::ffi::c_str!("test_json_rfc_8259_image_example.py"),
1769                pyo3::ffi::c_str!("test_json_rfc_8259_image_example"),
1770            )
1771            .unwrap();
1772
1773            let template = module.getattr("template").unwrap();
1774            let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1775            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1776            assert_eq!(
1777                rendered.text,
1778                "{\"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]}}"
1779            );
1780            assert_eq!(
1781                rendered.data,
1782                json!({
1783                    "Image": {
1784                        "Width": 800,
1785                        "Height": 600,
1786                        "Title": "View from 15th Floor",
1787                        "Thumbnail": {
1788                            "Url": "http://www.example.com/image/481989943",
1789                            "Height": 125,
1790                            "Width": 100,
1791                        },
1792                        "Animated": false,
1793                        "IDs": [116, 943, 234, 38793],
1794                    }
1795                })
1796            );
1797        });
1798    }
1799
1800    #[test]
1801    fn renders_rfc_8259_value_examples_text_and_data() {
1802        Python::with_gil(|py| {
1803            let module = PyModule::from_code(
1804                py,
1805                pyo3::ffi::c_str!(
1806                    "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"
1807                ),
1808                pyo3::ffi::c_str!("test_json_rfc_8259_value_examples.py"),
1809                pyo3::ffi::c_str!("test_json_rfc_8259_value_examples"),
1810            )
1811            .unwrap();
1812
1813            let array = module.getattr("array").unwrap();
1814            let array = extract_template(py, &array, "json_t/json_t_str").unwrap();
1815            let rendered = render_document(py, &parse_template(&array).unwrap()).unwrap();
1816            assert_eq!(
1817                rendered.text,
1818                "[{\"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\"}]"
1819            );
1820            assert_eq!(
1821                rendered.data,
1822                json!([
1823                    {
1824                        "precision": "zip",
1825                        "Latitude": 37.7668,
1826                        "Longitude": -122.3959,
1827                        "Address": "",
1828                        "City": "SAN FRANCISCO",
1829                        "State": "CA",
1830                        "Zip": "94107",
1831                        "Country": "US",
1832                    },
1833                    {
1834                        "precision": "zip",
1835                        "Latitude": 37.371991,
1836                        "Longitude": -122.026020,
1837                        "Address": "",
1838                        "City": "SUNNYVALE",
1839                        "State": "CA",
1840                        "Zip": "94085",
1841                        "Country": "US",
1842                    }
1843                ])
1844            );
1845
1846            for (name, expected_text, expected_value) in [
1847                ("string", "\"Hello world!\"", json!("Hello world!")),
1848                ("number", "42", json!(42)),
1849                ("boolean", "true", json!(true)),
1850            ] {
1851                let template = module.getattr(name).unwrap();
1852                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1853                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1854                assert_eq!(rendered.text, expected_text, "{name}");
1855                assert_eq!(rendered.data, expected_value, "{name}");
1856            }
1857        });
1858    }
1859
1860    #[test]
1861    fn renders_unicode_and_escape_mix_shapes() {
1862        Python::with_gil(|py| {
1863            let module = PyModule::from_code(
1864                py,
1865                pyo3::ffi::c_str!(
1866                    "unicode_array=t'[\"\\\\u2028\", \"\\\\u2029\", \"\\\\u00DF\"]'\nescape_object=t'{{\"x\":\"\\\\b\\\\u2028\\\\u2029\\\\/\"}}'\n"
1867                ),
1868                pyo3::ffi::c_str!("test_json_unicode_escape_mix.py"),
1869                pyo3::ffi::c_str!("test_json_unicode_escape_mix"),
1870            )
1871            .unwrap();
1872
1873            let unicode_array = module.getattr("unicode_array").unwrap();
1874            let unicode_array = extract_template(py, &unicode_array, "json_t/json_t_str").unwrap();
1875            let rendered = render_document(py, &parse_template(&unicode_array).unwrap()).unwrap();
1876            assert_eq!(rendered.text, "[\"\u{2028}\", \"\u{2029}\", \"ß\"]");
1877            assert_eq!(rendered.data, json!(["\u{2028}", "\u{2029}", "ß"]));
1878
1879            let escape_object = module.getattr("escape_object").unwrap();
1880            let escape_object = extract_template(py, &escape_object, "json_t/json_t_str").unwrap();
1881            let rendered = render_document(py, &parse_template(&escape_object).unwrap()).unwrap();
1882            assert_eq!(rendered.text, "{\"x\": \"\\b\u{2028}\u{2029}/\"}");
1883            assert_eq!(rendered.data, json!({"x": "\u{0008}\u{2028}\u{2029}/"}));
1884        });
1885    }
1886
1887    #[test]
1888    fn renders_control_escapes_and_reverse_solidus_variants() {
1889        Python::with_gil(|py| {
1890            let module = PyModule::from_code(
1891                py,
1892                pyo3::ffi::c_str!(
1893                    "escaped_controls=t'\"\\\\b\\\\f\\\\n\\\\r\\\\t\"'\nreverse_solidus_u=t'\"\\\\u005C/\"'\nescaped_quote_backslash=t'\"\\\\\\\"\\\\\\\\\"'\n"
1894                ),
1895                pyo3::ffi::c_str!("test_json_escape_variants.py"),
1896                pyo3::ffi::c_str!("test_json_escape_variants"),
1897            )
1898            .unwrap();
1899
1900            let escaped_controls = module.getattr("escaped_controls").unwrap();
1901            let escaped_controls =
1902                extract_template(py, &escaped_controls, "json_t/json_t_str").unwrap();
1903            let rendered =
1904                render_document(py, &parse_template(&escaped_controls).unwrap()).unwrap();
1905            assert_eq!(rendered.text, "\"\\b\\f\\n\\r\\t\"");
1906            assert_eq!(rendered.data, json!("\u{0008}\u{000c}\n\r\t"));
1907
1908            let reverse_solidus_u = module.getattr("reverse_solidus_u").unwrap();
1909            let reverse_solidus_u =
1910                extract_template(py, &reverse_solidus_u, "json_t/json_t_str").unwrap();
1911            let rendered =
1912                render_document(py, &parse_template(&reverse_solidus_u).unwrap()).unwrap();
1913            assert_eq!(rendered.text, "\"\\\\/\"");
1914            assert_eq!(rendered.data, json!("\\/"));
1915
1916            let escaped_quote_backslash = module.getattr("escaped_quote_backslash").unwrap();
1917            let escaped_quote_backslash =
1918                extract_template(py, &escaped_quote_backslash, "json_t/json_t_str").unwrap();
1919            let rendered =
1920                render_document(py, &parse_template(&escaped_quote_backslash).unwrap()).unwrap();
1921            assert_eq!(rendered.text, "\"\\\"\\\\\"");
1922            assert_eq!(rendered.data, json!("\"\\"));
1923        });
1924    }
1925
1926    #[test]
1927    fn renders_promoted_rows_and_fragment_text_shapes() {
1928        Python::with_gil(|py| {
1929            let module = PyModule::from_code(
1930                py,
1931                pyo3::ffi::c_str!(
1932                    "left='prefix'\nright='suffix'\nrows=[{'name': 'one'}, {'name': 'two'}]\nfragment=t'{{\"label\": {left}-{right}}}'\npromoted=t'{rows}'\n"
1933                ),
1934                pyo3::ffi::c_str!("test_json_promoted_rows.py"),
1935                pyo3::ffi::c_str!("test_json_promoted_rows"),
1936            )
1937            .unwrap();
1938
1939            let fragment = module.getattr("fragment").unwrap();
1940            let fragment = extract_template(py, &fragment, "json_t/json_t_str").unwrap();
1941            let rendered = render_document(py, &parse_template(&fragment).unwrap()).unwrap();
1942            assert_eq!(rendered.text, "{\"label\": \"prefix-suffix\"}");
1943            assert_eq!(rendered.data, json!({"label": "prefix-suffix"}));
1944
1945            let promoted = module.getattr("promoted").unwrap();
1946            let promoted = extract_template(py, &promoted, "json_t/json_t_str").unwrap();
1947            let rendered = render_document(py, &parse_template(&promoted).unwrap()).unwrap();
1948            assert_eq!(rendered.text, r#"[{"name": "one"}, {"name": "two"}]"#);
1949            assert_eq!(rendered.data, json!([{"name": "one"}, {"name": "two"}]));
1950        });
1951    }
1952
1953    #[test]
1954    fn renders_negative_zero_number_shapes() {
1955        Python::with_gil(|py| {
1956            let module = PyModule::from_code(
1957                py,
1958                pyo3::ffi::c_str!(
1959                    "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"
1960                ),
1961                pyo3::ffi::c_str!("test_json_negative_zero_shapes.py"),
1962                pyo3::ffi::c_str!("test_json_negative_zero_shapes"),
1963            )
1964            .unwrap();
1965
1966            for (name, expected_text, expected_data) in [
1967                ("neg_zero", "-0", json!(-0.0)),
1968                ("neg_zero_float", "-0.0", json!(-0.0)),
1969                ("neg_zero_exp", "-0E0", json!(-0.0)),
1970            ] {
1971                let template = module.getattr(name).unwrap();
1972                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
1973                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
1974                assert_eq!(rendered.text, expected_text);
1975                assert_eq!(rendered.data, expected_data);
1976            }
1977
1978            let combo = module.getattr("combo").unwrap();
1979            let combo = extract_template(py, &combo, "json_t/json_t_str").unwrap();
1980            let rendered = render_document(py, &parse_template(&combo).unwrap()).unwrap();
1981            assert_eq!(rendered.text, "{\"a\": [0, -0, -0.0, 1e0, -1E-0]}");
1982            assert_eq!(
1983                rendered.data,
1984                serde_json::from_str::<Value>("{\"a\": [0, -0, -0.0, 1e0, -1E-0]}").unwrap()
1985            );
1986        });
1987    }
1988
1989    #[test]
1990    fn renders_top_level_keywords_and_empty_collections() {
1991        Python::with_gil(|py| {
1992            let module = PyModule::from_code(
1993                py,
1994                pyo3::ffi::c_str!(
1995                    "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"
1996                ),
1997                pyo3::ffi::c_str!("test_json_top_level_keywords.py"),
1998                pyo3::ffi::c_str!("test_json_top_level_keywords"),
1999            )
2000            .unwrap();
2001
2002            for (name, expected_text, expected_data) in [
2003                ("top_bool_ws", "true", json!(true)),
2004                ("top_null_ws", "null", Value::Null),
2005                ("empty_object", "{}", json!({})),
2006                ("empty_array", "[]", json!([])),
2007            ] {
2008                let template = module.getattr(name).unwrap();
2009                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
2010                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
2011                assert_eq!(rendered.text, expected_text);
2012                assert_eq!(rendered.data, expected_data);
2013            }
2014        });
2015    }
2016
2017    #[test]
2018    fn renders_escape_unicode_and_keyword_text_shapes() {
2019        Python::with_gil(|py| {
2020            let module = PyModule::from_code(
2021                py,
2022                pyo3::ffi::c_str!(
2023                    "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"
2024                ),
2025                pyo3::ffi::c_str!("test_json_escape_unicode_keyword_shapes.py"),
2026                pyo3::ffi::c_str!("test_json_escape_unicode_keyword_shapes"),
2027            )
2028            .unwrap();
2029
2030            for (name, expected_text, expected_data) in [
2031                ("top_bool_ws", "true", json!(true)),
2032                ("top_null_ws", "null", Value::Null),
2033                (
2034                    "array_with_line_sep",
2035                    "[\"\u{2028}\", \"\u{2029}\"]",
2036                    json!(["\u{2028}", "\u{2029}"]),
2037                ),
2038                (
2039                    "unicode_mix_nested_obj",
2040                    "{\"x\": {\"a\": \"\\\\\", \"b\": \"ß\", \"c\": \"\u{2029}\"}}",
2041                    json!({"x": {"a": "\\", "b": "ß", "c": "\u{2029}"}}),
2042                ),
2043                (
2044                    "keyword_array",
2045                    "[true, false, null]",
2046                    json!([true, false, null]),
2047                ),
2048            ] {
2049                let template = module.getattr(name).unwrap();
2050                let template = extract_template(py, &template, "json_t/json_t_str").unwrap();
2051                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
2052                assert_eq!(rendered.text, expected_text, "{name}");
2053                assert_eq!(rendered.data, expected_data, "{name}");
2054            }
2055        });
2056    }
2057
2058    #[test]
2059    fn test_parse_rendered_json_surfaces_parse_failures() {
2060        let err = parse_rendered_json("{\"a\":,}").expect_err("expected JSON parse failure");
2061        assert_eq!(err.kind, ErrorKind::Parse);
2062        assert!(err.message.contains("Rendered JSON could not be reparsed"));
2063    }
2064}