Lines
90 %
Functions
100 %
Branches
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2025 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use std::fmt;
use crate::ast::option::EntryOption;
use crate::ast::primitive::{
Base64, File, Hex, KeyValue, LineTerminator, MultilineString, Number, Placeholder, Regex,
SourceInfo, Template, Whitespace,
};
use crate::ast::Filter;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Section {
pub line_terminators: Vec<LineTerminator>,
pub space0: Whitespace,
pub line_terminator0: LineTerminator,
pub value: SectionValue,
pub source_info: SourceInfo,
}
impl Section {
/// Returns the Hurl string identifier of this section.
pub fn identifier(&self) -> &'static str {
match self.value {
SectionValue::Asserts(_) => "Asserts",
SectionValue::QueryParams(_, true) => "Query",
SectionValue::QueryParams(_, false) => "QueryStringParams",
SectionValue::BasicAuth(_) => "BasicAuth",
SectionValue::FormParams(_, true) => "Form",
SectionValue::FormParams(_, false) => "FormParams",
SectionValue::Cookies(_) => "Cookies",
SectionValue::Captures(_) => "Captures",
SectionValue::MultipartFormData(_, true) => "Multipart",
SectionValue::MultipartFormData(_, false) => "MultipartFormData",
SectionValue::Options(_) => "Options",
#[allow(clippy::large_enum_variant)]
pub enum SectionValue {
QueryParams(Vec<KeyValue>, bool), // boolean param indicates if we use the short syntax
BasicAuth(Option<KeyValue>), // boolean param indicates if we use the short syntax
FormParams(Vec<KeyValue>, bool),
MultipartFormData(Vec<MultipartParam>, bool), // boolean param indicates if we use the short syntax
Cookies(Vec<Cookie>),
Captures(Vec<Capture>),
Asserts(Vec<Assert>),
Options(Vec<EntryOption>),
pub struct Cookie {
pub name: Template,
pub space1: Whitespace,
pub space2: Whitespace,
pub value: Template,
pub enum MultipartParam {
Param(KeyValue),
FileParam(FileParam),
pub struct FileParam {
pub key: Template,
pub value: FileValue,
pub struct FileValue {
pub filename: Template,
pub content_type: Option<Template>,
pub struct Capture {
pub query: Query,
pub filters: Vec<(Whitespace, Filter)>,
pub space3: Whitespace,
pub redact: bool,
pub struct Assert {
pub predicate: Predicate,
pub struct Query {
pub value: QueryValue,
pub enum QueryValue {
Status,
Version,
Url,
Header {
space0: Whitespace,
name: Template,
},
Cookie {
expr: CookiePath,
Body,
Xpath {
expr: Template,
Jsonpath {
Regex {
value: RegexValue,
Variable {
Duration,
Bytes,
Sha256,
Md5,
Certificate {
attribute_name: CertificateAttributeName,
Ip,
Redirects,
impl QueryValue {
/// Returns the Hurl string identifier of this query type.
match self {
QueryValue::Status => "status",
QueryValue::Version => "version",
QueryValue::Url => "url",
QueryValue::Header { .. } => "header",
QueryValue::Cookie { .. } => "cookie",
QueryValue::Body => "body",
QueryValue::Xpath { .. } => "xpath",
QueryValue::Jsonpath { .. } => "jsonpath",
QueryValue::Regex { .. } => "regex",
QueryValue::Variable { .. } => "variable",
QueryValue::Duration => "duration",
QueryValue::Bytes => "bytes",
QueryValue::Sha256 => "sha256",
QueryValue::Md5 => "md5",
QueryValue::Certificate { .. } => "certificate",
QueryValue::Ip => "ip",
QueryValue::Redirects => "redirects",
pub enum RegexValue {
Template(Template),
Regex(Regex),
pub struct CookiePath {
pub attribute: Option<CookieAttribute>,
impl fmt::Display for CookiePath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf = self.name.to_string();
if let Some(attribute) = &self.attribute {
let s = format!("[{}]", attribute.identifier());
buf.push_str(s.as_str());
write!(f, "{buf}")
pub struct CookieAttribute {
pub name: CookieAttributeName,
impl CookieAttribute {
fn identifier(&self) -> &'static str {
match self.name {
CookieAttributeName::MaxAge(_) => "Max-Age",
CookieAttributeName::Value(_) => "Value",
CookieAttributeName::Expires(_) => "Expires",
CookieAttributeName::Domain(_) => "Domain",
CookieAttributeName::Path(_) => "Path",
CookieAttributeName::Secure(_) => "Secure",
CookieAttributeName::HttpOnly(_) => "HttpOnly",
CookieAttributeName::SameSite(_) => "SameSite",
pub enum CookieAttributeName {
Value(String),
Expires(String),
MaxAge(String),
Domain(String),
Path(String),
Secure(String),
HttpOnly(String),
SameSite(String),
impl CookieAttributeName {
pub fn value(&self) -> String {
CookieAttributeName::Value(value)
| CookieAttributeName::Expires(value)
| CookieAttributeName::MaxAge(value)
| CookieAttributeName::Domain(value)
| CookieAttributeName::Path(value)
| CookieAttributeName::Secure(value)
| CookieAttributeName::HttpOnly(value)
| CookieAttributeName::SameSite(value) => value.to_string(),
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CertificateAttributeName {
Subject,
Issuer,
StartDate,
ExpireDate,
SerialNumber,
impl CertificateAttributeName {
/// Returns the Hurl string identifier of this certificate attribute name.
CertificateAttributeName::Subject => "Subject",
CertificateAttributeName::Issuer => "Issuer",
CertificateAttributeName::StartDate => "Start-Date",
CertificateAttributeName::ExpireDate => "Expire-Date",
CertificateAttributeName::SerialNumber => "Serial-Number",
pub struct Predicate {
pub not: bool,
pub predicate_func: PredicateFunc,
pub struct Not {
pub value: bool,
pub struct PredicateFunc {
pub value: PredicateFuncValue,
pub enum PredicateValue {
Base64(Base64),
Bool(bool),
File(File),
Hex(Hex),
MultilineString(MultilineString),
Null,
Number(Number),
Placeholder(Placeholder),
String(Template),
pub enum PredicateFuncValue {
Equal {
value: PredicateValue,
NotEqual {
GreaterThan {
GreaterThanOrEqual {
LessThan {
LessThanOrEqual {
StartWith {
EndWith {
Contain {
Include {
Match {
IsInteger,
IsFloat,
IsBoolean,
IsString,
IsCollection,
IsDate,
IsIsoDate,
Exist,
IsEmpty,
IsNumber,
IsIpv4,
IsIpv6,
impl PredicateFuncValue {
/// Returns the Hurl string identifier of this predicate.
PredicateFuncValue::Equal { .. } => "==",
PredicateFuncValue::NotEqual { .. } => "!=",
PredicateFuncValue::GreaterThan { .. } => ">",
PredicateFuncValue::GreaterThanOrEqual { .. } => ">=",
PredicateFuncValue::LessThan { .. } => "<",
PredicateFuncValue::LessThanOrEqual { .. } => "<=",
PredicateFuncValue::StartWith { .. } => "startsWith",
PredicateFuncValue::EndWith { .. } => "endsWith",
PredicateFuncValue::Contain { .. } => "contains",
PredicateFuncValue::Include { .. } => "includes",
PredicateFuncValue::Match { .. } => "matches",
PredicateFuncValue::IsInteger => "isInteger",
PredicateFuncValue::IsFloat => "isFloat",
PredicateFuncValue::IsBoolean => "isBoolean",
PredicateFuncValue::IsString => "isString",
PredicateFuncValue::IsCollection => "isCollection",
PredicateFuncValue::IsDate => "isDate",
PredicateFuncValue::IsIsoDate => "isIsoDate",
PredicateFuncValue::Exist => "exists",
PredicateFuncValue::IsEmpty => "isEmpty",
PredicateFuncValue::IsNumber => "isNumber",
PredicateFuncValue::IsIpv4 => "isIpv4",
PredicateFuncValue::IsIpv6 => "isIpv6",
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::primitive::{SourceInfo, Template, TemplateElement};
use crate::reader::Pos;
use crate::typing::ToSource;
fn whitespace() -> Whitespace {
Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0)),
#[test]
fn test_cookie_path() {
assert_eq!(
CookiePath {
name: Template {
delimiter: None,
elements: vec![TemplateElement::String {
value: "LSID".to_string(),
source: "unused".to_source(),
}],
attribute: Some(CookieAttribute {
space0: whitespace(),
name: CookieAttributeName::MaxAge("Max-Age".to_string()),
space1: whitespace(),
}),
.to_string(),
"LSID[Max-Age]".to_string()
);