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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
use std::{path::PathBuf, process::Command};
use crate::commands::errors::{InvocationError, NonZeroExitCode, SignalTermination};
use pavex::blueprint::Blueprint;
/// The configuration for `pavex`'s `generate` command.
///
/// You can use [`Client::generate`] to start building the command configuration.
///
/// [`Client::generate`]: crate::Client::generate
pub struct GenerateBuilder {
cmd: Command,
diagnostics_path: Option<PathBuf>,
blueprint: Blueprint,
output_directory: PathBuf,
check: bool,
}
/// The representation of this command used in error messages.
static GENERATE_DEBUG_COMMAND: &str = "pavex [...] generate [...]";
impl GenerateBuilder {
pub(crate) fn new(cmd: Command, blueprint: Blueprint, output_directory: PathBuf) -> Self {
Self {
diagnostics_path: None,
blueprint,
cmd,
output_directory,
check: false,
}
}
/// Generate the runtime library for the application.
///
/// 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<(), GenerateError> {
let mut cmd = self
.command()
.map_err(GenerateError::BlueprintPersistenceError)?;
let status = cmd
.status()
.map_err(|e| InvocationError {
source: e,
command: GENERATE_DEBUG_COMMAND,
})
.map_err(GenerateError::InvocationError)?;
if !status.success() {
if let Some(code) = status.code() {
return Err(GenerateError::NonZeroExitCode(NonZeroExitCode {
code,
command: GENERATE_DEBUG_COMMAND,
}));
} else {
return Err(GenerateError::SignalTermination(SignalTermination {
command: GENERATE_DEBUG_COMMAND,
}));
}
}
Ok(())
}
/// Assemble the `std::process::Command` that will be used to invoke `pavex`,
/// but do not run it.
/// It **will** persist the blueprint to a file, though.
///
/// This method can be useful if you need to customize the command before running it.
/// If that's not your usecase, consider using [`GenerateBuilder::execute`] instead.
pub fn command(mut self) -> Result<Command, BlueprintPersistenceError> {
// TODO: Pass the blueprint via `stdin` instead of writing it to a file.
let bp_path = self.output_directory.join("blueprint.ron");
self.blueprint
.persist(&bp_path)
.map_err(|source| BlueprintPersistenceError { source })?;
self.cmd
.arg("generate")
.arg("-b")
.arg(bp_path)
.arg("-o")
.arg(self.output_directory)
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
if let Some(path) = self.diagnostics_path {
self.cmd.arg("--diagnostics").arg(path);
}
if self.check {
self.cmd.arg("--check");
}
Ok(self.cmd)
}
/// Set the path to the file that Pavex will use to serialize diagnostic
/// information about the application.
///
/// Diagnostics are primarily used for debugging the generator itself.
///
/// If this is not set, Pavex will not persist any diagnostic information.
pub fn diagnostics_path(mut self, path: PathBuf) -> Self {
self.diagnostics_path = Some(path);
self
}
/// Enable check mode.
///
/// In check mode, `pavex generate` verifies that the generated server SDK is up-to-date.
/// If it isn't, it returns an error without updating the SDK.
pub fn check(mut self) -> Self {
self.check = true;
self
}
/// Disable check mode.
///
/// `pavex` will regenerate the server SDK and update it on disk if it is outdated.
pub fn no_check(mut self) -> Self {
self.check = false;
self
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum GenerateError {
#[error(transparent)]
InvocationError(InvocationError),
#[error(transparent)]
SignalTermination(SignalTermination),
#[error(transparent)]
NonZeroExitCode(NonZeroExitCode),
#[error(transparent)]
BlueprintPersistenceError(BlueprintPersistenceError),
}
#[derive(Debug, thiserror::Error)]
#[error("Failed to persist the blueprint to a file")]
pub struct BlueprintPersistenceError {
#[source]
source: anyhow::Error,
}