1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2024 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::linter::{LinterError, LinterErrorKind};
19
use hurl_core::ast::*;
20
use hurl_core::reader::Pos;
21
use hurl_core::typing::{Duration, DurationUnit};
22

            
23
/// Returns lint errors for the `hurl_file`.
24
6
pub fn check_hurl_file(hurl_file: &HurlFile) -> Vec<LinterError> {
25
6
    hurl_file.entries.iter().flat_map(check_entry).collect()
26
}
27

            
28
/// Returns a new linted instance from this `hurl_file`.
29
69
pub fn lint_hurl_file(hurl_file: &HurlFile) -> HurlFile {
30
69
    HurlFile {
31
69
        entries: hurl_file.entries.iter().map(lint_entry).collect(),
32
69
        line_terminators: hurl_file.line_terminators.clone(),
33
    }
34
}
35

            
36
6
fn check_entry(entry: &Entry) -> Vec<LinterError> {
37
6
    let mut errors = vec![];
38
6
    errors.append(&mut check_request(&entry.request));
39
6
    match &entry.response {
40
        Some(r) => errors.append(&mut check_response(r)),
41
6
        None => {}
42
    };
43
6
    errors
44
}
45

            
46
228
fn lint_entry(entry: &Entry) -> Entry {
47
228
    let request = lint_request(&entry.request);
48
228
    let response = entry.response.as_ref().map(lint_response);
49
228
    Entry { request, response }
50
}
51

            
52
6
fn check_request(request: &Request) -> Vec<LinterError> {
53
6
    let mut errors = vec![];
54
6
    if !request.space0.value.is_empty() {
55
        errors.push(LinterError {
56
            source_info: request.space0.source_info,
57
            kind: LinterErrorKind::UnnecessarySpace,
58
        });
59
    }
60
6
    if request.space1.value != " " {
61
3
        errors.push(LinterError {
62
3
            source_info: request.space1.source_info,
63
3
            kind: LinterErrorKind::OneSpace,
64
3
        });
65
    }
66
6
    for error in check_line_terminator(&request.line_terminator0) {
67
3
        errors.push(error);
68
    }
69
6
    errors.extend(request.sections.iter().flat_map(check_section));
70
6
    errors
71
}
72

            
73
228
fn lint_request(request: &Request) -> Request {
74
228
    let line_terminators = request.line_terminators.clone();
75
228
    let space0 = empty_whitespace();
76
228
    let method = request.method.clone();
77
228
    let space1 = one_whitespace();
78
228

            
79
228
    let url = request.url.clone();
80
228
    let line_terminator0 = lint_line_terminator(&request.line_terminator0);
81
228
    let headers = request.headers.iter().map(lint_key_value).collect();
82
228
    let body = request.body.as_ref().map(lint_body);
83
228
    let mut sections: Vec<Section> = request.sections.iter().map(lint_section).collect();
84
262
    sections.sort_by_key(|k| section_value_index(k.value.clone()));
85
228

            
86
228
    let source_info = SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0));
87
228
    Request {
88
228
        line_terminators,
89
228
        space0,
90
228
        method,
91
228
        space1,
92
228
        url,
93
228
        line_terminator0,
94
228
        headers,
95
228
        sections,
96
228
        body,
97
228
        source_info,
98
    }
99
}
100

            
101
fn check_response(response: &Response) -> Vec<LinterError> {
102
    let mut errors = vec![];
103
    if !response.space0.value.is_empty() {
104
        errors.push(LinterError {
105
            source_info: response.space0.source_info,
106
            kind: LinterErrorKind::UnnecessarySpace,
107
        });
108
    }
109
    errors.extend(response.sections.iter().flat_map(check_section));
110
    errors
111
}
112

            
113
105
fn lint_response(response: &Response) -> Response {
114
105
    let line_terminators = response.line_terminators.clone();
115
105
    let space0 = empty_whitespace();
116
105
    let version = response.version.clone();
117
105
    let space1 = response.space1.clone();
118
105
    let status = response.status.clone();
119
105
    let line_terminator0 = response.line_terminator0.clone();
120
105
    let headers = response.headers.iter().map(lint_key_value).collect();
121
105
    let mut sections: Vec<Section> = response.sections.iter().map(lint_section).collect();
122
111
    sections.sort_by_key(|k| section_value_index(k.value.clone()));
123
105
    let body = response.body.clone();
124
105

            
125
105
    Response {
126
105
        line_terminators,
127
105
        space0,
128
105
        version,
129
105
        space1,
130
105
        status,
131
105
        line_terminator0,
132
105
        headers,
133
105
        sections,
134
105
        body,
135
105
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
136
    }
137
}
138

            
139
6
fn check_section(section: &Section) -> Vec<LinterError> {
140
6
    let mut errors = vec![];
141
6
    if !section.space0.value.is_empty() {
142
3
        errors.push(LinterError {
143
3
            source_info: section.space0.source_info,
144
3
            kind: LinterErrorKind::UnnecessarySpace,
145
3
        });
146
    }
147
6
    for error in check_line_terminator(&section.line_terminator0) {
148
        errors.push(error);
149
    }
150
6
    errors
151
}
152

            
153
108
fn lint_section(section: &Section) -> Section {
154
108
    let line_terminators = section.line_terminators.clone();
155
108
    let line_terminator0 = section.line_terminator0.clone();
156
108
    let value = lint_section_value(&section.value);
157
108
    Section {
158
108
        line_terminators,
159
108
        space0: empty_whitespace(),
160
108
        value,
161
108
        line_terminator0,
162
108
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
163
    }
164
}
165

            
166
108
fn lint_section_value(section_value: &SectionValue) -> SectionValue {
167
108
    match section_value {
168
9
        SectionValue::QueryParams(params, short) => {
169
9
            SectionValue::QueryParams(params.iter().map(lint_key_value).collect(), *short)
170
        }
171
3
        SectionValue::BasicAuth(param) => {
172
3
            SectionValue::BasicAuth(param.as_ref().map(lint_key_value))
173
        }
174
9
        SectionValue::Captures(captures) => {
175
9
            SectionValue::Captures(captures.iter().map(lint_capture).collect())
176
        }
177
42
        SectionValue::Asserts(asserts) => {
178
42
            SectionValue::Asserts(asserts.iter().map(lint_assert).collect())
179
        }
180
6
        SectionValue::FormParams(params, short) => {
181
6
            SectionValue::FormParams(params.iter().map(lint_key_value).collect(), *short)
182
        }
183
6
        SectionValue::MultipartFormData(params, short) => SectionValue::MultipartFormData(
184
6
            params.iter().map(lint_multipart_param).collect(),
185
6
            *short,
186
6
        ),
187
9
        SectionValue::Cookies(cookies) => {
188
9
            SectionValue::Cookies(cookies.iter().map(lint_cookie).collect())
189
        }
190
24
        SectionValue::Options(options) => {
191
24
            SectionValue::Options(options.iter().map(lint_entry_option).collect())
192
        }
193
    }
194
}
195

            
196
120
fn section_value_index(section_value: SectionValue) -> u32 {
197
120
    match section_value {
198
        // Request sections
199
27
        SectionValue::Options(_) => 0,
200
15
        SectionValue::QueryParams(_, _) => 1,
201
9
        SectionValue::BasicAuth(_) => 2,
202
18
        SectionValue::FormParams(_, _) => 3,
203
18
        SectionValue::MultipartFormData(_, _) => 4,
204
15
        SectionValue::Cookies(_) => 5,
205
        // Response sections
206
9
        SectionValue::Captures(_) => 0,
207
9
        SectionValue::Asserts(_) => 1,
208
    }
209
}
210

            
211
312
fn lint_assert(assert: &Assert) -> Assert {
212
312
    let filters = assert
213
312
        .filters
214
312
        .iter()
215
341
        .map(|(_, f)| (one_whitespace(), lint_filter(f)))
216
312
        .collect();
217
312
    Assert {
218
312
        line_terminators: assert.line_terminators.clone(),
219
312
        space0: empty_whitespace(),
220
312
        query: lint_query(&assert.query),
221
312
        filters,
222
312
        space1: one_whitespace(),
223
312
        predicate: lint_predicate(&assert.predicate),
224
312
        line_terminator0: assert.line_terminator0.clone(),
225
    }
226
}
227

            
228
6
fn lint_capture(capture: &Capture) -> Capture {
229
6
    let filters = capture
230
6
        .filters
231
6
        .iter()
232
7
        .map(|(_, f)| (one_whitespace(), lint_filter(f)))
233
6
        .collect();
234
6
    Capture {
235
6
        line_terminators: capture.line_terminators.clone(),
236
6
        space0: empty_whitespace(),
237
6
        name: capture.name.clone(),
238
6
        space1: empty_whitespace(),
239
6
        space2: one_whitespace(),
240
6
        query: lint_query(&capture.query),
241
6
        filters,
242
6
        line_terminator0: lint_line_terminator(&capture.line_terminator0),
243
    }
244
}
245

            
246
318
fn lint_query(query: &Query) -> Query {
247
318
    Query {
248
318
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
249
318
        value: lint_query_value(&query.value),
250
    }
251
}
252

            
253
318
fn lint_query_value(query_value: &QueryValue) -> QueryValue {
254
318
    match query_value {
255
6
        QueryValue::Status => QueryValue::Status,
256
3
        QueryValue::Url => QueryValue::Url,
257
12
        QueryValue::Header { name, .. } => QueryValue::Header {
258
12
            name: name.clone(),
259
12
            space0: one_whitespace(),
260
12
        },
261
        QueryValue::Cookie {
262
6
            expr: CookiePath { name, attribute },
263
6
            ..
264
6
        } => {
265
6
            let attribute = attribute.as_ref().map(lint_cookie_attribute);
266
6
            QueryValue::Cookie {
267
6
                space0: one_whitespace(),
268
6
                expr: CookiePath {
269
6
                    name: name.clone(),
270
6
                    attribute,
271
6
                },
272
            }
273
        }
274
30
        QueryValue::Body => QueryValue::Body,
275
3
        QueryValue::Xpath { expr, .. } => QueryValue::Xpath {
276
3
            expr: expr.clone(),
277
3
            space0: one_whitespace(),
278
3
        },
279
189
        QueryValue::Jsonpath { expr, .. } => QueryValue::Jsonpath {
280
189
            expr: expr.clone(),
281
189
            space0: one_whitespace(),
282
189
        },
283
3
        QueryValue::Regex { value, .. } => QueryValue::Regex {
284
3
            value: lint_regex_value(value),
285
3
            space0: one_whitespace(),
286
3
        },
287
9
        QueryValue::Variable { name, .. } => QueryValue::Variable {
288
9
            name: name.clone(),
289
9
            space0: one_whitespace(),
290
9
        },
291
3
        QueryValue::Duration => QueryValue::Duration,
292
18
        QueryValue::Bytes => QueryValue::Bytes,
293
3
        QueryValue::Sha256 => QueryValue::Sha256,
294
3
        QueryValue::Md5 => QueryValue::Md5,
295
        QueryValue::Certificate {
296
30
            attribute_name: field,
297
30
            ..
298
30
        } => QueryValue::Certificate {
299
30
            attribute_name: *field,
300
30
            space0: one_whitespace(),
301
30
        },
302
    }
303
}
304

            
305
6
fn lint_regex_value(regex_value: &RegexValue) -> RegexValue {
306
6
    match regex_value {
307
3
        RegexValue::Template(template) => RegexValue::Template(lint_template(template)),
308
3
        RegexValue::Regex(regex) => RegexValue::Regex(regex.clone()),
309
    }
310
}
311

            
312
3
fn lint_cookie_attribute(cookie_attribute: &CookieAttribute) -> CookieAttribute {
313
3
    let space0 = empty_whitespace();
314
3
    let name = lint_cookie_attribute_name(&cookie_attribute.name);
315
3
    let space1 = empty_whitespace();
316
3
    CookieAttribute {
317
3
        space0,
318
3
        name,
319
3
        space1,
320
    }
321
}
322

            
323
3
fn lint_cookie_attribute_name(cookie_attribute_name: &CookieAttributeName) -> CookieAttributeName {
324
3
    match cookie_attribute_name {
325
        CookieAttributeName::Value(_) => CookieAttributeName::Value("Value".to_string()),
326
3
        CookieAttributeName::Expires(_) => CookieAttributeName::Expires("Expires".to_string()),
327
        CookieAttributeName::MaxAge(_) => CookieAttributeName::MaxAge("Max-Age".to_string()),
328
        CookieAttributeName::Domain(_) => CookieAttributeName::Domain("Domain".to_string()),
329
        CookieAttributeName::Path(_) => CookieAttributeName::Path("Path".to_string()),
330
        CookieAttributeName::Secure(_) => CookieAttributeName::Secure("Secure".to_string()),
331
        CookieAttributeName::HttpOnly(_) => CookieAttributeName::HttpOnly("HttpOnly".to_string()),
332
        CookieAttributeName::SameSite(_) => CookieAttributeName::SameSite("SameSite".to_string()),
333
    }
334
}
335

            
336
312
fn lint_predicate(predicate: &Predicate) -> Predicate {
337
312
    Predicate {
338
312
        not: predicate.not,
339
312
        space0: if predicate.not {
340
3
            one_whitespace()
341
        } else {
342
309
            empty_whitespace()
343
        },
344
312
        predicate_func: lint_predicate_func(&predicate.predicate_func),
345
    }
346
}
347

            
348
312
fn lint_predicate_func(predicate_func: &PredicateFunc) -> PredicateFunc {
349
312
    PredicateFunc {
350
312
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
351
312
        value: lint_predicate_func_value(&predicate_func.value),
352
    }
353
}
354

            
355
312
fn lint_predicate_func_value(predicate_func_value: &PredicateFuncValue) -> PredicateFuncValue {
356
312
    match predicate_func_value {
357
201
        PredicateFuncValue::Equal { value, .. } => PredicateFuncValue::Equal {
358
201
            space0: one_whitespace(),
359
201
            value: lint_predicate_value(value),
360
201
            operator: true,
361
201
        },
362
9
        PredicateFuncValue::NotEqual { value, .. } => PredicateFuncValue::NotEqual {
363
9
            space0: one_whitespace(),
364
9
            value: lint_predicate_value(value),
365
9
            operator: true,
366
9
        },
367
9
        PredicateFuncValue::GreaterThan { value, .. } => PredicateFuncValue::GreaterThan {
368
9
            space0: one_whitespace(),
369
9
            value: lint_predicate_value(value),
370
9
            operator: true,
371
9
        },
372
3
        PredicateFuncValue::GreaterThanOrEqual { value, .. } => {
373
3
            PredicateFuncValue::GreaterThanOrEqual {
374
3
                space0: one_whitespace(),
375
3
                value: lint_predicate_value(value),
376
3
                operator: true,
377
            }
378
        }
379
12
        PredicateFuncValue::LessThan { value, .. } => PredicateFuncValue::LessThan {
380
12
            space0: one_whitespace(),
381
12
            value: lint_predicate_value(value),
382
12
            operator: true,
383
12
        },
384
3
        PredicateFuncValue::LessThanOrEqual { value, .. } => PredicateFuncValue::LessThanOrEqual {
385
3
            space0: one_whitespace(),
386
3
            value: lint_predicate_value(value),
387
3
            operator: true,
388
3
        },
389
6
        PredicateFuncValue::Contain { value, .. } => PredicateFuncValue::Contain {
390
6
            space0: one_whitespace(),
391
6
            value: lint_predicate_value(value),
392
6
        },
393

            
394
3
        PredicateFuncValue::Include { value, .. } => PredicateFuncValue::Include {
395
3
            space0: one_whitespace(),
396
3
            value: lint_predicate_value(value),
397
3
        },
398

            
399
6
        PredicateFuncValue::Match { value, .. } => PredicateFuncValue::Match {
400
6
            space0: one_whitespace(),
401
6
            value: lint_predicate_value(value),
402
6
        },
403
9
        PredicateFuncValue::StartWith { value, .. } => PredicateFuncValue::StartWith {
404
9
            space0: one_whitespace(),
405
9
            value: lint_predicate_value(value),
406
9
        },
407
6
        PredicateFuncValue::EndWith { value, .. } => PredicateFuncValue::EndWith {
408
6
            space0: one_whitespace(),
409
6
            value: lint_predicate_value(value),
410
6
        },
411
3
        PredicateFuncValue::IsInteger => PredicateFuncValue::IsInteger,
412
3
        PredicateFuncValue::IsFloat => PredicateFuncValue::IsFloat,
413
3
        PredicateFuncValue::IsBoolean => PredicateFuncValue::IsBoolean,
414
3
        PredicateFuncValue::IsString => PredicateFuncValue::IsString,
415
3
        PredicateFuncValue::IsCollection => PredicateFuncValue::IsCollection,
416
9
        PredicateFuncValue::IsDate => PredicateFuncValue::IsDate,
417
3
        PredicateFuncValue::IsIsoDate => PredicateFuncValue::IsIsoDate,
418
12
        PredicateFuncValue::Exist => PredicateFuncValue::Exist,
419
3
        PredicateFuncValue::IsEmpty => PredicateFuncValue::IsEmpty,
420
3
        PredicateFuncValue::IsNumber => PredicateFuncValue::IsNumber,
421
    }
422
}
423

            
424
267
fn lint_predicate_value(predicate_value: &PredicateValue) -> PredicateValue {
425
267
    match predicate_value {
426
105
        PredicateValue::String(value) => PredicateValue::String(lint_template(value)),
427
15
        PredicateValue::MultilineString(value) => {
428
15
            PredicateValue::MultilineString(lint_multiline_string(value))
429
        }
430
3
        PredicateValue::Bool(value) => PredicateValue::Bool(*value),
431
3
        PredicateValue::Null => PredicateValue::Null,
432
108
        PredicateValue::Number(value) => PredicateValue::Number(value.clone()),
433
3
        PredicateValue::File(value) => PredicateValue::File(lint_file(value)),
434
21
        PredicateValue::Hex(value) => PredicateValue::Hex(lint_hex(value)),
435
3
        PredicateValue::Base64(value) => PredicateValue::Base64(lint_base64(value)),
436
3
        PredicateValue::Expression(value) => PredicateValue::Expression(value.clone()),
437
3
        PredicateValue::Regex(value) => PredicateValue::Regex(value.clone()),
438
    }
439
}
440

            
441
36
fn lint_multiline_string(multiline_string: &MultilineString) -> MultilineString {
442
36
    match multiline_string {
443
        MultilineString {
444
18
            kind: MultilineStringKind::Text(value),
445
18
            attributes,
446
18
        } => MultilineString {
447
18
            kind: MultilineStringKind::Text(lint_text(value)),
448
18
            attributes: lint_multiline_string_attributes(attributes),
449
18
        },
450
        MultilineString {
451
6
            kind: MultilineStringKind::Json(value),
452
6
            attributes,
453
6
        } => MultilineString {
454
6
            kind: MultilineStringKind::Json(lint_text(value)),
455
6
            attributes: lint_multiline_string_attributes(attributes),
456
6
        },
457
        MultilineString {
458
6
            kind: MultilineStringKind::Xml(value),
459
6
            attributes,
460
6
        } => MultilineString {
461
6
            kind: MultilineStringKind::Xml(lint_text(value)),
462
6
            attributes: lint_multiline_string_attributes(attributes),
463
6
        },
464
        MultilineString {
465
6
            kind: MultilineStringKind::GraphQl(value),
466
6
            attributes,
467
6
        } => MultilineString {
468
6
            kind: MultilineStringKind::GraphQl(lint_graphql(value)),
469
6
            attributes: lint_multiline_string_attributes(attributes),
470
6
        },
471
    }
472
}
473

            
474
36
fn lint_multiline_string_attributes(
475
36
    attributes: &[MultilineStringAttribute],
476
36
) -> Vec<MultilineStringAttribute> {
477
36
    attributes.to_vec()
478
}
479

            
480
30
fn lint_text(text: &Text) -> Text {
481
30
    let space = empty_whitespace();
482
30
    let newline = text.newline.clone();
483
30
    let value = lint_template(&text.value);
484
30
    Text {
485
30
        space,
486
30
        newline,
487
30
        value,
488
    }
489
}
490

            
491
6
fn lint_graphql(graphql: &GraphQl) -> GraphQl {
492
6
    let space = empty_whitespace();
493
6
    let newline = graphql.newline.clone();
494
6
    let value = lint_template(&graphql.value);
495
6
    let variables = graphql.variables.clone();
496
6
    GraphQl {
497
6
        space,
498
6
        newline,
499
6
        value,
500
6
        variables,
501
    }
502
}
503

            
504
9
fn lint_cookie(cookie: &Cookie) -> Cookie {
505
9
    cookie.clone()
506
}
507

            
508
45
fn lint_body(body: &Body) -> Body {
509
45
    let line_terminators = body.line_terminators.clone();
510
45
    let space0 = empty_whitespace();
511
45
    let value = lint_bytes(&body.value);
512
45
    let line_terminator0 = body.line_terminator0.clone();
513
45
    Body {
514
45
        line_terminators,
515
45
        space0,
516
45
        value,
517
45
        line_terminator0,
518
    }
519
}
520

            
521
45
fn lint_bytes(bytes: &Bytes) -> Bytes {
522
45
    match bytes {
523
6
        Bytes::File(value) => Bytes::File(lint_file(value)),
524
6
        Bytes::Base64(value) => Bytes::Base64(lint_base64(value)),
525
3
        Bytes::Hex(value) => Bytes::Hex(lint_hex(value)),
526
3
        Bytes::Json(value) => Bytes::Json(value.clone()),
527
3
        Bytes::OnelineString(value) => Bytes::OnelineString(lint_template(value)),
528
21
        Bytes::MultilineString(value) => Bytes::MultilineString(lint_multiline_string(value)),
529
3
        Bytes::Xml(value) => Bytes::Xml(value.clone()),
530
    }
531
}
532

            
533
9
fn lint_base64(base64: &Base64) -> Base64 {
534
9
    Base64 {
535
9
        space0: empty_whitespace(),
536
9
        value: base64.value.clone(),
537
9
        encoded: base64.encoded.clone(),
538
9
        space1: empty_whitespace(),
539
    }
540
}
541

            
542
24
fn lint_hex(hex: &Hex) -> Hex {
543
24
    Hex {
544
24
        space0: empty_whitespace(),
545
24
        value: hex.value.clone(),
546
24
        encoded: hex.encoded.clone(),
547
24
        space1: empty_whitespace(),
548
    }
549
}
550

            
551
9
fn lint_file(file: &File) -> File {
552
9
    File {
553
9
        space0: empty_whitespace(),
554
9
        filename: lint_template(&file.filename),
555
9
        space1: empty_whitespace(),
556
    }
557
}
558

            
559
117
fn lint_key_value(key_value: &KeyValue) -> KeyValue {
560
117
    KeyValue {
561
117
        line_terminators: key_value.line_terminators.clone(),
562
117
        space0: empty_whitespace(),
563
117
        key: key_value.key.clone(),
564
117
        space1: empty_whitespace(),
565
117
        space2: if key_value.value.elements.is_empty() {
566
3
            empty_whitespace()
567
        } else {
568
114
            one_whitespace()
569
        },
570
117
        value: key_value.value.clone(),
571
117
        line_terminator0: key_value.line_terminator0.clone(),
572
    }
573
}
574

            
575
9
fn lint_multipart_param(multipart_param: &MultipartParam) -> MultipartParam {
576
9
    match multipart_param {
577
3
        MultipartParam::Param(param) => MultipartParam::Param(lint_key_value(param)),
578
6
        MultipartParam::FileParam(file_param) => {
579
6
            MultipartParam::FileParam(lint_file_param(file_param))
580
        }
581
    }
582
}
583

            
584
6
fn lint_file_param(file_param: &FileParam) -> FileParam {
585
6
    let line_terminators = file_param.line_terminators.clone();
586
6
    let space0 = file_param.space0.clone();
587
6
    let key = file_param.key.clone();
588
6
    let space1 = file_param.space1.clone();
589
6
    let space2 = file_param.space2.clone();
590
6
    let value = file_param.value.clone();
591
6
    let line_terminator0 = file_param.line_terminator0.clone();
592
6
    FileParam {
593
6
        line_terminators,
594
6
        space0,
595
6
        key,
596
6
        space1,
597
6
        space2,
598
6
        value,
599
6
        line_terminator0,
600
    }
601
}
602

            
603
2220
fn empty_whitespace() -> Whitespace {
604
2220
    Whitespace {
605
2220
        value: String::new(),
606
2220
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
607
    }
608
}
609

            
610
1527
fn one_whitespace() -> Whitespace {
611
1527
    Whitespace {
612
1527
        value: " ".to_string(),
613
1527
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
614
    }
615
}
616

            
617
12
fn check_line_terminator(line_terminator: &LineTerminator) -> Vec<LinterError> {
618
12
    let mut errors = vec![];
619
12
    match &line_terminator.comment {
620
        Some(_) => {}
621
        None => {
622
12
            if !line_terminator.space0.value.is_empty() {
623
3
                errors.push(LinterError {
624
3
                    source_info: line_terminator.space0.source_info,
625
3
                    kind: LinterErrorKind::UnnecessarySpace,
626
3
                });
627
            }
628
        }
629
    }
630
12
    errors
631
}
632

            
633
234
fn lint_line_terminator(line_terminator: &LineTerminator) -> LineTerminator {
634
234
    let space0 = match line_terminator.comment {
635
234
        None => empty_whitespace(),
636
        Some(_) => Whitespace {
637
            value: line_terminator.space0.value.clone(),
638
            source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
639
        },
640
    };
641
234
    let comment = line_terminator.comment.as_ref().map(lint_comment);
642
234
    let newline = Whitespace {
643
234
        value: if line_terminator.newline.value.is_empty() {
644
            String::new()
645
        } else {
646
234
            "\n".to_string()
647
        },
648
234
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
649
234
    };
650
234
    LineTerminator {
651
234
        space0,
652
234
        comment,
653
234
        newline,
654
    }
655
}
656

            
657
fn lint_comment(comment: &Comment) -> Comment {
658
    Comment {
659
        value: if comment.value.starts_with(' ') {
660
            comment.value.clone()
661
        } else {
662
            format!(" {}", comment.value)
663
        },
664
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
665
    }
666
}
667

            
668
156
fn lint_template(template: &Template) -> Template {
669
156
    template.clone()
670
}
671

            
672
252
fn lint_entry_option(entry_option: &EntryOption) -> EntryOption {
673
252
    EntryOption {
674
252
        line_terminators: entry_option.line_terminators.clone(),
675
252
        space0: empty_whitespace(),
676
252
        space1: empty_whitespace(),
677
252
        space2: one_whitespace(),
678
252
        kind: lint_option_kind(&entry_option.kind),
679
252
        line_terminator0: entry_option.line_terminator0.clone(),
680
    }
681
}
682

            
683
252
fn lint_option_kind(option_kind: &OptionKind) -> OptionKind {
684
252
    match option_kind {
685
12
        OptionKind::Delay(duration) => {
686
12
            OptionKind::Delay(lint_duration_option(duration, DurationUnit::MilliSecond))
687
        }
688
12
        OptionKind::RetryInterval(duration) => {
689
12
            OptionKind::RetryInterval(lint_duration_option(duration, DurationUnit::MilliSecond))
690
        }
691
228
        _ => option_kind.clone(),
692
    }
693
}
694

            
695
24
fn lint_duration_option(
696
24
    duration_option: &DurationOption,
697
24
    default_unit: DurationUnit,
698
24
) -> DurationOption {
699
24
    match duration_option {
700
18
        DurationOption::Literal(duration) => {
701
18
            DurationOption::Literal(lint_duration(duration, default_unit))
702
        }
703
6
        DurationOption::Expression(expr) => DurationOption::Expression(expr.clone()),
704
    }
705
}
706

            
707
18
fn lint_duration(duration: &Duration, default_unit: DurationUnit) -> Duration {
708
18
    let value = duration.value;
709
18
    let unit = Some(duration.unit.unwrap_or(default_unit));
710
18
    Duration { value, unit }
711
}
712

            
713
90
fn lint_filter(filter: &Filter) -> Filter {
714
90
    Filter {
715
90
        source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
716
90
        value: lint_filter_value(&filter.value),
717
    }
718
}
719

            
720
90
fn lint_filter_value(filter_value: &FilterValue) -> FilterValue {
721
90
    match filter_value {
722
3
        FilterValue::Regex { value, .. } => FilterValue::Regex {
723
3
            space0: one_whitespace(),
724
3
            value: lint_regex_value(value),
725
3
        },
726
87
        f => f.clone(),
727
    }
728
}
729

            
730
#[cfg(test)]
731
mod tests {
732
    use super::*;
733

            
734
    #[test]
735
    fn test_hurl_file() {
736
        let hurl_file = HurlFile {
737
            entries: vec![],
738
            line_terminators: vec![],
739
        };
740
        let hurl_file_linted = HurlFile {
741
            entries: vec![],
742
            line_terminators: vec![],
743
        };
744
        assert_eq!(check_hurl_file(&hurl_file), vec![]);
745
        assert_eq!(lint_hurl_file(&hurl_file), hurl_file_linted);
746
    }
747

            
748
    #[test]
749
    fn test_entry() {
750
        let entry = HurlFile {
751
            entries: vec![],
752
            line_terminators: vec![],
753
        };
754
        let entry_linted = HurlFile {
755
            entries: vec![],
756
            line_terminators: vec![],
757
        };
758
        assert_eq!(check_hurl_file(&entry), vec![]);
759
        assert_eq!(lint_hurl_file(&entry), entry_linted);
760
    }
761
}