pavex_cli_client/commands/
new.rs

1use crate::commands::errors::{InvocationError, NonZeroExitCode, SignalTermination};
2use std::{path::PathBuf, process::Command, str::FromStr};
3
4/// The name of a template to use when creating a new Pavex project.
5#[derive(Clone, Debug, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum TemplateName {
8    /// A minimal API template.
9    Api,
10    /// The project template used by the Pavex quickstart guide.
11    Quickstart,
12}
13
14impl TemplateName {
15    pub fn as_str(&self) -> &str {
16        match self {
17            TemplateName::Api => "api",
18            TemplateName::Quickstart => "quickstart",
19        }
20    }
21}
22
23impl FromStr for TemplateName {
24    type Err = InvalidTemplateName;
25
26    fn from_str(s: &str) -> Result<Self, Self::Err> {
27        match s {
28            "api" => Ok(TemplateName::Api),
29            "quickstart" => Ok(TemplateName::Quickstart),
30            s => Err(InvalidTemplateName {
31                name: s.to_string(),
32            }),
33        }
34    }
35}
36
37#[derive(Debug, thiserror::Error)]
38#[error("`{name}` is not a valid template name. Use either `api` or `quickstart`.")]
39pub struct InvalidTemplateName {
40    pub(crate) name: String,
41}
42
43/// The configuration for `pavex`'s `new` command.
44///
45/// You can use [`Client::new`] to start building the command configuration.
46///
47/// [`Client::new`]: crate::Client::new
48pub struct NewBuilder {
49    cmd: Command,
50    path: PathBuf,
51    template: TemplateName,
52}
53
54impl NewBuilder {
55    pub(crate) fn new(cmd: Command, path: PathBuf) -> Self {
56        Self {
57            cmd,
58            path,
59            template: TemplateName::Api,
60        }
61    }
62
63    /// Set the template to use when creating a new Pavex project.
64    pub fn template(mut self, template: TemplateName) -> Self {
65        self.template = template;
66        self
67    }
68
69    /// Scaffold a new Pavex project.
70    ///
71    /// This will invoke `pavex` with the chosen configuration.
72    /// It won't return until `pavex` has finished running.
73    ///
74    /// If `pavex` exits with a non-zero status code, this will return an error.
75    pub fn execute(self) -> Result<(), NewError> {
76        let mut cmd = self.command();
77        let cmd_debug = format!("{cmd:?}");
78        let status = cmd
79            .status()
80            .map_err(|e| InvocationError {
81                source: e,
82                command: cmd_debug.clone(),
83            })
84            .map_err(NewError::InvocationError)?;
85        if !status.success() {
86            if let Some(code) = status.code() {
87                return Err(NewError::NonZeroExitCode(NonZeroExitCode {
88                    code,
89                    command: cmd_debug,
90                }));
91            } else {
92                return Err(NewError::SignalTermination(SignalTermination {
93                    command: cmd_debug,
94                }));
95            }
96        }
97        Ok(())
98    }
99
100    /// Assemble the `std::process::Command` that will be used to invoke `pavex`,
101    /// but do not run it.
102    ///
103    /// This method can be useful if you need to customize the command before running it.
104    /// If that's not your usecase, consider using [`NewBuilder::execute`] instead.
105    pub fn command(mut self) -> Command {
106        self.cmd
107            .arg("new")
108            .arg(self.path)
109            .arg("--template")
110            .arg(self.template.as_str())
111            .stdout(std::process::Stdio::inherit())
112            .stderr(std::process::Stdio::inherit());
113        self.cmd
114    }
115}
116
117#[derive(Debug, thiserror::Error)]
118#[non_exhaustive]
119pub enum NewError {
120    #[error(transparent)]
121    InvocationError(InvocationError),
122    #[error(transparent)]
123    SignalTermination(SignalTermination),
124    #[error(transparent)]
125    NonZeroExitCode(NonZeroExitCode),
126}