diff options
author | Marcel Müller <m.mueller@ifm.com> | 2022-05-05 12:13:26 +0200 |
---|---|---|
committer | Marcel Müller <m.mueller@ifm.com> | 2022-05-05 15:07:19 +0200 |
commit | 473801d5dd64ea9c2e669afe650ee210cd9edfa2 (patch) | |
tree | ba5969294aabe574cd1df9f1fb6356174f0f1c04 | |
parent | 93460d4807350cc35a88f40f10ea66b6c4e77b55 (diff) | |
parent | 60e1bad6bcb9701c5d738845116c8bb67b9883ed (diff) |
Merge branch 'feature/add_config_exploration' into feature/add_tedge_api_only
-rw-r--r-- | Cargo.lock | 166 | ||||
-rw-r--r-- | crates/core/tedge_api/Cargo.toml | 6 | ||||
-rw-r--r-- | crates/core/tedge_api/examples/print_config.rs | 80 | ||||
-rw-r--r-- | crates/core/tedge_api/src/config.rs | 212 | ||||
-rw-r--r-- | crates/core/tedge_api/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/core/tedge_api/src/message.rs | 7 | ||||
-rw-r--r-- | crates/core/tedge_api/src/plugin.rs | 13 |
7 files changed, 481 insertions, 6 deletions
@@ -84,6 +84,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33" [[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] name = "assert-json-diff" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -486,6 +492,15 @@ dependencies = [ ] [[package]] +name = "coolor" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaebeb52e38d53b890ebcb723ff23462f39353d4e73db48284591e6395a1c25e" +dependencies = [ + "crossterm", +] + +[[package]] name = "cpufeatures" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -540,6 +555,20 @@ dependencies = [ ] [[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] name = "crossbeam-channel" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -574,6 +603,16 @@ dependencies = [ ] [[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] name = "crossbeam-utils" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -584,6 +623,31 @@ dependencies = [ ] [[package]] +name = "crossterm" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio 0.7.14", + "parking_lot 0.11.2", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + +[[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1362,6 +1426,15 @@ dependencies = [ ] [[package]] +name = "minimad" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd37b2e65fbd459544194d8f52ed84027e031684335a062c708774c09d172b0b" +dependencies = [ + "once_cell", +] + +[[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1379,6 +1452,19 @@ dependencies = [ [[package]] name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "mio" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba42135c6a5917b9db9cd7b293e5409e1c6b041e6f9825e92e55a894c63b6f8" @@ -1550,6 +1636,16 @@ dependencies = [ ] [[package]] +name = "nu-ansi-term" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7bca0d33a384280d1563b97f49cb95303df9fa22588739a04b7d8015c1ccd50" +dependencies = [ + "overload", + "winapi", +] + +[[package]] name = "num" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1693,6 +1789,12 @@ dependencies = [ ] [[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1902,6 +2004,19 @@ dependencies = [ ] [[package]] +name = "pretty" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" +dependencies = [ + "arrayvec", + "log", + "termcolor", + "typed-arena", + "unicode-segmentation", +] + +[[package]] name = "pretty_assertions" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2538,6 +2653,27 @@ dependencies = [ ] [[package]] +name = "signal-hook" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio 0.7.14", + "signal-hook", +] + +[[package]] name = "signal-hook-registry" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2723,8 +2859,12 @@ dependencies = [ "downcast-rs", "futures", "miette", + "nu-ansi-term", + "pretty", "serde", "static_assertions", + "termcolor", + "termimad", "thiserror", "tokio", "tokio-util 0.7.0", @@ -2847,14 +2987,28 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] +name = "termimad" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c14caf224b3f3d43d93d0532098f9e4906bd79e0978a2e10951f17aab7898b" +dependencies = [ + "coolor", + "crossbeam", + "crossterm", + "minimad", + "thiserror", + "unicode-width", +] + +[[package]] name = "termtree" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2994,7 +3148,7 @@ dependencies = [ "bytes", "libc", "memchr", - "mio", + "mio 0.8.1", "num_cpus", "once_cell", "parking_lot 0.12.0", @@ -3229,6 +3383,12 @@ dependencies = [ ] [[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + +[[package]] name = "typenum" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/crates/core/tedge_api/Cargo.toml b/crates/core/tedge_api/Cargo.toml index afcd9800..7fdf5508 100644 --- a/crates/core/tedge_api/Cargo.toml +++ b/crates/core/tedge_api/Cargo.toml @@ -14,8 +14,12 @@ thiserror = "1.0.30" tokio = { version = "1.16.1", features = ["sync", "time"] } tokio-util = "0.7.0" toml = "0.5.8" +serde = { version = "1.0.136", features = ["derive"] } +pretty = { version = "0.11.3", features = ["termcolor"] } +termcolor = "1.1.3" +termimad = "0.20.1" +nu-ansi-term = "0.45.1" [dev-dependencies] -serde = { version = "1.0.136", features = ["derive"] } static_assertions = "1.1.0" tokio = { version = "1.16.1", features = ["full"] } diff --git a/crates/core/tedge_api/examples/print_config.rs b/crates/core/tedge_api/examples/print_config.rs new file mode 100644 index 00000000..5ee6458e --- /dev/null +++ b/crates/core/tedge_api/examples/print_config.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +use nu_ansi_term::Color; +use pretty::Arena; +use tedge_api::config::{AsConfig, ConfigDescription, ConfigKind}; +struct Port(u64); + +impl AsConfig for Port { + fn as_config() -> ConfigDescription { + ConfigDescription::new( + String::from("Integer"), + ConfigKind::Integer, + Some("A TCP port number is an integer between 0 and 65535"), + ) + } +} + +struct VHost; + +impl AsConfig for VHost { + fn as_config() -> ConfigDescription { + ConfigDescription::new( + String::from("VHost"), + ConfigKind::Struct(HashMap::from([(String::from("name"), String::as_config())])), + Some("A virtual host definition"), + ) + } +} + +fn main() { + let arena = Arena::new(); + + let doc = Vec::<String>::as_config(); + let rendered_doc = doc.as_terminal_doc(&arena); + + let mut output = String::new(); + + rendered_doc.render_fmt(80, &mut output).unwrap(); + + println!( + "------- Output for {}", + std::any::type_name::<Vec<String>>() + ); + println!("{}", output); + + let arena = Arena::new(); + + let doc = ConfigDescription::new( + String::from("ServerConfig"), + ConfigKind::Struct(HashMap::from([ + (String::from("port"), Port::as_config()), + (String::from("interface"), String::as_config()), + (String::from("virtual_hosts"), Vec::<VHost>::as_config()), + (String::from("headers"), HashMap::<String, String>::as_config()), + ])), + Some("Specify how the server should be started\n\n## Note\n\nThis is a reallly really loooooooooooooooooong loooooooooooooooooooong new *line*."), + ); + let rendered_doc = doc.as_terminal_doc(&arena); + + let mut output = String::new(); + + rendered_doc.render_fmt(80, &mut output).unwrap(); + + println!( + "Configuration for {} plugin kinds", + Color::White.bold().paint(doc.name()) + ); + println!( + "{}", + Color::White.dimmed().bold().paint(format!( + "=================={}=============", + std::iter::repeat('=') + .take(doc.name().len()) + .collect::<String>() + )) + ); + println!("------- Output for ServerConfig"); + println!("{}", output); + println!("-------"); +} diff --git a/crates/core/tedge_api/src/config.rs b/crates/core/tedge_api/src/config.rs new file mode 100644 index 00000000..7357b497 --- /dev/null +++ b/crates/core/tedge_api/src/config.rs @@ -0,0 +1,212 @@ +use std::collections::HashMap; + +use nu_ansi_term::Color; +use pretty::{Arena, Doc, DocAllocator, Pretty, RefDoc}; +use serde::Serialize; +use termimad::MadSkin; + +/// Generic config that represents what kind of config a plugin wishes to accept +#[derive(Debug, Serialize)] +pub struct ConfigDescription { + name: String, + kind: ConfigKind, + doc: Option<&'static str>, +} + +impl ConfigDescription { + /// Construct a new generic config explanation + #[must_use] + pub fn new(name: String, kind: ConfigKind, doc: Option<&'static str>) -> Self { + Self { name, kind, doc } + } + + /// Get a reference to the config's documentation. + #[must_use] + pub fn doc(&self) -> Option<&'static str> { + self.doc + } + + /// Get a reference to the config's kind. + #[must_use] + pub fn kind(&self) -> &ConfigKind { + &self.kind + } + + /// Set or replace the documentation of this [`Config`] + #[must_use] + pub fn with_doc(mut self, doc: Option<&'static str>) -> Self { + self.doc = doc; + self + } + + /// Get the config's name. + #[must_use] + pub fn name(&self) -> &str { + &self.name + } +} + +/// The specific kind a [`Config`] represents +#[derive(Debug, Serialize)] +pub enum ConfigKind { + /// Config represents a boolean `true`/`false` + Bool, + + /// Config represents an integer `1, 10, 200, 10_000, ...` + /// + /// # Note + /// + /// The maximum value that can be represented is between [`i64::MIN`] and [`i64::MAX`] + Integer, + + /// Config represents a floating point value `1.0, 20.235, 3.1419` + /// + /// # Note + /// Integers are also accepted and converted to their floating point variant + /// + /// The maximum value that can be represented is between [`f64::MIN`] and [`f64::MAX`] + Float, + + /// Config represents a string + String, + + /// Config represents an array of values of the given [`ConfigKind`] + Array(Box<ConfigDescription>), + + /// Config represents a map of different configurations + Struct(HashMap<String, ConfigDescription>), + + /// Config represents a hashmap of named configurations of the same type + /// + /// # Note + /// + /// The key is always a [`String`] so this only holds the value config + HashMap(Box<ConfigDescription>), +} + +/// Turn a plugin configuration into a [`Config`] object +/// +/// Plugin authors are expected to implement this for their configurations to give users +pub trait AsConfig { + /// Get a [`Config`] object from the type + fn as_config() -> ConfigDescription; +} + +impl<T: AsConfig> AsConfig for Vec<T> { + fn as_config() -> ConfigDescription { + ConfigDescription::new( + format!("Array of '{}'s", T::as_config().name()), + ConfigKind::Array(Box::new(T::as_config())), + None, + ) + } +} + +impl<V: AsConfig> AsConfig for HashMap<String, V> { + fn as_config() -> ConfigDescription { + ConfigDescription::new( + format!("Table of '{}'s", V::as_config().name()), + ConfigKind::HashMap(Box::new(V::as_config())), + None, + ) + } +} + +macro_rules! impl_config_kind { + ($kind:expr; $name:expr; $doc:expr => $($typ:ty),+) => { + $( + impl AsConfig for $typ { + fn as_config() -> ConfigDescription { + ConfigDescription::new({$name}.into(), $kind, Some($doc)) + } + } + )+ + }; +} + +impl_config_kind!(ConfigKind::Integer; "Integer"; "A signed integer with 64 bits" => u64, i64); +impl_config_kind!(ConfigKind::Float; "Float"; "A floating point value with 64 bits" => f64); +impl_config_kind!(ConfigKind::Bool; "Boolean"; "A boolean representing either true or false" => bool); +impl_config_kind!(ConfigKind::String; "String"; "An UTF-8 encoded string of characters" => String); + +/******Pretty Printing of Configs******/ + +impl ConfigDescription { + /// Get a [`RcDoc`](pretty::RcDoc) which can be used to write the documentation of this + pub fn as_terminal_doc<'a>(&'a self, arena: &'a Arena<'a>) -> RefDoc<'a> { + let mut doc = arena + .nil() + .append(Color::LightBlue.bold().paint(self.name()).to_string()) + .append(arena.hardline()); + + if let Some(conf_doc) = self.doc() { + let skin = MadSkin::default_dark(); + let rendered = skin.text(&conf_doc, None).to_string(); + doc = doc.append(arena.intersperse( + rendered.split("\n").map(|t| { + arena.intersperse( + t.split(char::is_whitespace).map(|t| t.to_string()), + arena.softline(), + ) + }), + arena.hardline(), + )); + } + + match self.kind() { + ConfigKind::Array(conf) => { + doc = doc.append(Pretty::pretty(conf.as_terminal_doc(arena), arena)) + } + ConfigKind::Struct(stc) => { + doc = doc + .append(arena.hardline()) + .append(Color::Blue.paint("[Members]").to_string()) + .append(arena.hardline()) + .append(arena.intersperse( + stc.iter().map(|(member_name, member_conf)| { + arena + .text(Color::Blue.bold().paint(member_name).to_string()) + .append(": ") + .append( + Pretty::pretty(member_conf.as_terminal_doc(arena), arena) + .nest(4), + ) + }), + Doc::hardline(), + )) + } + ConfigKind::HashMap(conf) => { + doc = doc.append(Pretty::pretty(conf.as_terminal_doc(arena), arena)) + } + _ => (), + }; + + doc.into_doc() + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::config::{AsConfig, ConfigDescription, ConfigKind}; + + #[test] + fn verify_correct_config_kinds() { + assert!(matches!( + Vec::<f64>::as_config(), + ConfigDescription { + doc: None, + kind: ConfigKind::Array(x), + .. + } if matches!(x.kind(), ConfigKind::Float) + )); + + let complex_config = HashMap::<String, Vec<HashMap<String, String>>>::as_config(); + println!("Complex config: {:#?}", complex_config); + + assert!( + matches!(complex_config.kind(), ConfigKind::HashMap(map) if matches!(map.kind(), ConfigKind::Array(arr) if matches!(arr.kind(), ConfigKind::HashMap(inner_map) if matches!(inner_map.kind(), ConfigKind::String)))) + ); + } +} diff --git a/crates/core/tedge_api/src/lib.rs b/crates/core/tedge_api/src/lib.rs index c2e793f4..40943224 100644 --- a/crates/core/tedge_api/src/lib.rs +++ b/crates/core/tedge_api/src/lib.rs @@ -10,6 +10,9 @@ pub mod plugin; pub use plugin::{Message, Plugin, PluginBuilder, PluginConfiguration, PluginDirectory, PluginExt}; +/// Generic representation of a configuration +pub mod config; + /// Addresses allow plugins to exchange messages pub mod address; pub use address::Address; diff --git a/crates/core/tedge_api/src/message.rs b/crates/core/tedge_api/src/message.rs index a91d3955..3e4a6249 100644 --- a/crates/core/tedge_api/src/message.rs +++ b/crates/core/tedge_api/src/message.rs @@ -1,3 +1,5 @@ +use serde::Serialize; + use crate::{address::AnyMessageBox, plugin::Message}; /// A message that can contain any other message @@ -42,15 +44,16 @@ impl AnyMessage { impl Message for AnyMessage {} /// The type of a message as used by `tedge_api` to represent a type -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct MessageType { name: &'static str, kind: MessageKind, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] enum MessageKind { Wildcard, + #[serde(skip)] Typed(std::any::TypeId), } diff --git a/crates/core/tedge_api/src/plugin.rs b/crates/core/tedge_api/src/plugin.rs index 1f5fdbf2..0f7ce152 100644 --- a/crates/core/tedge_api/src/plugin.rs +++ b/crates/core/tedge_api/src/plugin.rs @@ -13,6 +13,7 @@ use async_trait::async_trait; use crate::{ address::{InternalMessage, ReceiverBundle, ReplySenderFor}, + config::ConfigDescription, error::{DirectoryError, PluginError}, message::{CoreMessages, MessageType}, Address, @@ -163,6 +164,18 @@ pub trait PluginBuilder<PD: PluginDirectory>: Sync + Send + 'static { where Self: Sized; + /// Get a generic configuration description of what kind of input the + /// plugin expects. + /// + /// See [`Config`] as well as [`AsConfig`] for how to implement and use these types and + /// interfaces. + fn kind_configuration() -> Option<ConfigDescription> + where + Self: Sized, + { + None + } + /// Verify the configuration of the plugin for this plugin kind /// /// This function will be used by the core implementation to verify that a given plugin |