casper_node/utils/
external.rs1use std::{
7 fmt::{Debug, Display},
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use casper_types::{
13 crypto,
14 file_utils::{read_file, ReadFileError},
15};
16use datasize::DataSize;
17#[cfg(test)]
18use once_cell::sync::Lazy;
19use openssl::{
20 pkey::{PKey, Private},
21 x509::X509,
22};
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25
26use casper_types::SecretKey;
27
28use crate::tls::{self, LoadCertError, LoadSecretKeyError};
29
30#[cfg(test)]
32pub static RESOURCES_PATH: Lazy<PathBuf> =
33 Lazy::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../resources"));
34
35#[derive(Clone, DataSize, Eq, Debug, Deserialize, PartialEq, Serialize, Default)]
47#[serde(untagged)]
48pub enum External {
49 Path(PathBuf),
51 #[serde(skip)]
53 #[default]
54 Missing,
55}
56
57impl External {
58 pub fn load<T, P>(self, root: P) -> Result<T, LoadError<T::Error>>
61 where
62 T: Loadable,
63 P: AsRef<Path>,
64 {
65 match self {
66 External::Path(path) => {
67 let full_path = if path.is_relative() {
68 root.as_ref().join(&path)
69 } else {
70 path
71 };
72
73 T::from_path(&full_path).map_err(move |error| LoadError::Failed {
74 error,
75 path: full_path.canonicalize().unwrap_or(full_path),
79 })
80 }
81 External::Missing => Err(LoadError::Missing),
82 }
83 }
84}
85
86pub trait Loadable: Sized {
88 type Error: Debug + Display;
90
91 fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>;
93
94 #[cfg(test)]
96 fn from_resources<P: AsRef<Path>>(rel_path: P) -> Self {
97 Self::from_path(RESOURCES_PATH.join(rel_path.as_ref())).unwrap_or_else(|error| {
98 panic!(
99 "could not load resources from {}: {}",
100 rel_path.as_ref().display(),
101 error
102 )
103 })
104 }
105}
106
107fn display_res_path<E>(result: &Result<PathBuf, E>) -> String {
108 result
109 .as_ref()
110 .map(|p| p.display().to_string())
111 .unwrap_or_else(|_| String::new())
112}
113
114#[derive(Debug, Error)]
116pub enum LoadError<E: Debug + Display> {
117 #[error("could not load from {}: {error}", display_res_path(&.path.canonicalize()))]
119 Failed {
120 path: PathBuf,
122 error: E,
124 },
125 #[error("value is missing (default requested)")]
127 Missing,
128}
129
130impl Loadable for X509 {
132 type Error = anyhow::Error;
133
134 fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error> {
135 let error = match tls::load_cert(path) {
136 Ok(cert) => return Ok(cert),
137 Err(LoadCertError::ReadFile(error)) => {
138 anyhow::Error::new(error).context("failed to load certificate")
139 }
140 Err(LoadCertError::X509CertFromPem(error)) => {
141 anyhow::Error::new(error).context("parsing certificate")
142 }
143 };
144 Err(error)
145 }
146}
147
148impl Loadable for PKey<Private> {
149 type Error = anyhow::Error;
150
151 fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error> {
152 let error = match tls::load_secret_key(path) {
153 Ok(secret_key) => return Ok(secret_key),
154 Err(LoadSecretKeyError::ReadFile(error)) => {
155 anyhow::Error::new(error).context("failed to load private key")
156 }
157 Err(LoadSecretKeyError::PrivateKeyFromPem(error)) => {
158 anyhow::Error::new(error).context("parsing private key")
159 }
160 };
161
162 Err(error)
163 }
164}
165
166impl Loadable for Arc<SecretKey> {
167 type Error = crypto::ErrorExt;
168
169 fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error> {
170 Ok(Arc::new(SecretKey::from_file(path)?))
171 }
172}
173
174impl Loadable for Vec<u8> {
175 type Error = ReadFileError;
176
177 fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error> {
178 read_file(path)
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::External;
185
186 #[test]
187 fn test_to_string() {
188 let val: External = External::Path("foo/bar.toml".into());
189 assert_eq!(
190 "\"foo/bar.toml\"",
191 serde_json::to_string(&val).expect("serialization error")
192 );
193 }
194
195 #[test]
196 fn test_load_from_string() {
197 let input = "\"foo/bar.toml\"";
198
199 let val: External = serde_json::from_str(input).expect("deserialization failed");
200
201 assert_eq!(External::Path("foo/bar.toml".into()), val);
202 }
203}