From 2bd2174ae10b56616d77594e44763de6ad5cd860 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:52:04 +0200 Subject: [PATCH 01/11] feat(swagger-ui): cache swagger zip --- utoipa-swagger-ui/Cargo.toml | 1 + utoipa-swagger-ui/build.rs | 59 ++++--- utoipa-swagger-ui/src/internal/dirs.rs | 206 +++++++++++++++++++++++++ utoipa-swagger-ui/src/internal/mod.rs | 1 + 4 files changed, 249 insertions(+), 18 deletions(-) create mode 100644 utoipa-swagger-ui/src/internal/dirs.rs create mode 100644 utoipa-swagger-ui/src/internal/mod.rs diff --git a/utoipa-swagger-ui/Cargo.toml b/utoipa-swagger-ui/Cargo.toml index 13ed4908..852b19b1 100644 --- a/utoipa-swagger-ui/Cargo.toml +++ b/utoipa-swagger-ui/Cargo.toml @@ -18,6 +18,7 @@ debug-embed = ["rust-embed/debug-embed"] reqwest = ["dep:reqwest"] url = ["dep:url"] vendored = ["dep:utoipa-swagger-ui-vendored"] +cache = [] [dependencies] rust-embed = { version = "8" } diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index 3f9a5359..b949e1ad 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -9,6 +9,13 @@ use std::{ use regex::Regex; use zip::{result::ZipError, ZipArchive}; +#[path = "src/internal/mod.rs"] +#[cfg(feature = "cache")] +mod internal; + +#[cfg(feature = "cache")] +use internal::dirs::cache_dir; + /// the following env variables control the build process: /// 1. SWAGGER_UI_DOWNLOAD_URL: /// + the url from where to download the swagger-ui zip file if starts with http:// or https:// @@ -24,6 +31,11 @@ const SWAGGER_UI_DOWNLOAD_URL_DEFAULT: &str = const SWAGGER_UI_DOWNLOAD_URL: &str = "SWAGGER_UI_DOWNLOAD_URL"; const SWAGGER_UI_OVERWRITE_FOLDER: &str = "SWAGGER_UI_OVERWRITE_FOLDER"; +// wget && sha256sum | tr '[a-z]' '[A-Z]' +#[cfg(feature = "cache")] +const SWAGGER_UI_FILE_HASH: &str = + "481244D0812097B11FBAEEF79F71D942B171617F9C9F9514E63ACBE13E71CCDC"; + fn main() { let target_dir = env::var("OUT_DIR").unwrap(); println!("OUT_DIR: {target_dir}"); @@ -139,7 +151,7 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { let zip_filename = url.split('/').last().unwrap().to_string(); let zip_path = [target_dir, &zip_filename].iter().collect::(); - if env::var("CARGO_FEATURE_VENDORED").is_ok() { + if cfg!(feature = "vendored") { #[cfg(not(feature = "vendored"))] unreachable!("Cannot get vendored Swagger UI without `vendored` flag"); @@ -173,15 +185,26 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { .expect("failed to open file protocol copied Swagger UI"); SwaggerZip::File(zip) } else if url.starts_with("http://") || url.starts_with("https://") { - println!("start download to : {:?}", zip_path); + let mut cache_dir = cache_dir() + .expect("could not determine cache directory") + .join("swagger-ui") + .join(SWAGGER_UI_FILE_HASH); - // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes - println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); + if fs::create_dir_all(&cache_dir).is_err() { + cache_dir = env::var("OUT_DIR").unwrap().into(); + } + let zip_cache_path = cache_dir.join(&zip_filename); - download_file(url, zip_path.clone()) - .unwrap_or_else(|error| panic!("failed to download Swagger UI: {error}")); - let swagger_ui_zip = - File::open([target_dir, &zip_filename].iter().collect::()).unwrap(); + if zip_cache_path.exists() { + println!("using cached zip path from : {:?}", zip_cache_path); + } else { + println!("start download to : {:?}", zip_cache_path); + + // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes + println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); + download_file(url, &zip_cache_path).expect("failed to download Swagger UI"); + } + let swagger_ui_zip = File::open(&zip_cache_path).unwrap(); let zip = ZipArchive::new(swagger_ui_zip).expect("failed to open downloaded Swagger UI"); SwaggerZip::File(zip) } else { @@ -223,21 +246,20 @@ struct SwaggerUiDist; fs::write(path, contents).unwrap(); } -fn download_file(url: &str, path: PathBuf) -> Result<(), Box> { - let reqwest_feature = env::var("CARGO_FEATURE_REQWEST"); - println!("reqwest feature: {reqwest_feature:?}"); - if reqwest_feature.is_ok() { - #[cfg(feature = "reqwest")] - download_file_reqwest(url, path)?; - Ok(()) - } else { +fn download_file(url: &str, path: &Path) -> Result<(), Box> { + #[cfg(feature = "reqwest")] + { + download_file_reqwest(url, path) + } + #[cfg(not(feature = "reqwest"))] + { println!("trying to download using `curl` system package"); - download_file_curl(url, path.as_path()) + download_file_curl(url, path) } } #[cfg(feature = "reqwest")] -fn download_file_reqwest(url: &str, path: PathBuf) -> Result<(), Box> { +fn download_file_reqwest(url: &str, path: &Path) -> Result<(), Box> { let mut client_builder = reqwest::blocking::Client::builder(); if let Ok(cainfo) = env::var("CARGO_HTTP_CAINFO") { @@ -266,6 +288,7 @@ fn parse_ca_file(path: &str) -> Result> { Ok(cert) } +#[cfg(not(feature = "reqwest"))] fn download_file_curl>(url: &str, target_dir: T) -> Result<(), Box> { // Not using `CARGO_CFG_TARGET_OS` because of the possibility of cross-compilation. // When targeting `x86_64-pc-windows-gnu` on Linux for example, `cfg!()` in the diff --git a/utoipa-swagger-ui/src/internal/dirs.rs b/utoipa-swagger-ui/src/internal/dirs.rs new file mode 100644 index 00000000..db87a6a2 --- /dev/null +++ b/utoipa-swagger-ui/src/internal/dirs.rs @@ -0,0 +1,206 @@ +// based on https://github.com/pykeio/ort/blob/main/ort-sys/src/internal/dirs.rs and https://github.com/dirs-dev/dirs-sys-rs/blob/main/src/lib.rs + +pub const PACKAGE_NAME: &str = "utoipa-swagger-ui"; + +#[cfg(all(target_os = "windows", target_arch = "x86"))] +macro_rules! win32_extern { + ($library:literal $abi:literal $($link_name:literal)? $(#[$doc:meta])? fn $($function:tt)*) => ( + #[link(name = $library, kind = "raw-dylib", modifiers = "+verbatim", import_name_type = "undecorated")] + extern $abi { + $(#[$doc])? + $(#[link_name=$link_name])? + fn $($function)*; + } + ) +} +#[cfg(all(target_os = "windows", not(target_arch = "x86")))] +macro_rules! win32_extern { + ($library:literal $abi:literal $($link_name:literal)? $(#[$doc:meta])? fn $($function:tt)*) => ( + #[link(name = $library, kind = "raw-dylib", modifiers = "+verbatim")] + extern "C" { + $(#[$doc])? + $(#[link_name=$link_name])? + fn $($function)*; + } + ) +} + +#[cfg(target_os = "windows")] +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +mod windows { + use std::{ + ffi::{c_void, OsString}, + os::windows::prelude::OsStringExt, + path::PathBuf, + ptr, slice, + }; + + #[repr(C)] + #[derive(Clone, Copy)] + struct GUID { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], + } + + impl GUID { + pub const fn from_u128(uuid: u128) -> Self { + Self { + data1: (uuid >> 96) as u32, + data2: (uuid >> 80 & 0xffff) as u16, + data3: (uuid >> 64 & 0xffff) as u16, + #[allow(clippy::cast_possible_truncation)] + data4: (uuid as u64).to_be_bytes(), + } + } + } + + type HRESULT = i32; + type PWSTR = *mut u16; + type PCWSTR = *const u16; + type HANDLE = isize; + type KNOWN_FOLDER_FLAG = i32; + + win32_extern!("SHELL32.DLL" "system" fn SHGetKnownFolderPath(rfid: *const GUID, dwflags: KNOWN_FOLDER_FLAG, htoken: HANDLE, ppszpath: *mut PWSTR) -> HRESULT); + win32_extern!("KERNEL32.DLL" "system" fn lstrlenW(lpstring: PCWSTR) -> i32); + win32_extern!("OLE32.DLL" "system" fn CoTaskMemFree(pv: *const ::core::ffi::c_void) -> ()); + + fn known_folder(folder_id: GUID) -> Option { + unsafe { + let mut path_ptr: PWSTR = ptr::null_mut(); + let result = SHGetKnownFolderPath(&folder_id, 0, HANDLE::default(), &mut path_ptr); + if result == 0 { + let len = lstrlenW(path_ptr) as usize; + let path = slice::from_raw_parts(path_ptr, len); + let ostr: OsString = OsStringExt::from_wide(path); + CoTaskMemFree(path_ptr as *const c_void); + Some(PathBuf::from(ostr)) + } else { + CoTaskMemFree(path_ptr as *const c_void); + None + } + } + } + + #[allow(clippy::unusual_byte_groupings)] + const FOLDERID_LOCAL_APP_DATA: GUID = GUID::from_u128(0xf1b32785_6fba_4fcf_9d557b8e7f157091); + + #[must_use] + pub fn known_folder_local_app_data() -> Option { + known_folder(FOLDERID_LOCAL_APP_DATA) + } +} +#[cfg(target_os = "windows")] +#[must_use] +pub fn cache_dir() -> Option { + self::windows::known_folder_local_app_data().map(|h| h.join(PACKAGE_NAME)) +} + +#[cfg(unix)] +#[allow(non_camel_case_types)] +mod unix { + use std::{ + env, + ffi::{c_char, c_int, c_long, CStr, OsString}, + mem, + os::unix::prelude::OsStringExt, + path::PathBuf, + ptr, + }; + + type uid_t = u32; + type gid_t = u32; + type size_t = usize; + #[repr(C)] + struct passwd { + pub pw_name: *mut c_char, + pub pw_passwd: *mut c_char, + pub pw_uid: uid_t, + pub pw_gid: gid_t, + pub pw_gecos: *mut c_char, + pub pw_dir: *mut c_char, + pub pw_shell: *mut c_char, + } + + extern "C" { + fn sysconf(name: c_int) -> c_long; + fn getpwuid_r( + uid: uid_t, + pwd: *mut passwd, + buf: *mut c_char, + buflen: size_t, + result: *mut *mut passwd, + ) -> c_int; + fn getuid() -> uid_t; + } + + const SC_GETPW_R_SIZE_MAX: c_int = 70; + + #[must_use] + #[cfg(target_os = "linux")] + pub fn is_absolute_path(path: OsString) -> Option { + let path = PathBuf::from(path); + if path.is_absolute() { + Some(path) + } else { + None + } + } + + #[cfg(not(target_os = "windows"))] + #[must_use] + pub fn home_dir() -> Option { + return env::var_os("HOME") + .and_then(|h| if h.is_empty() { None } else { Some(h) }) + .or_else(|| unsafe { fallback() }) + .map(PathBuf::from); + + #[cfg(any(target_os = "android", target_os = "ios", target_os = "emscripten"))] + unsafe fn fallback() -> Option { + None + } + #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "emscripten")))] + unsafe fn fallback() -> Option { + let amt = match sysconf(SC_GETPW_R_SIZE_MAX) { + n if n < 0 => 512, + n => n as usize, + }; + let mut buf = Vec::with_capacity(amt); + let mut passwd: passwd = mem::zeroed(); + let mut result = ptr::null_mut(); + match getpwuid_r( + getuid(), + &mut passwd, + buf.as_mut_ptr(), + buf.capacity(), + &mut result, + ) { + 0 if !result.is_null() => { + let ptr = passwd.pw_dir as *const _; + let bytes = CStr::from_ptr(ptr).to_bytes(); + if bytes.is_empty() { + None + } else { + Some(OsStringExt::from_vec(bytes.to_vec())) + } + } + _ => None, + } + } + } +} + +#[cfg(target_os = "linux")] +#[must_use] +pub fn cache_dir() -> Option { + std::env::var_os("XDG_CACHE_HOME") + .and_then(self::unix::is_absolute_path) + .or_else(|| self::unix::home_dir().map(|h| h.join(".cache").join(PACKAGE_NAME))) +} + +#[cfg(target_os = "macos")] +#[must_use] +pub fn cache_dir() -> Option { + self::unix::home_dir().map(|h| h.join("Library/Caches").join(PACKAGE_NAME)) +} diff --git a/utoipa-swagger-ui/src/internal/mod.rs b/utoipa-swagger-ui/src/internal/mod.rs new file mode 100644 index 00000000..16ec3672 --- /dev/null +++ b/utoipa-swagger-ui/src/internal/mod.rs @@ -0,0 +1 @@ +pub mod dirs; From c2ed2692b3733bf553ab4185c1e6466891c660b2 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 02:59:32 +0200 Subject: [PATCH 02/11] simplify caching --- utoipa-swagger-ui/Cargo.toml | 4 +- utoipa-swagger-ui/build.rs | 59 ++++--- utoipa-swagger-ui/src/internal/dirs.rs | 206 ------------------------- utoipa-swagger-ui/src/internal/mod.rs | 1 - 4 files changed, 38 insertions(+), 232 deletions(-) delete mode 100644 utoipa-swagger-ui/src/internal/dirs.rs delete mode 100644 utoipa-swagger-ui/src/internal/mod.rs diff --git a/utoipa-swagger-ui/Cargo.toml b/utoipa-swagger-ui/Cargo.toml index 852b19b1..68c3b54f 100644 --- a/utoipa-swagger-ui/Cargo.toml +++ b/utoipa-swagger-ui/Cargo.toml @@ -18,7 +18,7 @@ debug-embed = ["rust-embed/debug-embed"] reqwest = ["dep:reqwest"] url = ["dep:url"] vendored = ["dep:utoipa-swagger-ui-vendored"] -cache = [] +cache = ["dep:dirs", "dep:sha2"] [dependencies] rust-embed = { version = "8" } @@ -48,6 +48,8 @@ rustdoc-args = ["--cfg", "doc_cfg"] [build-dependencies] zip = { version = "2", default-features = false, features = ["deflate"] } regex = "1.7" +dirs = { version = "5.0.1", optional = true } +sha2 = { version = "0.10.8", optional = true } # enabled optionally to allow rust only build with expense of bigger dependency tree and platform # independent build. By default `curl` system package is tried for downloading the Swagger UI. diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index b949e1ad..2ffd6795 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -9,13 +9,6 @@ use std::{ use regex::Regex; use zip::{result::ZipError, ZipArchive}; -#[path = "src/internal/mod.rs"] -#[cfg(feature = "cache")] -mod internal; - -#[cfg(feature = "cache")] -use internal::dirs::cache_dir; - /// the following env variables control the build process: /// 1. SWAGGER_UI_DOWNLOAD_URL: /// + the url from where to download the swagger-ui zip file if starts with http:// or https:// @@ -31,10 +24,19 @@ const SWAGGER_UI_DOWNLOAD_URL_DEFAULT: &str = const SWAGGER_UI_DOWNLOAD_URL: &str = "SWAGGER_UI_DOWNLOAD_URL"; const SWAGGER_UI_OVERWRITE_FOLDER: &str = "SWAGGER_UI_OVERWRITE_FOLDER"; -// wget && sha256sum | tr '[a-z]' '[A-Z]' #[cfg(feature = "cache")] -const SWAGGER_UI_FILE_HASH: &str = - "481244D0812097B11FBAEEF79F71D942B171617F9C9F9514E63ACBE13E71CCDC"; +fn sha256(data: &[u8]) -> String { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(data); + let hash = hasher.finalize(); + format!("{:x}", hash).to_uppercase() +} + +#[cfg(feature = "cache")] +fn get_cache_dir() -> Option { + dirs::cache_dir().map(|p| p.join("utoipa-swagger-ui")) +} fn main() { let target_dir = env::var("OUT_DIR").unwrap(); @@ -149,7 +151,8 @@ impl SwaggerZip { fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { let zip_filename = url.split('/').last().unwrap().to_string(); - let zip_path = [target_dir, &zip_filename].iter().collect::(); + #[allow(unused_mut)] + let mut zip_path = [target_dir, &zip_filename].iter().collect::(); if cfg!(feature = "vendored") { #[cfg(not(feature = "vendored"))] @@ -185,26 +188,34 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { .expect("failed to open file protocol copied Swagger UI"); SwaggerZip::File(zip) } else if url.starts_with("http://") || url.starts_with("https://") { - let mut cache_dir = cache_dir() - .expect("could not determine cache directory") - .join("swagger-ui") - .join(SWAGGER_UI_FILE_HASH); - - if fs::create_dir_all(&cache_dir).is_err() { - cache_dir = env::var("OUT_DIR").unwrap().into(); + #[cfg(feature = "cache")] + { + // Compute cache key based hashed URL + crate version + let mut cache_key = String::new(); + cache_key.push_str(url); + cache_key.push_str(&env::var("CARGO_PKG_VERSION").unwrap_or_default()); + let cache_key = sha256(cache_key.as_bytes()); + // Store the cache in the cache_key directory inside the OS's default cache folder + let mut cache_dir = get_cache_dir() + .expect("could not determine cache directory") + .join("swagger-ui") + .join(cache_key); + if fs::create_dir_all(&cache_dir).is_err() { + cache_dir = env::var("OUT_DIR").unwrap().into(); + } + zip_path = cache_dir.join(&zip_filename); } - let zip_cache_path = cache_dir.join(&zip_filename); - if zip_cache_path.exists() { - println!("using cached zip path from : {:?}", zip_cache_path); + if zip_path.exists() { + println!("using cached zip path from : {:?}", zip_path); } else { - println!("start download to : {:?}", zip_cache_path); + println!("start download to : {:?}", zip_path); // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); - download_file(url, &zip_cache_path).expect("failed to download Swagger UI"); + download_file(url, &zip_path).expect("failed to download Swagger UI"); } - let swagger_ui_zip = File::open(&zip_cache_path).unwrap(); + let swagger_ui_zip = File::open(&zip_path).unwrap(); let zip = ZipArchive::new(swagger_ui_zip).expect("failed to open downloaded Swagger UI"); SwaggerZip::File(zip) } else { diff --git a/utoipa-swagger-ui/src/internal/dirs.rs b/utoipa-swagger-ui/src/internal/dirs.rs deleted file mode 100644 index db87a6a2..00000000 --- a/utoipa-swagger-ui/src/internal/dirs.rs +++ /dev/null @@ -1,206 +0,0 @@ -// based on https://github.com/pykeio/ort/blob/main/ort-sys/src/internal/dirs.rs and https://github.com/dirs-dev/dirs-sys-rs/blob/main/src/lib.rs - -pub const PACKAGE_NAME: &str = "utoipa-swagger-ui"; - -#[cfg(all(target_os = "windows", target_arch = "x86"))] -macro_rules! win32_extern { - ($library:literal $abi:literal $($link_name:literal)? $(#[$doc:meta])? fn $($function:tt)*) => ( - #[link(name = $library, kind = "raw-dylib", modifiers = "+verbatim", import_name_type = "undecorated")] - extern $abi { - $(#[$doc])? - $(#[link_name=$link_name])? - fn $($function)*; - } - ) -} -#[cfg(all(target_os = "windows", not(target_arch = "x86")))] -macro_rules! win32_extern { - ($library:literal $abi:literal $($link_name:literal)? $(#[$doc:meta])? fn $($function:tt)*) => ( - #[link(name = $library, kind = "raw-dylib", modifiers = "+verbatim")] - extern "C" { - $(#[$doc])? - $(#[link_name=$link_name])? - fn $($function)*; - } - ) -} - -#[cfg(target_os = "windows")] -#[allow(non_camel_case_types, clippy::upper_case_acronyms)] -mod windows { - use std::{ - ffi::{c_void, OsString}, - os::windows::prelude::OsStringExt, - path::PathBuf, - ptr, slice, - }; - - #[repr(C)] - #[derive(Clone, Copy)] - struct GUID { - data1: u32, - data2: u16, - data3: u16, - data4: [u8; 8], - } - - impl GUID { - pub const fn from_u128(uuid: u128) -> Self { - Self { - data1: (uuid >> 96) as u32, - data2: (uuid >> 80 & 0xffff) as u16, - data3: (uuid >> 64 & 0xffff) as u16, - #[allow(clippy::cast_possible_truncation)] - data4: (uuid as u64).to_be_bytes(), - } - } - } - - type HRESULT = i32; - type PWSTR = *mut u16; - type PCWSTR = *const u16; - type HANDLE = isize; - type KNOWN_FOLDER_FLAG = i32; - - win32_extern!("SHELL32.DLL" "system" fn SHGetKnownFolderPath(rfid: *const GUID, dwflags: KNOWN_FOLDER_FLAG, htoken: HANDLE, ppszpath: *mut PWSTR) -> HRESULT); - win32_extern!("KERNEL32.DLL" "system" fn lstrlenW(lpstring: PCWSTR) -> i32); - win32_extern!("OLE32.DLL" "system" fn CoTaskMemFree(pv: *const ::core::ffi::c_void) -> ()); - - fn known_folder(folder_id: GUID) -> Option { - unsafe { - let mut path_ptr: PWSTR = ptr::null_mut(); - let result = SHGetKnownFolderPath(&folder_id, 0, HANDLE::default(), &mut path_ptr); - if result == 0 { - let len = lstrlenW(path_ptr) as usize; - let path = slice::from_raw_parts(path_ptr, len); - let ostr: OsString = OsStringExt::from_wide(path); - CoTaskMemFree(path_ptr as *const c_void); - Some(PathBuf::from(ostr)) - } else { - CoTaskMemFree(path_ptr as *const c_void); - None - } - } - } - - #[allow(clippy::unusual_byte_groupings)] - const FOLDERID_LOCAL_APP_DATA: GUID = GUID::from_u128(0xf1b32785_6fba_4fcf_9d557b8e7f157091); - - #[must_use] - pub fn known_folder_local_app_data() -> Option { - known_folder(FOLDERID_LOCAL_APP_DATA) - } -} -#[cfg(target_os = "windows")] -#[must_use] -pub fn cache_dir() -> Option { - self::windows::known_folder_local_app_data().map(|h| h.join(PACKAGE_NAME)) -} - -#[cfg(unix)] -#[allow(non_camel_case_types)] -mod unix { - use std::{ - env, - ffi::{c_char, c_int, c_long, CStr, OsString}, - mem, - os::unix::prelude::OsStringExt, - path::PathBuf, - ptr, - }; - - type uid_t = u32; - type gid_t = u32; - type size_t = usize; - #[repr(C)] - struct passwd { - pub pw_name: *mut c_char, - pub pw_passwd: *mut c_char, - pub pw_uid: uid_t, - pub pw_gid: gid_t, - pub pw_gecos: *mut c_char, - pub pw_dir: *mut c_char, - pub pw_shell: *mut c_char, - } - - extern "C" { - fn sysconf(name: c_int) -> c_long; - fn getpwuid_r( - uid: uid_t, - pwd: *mut passwd, - buf: *mut c_char, - buflen: size_t, - result: *mut *mut passwd, - ) -> c_int; - fn getuid() -> uid_t; - } - - const SC_GETPW_R_SIZE_MAX: c_int = 70; - - #[must_use] - #[cfg(target_os = "linux")] - pub fn is_absolute_path(path: OsString) -> Option { - let path = PathBuf::from(path); - if path.is_absolute() { - Some(path) - } else { - None - } - } - - #[cfg(not(target_os = "windows"))] - #[must_use] - pub fn home_dir() -> Option { - return env::var_os("HOME") - .and_then(|h| if h.is_empty() { None } else { Some(h) }) - .or_else(|| unsafe { fallback() }) - .map(PathBuf::from); - - #[cfg(any(target_os = "android", target_os = "ios", target_os = "emscripten"))] - unsafe fn fallback() -> Option { - None - } - #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "emscripten")))] - unsafe fn fallback() -> Option { - let amt = match sysconf(SC_GETPW_R_SIZE_MAX) { - n if n < 0 => 512, - n => n as usize, - }; - let mut buf = Vec::with_capacity(amt); - let mut passwd: passwd = mem::zeroed(); - let mut result = ptr::null_mut(); - match getpwuid_r( - getuid(), - &mut passwd, - buf.as_mut_ptr(), - buf.capacity(), - &mut result, - ) { - 0 if !result.is_null() => { - let ptr = passwd.pw_dir as *const _; - let bytes = CStr::from_ptr(ptr).to_bytes(); - if bytes.is_empty() { - None - } else { - Some(OsStringExt::from_vec(bytes.to_vec())) - } - } - _ => None, - } - } - } -} - -#[cfg(target_os = "linux")] -#[must_use] -pub fn cache_dir() -> Option { - std::env::var_os("XDG_CACHE_HOME") - .and_then(self::unix::is_absolute_path) - .or_else(|| self::unix::home_dir().map(|h| h.join(".cache").join(PACKAGE_NAME))) -} - -#[cfg(target_os = "macos")] -#[must_use] -pub fn cache_dir() -> Option { - self::unix::home_dir().map(|h| h.join("Library/Caches").join(PACKAGE_NAME)) -} diff --git a/utoipa-swagger-ui/src/internal/mod.rs b/utoipa-swagger-ui/src/internal/mod.rs deleted file mode 100644 index 16ec3672..00000000 --- a/utoipa-swagger-ui/src/internal/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod dirs; From 0410c9405fb84fbeae9a8c0c0aac9e715e542537 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:08:51 +0200 Subject: [PATCH 03/11] docs --- utoipa-swagger-ui/CHANGELOG.md | 4 ++++ utoipa-swagger-ui/Cargo.toml | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/utoipa-swagger-ui/CHANGELOG.md b/utoipa-swagger-ui/CHANGELOG.md index cbd839f5..f17d37b9 100644 --- a/utoipa-swagger-ui/CHANGELOG.md +++ b/utoipa-swagger-ui/CHANGELOG.md @@ -4,6 +4,10 @@ ### Added +* Cache swagger ui zip in build script (https://github.com/juhaku/utoipa/pull/1214) + +### Added + * Allow disabling syntax highlighting (https://github.com/juhaku/utoipa/pull/1188) ## 8.0.3 - Oct 23 2024 diff --git a/utoipa-swagger-ui/Cargo.toml b/utoipa-swagger-ui/Cargo.toml index 68c3b54f..b1a6c7de 100644 --- a/utoipa-swagger-ui/Cargo.toml +++ b/utoipa-swagger-ui/Cargo.toml @@ -18,6 +18,7 @@ debug-embed = ["rust-embed/debug-embed"] reqwest = ["dep:reqwest"] url = ["dep:url"] vendored = ["dep:utoipa-swagger-ui-vendored"] +# Cache swagger ui zip cache = ["dep:dirs", "dep:sha2"] [dependencies] @@ -41,7 +42,7 @@ tokio = { version = "1", features = ["macros"] } utoipa-swagger-ui = { path = ".", features = ["actix-web", "axum", "rocket"] } [package.metadata.docs.rs] -features = ["actix-web", "axum", "rocket", "vendored"] +features = ["actix-web", "axum", "rocket", "vendored", "cache"] no-default-features = true rustdoc-args = ["--cfg", "doc_cfg"] From dcf94c3dd6d4bc93bd627b5901c3896b2f01b961 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:12:54 +0200 Subject: [PATCH 04/11] remove unrelated changes --- utoipa-swagger-ui/build.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index 2ffd6795..d1986bdb 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -154,7 +154,7 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { #[allow(unused_mut)] let mut zip_path = [target_dir, &zip_filename].iter().collect::(); - if cfg!(feature = "vendored") { + if env::var("CARGO_FEATURE_VENDORED").is_ok() { #[cfg(not(feature = "vendored"))] unreachable!("Cannot get vendored Swagger UI without `vendored` flag"); @@ -213,9 +213,9 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); - download_file(url, &zip_path).expect("failed to download Swagger UI"); + download_file(url, zip_path.clone()).expect("failed to download Swagger UI"); } - let swagger_ui_zip = File::open(&zip_path).unwrap(); + let swagger_ui_zip = File::open(zip_path).unwrap(); let zip = ZipArchive::new(swagger_ui_zip).expect("failed to open downloaded Swagger UI"); SwaggerZip::File(zip) } else { @@ -257,15 +257,16 @@ struct SwaggerUiDist; fs::write(path, contents).unwrap(); } -fn download_file(url: &str, path: &Path) -> Result<(), Box> { - #[cfg(feature = "reqwest")] - { - download_file_reqwest(url, path) - } - #[cfg(not(feature = "reqwest"))] - { +fn download_file(url: &str, path: PathBuf) -> Result<(), Box> { + let reqwest_feature = env::var("CARGO_FEATURE_REQWEST"); + println!("reqwest feature: {reqwest_feature:?}"); + if reqwest_feature.is_ok() { + #[cfg(feature = "reqwest")] + download_file_reqwest(url, path)?; + Ok(()) + } else { println!("trying to download using `curl` system package"); - download_file_curl(url, path) + download_file_curl(url, path.as_path()) } } @@ -299,7 +300,6 @@ fn parse_ca_file(path: &str) -> Result> { Ok(cert) } -#[cfg(not(feature = "reqwest"))] fn download_file_curl>(url: &str, target_dir: T) -> Result<(), Box> { // Not using `CARGO_CFG_TARGET_OS` because of the possibility of cross-compilation. // When targeting `x86_64-pc-windows-gnu` on Linux for example, `cfg!()` in the From e8a37991fe972525381e3e9405ce46ae040919a0 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:14:47 +0200 Subject: [PATCH 05/11] remove unrelated changes --- utoipa-swagger-ui/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index d1986bdb..04ca0b8c 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -271,7 +271,7 @@ fn download_file(url: &str, path: PathBuf) -> Result<(), Box> { } #[cfg(feature = "reqwest")] -fn download_file_reqwest(url: &str, path: &Path) -> Result<(), Box> { +fn download_file_reqwest(url: &str, path: PathBuf) -> Result<(), Box> { let mut client_builder = reqwest::blocking::Client::builder(); if let Ok(cainfo) = env::var("CARGO_HTTP_CAINFO") { From 6f7370834b2c1780475731f25fe0386f962c8e6e Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:16:08 +0200 Subject: [PATCH 06/11] change location of rerun --- utoipa-swagger-ui/build.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index 04ca0b8c..7ad30e8a 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -188,6 +188,9 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { .expect("failed to open file protocol copied Swagger UI"); SwaggerZip::File(zip) } else if url.starts_with("http://") || url.starts_with("https://") { + // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes + println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); + #[cfg(feature = "cache")] { // Compute cache key based hashed URL + crate version @@ -210,9 +213,6 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { println!("using cached zip path from : {:?}", zip_path); } else { println!("start download to : {:?}", zip_path); - - // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes - println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); download_file(url, zip_path.clone()).expect("failed to download Swagger UI"); } let swagger_ui_zip = File::open(zip_path).unwrap(); From 93a9abed93bce21555c7be89c44e52613b4187f4 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:25:04 +0200 Subject: [PATCH 07/11] fallback if failed to resolve cache dir --- utoipa-swagger-ui/build.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index 7ad30e8a..8f88c023 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -191,6 +191,7 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { // with http protocol we update when the 'SWAGGER_UI_DOWNLOAD_URL' changes println!("cargo:rerun-if-env-changed={SWAGGER_UI_DOWNLOAD_URL}"); + // Update zip_path to point to the resolved cache directory #[cfg(feature = "cache")] { // Compute cache key based hashed URL + crate version @@ -199,10 +200,12 @@ fn get_zip_archive(url: &str, target_dir: &str) -> SwaggerZip { cache_key.push_str(&env::var("CARGO_PKG_VERSION").unwrap_or_default()); let cache_key = sha256(cache_key.as_bytes()); // Store the cache in the cache_key directory inside the OS's default cache folder - let mut cache_dir = get_cache_dir() - .expect("could not determine cache directory") - .join("swagger-ui") - .join(cache_key); + let mut cache_dir = if let Some(dir) = get_cache_dir() { + dir.join("swagger-ui").join(&cache_key) + } else { + println!("cargo:warning=Could not determine cache directory, using OUT_DIR"); + PathBuf::from(env::var("OUT_DIR").unwrap()) + }; if fs::create_dir_all(&cache_dir).is_err() { cache_dir = env::var("OUT_DIR").unwrap().into(); } From 90b4746849d1f881f396f93d053504ddc2faaa5f Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:25:43 +0200 Subject: [PATCH 08/11] docs --- utoipa-swagger-ui/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utoipa-swagger-ui/CHANGELOG.md b/utoipa-swagger-ui/CHANGELOG.md index f17d37b9..423f1eca 100644 --- a/utoipa-swagger-ui/CHANGELOG.md +++ b/utoipa-swagger-ui/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -* Cache swagger ui zip in build script (https://github.com/juhaku/utoipa/pull/1214) +* Add `cache` feature to cache swagger ui zip in build script (https://github.com/juhaku/utoipa/pull/1214) ### Added From cbefe604b1d18d582975bf491b30bc52e81a3e5f Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:28:25 +0200 Subject: [PATCH 09/11] docs --- utoipa-swagger-ui/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utoipa-swagger-ui/Cargo.toml b/utoipa-swagger-ui/Cargo.toml index b1a6c7de..b12dd8a4 100644 --- a/utoipa-swagger-ui/Cargo.toml +++ b/utoipa-swagger-ui/Cargo.toml @@ -18,7 +18,7 @@ debug-embed = ["rust-embed/debug-embed"] reqwest = ["dep:reqwest"] url = ["dep:url"] vendored = ["dep:utoipa-swagger-ui-vendored"] -# Cache swagger ui zip +# cache swagger ui zip cache = ["dep:dirs", "dep:sha2"] [dependencies] @@ -49,6 +49,8 @@ rustdoc-args = ["--cfg", "doc_cfg"] [build-dependencies] zip = { version = "2", default-features = false, features = ["deflate"] } regex = "1.7" + +# used by cache feature dirs = { version = "5.0.1", optional = true } sha2 = { version = "0.10.8", optional = true } From 1eea4577da4c8a431d9f2a244bb8cf261d1429f4 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:31:36 +0200 Subject: [PATCH 10/11] readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 392e02c0..31dbd835 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ and the `ipa` is _api_ reversed. Aaand... `ipa` is also an awesome type of beer - **`rc_schema`**: Add `ToSchema` support for `Arc` and `Rc` types. **Note!** serde `rc` feature flag must be enabled separately to allow serialization and deserialization of `Arc` and `Rc` types. See more about [serde feature flags](https://serde.rs/feature-flags.html). - **`config`** Enables [`utoipa-config`](./utoipa-config/README.md) for the project which allows defining global configuration options for `utoipa`. +- **`cache`** Enables caching of the Swagger UI download in `utoipa-swagger-ui` during the build process. ### Default Library Support From 5b9a654a3609bb395d8e75a27e333705b794c256 Mon Sep 17 00:00:00 2001 From: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Date: Mon, 2 Dec 2024 02:15:54 +0200 Subject: [PATCH 11/11] Update utoipa-swagger-ui/CHANGELOG.md Co-authored-by: Juha Kukkonen --- utoipa-swagger-ui/CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/utoipa-swagger-ui/CHANGELOG.md b/utoipa-swagger-ui/CHANGELOG.md index 423f1eca..5b02b17f 100644 --- a/utoipa-swagger-ui/CHANGELOG.md +++ b/utoipa-swagger-ui/CHANGELOG.md @@ -5,9 +5,6 @@ ### Added * Add `cache` feature to cache swagger ui zip in build script (https://github.com/juhaku/utoipa/pull/1214) - -### Added - * Allow disabling syntax highlighting (https://github.com/juhaku/utoipa/pull/1188) ## 8.0.3 - Oct 23 2024