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::parser::json::object_value;
24
use crate::parser::primitives::{literal, newline, try_literal, zero_or_more_spaces};
25
use crate::parser::{template, ParseError, ParseErrorKind, ParseResult};
26
use crate::reader::Reader;
27
use crate::typing::ToSource;
28

            
29
30100
pub fn multiline_string(reader: &mut Reader) -> ParseResult<MultilineString> {
30
30100
    try_literal("```", reader)?;
31

            
32
1125
    choice(
33
1125
        &[json_text, xml_text, graphql, raw_text, plain_text],
34
1125
        reader,
35
    )
36
}
37

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

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

            
91
880
    let newline = newline(reader)?;
92
880
    let value = multiline_string_value(templatized, reader)?;
93
880
    Ok((space, newline, value))
94
}
95

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

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

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

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

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

            
207
240
    let mut chars = vec![];
208

            
209
240
    let start = reader.cursor();
210
21035
    while reader.peek_n(3) != "```" && !reader.is_eof() {
211
20855
        let pos = reader.cursor().pos;
212
20855
        let c = reader.read().unwrap();
213
20855
        chars.push((c, c.to_string(), pos));
214
20855
        if c == '\n' {
215
2020
            let end = reader.cursor();
216
2020
            let variables = optional(graphql_variables, reader)?;
217
2020
            match variables {
218
1960
                None => continue,
219
60
                Some(variables) => {
220
60
                    literal("```", reader)?;
221

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

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

            
246
180
    let encoded_string = template::EncodedString {
247
180
        source_info: SourceInfo::new(start.pos, end.pos),
248
180
        chars,
249
180
    };
250

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

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

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

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

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

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

            
326
880
    let start = reader.cursor();
327
110240
    while reader.peek_n(3) != "```" && !reader.is_eof() {
328
109360
        let pos = reader.cursor().pos;
329
109360
        let c = reader.read().unwrap();
330
109360
        chars.push((c, c.to_string(), pos));
331
    }
332
880
    let end = reader.cursor();
333
880
    literal("```", reader)?;
334

            
335
880
    let source_info = SourceInfo::new(start.pos, end.pos);
336

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
820
variables {
821
  "name": "Han Solo"
822
}
823
```"#,
824
        );
825

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

            
891
    #[test]
892
    fn test_multiline_string_graphql_with_variables_error() {
893
        let mut reader = Reader::new(
894
            r#"```graphql
895
query Human($name: String!) {
896
  human(name: $name) {
897
    name
898
    height(unit: FOOT)
899
}
900

            
901
variables
902
```"#,
903
        );
904

            
905
        let error = multiline_string(&mut reader).err().unwrap();
906
        assert_eq!(
907
            error,
908
            ParseError::new(Pos::new(8, 10), false, ParseErrorKind::GraphQlVariables)
909
        );
910
    }
911

            
912
    #[test]
913
    fn test_multiline_string_graphql_with_variables_not_an_object() {
914
        let mut reader = Reader::new(
915
            r#"```graphql
916
query Human($name: String!) {
917
  human(name: $name) {
918
    name
919
    height(unit: FOOT)
920
}
921

            
922
variables [
923
  "one",
924
  "two",
925
  "three"
926
]
927
```"#,
928
        );
929

            
930
        let error = multiline_string(&mut reader).err().unwrap();
931
        assert_eq!(
932
            error,
933
            ParseError::new(Pos::new(8, 11), false, ParseErrorKind::GraphQlVariables)
934
        );
935
    }
936
}