Skip to main content

xidl_parser/hir/annotation/
location.rs

1use serde::{Deserialize, Serialize};
2
3/// Locations supported by named API key and API version parameters.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum HttpParameterLocation {
6    /// An HTTP request header; names ignore ASCII case.
7    Header,
8    /// A URL query parameter; names are case-sensitive.
9    Query,
10    /// An HTTP cookie; names are case-sensitive.
11    Cookie,
12}
13
14impl HttpParameterLocation {
15    /// Recognizes the annotation spelling, ignoring ASCII case.
16    pub fn parse(value: &str) -> Option<Self> {
17        match value.to_ascii_lowercase().as_str() {
18            "header" => Some(Self::Header),
19            "query" => Some(Self::Query),
20            "cookie" => Some(Self::Cookie),
21            _ => None,
22        }
23    }
24
25    /// Returns the spelling used by HTTP annotations and OpenAPI parameters.
26    pub fn as_str(self) -> &'static str {
27        match self {
28            Self::Header => "header",
29            Self::Query => "query",
30            Self::Cookie => "cookie",
31        }
32    }
33
34    /// Compares wire names using this location's protocol rule.
35    pub fn names_equal(self, first: &str, second: &str) -> bool {
36        match self {
37            Self::Header => first.eq_ignore_ascii_case(second),
38            Self::Query | Self::Cookie => first == second,
39        }
40    }
41
42    /// Checks a name before it is emitted in a URL, header or cookie.
43    pub fn valid_name(self, name: &str) -> bool {
44        match self {
45            Self::Header | Self::Cookie => http::HeaderName::from_bytes(name.as_bytes()).is_ok(),
46            Self::Query => !name.is_empty() && !name.chars().any(char::is_control),
47        }
48    }
49}
50
51/// The location and wire name declared by `@version(in = ..., name = ...)`.
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct VersionParameter {
54    /// Where generated clients carry their module's version value.
55    pub location: HttpParameterLocation,
56    /// The parameter's name at that location.
57    pub name: String,
58}
59
60impl VersionParameter {
61    pub(crate) fn validate_value(&self, value: &str) -> Result<(), String> {
62        if self.location == HttpParameterLocation::Header {
63            if value.trim_matches([' ', '\t']) != value {
64                return Err(
65                    "version header value cannot start or end with HTTP whitespace".to_string(),
66                );
67            }
68            let header = http::HeaderValue::from_str(value)
69                .map_err(|error| format!("invalid version header value: {error}"))?;
70            header
71                .to_str()
72                .map_err(|error| format!("version header value must be visible ASCII: {error}"))?;
73        }
74        Ok(())
75    }
76}