solana_cli_config/
lib.rs

1//! Loading and saving the Solana CLI configuration file.
2//!
3//! The configuration file used by the Solana CLI includes information about the
4//! RPC node to connect to, the path to the user's signing source, and more.
5//! Other software than the Solana CLI may wish to access the same configuration
6//! and signer.
7//!
8//! The default path to the configuration file can be retrieved from
9//! [`CONFIG_FILE`], which is a [lazy_static] of `Option<String>`, the value of
10//! which is
11//!
12//! > `~/.config/solana/cli/config.yml`
13//!
14//! [`CONFIG_FILE`]: struct@CONFIG_FILE
15//! [lazy_static]: https://docs.rs/lazy_static
16//!
17//! `CONFIG_FILE` will only be `None` if it is unable to identify the user's
18//! home directory, which should not happen under typical OS environments.
19//!
20//! The CLI configuration is defined by the [`Config`] struct, and its value is
21//! loaded with [`Config::load`] and saved with [`Config::save`].
22//!
23//! Two important fields of `Config` are
24//!
25//! - [`json_rpc_url`], the URL to pass to
26//!   `solana_rpc_client::rpc_client::RpcClient`.
27//! - [`keypair_path`], a signing source, which may be a keypair file, but
28//!   may also represent several other types of signers, as described in
29//!   the documentation for `solana_clap_utils::keypair::signer_from_path`.
30//!
31//! [`json_rpc_url`]: Config::json_rpc_url
32//! [`keypair_path`]: Config::keypair_path
33//!
34//! # Examples
35//!
36//! Loading and saving the configuration. Note that this uses the [anyhow] crate
37//! for error handling.
38//!
39//! [anyhow]: https://docs.rs/anyhow
40//!
41//! ```no_run
42//! use anyhow::anyhow;
43//! use solana_cli_config::{CONFIG_FILE, Config};
44//!
45//! let config_file = solana_cli_config::CONFIG_FILE.as_ref()
46//!     .ok_or_else(|| anyhow!("unable to get config file path"))?;
47//! let mut cli_config = Config::load(&config_file)?;
48//! // Set the RPC URL to devnet
49//! cli_config.json_rpc_url = "https://api.devnet.solana.com".to_string();
50//! cli_config.save(&config_file)?;
51//! # Ok::<(), anyhow::Error>(())
52//! ```
53
54#[macro_use]
55extern crate lazy_static;
56
57mod config;
58mod config_input;
59use std::{
60    fs::{create_dir_all, File},
61    io::{self, Write},
62    path::Path,
63};
64pub use {
65    config::{Config, CONFIG_FILE},
66    config_input::{ConfigInput, SettingType},
67};
68
69/// Load a value from a file in YAML format.
70///
71/// Despite the name, this function is generic YAML file deserializer, a thin
72/// wrapper around serde.
73///
74/// Most callers should instead use [`Config::load`].
75///
76/// # Errors
77///
78/// This function may return typical file I/O errors.
79pub fn load_config_file<T, P>(config_file: P) -> Result<T, io::Error>
80where
81    T: serde::de::DeserializeOwned,
82    P: AsRef<Path>,
83{
84    let file = File::open(config_file)?;
85    let config = serde_yaml::from_reader(file)
86        .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{err:?}")))?;
87    Ok(config)
88}
89
90/// Save a value to a file in YAML format.
91///
92/// Despite the name, this function is a generic YAML file serializer, a thin
93/// wrapper around serde.
94///
95/// If the file's directory does not exist, it will be created. If the file
96/// already exists, it will be overwritten.
97///
98/// Most callers should instead use [`Config::save`].
99///
100/// # Errors
101///
102/// This function may return typical file I/O errors.
103pub fn save_config_file<T, P>(config: &T, config_file: P) -> Result<(), io::Error>
104where
105    T: serde::ser::Serialize,
106    P: AsRef<Path>,
107{
108    let serialized = serde_yaml::to_string(config)
109        .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{err:?}")))?;
110
111    if let Some(outdir) = config_file.as_ref().parent() {
112        create_dir_all(outdir)?;
113    }
114    let mut file = File::create(config_file)?;
115    file.write_all(b"---\n")?;
116    file.write_all(&serialized.into_bytes())?;
117
118    Ok(())
119}