Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 52 additions & 26 deletions crates/configuration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use anyhow::{Context, Result, anyhow};
use camino::Utf8PathBuf;
use scarb_metadata::{Metadata, PackageId};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Number};
use std::collections::HashMap;
use std::fs::File;
use std::{env, fs};
use tempfile::{TempDir, tempdir};
use toml::Value;

pub const CONFIG_FILENAME: &str = "snfoundry.toml";

/// Defined in snfoundry.toml
Expand All @@ -30,6 +33,18 @@ pub trait PackageConfig {
Self: Sized;
}

#[derive(Debug, Serialize, Deserialize)]
struct ConfigSchema<T> {
#[serde(flatten)]
pub tools: HashMap<String, ToolProfiles<T>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct ToolProfiles<T> {
#[serde(flatten)]
pub profiles: HashMap<String, T>,
}

fn get_with_ownership(config: serde_json::Value, key: &str) -> Option<serde_json::Value> {
match config {
serde_json::Value::Object(mut map) => map.remove(key),
Expand Down Expand Up @@ -64,31 +79,40 @@ pub fn resolve_config_file() -> Utf8PathBuf {
})
}

pub fn load_config<T: Config + Default>(
path: Option<&Utf8PathBuf>,
profile: Option<&str>,
) -> Result<T> {
let config_path = path
pub fn load_config<T>(path: Option<&Utf8PathBuf>, profile: Option<&str>) -> Result<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename this something it suggests more it's loading form snfoundry_toml?

where
T: Config + Default + Serialize + DeserializeOwned + Clone,
{
let path = path
.as_ref()
.and_then(|p| search_config_upwards_relative_to(p).ok())
.or_else(|| find_config_file().ok());

match config_path {
Some(path) => {
let raw_config_toml = fs::read_to_string(path)
.context("Failed to read snfoundry.toml config file")?
.parse::<Value>()
.context("Failed to parse snfoundry.toml config file")?;
let Some(config_path) = path else {
return Ok(T::default());
};

let raw_config_json = serde_json::to_value(raw_config_toml)
.context("Conversion from TOML value to JSON value should not fail.")?;
let raw = fs::read_to_string(config_path).context("Failed to read snfoundry.toml")?;
let toml_value: toml::Value =
toml::from_str(&raw).context("Failed to parse snfoundry.toml config file")?;
let json_value = serde_json::to_value(toml_value)?;
let resolved_json = resolve_env_variables(json_value)?;
let parsed: ConfigSchema<T> = serde_json::from_value(resolved_json)
.context("Failed to deserialize resolved config into ConfigSchema")?;
let tool_name = T::tool_name();

let profile = get_profile(raw_config_json, T::tool_name(), profile)?;
T::from_raw(resolve_env_variables(profile)?)
}
None => Ok(T::default()),
}
let Some(tool_profiles) = parsed.tools.get(tool_name) else {
return Ok(T::default());
};

let profile_name = profile.unwrap_or("default");
let Some(profile_config) = tool_profiles.profiles.get(profile_name) else {
return Err(anyhow!("Profile [{profile_name}] not found in config"));
};

Ok(profile_config.clone())
}

/// Loads config for a specific package from the `Scarb.toml` file
/// # Arguments
/// * `metadata` - Scarb metadata object
Expand Down Expand Up @@ -252,7 +276,7 @@ mod tests {
);
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct StubConfig {
#[serde(default)]
pub url: String,
Expand Down Expand Up @@ -305,7 +329,7 @@ mod tests {
assert_eq!(config.url, String::new());
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct StubComplexConfig {
#[serde(default)]
pub url: String,
Expand All @@ -315,7 +339,7 @@ mod tests {
pub nested: StubComplexConfigNested,
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct StubComplexConfigNested {
#[serde(
default,
Expand Down Expand Up @@ -353,11 +377,13 @@ mod tests {
#[test]
#[expect(clippy::float_cmp)]
fn resolve_env_vars() {
let tempdir =
copy_config_to_tempdir("tests/data/stubtool_snfoundry.toml", Some("childdir1"))
.unwrap();
let tempdir = copy_config_to_tempdir(
"tests/data/stubtool_complex_snfoundry.toml",
Some("childdir1"),
)
.unwrap();
fs::copy(
"tests/data/stubtool_snfoundry.toml",
"tests/data/stubtool_complex_snfoundry.toml",
tempdir.path().join("childdir1").join(CONFIG_FILENAME),
)
.expect("Failed to copy config file to temp dir");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[stubtool.with-envs]
url = "$VALUE_STRING123132"
account = "$VALUE_INT123132"

[stubtool.with-envs.nested]
list-example = [ "$VALUE_BOOL1231321", "$VALUE_BOOL1231322" ]
url-nested = "$VALUE_FLOAT123132"
url-alt = "${VALUE_STRING123142}"
8 changes: 0 additions & 8 deletions crates/configuration/tests/data/stubtool_snfoundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,3 @@ account = "user3"
[stubtool.profile5]
url = "http://127.0.0.1:5055/rpc"
account = "user8"

[stubtool.with-envs]
url = "$VALUE_STRING123132"
account = "$VALUE_INT123132"
[stubtool.with-envs.nested]
list-example = [ "$VALUE_BOOL1231321", "$VALUE_BOOL1231322" ]
url-nested = "$VALUE_FLOAT123132"
url-alt = "${VALUE_STRING123142}"
1 change: 1 addition & 0 deletions crates/sncast/tests/e2e/show_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ async fn test_show_config_from_cli() {
Wait Timeout: 2s
Wait Retry Interval: 1s
Show Explorer Links: true
Block Explorer: StarkScan
", URL});
}

Expand Down
Loading