diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index 5b66731b..f4daee70 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -23,13 +23,20 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> { ); site.tera.register_function( "resize_image", - global_fns::ResizeImage::new(site.base_path.clone(), site.imageproc.clone()), + global_fns::ResizeImage::new( + site.base_path.clone(), + site.imageproc.clone(), + site.config.theme.clone(), + ), ); site.tera.register_function( "get_image_metadata", - global_fns::GetImageMetadata::new(site.base_path.clone()), + global_fns::GetImageMetadata::new(site.base_path.clone(), site.config.theme.clone()), + ); + site.tera.register_function( + "load_data", + global_fns::LoadData::new(site.base_path.clone(), site.config.theme.clone()), ); - site.tera.register_function("load_data", global_fns::LoadData::new(site.base_path.clone())); site.tera.register_function("trans", global_fns::Trans::new(site.config.clone())); site.tera.register_function( "get_taxonomy_url", @@ -39,8 +46,10 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> { site.config.slugify.taxonomies, ), ); - site.tera - .register_function("get_file_hash", global_fns::GetFileHash::new(site.base_path.clone())); + site.tera.register_function( + "get_file_hash", + global_fns::GetFileHash::new(site.base_path.clone(), site.config.theme.clone()), + ); Ok(()) } diff --git a/components/templates/src/global_fns/files.rs b/components/templates/src/global_fns/files.rs index 0fefe229..ba05b121 100644 --- a/components/templates/src/global_fns/files.rs +++ b/components/templates/src/global_fns/files.rs @@ -111,7 +111,7 @@ impl TeraFn for GetUrl { } if cachebust { - match search_for_file(&self.base_path, &path_with_lang) + match search_for_file(&self.base_path, &path_with_lang, &self.config.theme) .map_err(|e| format!("`get_url`: {}", e))? .and_then(|(p, _)| fs::File::open(&p).ok()) .and_then(|f| compute_file_hash::(f, false).ok()) @@ -141,10 +141,11 @@ impl TeraFn for GetUrl { #[derive(Debug)] pub struct GetFileHash { base_path: PathBuf, + theme: Option, } impl GetFileHash { - pub fn new(base_path: PathBuf) -> Self { - Self { base_path } + pub fn new(base_path: PathBuf, theme: Option) -> Self { + Self { base_path, theme } } } @@ -168,7 +169,7 @@ impl TeraFn for GetFileHash { ) .unwrap_or(true); - let file_path = match search_for_file(&self.base_path, &path) + let file_path = match search_for_file(&self.base_path, &path, &self.theme) .map_err(|e| format!("`get_file_hash`: {}", e))? { Some((f, _)) => f, @@ -358,7 +359,7 @@ title = "A title" #[test] fn can_get_file_hash_sha256_no_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(256).unwrap()); @@ -372,7 +373,7 @@ title = "A title" #[test] fn can_get_file_hash_sha256_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(256).unwrap()); @@ -383,7 +384,7 @@ title = "A title" #[test] fn can_get_file_hash_sha384_no_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("base64".to_string(), to_value(false).unwrap()); @@ -396,7 +397,7 @@ title = "A title" #[test] fn can_get_file_hash_sha384() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); assert_eq!( @@ -408,7 +409,7 @@ title = "A title" #[test] fn can_get_file_hash_sha512_no_base64() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(512).unwrap()); @@ -422,7 +423,7 @@ title = "A title" #[test] fn can_get_file_hash_sha512() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("app.css").unwrap()); args.insert("sha_type".to_string(), to_value(512).unwrap()); @@ -435,7 +436,7 @@ title = "A title" #[test] fn error_when_file_not_found_for_hash() { let dir = create_temp_dir(); - let static_fn = GetFileHash::new(dir.into_path()); + let static_fn = GetFileHash::new(dir.into_path(), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("doesnt-exist").unwrap()); let err = format!("{}", static_fn.call(&args).unwrap_err()); diff --git a/components/templates/src/global_fns/helpers.rs b/components/templates/src/global_fns/helpers.rs index f835ebef..d5cf0ae1 100644 --- a/components/templates/src/global_fns/helpers.rs +++ b/components/templates/src/global_fns/helpers.rs @@ -9,12 +9,20 @@ use utils::fs::is_path_in_directory; /// 1. base_path + path /// 2. base_path + static + path /// 3. base_path + content + path +/// 4. base_path + themes + {current_theme} + static + path /// A path starting with @/ will replace it with `content/` and a path starting with `/` will have /// it removed. /// It also returns the unified path so it can be used as unique hash for a given file. /// It will error if the file is not contained in the Zola directory. -pub fn search_for_file(base_path: &Path, path: &str) -> Result> { - let search_paths = [base_path.join("static"), base_path.join("content")]; +pub fn search_for_file( + base_path: &Path, + path: &str, + theme: &Option, +) -> Result> { + let mut search_paths = vec![base_path.join("static"), base_path.join("content")]; + if let Some(t) = theme { + search_paths.push(base_path.join("themes").join(t).join("static")); + } let actual_path = if path.starts_with("@/") { Cow::Owned(path.replace("@/", "content/")) } else { diff --git a/components/templates/src/global_fns/images.rs b/components/templates/src/global_fns/images.rs index 395ffa5f..6c5392b4 100644 --- a/components/templates/src/global_fns/images.rs +++ b/components/templates/src/global_fns/images.rs @@ -10,12 +10,17 @@ use crate::global_fns::helpers::search_for_file; pub struct ResizeImage { /// The base path of the Zola site base_path: PathBuf, + theme: Option, imageproc: Arc>, } impl ResizeImage { - pub fn new(base_path: PathBuf, imageproc: Arc>) -> Self { - Self { base_path, imageproc } + pub fn new( + base_path: PathBuf, + imageproc: Arc>, + theme: Option, + ) -> Self { + Self { base_path, imageproc, theme } } } @@ -55,7 +60,7 @@ impl TeraFn for ResizeImage { } let mut imageproc = self.imageproc.lock().unwrap(); - let (file_path, unified_path) = match search_for_file(&self.base_path, &path) + let (file_path, unified_path) = match search_for_file(&self.base_path, &path, &self.theme) .map_err(|e| format!("`resize_image`: {}", e))? { Some(f) => f, @@ -76,12 +81,13 @@ impl TeraFn for ResizeImage { pub struct GetImageMetadata { /// The base path of the Zola site base_path: PathBuf, + theme: Option, result_cache: Arc>>, } impl GetImageMetadata { - pub fn new(base_path: PathBuf) -> Self { - Self { base_path, result_cache: Arc::new(Mutex::new(HashMap::new())) } + pub fn new(base_path: PathBuf, theme: Option) -> Self { + Self { base_path, result_cache: Arc::new(Mutex::new(HashMap::new())), theme } } } @@ -99,7 +105,7 @@ impl TeraFn for GetImageMetadata { ) .unwrap_or(false); - let (src_path, unified_path) = match search_for_file(&self.base_path, &path) + let (src_path, unified_path) = match search_for_file(&self.base_path, &path, &self.theme) .map_err(|e| format!("`get_image_metadata`: {}", e))? { Some((f, p)) => (f, p), @@ -142,10 +148,16 @@ mod tests { let dir = tempdir().unwrap(); create_dir_all(dir.path().join("content").join("gallery")).unwrap(); create_dir_all(dir.path().join("static")).unwrap(); + create_dir_all(dir.path().join("themes").join("name").join("static")).unwrap(); copy("gutenberg.jpg", dir.path().join("content").join("gutenberg.jpg")).unwrap(); copy("gutenberg.jpg", dir.path().join("content").join("gallery").join("asset.jpg")) .unwrap(); copy("gutenberg.jpg", dir.path().join("static").join("gutenberg.jpg")).unwrap(); + copy( + "gutenberg.jpg", + dir.path().join("themes").join("name").join("static").join("in-theme.jpg"), + ) + .unwrap(); dir } @@ -156,7 +168,11 @@ mod tests { let dir = create_dir_with_image(); let imageproc = imageproc::Processor::new(dir.path().to_path_buf(), &Config::default()); - let static_fn = ResizeImage::new(dir.path().to_path_buf(), Arc::new(Mutex::new(imageproc))); + let static_fn = ResizeImage::new( + dir.path().to_path_buf(), + Arc::new(Mutex::new(imageproc)), + Some("name".to_owned()), + ); let mut args = HashMap::new(); args.insert("height".to_string(), to_value(40).unwrap()); args.insert("width".to_string(), to_value(40).unwrap()); @@ -212,6 +228,17 @@ mod tests { data["url"], to_value("http://a-website.com/processed_images/6296a3c153f701be00.jpg").unwrap() ); + + // 6. Looking up a file in the theme + args.insert("path".to_string(), to_value("in-theme.jpg").unwrap()); + assert_eq!( + data["static_path"], + to_value(&format!("{}", static_path.join("6296a3c153f701be00.jpg").display())).unwrap() + ); + assert_eq!( + data["url"], + to_value("http://a-website.com/processed_images/6296a3c153f701be00.jpg").unwrap() + ); } // TODO: consider https://github.com/getzola/zola/issues/1161 @@ -219,7 +246,7 @@ mod tests { fn can_get_image_metadata() { let dir = create_dir_with_image(); - let static_fn = GetImageMetadata::new(dir.path().to_path_buf()); + let static_fn = GetImageMetadata::new(dir.path().to_path_buf(), None); // Let's test a few scenarii let mut args = HashMap::new(); diff --git a/components/templates/src/global_fns/load_data.rs b/components/templates/src/global_fns/load_data.rs index bb6f238c..d3f17d49 100644 --- a/components/templates/src/global_fns/load_data.rs +++ b/components/templates/src/global_fns/load_data.rs @@ -88,13 +88,14 @@ impl DataSource { path_arg: Option, url_arg: Option, base_path: &Path, + theme: &Option, ) -> Result> { if path_arg.is_some() && url_arg.is_some() { return Err(GET_DATA_ARGUMENT_ERROR_MESSAGE.into()); } if let Some(path) = path_arg { - return match search_for_file(&base_path, &path) + return match search_for_file(&base_path, &path, &theme) .map_err(|e| format!("`load_data`: {}", e))? { Some((f, _)) => Ok(Some(DataSource::Path(f))), @@ -165,11 +166,12 @@ fn get_output_format_from_args( #[derive(Debug)] pub struct LoadData { base_path: PathBuf, + theme: Option, client: Arc>, result_cache: Arc>>, } impl LoadData { - pub fn new(base_path: PathBuf) -> Self { + pub fn new(base_path: PathBuf, theme: Option) -> Self { let client = Arc::new(Mutex::new( Client::builder() .user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))) @@ -177,7 +179,7 @@ impl LoadData { .expect("reqwest client build"), )); let result_cache = Arc::new(Mutex::new(HashMap::new())); - Self { base_path, client, result_cache } + Self { base_path, client, result_cache, theme } } } @@ -221,26 +223,28 @@ impl TeraFn for LoadData { }; // If the file doesn't exist, source is None - let data_source = - match (DataSource::from_args(path_arg.clone(), url_arg, &self.base_path), required) { - // If the file was not required, return a Null value to the template - (Ok(None), false) | (Err(_), false) => { - return Ok(Value::Null); - } - (Err(e), true) => { - return Err(e); - } - // If the file was required, error - (Ok(None), true) => { - // source is None only with path_arg (not URL), so path_arg is safely unwrap - return Err(format!( - "`load_data`: {} doesn't exist", - &self.base_path.join(path_arg.unwrap()).display() - ) - .into()); - } - (Ok(Some(data_source)), _) => data_source, - }; + let data_source = match ( + DataSource::from_args(path_arg.clone(), url_arg, &self.base_path, &self.theme), + required, + ) { + // If the file was not required, return a Null value to the template + (Ok(None), false) | (Err(_), false) => { + return Ok(Value::Null); + } + (Err(e), true) => { + return Err(e); + } + // If the file was required, error + (Ok(None), true) => { + // source is None only with path_arg (not URL), so path_arg is safely unwrap + return Err(format!( + "`load_data`: {} doesn't exist", + &self.base_path.join(path_arg.unwrap()).display() + ) + .into()); + } + (Ok(Some(data_source)), _) => data_source, + }; let file_format = get_output_format_from_args(format_arg, &data_source)?; let cache_key = @@ -474,7 +478,7 @@ mod tests { #[test] fn fails_illegal_method_parameter() { - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value("https://example.com").unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -501,7 +505,7 @@ mod tests { let url = format!("{}{}", mockito::server_url(), "/kr1zdgbm4y"); - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(url).unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -529,7 +533,7 @@ mod tests { let url = format!("{}{}", mockito::server_url(), "/kr1zdgbm4yw"); - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(url).unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -558,7 +562,7 @@ mod tests { let url = format!("{}{}", mockito::server_url(), "/kr1zdgbm4y"); - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(url).unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -574,7 +578,7 @@ mod tests { #[test] fn fails_when_missing_file() { - let static_fn = LoadData::new(PathBuf::from("../utils")); + let static_fn = LoadData::new(PathBuf::from("../utils"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("../../../READMEE.md").unwrap()); let result = static_fn.call(&args); @@ -584,7 +588,7 @@ mod tests { #[test] fn doesnt_fail_when_missing_file_is_not_required() { - let static_fn = LoadData::new(PathBuf::from("../utils")); + let static_fn = LoadData::new(PathBuf::from("../utils"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("../../../READMEE.md").unwrap()); args.insert("required".to_string(), to_value(false).unwrap()); @@ -603,7 +607,7 @@ mod tests { .unwrap(); copy(get_test_file("test.css"), dir.path().join("static").join("test.css")).unwrap(); - let static_fn = LoadData::new(dir.path().to_path_buf()); + let static_fn = LoadData::new(dir.path().to_path_buf(), None); let mut args = HashMap::new(); let val = if cfg!(windows) { ".hello {}\r\n" } else { ".hello {}\n" }; @@ -630,7 +634,7 @@ mod tests { #[test] fn cannot_load_outside_base_dir() { - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("../../README.md").unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -707,7 +711,7 @@ mod tests { .create(); let url = format!("{}{}", mockito::server_url(), "/zpydpkjj67"); - let static_fn = LoadData::new(PathBuf::new()); + let static_fn = LoadData::new(PathBuf::new(), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(&url).unwrap()); args.insert("format".to_string(), to_value("json").unwrap()); @@ -724,7 +728,7 @@ mod tests { .create(); let url = format!("{}{}", mockito::server_url(), "/aazeow0kog"); - let static_fn = LoadData::new(PathBuf::new()); + let static_fn = LoadData::new(PathBuf::new(), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(&url).unwrap()); args.insert("format".to_string(), to_value("json").unwrap()); @@ -745,7 +749,7 @@ mod tests { .create(); let url = format!("{}{}", mockito::server_url(), "/aazeow0kog"); - let static_fn = LoadData::new(PathBuf::new()); + let static_fn = LoadData::new(PathBuf::new(), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(&url).unwrap()); args.insert("format".to_string(), to_value("json").unwrap()); @@ -772,7 +776,7 @@ mod tests { .create(); let url = format!("{}{}", mockito::server_url(), "/chu8aizahBiy"); - let static_fn = LoadData::new(PathBuf::new()); + let static_fn = LoadData::new(PathBuf::new(), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(&url).unwrap()); args.insert("format".to_string(), to_value("json").unwrap()); @@ -782,7 +786,7 @@ mod tests { #[test] fn can_load_toml() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("test.toml").unwrap()); let result = static_fn.call(&args.clone()).unwrap(); @@ -802,7 +806,7 @@ mod tests { #[test] fn unknown_extension_defaults_to_plain() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("test.css").unwrap()); let result = static_fn.call(&args.clone()).unwrap(); @@ -817,7 +821,7 @@ mod tests { #[test] fn can_override_known_extension_with_format() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("test.csv").unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -835,7 +839,7 @@ mod tests { #[test] fn will_use_format_on_unknown_extension() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("test.css").unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -850,7 +854,7 @@ mod tests { #[test] fn can_load_csv() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("test.csv").unwrap()); let result = static_fn.call(&args.clone()).unwrap(); @@ -870,7 +874,7 @@ mod tests { // Test points to bad csv file with uneven row lengths #[test] fn bad_csv_should_result_in_error() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("uneven_rows.csv").unwrap()); let result = static_fn.call(&args.clone()); @@ -890,7 +894,7 @@ mod tests { #[test] fn bad_csv_should_result_in_error_even_when_not_required() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("uneven_rows.csv").unwrap()); args.insert("required".to_string(), to_value(false).unwrap()); @@ -911,7 +915,7 @@ mod tests { #[test] fn can_load_json() { - let static_fn = LoadData::new(PathBuf::from("../utils/test-files")); + let static_fn = LoadData::new(PathBuf::from("../utils/test-files"), None); let mut args = HashMap::new(); args.insert("path".to_string(), to_value("test.json").unwrap()); let result = static_fn.call(&args.clone()).unwrap(); @@ -937,7 +941,7 @@ mod tests { .create(); let url = format!("{}{}", mockito::server_url(), "/kr1zdgbm4y3"); - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(&url).unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); @@ -969,7 +973,7 @@ mod tests { .create(); let url = format!("{}{}", mockito::server_url(), "/kr1zdgbm4y2"); - let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils"))); + let static_fn = LoadData::new(PathBuf::from(PathBuf::from("../utils")), None); let mut args = HashMap::new(); args.insert("url".to_string(), to_value(&url).unwrap()); args.insert("format".to_string(), to_value("plain").unwrap()); diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index e886c5bd..6223b7bf 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -93,10 +93,11 @@ logic applies. 1. The base directory is the Zola root directory, where the `config.toml` is 2. For the given path: if it starts with `@/`, replace that with `content/` instead and trim any leading `/` -3. Search in the following 3 locations in this order, returning the first where the file exists: +3. Search in the following 3 or 4 locations in this order, returning the first where the file exists: a. $base_directory + $path b. $base_directory + "static/" + $path c. $base_directory + "content/" + $path + d. $base_directory + "themes" + $theme + "static/" + $path only if using a theme In practice this means that `@/some/image.jpg`, `/content/some/image.jpg` and `content/some/image.jpg` will point to the same thing. diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 8c06b257..d40fbe7e 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -288,7 +288,7 @@ pub fn serve( // Stop right there if we can't bind to the address let bind_address: SocketAddrV4 = match address.parse() { Ok(a) => a, - Err(_) => return Err(format!("Invalid address: {}.", address).into()) + Err(_) => return Err(format!("Invalid address: {}.", address).into()), }; if (TcpListener::bind(&bind_address)).is_err() { return Err(format!("Cannot start server on address {}.", address).into());