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}