1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2025 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, MultilineStringAttribute, MultilineStringKind,
20
    SourceInfo, Template, 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::string::escape_char;
26
use crate::parser::{template, ParseError, ParseErrorKind, ParseResult};
27
use crate::reader::Reader;
28

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

            
32
870
    choice(&[json_text, xml_text, graphql, plain_text], reader)
33
}
34

            
35
1960
fn text(
36
1960
    lang: Option<&str>,
37
1960
    reader: &mut Reader,
38
1960
) -> ParseResult<(
39
1960
    Vec<MultilineStringAttribute>,
40
1960
    Whitespace,
41
1960
    Whitespace,
42
1960
    Template,
43
1960
)> {
44
1960
    if let Some(lang) = lang {
45
1580
        try_literal(lang, reader)?;
46
285
        drop(try_literal(",", reader));
47
    }
48
665
    let attributes = multiline_string_attributes(reader)?;
49
660
    let escape = attributes.contains(&MultilineStringAttribute::Escape);
50
660
    let space = zero_or_more_spaces(reader)?;
51
660
    let newline = newline(reader)?;
52
660
    let value = multiline_string_value(reader, escape)?;
53
660
    Ok((attributes, space, newline, value))
54
}
55

            
56
380
fn plain_text(reader: &mut Reader) -> ParseResult<MultilineString> {
57
380
    let (attributes, space, newline, text) = text(None, reader)?;
58
375
    let kind = MultilineStringKind::Text(text);
59
375
    Ok(MultilineString {
60
375
        attributes,
61
375
        space,
62
375
        newline,
63
375
        kind,
64
375
    })
65
}
66

            
67
870
fn json_text(reader: &mut Reader) -> ParseResult<MultilineString> {
68
870
    let (attributes, space, newline, text) = text(Some("json"), reader)?;
69
160
    let kind = MultilineStringKind::Json(text);
70
160
    Ok(MultilineString {
71
160
        attributes,
72
160
        space,
73
160
        newline,
74
160
        kind,
75
160
    })
76
}
77

            
78
710
fn xml_text(reader: &mut Reader) -> ParseResult<MultilineString> {
79
710
    let (attributes, space, newline, text) = text(Some("xml"), reader)?;
80
125
    let kind = MultilineStringKind::Xml(text);
81
125
    Ok(MultilineString {
82
125
        attributes,
83
125
        space,
84
125
        newline,
85
125
        kind,
86
125
    })
87
}
88

            
89
585
fn graphql(reader: &mut Reader) -> ParseResult<MultilineString> {
90
585
    try_literal("graphql", reader)?;
91
205
    drop(try_literal(",", reader));
92
205
    let attributes = multiline_string_attributes(reader)?;
93
205
    let space = zero_or_more_spaces(reader)?;
94
205
    let newline = newline(reader)?;
95

            
96
205
    let mut chars = vec![];
97
205

            
98
205
    let start = reader.cursor();
99
17250
    while reader.peek_n(3) != "```" && !reader.is_eof() {
100
17085
        let pos = reader.cursor().pos;
101
17085
        let c = reader.read().unwrap();
102
17085
        chars.push((c, c.to_string(), pos));
103
17085
        if c == '\n' {
104
1705
            let end = reader.cursor();
105
1705
            let variables = optional(graphql_variables, reader)?;
106
1705
            match variables {
107
1665
                None => continue,
108
40
                Some(variables) => {
109
40
                    literal("```", reader)?;
110

            
111
40
                    let encoded_string = template::EncodedString {
112
40
                        source_info: SourceInfo::new(start.pos, end.pos),
113
40
                        chars: chars.clone(),
114
40
                    };
115

            
116
40
                    let elements = template::templatize(encoded_string)?;
117
40
                    let template =
118
40
                        Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
119
40
                    let kind = MultilineStringKind::GraphQl(GraphQl {
120
40
                        value: template,
121
40
                        variables: Some(variables),
122
40
                    });
123
40
                    let attributes = vec![];
124
40
                    return Ok(MultilineString {
125
40
                        attributes,
126
40
                        space,
127
40
                        newline,
128
40
                        kind,
129
40
                    });
130
                }
131
            }
132
        }
133
    }
134
165
    let end = reader.cursor();
135
165
    literal("```", reader)?;
136

            
137
165
    let encoded_string = template::EncodedString {
138
165
        source_info: SourceInfo::new(start.pos, end.pos),
139
165
        chars,
140
165
    };
141

            
142
165
    let elements = template::templatize(encoded_string)?;
143
165
    let template = Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
144
165
    let kind = MultilineStringKind::GraphQl(GraphQl {
145
165
        value: template,
146
165
        variables: None,
147
165
    });
148
165
    Ok(MultilineString {
149
165
        attributes,
150
165
        space,
151
165
        newline,
152
165
        kind,
153
165
    })
154
}
155

            
156
870
fn multiline_string_attributes(reader: &mut Reader) -> ParseResult<Vec<MultilineStringAttribute>> {
157
870
    let mut attributes = vec![];
158

            
159
955
    while !reader.is_eof() && reader.peek() != Some('\n') && reader.peek() != Some('\r') {
160
90
        let start = reader.cursor();
161
90
        zero_or_more_spaces(reader)?;
162
90
        let c = reader.peek();
163
90
        if c == Some('\r') || c == Some('\n') {
164
            reader.seek(start);
165
            break;
166
        }
167
90
        let attribute = reader
168
693
            .read_while(|c| c != ',' && c != '\r' && c != '\n' && !c.is_whitespace())
169
90
            .to_string();
170
90
        if attribute == "escape" {
171
70
            attributes.push(MultilineStringAttribute::Escape);
172
70
        } else if attribute == "novariable" {
173
15
            attributes.push(MultilineStringAttribute::NoVariable);
174
15
        } else {
175
5
            let kind = ParseErrorKind::MultilineAttribute(attribute);
176
5
            return Err(ParseError {
177
5
                kind,
178
5
                pos: start.pos,
179
5
                recoverable: false,
180
5
            });
181
        }
182
85
        drop(try_literal(",", reader));
183
    }
184
865
    Ok(attributes)
185
}
186

            
187
80
fn whitespace(reader: &mut Reader) -> ParseResult<Whitespace> {
188
80
    let start = reader.cursor();
189
80
    match reader.read() {
190
        None => Err(ParseError::new(start.pos, true, ParseErrorKind::Space)),
191
80
        Some(c) => {
192
80
            if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
193
40
                Ok(Whitespace {
194
40
                    value: c.to_string(),
195
40
                    source_info: SourceInfo::new(start.pos, reader.cursor().pos),
196
40
                })
197
            } else {
198
40
                Err(ParseError::new(start.pos, true, ParseErrorKind::Space))
199
            }
200
        }
201
    }
202
}
203

            
204
40
fn zero_or_more_whitespaces(reader: &mut Reader) -> ParseResult<Whitespace> {
205
40
    let start = reader.cursor();
206
40
    match zero_or_more(whitespace, reader) {
207
40
        Ok(v) => {
208
48
            let s = v.iter().map(|x| x.value.clone()).collect();
209
40
            Ok(Whitespace {
210
40
                value: s,
211
40
                source_info: SourceInfo::new(start.pos, reader.cursor().pos),
212
40
            })
213
        }
214
        Err(e) => Err(e),
215
    }
216
}
217

            
218
1705
fn graphql_variables(reader: &mut Reader) -> ParseResult<GraphQlVariables> {
219
1705
    try_literal("variables", reader)?;
220
40
    let space = zero_or_more_spaces(reader)?;
221
40
    let start = reader.cursor();
222
40
    let object = object_value(reader);
223
40
    let value = match object {
224
40
        Ok(obj) => obj,
225
        Err(_) => {
226
            return Err(ParseError::new(
227
                start.pos,
228
                false,
229
                ParseErrorKind::GraphQlVariables,
230
            ))
231
        }
232
    };
233
40
    let whitespace = zero_or_more_whitespaces(reader)?;
234
40
    Ok(GraphQlVariables {
235
40
        space,
236
40
        value,
237
40
        whitespace,
238
40
    })
239
}
240

            
241
660
fn multiline_string_value(reader: &mut Reader, escape: bool) -> ParseResult<Template> {
242
660
    let mut chars = vec![];
243
660

            
244
660
    let start = reader.cursor();
245
96965
    while reader.peek_n(3) != "```" && !reader.is_eof() {
246
96305
        let pos = reader.cursor().pos;
247
96305
        let cursor = reader.cursor();
248
96305
        if escape {
249
3210
            match escape_char(reader) {
250
100
                Ok(c) => {
251
100
                    let s = reader.read_from(cursor.index);
252
100
                    chars.push((c, s, pos));
253
                }
254
3110
                Err(_) => {
255
3110
                    let c = reader.read().unwrap();
256
3110
                    chars.push((c, c.to_string(), pos));
257
                }
258
            }
259
93095
        } else {
260
93095
            let c = reader.read().unwrap();
261
93095
            chars.push((c, c.to_string(), pos));
262
        }
263
    }
264
660
    let end = reader.cursor();
265
660
    literal("```", reader)?;
266

            
267
660
    let encoded_string = template::EncodedString {
268
660
        source_info: SourceInfo::new(start.pos, end.pos),
269
660
        chars,
270
660
    };
271

            
272
660
    let elements = template::templatize(encoded_string)?;
273
660
    let template = Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
274
660
    Ok(template)
275
}
276

            
277
#[cfg(test)]
278
mod tests {
279
    use super::*;
280
    use crate::ast::{JsonObjectElement, JsonValue, TemplateElement};
281
    use crate::reader::Pos;
282
    use crate::typing::ToSource;
283

            
284
    #[test]
285
    fn test_multiline_string_text() {
286
        let mut reader = Reader::new("```\nline1\nline2\nline3\n```");
287
        assert_eq!(
288
            multiline_string(&mut reader).unwrap(),
289
            MultilineString {
290
                attributes: vec![],
291
                space: Whitespace {
292
                    value: String::new(),
293
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
294
                },
295
                newline: Whitespace {
296
                    value: "\n".to_string(),
297
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
298
                },
299
                kind: MultilineStringKind::Text(Template::new(
300
                    None,
301
                    vec![TemplateElement::String {
302
                        value: "line1\nline2\nline3\n".to_string(),
303
                        source: "line1\nline2\nline3\n".to_source(),
304
                    }],
305
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
306
                )),
307
            }
308
        );
309

            
310
        let mut reader = Reader::new("```         \nline1\nline2\nline3\n```");
311
        assert_eq!(
312
            multiline_string(&mut reader).unwrap(),
313
            MultilineString {
314
                attributes: vec![],
315
                space: Whitespace {
316
                    value: "         ".to_string(),
317
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 13)),
318
                },
319
                newline: Whitespace {
320
                    value: "\n".to_string(),
321
                    source_info: SourceInfo::new(Pos::new(1, 13), Pos::new(2, 1)),
322
                },
323
                kind: MultilineStringKind::Text(Template::new(
324
                    None,
325
                    vec![TemplateElement::String {
326
                        value: "line1\nline2\nline3\n".to_string(),
327
                        source: "line1\nline2\nline3\n".to_source(),
328
                    }],
329
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
330
                )),
331
            }
332
        );
333

            
334
        let mut reader = Reader::new("```escape,novariable         \nline1\nline2\nline3\n```");
335
        assert_eq!(
336
            multiline_string(&mut reader).unwrap(),
337
            MultilineString {
338
                attributes: vec![
339
                    MultilineStringAttribute::Escape,
340
                    MultilineStringAttribute::NoVariable
341
                ],
342
                space: Whitespace {
343
                    value: "         ".to_string(),
344
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 30)),
345
                },
346
                newline: Whitespace {
347
                    value: "\n".to_string(),
348
                    source_info: SourceInfo::new(Pos::new(1, 30), Pos::new(2, 1)),
349
                },
350
                kind: MultilineStringKind::Text(Template::new(
351
                    None,
352
                    vec![TemplateElement::String {
353
                        value: "line1\nline2\nline3\n".to_string(),
354
                        source: "line1\nline2\nline3\n".to_source(),
355
                    }],
356
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
357
                )),
358
            }
359
        );
360
    }
361

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

            
388
        let mut reader = Reader::new(
389
            r#"```json,escape
390
{
391
  "g_clef": "\u{1D11E}"
392
}
393
```"#,
394
        );
395
        assert_eq!(
396
            multiline_string(&mut reader).unwrap(),
397
            MultilineString {
398
                attributes: vec![MultilineStringAttribute::Escape],
399
                space: Whitespace {
400
                    value: String::new(),
401
                    source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
402
                },
403
                newline: Whitespace {
404
                    value: "\n".to_string(),
405
                    source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(2, 1)),
406
                },
407
                kind: MultilineStringKind::Json(Template::new(
408
                    None,
409
                    vec![TemplateElement::String {
410
                        value: "{\n  \"g_clef\": \"𝄞\"\n}\n".to_string(),
411
                        source: "{\n  \"g_clef\": \"\\u{1D11E}\"\n}\n".to_source(),
412
                    }],
413
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
414
                )),
415
            }
416
        );
417
    }
418

            
419
    #[test]
420
    fn test_multiline_string_graphql() {
421
        let mut reader = Reader::new("```graphql\nline1\nline2\nline3\n```");
422
        assert_eq!(
423
            multiline_string(&mut reader).unwrap(),
424
            MultilineString {
425
                attributes: vec![],
426
                space: Whitespace {
427
                    value: String::new(),
428
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
429
                },
430
                newline: Whitespace {
431
                    value: "\n".to_string(),
432
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(2, 1)),
433
                },
434
                kind: MultilineStringKind::GraphQl(GraphQl {
435
                    value: Template::new(
436
                        None,
437
                        vec![TemplateElement::String {
438
                            value: "line1\nline2\nline3\n".to_string(),
439
                            source: "line1\nline2\nline3\n".to_source(),
440
                        }],
441
                        SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1))
442
                    ),
443
                    variables: None,
444
                }),
445
            }
446
        );
447

            
448
        let mut reader = Reader::new("```graphql      \nline1\nline2\nline3\n```");
449
        assert_eq!(
450
            multiline_string(&mut reader).unwrap(),
451
            MultilineString {
452
                attributes: vec![],
453
                space: Whitespace {
454
                    value: "      ".to_string(),
455
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 17)),
456
                },
457
                newline: Whitespace {
458
                    value: "\n".to_string(),
459
                    source_info: SourceInfo::new(Pos::new(1, 17), Pos::new(2, 1)),
460
                },
461
                kind: MultilineStringKind::GraphQl(GraphQl {
462
                    value: Template::new(
463
                        None,
464
                        vec![TemplateElement::String {
465
                            value: "line1\nline2\nline3\n".to_string(),
466
                            source: "line1\nline2\nline3\n".to_source(),
467
                        }],
468
                        SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
469
                    ),
470
                    variables: None,
471
                }),
472
            }
473
        );
474
    }
475

            
476
    #[test]
477
    fn test_multiline_string_failed() {
478
        let data = [
479
            "```hexaaa\nline1\nline2\nline3\n```",
480
            "```aaa\nline1\nline2\nline3\n```",
481
        ];
482

            
483
        for text in data.iter() {
484
            let mut reader = Reader::new(text);
485
            assert!(multiline_string(&mut reader).is_err());
486
        }
487
    }
488

            
489
    #[test]
490
    fn test_multiline_string_empty() {
491
        let mut reader = Reader::new("```\n```");
492
        assert_eq!(
493
            multiline_string(&mut reader).unwrap(),
494
            MultilineString {
495
                attributes: vec![],
496
                space: Whitespace {
497
                    value: String::new(),
498
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
499
                },
500
                newline: Whitespace {
501
                    value: "\n".to_string(),
502
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
503
                },
504
                kind: MultilineStringKind::Text(Template::new(
505
                    None,
506
                    vec![],
507
                    SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1))
508
                )),
509
            }
510
        );
511
        let mut reader = Reader::new("```\r\n```");
512
        assert_eq!(
513
            multiline_string(&mut reader).unwrap(),
514
            MultilineString {
515
                attributes: vec![],
516
                space: Whitespace {
517
                    value: String::new(),
518
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
519
                },
520
                newline: Whitespace {
521
                    value: "\r\n".to_string(),
522
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
523
                },
524
                kind: MultilineStringKind::Text(Template::new(
525
                    None,
526
                    vec![],
527
                    SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1)),
528
                )),
529
            }
530
        );
531
    }
532

            
533
    #[test]
534
    fn test_multiline_string_hello_error() {
535
        let mut reader = Reader::new("```Hello World!```");
536
        let error = multiline_string(&mut reader).unwrap_err();
537
        assert_eq!(error.pos, Pos::new(1, 4));
538
        assert_eq!(
539
            error.kind,
540
            ParseErrorKind::MultilineAttribute("Hello".to_string())
541
        );
542
    }
543

            
544
    #[test]
545
    fn test_multiline_string_csv() {
546
        let mut reader = Reader::new("```\nline1\nline2\nline3\n```");
547
        assert_eq!(
548
            multiline_string(&mut reader).unwrap(),
549
            MultilineString {
550
                attributes: vec![],
551
                space: Whitespace {
552
                    value: String::new(),
553
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
554
                },
555
                newline: Whitespace {
556
                    value: "\n".to_string(),
557
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
558
                },
559
                kind: MultilineStringKind::Text(Template::new(
560
                    None,
561
                    vec![TemplateElement::String {
562
                        value: "line1\nline2\nline3\n".to_string(),
563
                        source: "line1\nline2\nline3\n".to_source(),
564
                    }],
565
                    SourceInfo::new(Pos::new(2, 1), Pos::new(5, 1)),
566
                )),
567
            }
568
        );
569
    }
570

            
571
    #[test]
572
    fn test_multiline_string_one_empty_line() {
573
        let mut reader = Reader::new("```\n\n```");
574
        assert_eq!(
575
            multiline_string(&mut reader).unwrap(),
576
            MultilineString {
577
                kind: MultilineStringKind::Text(Template::new(
578
                    None,
579
                    vec![TemplateElement::String {
580
                        value: "\n".to_string(),
581
                        source: "\n".to_source(),
582
                    }],
583
                    SourceInfo::new(Pos::new(2, 1), Pos::new(3, 1)),
584
                )),
585
                attributes: vec![],
586
                space: Whitespace {
587
                    value: String::new(),
588
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
589
                },
590
                newline: Whitespace {
591
                    value: "\n".to_string(),
592
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
593
                },
594
            }
595
        );
596

            
597
        // One cr
598
        let mut reader = Reader::new("```\n\r\n````");
599
        assert_eq!(
600
            multiline_string(&mut reader).unwrap(),
601
            MultilineString {
602
                attributes: vec![],
603
                space: Whitespace {
604
                    value: String::new(),
605
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 4)),
606
                },
607
                newline: Whitespace {
608
                    value: "\n".to_string(),
609
                    source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(2, 1)),
610
                },
611
                kind: MultilineStringKind::Text(Template::new(
612
                    None,
613
                    vec![TemplateElement::String {
614
                        value: "\r\n".to_string(),
615
                        source: "\r\n".to_source(),
616
                    }],
617
                    SourceInfo::new(Pos::new(2, 1), Pos::new(3, 1)),
618
                )),
619
            }
620
        );
621
    }
622

            
623
    #[test]
624
    fn test_multiline_string_attributes() {
625
        let mut reader = Reader::new("escape\n```");
626
        assert_eq!(
627
            multiline_string_attributes(&mut reader).unwrap(),
628
            vec![MultilineStringAttribute::Escape]
629
        );
630
        assert_eq!(reader.cursor().index, 6);
631

            
632
        let mut reader = Reader::new("\n```");
633
        assert_eq!(multiline_string_attributes(&mut reader).unwrap(), vec![]);
634
        assert_eq!(reader.cursor().index, 0);
635

            
636
        let mut reader = Reader::new("\r\n```");
637
        assert_eq!(multiline_string_attributes(&mut reader).unwrap(), vec![]);
638
        assert_eq!(reader.cursor().index, 0);
639

            
640
        let mut reader = Reader::new("toto\n```");
641
        let error = multiline_string_attributes(&mut reader).unwrap_err();
642
        assert_eq!(
643
            error.kind,
644
            ParseErrorKind::MultilineAttribute("toto".to_string())
645
        );
646
        assert_eq!(error.pos, Pos::new(1, 1));
647

            
648
        let mut reader = Reader::new(",escape\n```");
649
        let error = multiline_string_attributes(&mut reader).unwrap_err();
650
        assert_eq!(
651
            error.kind,
652
            ParseErrorKind::MultilineAttribute(String::new())
653
        );
654
        assert_eq!(error.pos, Pos::new(1, 1));
655
    }
656

            
657
    #[test]
658
    fn test_multiline_string_escape() {
659
        let mut reader = Reader::new("```escape\n\\t\n```");
660
        assert_eq!(
661
            multiline_string(&mut reader).unwrap(),
662
            MultilineString {
663
                attributes: vec![MultilineStringAttribute::Escape],
664
                space: Whitespace {
665
                    value: String::new(),
666
                    source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 10)),
667
                },
668
                newline: Whitespace {
669
                    value: "\n".to_string(),
670
                    source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(2, 1)),
671
                },
672
                kind: MultilineStringKind::Text(Template::new(
673
                    None,
674
                    vec![TemplateElement::String {
675
                        value: "\t\n".to_string(),
676
                        source: "\\t\n".to_source(),
677
                    }],
678
                    SourceInfo::new(Pos::new(2, 1), Pos::new(3, 1)),
679
                )),
680
            }
681
        );
682
    }
683

            
684
    #[test]
685
    fn test_multiline_string_error() {
686
        let mut reader = Reader::new("xxx");
687
        let error = multiline_string(&mut reader).err().unwrap();
688
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
689
        assert_eq!(
690
            error.kind,
691
            ParseErrorKind::Expecting {
692
                value: "```".to_string()
693
            }
694
        );
695
        assert!(error.recoverable);
696

            
697
        let mut reader = Reader::new("```\nxxx");
698
        let error = multiline_string(&mut reader).err().unwrap();
699
        assert_eq!(error.pos, Pos { line: 2, column: 4 });
700
        assert_eq!(
701
            error.kind,
702
            ParseErrorKind::Expecting {
703
                value: "```".to_string()
704
            }
705
        );
706
        assert!(!error.recoverable);
707

            
708
        let mut reader = Reader::new("```xxx");
709
        let error = multiline_string(&mut reader).err().unwrap();
710
        assert_eq!(error.pos, Pos { line: 1, column: 4 });
711
        assert_eq!(
712
            error.kind,
713
            ParseErrorKind::MultilineAttribute("xxx".to_string())
714
        );
715
        assert!(!error.recoverable);
716
    }
717

            
718
    #[test]
719
    fn test_multiline_string_value() {
720
        let mut reader = Reader::new("```");
721
        assert_eq!(
722
            multiline_string_value(&mut reader, false).unwrap(),
723
            Template::new(
724
                None,
725
                vec![],
726
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1))
727
            )
728
        );
729
        assert_eq!(reader.cursor().index, 3);
730

            
731
        let mut reader = Reader::new("hello```");
732
        assert_eq!(
733
            multiline_string_value(&mut reader, false).unwrap(),
734
            Template::new(
735
                None,
736
                vec![TemplateElement::String {
737
                    value: "hello".to_string(),
738
                    source: "hello".to_source(),
739
                }],
740
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6))
741
            )
742
        );
743
        assert_eq!(reader.cursor().index, 8);
744
    }
745

            
746
    #[test]
747
    fn test_multiline_string_graphql_with_variables() {
748
        let mut reader = Reader::new(
749
            r#"```graphql
750
query Human($name: String!) {
751
  human(name: $name) {
752
    name
753
    height(unit: FOOT)
754
}
755

            
756
variables {
757
  "name": "Han Solo"
758
}
759
```"#,
760
        );
761

            
762
        assert_eq!(
763
            multiline_string(&mut reader).unwrap(),
764
            MultilineString {
765
                attributes: vec![],
766
                space: Whitespace {
767
                    value: String::new(),
768
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(1, 11)),
769
                },
770
                newline: Whitespace {
771
                    value: "\n".to_string(),
772
                    source_info: SourceInfo::new(Pos::new(1, 11), Pos::new(2, 1)),
773
                },
774
                kind: MultilineStringKind::GraphQl(GraphQl {
775
                    value: Template::new(
776
                        None,
777
                        vec![TemplateElement::String {
778
                            value: "query Human($name: String!) {\n  human(name: $name) {\n    name\n    height(unit: FOOT)\n}\n\n".to_string(),
779
                            source:
780
                            "query Human($name: String!) {\n  human(name: $name) {\n    name\n    height(unit: FOOT)\n}\n\n".to_source()
781
                        }],
782
                        SourceInfo::new(Pos::new(2, 1), Pos::new(8, 1)),
783
                    ),
784
                    variables: Some(GraphQlVariables {
785
                        space: Whitespace {
786
                            value: " ".to_string(),
787
                            source_info: SourceInfo::new(Pos::new(8, 10), Pos::new(8, 11)),
788
                        },
789
                        value: JsonValue::Object {
790
                            space0: "\n  ".to_string(),
791
                            elements: vec![JsonObjectElement {
792
                                space0: String::new(),
793
                                name: Template::new(
794
                                    Some('"'),
795
                                    vec![
796
                                        TemplateElement::String {
797
                                            value: "name".to_string(),
798
                                            source: "name".to_source()
799
                                        }
800
                                    ],
801
                                    SourceInfo::new(Pos::new(9, 4), Pos::new(9, 8))
802
                                ),
803
                                space1: String::new(),
804
                                space2: " ".to_string(),
805
                                value: JsonValue::String(Template::new(
806
                                    Some('"'),
807
                                    vec![
808
                                        TemplateElement::String {
809
                                            value: "Han Solo".to_string(),
810
                                            source: "Han Solo".to_source()
811
                                        }
812
                                    ],
813
                                    SourceInfo::new(Pos::new(9, 12), Pos::new(9, 20))
814
                                )),
815
                                space3: "\n".to_string()
816
                            }]
817
                        },
818
                        whitespace: Whitespace {
819
                            value: "\n".to_string(),
820
                            source_info: SourceInfo::new(Pos::new(10, 2), Pos::new(11, 1))
821
                        }
822
                    })
823
                }),
824
            }
825
        );
826
    }
827

            
828
    #[test]
829
    fn test_multiline_string_graphql_with_variables_error() {
830
        let mut reader = Reader::new(
831
            r#"```graphql
832
query Human($name: String!) {
833
  human(name: $name) {
834
    name
835
    height(unit: FOOT)
836
}
837

            
838
variables
839
```"#,
840
        );
841

            
842
        let error = multiline_string(&mut reader).err().unwrap();
843
        assert_eq!(
844
            error,
845
            ParseError::new(Pos::new(8, 10), false, ParseErrorKind::GraphQlVariables)
846
        );
847
    }
848

            
849
    #[test]
850
    fn test_multiline_string_graphql_with_variables_not_an_object() {
851
        let mut reader = Reader::new(
852
            r#"```graphql
853
query Human($name: String!) {
854
  human(name: $name) {
855
    name
856
    height(unit: FOOT)
857
}
858

            
859
variables [
860
  "one",
861
  "two",
862
  "three"
863
]
864
```"#,
865
        );
866

            
867
        let error = multiline_string(&mut reader).err().unwrap();
868
        assert_eq!(
869
            error,
870
            ParseError::new(Pos::new(8, 11), false, ParseErrorKind::GraphQlVariables)
871
        );
872
    }
873
}