starina_api/environ.rs
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
//! Environ, a collection of key-value pairs passed to the application.
use alloc::vec::Vec;
use core::fmt;
use hashbrown::HashMap;
use starina_types::environ::Device;
use starina_types::environ::EnvType;
use starina_types::environ::EnvironDeserializer;
use starina_types::handle::HandleId;
use crate::channel::Channel;
use crate::handle::OwnedHandle;
use crate::vmspace::VmSpace;
#[derive(Debug)]
enum Value {
Channel(Channel),
VmSpace(VmSpace),
Devices(Vec<Device>),
String(&'static str),
}
/// Environ, short for *environment*, is a collection of key-value pairs that
/// are used to:
///
/// - Dependency injection. Especially channels connected to dependent services.
/// - Configuration settings.
/// - Command-line arguments (shell is not available as of this writing though!).
/// - The [`VmSpace`] of the current process. To manage its own address space.
///
/// # Environ is a key-value store
///
/// The keys are always strings, and the values can be of different types.
/// Currently, the supported types are:
///
/// - Channel.
/// - VmSpace.
/// - A list of found devices (for device drivers).
///
/// # How to request environ items
///
/// To request an environ item,
///
/// # Examples
///
/// ```
/// pub fn main(mut env: Environ) {
/// // Dump all environ items.
/// info!("env: {:#?}", env);
///
/// // Take the ownership of the channel.
/// let driver_ch: Channel = env.take_channel("dep:ethernet_device").unwrap();
/// }
/// ```
///
/// This snippet logs:
///
/// ```text
/// [tcpip ] INFO env: {
/// "dep:ethernet_device": Channel(
/// Channel(#1),
/// ),
/// "dep:startup": Channel(
/// Channel(#2),
/// ),
/// }
/// ```
///
/// # Difference from environment variables
///
/// Environ is similar to environment variables in POSIX, and actually, internal
/// implementation is mostly the same (both key and value are strings). However,
/// the key difference is that Starina enforces convention on key names so that we can
/// provide a consistent and type-safe API.
///
/// Otherwise, each application would have different command-line parsers.
pub struct Environ {
entries: HashMap<&'static str, Value>,
}
impl Environ {
pub(crate) fn parse(raw: &'static str) -> Environ {
let mut entries = HashMap::new();
let mut deserializer = EnvironDeserializer::new(raw);
while let Some((key, env_type, value_str)) = deserializer.pop() {
let value = match env_type {
EnvType::Channel => {
let raw_handle_id = value_str.parse::<i32>().unwrap();
debug_assert!(raw_handle_id >= 0);
let handle_id = HandleId::from_raw(raw_handle_id);
let channel = Channel::from_handle(OwnedHandle::from_raw(handle_id));
Value::Channel(channel)
}
EnvType::VmSpace => {
let raw_handle_id = value_str.parse::<i32>().unwrap();
debug_assert!(raw_handle_id >= 0);
let handle_id = HandleId::from_raw(raw_handle_id);
let vmspace = VmSpace::from_handle(OwnedHandle::from_raw(handle_id));
Value::VmSpace(vmspace)
}
EnvType::Devices => {
let devices = serde_json::from_str(value_str).unwrap();
Value::Devices(devices)
}
EnvType::String => Value::String(value_str),
};
entries.insert(key, value);
}
Environ { entries }
}
/// Returns the channel associated with the key.
///
/// If the key is not found, or is already taken, `None` is returned.
///
/// # Panics
///
/// Panics if the value associated with the key is not a channel.
pub fn take_channel(&mut self, key: &str) -> Option<Channel> {
match self.entries.remove(key) {
Some(Value::Channel(channel)) => Some(channel),
Some(_) => panic!("not a channel"),
None => None,
}
}
/// Returns the vmspace associated with the key.
///
/// If the key is not found, or is already taken, `None` is returned.
///
/// # Panics
///
/// Panics if the value associated with the key is not a vmspace.
pub fn take_vmspace(&mut self, key: &str) -> Option<VmSpace> {
match self.entries.remove(key) {
Some(Value::VmSpace(vmspace)) => Some(vmspace),
Some(_) => panic!("not a channel"),
None => None,
}
}
/// Returns the devices associated with the key.
pub fn devices(&self, key: &str) -> Option<&[Device]> {
match self.entries.get(key) {
Some(Value::Devices(devices)) => Some(devices),
Some(_) => panic!("not devices"),
None => None,
}
}
pub fn string(&self, key: &str) -> Option<&str> {
match self.entries.get(key) {
Some(Value::String(s)) => Some(s),
Some(_) => panic!("not a string"),
None => None,
}
}
}
impl fmt::Debug for Environ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.entries.iter()).finish()
}
}