spacegate_model/
http_route.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
use std::fmt::Display;

use crate::constants::DEFAULT_NAMESPACE;

pub use super::route_match::*;
use serde::{Deserialize, Serialize};

use super::{gateway::SgBackendProtocol, PluginInstanceId};

/// HTTPRoute provides a way to route HTTP requests.
///
/// Reference: [Kubernetes Gateway](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.HTTPRoute)
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(export))]
#[serde(default)]
pub struct SgHttpRoute<P = PluginInstanceId> {
    /// Route name
    pub route_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request.
    pub hostnames: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    /// Filters define the filters that are applied to requests that match this hostnames.
    pub plugins: Vec<P>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    /// Rules are a list of HTTP matchers, filters and actions.
    pub rules: Vec<SgHttpRouteRule<P>>,
    /// Rule priority, the rule of higher priority will be chosen.
    pub priority: i16,
}

impl<P> SgHttpRoute<P> {
    pub fn map_plugins<F, T>(self, mut f: F) -> SgHttpRoute<T>
    where
        F: FnMut(P) -> T,
    {
        SgHttpRoute {
            route_name: self.route_name,
            hostnames: self.hostnames,
            plugins: self.plugins.into_iter().map(&mut f).collect(),
            rules: self.rules.into_iter().map(|rule| rule.map_plugins(&mut f)).collect(),
            priority: self.priority,
        }
    }
}

impl<P> Default for SgHttpRoute<P> {
    fn default() -> Self {
        Self {
            route_name: Default::default(),
            hostnames: Default::default(),
            plugins: Default::default(),
            rules: Default::default(),
            priority: 1,
        }
    }
}

/// HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(export))]
#[serde(default)]
pub struct SgHttpRouteRule<P = PluginInstanceId> {
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if any one of the matches is satisfied.
    pub matches: Option<Vec<SgHttpRouteMatch>>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    /// Filters define the filters that are applied to requests that match this rule.
    pub plugins: Vec<P>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    /// BackendRefs defines the backend(s) where matching requests should be sent.
    pub backends: Vec<SgBackendRef<P>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Timeout define the timeout for requests that match this rule.
    pub timeout_ms: Option<u32>,
}

impl<P> SgHttpRouteRule<P> {
    pub fn map_plugins<F, T>(self, mut f: F) -> SgHttpRouteRule<T>
    where
        F: FnMut(P) -> T,
    {
        SgHttpRouteRule {
            matches: self.matches,
            plugins: self.plugins.into_iter().map(&mut f).collect(),
            backends: self.backends.into_iter().map(|backend| backend.map_plugins(&mut f)).collect(),
            timeout_ms: self.timeout_ms,
        }
    }
}

impl<P> Default for SgHttpRouteRule<P> {
    fn default() -> Self {
        Self {
            matches: Default::default(),
            plugins: Default::default(),
            backends: Default::default(),
            timeout_ms: Default::default(),
        }
    }
}

/// BackendRef defines how a HTTPRoute should forward an HTTP request.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(export))]
#[serde(default)]
pub struct SgBackendRef<P = PluginInstanceId> {
    // #[serde(flatten)]
    pub host: BackendHost,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Port specifies the destination port number to use for this resource.
    pub port: Option<u16>,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Timeout specifies the timeout for requests forwarded to the referenced backend.
    pub timeout_ms: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    // Protocol specifies the protocol used to talk to the referenced backend.
    pub protocol: Option<SgBackendProtocol>,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Downgrade HTTP2 connections, it is useful when the backend does not support HTTP2.
    pub downgrade_http2: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Weight specifies the proportion of requests forwarded to the referenced backend.
    /// This is computed as weight/(sum of all weights in this BackendRefs list).
    /// For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports.
    /// Weight is not a percentage and the sum of weights does not need to equal 100.
    pub weight: Option<u16>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    /// plugins define the filters that are applied to backend that match this hostnames.
    ///
    /// # Notice!
    /// this field is ordered, the first plugin will be the outermost plugin.
    pub plugins: Vec<P>,
}

impl<P> SgBackendRef<P> {
    pub fn map_plugins<F, T>(self, f: F) -> SgBackendRef<T>
    where
        F: FnMut(P) -> T,
    {
        SgBackendRef {
            host: self.host,
            port: self.port,
            timeout_ms: self.timeout_ms,
            protocol: self.protocol,
            downgrade_http2: self.downgrade_http2,
            weight: self.weight,
            plugins: self.plugins.into_iter().map(f).collect(),
        }
    }

    pub fn get_host(&self) -> String {
        self.host.to_string()
    }
}

impl<P> Default for SgBackendRef<P> {
    fn default() -> Self {
        Self {
            host: Default::default(),
            port: Default::default(),
            timeout_ms: Default::default(),
            downgrade_http2: Default::default(),
            protocol: Default::default(),
            weight: Default::default(),
            plugins: Default::default(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(export))]
pub struct K8sServiceData {
    pub name: String,
    #[serde(alias = "ns")]
    pub namespace: Option<String>,
}

impl Display for K8sServiceData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.namespace {
            Some(ref ns) => write!(f, "{}.{}", self.name, ns),
            None => write!(f, "{}.{}", self.name, DEFAULT_NAMESPACE),
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(export))]
#[serde(tag = "kind")]
pub enum BackendHost {
    Host { host: String },
    K8sService(K8sServiceData),
    File { path: String },
}

impl Display for BackendHost {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Host { host } => write!(f, "{}", host),
            Self::K8sService(k8s_service) => write!(f, "{}", k8s_service),
            Self::File { path } => write!(f, "{}", path),
        }
    }
}

impl Default for BackendHost {
    fn default() -> Self {
        Self::Host { host: String::default() }
    }
}