Lines
91.82 %
Functions
66.67 %
mod dataset;
mod fetch;
use self::{
dataset::{load_datasets, Dataset, DatasetVersion},
fetch::fetch_all,
};
use crate::{server_config::ServerConfig, server_error::MyError};
use axum::{
extract::{Path, State},
response::Html,
routing::get,
Router,
use chrono::{DateTime, Utc};
use serde::Serialize;
use std::{
error::Error,
path::PathBuf,
sync::{Arc, RwLock},
use tera::{Context, Tera};
#[derive(Serialize)]
struct DatasetSummary<'a> {
id: &'a str,
latest_version: Option<DateTime<Utc>>,
number_of_versions: usize,
}
impl<'a> From<&'a Dataset> for DatasetSummary<'a> {
fn from(dataset: &'a Dataset) -> Self {
DatasetSummary {
id: &dataset.id,
latest_version: dataset.latest_version(),
number_of_versions: dataset.versions.len(),
async fn route_get_index(State(state): State<SharedState>) -> Result<Html<String>, MyError> {
let state = &mut state.read().unwrap();
let mut context = Context::new();
let datasets: Vec<_> = state.datasets.iter().map(DatasetSummary::from).collect();
context.insert("datasets", &datasets);
let html = state.tera.render("index.html", &context)?;
Ok(Html(html))
struct DatasetVersionSummary {
date: DateTime<Utc>,
number_of_quads: usize,
impl<'a> From<&'a DatasetVersion> for DatasetVersionSummary {
fn from(version: &'a DatasetVersion) -> Self {
DatasetVersionSummary {
date: version.date,
number_of_quads: version.number_of_quads(),
fn get_dataset<'a>(datasets: &'a [Dataset], id: &str) -> Result<&'a Dataset, MyError> {
if let Some(dataset) = datasets.iter().find(|d| d.id == id) {
Ok(dataset)
} else {
Err(MyError::NotFound(format!("No dataset with id {}", id)))
fn get_dataset_version<'a>(
datasets: &'a [Dataset],
id: &str,
) -> Result<(&'a Dataset, &'a DatasetVersion), MyError> {
let dataset = get_dataset(datasets, id)?;
if let Some(version) = dataset.versions.iter().find(|v| v.date == date) {
Ok((dataset, version))
Err(MyError::NotFound(format!(
"No dataset with id {} and version {}",
id, date
)))
async fn route_get_dataset(
State(state): State<SharedState>,
Path(id): Path<String>,
) -> Result<Html<String>, MyError> {
let dataset = get_dataset(&state.datasets, &id)?;
let summary = DatasetSummary::from(dataset);
context.insert("dataset", &summary);
let versions: Vec<_> = dataset
.versions
.iter()
.map(DatasetVersionSummary::from)
.collect();
context.insert("versions", &versions);
Ok(Html(state.tera.render("dataset.html", &context)?))
async fn route_get_dataset_version(
Path((id, version)): Path<(String, DateTime<Utc>)>,
let (dataset, version) = get_dataset_version(&state.datasets, &id, version)?;
let subjects = version.subjects();
let dataset = DatasetSummary::from(dataset);
let version = DatasetVersionSummary::from(version);
context.insert("dataset", &dataset);
context.insert("version", &version);
context.insert("subjects", &subjects);
Ok(Html(state.tera.render("version.html", &context)?))
async fn route_get_dataset_version_subject(
Path((id, version, subject)): Path<(String, DateTime<Utc>, String)>,
let pos = version.pos(&subject);
context.insert("subject", &subject);
context.insert("pos", &pos);
Ok(Html(state.tera.render("subject.html", &context)?))
async fn route_get_crawl(State(state): State<SharedState>) -> Result<Html<String>, MyError> {
let dataset_configs: Vec<_>;
{
dataset_configs = state.config.datasets.clone();
fetch_all(dataset_configs, &PathBuf::from("datasets")).await?;
Ok(Html("Ok".into()))
type SharedState = Arc<RwLock<AppState>>;
struct AppState {
config: ServerConfig,
datasets: Vec<Dataset>,
tera: Tera,
fn init_dataset(config: &ServerConfig) -> Result<(), Box<dyn Error>> {
let dir = PathBuf::from("datasets");
for c in &config.datasets {
std::fs::create_dir_all(dir.join(&c.name))?;
Ok(())
pub fn create_app(
) -> Result<axum::routing::IntoMakeService<Router>, Box<dyn Error>> {
init_dataset(&config)?;
let tera = Tera::new("templates/**/[a-zA-Z0-9]*")?;
let datasets = load_datasets("datasets")?;
let shared_state = Arc::new(RwLock::new(AppState {
datasets,
tera,
config,
}));
Ok(Router::new()
.route("/", get(route_get_index))
.route("/dataset/", get(route_get_index))
.route("/dataset/:id", get(route_get_dataset))
.route("/dataset/:id/:version", get(route_get_dataset_version))
.route(
"/dataset/:id/:version/:subject",
get(route_get_dataset_version_subject),
)
.route("/crawl", get(route_get_crawl))
.with_state(Arc::clone(&shared_state))
.into_make_service())
#[cfg(test)]
mod test {
use crate::server_config::ServerConfig;
use axum_test::TestServer;
async fn create_server() -> TestServer {
let my_app = super::create_app(ServerConfig { datasets: vec![] }).unwrap();
let server = TestServer::new(my_app).unwrap();
server
#[tokio::test]
async fn route_root() {
let server = create_server().await;
let response = server.get("/").await;
assert_eq!(response.status_code(), 200);
async fn route_dataset() {
let response = server.get("/dataset/").await;
async fn route_dataset_a() {
let response = server.get("/dataset/a").await;
async fn route_dataset_a_date() {
let response = server.get("/dataset/a/2016-12-20T00:39:57Z").await;
async fn route_dataset_a_date_subject() {
let response = server.get("/dataset/a/2016-12-20T00:39:57Z/http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23").await;
async fn route_dataset_b() {
let response = server.get("/dataset/b").await;
assert_eq!(response.status_code(), 404);