Skip to content

Commit

Permalink
Add support for the config format JSON and YAML
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Dec 10, 2023
1 parent b9f28aa commit 85804bc
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 81 deletions.
3 changes: 2 additions & 1 deletion zino-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ reqwest-middleware = "0.2.4"
reqwest-retry = "0.3.0"
reqwest-tracing = "0.4.6"
rmp-serde = "1.1.2"
ryu = "1.0.15"
ryu = "1.0.16"
serde_qs = "0.12.0"
serde_yaml = "0.9.27"
sha2 = "0.10.8"
sysinfo = "0.29.11"
task-local-extensions = "0.1.4"
Expand Down
3 changes: 2 additions & 1 deletion zino-core/src/application/secret_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ pub(super) fn init<APP: Application + ?Sized>() {
crypto::digest(secret.as_bytes())
});

let secret_key = crypto::derive_key("ZINO:APPLICATION", &checksum);
let info = config.get_str("info").unwrap_or("ZINO:APPLICATION");
let secret_key = crypto::derive_key(info, &checksum);
SECRET_KEY
.set(secret_key)
.expect("fail to set the secret key");
Expand Down
3 changes: 2 additions & 1 deletion zino-core/src/auth/access_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,6 @@ static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
});
crypto::digest(secret.as_bytes())
});
crypto::derive_key("ZINO:ACCESS-KEY", &checksum)
let info = config.get_str("info").unwrap_or("ZINO:ACCESS-KEY");
crypto::derive_key(info, &checksum)
});
3 changes: 2 additions & 1 deletion zino-core/src/auth/jwt_claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ static SECRET_KEY: LazyLock<JwtHmacKey> = LazyLock::new(|| {
});
crypto::digest(secret.as_bytes())
});
let secret_key = crypto::derive_key("ZINO:JWT", &checksum);
let info = config.get_str("info").unwrap_or("ZINO:JWT");
let secret_key = crypto::derive_key(info, &checksum);
JwtHmacKey::from_bytes(&secret_key)
});

Expand Down
6 changes: 3 additions & 3 deletions zino-core/src/model/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl<'a> Column<'a> {
self.extra.contains_key(attribute)
}

/// Returns `true` if the user has any of the specific attributes.
/// Returns `true` if the column has any of the specific attributes.
pub fn has_any_attributes(&self, attributes: &[&str]) -> bool {
for attribute in attributes {
if self.has_attribute(attribute) {
Expand All @@ -162,7 +162,7 @@ impl<'a> Column<'a> {
false
}

/// Returns `true` if the user has all of the specific attributes.
/// Returns `true` if the column has all of the specific attributes.
pub fn has_all_attributes(&self, attributes: &[&str]) -> bool {
for attribute in attributes {
if !self.has_attribute(attribute) {
Expand Down Expand Up @@ -549,7 +549,7 @@ impl<'a> Column<'a> {
}
}

/// Generates a mocked Json value for the column
/// Generates a mocked Json value for the column.
pub fn mock_value(&self) -> JsonValue {
if self.reference().is_some() {
return JsonValue::Null;
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/orm/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
use convert_case::{Case, Casing};

/// Extension trait for [`Column`](crate::model::Column).
/// Extension trait for [`Column`].
pub(super) trait ColumnExt {
/// Returns the type annotation.
fn type_annotation(&self) -> &'static str;
Expand Down
3 changes: 2 additions & 1 deletion zino-core/src/orm/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ static SECRET_KEY: LazyLock<[u8; 64]> = LazyLock::new(|| {
});
crypto::digest(secret.as_bytes())
});
crypto::derive_key("ZINO:ORM", &checksum)
let info = config.get_str("info").unwrap_or("ZINO:ORM");
crypto::derive_key(info, &checksum)
});
4 changes: 2 additions & 2 deletions zino-core/src/orm/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,9 @@ where
}
}

impl<K, T> ScalarQuery<K> for T
impl<M, K> ScalarQuery<K> for M
where
M: Schema<PrimaryKey = K>,
K: Default + Display + PartialEq,
T: Schema<PrimaryKey = K>,
{
}
71 changes: 41 additions & 30 deletions zino-core/src/orm/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,9 +709,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {

/// Finds a list of models selected by the query in the table,
/// and decodes it as `Vec<T>`.
async fn find<T: DecodeRow<DatabaseRow, Error = Error>>(
query: &Query,
) -> Result<Vec<T>, Error> {
async fn find<T>(query: &Query) -> Result<Vec<T>, Error>
where
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();
Self::before_query(query).await?;

Expand Down Expand Up @@ -749,9 +750,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {

/// Finds one model selected by the query in the table,
/// and decodes it as an instance of type `T`.
async fn find_one<T: DecodeRow<DatabaseRow, Error = Error>>(
query: &Query,
) -> Result<Option<T>, Error> {
async fn find_one<T>(query: &Query) -> Result<Option<T>, Error>
where
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();
Self::before_query(query).await?;

Expand Down Expand Up @@ -967,11 +969,15 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {

/// Performs a left outer join to another table to filter rows in the joined table,
/// and decodes it as `Vec<T>`.
async fn lookup<M: Schema, T: DecodeRow<DatabaseRow, Error = Error>>(
async fn lookup<M, T, const N: usize>(
query: &Query,
left_columns: &[&str],
right_columns: &[&str],
) -> Result<Vec<T>, Error> {
left_columns: [&str; N],
right_columns: [&str; N],
) -> Result<Vec<T>, Error>
where
M: Schema,
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();
Self::before_query(query).await?;

Expand Down Expand Up @@ -1016,12 +1022,16 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {

/// Performs a left outer join to another table to filter rows in the "joined" table,
/// and parses it as `Vec<T>`.
async fn lookup_as<M: Schema, T: DeserializeOwned>(
async fn lookup_as<M, T, const N: usize>(
query: &Query,
left_columns: &[&str],
right_columns: &[&str],
) -> Result<Vec<T>, Error> {
let mut data = Self::lookup::<M, Map>(query, left_columns, right_columns).await?;
left_columns: [&str; N],
right_columns: [&str; N],
) -> Result<Vec<T>, Error>
where
M: Schema,
T: DeserializeOwned,
{
let mut data = Self::lookup::<M, Map, N>(query, left_columns, right_columns).await?;
let translate_enabled = query.translate_enabled();
for model in data.iter_mut() {
Self::after_decode(model).await?;
Expand Down Expand Up @@ -1070,10 +1080,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {

/// Counts the number of rows selected by the query in the table.
/// The boolean value determines whether it only counts distinct values or not.
async fn count_many<T: DecodeRow<DatabaseRow, Error = Error>>(
query: &Query,
columns: &[(&str, bool)],
) -> Result<T, Error> {
async fn count_many<T>(query: &Query, columns: &[(&str, bool)]) -> Result<T, Error>
where
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();
Self::before_count(query).await?;

Expand Down Expand Up @@ -1136,10 +1146,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
}

/// Executes the query in the table, and decodes it as `Vec<T>`.
async fn query<T: DecodeRow<DatabaseRow, Error = Error>>(
query: &str,
params: Option<&Map>,
) -> Result<Vec<T>, Error> {
async fn query<T>(query: &str, params: Option<&Map>) -> Result<Vec<T>, Error>
where
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();
let (sql, values) = Query::prepare_query(query, params);

Expand Down Expand Up @@ -1174,10 +1184,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
}

/// Executes the query in the table, and decodes it as an instance of type `T`.
async fn query_one<T: DecodeRow<DatabaseRow, Error = Error>>(
query: &str,
params: Option<&Map>,
) -> Result<Option<T>, Error> {
async fn query_one<T>(query: &str, params: Option<&Map>) -> Result<Option<T>, Error>
where
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();
let (sql, values) = Query::prepare_query(query, params);

Expand Down Expand Up @@ -1253,9 +1263,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {

/// Finds a model selected by the primary key in the table,
/// and decodes it as an instance of type `T`.
async fn find_by_id<T: DecodeRow<DatabaseRow, Error = Error>>(
primary_key: &Self::PrimaryKey,
) -> Result<Option<T>, Error> {
async fn find_by_id<T>(primary_key: &Self::PrimaryKey) -> Result<Option<T>, Error>
where
T: DecodeRow<DatabaseRow, Error = Error>,
{
let pool = Self::acquire_reader().await?.pool();

let primary_key_name = Self::PRIMARY_KEY_NAME;
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/orm/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ where
}

#[cfg(feature = "orm-sqlx")]
impl<'c, K, M> Transaction<K, sqlx::Transaction<'c, DatabaseDriver>> for M
impl<'c, M, K> Transaction<K, sqlx::Transaction<'c, DatabaseDriver>> for M
where
K: Default + Display + PartialEq,
M: Schema<PrimaryKey = K>,
K: Default + Display + PartialEq,
{
/// Executes the specific operations inside a transaction.
/// If the operations return an error, the transaction will be rolled back;
Expand Down
35 changes: 35 additions & 0 deletions zino-core/src/state/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use crate::error::Error;
use std::path::Path;
use toml::value::Table;

/// Fetches the config from a URL.
pub(super) fn fetch_config_url(config_url: &str, env: &str) -> Result<Table, Error> {
let res = ureq::get(config_url).query("env", env).call()?;
let config_table = match res.content_type() {
"application/json" => {
let data = res.into_string()?;
serde_json::from_str(&data)?
}
"application/yaml" => {
let data = res.into_string()?;
serde_yaml::from_str(&data)?
}
_ => res.into_string()?.parse()?,
};
tracing::info!(env, "`{config_url}` fetched");
Ok(config_table)
}

/// Reads the config from a local file.
pub(super) fn read_config_file(config_file: &Path, env: &str) -> Result<Table, Error> {
let data = std::fs::read_to_string(config_file)?;
let config_table = match config_file.extension().and_then(|s| s.to_str()) {
Some("json") => serde_json::from_str(&data)?,
Some("yaml" | "yml") => serde_yaml::from_str(&data)?,
_ => data.parse()?,
};
if let Some(file_name) = config_file.file_name().and_then(|s| s.to_str()) {
tracing::info!(env, "`{file_name}` loaded");
}
Ok(config_table)
}
51 changes: 22 additions & 29 deletions zino-core/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::{
};
use toml::value::Table;

mod config;
mod data;
mod env;

Expand Down Expand Up @@ -42,39 +43,31 @@ impl<T> State<T> {
}
}

/// Loads the config file according to the specific env.
/// Loads the config according to the specific env.
///
/// It supports the `json`, `yaml` or `toml` format of configuration source data,
/// which can be specified by the environment variable `ZINO_APP_CONFIG_FORMAT`.
/// By default, it reads the config from a local file. If `ZINO_APP_CONFIG_URL` is set,
/// it will fetch the config from the URL instead.
pub fn load_config(&mut self) {
let env = self.env.as_str();
let config = if let Ok(config_url) = std::env::var("ZINO_APP_CONFIG_URL") {
match ureq::get(&config_url)
.query("env", env)
.call()
.and_then(|res| res.into_string().map_err(|err| err.into()))
{
Ok(toml_str) => {
tracing::info!(env, "`{config_url}` fetched");
toml_str.parse().unwrap_or_default()
}
Err(err) => {
tracing::error!("fail to fetch the config url `{config_url}`: {err}");
Table::new()
}
}
let config_table = if let Ok(config_url) = std::env::var("ZINO_APP_CONFIG_URL") {
config::fetch_config_url(&config_url, env).unwrap_or_else(|err| {
tracing::error!("fail to fetch the config url `{config_url}`: {err}");
Table::new()
})
} else {
let config_file = application::PROJECT_DIR.join(format!("./config/config.{env}.toml"));
match std::fs::read_to_string(&config_file) {
Ok(toml_str) => {
tracing::info!(env, "`config.{env}.toml` loaded");
toml_str.parse().unwrap_or_default()
}
Err(err) => {
let config_file = config_file.display();
tracing::error!("fail to read the config file `{config_file}`: {err}");
Table::new()
}
}
let format = std::env::var("ZINO_APP_CONFIG_FORMAT")
.map(|s| s.to_ascii_lowercase())
.unwrap_or_else(|_| "toml".to_owned());
let config_file = format!("./config/config.{env}.{format}");
let config_file_path = application::PROJECT_DIR.join(&config_file);
config::read_config_file(&config_file_path, env).unwrap_or_else(|err| {
tracing::error!("fail to read the config file `{config_file}`: {err}");
Table::new()
})
};
self.config = config;
self.config = config_table;
}

/// Set the state data.
Expand Down
9 changes: 6 additions & 3 deletions zino-derive/docs/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Derives the [`Model`](zino_core::model::Model) trait.
create a new instance of the column type in `Model::new()`.
The function must be callable as `fn() -> T`.

- **`#[schema(read_only)]`**: The `read_only` annotation is used to indicate that
- **`#[schema(read_only)]`**: The `read_only` annotation indicates that
the column is read-only and can not be modified after creation.
It also can not been seen in the model definition.

- **`#[schema(generated)]`**: The `generated` annotation is used to indicate that
- **`#[schema(generated)]`**: The `generated` annotation indicates that
the column value is generated by the backend and do not need any frontend input.
The column will not been seen in the model definition.

Expand All @@ -19,7 +19,10 @@ Derives the [`Model`](zino_core::model::Model) trait.
**`created_at`** | **`updated_at`** | **`deleted_at`** | **`is_deleted`** | **`is_locked`**
| **`is_archived`** | **`version`** | **`edition`**.

- **`#[schema(auto_initialized)]`**: The `auto_initialized` annotation is used to indicate that
- **`#[schema(auto_initialized)]`**: The `auto_initialized` annotation indicates that
the column has an automatically initialized value. Different to the `generated` column,
an `auto_initialized` value can be modified after creation.
The column will not been seen in the model definition.

- **`#[schema(inherent)]`**: The `inherent` annotation indicates that
the column value is parsed by an associated function in the model's inherent implementation.
Loading

0 comments on commit 85804bc

Please sign in to comment.