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