1
use std::io::Error as IoError;
2

            
3
use cargo_toml::Error as CargoManifestError;
4
use object_store::Error as S3Error;
5
use serde_json::Error as SerdeJsonError;
6
use thiserror::Error as DeriveError;
7

            
8
pub(crate) const DEFAULT_SOLANA_CONFIG_PATH: &str = ".config/solana/cli/config.yml";
9
pub(crate) const SOLANA_CONFIG_DOCS_URL: &str =
10
    "https://solana.com/docs/intro/installation#solana-config";
11

            
12
#[derive(Debug, DeriveError)]
13
pub enum BonsolCliError {
14
    #[error(transparent)]
15
    ParseConfigError(#[from] ParseConfigError),
16

            
17
    #[error("Failed to read keypair from file '{file}': {err}")]
18
    FailedToReadKeypair { file: String, err: String },
19

            
20
    #[error("Account '{0}' does not have any SOL to pay for the transaction(s)")]
21
    InsufficientFunds(String),
22

            
23
    #[error(transparent)]
24
    ZkManifestError(#[from] ZkManifestError),
25

            
26
    #[error("Build failed: the following errors were captured from stderr:\n\n{0}")]
27
    BuildFailure(String),
28

            
29
    #[error("Failed to compute an image ID from binary at path '{binary_path}': {err:?}")]
30
    FailedToComputeImageId {
31
        binary_path: String,
32
        err: anyhow::Error,
33
    },
34

            
35
    #[error(transparent)]
36
    S3ClientError(#[from] S3ClientError),
37

            
38
    #[error("This upload method is not supported")]
39
    UnsupportedDeployError(),
40

            
41
    #[error("The binary uploaded does not match the local binary at path '{binary_path}', is the URL correct?\nupload_url: {url}")]
42
    OriginBinaryMismatch { url: String, binary_path: String },
43

            
44
    #[error("The following build dependencies are missing: {}", missing_deps.join(", "))]
45
    MissingBuildDependencies { missing_deps: Vec<String> },
46

            
47
    #[error("Build Dependency version mismatch: {} is required at version {}, but the current version is {}", dep, version, current_version)]
48
    BuildDependencyVersionMismatch {
49
        dep: String,
50
        version: String,
51
        current_version: String,
52
    },
53
}
54

            
55
#[derive(Debug, DeriveError, Clone)]
56
pub enum ParseConfigError {
57
    #[error("")]
58
    Uninitialized,
59

            
60
    #[error("The provided solana cli config path '{path:?}' does not exist")]
61
    ConfigNotFound { path: String },
62

            
63
    #[error("The default solana cli config path '/home/{whoami}/{DEFAULT_SOLANA_CONFIG_PATH}' does not exist.")]
64
    DefaultConfigNotFound { whoami: String },
65

            
66
    #[error("Failed to load solana cli config at '{path}': {err}")]
67
    FailedToLoad { path: String, err: String },
68
}
69
impl ParseConfigError {
70
    pub(crate) fn context(&self, whoami: Option<String>) -> String {
71
        match self {
72
            Self::ConfigNotFound { .. } => "The solana cli config path was invalid, please double check that the path is correct and try again.\nTip: Try using an absolute path.".to_string(),
73
            Self::DefaultConfigNotFound { .. } => format!(
74
"The default solana cli config path is used when no other options for deriving the RPC URL and keypair file path are provided, ie. '--rpc_url' and '--keypair', or a path to a config that isn't at the default location, ie '--config'.
75
Tip: Try running 'solana config get'. If you have a custom config path set, double check that the default path also exists. A custom config path can be passed to bonsol with the '--config' option, eg. 'bonsol --config /path/to/config.yml'.
76

            
77
For more information on the solana cli config see: {}",
78
                SOLANA_CONFIG_DOCS_URL
79
            ),
80
            Self::FailedToLoad { path, .. } => {
81
                if let Some(whoami) = whoami {
82
                    let default_path = format!("/home/{}/{}", whoami, DEFAULT_SOLANA_CONFIG_PATH);
83
                    if path == &default_path {
84
                        return format!(
85
"The default solana cli config path is used when no other options for deriving the RPC URL and keypair file path are provided, ie. '--rpc_url' and '--keypair', or a path to a config that isn't at the default location, ie '--config'.
86
Tip: Try running 'solana config get'. This will give you information about your current config. If for whatever reason the keypair or RPC URL are missing, please follow the instructions below and try again.
87

            
88
- To generate a new keypair at the default path: 'solana-keygen new'
89
- To set the RPC URL, select a cluster. For instance, 'mainnet-beta': 'solana config set --url mainnet-beta'
90

            
91
For more information on the solana cli config see: {}",
92
                            SOLANA_CONFIG_DOCS_URL
93
                        );
94
                    }
95
                }
96
                format!(
97
"The config at '{}' exists, but there was a problem parsing it into what bonsol needs, ie. a keypair file and RPC URL.
98
Tip: Try running 'solana config get'. This will give you information about your current config. If for whatever reason the keypair or RPC URL are missing, please follow the instructions below and try again.
99

            
100
- To generate a new keypair at the default path: 'solana-keygen new'
101
- To set the RPC URL, select a cluster. For instance, 'mainnet-beta': 'solana config set --url mainnet-beta'
102

            
103
For more information on the solana cli config see: {}",
104
                    path,
105
                    SOLANA_CONFIG_DOCS_URL
106
                )
107
            },
108
            Self::Uninitialized => unreachable!(),
109
        }
110
    }
111
}
112

            
113
#[derive(Debug, DeriveError)]
114
pub enum ZkManifestError {
115
    #[error("Failed to open manifest at '{manifest_path}': {err:?}")]
116
    FailedToOpen { manifest_path: String, err: IoError },
117

            
118
    #[error("Failed to deserialize json manifest at '{manifest_path}': {err:?}")]
119
    FailedDeserialization {
120
        manifest_path: String,
121
        err: SerdeJsonError,
122
    },
123

            
124
    #[error(
125
        "Failed to produce zkprogram image binary path: Image binary path contains non-UTF8 encoded characters"
126
    )]
127
    InvalidBinaryPath,
128

            
129
    #[error("Failed to load binary from manifest at '{binary_path}': {err:?}")]
130
    FailedToLoadBinary { binary_path: String, err: IoError },
131

            
132
    #[error("Program path {0} does not contain a Cargo.toml")]
133
    MissingManifest(String),
134

            
135
    #[error("Failed to load manifest at '{manifest_path}': {err:?}")]
136
    FailedToLoadManifest {
137
        manifest_path: String,
138
        err: CargoManifestError,
139
    },
140

            
141
    #[error("Expected '{name}' to be a table at '{manifest_path}'")]
142
    ExpectedTable { manifest_path: String, name: String },
143

            
144
    #[error("Expected '{name}' to be an array at '{manifest_path}'")]
145
    ExpectedArray { manifest_path: String, name: String },
146

            
147
    #[error("Manifest at '{0}' does not contain a package name")]
148
    MissingPackageName(String),
149

            
150
    #[error("Manifest at '{0}' does not contain a package metadata field")]
151
    MissingPackageMetadata(String),
152

            
153
    #[error("Manifest at '{manifest_path}' has a metadata table that is missing a zkprogram metadata key: meta: {meta:?}")]
154
    MissingProgramMetadata {
155
        manifest_path: String,
156
        meta: cargo_toml::Value,
157
    },
158

            
159
    #[error("Manifest at '{manifest_path}' has a zkprogram metadata table that is missing a input_order key: zkprogram: {zkprogram:?}")]
160
    MissingInputOrder {
161
        manifest_path: String,
162
        zkprogram: cargo_toml::Value,
163
    },
164

            
165
    #[error("Failed to parse input: Input contains non-UTF8 encoded characters: {0}")]
166
    InvalidInput(cargo_toml::Value),
167

            
168
    #[error("Failed to parse the following inputs at '{manifest_path}': {}", errs.join("\n"))]
169
    InvalidInputs {
170
        manifest_path: String,
171
        errs: Vec<String>,
172
    },
173
}
174

            
175
#[derive(Debug, DeriveError)]
176
pub enum S3ClientError {
177
    #[error("Failed to build S3 client with the following args:\n{}\n\n{err:?}", args.join(",\n"))]
178
    FailedToBuildClient { args: Vec<String>, err: S3Error },
179

            
180
    #[error("Failed to upload to '{dest}': {err:?}")]
181
    UploadFailed {
182
        dest: object_store::path::Path,
183
        err: S3Error,
184
    },
185
}