summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcel Müller <m.mueller@ifm.com>2022-05-05 12:13:26 +0200
committerMarcel Müller <m.mueller@ifm.com>2022-05-05 15:07:19 +0200
commit473801d5dd64ea9c2e669afe650ee210cd9edfa2 (patch)
treeba5969294aabe574cd1df9f1fb6356174f0f1c04
parent93460d4807350cc35a88f40f10ea66b6c4e77b55 (diff)
parent60e1bad6bcb9701c5d738845116c8bb67b9883ed (diff)
Merge branch 'feature/add_config_exploration' into feature/add_tedge_api_only
-rw-r--r--Cargo.lock166
-rw-r--r--crates/core/tedge_api/Cargo.toml6
-rw-r--r--crates/core/tedge_api/examples/print_config.rs80
-rw-r--r--crates/core/tedge_api/src/config.rs212
-rw-r--r--crates/core/tedge_api/src/lib.rs3
-rw-r--r--crates/core/tedge_api/src/message.rs7
-rw-r--r--crates/core/tedge_api/src/plugin.rs13
7 files changed, 481 insertions, 6 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ae291b95..b0cec5fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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