use axum::{
Json,
extract::{Path, State},
http::{StatusCode, header::LOCATION},
{Router, routing::get},
};
use hyper::HeaderMap;
use ogcapi_types::common::{
Collection, Collections, Crs, Link, Linked, Query,
link_rel::{DATA, ITEMS, ROOT, SELF},
media_type::{GEO_JSON, JSON},
};
use crate::{
AppState, Error, Result,
extractors::{Qs, RemoteUrl},
};
const CONFORMANCE: [&str; 3] = [
"http://www.opengis.net/spec/ogcapi-common-1/1.0/conf/core",
"http://www.opengis.net/spec/ogcapi-common-2/1.0/conf/collections",
"http://www.opengis.net/spec/ogcapi_common-2/1.0/conf/json",
];
async fn create(
State(state): State<AppState>,
RemoteUrl(url): RemoteUrl,
Json(collection): Json<Collection>,
) -> Result<(StatusCode, HeaderMap)> {
if state
.drivers
.collections
.read_collection(&collection.id)
.await?
.is_some()
{
return Err(Error::Exception(
StatusCode::CONFLICT,
format!("Collection with id `{}` already exists.", collection.id),
));
}
let id = state
.drivers
.collections
.create_collection(&collection)
.await?;
let location = url.join(&format!("collections/{id}"))?;
let mut headers = HeaderMap::new();
headers.insert(LOCATION, location.as_str().parse().unwrap());
Ok((StatusCode::CREATED, headers))
}
async fn read(
State(state): State<AppState>,
Path(collection_id): Path<String>,
RemoteUrl(url): RemoteUrl,
) -> Result<Json<Collection>> {
let mut collection = state
.drivers
.collections
.read_collection(&collection_id)
.await?
.ok_or(Error::NotFound)?;
collection.links.insert_or_update(&[
Link::new(&url, SELF),
Link::new(url.join("..")?, ROOT).mediatype(JSON),
]);
#[cfg(not(feature = "stac"))]
collection.links.insert_or_update(&[Link::new(
&url.join(&format!("{}/items", collection.id))?,
ITEMS,
)
.mediatype(GEO_JSON)]);
#[cfg(feature = "stac")]
if collection.r#type == "Collection" {
collection.links.insert_or_update(&[Link::new(
url.join(&format!("{}/items", collection.id))?,
ITEMS,
)
.mediatype(GEO_JSON)]);
}
collection.links.resolve_relative_links();
Ok(Json(collection))
}
async fn update(
State(state): State<AppState>,
Path(collection_id): Path<String>,
Json(mut collection): Json<Collection>,
) -> Result<StatusCode> {
collection.id = collection_id;
state
.drivers
.collections
.update_collection(&collection)
.await?;
Ok(StatusCode::NO_CONTENT)
}
async fn remove(
Path(collection_id): Path<String>,
State(state): State<AppState>,
) -> Result<StatusCode> {
state
.drivers
.collections
.delete_collection(&collection_id)
.await?;
Ok(StatusCode::NO_CONTENT)
}
async fn collections(
Qs(query): Qs<Query>,
RemoteUrl(url): RemoteUrl,
State(state): State<AppState>,
) -> Result<Json<Collections>> {
let mut collections = state.drivers.collections.list_collections(&query).await?;
for collection in collections.collections.iter_mut() {
collection.links.insert_or_update(&[
Link::new(url.join(&format!("collections/{}", collection.id))?, SELF).mediatype(JSON),
Link::new(url.join(".")?, ROOT).mediatype(JSON),
Link::new(
url.join(&format!("collections/{}/items", collection.id))?,
ITEMS,
)
.mediatype(GEO_JSON),
]);
collection.links.resolve_relative_links()
}
collections.links = vec![
Link::new(&url, SELF).mediatype(JSON).title("this document"),
Link::new(url.join(".")?, ROOT).mediatype(JSON),
];
collections.crs = vec![Crs::default(), Crs::from_epsg(3857)];
Ok(Json(collections))
}
pub(crate) fn router(state: &AppState) -> Router<AppState> {
let mut root = state.root.write().unwrap();
root.links.push(
Link::new("collections", DATA)
.title("Metadata about the resource collections")
.mediatype(JSON),
);
state.conformance.write().unwrap().extend(&CONFORMANCE);
Router::new()
.route("/collections", get(collections).post(create))
.route(
"/collections/{collection_id}",
get(read).put(update).delete(remove),
)
}