1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/* -------------------------------------------------------- *\
 *                                                          *
 *      ███╗░░░███╗░█████╗░░██████╗██╗░░██╗██╗███╗░░██╗     *
 *      ████╗░████║██╔══██╗██╔════╝██║░░██║██║████╗░██║     *
 *      ██╔████╔██║███████║╚█████╗░███████║██║██╔██╗██║     *
 *      ██║╚██╔╝██║██╔══██║░╚═══██╗██╔══██║██║██║╚████║     *
 *      ██║░╚═╝░██║██║░░██║██████╔╝██║░░██║██║██║░╚███║     *
 *      ╚═╝░░░░░╚═╝╚═╝░░╚═╝╚═════╝░╚═╝░░╚═╝╚═╝╚═╝░░╚══╝     *
 *                                         by Nutshimit     *
 * -------------------------------------------------------- *
 *                                                          *
 *   This file is dual-licensed as Apache-2.0 or GPL-3.0.   *
 *   see LICENSE for license details.                       *
 *                                                          *
\* ---------------------------------------------------------*/

use std::{
	any::{type_name, Any, TypeId},
	collections::BTreeMap,
};

/// `ProviderState` is a storage container for provider-specific data that can be shared between
/// provider and resource instances. It allows providers to store and access data such as API
/// clients or other shared information.
///
/// The implementation is inspired by and based on the `GothamState` from the Deno project.
/// See <https://github.com/denoland/deno/blob/main/core/gotham_state.rs> for the original code.
///
/// `ProviderState` stores data in a type-safe manner using the `TypeId` of the stored value.
/// It supports storing one value for each type, and provides methods for inserting, borrowing,
/// borrowing mutably, and taking ownership of values.
///
/// # Examples
///
/// ```no_run
/// use mashin_sdk::ProviderState;
///
/// struct ApiClient {
///     // ...
/// }
///
/// let mut state = ProviderState::default();
/// state.put(ApiClient { /* ... */ });
///
/// // Accessing the stored ApiClient instance
/// let api_client: &ApiClient = state.borrow();
/// ```
///
/// In this example, an `ApiClient` instance is stored in a `ProviderState` and later borrowed for use.
///
#[derive(Debug, Default)]
pub struct ProviderState {
	data: BTreeMap<TypeId, Box<dyn Any + Send + Sync>>,
}

impl ProviderState {
	/// Puts a value into the `State` storage. One value of each type is retained.
	/// Successive calls to `put` will overwrite the existing value of the same
	/// type.
	pub fn put<T: 'static + Send + Sync>(&mut self, t: T) {
		let type_id = TypeId::of::<T>();
		//trace!(" inserting record to state for type_id `{:?}`", type_id);
		self.data.insert(type_id, Box::new(t));
	}

	/// Determines if the current value exists in `State` storage.
	pub fn has<T: 'static>(&self) -> bool {
		let type_id = TypeId::of::<T>();
		self.data.get(&type_id).is_some()
	}

	/// Tries to borrow a value from the `State` storage.
	#[allow(clippy::should_implement_trait)]
	pub fn try_borrow<T: 'static>(&self) -> Option<&T> {
		let type_id = TypeId::of::<T>();
		//trace!(" borrowing state data for type_id `{:?}`", type_id);
		self.data.get(&type_id).and_then(|b| b.downcast_ref())
	}

	/// Borrows a value from the `State` storage.
	#[allow(clippy::should_implement_trait)]
	pub fn borrow<T: 'static>(&self) -> &T {
		self.try_borrow().unwrap_or_else(|| missing::<T>())
	}

	/// Tries to mutably borrow a value from the `State` storage.
	#[allow(clippy::should_implement_trait)]
	pub fn try_borrow_mut<T: 'static>(&mut self) -> Option<&mut T> {
		let type_id = TypeId::of::<T>();
		//trace!(" mutably borrowing state data for type_id `{:?}`", type_id);
		self.data.get_mut(&type_id).and_then(|b| b.downcast_mut())
	}

	/// Mutably borrows a value from the `State` storage.
	#[allow(clippy::should_implement_trait)]
	pub fn borrow_mut<T: 'static>(&mut self) -> &mut T {
		self.try_borrow_mut().unwrap_or_else(|| missing::<T>())
	}

	/// Tries to move a value out of the `State` storage and return ownership.
	pub fn try_take<T: 'static>(&mut self) -> Option<T> {
		let type_id = TypeId::of::<T>();
		//trace!(
		//    " taking ownership from state data for type_id `{:?}`",
		//    type_id
		//);
		self.data.remove(&type_id).and_then(|b| b.downcast().ok()).map(|b| *b)
	}

	/// Moves a value out of the `State` storage and returns ownership.
	///
	/// # Panics
	///
	/// If a value of type `T` is not present in `State`.
	pub fn take<T: 'static>(&mut self) -> T {
		self.try_take().unwrap_or_else(|| missing::<T>())
	}
}

fn missing<T: 'static>() -> ! {
	panic!("required type {} is not present in State container", type_name::<T>());
}