1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::commands::errors::{InvocationError, NonZeroExitCode, SignalTermination};
use std::{path::PathBuf, process::Command, str::FromStr};

/// The name of a template to use when creating a new Pavex project.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TemplateName {
    /// A minimal API template.
    Api,
    /// The project template used by the Pavex quickstart guide.
    Quickstart,
}

impl TemplateName {
    pub fn as_str(&self) -> &str {
        match self {
            TemplateName::Api => "api",
            TemplateName::Quickstart => "quickstart",
        }
    }
}

impl FromStr for TemplateName {
    type Err = InvalidTemplateName;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "api" => Ok(TemplateName::Api),
            "quickstart" => Ok(TemplateName::Quickstart),
            s => Err(InvalidTemplateName {
                name: s.to_string(),
            }),
        }
    }
}

#[derive(Debug, thiserror::Error)]
#[error("`{name}` is not a valid template name. Use either `api` or `quickstart`.")]
pub struct InvalidTemplateName {
    pub(crate) name: String,
}

/// The configuration for `pavex`'s `new` command.
///
/// You can use [`Client::new`] to start building the command configuration.
///
/// [`Client::new`]: crate::Client::new
pub struct NewBuilder {
    cmd: Command,
    path: PathBuf,
    template: TemplateName,
}

/// The representation of this command used in error messages.
static NEW_DEBUG_COMMAND: &str = "pavex [...] new [...]";

impl NewBuilder {
    pub(crate) fn new(cmd: Command, path: PathBuf) -> Self {
        Self {
            cmd,
            path,
            template: TemplateName::Api,
        }
    }

    /// Set the template to use when creating a new Pavex project.
    pub fn template(mut self, template: TemplateName) -> Self {
        self.template = template;
        self
    }

    /// Scaffold a new Pavex project.
    ///
    /// This will invoke `pavex` with the chosen configuration.
    /// It won't return until `pavex` has finished running.
    ///
    /// If `pavex` exits with a non-zero status code, this will return an error.
    pub fn execute(self) -> Result<(), NewError> {
        let mut cmd = self.command();
        let status = cmd
            .status()
            .map_err(|e| InvocationError {
                source: e,
                command: NEW_DEBUG_COMMAND,
            })
            .map_err(NewError::InvocationError)?;
        if !status.success() {
            if let Some(code) = status.code() {
                return Err(NewError::NonZeroExitCode(NonZeroExitCode {
                    code,
                    command: NEW_DEBUG_COMMAND,
                }));
            } else {
                return Err(NewError::SignalTermination(SignalTermination {
                    command: NEW_DEBUG_COMMAND,
                }));
            }
        }
        Ok(())
    }

    /// Assemble the `std::process::Command` that will be used to invoke `pavex`,
    /// but do not run it.
    ///
    /// This method can be useful if you need to customize the command before running it.
    /// If that's not your usecase, consider using [`NewBuilder::execute`] instead.
    pub fn command(mut self) -> Command {
        self.cmd
            .arg("new")
            .arg(self.path)
            .arg("--template")
            .arg(self.template.as_str())
            .stdout(std::process::Stdio::inherit())
            .stderr(std::process::Stdio::inherit());
        self.cmd
    }
}

#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum NewError {
    #[error(transparent)]
    InvocationError(InvocationError),
    #[error(transparent)]
    SignalTermination(SignalTermination),
    #[error(transparent)]
    NonZeroExitCode(NonZeroExitCode),
}