Skip to main content

nu_protocol/pipeline/
metadata.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::Record;
6
7/// Metadata that is valid for the whole [`PipelineData`](crate::PipelineData)
8///
9/// ## Custom Metadata
10///
11/// The `custom` field allows commands and plugins to attach arbitrary metadata to pipeline data.
12/// To avoid key collisions, it is recommended to use namespaced keys with an underscore separator:
13///
14/// - `"http_response"` - HTTP response metadata (status, headers, etc.)
15/// - `"polars_schema"` - DataFrame schema information
16/// - `"custom_plugin_field"` - Plugin-specific metadata
17///
18/// This convention helps ensure different commands and plugins don't overwrite each other's metadata.
19#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
20pub struct PipelineMetadata {
21    pub data_source: DataSource,
22    pub path_columns: Vec<String>,
23    pub content_type: Option<String>,
24    #[serde(default)]
25    pub custom: Record,
26}
27
28impl PipelineMetadata {
29    pub fn with_data_source(self, data_source: DataSource) -> Self {
30        Self {
31            data_source,
32            ..self
33        }
34    }
35
36    pub fn with_path_columns(self, path_columns: Vec<String>) -> Self {
37        Self {
38            path_columns,
39            ..self
40        }
41    }
42
43    pub fn with_content_type(self, content_type: Option<String>) -> Self {
44        Self {
45            content_type,
46            ..self
47        }
48    }
49
50    /// Transform metadata for the `collect` operation.
51    ///
52    /// After collecting a stream into a value, `FilePath` data sources are no longer meaningful
53    /// and should be converted to `None`. If all metadata fields become empty after this
54    /// transformation, returns `None` to avoid carrying around empty metadata.
55    pub fn for_collect(self) -> Option<Self> {
56        let Self {
57            data_source,
58            path_columns,
59            content_type,
60            custom,
61        } = self;
62
63        // Transform FilePath to None after collect
64        let data_source = match data_source {
65            DataSource::FilePath(_) => DataSource::None,
66            other => other,
67        };
68
69        // Return None if completely empty
70        if matches!(data_source, DataSource::None)
71            && path_columns.is_empty()
72            && content_type.is_none()
73            && custom.is_empty()
74        {
75            None
76        } else {
77            Some(Self {
78                data_source,
79                path_columns,
80                content_type,
81                custom,
82            })
83        }
84    }
85}
86
87/// Describes where the particular [`PipelineMetadata`] originates.
88///
89/// This can either be a particular family of commands (useful so downstream commands can adjust
90/// the presentation e.g. `Ls`) or the opened file to protect against overwrite-attempts properly.
91#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
92pub enum DataSource {
93    #[deprecated(
94        since = "0.111.0",
95        note = "Use `PipelineMetadata { path_columns: vec![\"name\".to_string()], .. }` instead."
96    )]
97    Ls,
98    HtmlThemes,
99    FilePath(PathBuf),
100    #[default]
101    None,
102}