|
3 | 3 | use crate::{
|
4 | 4 | AsyncStorage, Config, InstanceMetrics, RUSTDOC_STATIC_STORAGE_PREFIX,
|
5 | 5 | db::Pool,
|
6 |
| - storage::rustdoc_archive_path, |
| 6 | + storage::{RustdocJsonFormatVersion, rustdoc_archive_path, rustdoc_json_path}, |
7 | 7 | utils,
|
8 | 8 | web::{
|
9 | 9 | MetaData, ReqVersion, axum_cached_redirect, axum_parse_uri_with_params,
|
@@ -817,6 +817,72 @@ pub(crate) async fn badge_handler(
|
817 | 817 | ))
|
818 | 818 | }
|
819 | 819 |
|
| 820 | +#[derive(Clone, Deserialize, Debug)] |
| 821 | +pub(crate) struct JsonDownloadParams { |
| 822 | + pub(crate) name: String, |
| 823 | + pub(crate) version: ReqVersion, |
| 824 | + pub(crate) target: Option<String>, |
| 825 | + pub(crate) format_version: Option<RustdocJsonFormatVersion>, |
| 826 | +} |
| 827 | + |
| 828 | +#[instrument(skip_all)] |
| 829 | +pub(crate) async fn json_download_handler( |
| 830 | + Path(params): Path<JsonDownloadParams>, |
| 831 | + mut conn: DbConnection, |
| 832 | + Extension(config): Extension<Arc<Config>>, |
| 833 | +) -> AxumResult<impl IntoResponse> { |
| 834 | + let matched_release = match_version(&mut conn, ¶ms.name, ¶ms.version) |
| 835 | + .await? |
| 836 | + .assume_exact_name()?; |
| 837 | + |
| 838 | + if !matched_release.rustdoc_status() { |
| 839 | + // without docs we'll never have JSON docs too |
| 840 | + return Err(AxumNope::ResourceNotFound); |
| 841 | + } |
| 842 | + |
| 843 | + let krate = CrateDetails::from_matched_release(&mut conn, matched_release).await?; |
| 844 | + |
| 845 | + let target = if let Some(wanted_target) = params.target { |
| 846 | + if krate |
| 847 | + .metadata |
| 848 | + .doc_targets |
| 849 | + .as_ref() |
| 850 | + .expect("we are checking rustdoc_status() above, so we always have metadata") |
| 851 | + .iter() |
| 852 | + .any(|s| s == &wanted_target) |
| 853 | + { |
| 854 | + wanted_target |
| 855 | + } else { |
| 856 | + return Err(AxumNope::TargetNotFound); |
| 857 | + } |
| 858 | + } else { |
| 859 | + krate |
| 860 | + .metadata |
| 861 | + .default_target |
| 862 | + .as_ref() |
| 863 | + .expect("we are checking rustdoc_status() above, so we always have metadata") |
| 864 | + .to_string() |
| 865 | + }; |
| 866 | + |
| 867 | + let format_version = params |
| 868 | + .format_version |
| 869 | + .unwrap_or(RustdocJsonFormatVersion::Latest); |
| 870 | + |
| 871 | + let storage_path = rustdoc_json_path( |
| 872 | + &krate.name, |
| 873 | + &krate.version.to_string(), |
| 874 | + &target, |
| 875 | + format_version, |
| 876 | + ); |
| 877 | + |
| 878 | + // since we didn't build rustdoc json for all releases yet, |
| 879 | + // this redirect might redirect to a location that doesn't exist. |
| 880 | + Ok(super::axum_cached_redirect( |
| 881 | + format!("{}/{}", config.s3_static_root_path, storage_path), |
| 882 | + CachePolicy::ForeverInCdn, |
| 883 | + )?) |
| 884 | +} |
| 885 | + |
820 | 886 | #[instrument(skip_all)]
|
821 | 887 | pub(crate) async fn download_handler(
|
822 | 888 | Path((name, req_version)): Path<(String, ReqVersion)>,
|
@@ -874,6 +940,7 @@ pub(crate) async fn static_asset_handler(
|
874 | 940 |
|
875 | 941 | #[cfg(test)]
|
876 | 942 | mod test {
|
| 943 | + use super::*; |
877 | 944 | use crate::{
|
878 | 945 | Config,
|
879 | 946 | registry_api::{CrateOwner, OwnerKind},
|
@@ -3009,4 +3076,132 @@ mod test {
|
3009 | 3076 | Ok(())
|
3010 | 3077 | });
|
3011 | 3078 | }
|
| 3079 | + |
| 3080 | + #[test_case( |
| 3081 | + "latest/json", |
| 3082 | + "0.2.0", |
| 3083 | + "x86_64-unknown-linux-gnu", |
| 3084 | + RustdocJsonFormatVersion::Latest |
| 3085 | + )] |
| 3086 | + #[test_case( |
| 3087 | + "0.1/json", |
| 3088 | + "0.1.0", |
| 3089 | + "x86_64-unknown-linux-gnu", |
| 3090 | + RustdocJsonFormatVersion::Latest; |
| 3091 | + "semver" |
| 3092 | + )] |
| 3093 | + #[test_case( |
| 3094 | + "0.1.0/json", |
| 3095 | + "0.1.0", |
| 3096 | + "x86_64-unknown-linux-gnu", |
| 3097 | + RustdocJsonFormatVersion::Latest |
| 3098 | + )] |
| 3099 | + #[test_case( |
| 3100 | + "latest/json/latest", |
| 3101 | + "0.2.0", |
| 3102 | + "x86_64-unknown-linux-gnu", |
| 3103 | + RustdocJsonFormatVersion::Latest |
| 3104 | + )] |
| 3105 | + #[test_case( |
| 3106 | + "latest/json/42", |
| 3107 | + "0.2.0", |
| 3108 | + "x86_64-unknown-linux-gnu", |
| 3109 | + RustdocJsonFormatVersion::Version(42) |
| 3110 | + )] |
| 3111 | + #[test_case( |
| 3112 | + "latest/i686-pc-windows-msvc/json", |
| 3113 | + "0.2.0", |
| 3114 | + "i686-pc-windows-msvc", |
| 3115 | + RustdocJsonFormatVersion::Latest |
| 3116 | + )] |
| 3117 | + #[test_case( |
| 3118 | + "latest/i686-pc-windows-msvc/json/42", |
| 3119 | + "0.2.0", |
| 3120 | + "i686-pc-windows-msvc", |
| 3121 | + RustdocJsonFormatVersion::Version(42) |
| 3122 | + )] |
| 3123 | + fn json_download( |
| 3124 | + request_path_suffix: &str, |
| 3125 | + redirect_version: &str, |
| 3126 | + redirect_target: &str, |
| 3127 | + redirect_format_version: RustdocJsonFormatVersion, |
| 3128 | + ) { |
| 3129 | + async_wrapper(|env| async move { |
| 3130 | + env.override_config(|config| { |
| 3131 | + config.s3_static_root_path = "https://static.docs.rs".into(); |
| 3132 | + }); |
| 3133 | + env.fake_release() |
| 3134 | + .await |
| 3135 | + .name("dummy") |
| 3136 | + .version("0.1.0") |
| 3137 | + .archive_storage(true) |
| 3138 | + .default_target("x86_64-unknown-linux-gnu") |
| 3139 | + .add_target("i686-pc-windows-msvc") |
| 3140 | + .create() |
| 3141 | + .await?; |
| 3142 | + |
| 3143 | + env.fake_release() |
| 3144 | + .await |
| 3145 | + .name("dummy") |
| 3146 | + .version("0.2.0") |
| 3147 | + .archive_storage(true) |
| 3148 | + .default_target("x86_64-unknown-linux-gnu") |
| 3149 | + .add_target("i686-pc-windows-msvc") |
| 3150 | + .create() |
| 3151 | + .await?; |
| 3152 | + |
| 3153 | + let web = env.web_app().await; |
| 3154 | + |
| 3155 | + web.assert_redirect_cached_unchecked( |
| 3156 | + &format!("/crate/dummy/{request_path_suffix}"), |
| 3157 | + &format!("https://static.docs.rs/rustdoc-json/dummy/{redirect_version}/{redirect_target}/\ |
| 3158 | + dummy_{redirect_version}_{redirect_target}_{redirect_format_version}.json.zst"), |
| 3159 | + CachePolicy::ForeverInCdn, |
| 3160 | + &env.config(), |
| 3161 | + ) |
| 3162 | + .await?; |
| 3163 | + Ok(()) |
| 3164 | + }); |
| 3165 | + } |
| 3166 | + |
| 3167 | + #[test_case("0.1.0/json"; "rustdoc status false")] |
| 3168 | + #[test_case("0.2.0/unknown-target/json"; "unknown target")] |
| 3169 | + #[test_case("0.42.0/json"; "unknown version")] |
| 3170 | + fn json_download_not_found(request_path_suffix: &str) { |
| 3171 | + async_wrapper(|env| async move { |
| 3172 | + env.override_config(|config| { |
| 3173 | + config.s3_static_root_path = "https://static.docs.rs".into(); |
| 3174 | + }); |
| 3175 | + |
| 3176 | + env.fake_release() |
| 3177 | + .await |
| 3178 | + .name("dummy") |
| 3179 | + .version("0.1.0") |
| 3180 | + .archive_storage(true) |
| 3181 | + .default_target("x86_64-unknown-linux-gnu") |
| 3182 | + .add_target("i686-pc-windows-msvc") |
| 3183 | + .binary(true) // binary => rustdoc_status = false |
| 3184 | + .create() |
| 3185 | + .await?; |
| 3186 | + |
| 3187 | + env.fake_release() |
| 3188 | + .await |
| 3189 | + .name("dummy") |
| 3190 | + .version("0.2.0") |
| 3191 | + .archive_storage(true) |
| 3192 | + .default_target("x86_64-unknown-linux-gnu") |
| 3193 | + .add_target("i686-pc-windows-msvc") |
| 3194 | + .create() |
| 3195 | + .await?; |
| 3196 | + |
| 3197 | + let web = env.web_app().await; |
| 3198 | + |
| 3199 | + let response = web |
| 3200 | + .get(&format!("/crate/dummy/{request_path_suffix}")) |
| 3201 | + .await?; |
| 3202 | + |
| 3203 | + assert_eq!(response.status(), StatusCode::NOT_FOUND); |
| 3204 | + Ok(()) |
| 3205 | + }); |
| 3206 | + } |
3012 | 3207 | }
|
0 commit comments