Skip to main content

gaze_proxy/
adapter.rs

1use http::Method;
2use serde_json::Value;
3use url::Url;
4
5#[async_trait::async_trait]
6pub trait ProviderAdapter: Send + Sync + 'static {
7    fn name(&self) -> &'static str;
8    fn matches_path(&self, method: &Method, path: &str) -> bool;
9    fn upstream_base(&self) -> &Url;
10    fn request_pii_surfaces<'a>(&self, body: &'a mut Value) -> Vec<PiiSurface<'a>>;
11    fn response_pii_surfaces<'a>(&self, body: &'a mut Value) -> Vec<PiiSurface<'a>>;
12    fn sse_event_pii_surfaces<'a>(&self, event: &'a mut SseEvent) -> Vec<PiiSurface<'a>>;
13}
14
15pub struct PiiSurface<'a> {
16    pub field_path: String,
17    pub text: &'a mut String,
18}
19
20#[derive(Debug, Clone)]
21#[non_exhaustive]
22pub struct SseEvent {
23    pub event: Option<String>,
24    pub data: Value,
25}
26
27impl SseEvent {
28    pub fn new(event: Option<String>, data: Value) -> Self {
29        Self { event, data }
30    }
31}
32
33pub(crate) fn push_string<'a>(
34    surfaces: &mut Vec<PiiSurface<'a>>,
35    field_path: impl Into<String>,
36    value: &'a mut Value,
37) {
38    if let Value::String(text) = value {
39        surfaces.push(PiiSurface {
40            field_path: field_path.into(),
41            text,
42        });
43    }
44}
45
46pub(crate) fn walk_all_strings<'a>(
47    surfaces: &mut Vec<PiiSurface<'a>>,
48    prefix: String,
49    value: &'a mut Value,
50) {
51    match value {
52        Value::String(text) => surfaces.push(PiiSurface {
53            field_path: prefix,
54            text,
55        }),
56        Value::Array(items) => {
57            for (index, item) in items.iter_mut().enumerate() {
58                walk_all_strings(surfaces, format!("{prefix}[{index}]"), item);
59            }
60        }
61        Value::Object(map) => {
62            for (key, child) in map {
63                let path = if prefix.is_empty() {
64                    key.clone()
65                } else {
66                    format!("{prefix}.{key}")
67                };
68                walk_all_strings(surfaces, path, child);
69            }
70        }
71        Value::Null | Value::Bool(_) | Value::Number(_) => {}
72    }
73}
74
75pub(crate) fn push_text_blocks<'a>(
76    surfaces: &mut Vec<PiiSurface<'a>>,
77    prefix: &str,
78    value: &'a mut Value,
79) {
80    match value {
81        Value::String(text) => surfaces.push(PiiSurface {
82            field_path: prefix.to_string(),
83            text,
84        }),
85        Value::Array(items) => {
86            for (index, item) in items.iter_mut().enumerate() {
87                match item {
88                    Value::String(text) => surfaces.push(PiiSurface {
89                        field_path: format!("{prefix}[{index}]"),
90                        text,
91                    }),
92                    Value::Object(map) => {
93                        if matches!(map.get("type"), Some(Value::String(kind)) if kind == "text") {
94                            if let Some(Value::String(text)) = map.get_mut("text") {
95                                surfaces.push(PiiSurface {
96                                    field_path: format!("{prefix}[{index}].text"),
97                                    text,
98                                });
99                            }
100                        }
101                    }
102                    _ => {}
103                }
104            }
105        }
106        _ => {}
107    }
108}