use std::sync::Arc;
use axum::extract::{Path, State};
use axum::http::{HeaderMap, HeaderValue, header};
use axum::response::{IntoResponse, Response};
use crate::App;
use crate::http::error::ApiError;
use crate::npm::{self, PackageName};
use crate::store::cache::Representation;
const ABBREVIATED_ACCEPT: &str = "application/vnd.npm.install-v1+json";
pub async fn ping() -> Response {
let mut response = "{}".into_response();
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
response
}
pub async fn package(
State(app): State<Arc<App>>,
headers: HeaderMap,
Path(package): Path<String>,
) -> Result<Response, ApiError> {
let _permit = app.limits.active_permit().ok_or(ApiError::Overloaded)?;
let name = parse_name(&package)?;
let rendered = npm::serve(&app, &name, negotiate(&headers)).await?;
Ok(respond(rendered))
}
pub async fn package_version(
State(app): State<Arc<App>>,
headers: HeaderMap,
Path((package, version_or_tag)): Path<(String, String)>,
) -> Result<Response, ApiError> {
let _permit = app.limits.active_permit().ok_or(ApiError::Overloaded)?;
if package.starts_with('@') && !package.contains('/') {
let name = parse_name(&format!("{package}/{version_or_tag}"))?;
let rendered = npm::serve(&app, &name, negotiate(&headers)).await?;
return Ok(respond(rendered));
}
let name = parse_name(&package)?;
let rendered = npm::serve(&app, &name, Representation::NpmVersion(version_or_tag)).await?;
Ok(respond(rendered))
}
fn parse_name(raw: &str) -> Result<PackageName, ApiError> {
PackageName::parse_route(raw).map_err(|err| {
tracing::debug!(error = %err, "refused a package name");
ApiError::InvalidInput("the package name is not a valid npm package name")
})
}
fn negotiate(headers: &HeaderMap) -> Representation {
let accepts_abbreviated = headers
.get(header::ACCEPT)
.and_then(|value| value.to_str().ok())
.is_some_and(|accept| accept.contains(ABBREVIATED_ACCEPT));
if accepts_abbreviated {
Representation::NpmAbbreviated
} else {
Representation::NpmFull
}
}
fn respond(rendered: npm::Rendered) -> Response {
let mut response = rendered.body.to_vec().into_response();
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static(rendered.content_type),
);
response
}