platz_chart_ext/
ext_type.rs

1use super::actions::ChartExtActions;
2use super::features::ChartExtFeatures;
3use super::ui_schema::UiSchema;
4use crate::metadata::ChartMetadata;
5use crate::resource_types::ChartExtResourceTypes;
6use serde::{de::DeserializeOwned, Serialize};
7use std::io::ErrorKind;
8use std::path::{Path, PathBuf};
9use tokio::fs::{self, read_to_string};
10use tokio::try_join;
11
12#[derive(Debug)]
13#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
14pub struct ChartExt {
15    pub metadata: Option<ChartMetadata>,
16    pub ui_schema: Option<UiSchema>,
17    pub actions: Option<ChartExtActions>,
18    pub features: Option<ChartExtFeatures>,
19    pub resource_types: Option<ChartExtResourceTypes>,
20    pub error: Option<String>,
21}
22
23impl ChartExt {
24    pub async fn from_path(path: &Path) -> Result<Self, std::io::Error> {
25        match read_chart(path).await {
26            Ok((metadata, ui_schema, actions, features, resource_types)) => Ok(Self {
27                metadata: Some(metadata),
28                ui_schema,
29                actions,
30                features,
31                resource_types,
32                error: None,
33            }),
34            Err(ChartExtError::IoError(err)) => Err(err),
35            Err(error) => Ok(Self {
36                metadata: None,
37                ui_schema: None,
38                actions: None,
39                features: None,
40                resource_types: None,
41                error: Some(error.to_string()),
42            }),
43        }
44    }
45
46    pub fn new_with_error(error: String) -> Self {
47        Self {
48            metadata: None,
49            ui_schema: None,
50            actions: None,
51            features: None,
52            resource_types: None,
53            error: Some(error),
54        }
55    }
56}
57
58#[derive(Debug, thiserror::Error)]
59enum ChartExtError {
60    #[error("std::io::Error: {0}")]
61    IoError(#[from] std::io::Error),
62    #[error("Could not find Chart.yaml in {0}")]
63    NoChartYaml(PathBuf),
64    #[error("Error while parsing {0}: {1}")]
65    ParseError(String, String),
66}
67
68async fn platz_dir_exists(path: &Path) -> Result<bool, std::io::Error> {
69    match fs::metadata(path.join("platz")).await {
70        Ok(metadata) if metadata.is_dir() => Ok(true),
71        Ok(_) => Ok(false),
72        Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
73        Err(err) => Err(err),
74    }
75}
76
77async fn read_chart(
78    path: &Path,
79) -> Result<
80    (
81        ChartMetadata,
82        Option<UiSchema>,
83        Option<ChartExtActions>,
84        Option<ChartExtFeatures>,
85        Option<ChartExtResourceTypes>,
86    ),
87    ChartExtError,
88> {
89    let metadata = try_read_chart_metadata(path).await?;
90    let (ui_schema, actions, features, resource_types) = if platz_dir_exists(path).await? {
91        try_read_chart_extensions(
92            path,
93            Some("platz/values-ui.yaml"),
94            Some("platz/actions.yaml"),
95            Some("platz/features.yaml"),
96            Some("platz/resources.yaml"),
97        )
98        .await?
99    } else {
100        try_read_chart_extensions(
101            path,
102            Some("values.ui.json"),
103            Some("actions.schema.json"),
104            Some("features.json"),
105            None,
106        )
107        .await?
108    };
109    Ok((metadata, ui_schema, actions, features, resource_types))
110}
111
112async fn try_read_chart_metadata(chart_path: &Path) -> Result<ChartMetadata, ChartExtError> {
113    read_spec_file(chart_path, Some("Chart.yaml"))
114        .await?
115        .ok_or_else(|| ChartExtError::NoChartYaml(chart_path.into()))
116}
117
118async fn try_read_chart_extensions(
119    chart_path: &Path,
120    ui_schema_filename: Option<&str>,
121    actions_filename: Option<&str>,
122    features_filename: Option<&str>,
123    resource_types_filename: Option<&str>,
124) -> Result<
125    (
126        Option<UiSchema>,
127        Option<ChartExtActions>,
128        Option<ChartExtFeatures>,
129        Option<ChartExtResourceTypes>,
130    ),
131    ChartExtError,
132> {
133    Ok(try_join!(
134        read_spec_file(chart_path, ui_schema_filename),
135        read_spec_file(chart_path, actions_filename),
136        read_spec_file(chart_path, features_filename),
137        read_spec_file(chart_path, resource_types_filename),
138    )?)
139}
140
141async fn read_spec_file<T>(path: &Path, filename: Option<&str>) -> Result<Option<T>, ChartExtError>
142where
143    T: Serialize + DeserializeOwned,
144{
145    let Some(filename) = filename else {
146        return Ok(None)
147    };
148
149    let full_path = path.join(filename);
150
151    let file_ext = full_path
152        .extension()
153        .and_then(|osstr| osstr.to_str())
154        .map(ToString::to_string);
155
156    let contents = match read_to_string(full_path).await {
157        Ok(contents) => contents,
158        Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None),
159        Err(err) => return Err(err.into()),
160    };
161
162    match file_ext.as_deref() {
163        Some("yaml") | Some("yml") => {
164            Ok(Some(serde_yaml::from_str(&contents).map_err(|err| {
165                ChartExtError::ParseError(filename.to_owned(), err.to_string())
166            })?))
167        }
168        Some("json") => Ok(Some(serde_json::from_str(&contents).map_err(|err| {
169            ChartExtError::ParseError(filename.to_owned(), err.to_string())
170        })?)),
171        _ => Err(ChartExtError::ParseError(
172            filename.to_owned(),
173            "Unknown file extension".to_owned(),
174        )),
175    }
176}