mirror of https://github.com/zino-rs/zino
Refactor application state
This commit is contained in:
parent
2e03cd0e39
commit
5bdd6081be
|
@ -1,6 +1,7 @@
|
|||
# zino
|
||||
|
||||
`zino` is a full featured web application framework which focuses on productivity and performance.
|
||||
`zino` is a full featured web application framework for Rust which focuses on
|
||||
productivity and performance.
|
||||
|
||||
[![Crates.io](https://img.shields.io/crates/v/zino)][zino]
|
||||
[![Documentation](https://shields.io/docsrs/zino)][zino-docs]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde_json::json;
|
||||
use zino::Request;
|
||||
use zino_core::{Model, Query, Rejection, RequestContext, Response, Schema, Uuid};
|
||||
use zino::{AxumCluster, Request};
|
||||
use zino_core::{Application, Model, Query, Rejection, RequestContext, Response, Schema, Uuid};
|
||||
use zino_model::User;
|
||||
|
||||
pub(crate) async fn new(mut req: Request) -> zino::Result {
|
||||
|
@ -63,7 +63,8 @@ pub(crate) async fn view(mut req: Request) -> zino::Result {
|
|||
|
||||
let data = json!({
|
||||
"user": user,
|
||||
"state": state_data,
|
||||
"app_state_data": AxumCluster::state_data(),
|
||||
"state_data": state_data,
|
||||
"config": req.config(),
|
||||
});
|
||||
res.set_data(data);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
name = "data-cube"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
|
||||
[main]
|
||||
host = "127.0.0.1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
name = "data-cube"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
|
||||
[main]
|
||||
host = "127.0.0.1"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{AsyncCronJob, CronJob, Job, JobScheduler, State};
|
||||
use std::{collections::HashMap, sync::LazyLock, thread, time::Instant};
|
||||
use crate::{AsyncCronJob, CronJob, Job, JobScheduler, Map, State};
|
||||
use std::{collections::HashMap, thread};
|
||||
use toml::value::Table;
|
||||
|
||||
/// Application.
|
||||
|
@ -10,8 +10,8 @@ pub trait Application {
|
|||
/// Creates a new application.
|
||||
fn new() -> Self;
|
||||
|
||||
/// Returns the start time.
|
||||
fn start_time(&self) -> Instant;
|
||||
/// Returns a reference to the shared application state.
|
||||
fn shared() -> &'static State;
|
||||
|
||||
/// Registers routes.
|
||||
fn register(self, routes: HashMap<&'static str, Self::Router>) -> Self;
|
||||
|
@ -22,13 +22,19 @@ pub trait Application {
|
|||
/// Returns the application env.
|
||||
#[inline]
|
||||
fn env() -> &'static str {
|
||||
SHARED_STATE.env()
|
||||
Self::shared().env()
|
||||
}
|
||||
|
||||
/// Returns a reference to the application scoped config.
|
||||
/// Returns a reference to the shared application config.
|
||||
#[inline]
|
||||
fn config() -> &'static Table {
|
||||
SHARED_STATE.config()
|
||||
Self::shared().config()
|
||||
}
|
||||
|
||||
/// Returns a reference to the shared application state data.
|
||||
#[inline]
|
||||
fn state_data() -> &'static Map {
|
||||
Self::shared().data()
|
||||
}
|
||||
|
||||
/// Spawns a new thread to run cron jobs.
|
||||
|
@ -47,6 +53,3 @@ pub trait Application {
|
|||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared application state.
|
||||
static SHARED_STATE: LazyLock<State> = LazyLock::new(State::default);
|
||||
|
|
|
@ -4,7 +4,7 @@ use serde::Serialize;
|
|||
use serde_json::Value;
|
||||
use sqlx::{postgres::PgRow, Column as _, Error, Row, TypeInfo};
|
||||
|
||||
/// A column is a model field with associated metadata.
|
||||
/// A model field with associated metadata.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Column<'a> {
|
||||
/// Column name.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{crypto, State};
|
||||
use crate::{crypto, state::SHARED_STATE};
|
||||
use sqlx::{
|
||||
postgres::{PgConnectOptions, PgPoolOptions},
|
||||
PgPool,
|
||||
|
@ -167,21 +167,31 @@ impl ConnectionPool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Shared state.
|
||||
pub(super) static SHARED_STATE: LazyLock<State> = LazyLock::new(|| {
|
||||
let mut state = State::default();
|
||||
/// A list of database connection pools.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ConnectionPools(Vec<ConnectionPool>);
|
||||
|
||||
impl ConnectionPools {
|
||||
/// Returns a connection pool with the specific name.
|
||||
#[inline]
|
||||
pub(crate) fn get_pool(&self, name: &str) -> Option<&ConnectionPool> {
|
||||
self.0.iter().find(|c| c.name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared connection pools.
|
||||
pub(super) static SHARED_CONNECTION_POOLS: LazyLock<ConnectionPools> = LazyLock::new(|| {
|
||||
let config = SHARED_STATE.config();
|
||||
|
||||
// Application name.
|
||||
let application_name = state
|
||||
.config()
|
||||
let application_name = config
|
||||
.get("name")
|
||||
.and_then(|t| t.as_str())
|
||||
.expect("the `name` field should be specified");
|
||||
|
||||
// Database connection pools.
|
||||
let mut pools = Vec::new();
|
||||
let databases = state
|
||||
.config()
|
||||
let databases = config
|
||||
.get("postgres")
|
||||
.expect("the `postgres` field should be specified")
|
||||
.as_array()
|
||||
|
@ -195,10 +205,7 @@ pub(super) static SHARED_STATE: LazyLock<State> = LazyLock::new(|| {
|
|||
pools.push(pool);
|
||||
}
|
||||
}
|
||||
if !pools.is_empty() {
|
||||
state.set_pools(pools);
|
||||
}
|
||||
state
|
||||
ConnectionPools(pools)
|
||||
});
|
||||
|
||||
/// Database namespace prefix.
|
||||
|
|
|
@ -57,7 +57,7 @@ pub trait Schema: 'static + Send + Sync + Model {
|
|||
/// Initializes the model reader.
|
||||
#[inline]
|
||||
fn init_reader() -> Result<&'static ConnectionPool, Error> {
|
||||
super::SHARED_STATE
|
||||
super::SHARED_CONNECTION_POOLS
|
||||
.get_pool(Self::READER_NAME)
|
||||
.ok_or(Error::PoolClosed)
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ pub trait Schema: 'static + Send + Sync + Model {
|
|||
/// Initializes the model writer.
|
||||
#[inline]
|
||||
fn init_writer() -> Result<&'static ConnectionPool, Error> {
|
||||
super::SHARED_STATE
|
||||
super::SHARED_CONNECTION_POOLS
|
||||
.get_pool(Self::WRITER_NAME)
|
||||
.ok_or(Error::PoolClosed)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use chrono::{
|
|||
Local, SecondsFormat, TimeZone, Utc,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
fmt,
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
|
@ -64,15 +65,16 @@ impl DateTime {
|
|||
/// Returns an RFC 2822 date and time string.
|
||||
#[inline]
|
||||
pub fn to_utc_string(&self) -> String {
|
||||
let datetime = self.0.with_timezone(&Utc).to_rfc2822();
|
||||
format!("{} GMT", datetime.trim_end_matches(" +0000"))
|
||||
let datetime = self.0.with_timezone(&Utc);
|
||||
format!("{} GMT", datetime.to_rfc2822().trim_end_matches(" +0000"))
|
||||
}
|
||||
|
||||
/// Return an RFC 3339 and ISO 8601 date and time string with subseconds
|
||||
/// formatted as [`SecondsFormat::Millis`](chrono::SecondsFormat::Millis).
|
||||
#[inline]
|
||||
pub fn to_iso_string(&self) -> String {
|
||||
self.0.to_rfc3339_opts(SecondsFormat::Millis, true)
|
||||
let datetime = self.0.with_timezone(&Utc);
|
||||
datetime.to_rfc3339_opts(SecondsFormat::Millis, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,15 +85,21 @@ impl Default for DateTime {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<chrono::DateTime<Local>> for DateTime {
|
||||
fn from(dt: chrono::DateTime<Local>) -> Self {
|
||||
Self(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateTime> for chrono::DateTime<Local> {
|
||||
fn from(dt: DateTime) -> Self {
|
||||
dt.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chrono::DateTime<Local>> for DateTime {
|
||||
fn from(dt: chrono::DateTime<Local>) -> Self {
|
||||
Self(dt)
|
||||
impl From<DateTime> for Value {
|
||||
fn from(dt: DateTime) -> Self {
|
||||
Value::String(dt.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ pub use validation::Validation;
|
|||
|
||||
/// Request context.
|
||||
pub trait RequestContext {
|
||||
/// Returns a reference to the application scoped config.
|
||||
/// Returns a reference to the application config.
|
||||
fn config(&self) -> &Table;
|
||||
|
||||
/// Returns a reference to the request scoped state data.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{ConnectionPool, Map};
|
||||
use std::{env, fs, path::Path};
|
||||
use crate::Map;
|
||||
use std::{env, fs, path::Path, sync::LazyLock};
|
||||
use toml::value::{Table, Value};
|
||||
|
||||
/// Application state.
|
||||
|
@ -9,8 +9,6 @@ pub struct State {
|
|||
env: String,
|
||||
/// Configuration.
|
||||
config: Table,
|
||||
/// Connection pools.
|
||||
pools: Vec<ConnectionPool>,
|
||||
/// Associated data.
|
||||
data: Map,
|
||||
}
|
||||
|
@ -22,7 +20,6 @@ impl State {
|
|||
Self {
|
||||
env,
|
||||
config: Table::new(),
|
||||
pools: Vec::new(),
|
||||
data: Map::new(),
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +43,12 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the state data.
|
||||
#[inline]
|
||||
pub fn set_data(&mut self, data: Map) {
|
||||
self.data = data;
|
||||
}
|
||||
|
||||
/// Returns the env as `&str`.
|
||||
#[inline]
|
||||
pub fn env(&self) -> &str {
|
||||
|
@ -73,7 +76,7 @@ impl State {
|
|||
/// Returns a list of listeners as `Vec<String>`.
|
||||
pub fn listeners(&self) -> Vec<String> {
|
||||
let config = self.config();
|
||||
let mut listeners = vec![];
|
||||
let mut listeners = Vec::new();
|
||||
|
||||
// Main server.
|
||||
let main = config
|
||||
|
@ -122,32 +125,24 @@ impl State {
|
|||
|
||||
listeners
|
||||
}
|
||||
|
||||
/// Returns a connection pool with the specific name.
|
||||
#[inline]
|
||||
pub(crate) fn get_pool(&self, name: &str) -> Option<&ConnectionPool> {
|
||||
self.pools.iter().find(|c| c.name() == name)
|
||||
}
|
||||
|
||||
/// Sets the connection pools.
|
||||
#[inline]
|
||||
pub(crate) fn set_pools(&mut self, pools: Vec<ConnectionPool>) {
|
||||
self.pools = pools;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
let mut app_env = "dev".to_string();
|
||||
for arg in env::args() {
|
||||
if arg.starts_with("--env=") {
|
||||
app_env = arg.strip_prefix("--env=").unwrap().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = State::new(app_env);
|
||||
state.load_config();
|
||||
state
|
||||
SHARED_STATE.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared application state.
|
||||
pub(crate) static SHARED_STATE: LazyLock<State> = LazyLock::new(|| {
|
||||
let mut app_env = "dev".to_string();
|
||||
for arg in env::args() {
|
||||
if arg.starts_with("--env=") {
|
||||
app_env = arg.strip_prefix("--env=").unwrap().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = State::new(app_env);
|
||||
state.load_config();
|
||||
state
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "zino"
|
||||
description = "A full featured web application framework."
|
||||
description = "Full featured web application framework for Rust."
|
||||
version = "0.3.0"
|
||||
rust-version = "1.68"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# zino
|
||||
|
||||
`zino` is a full featured web application framework which focuses on productivity and performance.
|
||||
`zino` is a full featured web application framework for Rust which focuses on
|
||||
productivity and performance.
|
||||
|
||||
## Highlights
|
||||
|
||||
|
|
|
@ -117,8 +117,9 @@ impl Default for MessageChannel {
|
|||
}
|
||||
|
||||
/// Channel capacity.
|
||||
static CHANNEL_CAPACITY: LazyLock<usize> =
|
||||
LazyLock::new(|| match crate::AxumCluster::config().get("channel") {
|
||||
static CHANNEL_CAPACITY: LazyLock<usize> = LazyLock::new(|| {
|
||||
let config = crate::AxumCluster::config();
|
||||
match config.get("channel") {
|
||||
Some(config) => config
|
||||
.as_table()
|
||||
.expect("the `channel` field should be a table")
|
||||
|
@ -129,7 +130,8 @@ static CHANNEL_CAPACITY: LazyLock<usize> =
|
|||
.try_into()
|
||||
.expect("the `channel.capacity` field should be a positive integer"),
|
||||
None => 10000,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/// Channel subscribers.
|
||||
static CHANNEL_SUBSCRIBERS: LazyLock<RwLock<HashMap<Uuid, Subscriber>>> =
|
||||
|
|
|
@ -12,8 +12,8 @@ use std::{
|
|||
env, io,
|
||||
net::SocketAddr,
|
||||
path::Path,
|
||||
sync::{Arc, LazyLock},
|
||||
time::{Duration, Instant},
|
||||
sync::LazyLock,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::runtime::Builder;
|
||||
use tower::{
|
||||
|
@ -27,12 +27,10 @@ use tower_http::{
|
|||
};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::fmt::{time, writer::MakeWriterExt};
|
||||
use zino_core::{Application, AsyncCronJob, Job, JobScheduler, Response, State};
|
||||
use zino_core::{Application, AsyncCronJob, DateTime, Job, JobScheduler, Map, Response, State};
|
||||
|
||||
/// An HTTP server cluster for `axum`.
|
||||
pub struct AxumCluster {
|
||||
/// Start time.
|
||||
start_time: Instant,
|
||||
/// Routes.
|
||||
routes: HashMap<&'static str, Router>,
|
||||
}
|
||||
|
@ -98,15 +96,14 @@ impl Application for AxumCluster {
|
|||
.init();
|
||||
|
||||
Self {
|
||||
start_time: Instant::now(),
|
||||
routes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the start time.
|
||||
/// Returns a reference to the shared application state.
|
||||
#[inline]
|
||||
fn start_time(&self) -> Instant {
|
||||
self.start_time
|
||||
fn shared() -> &'static State {
|
||||
LazyLock::force(&SHARED_CLUSTER_STATE)
|
||||
}
|
||||
|
||||
/// Registers routes.
|
||||
|
@ -171,7 +168,7 @@ impl Application for AxumCluster {
|
|||
app = app.nest(path, route.clone());
|
||||
}
|
||||
|
||||
let state = Arc::new(app_state.clone());
|
||||
let state = app_state.clone();
|
||||
app = app
|
||||
.fallback_service(tower::service_fn(|_| async {
|
||||
let res = Response::new(StatusCode::NOT_FOUND);
|
||||
|
@ -220,3 +217,24 @@ impl Application for AxumCluster {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared cluster state.
|
||||
static SHARED_CLUSTER_STATE: LazyLock<State> = LazyLock::new(|| {
|
||||
let mut state = State::default();
|
||||
let config = state.config();
|
||||
let app_name = config
|
||||
.get("name")
|
||||
.and_then(|t| t.as_str())
|
||||
.expect("the `name` field should be specified");
|
||||
let app_version = config
|
||||
.get("version")
|
||||
.and_then(|t| t.as_str())
|
||||
.expect("the `version` field should be specified");
|
||||
|
||||
let mut data = Map::new();
|
||||
data.insert("app_name".to_string(), app_name.into());
|
||||
data.insert("app_version".to_string(), app_version.into());
|
||||
data.insert("cluster_start_at".to_string(), DateTime::now().into());
|
||||
state.set_data(data);
|
||||
state
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! [`zino`] is a full featured web application framework which focuses on productivity and performance.
|
||||
//! [`zino`] is a full featured web application framework for Rust
|
||||
//! which focuses on productivity and performance.
|
||||
//!
|
||||
//! ## Highlights
|
||||
//!
|
||||
|
|
|
@ -4,10 +4,8 @@ use zino_core::Application;
|
|||
|
||||
// CORS middleware.
|
||||
pub(crate) static CORS_MIDDLEWARE: LazyLock<CorsLayer> = LazyLock::new(|| {
|
||||
match crate::AxumCluster::config()
|
||||
.get("cors")
|
||||
.and_then(|t| t.as_table())
|
||||
{
|
||||
let config = crate::AxumCluster::config();
|
||||
match config.get("cors").and_then(|t| t.as_table()) {
|
||||
Some(cors) => {
|
||||
let allow_credentials = cors
|
||||
.get("allow-credentials")
|
||||
|
|
|
@ -10,7 +10,6 @@ use serde::de::DeserializeOwned;
|
|||
use std::{
|
||||
convert::Infallible,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
use toml::value::Table;
|
||||
use zino_core::{CloudEvent, Context, Map, Rejection, RequestContext, State, Validation};
|
||||
|
@ -37,18 +36,18 @@ impl<T> DerefMut for AxumExtractor<T> {
|
|||
impl RequestContext for AxumExtractor<Request<Body>> {
|
||||
#[inline]
|
||||
fn config(&self) -> &Table {
|
||||
let state = self.extensions().get::<Arc<State>>().unwrap();
|
||||
let state = self.extensions().get::<State>().unwrap();
|
||||
state.config()
|
||||
}
|
||||
|
||||
fn state_data(&self) -> &Map {
|
||||
let state = self.extensions().get::<Arc<State>>().unwrap();
|
||||
let state = self.extensions().get::<State>().unwrap();
|
||||
state.data()
|
||||
}
|
||||
|
||||
fn state_data_mut(&mut self) -> &mut Map {
|
||||
let state = self.extensions_mut().get_mut::<Arc<State>>().unwrap();
|
||||
Arc::make_mut(state).data_mut()
|
||||
let state = self.extensions_mut().get_mut::<State>().unwrap();
|
||||
state.data_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
Loading…
Reference in New Issue