Skip to content

Commit

Permalink
Add the parse_path method in the Application trait
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Sep 10, 2024
1 parent bba35b8 commit 64db7d1
Show file tree
Hide file tree
Showing 15 changed files with 116 additions and 98 deletions.
103 changes: 74 additions & 29 deletions zino-core/src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,15 @@ use crate::{
trace::TraceContext,
LazyLock, Map,
};
use ahash::{HashMap, HashMapExt};
use reqwest::Response;
use serde::de::DeserializeOwned;
use std::{env, fs, path::PathBuf, thread};
use std::{
borrow::Cow,
env, fs,
path::{Path, PathBuf},
thread,
};
use toml::value::Table;

#[cfg(feature = "openapi")]
Expand Down Expand Up @@ -117,14 +123,11 @@ pub trait Application {
crate::view::init::<Self>();

// Initializes the directories to ensure that they are ready for use
if let Some(dirs) = SHARED_APP_STATE.get_config("dirs") {
for dir in dirs.values().filter_map(|v| v.as_str()) {
let path = parse_path(dir);
if !path.exists() {
if let Err(err) = fs::create_dir_all(&path) {
let path = path.display();
tracing::error!("fail to create the directory {path}: {err}");
}
for path in SHARED_DIRS.values() {
if !path.exists() {
if let Err(err) = fs::create_dir_all(&path) {
let path = path.display();
tracing::error!("fail to create the directory {path}: {err}");
}
}
}
Expand Down Expand Up @@ -233,35 +236,57 @@ pub trait Application {
APP_DOMAIN.as_ref()
}

/// Returns the secret key for the application.
/// It should have at least 64 bytes.
///
/// # Note
///
/// This should only be used for internal services. Do not expose it to external users.
#[inline]
fn secret_key() -> &'static [u8] {
SECRET_KEY.get().expect("fail to get the secret key")
}

/// Returns the project directory for the application.
#[inline]
fn project_dir() -> &'static PathBuf {
LazyLock::force(&PROJECT_DIR)
}

/// Returns the secret key for the application.
/// It should have at least 64 bytes.
/// Returns the config directory for the application.
///
/// # Note
///
/// This should only be used for internal services. Do not expose it to external users.
/// The default config directory is `${PROJECT_DIR}/config`.
/// It can also be specified by the environment variable `ZINO_APP_CONFIG_DIR`.
#[inline]
fn secret_key() -> &'static [u8] {
SECRET_KEY.get().expect("fail to get the secret key")
fn config_dir() -> &'static PathBuf {
LazyLock::force(&CONFIG_DIR)
}

/// Returns the shared directory with the specific name,
/// Returns the shared directory with a specific name,
/// which is defined in the `dirs` table.
fn shared_dir(name: &str) -> PathBuf {
let path = if let Some(path) = SHARED_APP_STATE
.get_config("dirs")
.and_then(|t| t.get_str(name))
{
path
} else {
name
};
Self::project_dir().join(path)
///
/// # Examples
///
/// ```toml
/// [dirs]
/// data = "/data/zino" # an absolute path
/// cache = "~/zino/cache" # a path in the home dir
/// assets = "local/assets" # a path in the project dir
/// ```
#[inline]
fn shared_dir(name: &str) -> Cow<'_, PathBuf> {
SHARED_DIRS
.get(name)
.map(|path| Cow::Borrowed(path))
.unwrap_or_else(|| Cow::Owned(Self::parse_path(name)))
}

/// Parses an absolute path, or a path relative to the home dir `~/` or project dir.
#[inline]
fn parse_path(path: &str) -> PathBuf {
join_path(&PROJECT_DIR, path)
}

/// Spawns a new thread to run cron jobs.
Expand Down Expand Up @@ -331,18 +356,18 @@ pub trait Application {
}
}

/// Parses a path relative to the project dir.
pub(crate) fn parse_path(path: &str) -> PathBuf {
/// Joins a path to the specific dir.
pub(crate) fn join_path(dir: &Path, path: &str) -> PathBuf {
if path.starts_with('/') {
path.into()
} else if let Some(path) = path.strip_prefix("~/") {
if let Some(home_dir) = dirs::home_dir() {
home_dir.join(path)
} else {
PROJECT_DIR.join(path)
dir.join(path)
}
} else {
PROJECT_DIR.join(path)
dir.join(path)
}
}

Expand Down Expand Up @@ -392,6 +417,26 @@ pub(crate) static PROJECT_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
})
});

/// The config directory.
pub(crate) static CONFIG_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
env::var("ZINO_APP_CONFIG_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PROJECT_DIR.join("config"))
});

/// Shared directories.
static SHARED_DIRS: LazyLock<HashMap<String, PathBuf>> = LazyLock::new(|| {
let mut dirs = HashMap::new();
if let Some(config) = SHARED_APP_STATE.get_config("dirs") {
for (key, value) in config {
if let Some(path) = value.as_str() {
dirs.insert(key.to_owned(), join_path(&PROJECT_DIR, path));
}
}
}
dirs
});

/// Shared app state.
static SHARED_APP_STATE: LazyLock<State<Map>> = LazyLock::new(|| {
let mut state = State::default();
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/application/tracing_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub(super) fn init<APP: Application + ?Sized>() {
flatten_event = config.get_bool("flatten-event").unwrap_or(false);
}

let log_dir = super::parse_path(log_dir);
let log_dir = APP::parse_path(log_dir);
if !log_dir.exists() {
fs::create_dir(log_dir.as_path()).unwrap_or_else(|err| {
let log_dir = log_dir.display();
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/auth/rego_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl RegoEngine {
/// Shared Rego evaluation engine.
static SHARED_REGO_ENGINE: LazyLock<RegoEngine> = LazyLock::new(|| {
let engine = RegoEngine::new();
let opa_dir = application::PROJECT_DIR.join("./config/opa");
let opa_dir = application::CONFIG_DIR.join("opa");
match fs::read_dir(opa_dir) {
Ok(entries) => {
let files = entries.filter_map(|entry| entry.ok());
Expand Down
8 changes: 4 additions & 4 deletions zino-core/src/connector/arrow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use super::{Connector, DataSource, DataSourceConnector::Arrow};
use crate::{
application::{http_client, PROJECT_DIR},
application::{self, http_client, PROJECT_DIR},
bail,
error::Error,
extension::TomlTableExt,
Expand Down Expand Up @@ -64,7 +64,7 @@ impl ArrowConnector {
pub fn new() -> Self {
Self {
context: OnceLock::new(),
root: PROJECT_DIR.join("local/data/"),
root: PROJECT_DIR.to_owned(),
tables: None,
system_variables: ScalarValueProvider::default(),
user_defined_variables: ScalarValueProvider::default(),
Expand All @@ -73,14 +73,14 @@ impl ArrowConnector {

/// Creates a new instance with the configuration.
pub fn with_config(config: &Table) -> Self {
let root = config.get_str("root").unwrap_or("local/data/");
let root = config.get_str("root").unwrap_or_default();
let mut system_variables = ScalarValueProvider::default();
if let Some(variables) = config.get_table("variables") {
system_variables.read_toml_table(variables);
}
Self {
context: OnceLock::new(),
root: PROJECT_DIR.join(root),
root: application::join_path(&PROJECT_DIR, root),
tables: config.get_array("tables").cloned(),
system_variables,
user_defined_variables: ScalarValueProvider::default(),
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/i18n/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type Translation = FluentBundle<FluentResource, IntlLangMemoizer>;
/// Localization.
static LOCALIZATION: LazyLock<Vec<(LanguageIdentifier, Translation)>> = LazyLock::new(|| {
let mut locales = Vec::new();
let locale_dir = application::PROJECT_DIR.join("./config/locale");
let locale_dir = application::CONFIG_DIR.join("locale");
match fs::read_dir(locale_dir) {
Ok(entries) => {
let files = entries.filter_map(|entry| entry.ok());
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/openapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ pub(crate) fn default_external_docs() -> Option<ExternalDocs> {
/// OpenAPI paths.
static OPENAPI_PATHS: LazyLock<BTreeMap<String, PathItem>> = LazyLock::new(|| {
let mut paths: BTreeMap<String, PathItem> = BTreeMap::new();
let openapi_dir = application::PROJECT_DIR.join("./config/openapi");
let openapi_dir = application::CONFIG_DIR.join("openapi");
match fs::read_dir(openapi_dir) {
Ok(entries) => {
let mut openapi_tags = Vec::new();
Expand Down
3 changes: 2 additions & 1 deletion zino-core/src/orm/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ cfg_if::cfg_if! {
connect_options
}
} else {
use crate::application::{self, PROJECT_DIR};
use sqlx::sqlite::SqliteConnectOptions;

/// Options and flags which can be used to configure a SQLite connection.
Expand All @@ -180,7 +181,7 @@ cfg_if::cfg_if! {
connect_options = connect_options.read_only(read_only);
}

let database_path = crate::application::parse_path(database);
let database_path = application::join_path(&PROJECT_DIR, database);
connect_options.filename(database_path)
}
}
Expand Down
8 changes: 5 additions & 3 deletions zino-core/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ impl<T> State<T> {

/// Loads the config according to the specific env.
///
/// # Note
///
/// It supports the `json` 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,
Expand All @@ -115,10 +117,10 @@ impl<T> State<T> {
let format = std::env::var("ZINO_APP_CONFIG_FORMAT")
.map(|s| s.to_ascii_lowercase())
.unwrap_or_else(|_| "toml".to_owned());
let config_file_dir = application::PROJECT_DIR.join("config");
if config_file_dir.exists() {
let config_dir = &application::CONFIG_DIR;
if config_dir.exists() {
let config_file = format!("config.{env}.{format}");
let config_file_path = config_file_dir.join(&config_file);
let config_file_path = config_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()
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/view/minijinja.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{error::Error, state::State, warn, Map};
use convert_case::{Case, Casing};
use minijinja::Environment;
use std::sync::OnceLock;
use std::{path::PathBuf, sync::OnceLock};

/// Renders a template with the given data using [`minijinja`](https://crates.io/crates/minijinja).
pub fn render(template_name: &str, data: Map) -> Result<String, Error> {
Expand All @@ -13,7 +13,7 @@ pub fn render(template_name: &str, data: Map) -> Result<String, Error> {
}

/// Loads templates.
pub(crate) fn load_templates(app_state: &'static State<Map>, template_dir: String) {
pub(crate) fn load_templates(app_state: &'static State<Map>, template_dir: PathBuf) {
let mut view_engine = Environment::new();
let app_env = app_state.env();
view_engine.set_debug(app_env.is_dev());
Expand Down
12 changes: 1 addition & 11 deletions zino-core/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
//! | `view-tera` | Enables the `tera` template engine. | No |
use crate::{application::Application, extension::TomlTableExt};
use std::path::Path;

cfg_if::cfg_if! {
if #[cfg(feature = "view-tera")] {
Expand All @@ -35,14 +34,5 @@ pub(crate) fn init<APP: Application + ?Sized>() {
template_dir = dir;
}
}

let template_dir = if Path::new(template_dir).exists() {
template_dir.to_owned()
} else {
APP::project_dir()
.join("templates")
.to_string_lossy()
.into()
};
load_templates(app_state, template_dir);
load_templates(app_state, APP::parse_path(template_dir));
}
9 changes: 4 additions & 5 deletions zino-core/src/view/tera.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{error::Error, state::State, warn, Map};
use std::sync::OnceLock;
use std::{path::PathBuf, sync::OnceLock};
use tera::{Context, Tera};

/// Renders a template with the given data using [`tera`](https://crates.io/crates/tera).
Expand All @@ -14,10 +14,9 @@ pub fn render(template_name: &str, data: Map) -> Result<String, Error> {
}

/// Loads templates.
pub(crate) fn load_templates(app_state: &'static State<Map>, template_dir: String) {
let template_dir_glob = template_dir + "/**/*";
let mut view_engine =
Tera::new(template_dir_glob.as_str()).expect("fail to parse html templates");
pub(crate) fn load_templates(app_state: &'static State<Map>, template_dir: PathBuf) {
let dir_glob = template_dir.to_string_lossy().into_owned() + "/**/*";
let mut view_engine = Tera::new(dir_glob.as_str()).expect("fail to parse html templates");
view_engine.autoescape_on(vec![".html", ".html.tera", ".tera"]);
if app_state.env().is_dev() {
view_engine
Expand Down
17 changes: 6 additions & 11 deletions zino/src/application/actix_cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use actix_web::{
web::{self, FormConfig, JsonConfig, PayloadConfig},
App, HttpServer, Responder,
};
use std::{fs, path::PathBuf, time::Duration};
use std::{fs, time::Duration};
use utoipa_rapidoc::RapiDoc;
use zino_core::{
application::{Application, Plugin, ServerTag},
Expand Down Expand Up @@ -86,22 +86,18 @@ impl Application for ActixCluster {
);

// Server config
let project_dir = Self::project_dir();
let default_public_dir = project_dir.join("public");
let mut public_dir = "public";
let mut public_route_prefix = "/public";
let mut public_dir = PathBuf::new();
let mut backlog = 2048; // Maximum number of pending connections
let mut max_connections = 25000; // Maximum number of concurrent connections
let mut body_limit = 128 * 1024 * 1024; // 128MB
let mut request_timeout = Duration::from_secs(60); // 60 seconds
if let Some(config) = app_state.get_config("server") {
if let Some(dir) = config.get_str("page-dir") {
public_dir = dir;
public_route_prefix = "/page";
public_dir.push(dir);
} else if let Some(dir) = config.get_str("public-dir") {
public_dir.push(dir);
} else {
public_dir = default_public_dir;
public_dir = dir;
}
if let Some(route_prefix) = config.get_str("public-route-prefix") {
public_route_prefix = route_prefix;
Expand All @@ -118,10 +114,9 @@ impl Application for ActixCluster {
if let Some(timeout) = config.get_duration("request-timeout") {
request_timeout = timeout;
}
} else {
public_dir = default_public_dir;
}

let public_dir = Self::parse_path(public_dir);
HttpServer::new(move || {
let mut app = App::new().default_service(web::to(|req: Request| async {
let res = Response::new(StatusCode::NOT_FOUND);
Expand Down Expand Up @@ -192,7 +187,7 @@ impl Application for ActixCluster {
RapiDoc::with_openapi("/api-docs/openapi.json", Self::openapi())
};
if let Some(custom_html) = config.get_str("custom-html") {
let custom_html_file = project_dir.join(custom_html);
let custom_html_file = Self::parse_path(custom_html);
if let Ok(html) = fs::read_to_string(custom_html_file) {
rapidoc = rapidoc.custom_html(html);
}
Expand Down
Loading

0 comments on commit 64db7d1

Please sign in to comment.