gravel_ffi/
config.rs

1use abi_stable::StableAbi;
2use abi_stable::std_types::{RSlice, RString};
3use figment::providers::{Format, Yaml};
4use serde::Deserialize;
5
6/// Abstracts config deserialization from plugins, allowing differing
7/// configuration for different plugin instances.
8#[repr(C)]
9#[derive(StableAbi)]
10pub struct PluginConfigAdapter<'a> {
11	key: RString,
12	config_sources: RSlice<'a, __private_api::ConfigLayer>,
13}
14
15impl<'a> PluginConfigAdapter<'a> {
16	/// Used internally by gravel, do not use in plugins.
17	#[doc(hidden)]
18	pub fn from(key: RString, config_sources: RSlice<'a, __private_api::ConfigLayer>) -> Self {
19		Self { key, config_sources }
20	}
21
22	/// Build and deserialize the plugin's config into the given type.
23	#[must_use]
24	pub fn get<'de, T: Deserialize<'de>>(&self, default_config: &str) -> T {
25		log::trace!("reading plugin config for {}", self.key);
26
27		// layer the plugins' defaults under the provider's config section
28		let figment = __private_api::create_figment(&self.config_sources)
29			.focus(&format!("{}.config", self.key))
30			.join(Yaml::string(default_config));
31
32		match figment.extract() {
33			Ok(config) => config,
34			Err(err) => {
35				log::error!("error in plugin config {}: {err}", self.key);
36				std::process::exit(1);
37			}
38		}
39	}
40}
41
42/// Used internally by gravel, do not use in plugins.
43#[doc(hidden)]
44pub mod __private_api {
45	use abi_stable::StableAbi;
46	use abi_stable::std_types::{RStr, RString};
47	use figment::Figment;
48	use figment::providers::{Format, Yaml};
49
50	// I'd really like to keep the app's figment and re-use it for plugins,
51	// but unfortunately it can't safely pass through FFI-boundaries.
52	pub fn create_figment(config_sources: &[ConfigLayer]) -> Figment {
53		fn fold(figment: Figment, ConfigLayer(source, strategy): &ConfigLayer) -> Figment {
54			let source = match source {
55				ConfigSource::String(s) => Yaml::string(s.as_str()),
56				ConfigSource::File(f) => Yaml::file(f.as_str()),
57			};
58
59			match strategy {
60				MergeStrategy::Merge => figment.merge(source),
61				MergeStrategy::AdMerge => figment.admerge(source),
62			}
63		}
64
65		config_sources.iter().fold(Figment::new(), fold)
66	}
67
68	#[repr(C)]
69	#[derive(StableAbi, Debug)]
70	pub struct ConfigLayer(pub ConfigSource, pub MergeStrategy);
71
72	#[repr(u8)]
73	#[derive(StableAbi, Debug)]
74	pub enum MergeStrategy {
75		Merge,
76		AdMerge,
77	}
78
79	#[repr(u8)]
80	#[derive(StableAbi, Debug)]
81	pub enum ConfigSource {
82		String(RStr<'static>),
83		File(RString),
84	}
85}