diff --git a/crates/core/src/constants.rs b/crates/core/src/constants.rs index c57784c..85edcea 100644 --- a/crates/core/src/constants.rs +++ b/crates/core/src/constants.rs @@ -19,4 +19,17 @@ // ---------------------------------------------------------------- pub const SIGMA_VERSION: &str = "0.1.0"; -pub const SIGMA_CORE_PROFILE_ACTIVES: &str = "default"; +pub const SIGMA_CORE_PROFILE_ACTIVES_DEFAULT: &str = "default"; + +// ---------------------------------------------------------------- + +// omiga.toml | omiga-dev.toml ... +pub const SIGMA_CORE_CONFIG_FILE_NAME_DEFAULT: &str = "omiga"; +// toml* | yml/yaml | json | properties | ini | ... +pub const SIGMA_CORE_CONFIG_FILE_SUFFIX_DEFAULT: &str = "toml"; +pub const SIGMA_CORE_CONFIG_FILE_SEARCH_PATHS_DEFAULT: &str = ".,configs,resources"; + +// ---------------------------------------------------------------- + +/// 9320: A dream moment for Manchester City's forward `Agüero`. +pub const SIGMA_WEB_SERVER_PORT_DEFAULT: u32 = 9320; diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs new file mode 100644 index 0000000..d4160fc --- /dev/null +++ b/crates/core/src/error.rs @@ -0,0 +1,54 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// error + +// ---------------------------------------------------------------- + +use std::error::Error; +use std::fmt; + +#[allow(dead_code)] // tmp +#[derive(Debug, PartialEq)] +pub enum OmigaError { + Runtime(String), + IO(String), + Database(String), + Business(String), + Unknown(String), +} + +impl fmt::Display for OmigaError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + OmigaError::Runtime(message) => { + write!(f, "Omiga: runtime error, message:[{}]", message) + } + OmigaError::IO(message) => write!(f, "Omiga: I/O error, message:[{}]", message), + OmigaError::Database(message) => { + write!(f, "Omiga: database error, message:[{}]", message) + } + OmigaError::Business(message) => { + write!(f, "Omiga: business error, message:[{}]", message) + } + OmigaError::Unknown(message) => { + write!(f, "Omiga: unknown error, message:[{}]", message) + } + } + } +} + +impl Error for OmigaError {} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 90a6f26..dbd9fa3 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -19,3 +19,4 @@ // ---------------------------------------------------------------- pub mod constants; +mod error; diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index 01f0df1..65cc7c6 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] +chrono = "0.4" diff --git a/crates/env/README.md b/crates/env/README.md index 9ed65b9..43affb9 100644 --- a/crates/env/README.md +++ b/crates/env/README.md @@ -1 +1,6 @@ -# `env` \ No newline at end of file +# `env` + +The core functionality is inspired by [configer](https://github.com/photowey/configer), and is mainly a migration to +adapt to the `omiga` project. + +> diff --git a/crates/env/src/core.rs b/crates/env/src/core.rs new file mode 100644 index 0000000..014a5ba --- /dev/null +++ b/crates/env/src/core.rs @@ -0,0 +1,22 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// core + +// ------------------------------------------------------------ + +pub mod domain; +pub mod error; diff --git a/crates/env/src/core/domain.rs b/crates/env/src/core/domain.rs new file mode 100644 index 0000000..88a4041 --- /dev/null +++ b/crates/env/src/core/domain.rs @@ -0,0 +1,323 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// core/domain + +// ---------------------------------------------------------------- + +use std::collections::HashMap; +use std::mem; + +use chrono::NaiveDateTime; + +// ---------------------------------------------------------------- + +pub type Table = HashMap; + +pub type Array = Vec; + +// ---------------------------------------------------------------- + +#[derive(Debug, PartialEq, Clone)] +pub enum Value { + Nested(Table), + Array(Array), + DateTime(NaiveDateTime), + String(String), + Boolean(bool), + IntU128(u128), + IntU64(u64), + IntU32(u32), + Int128(i128), + Int64(i64), + Int32(i32), + Float64(f64), + Float32(f32), + None, +} + +// ---------------------------------------------------------------- + +impl Value { + pub fn as_nested_mut(&mut self) -> Option<&mut Table> { + match self { + Value::Nested(ref mut nested) => Some(nested), + _ => None, + } + } +} + +// ---------------------------------------------------------------- + +impl Default for Value { + fn default() -> Self { + Self::None + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: Table) -> Self { + Value::Nested(value) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: Array) -> Self { + Value::Array(value) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: NaiveDateTime) -> Self { + Value::DateTime(value) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: String) -> Self { + Value::String(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Value::String(value.to_string()) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: bool) -> Self { + Value::Boolean(value) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: i32) -> Self { + Value::Int32(value) + } +} + +impl From for Value { + fn from(value: i64) -> Self { + Value::Int64(value) + } +} + +impl From for Value { + fn from(value: i128) -> Self { + Value::Int128(value) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: u32) -> Self { + Value::IntU32(value) + } +} + +impl From for Value { + fn from(value: u64) -> Self { + Value::IntU64(value) + } +} + +impl From for Value { + fn from(value: u128) -> Self { + Value::IntU128(value) + } +} + +// ---------------------------------------------------------------- + +impl From for Value { + fn from(value: f64) -> Self { + Value::Float64(value) + } +} + +impl From for Value { + fn from(value: f32) -> Self { + Value::Float32(value) + } +} + +// ---------------------------------------------------------------- + +impl<'a> From<&'a Value> for Option<&'a Table> { + fn from(value: &'a Value) -> Option<&'a Table> { + match *value { + Value::Nested(ref table) => Some(table), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a Array> { + fn from(value: &'a Value) -> Option<&'a Array> { + match *value { + Value::Array(ref array) => Some(array), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a NaiveDateTime> { + fn from(value: &'a Value) -> Option<&'a NaiveDateTime> { + match *value { + Value::DateTime(ref time) => Some(time), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a String> { + fn from(value: &'a Value) -> Option<&'a String> { + match *value { + Value::String(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a bool> { + fn from(value: &'a Value) -> Option<&'a bool> { + match *value { + Value::Boolean(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a i128> { + fn from(value: &'a Value) -> Option<&'a i128> { + match *value { + Value::Int128(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a u128> { + fn from(value: &'a Value) -> Option<&'a u128> { + match *value { + Value::IntU128(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a i64> { + fn from(value: &'a Value) -> Option<&'a i64> { + match *value { + Value::Int64(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a u64> { + fn from(value: &'a Value) -> Option<&'a u64> { + match *value { + Value::IntU64(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a i32> { + fn from(value: &'a Value) -> Option<&'a i32> { + match *value { + Value::Int32(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a u32> { + fn from(value: &'a Value) -> Option<&'a u32> { + match *value { + Value::IntU32(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a f64> { + fn from(value: &'a Value) -> Option<&'a f64> { + match *value { + Value::Float64(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a f32> { + fn from(value: &'a Value) -> Option<&'a f32> { + match *value { + Value::Float32(ref v) => Some(v), + _ => None, + } + } +} + +impl<'a> From<&'a Value> for Option<&'a ()> { + fn from(value: &'a Value) -> Option<&'a ()> { + match *value { + Value::None => Some(&()), + _ => None, + } + } +} + +// ---------------------------------------------------------------- Merge start + +pub fn merge_tables(mut dst: Table, src: Table) -> Table { + for (key, src_value) in src { + let dst_value = dst.get_mut(&key).map(mem::take); + + match (dst_value, src_value) { + (Some(Value::Nested(mut dst_nested)), Value::Nested(src_nested)) => { + dst_nested = merge_tables(mem::take(&mut dst_nested), src_nested); + dst.insert(key, Value::Nested(dst_nested)); + } + (Some(Value::Array(mut dst_array)), Value::Array(src_array)) => { + dst_array.extend(src_array); + dst.insert(key, Value::Array(dst_array)); + } + (_, other_value) => { + dst.insert(key, other_value); + } + } + } + + dst +} + +// ---------------------------------------------------------------- Merge end diff --git a/crates/env/src/core/error.rs b/crates/env/src/core/error.rs new file mode 100644 index 0000000..f03b4d5 --- /dev/null +++ b/crates/env/src/core/error.rs @@ -0,0 +1,87 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// core/error + +// ---------------------------------------------------------------- + +use std::error::Error; +use std::fmt; + +// ---------------------------------------------------------------- + +#[derive(Debug, PartialEq)] +pub enum ConfigError { + EmptyKey, + NonNested, + NotFound, +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ConfigError::EmptyKey => write!(f, "Omiga: key can't be empty"), + ConfigError::NonNested => { + write!( + f, + "Omiga: attempted to set/get a nested value on a non-nested node" + ) + } + ConfigError::NotFound => write!(f, "Omiga: not found"), + } + } +} + +impl Error for ConfigError {} + +// ---------------------------------------------------------------- + +#[derive(Debug, PartialEq)] +pub enum ReadError { + InvalidPath(String), + InvalidFile(String), + ReaderNotFound(String), + ReadFailed(String), + IncorrectFormat(String), + ParseFailed(String, String), +} + +impl fmt::Display for ReadError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ReadError::InvalidPath(path) => write!(f, "Omiga: invalid path:[{}]", path), + ReadError::InvalidFile(file) => write!(f, "Omiga: invalid config file type:[{}]", file), + ReadError::ReaderNotFound(suffix) => { + write!(f, "Omiga: reader not found, suffix: [{}]", suffix) + } + ReadError::ReadFailed(path) => { + write!(f, "Omiga: failed to read config file, path:[{}]", path) + } + ReadError::IncorrectFormat(format) => write!( + f, + "Omiga: incorrect [{}] format, missing table data.", + format + ), + ReadError::ParseFailed(format, message) => write!( + f, + "Omiga: failed to parse [{}] config file, message: [{}]", + format, message + ), + } + } +} + +impl Error for ReadError {} diff --git a/crates/env/src/environment.rs b/crates/env/src/environment.rs new file mode 100644 index 0000000..12bb29e --- /dev/null +++ b/crates/env/src/environment.rs @@ -0,0 +1,44 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// environment + +// ---------------------------------------------------------------- + +use crate::core::domain::Value; +use crate::core::error::ConfigError; +use crate::reader::ConfigReader; + +// ---------------------------------------------------------------- + +pub trait Environment { + fn set(&mut self, key: &str, value: Value) -> Result<(), ConfigError>; + fn get(&self, key: &str) -> Result<&Value, ConfigError>; + + fn try_acquire(&self, suffix: &str) -> Option<&dyn ConfigReader>; + fn try_acquires(&self) -> Vec<&dyn ConfigReader>; +} + +// ---------------------------------------------------------------- + +pub trait DynamicEnvironment: Environment { + fn set_t(&mut self, k: &str, v: T) -> Result<(), ConfigError> + where + T: Into, + { + self.set(k, v.into()) + } +} diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index eb8350e..94cdcb1 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -17,3 +17,8 @@ // omigaenv // ---------------------------------------------------------------- + +pub mod core; +pub mod environment; +pub mod reader; +pub mod standard; diff --git a/crates/env/src/reader.rs b/crates/env/src/reader.rs new file mode 100644 index 0000000..d71d195 --- /dev/null +++ b/crates/env/src/reader.rs @@ -0,0 +1,44 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// reader + +// ---------------------------------------------------------------- + +use std::fs; +use std::path::PathBuf; + +use crate::core::domain::Table; +use crate::core::error::ReadError; + +// ---------------------------------------------------------------- + +pub trait ConfigReader { + fn name(&self) -> String; + fn suffix(&self) -> String; + fn supports(&self, suffix: &str) -> bool; + + fn read_from_str(&self, data: &str) -> Result; + + fn read_from_path(&self, path: &str) -> Result { + let canon = PathBuf::from(path) + .canonicalize() + .map_err(|_| ReadError::InvalidPath(path.to_string()))?; + let content = + fs::read_to_string(canon).map_err(|_| ReadError::ReadFailed(path.to_string()))?; + self.read_from_str(&content) + } +} diff --git a/crates/env/src/standard.rs b/crates/env/src/standard.rs new file mode 100644 index 0000000..8637d20 --- /dev/null +++ b/crates/env/src/standard.rs @@ -0,0 +1,19 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// standard + +// ---------------------------------------------------------------- diff --git a/crates/tests/src/integration_tests.rs b/crates/tests/src/integration_tests.rs index f3badf8..8a79d0f 100644 --- a/crates/tests/src/integration_tests.rs +++ b/crates/tests/src/integration_tests.rs @@ -18,7 +18,7 @@ // ---------------------------------------------------------------- -use omigacore::constants::SIGMA_CORE_PROFILE_ACTIVES; +use omigacore::constants::SIGMA_CORE_PROFILE_ACTIVES_DEFAULT; use omigaweb::app::{Application, OmigaApplication}; // ---------------------------------------------------------------- @@ -35,7 +35,7 @@ fn test_app_default_profile() { let app = OmigaApplication::builder().build(); let profiles = app.profiles_active(); - assert_eq!(SIGMA_CORE_PROFILE_ACTIVES, profiles); + assert_eq!(SIGMA_CORE_PROFILE_ACTIVES_DEFAULT, profiles); let ok = app.is_default_profile(); assert!(ok); diff --git a/crates/web/src/app.rs b/crates/web/src/app.rs index 58accaa..86a1c45 100644 --- a/crates/web/src/app.rs +++ b/crates/web/src/app.rs @@ -18,9 +18,11 @@ // ---------------------------------------------------------------- -use omigacore::constants::SIGMA_CORE_PROFILE_ACTIVES; +use omigacore::constants::{ + SIGMA_CORE_CONFIG_FILE_SUFFIX_DEFAULT, SIGMA_CORE_PROFILE_ACTIVES_DEFAULT, +}; -// default +use crate::core::kv::Kv; // ---------------------------------------------------------------- @@ -40,6 +42,12 @@ impl OmigaApplication { OmigaApplicationBuilder::default() } + // omiga start --omiga.server.port=9320 + // ^~~~ k/v + pub fn walk(/*tmp*/ _opts: Kv) -> OmigaApplicationBuilder { + panic!("Unsupported now.") + } + // ---------------------------------------------------------------- pub fn new(configs: Vec, profiles: Vec) -> Self { @@ -70,7 +78,7 @@ impl OmigaApplication { pub fn is_default_profile(&self) -> bool { self.profiles - .contains(&SIGMA_CORE_PROFILE_ACTIVES.to_string()) + .contains(&SIGMA_CORE_PROFILE_ACTIVES_DEFAULT.to_string()) } } @@ -85,13 +93,17 @@ impl Application for OmigaApplication { pub struct OmigaApplicationBuilder { configs: Vec, profiles: Vec, + suffix: Option, + search_paths: Vec, } impl OmigaApplicationBuilder { pub fn new() -> Self { Self { configs: Vec::new(), - profiles: vec![SIGMA_CORE_PROFILE_ACTIVES.to_string()], + profiles: vec![SIGMA_CORE_PROFILE_ACTIVES_DEFAULT.to_string()], + suffix: Some(SIGMA_CORE_CONFIG_FILE_SUFFIX_DEFAULT.to_string()), + search_paths: Vec::new(), } } @@ -126,8 +138,22 @@ impl OmigaApplicationBuilder { // ---------------------------------------------------------------- + pub fn suffix(mut self, suffix: String) -> Self { + self.suffix = Some(suffix); + + self + } + + pub fn search_path(mut self, search_paths: Vec) -> Self { + self.search_paths.extend(search_paths); + + self + } + + // ---------------------------------------------------------------- + fn is_not_default_profile(profile: &str) -> bool { - profile != SIGMA_CORE_PROFILE_ACTIVES + profile != SIGMA_CORE_PROFILE_ACTIVES_DEFAULT } // ---------------------------------------------------------------- diff --git a/crates/web/src/core.rs b/crates/web/src/core.rs new file mode 100644 index 0000000..92b15c3 --- /dev/null +++ b/crates/web/src/core.rs @@ -0,0 +1,21 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// core + +// ---------------------------------------------------------------- + +pub mod kv; diff --git a/crates/web/src/core/kv.rs b/crates/web/src/core/kv.rs new file mode 100644 index 0000000..e2634c6 --- /dev/null +++ b/crates/web/src/core/kv.rs @@ -0,0 +1,40 @@ +/* + * Copyright © 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// core/kv + +// ---------------------------------------------------------------- + +use std::collections::HashMap; + +// ---------------------------------------------------------------- + +/// `Omiga` server command k/v args if present. +pub struct Kv { + pub kv: Option>, +} + +impl Kv { + pub fn new() -> Self { + Self { kv: None } + } +} + +impl Default for Kv { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/web/src/lib.rs b/crates/web/src/lib.rs index 44799bd..a36a01c 100644 --- a/crates/web/src/lib.rs +++ b/crates/web/src/lib.rs @@ -19,3 +19,4 @@ // ---------------------------------------------------------------- pub mod app; +pub mod core;