Skip to main content

tstring_json/
lib.rs

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