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
72
pub fn lint_hurl_file(hurl_file: &HurlFile) -> HurlFile {
30
72
    HurlFile {
31
72
        entries: hurl_file.entries.iter().map(lint_entry).collect(),
32
72
        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
    if let Some(response) = &entry.response {
40
        errors.append(&mut check_response(response));
41
    }
42
6
    errors
43
}
44

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
682
267
fn lint_option_kind(option_kind: &OptionKind) -> OptionKind {
683
267
    match option_kind {
684
12
        OptionKind::Delay(duration) => {
685
12
            OptionKind::Delay(lint_duration_option(duration, DurationUnit::MilliSecond))
686
        }
687
12
        OptionKind::RetryInterval(duration) => {
688
12
            OptionKind::RetryInterval(lint_duration_option(duration, DurationUnit::MilliSecond))
689
        }
690
27
        OptionKind::Variable(var_def) => OptionKind::Variable(lint_variable_definition(var_def)),
691
216
        _ => 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::Placeholder(expr) => DurationOption::Placeholder(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
27
fn lint_variable_definition(var_def: &VariableDefinition) -> VariableDefinition {
731
27
    VariableDefinition {
732
27
        space0: empty_whitespace(),
733
27
        space1: empty_whitespace(),
734
27
        ..var_def.clone()
735
    }
736
}
737

            
738
#[cfg(test)]
739
mod tests {
740
    use super::*;
741

            
742
    #[test]
743
    fn test_hurl_file() {
744
        let hurl_file = HurlFile {
745
            entries: vec![],
746
            line_terminators: vec![],
747
        };
748
        let hurl_file_linted = HurlFile {
749
            entries: vec![],
750
            line_terminators: vec![],
751
        };
752
        assert_eq!(check_hurl_file(&hurl_file), vec![]);
753
        assert_eq!(lint_hurl_file(&hurl_file), hurl_file_linted);
754
    }
755

            
756
    #[test]
757
    fn test_entry() {
758
        let entry = HurlFile {
759
            entries: vec![],
760
            line_terminators: vec![],
761
        };
762
        let entry_linted = HurlFile {
763
            entries: vec![],
764
            line_terminators: vec![],
765
        };
766
        assert_eq!(check_hurl_file(&entry), vec![]);
767
        assert_eq!(lint_hurl_file(&entry), entry_linted);
768
    }
769
}