1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use crate::ast::{
19
    GraphQl, GraphQlVariables, MultilineString, MultilineStringKind, SourceInfo, Template,
20
    TemplateElement, Whitespace,
21
};
22
use crate::combinator::{choice, optional, zero_or_more};
23
use crate::reader::Reader;
24
use crate::typing::ToSource;
25

            
26
use super::error::{ParseError, ParseErrorKind};
27
use super::json::object_value;
28
use super::primitives::{literal, newline, try_literal, zero_or_more_spaces};
29
use super::{ParseResult, template};
30

            
31
27104
pub fn multiline_string(reader: &mut Reader) -> ParseResult<MultilineString> {
32
27104
    try_literal("```", reader)?;
33

            
34
916
    choice(
35
916
        &[json_text, xml_text, graphql, raw_text, plain_text],
36
916
        reader,
37
    )
38
}
39

            
40
/// Parses a multiline text bloc.
41
///
42
/// Plain text can have a language hint `lang` (like <code>&#96;&#96;&#96;json</code>,
43
/// or <code>&#96;&#96;&#96;xml</code>) to give some semantic to the string bloc. All multiline
44
/// variants don't escape chars (`\n` is a literal two chars `\` and `n`) and evaluate variables
45
/// (`raw` multiline is the only exception that doesn't evaluate variables).
46
///
47
/// ## Example of a text bloc without language hint
48
///
49
/// ~~~hurl
50
/// GET https://foo.com
51
/// ```
52
/// one
53
/// two
54
/// three
55
/// ```
56
/// ~~~
57
///
58
/// ## Example of a text bloc with JSON language hint
59
///
60
/// ~~~hurl
61
/// GET https://foo.com
62
/// ```json
63
/// {
64
///   "name: "bob"
65
/// }
66
/// ```
67
/// ~~~
68
///
69
///
70
2504
fn text(
71
2504
    lang: Option<&str>,
72
2504
    templatized: bool,
73
2504
    reader: &mut Reader,
74
2504
) -> ParseResult<(Whitespace, Whitespace, Template)> {
75
2504
    if let Some(lang) = lang {
76
2156
        try_literal(lang, reader)?;
77
    }
78
724
    let space = zero_or_more_spaces(reader)?;
79

            
80
    // We check that we have a newline here, and raise error if we have a language hint.
81
    // The language hints that we supported (`json`, `raw` etc...) have been processed by
82
    // earlier parse functions.
83
724
    let start = reader.cursor();
84
724
    let hint = language_hint(reader)?;
85
724
    if !hint.is_empty() {
86
4
        return Err(ParseError {
87
4
            pos: start.pos,
88
4
            recoverable: false,
89
4
            kind: ParseErrorKind::MultilineLanguageHint(hint),
90
4
        });
91
    }
92

            
93
720
    let newline = newline(reader)?;
94
720
    let value = multiline_string_value(templatized, reader)?;
95
720
    Ok((space, newline, value))
96
}
97

            
98
/// Parses a plain text multilines block.
99
///
100
/// ## Example
101
///
102
/// ~~~hurl
103
/// GET https://foo.com
104
/// ```
105
/// toto
106
/// ```
107
/// ~~~
108
348
fn plain_text(reader: &mut Reader) -> ParseResult<MultilineString> {
109
348
    let (space, newline, text) = text(None, true, reader)?;
110
344
    let kind = MultilineStringKind::Text(text);
111
344
    Ok(MultilineString {
112
344
        space,
113
344
        newline,
114
344
        kind,
115
344
    })
116
}
117

            
118
/// Parses a plain text multilines block.
119
///
120
/// Contrary to [`plain_text`], this function doesn't templatize the text (i.e. the inline block
121
/// `{{my_variable}}` are not transformed to template elements, and remains simple text.
122
///
123
/// ## Example
124
///
125
/// ~~~hurl
126
/// GET https://foo.com
127
/// ```
128
/// toto
129
/// ```
130
/// ~~~
131
468
fn raw_text(reader: &mut Reader) -> ParseResult<MultilineString> {
132
468
    let (space, newline, text) = text(Some("raw"), false, reader)?;
133
120
    let kind = MultilineStringKind::Raw(text);
134
120
    Ok(MultilineString {
135
120
        space,
136
120
        newline,
137
120
        kind,
138
120
    })
139
}
140

            
141
/// Parses a JSON multilines block.
142
///
143
/// ## Example
144
///
145
/// ~~~hurl
146
/// GET https://foo.com
147
/// ```json
148
/// {"foo":"bar"}
149
/// ```
150
/// ~~~
151
916
fn json_text(reader: &mut Reader) -> ParseResult<MultilineString> {
152
916
    let (space, newline, text) = text(Some("json"), true, reader)?;
153
144
    let kind = MultilineStringKind::Json(text);
154
144
    Ok(MultilineString {
155
144
        space,
156
144
        newline,
157
144
        kind,
158
144
    })
159
}
160

            
161
/// Parses an XML multilines block.
162
///
163
/// ## Example
164
///
165
/// ~~~hurl
166
/// GET https://foo.com
167
/// ```xml
168
/// <?xml version="1.0"?>
169
///     <book id="bk101">
170
///         <author>Gambardella, Matthew</author>
171
///     </book>
172
/// </xml>
173
/// ```
174
/// ~~~
175
772
fn xml_text(reader: &mut Reader) -> ParseResult<MultilineString> {
176
772
    let (space, newline, text) = text(Some("xml"), true, reader)?;
177
112
    let kind = MultilineStringKind::Xml(text);
178
112
    Ok(MultilineString {
179
112
        space,
180
112
        newline,
181
112
        kind,
182
112
    })
183
}
184

            
185
/// Parses a GraphQL multilines block.
186
///
187
/// ## Example
188
///
189
/// ~~~hurl
190
/// GET https://foo.com
191
/// ```graphql
192
/// query getCity($city: String) {
193
///     cities(name: $city) {
194
///         population
195
///         weather {
196
///             temperature
197
///             precipitation
198
///         }
199
///     }
200
/// }
201
/// ```
202
/// ~~~
203
660
fn graphql(reader: &mut Reader) -> ParseResult<MultilineString> {
204
660
    try_literal("graphql", reader)?;
205
192
    drop(try_literal(",", reader));
206
192
    let space = zero_or_more_spaces(reader)?;
207
192
    let newline = newline(reader)?;
208

            
209
192
    let mut chars = vec![];
210

            
211
192
    let start = reader.cursor();
212
16828
    while reader.peek_n(3) != "```" && !reader.is_eof() {
213
16684
        let pos = reader.cursor().pos;
214
16684
        let c = reader.read().unwrap();
215
16684
        chars.push((c, c.to_string(), pos));
216
16684
        if c == '\n' {
217
1616
            let end = reader.cursor();
218
1616
            let variables = optional(graphql_variables, reader)?;
219
1616
            match variables {
220
1568
                None => continue,
221
48
                Some(variables) => {
222
48
                    literal("```", reader)?;
223

            
224
48
                    let encoded_string = template::EncodedString {
225
48
                        source_info: SourceInfo::new(start.pos, end.pos),
226
48
                        chars: chars.clone(),
227
48
                    };
228

            
229
48
                    let elements = template::templatize(encoded_string)?;
230
48
                    let template =
231
48
                        Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
232
48
                    let kind = MultilineStringKind::GraphQl(GraphQl {
233
48
                        value: template,
234
48
                        variables: Some(variables),
235
48
                    });
236
48
                    return Ok(MultilineString {
237
48
                        space,
238
48
                        newline,
239
48
                        kind,
240
48
                    });
241
                }
242
            }
243
        }
244
    }
245
144
    let end = reader.cursor();
246
144
    literal("```", reader)?;
247

            
248
144
    let encoded_string = template::EncodedString {
249
144
        source_info: SourceInfo::new(start.pos, end.pos),
250
144
        chars,
251
144
    };
252

            
253
144
    let elements = template::templatize(encoded_string)?;
254
144
    let template = Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
255
144
    let kind = MultilineStringKind::GraphQl(GraphQl {
256
144
        value: template,
257
144
        variables: None,
258
144
    });
259
144
    Ok(MultilineString {
260
144
        space,
261
144
        newline,
262
144
        kind,
263
144
    })
264
}
265

            
266
96
fn whitespace(reader: &mut Reader) -> ParseResult<Whitespace> {
267
96
    let start = reader.cursor();
268
96
    match reader.read() {
269
        None => Err(ParseError::new(start.pos, true, ParseErrorKind::Space)),
270
96
        Some(c) => {
271
96
            if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
272
48
                Ok(Whitespace {
273
48
                    value: c.to_string(),
274
48
                    source_info: SourceInfo::new(start.pos, reader.cursor().pos),
275
48
                })
276
            } else {
277
48
                Err(ParseError::new(start.pos, true, ParseErrorKind::Space))
278
            }
279
        }
280
    }
281
}
282

            
283
48
fn zero_or_more_whitespaces(reader: &mut Reader) -> ParseResult<Whitespace> {
284
48
    let start = reader.cursor();
285
48
    match zero_or_more(whitespace, reader) {
286
48
        Ok(v) => {
287
48
            let s = v.iter().map(|x| x.value.clone()).collect();
288
48
            Ok(Whitespace {
289
48
                value: s,
290
48
                source_info: SourceInfo::new(start.pos, reader.cursor().pos),
291
48
            })
292
        }
293
        Err(e) => Err(e),
294
    }
295
}
296

            
297
724
fn language_hint(reader: &mut Reader) -> ParseResult<String> {
298
736
    let hint = reader.read_while(|c| c != '\n' && c != '\r' && c != '`');
299
724
    Ok(hint)
300
}
301

            
302
1616
fn graphql_variables(reader: &mut Reader) -> ParseResult<GraphQlVariables> {
303
1616
    try_literal("variables", reader)?;
304
48
    let space = zero_or_more_spaces(reader)?;
305
48
    let start = reader.cursor();
306
48
    let object = object_value(reader, 0);
307
48
    let value = match object {
308
48
        Ok(obj) => obj,
309
        Err(_) => {
310
            return Err(ParseError::new(
311
                start.pos,
312
                false,
313
                ParseErrorKind::GraphQlVariables,
314
            ));
315
        }
316
    };
317
48
    let whitespace = zero_or_more_whitespaces(reader)?;
318
48
    Ok(GraphQlVariables {
319
48
        space,
320
48
        value,
321
48
        whitespace,
322
48
    })
323
}
324

            
325
720
fn multiline_string_value(templatize: bool, reader: &mut Reader) -> ParseResult<Template> {
326
720
    let mut chars = vec![];
327

            
328
720
    let start = reader.cursor();
329
88408
    while reader.peek_n(3) != "```" && !reader.is_eof() {
330
87688
        let pos = reader.cursor().pos;
331
87688
        let c = reader.read().unwrap();
332
87688
        chars.push((c, c.to_string(), pos));
333
    }
334
720
    let end = reader.cursor();
335
720
    literal("```", reader)?;
336

            
337
720
    let source_info = SourceInfo::new(start.pos, end.pos);
338

            
339
720
    let elements = if templatize {
340
600
        let encoded_string = template::EncodedString { source_info, chars };
341
600
        template::templatize(encoded_string)?
342
    } else {
343
120
        let source = chars.iter().map(|(c, _, _)| c).collect::<String>();
344
120
        let template = TemplateElement::String {
345
120
            value: source.to_string(),
346
120
            source: source.to_source(),
347
120
        };
348
120
        vec![template]
349
    };
350
720
    let template = Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
351
720
    Ok(template)
352
}
353

            
354
#[cfg(test)]
355
mod tests {
356
    use super::*;
357
    use crate::ast::{
358
        Expr, ExprKind, JsonObjectElement, JsonValue, JsonValueKind, Placeholder, TemplateElement,
359
        Variable,
360
    };
361
    use crate::reader::{CharPos, Pos};
362
    use crate::types::ToSource;
363

            
364
    #[test]
365
    fn test_multiline_string_text() {
366
        let mut reader = Reader::new("```\nline1\nline2\nline3\n```");
367
        assert_eq!(
368
            multiline_string(&mut reader).unwrap(),
369
            MultilineString {
370
                space: Whitespace {
371
                    value: String::new(),
372
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
373
                },
374
                newline: Whitespace {
375
                    value: "\n".to_string(),
376
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
377
                },
378
                kind: MultilineStringKind::Text(Template::new(
379
                    None,
380
                    vec![TemplateElement::String {
381
                        value: "line1\nline2\nline3\n".to_string(),
382
                        source: "line1\nline2\nline3\n".to_source(),
383
                    }],
384
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
385
                )),
386
            }
387
        );
388

            
389
        let mut reader = Reader::new("```         \nline1\nline2\nline3\n```");
390
        assert_eq!(
391
            multiline_string(&mut reader).unwrap(),
392
            MultilineString {
393
                space: Whitespace {
394
                    value: "         ".to_string(),
395
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 13)),
396
                },
397
                newline: Whitespace {
398
                    value: "\n".to_string(),
399
                    source_info: SourceInfo::new(Pos::new(1, 13), Pos::new(2, 1)),
400
                },
401
                kind: MultilineStringKind::Text(Template::new(
402
                    None,
403
                    vec![TemplateElement::String {
404
                        value: "line1\nline2\nline3\n".to_string(),
405
                        source: "line1\nline2\nline3\n".to_source(),
406
                    }],
407
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
408
                )),
409
            }
410
        );
411
    }
412

            
413
    #[test]
414
    fn test_multiline_string_text_with_variables() {
415
        let mut reader = Reader::new("```\nfoo\n{{var_1}} bar {{var2}}\nline3\n```");
416
        assert_eq!(
417
            multiline_string(&mut reader).unwrap(),
418
            MultilineString {
419
                space: Whitespace {
420
                    value: String::new(),
421
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
422
                },
423
                newline: Whitespace {
424
                    value: "\n".to_string(),
425
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
426
                },
427
                kind: MultilineStringKind::Text(Template::new(
428
                    None,
429
                    vec![
430
                        TemplateElement::String {
431
                            value: "foo\n".to_string(),
432
                            source: "foo\n".to_source()
433
                        },
434
                        TemplateElement::Placeholder(Placeholder {
435
                            space0: Whitespace {
436
                                value: String::new(),
437
                                source_info: SourceInfo::new(Pos::new(3, 3), Pos::new(3, 3)),
438
                            },
439
                            expr: Expr {
440
                                source_info: SourceInfo::new(Pos::new(3, 3), Pos::new(3, 8)),
441
                                kind: ExprKind::Variable(Variable {
442
                                    name: "var_1".to_string(),
443
                                    source_info: SourceInfo::new(Pos::new(3, 3), Pos::new(3, 8)),
444
                                }),
445
                            },
446
                            space1: Whitespace {
447
                                value: String::new(),
448
                                source_info: SourceInfo::new(Pos::new(3, 8), Pos::new(3, 8)),
449
                            },
450
                        }),
451
                        TemplateElement::String {
452
                            value: " bar ".to_string(),
453
                            source: " bar ".to_source()
454
                        },
455
                        TemplateElement::Placeholder(Placeholder {
456
                            space0: Whitespace {
457
                                value: String::new(),
458
                                source_info: SourceInfo::new(Pos::new(3, 17), Pos::new(3, 17)),
459
                            },
460
                            expr: Expr {
461
                                source_info: SourceInfo::new(Pos::new(3, 17), Pos::new(3, 21)),
462
                                kind: ExprKind::Variable(Variable {
463
                                    name: "var2".to_string(),
464
                                    source_info: SourceInfo::new(Pos::new(3, 17), Pos::new(3, 21)),
465
                                }),
466
                            },
467
                            space1: Whitespace {
468
                                value: String::new(),
469
                                source_info: SourceInfo::new(Pos::new(3, 21), Pos::new(3, 21)),
470
                            },
471
                        }),
472
                        TemplateElement::String {
473
                            value: "\nline3\n".to_string(),
474
                            source: "\nline3\n".to_source()
475
                        },
476
                    ],
477
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
478
                )),
479
            }
480
        );
481
    }
482

            
483
    #[test]
484
    fn test_multiline_string_text_with_variables_error() {
485
        let mut reader = Reader::new("```\nline1\n{{bar\n```");
486
        assert_eq!(
487
            multiline_string(&mut reader).unwrap_err(),
488
            ParseError::new(
489
                Pos::new(4, 1),
490
                false,
491
                ParseErrorKind::Expecting {
492
                    value: "}}".to_string()
493
                }
494
            )
495
        );
496
    }
497

            
498
    #[test]
499
    fn test_multiline_string_json() {
500
        let mut reader = Reader::new("```json\nline1\nline2\nline3\n```");
501
        assert_eq!(
502
            multiline_string(&mut reader).unwrap(),
503
            MultilineString {
504
                space: Whitespace {
505
                    value: String::new(),
506
                    source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(1, 8)),
507
                },
508
                newline: Whitespace {
509
                    value: "\n".to_string(),
510
                    source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(2, 1)),
511
                },
512
                kind: MultilineStringKind::Json(Template::new(
513
                    None,
514
                    vec![TemplateElement::String {
515
                        value: "line1\nline2\nline3\n".to_string(),
516
                        source: "line1\nline2\nline3\n".to_source(),
517
                    }],
518
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
519
                )),
520
            }
521
        );
522

            
523
        // JSON multilines don't escape test (so Hurl Unicode literals are not supported)
524
        let mut reader = Reader::new(
525
            r#"```json
526
{
527
  "g_clef": "\u{1D11E}"
528
}
529
```"#,
530
        );
531
        assert_eq!(
532
            multiline_string(&mut reader).unwrap(),
533
            MultilineString {
534
                space: Whitespace {
535
                    value: String::new(),
536
                    source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(1, 8)),
537
                },
538
                newline: Whitespace {
539
                    value: "\n".to_string(),
540
                    source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(2, 1)),
541
                },
542
                kind: MultilineStringKind::Json(Template::new(
543
                    None,
544
                    vec![TemplateElement::String {
545
                        value: "{\n  \"g_clef\": \"\\u{1D11E}\"\n}\n".to_string(),
546
                        source: "{\n  \"g_clef\": \"\\u{1D11E}\"\n}\n".to_source(),
547
                    }],
548
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
549
                )),
550
            }
551
        );
552
    }
553

            
554
    #[test]
555
    fn test_multiline_string_graphql() {
556
        let mut reader = Reader::new("```graphql\nline1\nline2\nline3\n```");
557
        assert_eq!(
558
            multiline_string(&mut reader).unwrap(),
559
            MultilineString {
560
                space: Whitespace {
561
                    value: String::new(),
562
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
563
                },
564
                newline: Whitespace {
565
                    value: "\n".to_string(),
566
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(2, 1)),
567
                },
568
                kind: MultilineStringKind::GraphQl(GraphQl {
569
                    value: Template::new(
570
                        None,
571
                        vec![TemplateElement::String {
572
                            value: "line1\nline2\nline3\n".to_string(),
573
                            source: "line1\nline2\nline3\n".to_source(),
574
                        }],
575
                        SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
576
                    ),
577
                    variables: None,
578
                }),
579
            }
580
        );
581

            
582
        let mut reader = Reader::new("```graphql      \nline1\nline2\nline3\n```");
583
        assert_eq!(
584
            multiline_string(&mut reader).unwrap(),
585
            MultilineString {
586
                space: Whitespace {
587
                    value: "      ".to_string(),
588
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 17)),
589
                },
590
                newline: Whitespace {
591
                    value: "\n".to_string(),
592
                    source_info: SourceInfo::new(Pos::new(1, 17), Pos::new(2, 1)),
593
                },
594
                kind: MultilineStringKind::GraphQl(GraphQl {
595
                    value: Template::new(
596
                        None,
597
                        vec![TemplateElement::String {
598
                            value: "line1\nline2\nline3\n".to_string(),
599
                            source: "line1\nline2\nline3\n".to_source(),
600
                        }],
601
                        SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
602
                    ),
603
                    variables: None,
604
                }),
605
            }
606
        );
607
    }
608

            
609
    #[test]
610
    fn test_multiline_string_failed() {
611
        let data = [
612
            "```hexaaa\nline1\nline2\nline3\n```",
613
            "```aaa\nline1\nline2\nline3\n```",
614
        ];
615

            
616
        for text in data.iter() {
617
            let mut reader = Reader::new(text);
618
            assert!(multiline_string(&mut reader).is_err());
619
        }
620
    }
621

            
622
    #[test]
623
    fn test_multiline_string_empty() {
624
        let mut reader = Reader::new("```\n```");
625
        assert_eq!(
626
            multiline_string(&mut reader).unwrap(),
627
            MultilineString {
628
                space: Whitespace {
629
                    value: String::new(),
630
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
631
                },
632
                newline: Whitespace {
633
                    value: "\n".to_string(),
634
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
635
                },
636
                kind: MultilineStringKind::Text(Template::new(
637
                    None,
638
                    vec![],
639
                    SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1))
640
                )),
641
            }
642
        );
643
        let mut reader = Reader::new("```\r\n```");
644
        assert_eq!(
645
            multiline_string(&mut reader).unwrap(),
646
            MultilineString {
647
                space: Whitespace {
648
                    value: String::new(),
649
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
650
                },
651
                newline: Whitespace {
652
                    value: "\r\n".to_string(),
653
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
654
                },
655
                kind: MultilineStringKind::Text(Template::new(
656
                    None,
657
                    vec![],
658
                    SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1)),
659
                )),
660
            }
661
        );
662
    }
663

            
664
    #[test]
665
    fn test_multiline_string_hello_error() {
666
        let mut reader = Reader::new("```Hello World!```");
667
        let error = multiline_string(&mut reader).unwrap_err();
668
        assert_eq!(error.pos, Pos::new(1, 4));
669
        assert_eq!(
670
            error.kind,
671
            ParseErrorKind::MultilineLanguageHint("Hello World!".to_string())
672
        );
673
    }
674

            
675
    #[test]
676
    fn test_multiline_string_csv() {
677
        let mut reader = Reader::new("```\nline1\nline2\nline3\n```");
678
        assert_eq!(
679
            multiline_string(&mut reader).unwrap(),
680
            MultilineString {
681
                space: Whitespace {
682
                    value: String::new(),
683
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
684
                },
685
                newline: Whitespace {
686
                    value: "\n".to_string(),
687
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
688
                },
689
                kind: MultilineStringKind::Text(Template::new(
690
                    None,
691
                    vec![TemplateElement::String {
692
                        value: "line1\nline2\nline3\n".to_string(),
693
                        source: "line1\nline2\nline3\n".to_source(),
694
                    }],
695
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
696
                )),
697
            }
698
        );
699
    }
700

            
701
    #[test]
702
    fn test_multiline_string_one_empty_line() {
703
        let mut reader = Reader::new("```\n\n```");
704
        assert_eq!(
705
            multiline_string(&mut reader).unwrap(),
706
            MultilineString {
707
                kind: MultilineStringKind::Text(Template::new(
708
                    None,
709
                    vec![TemplateElement::String {
710
                        value: "\n".to_string(),
711
                        source: "\n".to_source(),
712
                    }],
713
                    SourceInfo::new(Pos::new(2, 1), Pos::new(3, 1)),
714
                )),
715
                space: Whitespace {
716
                    value: String::new(),
717
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
718
                },
719
                newline: Whitespace {
720
                    value: "\n".to_string(),
721
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
722
                },
723
            }
724
        );
725

            
726
        // One cr
727
        let mut reader = Reader::new("```\n\r\n````");
728
        assert_eq!(
729
            multiline_string(&mut reader).unwrap(),
730
            MultilineString {
731
                space: Whitespace {
732
                    value: String::new(),
733
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
734
                },
735
                newline: Whitespace {
736
                    value: "\n".to_string(),
737
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
738
                },
739
                kind: MultilineStringKind::Text(Template::new(
740
                    None,
741
                    vec![TemplateElement::String {
742
                        value: "\r\n".to_string(),
743
                        source: "\r\n".to_source(),
744
                    }],
745
                    SourceInfo::new(Pos::new(2, 1), Pos::new(3, 1)),
746
                )),
747
            }
748
        );
749
    }
750

            
751
    #[test]
752
    fn test_multiline_string_error() {
753
        let mut reader = Reader::new("xxx");
754
        let error = multiline_string(&mut reader).err().unwrap();
755
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
756
        assert_eq!(
757
            error.kind,
758
            ParseErrorKind::Expecting {
759
                value: "```".to_string()
760
            }
761
        );
762
        assert!(error.recoverable);
763

            
764
        let mut reader = Reader::new("```\nxxx");
765
        let error = multiline_string(&mut reader).err().unwrap();
766
        assert_eq!(error.pos, Pos { line: 2, column: 4 });
767
        assert_eq!(
768
            error.kind,
769
            ParseErrorKind::Expecting {
770
                value: "```".to_string()
771
            }
772
        );
773
        assert!(!error.recoverable);
774

            
775
        let mut reader = Reader::new("```xxx");
776
        let error = multiline_string(&mut reader).err().unwrap();
777
        assert_eq!(error.pos, Pos { line: 1, column: 4 });
778
        assert_eq!(
779
            error.kind,
780
            ParseErrorKind::MultilineLanguageHint("xxx".to_string())
781
        );
782
        assert!(!error.recoverable);
783
    }
784

            
785
    #[test]
786
    fn test_multiline_string_value() {
787
        let mut reader = Reader::new("```");
788
        assert_eq!(
789
            multiline_string_value(true, &mut reader).unwrap(),
790
            Template::new(
791
                None,
792
                vec![],
793
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1))
794
            )
795
        );
796
        assert_eq!(reader.cursor().index, CharPos(3));
797

            
798
        let mut reader = Reader::new("hello```");
799
        assert_eq!(
800
            multiline_string_value(true, &mut reader).unwrap(),
801
            Template::new(
802
                None,
803
                vec![TemplateElement::String {
804
                    value: "hello".to_string(),
805
                    source: "hello".to_source(),
806
                }],
807
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6))
808
            )
809
        );
810
        assert_eq!(reader.cursor().index, CharPos(8));
811
    }
812

            
813
    #[test]
814
    fn test_multiline_string_graphql_with_variables() {
815
        let mut reader = Reader::new(
816
            r#"```graphql
817
query Human($name: String!) {
818
  human(name: $name) {
819
    name
820
    height(unit: FOOT)
821
}
822

            
823
variables {
824
  "name": "Han Solo"
825
}
826
```"#,
827
        );
828

            
829
        assert_eq!(
830
            multiline_string(&mut reader).unwrap(),
831
            MultilineString {
832
                space: Whitespace {
833
                    value: String::new(),
834
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
835
                },
836
                newline: Whitespace {
837
                    value: "\n".to_string(),
838
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(2, 1)),
839
                },
840
                kind: MultilineStringKind::GraphQl(GraphQl {
841
                    value: Template::new(
842
                        None,
843
                        vec![TemplateElement::String {
844
                            value: "query Human($name: String!) {\n  human(name: $name) {\n    name\n    height(unit: FOOT)\n}\n\n".to_string(),
845
                            source:
846
                            "query Human($name: String!) {\n  human(name: $name) {\n    name\n    height(unit: FOOT)\n}\n\n".to_source()
847
                        }],
848
                        SourceInfo::new(Pos::new(2, 1), Pos::new(8, 1)),
849
                    ),
850
                    variables: Some(GraphQlVariables {
851
                        space: Whitespace {
852
                            value: " ".to_string(),
853
                            source_info: SourceInfo::new(Pos::new(8, 10), Pos::new(8, 11)),
854
                        },
855
                        value: JsonValue::new(
856
                            SourceInfo::new(Pos::new(8, 11), Pos::new(10, 2)),
857
                            JsonValueKind::Object {
858
                            space0: "\n  ".to_string(),
859
                            elements: vec![JsonObjectElement {
860
                                space0: String::new(),
861
                                name: Template::new(
862
                                    Some('"'),
863
                                    vec![
864
                                        TemplateElement::String {
865
                                            value: "name".to_string(),
866
                                            source: "name".to_source()
867
                                        }
868
                                    ],
869
                                    SourceInfo::new(Pos::new(9, 4), Pos::new(9, 8))
870
                                ),
871
                                space1: String::new(),
872
                                space2: " ".to_string(),
873
                                value: JsonValue::new(
874
                                    SourceInfo::new(Pos::new(9, 11), Pos::new(9, 21)),
875
                                    JsonValueKind::String(Template::new(
876
                                        Some('"'),
877
                                        vec![TemplateElement::String {
878
                                            value: "Han Solo".to_string(),
879
                                            source: "Han Solo".to_source()
880
                                        }],
881
                                        SourceInfo::new(Pos::new(9, 12), Pos::new(9, 20))
882
                                    )),
883
                                ),
884
                                space3: "\n".to_string()
885
                            }]
886
                            },
887
                        ),
888
                        whitespace: Whitespace {
889
                            value: "\n".to_string(),
890
                            source_info: SourceInfo::new(Pos::new(10, 2), Pos::new(11, 1))
891
                        }
892
                    })
893
                }),
894
            }
895
        );
896
    }
897

            
898
    #[test]
899
    fn test_multiline_string_graphql_with_variables_error() {
900
        let mut reader = Reader::new(
901
            r#"```graphql
902
query Human($name: String!) {
903
  human(name: $name) {
904
    name
905
    height(unit: FOOT)
906
}
907

            
908
variables
909
```"#,
910
        );
911

            
912
        let error = multiline_string(&mut reader).err().unwrap();
913
        assert_eq!(
914
            error,
915
            ParseError::new(Pos::new(8, 10), false, ParseErrorKind::GraphQlVariables)
916
        );
917
    }
918

            
919
    #[test]
920
    fn test_multiline_string_graphql_with_variables_not_an_object() {
921
        let mut reader = Reader::new(
922
            r#"```graphql
923
query Human($name: String!) {
924
  human(name: $name) {
925
    name
926
    height(unit: FOOT)
927
}
928

            
929
variables [
930
  "one",
931
  "two",
932
  "three"
933
]
934
```"#,
935
        );
936

            
937
        let error = multiline_string(&mut reader).err().unwrap();
938
        assert_eq!(
939
            error,
940
            ParseError::new(Pos::new(8, 11), false, ParseErrorKind::GraphQlVariables)
941
        );
942
    }
943
}