mod utils; use std::str::FromStr; use serde::Deserialize; use shiny_configuration::{Configuration, GetConfigurationError}; use shiny_configuration::configuration_builder::BuildConfigurationError; use shiny_configuration::configuration_provider::env_provider::EnvironmentVariablesProvider; use shiny_configuration::configuration_provider::json5_provider::Json5Provider; #[test] fn integration_test() { let file_path_main = utils::write_to_temp_file( r#"{ root_object_one: { string: "Hello World", number: 15, bool: true, array: [ 1, 2 ] }, root_object_two: { inner_object: { enum_value: "EnumVariantTwo" } }, "root_object_three": { "value": "{{ profile.object_three_value }}", "value_two": "{{ env.ENV_VAR_TWO }}" } }"#, ); let file_path_profile = utils::write_to_temp_file( r#"{ object_three_value: 14 }"#, ); #[derive(Deserialize, PartialEq, Eq, Debug)] struct Root { root_object_one: RootObjectOne, root_object_two: RootObjectTwo, root_object_three: RootObjectThree, } #[derive(Deserialize, PartialEq, Eq, Debug)] struct RootObjectOne { string: String, number: i32, bool: bool, array: Vec, } #[derive(Deserialize, PartialEq, Eq, Debug)] struct RootObjectTwo { inner_object: RootObjectTwoInnerValue, } #[derive(Deserialize, PartialEq, Eq, Debug)] struct RootObjectTwoInnerValue { enum_value: EnumOne, } #[derive(Deserialize, PartialEq, Eq, Debug)] enum EnumOne { EnumVariantOne, EnumVariantTwo, ComplexEnumVariant { value: String, id: i32 }, } #[derive(Deserialize, PartialEq, Eq, Debug)] struct RootObjectThree { value: u64, value_two: EnumOne, } std::env::set_var("APP_PREFIX_ENV_VAR_TWO", "EnumVariantOne"); let main_yaml_provider = Json5Provider::from_path(&file_path_main).unwrap(); let profile_yaml_provider = Json5Provider::from_path(&file_path_profile).unwrap(); let configuration = Configuration::builder(main_yaml_provider) .with_provider("profile", profile_yaml_provider) .with_provider("env", EnvironmentVariablesProvider::prefixed("APP_PREFIX_")) .build() .unwrap(); let value: Root = configuration.get("").unwrap(); assert_eq!( value, Root { root_object_one: RootObjectOne { string: "Hello World".to_string(), number: 15, bool: true, array: vec![1, 2], }, root_object_two: RootObjectTwo { inner_object: RootObjectTwoInnerValue { enum_value: EnumOne::EnumVariantTwo, }, }, root_object_three: RootObjectThree { value: 14, value_two: EnumOne::EnumVariantOne, }, } ); let value: RootObjectOne = configuration.get("root_object_one").unwrap(); assert_eq!( value, RootObjectOne { string: "Hello World".to_string(), number: 15, bool: true, array: vec![1, 2], } ); let value: RootObjectTwo = configuration.get("root_object_two").unwrap(); assert!(matches!( value, RootObjectTwo { inner_object: RootObjectTwoInnerValue { enum_value: EnumOne::EnumVariantTwo, }, } )); let value: Vec = configuration.get("root_object_one.array").unwrap(); assert_eq!(value, vec![1u64, 2u64]); let err = configuration .get::>("non_existent_object") .unwrap_err(); assert!( matches!(err, GetConfigurationError::MissingValue(key) if key == "non_existent_object") ); let value: Option = configuration.get("root_object_two").unwrap(); assert_eq!( value, Some(RootObjectTwo { inner_object: RootObjectTwoInnerValue { enum_value: EnumOne::EnumVariantTwo, }, }) ); let value = configuration .get::("root_object_one") .unwrap_err(); assert_eq!( value.to_string(), "Failed to deserialize value [key = `root_object_one`]" ); } #[test] fn circular_dependency_test() { let file_path_valid = utils::write_to_temp_file( r#" { one: { a: "{{ provider.one.b }}", b: "{{ provider.one.c }}", c: "{{ provider.three.b }}" }, two: "{{ provider.one }}", three: { a: "{{ provider.two }}", b: 13 } } "#, ); let file_path_invalid_1 = utils::write_to_temp_file( r#"{ one: "{{ provider.two }}", two: "{{ provider.three }}", three: "{{ provider.one }}", }"#, ); let file_path_invalid_2 = utils::write_to_temp_file( r#"{ "one": { "b": "{{ provider.two.d }}", "d": 13 }, "two": { "d": { "e": "{{ provider.two.f }}" }, "f": "{{ provider.one }}" } }"#, ); let main = Json5Provider::from_str("{main: \"{{ provider.one }}\"}").unwrap(); let valid_provider = Json5Provider::from_path(&file_path_valid).unwrap(); let invalid_provider_1 = Json5Provider::from_path(&file_path_invalid_1).unwrap(); let invalid_provider_2 = Json5Provider::from_path(&file_path_invalid_2).unwrap(); let _ = Configuration::builder(main) .with_provider("provider", valid_provider) .build() .unwrap(); let main = Json5Provider::from_str("{main: \"{{provider.one}}\" }").unwrap(); let err = Configuration::builder(main) .with_provider("provider", invalid_provider_1) .build() .unwrap_err(); assert!(matches!(err, BuildConfigurationError::CircularReference { .. })); assert!(err.to_string().contains("provider.one -> provider.two")); assert!(err.to_string().contains("provider.two -> provider.three")); assert!(err.to_string().contains("provider.three -> provider.one")); let main = Json5Provider::from_str("{ main: \"{{ provider.one }}\" }").unwrap(); let err = Configuration::builder(main) .with_provider("provider", invalid_provider_2) .build() .unwrap_err(); assert!(matches!(err, BuildConfigurationError::CircularReference { .. })); assert!(err .to_string() .contains("provider.two.d.e -> provider.two.f")); assert!(err.to_string().contains("provider.two.f -> provider.one.b")); assert!(err .to_string() .contains("provider.one.b -> provider.two.d.e")); }