Skip to main content

powhttp_sdk/
overview.rs

1use std::sync::Arc;
2use serde::Serialize;
3
4use crate::runtime::handle::ExtensionHandle;
5use crate::runtime::state::OverviewHandler;
6use crate::runtime::handlers::SingleEntryContext;
7use crate::error::Error;
8
9/// A single computed field displayed in the Overview section of the Inspector.
10///
11/// The handler receives a [`SingleEntryContext`] and returns an optional string
12/// value to display. Return `None` to hide the field for that entry.
13///
14/// ```
15/// use powhttp_sdk::{OverviewField, ExtensionHandle, SingleEntryContext, Error};
16///
17/// let field = OverviewField::new(
18///     "content-length",
19///     "Content Length",
20///     async |ctx: SingleEntryContext, handle: ExtensionHandle| {
21///         let entry = handle.get_session_entry(ctx.session_id, ctx.entry_id).await?;
22///         let size = entry
23///             .and_then(|entry| entry.response)
24///             .and_then(|res| res.body_size)
25///             .map(|size| size.to_string());
26///         Ok(size)
27///     },
28/// );
29/// ```
30#[derive(Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct OverviewField {
33    id: String,
34    label: String,
35    #[serde(skip)]
36    handler: OverviewHandler,
37}
38
39impl OverviewField {
40    /// Creates a new overview field with the given `id`, display `label` and async handler.
41    pub fn new<F, Fut>(id: impl Into<String>, label: impl Into<String>, handler: F) -> Self
42    where
43        F: Fn(SingleEntryContext, ExtensionHandle) -> Fut + Send + Sync + 'static,
44        Fut: Future<Output = Result<Option<String>, Error>> + Send + 'static,
45    {
46        Self {
47            id: id.into(),
48            label: label.into(),
49            handler: Arc::new(move |ctx, handle| {
50                let fut = handler(ctx, handle);
51                Box::pin(async move { fut.await.map_err(Error::into_jrpc) })
52            }),
53        }
54    }
55}
56
57/// A named group of overview fields, displayed as a collapsible section.
58#[derive(Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct OverviewSection {
61    label: String,
62    children: Vec<OverviewNode>,
63}
64
65impl OverviewSection {
66    /// Creates an empty section with the given display `label`.
67    pub fn new(label: impl Into<String>) -> Self {
68        Self {
69            label: label.into(),
70            children: Vec::new()
71        }
72    }
73
74    /// Appends all nodes from `children`, draining the provided vec.
75    pub fn with_children(mut self, children: &mut Vec<OverviewNode>) -> Self {
76        self.children.append(children);
77        self
78    }
79
80    /// Appends a single child node (field or nested section).
81    pub fn with_child(mut self, child: OverviewNode) -> Self {
82        self.children.push(child);
83        self
84    }
85}
86
87/// A node in the overview tree, either a leaf [`Field`](OverviewNode::Field)
88/// or a [`Section`](OverviewNode::Section).
89#[derive(Serialize)]
90#[serde(tag = "type", rename_all = "snake_case")]
91pub enum OverviewNode {
92    Section(OverviewSection),
93    Field(OverviewField),
94}
95
96impl From<OverviewField> for OverviewNode {
97    fn from(field: OverviewField) -> Self {
98        OverviewNode::Field(field)
99    }
100}
101
102impl From<OverviewSection> for OverviewNode {
103    fn from(section: OverviewSection) -> Self {
104        OverviewNode::Section(section)
105    }
106}
107
108impl OverviewNode {
109    pub(crate) fn extract_handlers(&self) -> Vec<(String, OverviewHandler)> {
110        match self {
111            Self::Field(field) => vec![(field.id.clone(), Arc::clone(&field.handler))],
112            Self::Section(section) => section
113                .children
114                .iter()
115                .flat_map(|child| child.extract_handlers())
116                .collect(),
117        }
118    }
119}