Skip to main content

brooks_lib/cdni/ps/
spec.rs

1// brooks, Copyright 2026, Will Hawkins
2//
3// This file is part of brooks.
4
5// This file is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! Processing stages metadata definitions
19
20use crate::macros::IntoCdniMetadata;
21use brooks_macros::CdniMetadata;
22use serde::{Deserialize, Serialize};
23use serde_json::Value;
24
25use crate::cdni::gmd::spec::TypedGenericMetadata;
26
27use std::fmt::Debug;
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SyntheticResponse<A: Debug + Clone + Default> {
31    pub headers: Option<Vec<TypedHeader<A>>>,
32
33    #[serde(rename = "response-status")]
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub response_status: Option<String>,
36
37    #[serde(rename = "status-is-expression")]
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub response_status_expr: Option<bool>,
40
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub body: Option<String>,
43
44    #[serde(rename = "body-is-expression")]
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub body_expr: Option<bool>,
47
48    #[serde(skip)]
49    pub aug: A,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
53pub struct TypedSyntheticResponse<A: Debug + Clone + Default> {
54    #[serde(rename = "generic-metadata-type")]
55    pub tpe: String,
56    #[serde(rename = "generic-metadata-value")]
57    pub value: SyntheticResponse<A>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Header<A: Debug + Clone + Default> {
62    pub name: String,
63    pub value: String,
64    #[serde(rename = "value-is-expression")]
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub value_expr: Option<bool>,
67
68    #[serde(skip)]
69    pub aug: A,
70}
71#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
72pub struct TypedHeader<A: Debug + Clone + Default> {
73    #[serde(rename = "generic-metadata-type")]
74    pub tpe: String,
75    #[serde(rename = "generic-metadata-value")]
76    pub value: Header<A>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, Default)]
80pub struct HeaderTransform<A: Debug + Clone + Default> {
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub delete: Option<Vec<String>>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub add: Option<Vec<TypedHeader<A>>>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub replace: Option<Vec<TypedHeader<A>>>,
87    #[serde(skip)]
88    pub aug: A,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
92pub struct TypedHeaderTransform<A: Debug + Clone + Default> {
93    #[serde(rename = "generic-metadata-type")]
94    pub tpe: String,
95    #[serde(rename = "generic-metadata-value")]
96    pub value: HeaderTransform<A>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, Default)]
100pub struct RequestTransform<A: Debug + Clone + Default> {
101    #[serde(rename = "header-transform")]
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub xform: Option<TypedHeaderTransform<A>>,
104
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub uri: Option<String>,
107
108    #[serde(rename = "uri-is-expression")]
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub uri_is_expr: Option<bool>,
111
112    #[serde(skip)]
113    pub aug: A,
114}
115#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
116pub struct TypedRequestTransform<A: Debug + Clone + Default> {
117    #[serde(rename = "generic-metadata-type")]
118    pub tpe: String,
119    #[serde(rename = "generic-metadata-value")]
120    pub value: RequestTransform<A>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct ResponseTransform<A: Debug + Clone + Default> {
125    #[serde(rename = "header-transform")]
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub xform: Option<TypedHeaderTransform<A>>,
128
129    #[serde(rename = "response-status")]
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub response_status: Option<String>,
132
133    #[serde(rename = "status-is-expression")]
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub response_status_expr: Option<bool>,
136
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub synthetic: Option<TypedSyntheticResponse<A>>,
139
140    #[serde(skip)]
141    pub aug: A,
142}
143#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
144pub struct TypedResponseTransform<A: Debug + Clone + Default> {
145    #[serde(rename = "generic-metadata-type")]
146    pub tpe: String,
147    #[serde(rename = "generic-metadata-value")]
148    pub value: ResponseTransform<A>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
152pub struct StageMetadata<A: Debug + Clone + Default> {
153    #[serde(rename = "generic-metadata")]
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub generic: Option<Vec<TypedGenericMetadata<A>>>,
156
157    #[serde(rename = "request-transform")]
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub request_xform: Option<TypedRequestTransform<A>>,
160
161    #[serde(rename = "response-transform")]
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub response_xform: Option<TypedResponseTransform<A>>,
164
165    #[serde(skip)]
166    pub aug: A,
167}
168#[derive(Debug, Clone, Serialize, Deserialize, Default, CdniMetadata)]
169pub struct TypedStageMetadata<A: Debug + Clone + Default> {
170    #[serde(rename = "generic-metadata-type")]
171    pub tpe: String,
172    #[serde(rename = "generic-metadata-value")]
173    pub value: StageMetadata<A>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ExpressionMatch<A: Debug + Clone + Default> {
178    pub expression: String,
179    #[serde(skip)]
180    pub aug: A,
181}
182#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
183pub struct TypedExpressionMatch<A: Debug + Clone + Default> {
184    #[serde(rename = "generic-metadata-type")]
185    pub tpe: String,
186    #[serde(rename = "generic-metadata-value")]
187    pub value: ExpressionMatch<A>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct StageRules<A: Debug + Clone + Default> {
192    #[serde(rename = "match")]
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub mtch: Option<TypedExpressionMatch<A>>,
195    #[serde(rename = "stage-metadata")]
196    pub stage_metadata: TypedStageMetadata<A>,
197
198    #[serde(skip)]
199    pub aug: A,
200}
201#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
202pub struct TypedStageRules<A: Debug + Clone + Default> {
203    #[serde(rename = "generic-metadata-type")]
204    pub tpe: String,
205    #[serde(rename = "generic-metadata-value")]
206    pub value: StageRules<A>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct MatchGroup<A: Debug + Clone + Default> {
211    #[serde(rename = "if-rule")]
212    pub if_rule: TypedStageRules<A>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    #[serde(rename = "else-if-rules")]
215    pub else_ifs: Option<Vec<TypedStageRules<A>>>,
216    #[serde(skip)]
217    pub aug: A,
218}
219#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
220pub struct TypedMatchGroup<A: Debug + Clone + Default> {
221    #[serde(rename = "generic-metadata-type")]
222    pub tpe: String,
223    #[serde(rename = "generic-metadata-value")]
224    pub value: MatchGroup<A>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, Default)]
228pub struct ProcessingStages<A: Debug + Clone + Default> {
229    #[serde(rename = "client-request")]
230    pub client_req: Vec<TypedStageRules<A>>,
231    #[serde(rename = "origin-request")]
232    pub origin_req: Vec<TypedStageRules<A>>,
233    #[serde(rename = "origin-response")]
234    pub origin_res: Vec<TypedStageRules<A>>,
235    #[serde(rename = "client-response")]
236    pub client_res: Vec<TypedStageRules<A>>,
237
238    #[serde(skip)]
239    pub aug: A,
240}
241#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
242pub struct TypedProcessingStages<A: Debug + Clone + Default> {
243    #[serde(rename = "generic-metadata-type")]
244    pub tpe: String,
245    #[serde(rename = "generic-metadata-value")]
246    pub value: ProcessingStages<A>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ClientRequestStage<A: Debug + Clone + Default> {
251    #[serde(rename = "match-groups")]
252    pub match_groups: Vec<TypedMatchGroup<A>>,
253    #[serde(skip)]
254    pub aug: A,
255}
256#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
257pub struct TypedClientRequestStage<A: Debug + Clone + Default> {
258    #[serde(rename = "generic-metadata-type")]
259    pub tpe: String,
260    #[serde(rename = "generic-metadata-value")]
261    pub value: ClientRequestStage<A>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct ClientResponseStage<A: Debug + Clone + Default> {
266    #[serde(rename = "match-groups")]
267    pub match_groups: Vec<TypedMatchGroup<A>>,
268    #[serde(skip)]
269    pub aug: A,
270}
271#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
272pub struct TypedClientResponseStage<A: Debug + Clone + Default> {
273    #[serde(rename = "generic-metadata-type")]
274    pub tpe: String,
275    #[serde(rename = "generic-metadata-value")]
276    pub value: ClientResponseStage<A>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct OriginRequestStage<A: Debug + Clone + Default> {
281    #[serde(rename = "match-groups")]
282    pub match_groups: Vec<TypedMatchGroup<A>>,
283    #[serde(skip)]
284    pub aug: A,
285}
286#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
287pub struct TypedOriginRequestStage<A: Debug + Clone + Default> {
288    #[serde(rename = "generic-metadata-type")]
289    pub tpe: String,
290    #[serde(rename = "generic-metadata-value")]
291    pub value: OriginRequestStage<A>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct OriginResponseStage<A: Debug + Clone + Default> {
296    #[serde(rename = "match-groups")]
297    pub match_groups: Vec<TypedMatchGroup<A>>,
298    #[serde(skip)]
299    pub aug: A,
300}
301#[derive(Debug, Clone, Serialize, Deserialize, CdniMetadata)]
302pub struct TypedOriginResponseStage<A: Debug + Clone + Default> {
303    #[serde(rename = "generic-metadata-type")]
304    pub tpe: String,
305    #[serde(rename = "generic-metadata-value")]
306    pub value: OriginResponseStage<A>,
307}
308
309#[derive(Debug, Clone, PartialEq)]
310pub enum TypedStageTypes {
311    ClientRequest,
312    ClientResponse,
313    OriginRequest,
314    OriginResponse,
315}
316
317#[derive(Debug, Clone)]
318pub enum TypedStage<A: Debug + Clone + Default> {
319    ClientRequest(TypedClientRequestStage<A>),
320    ClientResponse(TypedClientResponseStage<A>),
321    OriginRequest(TypedOriginRequestStage<A>),
322    OriginResponse(TypedOriginResponseStage<A>),
323}
324
325impl<A: Debug + Clone + Default> From<&TypedStage<A>> for TypedStageTypes {
326    fn from(value: &TypedStage<A>) -> Self {
327        match value {
328            TypedStage::ClientRequest(_) => TypedStageTypes::ClientRequest,
329            TypedStage::ClientResponse(_) => TypedStageTypes::ClientResponse,
330            TypedStage::OriginRequest(_) => TypedStageTypes::OriginRequest,
331            TypedStage::OriginResponse(_) => TypedStageTypes::OriginResponse,
332        }
333    }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct TypedGenericStage {
338    #[serde(rename = "generic-metadata-type")]
339    pub tpe: String,
340    #[serde(rename = "generic-metadata-value")]
341    pub value: Value,
342}
343
344#[cfg(test)]
345mod test_spec {
346    use std::path::Path;
347
348    use crate::cdni::tests::test_helpers::{expression_match, stage_metadata, stage_rule};
349
350    use crate::{
351        cdni::ps::spec::{ProcessingStages, TypedClientRequestStage, TypedProcessingStages},
352        tests::read_test_file,
353    };
354
355    #[test]
356    fn test_simple_serialize() {
357        let x = TypedProcessingStages::<()> {
358            tpe: "MI.ProcessingStages".to_string(),
359            value: ProcessingStages::<()> {
360                client_req: vec![stage_rule(
361                    Some(expression_match("5 + 4")),
362                    stage_metadata(),
363                )],
364                origin_req: vec![stage_rule(
365                    Some(expression_match("5 + 4")),
366                    stage_metadata(),
367                )],
368                origin_res: vec![stage_rule(
369                    Some(expression_match("5 + 4")),
370                    stage_metadata(),
371                )],
372                client_res: vec![stage_rule(
373                    Some(expression_match("5 + 4")),
374                    stage_metadata(),
375                )],
376                aug: (),
377            },
378        };
379        let expected = read_test_file(Path::new("./tests/data/processing/simple/serialize.json"));
380        let actual = serde_json::to_string_pretty(&x).expect("Could not serialize");
381        pretty_assertions::assert_eq!(expected, actual);
382    }
383
384    #[test]
385    fn test_deserialize_example8() {
386        let json = read_test_file(Path::new("./tests/data/processing/from-spec/example8.json"));
387
388        let result = serde_json::from_str::<TypedProcessingStages<()>>(&json)
389            .expect("Could not deserialize Example 8");
390
391        // There is one client request stage rules ...
392        assert_eq!(result.value.client_req.len(), 1);
393        let client_req = &result.value.client_req[0];
394
395        // ... and it has a match ...
396        assert!(client_req.value.mtch.is_some());
397        // ... and it requests application of a piece of metadata ...
398        assert!(client_req.value.stage_metadata.value.generic.is_some());
399        // ... and, finally, it has a response transform.
400        assert!(
401            client_req
402                .value
403                .stage_metadata
404                .value
405                .response_xform
406                .is_some()
407        );
408    }
409
410    #[test]
411    fn test_deserialize_client_request_stage() {
412        let json = read_test_file(Path::new(
413            "./tests/data/processing/client_request_stage/if.json",
414        ));
415
416        let result = serde_json::from_str::<TypedClientRequestStage<()>>(&json)
417            .expect("Could not deserialize simple client request stage JSON");
418
419        // There is one client request stage rules ...
420        assert_eq!(result.value.match_groups.len(), 1);
421        let mg = &result.value.match_groups[0];
422
423        // ... and it has a match ...
424        assert!(mg.value.if_rule.value.mtch.is_some());
425    }
426}