pub mod builder;
pub mod signer;
#[cfg(feature = "file_io")]
use std::path::Path;
use std::{
cell::RefCell,
io::{BufRead, BufReader, Cursor},
};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use signer::SignerSettings;
use crate::{
crypto::base64, http::restricted::HostPattern, settings::builder::BuilderSettings, Error,
Result,
};
const VERSION: u32 = 1;
const MERGE_MAX_DEPTH: usize = 64;
pub(crate) const MAX_ASSERTIONS: usize = 100_000;
thread_local!(
static SETTINGS: RefCell<Value> = RefCell::new(
serde_json::to_value(Settings::default()).unwrap_or(Value::Object(Map::new())),
);
);
pub(crate) trait SettingsValidate {
fn validate(&self) -> Result<()> {
Ok(())
}
}
#[cfg_attr(
feature = "json_schema",
derive(schemars::JsonSchema),
schemars(default)
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Trust {
pub(crate) verify_trust_list: bool,
pub user_anchors: Option<String>,
pub trust_anchors: Option<String>,
pub trust_config: Option<String>,
pub allowed_list: Option<String>,
}
impl Trust {
fn load_trust_from_data(&self, trust_data: &[u8]) -> Result<Vec<Vec<u8>>> {
let mut certs = Vec::new();
let trust_data = String::from_utf8_lossy(trust_data)
.replace("\\n", "\n")
.into_bytes();
for pem_result in x509_parser::pem::Pem::iter_from_buffer(&trust_data) {
let pem = pem_result.map_err(|_e| Error::CoseInvalidCert)?;
certs.push(pem.contents);
}
Ok(certs)
}
fn test_load_trust(&self, allowed_list: &[u8]) -> Result<()> {
if let Ok(cert_list) = self.load_trust_from_data(allowed_list) {
if !cert_list.is_empty() {
return Ok(());
}
}
let reader = Cursor::new(allowed_list);
let buf_reader = BufReader::new(reader);
let mut found_der_hash = false;
let mut inside_cert_block = false;
for l in buf_reader.lines().map_while(|v| v.ok()) {
if l.contains("-----BEGIN") {
inside_cert_block = true;
}
if l.contains("-----END") {
inside_cert_block = false;
}
if !inside_cert_block && base64::decode(&l).is_ok() && !l.is_empty() {
found_der_hash = true;
}
}
if found_der_hash {
Ok(())
} else {
Err(Error::CoseInvalidCert)
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for Trust {
fn default() -> Self {
#[cfg(test)]
{
let mut trust = Self {
verify_trust_list: true,
user_anchors: None,
trust_anchors: None,
trust_config: None,
allowed_list: None,
};
trust.trust_config = Some(
String::from_utf8_lossy(include_bytes!(
"../../tests/fixtures/certs/trust/store.cfg"
))
.into_owned(),
);
trust.user_anchors = Some(
String::from_utf8_lossy(include_bytes!(
"../../tests/fixtures/certs/trust/test_cert_root_bundle.pem"
))
.into_owned(),
);
trust
}
#[cfg(not(test))]
{
Self {
verify_trust_list: true,
user_anchors: None,
trust_anchors: None,
trust_config: None,
allowed_list: None,
}
}
}
}
impl SettingsValidate for Trust {
fn validate(&self) -> Result<()> {
if let Some(ta) = &self.trust_anchors {
self.test_load_trust(ta.as_bytes())?;
}
if let Some(pa) = &self.user_anchors {
self.test_load_trust(pa.as_bytes())?;
}
if let Some(al) = &self.allowed_list {
self.test_load_trust(al.as_bytes())?;
}
Ok(())
}
}
#[cfg_attr(
feature = "json_schema",
derive(schemars::JsonSchema),
schemars(default)
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Core {
pub merkle_tree_chunk_size_in_kb: Option<usize>,
pub merkle_tree_max_proofs: usize,
pub backing_store_memory_threshold_in_mb: usize,
pub decode_identity_assertions: bool,
pub allowed_network_hosts: Option<Vec<HostPattern>>,
pub prefer_compress_manifests: bool,
}
impl Default for Core {
fn default() -> Self {
Self {
merkle_tree_chunk_size_in_kb: None,
merkle_tree_max_proofs: 5,
backing_store_memory_threshold_in_mb: 512,
decode_identity_assertions: true,
allowed_network_hosts: None,
prefer_compress_manifests: false,
}
}
}
impl SettingsValidate for Core {
fn validate(&self) -> Result<()> {
Ok(())
}
}
#[cfg_attr(
feature = "json_schema",
derive(schemars::JsonSchema),
schemars(default)
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Verify {
pub verify_after_reading: bool,
pub verify_after_sign: bool,
pub(crate) verify_trust: bool,
pub(crate) verify_timestamp_trust: bool,
pub ocsp_fetch: bool,
pub remote_manifest_fetch: bool,
pub(crate) skip_ingredient_conflict_resolution: bool,
pub strict_v1_validation: bool,
}
impl Default for Verify {
fn default() -> Self {
Self {
verify_after_reading: true,
verify_after_sign: false, verify_trust: true,
verify_timestamp_trust: !cfg!(test), ocsp_fetch: false,
remote_manifest_fetch: true,
skip_ingredient_conflict_resolution: false,
strict_v1_validation: false,
}
}
}
impl SettingsValidate for Verify {}
#[cfg_attr(
feature = "json_schema",
derive(schemars::JsonSchema),
schemars(default)
)]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Settings {
pub version: u32,
pub trust: Trust,
pub cawg_trust: Trust,
pub core: Core,
pub verify: Verify,
pub builder: BuilderSettings,
#[serde(skip_serializing_if = "Option::is_none")]
pub signer: Option<SignerSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cawg_x509_signer: Option<SignerSettings>,
}
impl Settings {
#[cfg(feature = "file_io")]
#[doc(hidden)]
#[deprecated(
note = "Use `Settings::new().with_file(path)` instead, which does not modify thread-local state."
)]
#[allow(deprecated)]
pub fn from_file<P: AsRef<Path>>(settings_path: P) -> Result<Self> {
let ext = settings_path
.as_ref()
.extension()
.ok_or(Error::UnsupportedType)?
.to_string_lossy();
let setting_buf = std::fs::read(&settings_path).map_err(Error::IoError)?;
Settings::from_string(&String::from_utf8_lossy(&setting_buf), &ext)
}
#[doc(hidden)]
#[deprecated(
note = "Use `Settings::new().with_json(str)` or `Settings::new().with_toml(str)` instead, which do not modify thread-local state."
)]
pub fn from_string(settings_str: &str, format: &str) -> Result<Self> {
let overlay = parse_to_value(settings_str, format)?;
let mut merged = SETTINGS.with_borrow(Value::clone);
merge_json(&mut merged, overlay);
let settings: Settings =
serde_json::from_value(merged.clone()).map_err(|e| Error::BadParam(e.to_string()))?;
settings.validate()?;
SETTINGS.set(merged);
Ok(settings)
}
#[deprecated(
note = "Use `Settings::new().with_toml(toml)` instead, which does not modify thread-local state."
)]
#[allow(deprecated)]
pub fn from_toml(toml: &str) -> Result<()> {
Settings::from_string(toml, "toml").map(|_| ())
}
pub fn update_from_str(&mut self, settings_str: &str, format: &str) -> Result<()> {
*self = self.with_string(settings_str, format)?;
Ok(())
}
#[allow(unused)]
pub(crate) fn set_thread_local_value<T: Into<Value>>(value_path: &str, value: T) -> Result<()> {
let mut merged = SETTINGS.with_borrow(Value::clone);
set_at_path(&mut merged, value_path, value.into())?;
let settings: Settings =
serde_json::from_value(merged.clone()).map_err(|e| Error::BadParam(e.to_string()))?;
settings.validate()?;
SETTINGS.set(merged);
Ok(())
}
#[allow(unused)]
fn get_thread_local_value<T: serde::de::DeserializeOwned>(value_path: &str) -> Result<T> {
SETTINGS.with_borrow(|current| {
let leaf = get_at_path(current, value_path)
.ok_or_else(|| Error::BadParam("could not get settings value".into()))?;
serde_json::from_value(leaf.clone())
.map_err(|err| Error::BadParam(format!("could not get settings value: {err}")))
})
}
#[allow(unused)]
pub(crate) fn reset() -> Result<()> {
let value = serde_json::to_value(Settings::default())
.map_err(|err| Error::OtherError(format!("could not reset settings: {err}").into()))?;
SETTINGS.set(value);
Ok(())
}
pub fn new() -> Self {
Self::default()
}
pub fn with_json(&self, json: &str) -> Result<Self> {
self.with_string(json, "json")
}
pub fn with_toml(&self, toml: &str) -> Result<Self> {
self.with_string(toml, "toml")
}
#[cfg(feature = "file_io")]
pub fn with_file<P: AsRef<Path>>(&self, path: P) -> Result<Self> {
let path = path.as_ref();
let ext = path
.extension()
.ok_or(Error::BadParam(
"settings file must have json or toml extension".into(),
))?
.to_str()
.ok_or(Error::BadParam("invalid settings file name".into()))?;
let setting_buf = std::fs::read(path).map_err(Error::IoError)?;
self.with_string(&String::from_utf8_lossy(&setting_buf), ext)
}
fn with_string(&self, settings_str: &str, format: &str) -> Result<Self> {
let overlay = parse_to_value(settings_str, format)?;
let mut merged =
serde_json::to_value(self).map_err(|err| Error::OtherError(Box::new(err)))?;
merge_json(&mut merged, overlay);
let settings: Settings =
serde_json::from_value(merged).map_err(|err| Error::BadParam(err.to_string()))?;
settings.validate()?;
Ok(settings)
}
#[doc(hidden)]
#[deprecated(
note = "Use `toml::to_string(&settings)` on a `Settings` instance instead of reading from thread-local state."
)]
pub fn to_toml() -> Result<String> {
let settings = get_thread_local_settings();
Ok(toml::to_string(&settings)?)
}
#[doc(hidden)]
#[deprecated(
note = "Use `toml::to_string_pretty(&settings)` on a `Settings` instance instead of reading from thread-local state."
)]
pub fn to_pretty_toml() -> Result<String> {
let settings = get_thread_local_settings();
Ok(toml::to_string_pretty(&settings)?)
}
#[inline]
#[deprecated(
note = "Configure the signer via `Context` and pass it to `Builder::from_context` instead of using thread-local signer settings."
)]
pub fn signer() -> Result<crate::BoxedSigner> {
SignerSettings::signer()
}
pub fn with_value<T: Into<Value>>(&self, path: &str, value: T) -> Result<Self> {
let mut merged =
serde_json::to_value(self).map_err(|err| Error::OtherError(Box::new(err)))?;
set_at_path(&mut merged, path, value.into())
.map_err(|err| Error::BadParam(format!("invalid path '{path}': {err}")))?;
let updated_settings: Settings = serde_json::from_value(merged)
.map_err(|err| Error::BadParam(format!("invalid value for '{path}': {err}")))?;
updated_settings.validate()?;
Ok(updated_settings)
}
pub fn set_value<T: Into<Value>>(&mut self, path: &str, value: T) -> Result<()> {
*self = self.with_value(path, value)?;
Ok(())
}
pub fn get_value<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
let value = serde_json::to_value(self).map_err(|err| Error::OtherError(Box::new(err)))?;
let leaf = get_at_path(&value, path)
.ok_or_else(|| Error::BadParam(format!("value at '{path}' not found")))?;
serde_json::from_value(leaf.clone())
.map_err(|err| Error::BadParam(format!("failed to get value at '{path}': {err}")))
}
}
impl Default for Settings {
fn default() -> Self {
Settings {
version: VERSION,
trust: Default::default(),
cawg_trust: Default::default(),
core: Default::default(),
verify: Default::default(),
builder: Default::default(),
signer: None,
cawg_x509_signer: None,
}
}
}
impl SettingsValidate for Settings {
fn validate(&self) -> Result<()> {
if self.version > VERSION {
return Err(Error::VersionCompatibility(
"settings version too new".into(),
));
}
if let Some(signer) = &self.signer {
signer.validate()?;
}
if let Some(cawg_x509_signer) = &self.cawg_x509_signer {
cawg_x509_signer.validate()?;
}
self.trust.validate()?;
self.cawg_trust.validate()?;
self.core.validate()?;
self.builder.validate()
}
}
fn merge_json(target: &mut Value, overlay: Value) {
merge_json_depth(target, overlay, 0);
}
fn merge_json_depth(target: &mut Value, overlay: Value, depth: usize) {
match (target, overlay) {
(Value::Object(target_map), Value::Object(overlay_map)) if depth < MERGE_MAX_DEPTH => {
for (key, overlay_value) in overlay_map {
merge_json_depth(
target_map.entry(key).or_insert(Value::Null),
overlay_value,
depth + 1,
);
}
}
(target, overlay) => *target = overlay,
}
}
fn parse_to_value(settings_str: &str, format: &str) -> Result<Value> {
match format.to_lowercase().as_str() {
"json" => serde_json::from_str(settings_str)
.map_err(|err| Error::BadParam(format!("could not parse configuration: {err}"))),
"toml" => {
let toml_value: toml::Value = toml::from_str(settings_str)
.map_err(|err| Error::BadParam(format!("could not parse configuration: {err}")))?;
serde_json::to_value(toml_value)
.map_err(|err| Error::BadParam(format!("could not parse configuration: {err}")))
}
_ => Err(Error::UnsupportedType),
}
}
fn set_at_path(target: &mut Value, path: &str, value: Value) -> Result<()> {
let mut segments = path.split('.').peekable();
let mut current = target;
while let Some(segment) = segments.next() {
if !current.is_object() {
*current = Value::Object(Map::new());
}
let Some(map) = current.as_object_mut() else {
return Err(Error::BadParam("expected object at path segment".into()));
};
if segments.peek().is_none() {
map.insert(segment.to_string(), value);
return Ok(());
}
current = map
.entry(segment.to_string())
.or_insert_with(|| Value::Object(Map::new()));
}
Err(Error::BadParam("empty path".into()))
}
fn get_at_path<'a>(value: &'a Value, path: &str) -> Option<&'a Value> {
let mut current = value;
for segment in path.split('.') {
current = current.as_object()?.get(segment)?;
}
Some(current)
}
#[allow(unused)]
pub(crate) fn get_thread_local_settings() -> Settings {
SETTINGS.with_borrow(|value| serde_json::from_value(value.clone()).unwrap_or_default())
}
#[cfg(test)]
pub(crate) fn set_settings_value<T: Into<Value>>(value_path: &str, value: T) -> Result<()> {
Settings::set_thread_local_value(value_path, value)
}
#[cfg(test)]
fn get_settings_value<T: serde::de::DeserializeOwned>(value_path: &str) -> Result<T> {
Settings::get_thread_local_value(value_path)
}
#[cfg(test)]
pub fn reset_default_settings() -> Result<()> {
Settings::reset()
}
pub(crate) fn deserialize_case_insensitive<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::de::DeserializeOwned,
{
let value = Value::deserialize(deserializer)?;
serde_json::from_value(normalize_enum_value(value)).map_err(serde::de::Error::custom)
}
pub(crate) fn deserialize_case_insensitive_opt<'de, D, T>(
deserializer: D,
) -> Result<Option<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::de::DeserializeOwned,
{
Option::<Value>::deserialize(deserializer)?
.map(|v| serde_json::from_value(normalize_enum_value(v)).map_err(serde::de::Error::custom))
.transpose()
}
fn normalize_enum_value(value: Value) -> Value {
match value {
Value::String(s) => Value::String(s.to_lowercase()),
other => other,
}
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::panic)]
#![allow(clippy::unwrap_used)]
use super::*;
#[cfg(feature = "file_io")]
use crate::utils::io_utils::tempdirectory;
use crate::{utils::test::test_settings, SigningAlg};
#[cfg(feature = "file_io")]
fn save_settings_as_json<P: AsRef<Path>>(settings_path: P) -> Result<()> {
let settings = get_thread_local_settings();
let settings_json = serde_json::to_string_pretty(&settings).map_err(Error::JsonError)?;
std::fs::write(settings_path, settings_json.as_bytes()).map_err(Error::IoError)
}
#[test]
fn test_thread_local_settings() {
let settings = get_thread_local_settings();
assert_eq!(settings.core, Core::default());
assert_eq!(settings.trust, Trust::default());
assert_eq!(settings.verify, Verify::default());
assert_eq!(settings.builder, BuilderSettings::default());
assert_eq!(
get_settings_value::<bool>("builder.thumbnail.enabled").unwrap(),
BuilderSettings::default().thumbnail.enabled
);
assert_eq!(
get_settings_value::<bool>("verify.remote_manifest_fetch").unwrap(),
Verify::default().remote_manifest_fetch
);
Settings::set_thread_local_value("core.merkle_tree_chunk_size_in_kb", 10).unwrap();
Settings::set_thread_local_value("verify.remote_manifest_fetch", false).unwrap();
Settings::set_thread_local_value("builder.thumbnail.enabled", false).unwrap();
assert_eq!(
get_settings_value::<usize>("core.merkle_tree_chunk_size_in_kb").unwrap(),
10
);
assert!(!get_settings_value::<bool>("verify.remote_manifest_fetch").unwrap());
assert!(!get_settings_value::<bool>("builder.thumbnail.enabled").unwrap());
reset_default_settings().unwrap();
}
#[cfg(feature = "file_io")]
#[test]
fn test_save_load() {
let temp_dir = tempdirectory().unwrap();
let op = crate::utils::test::temp_dir_path(&temp_dir, "sdk_config.json");
save_settings_as_json(&op).unwrap();
let settings = Settings::new().with_file(&op).unwrap();
assert_eq!(settings, Settings::default());
}
#[test]
fn test_settings_from_json_str() {
let json = serde_json::to_string(&Settings::default()).unwrap();
let settings = Settings::new().with_json(&json).unwrap();
assert_eq!(settings, Settings::default());
}
#[test]
fn test_bad_setting() {
let modified_core = toml::toml! {
[core]
merkle_tree_chunk_size_in_kb = true
merkle_tree_max_proofs = "sha1000000"
backing_store_memory_threshold_in_mb = -123456
}
.to_string();
assert!(Settings::new().with_toml(&modified_core).is_err());
}
#[test]
#[allow(deprecated)]
fn test_thread_local_hidden_setting() {
let secret = toml::toml! {
[hidden]
test1 = true
test2 = "hello world"
test3 = 123456
}
.to_string();
Settings::from_toml(&secret).unwrap();
assert!(get_settings_value::<bool>("hidden.test1").unwrap());
assert_eq!(
get_settings_value::<String>("hidden.test2").unwrap(),
"hello world".to_string()
);
assert_eq!(
get_settings_value::<u32>("hidden.test3").unwrap(),
123456u32
);
reset_default_settings().unwrap();
}
#[test]
fn test_load_settings_from_sample_toml() {
let toml = include_bytes!("../../examples/c2pa.toml");
Settings::new()
.with_toml(std::str::from_utf8(toml).unwrap())
.unwrap();
}
#[test]
fn test_update_from_str_toml() {
let mut settings = Settings::default();
assert!(settings.verify.verify_after_reading);
assert!(settings.verify.verify_trust);
settings
.update_from_str(
r#"
[verify]
verify_after_reading = false
verify_trust = false
"#,
"toml",
)
.unwrap();
assert!(!settings.verify.verify_after_reading);
assert!(!settings.verify.verify_trust);
settings
.update_from_str(
r#"
[verify]
verify_after_reading = true
"#,
"toml",
)
.unwrap();
assert!(settings.verify.verify_after_reading);
assert!(!settings.verify.verify_trust);
}
#[test]
fn test_update_from_str_json() {
let mut settings = Settings::default();
assert!(settings.verify.verify_after_reading);
assert!(settings.verify.verify_trust);
assert!(settings.builder.created_assertion_labels.is_none());
settings
.update_from_str(
r#"
{
"verify": {
"verify_after_reading": false,
"verify_trust": false
},
"builder": {
"created_assertion_labels": ["c2pa.metadata"]
}
}
"#,
"json",
)
.unwrap();
assert!(!settings.verify.verify_after_reading);
assert!(!settings.verify.verify_trust);
assert_eq!(
settings.builder.created_assertion_labels,
Some(vec!["c2pa.metadata".to_string()])
);
settings
.update_from_str(
r#"
{
"verify": {
"verify_after_reading": true
}
}
"#,
"json",
)
.unwrap();
assert!(settings.verify.verify_after_reading);
assert!(!settings.verify.verify_trust);
assert_eq!(
settings.builder.created_assertion_labels,
Some(vec!["c2pa.metadata".to_string()])
);
settings
.update_from_str(
r#"
{
"builder": {
"created_assertion_labels": null
}
}
"#,
"json",
)
.unwrap();
assert!(settings.verify.verify_after_reading);
assert!(!settings.verify.verify_trust);
assert!(settings.builder.created_assertion_labels.is_none());
}
#[test]
fn test_update_from_str_invalid() {
assert!(Settings::default()
.update_from_str("invalid toml { ]", "toml")
.is_err());
assert!(Settings::default()
.update_from_str("{ invalid json }", "json")
.is_err());
assert!(Settings::default().update_from_str("data", "yaml").is_err());
}
#[test]
fn test_instance_with_value() {
let settings = Settings::default()
.with_value("verify.verify_trust", false)
.unwrap()
.with_value("core.merkle_tree_chunk_size_in_kb", 1024i64)
.unwrap()
.with_value("builder.thumbnail.enabled", false)
.unwrap();
assert!(!settings.verify.verify_trust);
assert_eq!(settings.core.merkle_tree_chunk_size_in_kb, Some(1024));
assert!(!settings.builder.thumbnail.enabled);
}
#[test]
fn test_instance_set_value() {
let mut settings = Settings::default();
settings.set_value("verify.verify_trust", true).unwrap();
settings
.set_value("core.merkle_tree_chunk_size_in_kb", 2048i64)
.unwrap();
settings
.set_value("builder.thumbnail.enabled", false)
.unwrap();
assert!(settings.verify.verify_trust);
assert_eq!(settings.core.merkle_tree_chunk_size_in_kb, Some(2048));
assert!(!settings.builder.thumbnail.enabled);
}
#[test]
fn test_instance_get_value() {
let mut settings = Settings::default();
settings.verify.verify_trust = false;
settings.core.merkle_tree_chunk_size_in_kb = Some(512);
let verify_trust: bool = settings.get_value("verify.verify_trust").unwrap();
assert!(!verify_trust);
let chunk_size = settings
.get_value::<Option<usize>>("core.merkle_tree_chunk_size_in_kb")
.unwrap();
assert_eq!(chunk_size, Some(512));
}
#[test]
fn test_instance_methods_with_context() {
use crate::Context;
let settings = Settings::default()
.with_value("verify.verify_after_sign", true)
.unwrap()
.with_value("verify.verify_trust", true)
.unwrap();
let _context = Context::new().with_settings(settings).unwrap();
}
#[test]
fn test_instance_value_error_handling() {
let mut settings = Settings::default();
let result = settings.set_value("verify.verify_trust", "not a bool");
assert!(result.is_err());
let settings = Settings::default();
let result = settings.get_value::<bool>("does.not.exist");
assert!(result.is_err());
let result = Settings::default().with_value("verify.verify_trust", "not a bool");
assert!(result.is_err());
}
#[test]
fn test_test_settings() {
let settings = test_settings();
assert!(
settings.trust.trust_anchors.is_some(),
"test_settings should include trust anchors"
);
assert!(
!settings.trust.trust_anchors.as_ref().unwrap().is_empty(),
"test_settings trust_anchors should not be empty"
);
assert!(
settings.signer.is_some(),
"test_settings should include a signer"
);
if let Some(SignerSettings::Local { alg, .. }) = &settings.signer {
assert!(
matches!(
alg,
SigningAlg::Ps256
| SigningAlg::Es256
| SigningAlg::Es384
| SigningAlg::Es512
| SigningAlg::Ed25519
),
"test_settings should have a valid signing algorithm"
);
} else {
panic!("test_settings should have a Local signer configured");
}
}
#[test]
fn test_builder_pattern() {
let settings = Settings::new()
.with_json(r#"{"verify": {"verify_trust": false}}"#)
.unwrap();
assert!(!settings.verify.verify_trust);
let settings = Settings::new()
.with_json(r#"{"verify": {"verify_after_reading": false}}"#)
.unwrap()
.with_value("verify.verify_trust", true)
.unwrap();
assert!(!settings.verify.verify_after_reading);
assert!(settings.verify.verify_trust);
let settings = Settings::new()
.with_toml(
r#"
[verify]
verify_trust = false
verify_after_sign = false
"#,
)
.unwrap();
assert!(!settings.verify.verify_trust);
assert!(!settings.verify.verify_after_sign);
let original = get_thread_local_settings();
let _settings = Settings::new()
.with_json(r#"{"verify": {"verify_trust": false}}"#)
.unwrap();
let after = get_thread_local_settings();
assert_eq!(
original.verify.verify_trust, after.verify.verify_trust,
"Builder pattern should not modify thread_local settings"
);
}
}