pavex_cli_client/commands/
generate.rs

1use std::{path::PathBuf, process::Command};
2
3use crate::commands::errors::{InvocationError, NonZeroExitCode, SignalTermination};
4use pavex::Blueprint;
5
6/// The configuration for `pavex`'s `generate` command.
7///
8/// You can use [`Client::generate`] to start building the command configuration.
9///
10/// [`Client::generate`]: crate::Client::generate
11pub struct GenerateBuilder {
12    cmd: Command,
13    diagnostics_path: Option<PathBuf>,
14    blueprint: Blueprint,
15    output_directory: PathBuf,
16    check: bool,
17}
18
19impl GenerateBuilder {
20    pub(crate) fn new(cmd: Command, blueprint: Blueprint, output_directory: PathBuf) -> Self {
21        Self {
22            diagnostics_path: None,
23            blueprint,
24            cmd,
25            output_directory,
26            check: false,
27        }
28    }
29
30    /// Generate the runtime library for the application.
31    ///
32    /// This will invoke `pavex` with the chosen configuration.
33    /// It won't return until `pavex` has finished running.
34    ///
35    /// If `pavex` exits with a non-zero status code, this will return an error.
36    pub fn execute(self) -> Result<(), GenerateError> {
37        let mut cmd = self
38            .command()
39            .map_err(GenerateError::BlueprintPersistenceError)?;
40        let cmd_debug = format!("{cmd:?}");
41        let status = cmd
42            .status()
43            .map_err(|e| InvocationError {
44                source: e,
45                command: cmd_debug.clone(),
46            })
47            .map_err(GenerateError::InvocationError)?;
48        if !status.success() {
49            if let Some(code) = status.code() {
50                return Err(GenerateError::NonZeroExitCode(NonZeroExitCode {
51                    code,
52                    command: cmd_debug,
53                }));
54            } else {
55                return Err(GenerateError::SignalTermination(SignalTermination {
56                    command: cmd_debug,
57                }));
58            }
59        }
60        Ok(())
61    }
62
63    /// Assemble the `std::process::Command` that will be used to invoke `pavex`,
64    /// but do not run it.
65    /// It **will** persist the blueprint to a file, though.
66    ///
67    /// This method can be useful if you need to customize the command before running it.
68    /// If that's not your usecase, consider using [`GenerateBuilder::execute`] instead.
69    pub fn command(mut self) -> Result<Command, BlueprintPersistenceError> {
70        // TODO: Pass the blueprint via `stdin` instead of writing it to a file.
71        let bp_path = self.output_directory.join("blueprint.ron");
72        self.blueprint
73            .persist(&bp_path)
74            .map_err(|source| BlueprintPersistenceError { source })?;
75
76        self.cmd
77            .arg("generate")
78            .arg("-b")
79            .arg(bp_path)
80            .arg("-o")
81            .arg(self.output_directory)
82            .stdout(std::process::Stdio::inherit())
83            .stderr(std::process::Stdio::inherit());
84
85        if let Some(path) = self.diagnostics_path {
86            self.cmd.arg("--diagnostics").arg(path);
87        }
88        if self.check {
89            self.cmd.arg("--check");
90        }
91        Ok(self.cmd)
92    }
93
94    /// Set the path to the file that Pavex will use to serialize diagnostic
95    /// information about the application.
96    ///
97    /// Diagnostics are primarily used for debugging the generator itself.
98    ///
99    /// If this is not set, Pavex will not persist any diagnostic information.
100    pub fn diagnostics_path(mut self, path: PathBuf) -> Self {
101        self.diagnostics_path = Some(path);
102        self
103    }
104
105    /// Enable check mode.
106    ///
107    /// In check mode, `pavex generate` verifies that the generated server SDK is up-to-date.
108    /// If it isn't, it returns an error without updating the SDK.
109    pub fn check(mut self) -> Self {
110        self.check = true;
111        self
112    }
113
114    /// Disable check mode.
115    ///
116    /// `pavex` will regenerate the server SDK and update it on disk if it is outdated.
117    pub fn no_check(mut self) -> Self {
118        self.check = false;
119        self
120    }
121}
122
123#[derive(Debug, thiserror::Error)]
124#[non_exhaustive]
125pub enum GenerateError {
126    #[error(transparent)]
127    InvocationError(InvocationError),
128    #[error(transparent)]
129    SignalTermination(SignalTermination),
130    #[error(transparent)]
131    NonZeroExitCode(NonZeroExitCode),
132    #[error(transparent)]
133    BlueprintPersistenceError(BlueprintPersistenceError),
134}
135
136#[derive(Debug, thiserror::Error)]
137#[error("Failed to persist the blueprint to a file")]
138pub struct BlueprintPersistenceError {
139    #[source]
140    source: anyhow::Error,
141}