1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Switch to camino's Utf8Path* instead of std::path::Path*

This commit is contained in:
Quentin Gliech
2022-11-18 18:50:11 +01:00
parent c76a1dd2e7
commit a86798d2b3
21 changed files with 99 additions and 74 deletions

5
Cargo.lock generated
View File

@ -2500,6 +2500,7 @@ dependencies = [
"argon2", "argon2",
"atty", "atty",
"axum 0.6.0-rc.4", "axum 0.6.0-rc.4",
"camino",
"clap", "clap",
"dotenv", "dotenv",
"futures-util", "futures-util",
@ -2549,6 +2550,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"camino",
"chrono", "chrono",
"figment", "figment",
"indoc", "indoc",
@ -2719,6 +2721,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"camino",
"convert_case", "convert_case",
"csv", "csv",
"futures-util", "futures-util",
@ -2862,6 +2865,7 @@ name = "mas-static-files"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum 0.6.0-rc.4", "axum 0.6.0-rc.4",
"camino",
"headers", "headers",
"http", "http",
"http-body", "http-body",
@ -2915,6 +2919,7 @@ name = "mas-templates"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"camino",
"chrono", "chrono",
"mas-data-model", "mas-data-model",
"mas-router", "mas-router",

View File

@ -9,4 +9,6 @@ disallowed-methods = [
disallowed-types = [ disallowed-types = [
"rand::OsRng", "rand::OsRng",
{ path = "std::path::PathBuf", reason = "use camino::Utf8PathBuf instead" },
{ path = "std::path::Path", reason = "use camino::Utf8Path instead" },
] ]

View File

@ -10,6 +10,7 @@ anyhow = "1.0.66"
argon2 = { version = "0.4.1", features = ["password-hash"] } argon2 = { version = "0.4.1", features = ["password-hash"] }
atty = "0.2.14" atty = "0.2.14"
axum = "0.6.0-rc.4" axum = "0.6.0-rc.4"
camino = "1.1.1"
clap = { version = "4.0.26", features = ["derive"] } clap = { version = "4.0.26", features = ["derive"] }
dotenv = "0.15.0" dotenv = "0.15.0"
futures-util = "0.3.25" futures-util = "0.3.25"

View File

@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::path::PathBuf;
use anyhow::Context; use anyhow::Context;
use camino::Utf8PathBuf;
use clap::Parser; use clap::Parser;
use mas_config::ConfigurationSection; use mas_config::ConfigurationSection;
@ -50,7 +49,7 @@ enum Subcommand {
pub struct Options { pub struct Options {
/// Path to the configuration file /// Path to the configuration file
#[arg(short, long, global = true, action = clap::ArgAction::Append)] #[arg(short, long, global = true, action = clap::ArgAction::Append)]
config: Vec<PathBuf>, config: Vec<Utf8PathBuf>,
#[command(subcommand)] #[command(subcommand)]
subcommand: Option<Subcommand>, subcommand: Option<Subcommand>,
@ -78,7 +77,7 @@ impl Options {
.unwrap_or_else(|_| "config.yaml".to_owned()) .unwrap_or_else(|_| "config.yaml".to_owned())
// Split the file list on `:` // Split the file list on `:`
.split(':') .split(':')
.map(PathBuf::from) .map(Utf8PathBuf::from)
.collect() .collect()
} else { } else {
self.config.clone() self.config.clone()

View File

@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::path::PathBuf; use camino::Utf8PathBuf;
use clap::Parser; use clap::Parser;
use mas_storage::Clock; use mas_storage::Clock;
use mas_templates::Templates; use mas_templates::Templates;
@ -29,7 +28,7 @@ enum Subcommand {
/// Save the builtin templates to a folder /// Save the builtin templates to a folder
Save { Save {
/// Where the templates should be saved /// Where the templates should be saved
path: PathBuf, path: Utf8PathBuf,
/// Overwrite existing template files /// Overwrite existing template files
#[arg(long)] #[arg(long)]

View File

@ -17,8 +17,6 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
use std::path::PathBuf;
use anyhow::Context; use anyhow::Context;
use clap::Parser; use clap::Parser;
use mas_config::TelemetryConfig; use mas_config::TelemetryConfig;
@ -44,7 +42,7 @@ async fn main() -> anyhow::Result<()> {
async fn try_main() -> anyhow::Result<()> { async fn try_main() -> anyhow::Result<()> {
// Load environment variables from .env files // Load environment variables from .env files
// We keep the path to log it afterwards // We keep the path to log it afterwards
let dotenv_path: Result<Option<PathBuf>, _> = dotenv::dotenv() let dotenv_path: Result<Option<_>, _> = dotenv::dotenv()
.map(Some) .map(Some)
// Display the error if it is something other than the .env file not existing // Display the error if it is something other than the .env file not existing
.or_else(|e| if e.not_found() { Ok(None) } else { Err(e) }); .or_else(|e| if e.not_found() { Ok(None) } else { Err(e) });

View File

@ -60,7 +60,7 @@ where
router.merge(mas_handlers::graphql_router::<AppState, B>(*playground)) router.merge(mas_handlers::graphql_router::<AppState, B>(*playground))
} }
mas_config::HttpResource::Static { web_root } => { mas_config::HttpResource::Static { web_root } => {
let handler = mas_static_files::service(web_root); let handler = mas_static_files::service(web_root.as_deref());
router.nest_service(mas_router::StaticAsset::route(), handler) router.nest_service(mas_router::StaticAsset::route(), handler)
} }
mas_config::HttpResource::OAuth => { mas_config::HttpResource::OAuth => {
@ -91,11 +91,8 @@ where
"root": app_base, "root": app_base,
}); });
let index_service = ViteManifestService::new( let index_service =
manifest.clone().try_into().unwrap(), ViteManifestService::new(manifest.clone(), assets_base.into(), config);
assets_base.into(),
config,
);
let static_service = ServeDir::new(assets).append_index_html_on_directories(false); let static_service = ServeDir::new(assets).append_index_html_on_directories(false);

View File

@ -13,11 +13,12 @@ async-trait = "0.1.58"
thiserror = "1.0.37" thiserror = "1.0.37"
anyhow = "1.0.66" anyhow = "1.0.66"
schemars = { version = "0.8.11", features = ["url", "chrono"] } camino = { version = "1.1.1", features = ["serde1"] }
figment = { version = "0.10.8", features = ["env", "yaml", "test"] }
chrono = { version = "0.4.23", features = ["serde"] } chrono = { version = "0.4.23", features = ["serde"] }
url = { version = "2.3.1", features = ["serde"] } figment = { version = "0.10.8", features = ["env", "yaml", "test"] }
schemars = { version = "0.8.11", features = ["url", "chrono"] }
ulid = { version = "1.0.0", features = ["serde"] } ulid = { version = "1.0.0", features = ["serde"] }
url = { version = "2.3.1", features = ["serde"] }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
serde_with = { version = "2.1.0", features = ["hex", "chrono"] } serde_with = { version = "2.1.0", features = ["hex", "chrono"] }

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{num::NonZeroU32, path::PathBuf, time::Duration}; use std::{num::NonZeroU32, time::Duration};
use anyhow::Context; use anyhow::Context;
use async_trait::async_trait; use async_trait::async_trait;
use camino::Utf8PathBuf;
use rand::Rng; use rand::Rng;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -85,7 +86,8 @@ enum ConnectConfig {
/// Directory containing the UNIX socket to connect to /// Directory containing the UNIX socket to connect to
#[serde(default)] #[serde(default)]
socket: Option<PathBuf>, #[schemars(with = "Option<String>")]
socket: Option<Utf8PathBuf>,
/// PostgreSQL user name to connect as /// PostgreSQL user name to connect as
#[serde(default)] #[serde(default)]

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{borrow::Cow, io::Cursor, ops::Deref, path::PathBuf}; use std::{borrow::Cow, io::Cursor, ops::Deref};
use anyhow::bail; use anyhow::bail;
use async_trait::async_trait; use async_trait::async_trait;
use camino::Utf8PathBuf;
use mas_keystore::PrivateKey; use mas_keystore::PrivateKey;
use rand::Rng; use rand::Rng;
use schemars::JsonSchema; use schemars::JsonSchema;
@ -99,7 +100,8 @@ pub enum BindConfig {
/// Listen on a UNIX domain socket /// Listen on a UNIX domain socket
Unix { Unix {
/// Path to the socket /// Path to the socket
socket: PathBuf, #[schemars(with = "String")]
socket: Utf8PathBuf,
}, },
/// Accept connections on file descriptors passed by the parent process. /// Accept connections on file descriptors passed by the parent process.
@ -125,14 +127,16 @@ pub enum BindConfig {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum KeyOrFile { pub enum KeyOrFile {
Key(String), Key(String),
KeyFile(PathBuf), #[schemars(with = "String")]
KeyFile(Utf8PathBuf),
} }
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] #[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum CertificateOrFile { pub enum CertificateOrFile {
Certificate(String), Certificate(String),
CertificateFile(PathBuf), #[schemars(with = "String")]
CertificateFile(Utf8PathBuf),
} }
/// Configuration related to TLS on a listener /// Configuration related to TLS on a listener
@ -252,7 +256,8 @@ pub enum Resource {
/// Path from which to serve static files. If not specified, it will /// Path from which to serve static files. If not specified, it will
/// serve the static files embedded in the server binary /// serve the static files embedded in the server binary
#[serde(default)] #[serde(default)]
web_root: Option<PathBuf>, #[schemars(with = "Option<String>")]
web_root: Option<Utf8PathBuf>,
}, },
/// Mount a "/connection-info" handler which helps debugging informations on /// Mount a "/connection-info" handler which helps debugging informations on
@ -263,10 +268,12 @@ pub enum Resource {
/// Mount the single page app /// Mount the single page app
Spa { Spa {
/// Path to the vite manifest /// Path to the vite manifest
manifest: PathBuf, #[schemars(with = "String")]
manifest: Utf8PathBuf,
/// Path to the assets to server /// Path to the assets to server
assets: PathBuf, #[schemars(with = "String")]
assets: Utf8PathBuf,
}, },
} }

View File

@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use camino::Utf8PathBuf;
use rand::Rng; use rand::Rng;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -40,7 +39,8 @@ fn default_authorization_grant_endpoint() -> String {
pub struct PolicyConfig { pub struct PolicyConfig {
/// Path to the WASM module /// Path to the WASM module
#[serde(default)] #[serde(default)]
pub wasm_module: Option<PathBuf>, #[schemars(with = "Option<String>")]
pub wasm_module: Option<Utf8PathBuf>,
/// Entrypoint to use when evaluating client registrations /// Entrypoint to use when evaluating client registrations
#[serde(default = "default_client_registration_endpoint")] #[serde(default = "default_client_registration_endpoint")]

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{borrow::Cow, path::PathBuf}; use std::borrow::Cow;
use anyhow::Context; use anyhow::Context;
use async_trait::async_trait; use async_trait::async_trait;
use camino::Utf8PathBuf;
use mas_jose::jwk::{JsonWebKey, JsonWebKeySet}; use mas_jose::jwk::{JsonWebKey, JsonWebKeySet};
use mas_keystore::{Encrypter, Keystore, PrivateKey}; use mas_keystore::{Encrypter, Keystore, PrivateKey};
use rand::{ use rand::{
@ -38,14 +39,16 @@ fn example_secret() -> &'static str {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum KeyOrFile { pub enum KeyOrFile {
Key(String), Key(String),
KeyFile(PathBuf), #[schemars(with = "String")]
KeyFile(Utf8PathBuf),
} }
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] #[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum PasswordOrFile { pub enum PasswordOrFile {
Password(String), Password(String),
PasswordFile(PathBuf), #[schemars(with = "String")]
PasswordFile(Utf8PathBuf),
} }
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)] #[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
use async_trait::async_trait; use async_trait::async_trait;
use camino::Utf8PathBuf;
use rand::Rng; use rand::Rng;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -28,7 +29,8 @@ fn default_builtin() -> bool {
pub struct TemplatesConfig { pub struct TemplatesConfig {
/// Path to the folder that holds the custom templates /// Path to the folder that holds the custom templates
#[serde(default)] #[serde(default)]
pub path: Option<String>, #[schemars(with = "Option<String>")]
pub path: Option<Utf8PathBuf>,
/// Load the templates embedded in the binary /// Load the templates embedded in the binary
#[serde(default = "default_builtin")] #[serde(default = "default_builtin")]

View File

@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::path::Path;
use anyhow::Context; use anyhow::Context;
use async_trait::async_trait; use async_trait::async_trait;
use camino::Utf8Path;
use figment::{ use figment::{
error::Error as FigmentError, error::Error as FigmentError,
providers::{Env, Format, Serialized, Yaml}, providers::{Env, Format, Serialized, Yaml},
@ -65,20 +64,20 @@ pub trait ConfigurationSection<'a>: Sized + Deserialize<'a> + Serialize {
/// Load configuration from a list of files and environment variables. /// Load configuration from a list of files and environment variables.
fn load_from_files<P>(paths: &[P]) -> Result<Self, FigmentError> fn load_from_files<P>(paths: &[P]) -> Result<Self, FigmentError>
where where
P: AsRef<Path>, P: AsRef<Utf8Path>,
{ {
let base = Figment::new().merge(Env::prefixed("MAS_").split("_")); let base = Figment::new().merge(Env::prefixed("MAS_").split("_"));
paths paths
.iter() .iter()
.fold(base, |f, path| f.merge(Yaml::file(path))) .fold(base, |f, path| f.merge(Yaml::file(path.as_ref())))
.extract_inner(Self::path()) .extract_inner(Self::path())
} }
/// Load configuration from a file and environment variables. /// Load configuration from a file and environment variables.
fn load_from_file<P>(path: P) -> Result<Self, FigmentError> fn load_from_file<P>(path: P) -> Result<Self, FigmentError>
where where
P: AsRef<Path>, P: AsRef<Utf8Path>,
{ {
Self::load_from_files(&[path]) Self::load_from_files(&[path])
} }

View File

@ -8,6 +8,7 @@ license = "Apache-2.0"
[dependencies] [dependencies]
anyhow = "1.0.66" anyhow = "1.0.66"
async-trait = "0.1.58" async-trait = "0.1.58"
camino = "1.1.1"
convert_case = "0.6.0" convert_case = "0.6.0"
csv = "1.1.6" csv = "1.1.6"
futures-util = "0.3.25" futures-util = "0.3.25"

View File

@ -16,8 +16,9 @@
#![deny(clippy::all, clippy::str_to_string, rustdoc::broken_intra_doc_links)] #![deny(clippy::all, clippy::str_to_string, rustdoc::broken_intra_doc_links)]
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc}; use std::{collections::HashMap, fmt::Display, sync::Arc};
use camino::{Utf8Path, Utf8PathBuf};
use reqwest::Client; use reqwest::Client;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tracing::Level; use tracing::Level;
@ -35,8 +36,8 @@ struct File {
items: HashMap<&'static str, Vec<EnumMember>>, items: HashMap<&'static str, Vec<EnumMember>>,
} }
fn resolve_path(relative: PathBuf) -> PathBuf { fn resolve_path(relative: impl AsRef<Utf8Path>) -> Utf8PathBuf {
let crate_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let crate_root = Utf8Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace_root = crate_root.parent().unwrap().parent().unwrap(); let workspace_root = crate_root.parent().unwrap().parent().unwrap();
workspace_root.join(relative) workspace_root.join(relative)
} }
@ -65,12 +66,12 @@ impl File {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn write(&self, path: PathBuf) -> anyhow::Result<()> { async fn write(&self, path: impl AsRef<Utf8Path>) -> anyhow::Result<()> {
let mut file = tokio::fs::OpenOptions::new() let mut file = tokio::fs::OpenOptions::new()
.create(true) .create(true)
.truncate(true) .truncate(true)
.write(true) .write(true)
.open(path) .open(path.as_ref())
.await?; .await?;
tracing::info!("Writing file"); tracing::info!("Writing file");
@ -180,8 +181,11 @@ pub enum {} {{"#,
use self::traits::{EnumEntry, EnumMember, Section}; use self::traits::{EnumEntry, EnumMember, Section};
#[tracing::instrument(skip(client))] #[tracing::instrument(skip_all, fields(%path))]
async fn generate_jose(client: &Arc<Client>, path: PathBuf) -> anyhow::Result<()> { async fn generate_jose(
client: &Arc<Client>,
path: impl AsRef<Utf8Path> + std::fmt::Display,
) -> anyhow::Result<()> {
let path = resolve_path(path); let path = resolve_path(path);
let client = client.clone(); let client = client.clone();
@ -208,8 +212,11 @@ async fn generate_jose(client: &Arc<Client>, path: PathBuf) -> anyhow::Result<()
Ok(()) Ok(())
} }
#[tracing::instrument(skip(client))] #[tracing::instrument(skip_all, fields(%path))]
async fn generate_oauth(client: &Arc<Client>, path: PathBuf) -> anyhow::Result<()> { async fn generate_oauth(
client: &Arc<Client>,
path: impl AsRef<Utf8Path> + std::fmt::Display,
) -> anyhow::Result<()> {
let path = resolve_path(path); let path = resolve_path(path);
let client = client.clone(); let client = client.clone();
@ -244,7 +251,7 @@ async fn main() -> anyhow::Result<()> {
let client = Client::builder().user_agent("iana-parser/0.0.1").build()?; let client = Client::builder().user_agent("iana-parser/0.0.1").build()?;
let client = Arc::new(client); let client = Arc::new(client);
let iana_crate_root = PathBuf::from("crates/iana/"); let iana_crate_root = Utf8Path::new("crates/iana/");
generate_jose(&client, iana_crate_root.join("src/jose.rs")).await?; generate_jose(&client, iana_crate_root.join("src/jose.rs")).await?;
generate_oauth(&client, iana_crate_root.join("src/oauth.rs")).await?; generate_oauth(&client, iana_crate_root.join("src/oauth.rs")).await?;

View File

@ -10,6 +10,7 @@ dev = []
[dependencies] [dependencies]
axum = { version = "0.6.0-rc.4", features = ["headers"] } axum = { version = "0.6.0-rc.4", features = ["headers"] }
camino = "1.1.1"
headers = "0.3.8" headers = "0.3.8"
http = "0.2.8" http = "0.2.8"
http-body = "0.4.5" http-body = "0.4.5"

View File

@ -25,6 +25,9 @@
#[cfg(not(feature = "dev"))] #[cfg(not(feature = "dev"))]
mod builtin { mod builtin {
// the RustEmbed derive uses std::path::Path
#![allow(clippy::disallowed_types)]
use std::{ use std::{
fmt::Write, fmt::Write,
future::{ready, Ready}, future::{ready, Ready},
@ -127,25 +130,25 @@ mod builtin {
#[cfg(feature = "dev")] #[cfg(feature = "dev")]
mod builtin { mod builtin {
use std::path::PathBuf; use camnio::Utf8Path;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
/// Serve static files in dev mode /// Serve static files in dev mode
#[must_use] #[must_use]
pub fn service() -> ServeDir { pub fn service() -> ServeDir {
let path = PathBuf::from(format!("{}/public", env!("CARGO_MANIFEST_DIR"))); let path = Utf8Path::new(format!("{}/public", env!("CARGO_MANIFEST_DIR")));
ServeDir::new(path).append_index_html_on_directories(false) ServeDir::new(path).append_index_html_on_directories(false)
} }
} }
use std::{convert::Infallible, future::ready, path::PathBuf}; use std::{convert::Infallible, future::ready};
use axum::{ use axum::{
body::HttpBody, body::HttpBody,
response::Response, response::Response,
routing::{on_service, MethodFilter}, routing::{on_service, MethodFilter},
}; };
use camino::Utf8Path;
use http::{Request, StatusCode}; use http::{Request, StatusCode};
use tower::{util::BoxCloneService, ServiceExt}; use tower::{util::BoxCloneService, ServiceExt};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@ -153,7 +156,7 @@ use tower_http::services::ServeDir;
/// Serve static files /// Serve static files
#[must_use] #[must_use]
pub fn service<B: HttpBody + Send + 'static>( pub fn service<B: HttpBody + Send + 'static>(
path: &Option<PathBuf>, path: Option<&Utf8Path>,
) -> BoxCloneService<Request<B>, Response, Infallible> { ) -> BoxCloneService<Request<B>, Response, Infallible> {
let svc = if let Some(path) = path { let svc = if let Some(path) = path {
let handler = ServeDir::new(path).append_index_html_on_directories(false); let handler = ServeDir::new(path).append_index_html_on_directories(false);

View File

@ -20,6 +20,7 @@ serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.88" serde_json = "1.0.88"
serde_urlencoded = "0.7.1" serde_urlencoded = "0.7.1"
camino = "1.1.1"
chrono = "0.4.23" chrono = "0.4.23"
url = "2.3.1" url = "2.3.1"
ulid = { version = "1.0.0", features = ["serde"] } ulid = { version = "1.0.0", features = ["serde"] }

View File

@ -227,8 +227,8 @@ mod tests {
); );
let form = TestForm { let form = TestForm {
foo: "".to_owned(), foo: String::new(),
bar: "".to_owned(), bar: String::new(),
}; };
let state = form let state = form
.to_form_state() .to_form_state()

View File

@ -24,15 +24,10 @@
//! Templates rendering //! Templates rendering
use std::{ use std::{collections::HashSet, io::Cursor, string::ToString, sync::Arc};
collections::HashSet,
io::Cursor,
path::{Path, PathBuf},
string::ToString,
sync::Arc,
};
use anyhow::{bail, Context as _}; use anyhow::{bail, Context as _};
use camino::{Utf8Path, Utf8PathBuf};
use mas_data_model::StorageBackend; use mas_data_model::StorageBackend;
use mas_router::UrlBuilder; use mas_router::UrlBuilder;
use serde::Serialize; use serde::Serialize;
@ -64,7 +59,7 @@ pub use self::{
pub struct Templates { pub struct Templates {
tera: Arc<RwLock<Tera>>, tera: Arc<RwLock<Tera>>,
url_builder: UrlBuilder, url_builder: UrlBuilder,
path: Option<String>, path: Option<Utf8PathBuf>,
builtin: bool, builtin: bool,
} }
@ -91,7 +86,7 @@ pub enum TemplateLoadingError {
impl Templates { impl Templates {
/// List directories to watch /// List directories to watch
pub async fn watch_roots(&self) -> Vec<PathBuf> { pub async fn watch_roots(&self) -> Vec<Utf8PathBuf> {
Self::roots(self.path.as_deref(), self.builtin) Self::roots(self.path.as_deref(), self.builtin)
.await .await
.into_iter() .into_iter()
@ -99,18 +94,21 @@ impl Templates {
.collect() .collect()
} }
async fn roots(path: Option<&str>, builtin: bool) -> Vec<Result<PathBuf, std::io::Error>> { async fn roots(
path: Option<&Utf8Path>,
builtin: bool,
) -> Vec<Result<Utf8PathBuf, std::io::Error>> {
let mut paths = Vec::new(); let mut paths = Vec::new();
if builtin && cfg!(feature = "dev") { if builtin && cfg!(feature = "dev") {
paths.push( paths.push(
Path::new(env!("CARGO_MANIFEST_DIR")) Utf8Path::new(env!("CARGO_MANIFEST_DIR"))
.join("src") .join("src")
.join("res"), .join("res"),
); );
} }
if let Some(path) = path { if let Some(path) = path {
paths.push(PathBuf::from(path)); paths.push(Utf8PathBuf::from(path));
} }
let mut ret = Vec::new(); let mut ret = Vec::new();
@ -137,7 +135,7 @@ impl Templates {
/// Load the templates from the given config /// Load the templates from the given config
pub async fn load( pub async fn load(
path: Option<String>, path: Option<Utf8PathBuf>,
builtin: bool, builtin: bool,
url_builder: UrlBuilder, url_builder: UrlBuilder,
) -> Result<Self, TemplateLoadingError> { ) -> Result<Self, TemplateLoadingError> {
@ -151,7 +149,7 @@ impl Templates {
} }
async fn load_( async fn load_(
path: Option<&str>, path: Option<&Utf8Path>,
builtin: bool, builtin: bool,
url_builder: UrlBuilder, url_builder: UrlBuilder,
) -> Result<Tera, TemplateLoadingError> { ) -> Result<Tera, TemplateLoadingError> {
@ -169,8 +167,7 @@ impl Templates {
// This uses blocking I/Os, do that in a blocking task // This uses blocking I/Os, do that in a blocking task
let tera = tokio::task::spawn_blocking(move || { let tera = tokio::task::spawn_blocking(move || {
// Using `to_string_lossy` here is probably fine let path = format!("{}/**/*.{{html,txt,subject}}", root);
let path = format!("{}/**/*.{{html,txt,subject}}", root.to_string_lossy());
info!(%path, "Loading templates from filesystem"); info!(%path, "Loading templates from filesystem");
Tera::parse(&path) Tera::parse(&path)
}) })
@ -223,7 +220,7 @@ impl Templates {
} }
/// Save the builtin templates to a folder /// Save the builtin templates to a folder
pub async fn save(path: &Path, overwrite: bool) -> anyhow::Result<()> { pub async fn save(path: &Utf8Path, overwrite: bool) -> anyhow::Result<()> {
if cfg!(feature = "dev") { if cfg!(feature = "dev") {
bail!("Builtin templates are not included in dev binaries") bail!("Builtin templates are not included in dev binaries")
} }