Skip to main content

nu_protocol/pipeline/
metadata.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{Record, Span, Value};
6
7pub const TABLE_WIDTH_PRIORITY_COLUMNS_METADATA_KEY: &str = "table_width_priority_columns";
8
9/// Metadata that is valid for the whole [`PipelineData`](crate::PipelineData)
10///
11/// ## Custom Metadata
12///
13/// The `custom` field allows commands and plugins to attach arbitrary metadata to pipeline data.
14/// To avoid key collisions, it is recommended to use namespaced keys with an underscore separator:
15///
16/// - `"http_response"` - HTTP response metadata (status, headers, etc.)
17/// - `"polars_schema"` - DataFrame schema information
18/// - `"custom_plugin_field"` - Plugin-specific metadata
19///
20/// This convention helps ensure different commands and plugins don't overwrite each other's metadata.
21#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
22pub struct PipelineMetadata {
23    pub data_source: DataSource,
24    pub path_columns: Vec<String>,
25    pub content_type: Option<String>,
26    #[serde(default)]
27    pub custom: Record,
28}
29
30impl PipelineMetadata {
31    pub fn with_data_source(self, data_source: DataSource) -> Self {
32        Self {
33            data_source,
34            ..self
35        }
36    }
37
38    pub fn with_path_columns(self, path_columns: Vec<String>) -> Self {
39        Self {
40            path_columns,
41            ..self
42        }
43    }
44
45    pub fn with_content_type(self, content_type: Option<String>) -> Self {
46        Self {
47            content_type,
48            ..self
49        }
50    }
51
52    /// Set table width-priority columns in custom metadata.
53    ///
54    /// This helper accepts plain strings to avoid manually constructing `Value::list`.
55    pub fn set_table_width_priority_columns<I, S>(&mut self, span: Span, columns: I)
56    where
57        I: IntoIterator<Item = S>,
58        S: AsRef<str>,
59    {
60        let columns: Vec<Value> = columns
61            .into_iter()
62            .map(|column| Value::string(column.as_ref(), span))
63            .collect();
64
65        // Replace an existing entry if present.
66        self.custom
67            .retain(|key, _| key != TABLE_WIDTH_PRIORITY_COLUMNS_METADATA_KEY);
68
69        if !columns.is_empty() {
70            self.custom.push(
71                TABLE_WIDTH_PRIORITY_COLUMNS_METADATA_KEY.to_string(),
72                Value::list(columns, span),
73            );
74        }
75    }
76
77    /// Builder-style variant of [`PipelineMetadata::set_table_width_priority_columns`].
78    pub fn with_table_width_priority_columns<I, S>(mut self, span: Span, columns: I) -> Self
79    where
80        I: IntoIterator<Item = S>,
81        S: AsRef<str>,
82    {
83        self.set_table_width_priority_columns(span, columns);
84        self
85    }
86
87    /// Transform metadata for the `collect` operation.
88    ///
89    /// After collecting a stream into a value, `FilePath` data sources are no longer meaningful
90    /// and should be converted to `None`. If all metadata fields become empty after this
91    /// transformation, returns `None` to avoid carrying around empty metadata.
92    pub fn for_collect(self) -> Option<Self> {
93        let Self {
94            data_source,
95            path_columns,
96            content_type,
97            custom,
98        } = self;
99
100        // Transform FilePath to None after collect
101        let data_source = match data_source {
102            DataSource::FilePath(_) => DataSource::None,
103            other => other,
104        };
105
106        // Return None if completely empty
107        if matches!(data_source, DataSource::None)
108            && path_columns.is_empty()
109            && content_type.is_none()
110            && custom.is_empty()
111        {
112            None
113        } else {
114            Some(Self {
115                data_source,
116                path_columns,
117                content_type,
118                custom,
119            })
120        }
121    }
122}
123
124/// Describes where the particular [`PipelineMetadata`] originates.
125///
126/// This can either be a particular family of commands or the opened file to protect against
127/// overwrite-attempts properly.
128#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
129pub enum DataSource {
130    HtmlThemes,
131    FilePath(PathBuf),
132    #[default]
133    None,
134}