use serde::ser::{SerializeSeq, Serializer};
use serde::Serialize;
pub mod config_type {
#[derive(Clone, Debug)]
pub struct Integer;
#[derive(Clone, Debug)]
pub struct DefaultInteger;
#[derive(Clone, Debug)]
pub struct IntegerArray;
#[derive(Clone, Debug)]
pub struct DefaultIntegerArray;
#[derive(Clone, Debug)]
pub struct String;
#[derive(Clone, Debug)]
pub struct DefaultString;
#[derive(Clone, Debug)]
pub struct StringArray;
#[derive(Clone, Debug)]
pub struct DefaultStringArray;
#[derive(Clone, Debug)]
pub struct Boolean;
#[derive(Clone, Debug)]
pub struct DefaultBoolean;
#[derive(Clone, Debug)]
pub struct Flag;
}
pub type IntegerConfigOption<'a> = ConfigOption<'a, config_type::Integer>;
pub type IntegerArrayConfigOption<'a> = ConfigOption<'a, config_type::IntegerArray>;
pub type StringConfigOption<'a> = ConfigOption<'a, config_type::String>;
pub type StringArrayConfigOption<'a> = ConfigOption<'a, config_type::StringArray>;
pub type BooleanConfigOption<'a> = ConfigOption<'a, config_type::Boolean>;
pub type DefaultIntegerConfigOption<'a> = ConfigOption<'a, config_type::DefaultInteger>;
pub type DefaultIntegerArrayConfigOption<'a> = ConfigOption<'a, config_type::DefaultIntegerArray>;
pub type DefaultStringConfigOption<'a> = ConfigOption<'a, config_type::DefaultString>;
pub type DefaultStringArrayConfigOption<'a> = ConfigOption<'a, config_type::DefaultStringArray>;
pub type DefaultBooleanConfigOption<'a> = ConfigOption<'a, config_type::DefaultBoolean>;
pub type FlagConfigOption<'a> = ConfigOption<'a, config_type::Flag>;
pub trait OptionType<'a> {
type OutputValue;
type DefaultValue;
fn convert_default(value: &Self::DefaultValue) -> Option<Value>;
fn from_value(value: &Option<Value>) -> Self::OutputValue;
fn get_value_type() -> ValueType;
}
impl<'a> OptionType<'a> for config_type::DefaultString {
type OutputValue = String;
type DefaultValue = &'a str;
fn convert_default(value: &Self::DefaultValue) -> Option<Value> {
Some(Value::String(value.to_string()))
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::String(s)) => s.to_string(),
_ => panic!("Type mismatch. Expected string but found {:?}", value),
}
}
fn get_value_type() -> ValueType {
ValueType::String
}
}
impl<'a> OptionType<'a> for config_type::DefaultStringArray {
type OutputValue = Vec<String>;
type DefaultValue = &'a str;
fn convert_default(value: &Self::DefaultValue) -> Option<Value> {
Some(Value::String(value.to_string()))
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::StringArray(s)) => s.clone(),
_ => panic!("Type mismatch. Expected string-array but found {:?}", value),
}
}
fn get_value_type() -> ValueType {
ValueType::String
}
}
impl<'a> OptionType<'a> for config_type::DefaultInteger {
type OutputValue = i64;
type DefaultValue = i64;
fn convert_default(value: &Self::DefaultValue) -> Option<Value> {
Some(Value::Integer(*value))
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::Integer(i)) => *i,
_ => panic!("Type mismatch. Expected Integer but found {:?}", value),
}
}
fn get_value_type() -> ValueType {
ValueType::Integer
}
}
impl<'a> OptionType<'a> for config_type::DefaultIntegerArray {
type OutputValue = Vec<i64>;
type DefaultValue = i64;
fn convert_default(value: &Self::DefaultValue) -> Option<Value> {
Some(Value::Integer(*value))
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::IntegerArray(i)) => i.clone(),
_ => panic!(
"Type mismatch. Expected Integer-array but found {:?}",
value
),
}
}
fn get_value_type() -> ValueType {
ValueType::Integer
}
}
impl<'a> OptionType<'a> for config_type::DefaultBoolean {
type OutputValue = bool;
type DefaultValue = bool;
fn convert_default(value: &bool) -> Option<Value> {
Some(Value::Boolean(*value))
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::Boolean(b)) => *b,
_ => panic!("Type mismatch. Expected Boolean but found {:?}", value),
}
}
fn get_value_type() -> ValueType {
ValueType::Boolean
}
}
impl<'a> OptionType<'a> for config_type::Flag {
type OutputValue = bool;
type DefaultValue = ();
fn convert_default(_value: &()) -> Option<Value> {
Some(Value::Boolean(false))
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::Boolean(b)) => *b,
_ => panic!("Type mismatch. Expected Boolean but found {:?}", value),
}
}
fn get_value_type() -> ValueType {
ValueType::Flag
}
}
impl<'a> OptionType<'a> for config_type::String {
type OutputValue = Option<String>;
type DefaultValue = ();
fn convert_default(_value: &()) -> Option<Value> {
None
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::String(s)) => Some(s.to_string()),
None => None,
_ => panic!(
"Type mismatch. Expected Option<string> but found {:?}",
value
),
}
}
fn get_value_type() -> ValueType {
ValueType::String
}
}
impl<'a> OptionType<'a> for config_type::StringArray {
type OutputValue = Option<Vec<String>>;
type DefaultValue = ();
fn convert_default(_value: &()) -> Option<Value> {
None
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::StringArray(s)) => Some(s.clone()),
None => None,
_ => panic!(
"Type mismatch. Expected Option<Vec<String>> but found {:?}",
value
),
}
}
fn get_value_type() -> ValueType {
ValueType::String
}
}
impl<'a> OptionType<'a> for config_type::Integer {
type OutputValue = Option<i64>;
type DefaultValue = ();
fn convert_default(_value: &()) -> Option<Value> {
None
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::Integer(i)) => Some(*i),
None => None,
_ => panic!(
"Type mismatch. Expected Option<Integer> but found {:?}",
value
),
}
}
fn get_value_type() -> ValueType {
ValueType::Integer
}
}
impl<'a> OptionType<'a> for config_type::IntegerArray {
type OutputValue = Option<Vec<i64>>;
type DefaultValue = ();
fn convert_default(_value: &()) -> Option<Value> {
None
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::IntegerArray(i)) => Some(i.clone()),
None => None,
_ => panic!(
"Type mismatch. Expected Option<Vec<Integer>> but found {:?}",
value
),
}
}
fn get_value_type() -> ValueType {
ValueType::Integer
}
}
impl<'a> OptionType<'a> for config_type::Boolean {
type OutputValue = Option<bool>;
type DefaultValue = ();
fn convert_default(_value: &()) -> Option<Value> {
None
}
fn from_value(value: &Option<Value>) -> Self::OutputValue {
match value {
Some(Value::Boolean(b)) => Some(*b),
None => None,
_ => panic!(
"Type mismatch. Expected Option<Boolean> but found {:?}",
value
),
}
}
fn get_value_type() -> ValueType {
ValueType::Boolean
}
}
#[derive(Clone, Debug, Serialize)]
pub enum ValueType {
#[serde(rename = "string")]
String,
#[serde(rename = "int")]
Integer,
#[serde(rename = "bool")]
Boolean,
#[serde(rename = "flag")]
Flag,
}
#[derive(Clone, Debug)]
pub enum Value {
String(String),
Integer(i64),
Boolean(bool),
StringArray(Vec<String>),
IntegerArray(Vec<i64>),
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::String(s) => serializer.serialize_str(s),
Value::Integer(i) => serializer.serialize_i64(*i),
Value::Boolean(b) => serializer.serialize_bool(*b),
Value::StringArray(sa) => {
let mut seq = serializer.serialize_seq(Some(sa.len()))?;
for element in sa {
seq.serialize_element(element)?;
}
seq.end()
}
Value::IntegerArray(sa) => {
let mut seq = serializer.serialize_seq(Some(sa.len()))?;
for element in sa {
seq.serialize_element(element)?;
}
seq.end()
}
}
}
}
impl Value {
pub fn is_string(&self) -> bool {
self.as_str().is_some()
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(&s),
Value::Integer(_) => None,
Value::Boolean(_) => None,
Value::StringArray(_) => None,
Value::IntegerArray(_) => None,
}
}
pub fn is_i64(&self) -> bool {
self.as_i64().is_some()
}
pub fn as_i64(&self) -> Option<i64> {
match *self {
Value::Integer(n) => Some(n),
_ => None,
}
}
pub fn is_boolean(&self) -> bool {
self.as_bool().is_some()
}
pub fn as_bool(&self) -> Option<bool> {
match *self {
Value::Boolean(b) => Some(b),
_ => None,
}
}
pub fn is_str_arr(&self) -> bool {
self.as_str_arr().is_some()
}
pub fn as_str_arr(&self) -> Option<&Vec<String>> {
match self {
Value::StringArray(sa) => Some(sa),
_ => None,
}
}
pub fn is_i64_arr(&self) -> bool {
self.as_i64_arr().is_some()
}
pub fn as_i64_arr(&self) -> Option<&Vec<i64>> {
match self {
Value::IntegerArray(sa) => Some(sa),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct ConfigOption<'a, V: OptionType<'a>> {
pub name: &'a str,
pub default: V::DefaultValue,
pub description: &'a str,
pub deprecated: bool,
pub dynamic: bool,
pub multi: bool,
}
impl<'a, V: OptionType<'a>> ConfigOption<'a, V> {
pub fn build(&self) -> UntypedConfigOption {
UntypedConfigOption {
name: self.name.to_string(),
value_type: V::get_value_type(),
default: <V as OptionType>::convert_default(&self.default),
description: self.description.to_string(),
deprecated: self.deprecated,
dynamic: self.dynamic,
multi: self.multi,
}
}
}
impl<'a> DefaultStringConfigOption<'a> {
pub const fn new_str_with_default(
name: &'a str,
default: &'a str,
description: &'a str,
) -> Self {
Self {
name: name,
default: default,
description: description,
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> StringConfigOption<'a> {
pub const fn new_str_no_default(name: &'a str, description: &'a str) -> Self {
Self {
name,
default: (),
description: description,
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> DefaultStringArrayConfigOption<'a> {
pub const fn new_str_arr_with_default(
name: &'a str,
default: &'a str,
description: &'a str,
) -> Self {
Self {
name,
default,
description,
deprecated: false,
dynamic: false,
multi: true,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> StringArrayConfigOption<'a> {
pub const fn new_str_arr_no_default(name: &'a str, description: &'a str) -> Self {
Self {
name,
default: (),
description,
deprecated: false,
dynamic: false,
multi: true,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> DefaultIntegerConfigOption<'a> {
pub const fn new_i64_with_default(name: &'a str, default: i64, description: &'a str) -> Self {
Self {
name: name,
default: default,
description: description,
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> IntegerConfigOption<'a> {
pub const fn new_i64_no_default(name: &'a str, description: &'a str) -> Self {
Self {
name: name,
default: (),
description: description,
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> DefaultIntegerArrayConfigOption<'a> {
pub const fn new_i64_arr_with_default(
name: &'a str,
default: i64,
description: &'a str,
) -> Self {
Self {
name,
default,
description,
deprecated: false,
dynamic: false,
multi: true,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> IntegerArrayConfigOption<'a> {
pub const fn new_i64_arr_no_default(name: &'a str, description: &'a str) -> Self {
Self {
name,
default: (),
description,
deprecated: false,
dynamic: false,
multi: true,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> BooleanConfigOption<'a> {
pub const fn new_bool_no_default(name: &'a str, description: &'a str) -> Self {
Self {
name,
description,
default: (),
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> DefaultBooleanConfigOption<'a> {
pub const fn new_bool_with_default(name: &'a str, default: bool, description: &'a str) -> Self {
Self {
name,
description,
default: default,
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a> FlagConfigOption<'a> {
pub const fn new_flag(name: &'a str, description: &'a str) -> Self {
Self {
name,
description,
default: (),
deprecated: false,
dynamic: false,
multi: false,
}
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
fn is_false(b: &bool) -> bool {
*b == false
}
#[derive(Clone, Debug, Serialize)]
pub struct UntypedConfigOption {
name: String,
#[serde(rename = "type")]
pub(crate) value_type: ValueType,
#[serde(skip_serializing_if = "Option::is_none")]
default: Option<Value>,
description: String,
#[serde(skip_serializing_if = "is_false")]
deprecated: bool,
dynamic: bool,
multi: bool,
}
impl UntypedConfigOption {
pub fn name(&self) -> &str {
&self.name
}
pub fn default(&self) -> &Option<Value> {
&self.default
}
pub fn dynamic(mut self) -> Self {
self.dynamic = true;
self
}
}
impl<'a, V> ConfigOption<'a, V>
where
V: OptionType<'a>,
{
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> &str {
&self.description
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_option_serialize() {
let tests = vec![
(
ConfigOption::new_str_with_default("name", "default", "description").build(),
json!({
"name": "name",
"description":"description",
"default": "default",
"type": "string",
"dynamic": false,
"multi": false,
}),
),
(
ConfigOption::new_i64_with_default("name", 42, "description").build(),
json!({
"name": "name",
"description":"description",
"default": 42,
"type": "int",
"dynamic": false,
"multi": false,
}),
),
(
{
ConfigOption::new_bool_with_default("name", true, "description")
.build()
.dynamic()
},
json!({
"name": "name",
"description":"description",
"default": true,
"type": "bool",
"dynamic": true,
"multi": false,
}),
),
(
ConfigOption::new_flag("name", "description").build(),
json!({
"name" : "name",
"description": "description",
"type" : "flag",
"default" : false,
"dynamic": false,
"multi": false,
}),
),
(
ConfigOption::new_str_arr_with_default("name", "Default1", "description").build(),
json!({
"name" : "name",
"description": "description",
"type" : "string",
"default" : "Default1",
"dynamic": false,
"multi": true,
}),
),
(
ConfigOption::new_i64_arr_with_default("name", -46, "description").build(),
json!({
"name" : "name",
"description": "description",
"type" : "int",
"default" : -46,
"dynamic": false,
"multi": true,
}),
),
];
for (input, expected) in tests.iter() {
let res = serde_json::to_value(input).unwrap();
assert_eq!(&res, expected);
}
}
#[test]
fn const_config_option() {
const _: FlagConfigOption = ConfigOption::new_flag("flag-option", "A flag option");
const _: DefaultBooleanConfigOption =
ConfigOption::new_bool_with_default("bool-option", false, "A boolean option");
const _: BooleanConfigOption =
ConfigOption::new_bool_no_default("bool-option", "A boolean option");
const _: IntegerConfigOption =
ConfigOption::new_i64_no_default("integer-option", "A flag option");
const _: DefaultIntegerConfigOption =
ConfigOption::new_i64_with_default("integer-option", 12, "A flag option");
const _: StringConfigOption =
ConfigOption::new_str_no_default("integer-option", "A flag option");
const _: DefaultStringConfigOption =
ConfigOption::new_str_with_default("integer-option", "erik", "A flag option");
}
#[test]
fn test_type_serialize() {
assert_eq!(json!(ValueType::Integer), json!("int"));
assert_eq!(json!(ValueType::Flag), json!("flag"));
}
}