Lines
0 %
Functions
use axum::{
extract::{Query, State},
response::{Html, IntoResponse},
routing::get,
Json, Router,
};
use http::{Method, StatusCode};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
error::Error,
fmt::Display,
path::PathBuf,
sync::{Arc, RwLock},
use tokio::fs::read_to_string;
use tower_http::{
cors::{Any, CorsLayer},
services::{ServeDir, ServeFile},
use url::Url;
#[derive(Serialize, Deserialize, Clone)]
pub struct AnnotationServerConfig {
assets_dir: PathBuf,
sparql_endpoint: String,
sparql_user: String,
sparql_password: String,
template_url: String,
query_configs: BTreeMap<String, QueryConfig>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct QueryConfig {
pub url_pattern: String,
pub xpath: String,
pub text_pattern: String,
pub query_file: String,
pub template_file: String,
type SharedState = Arc<RwLock<AppState>>;
struct AppState {
config: AnnotationServerConfig,
#[derive(Debug)]
pub struct MyError {
msg: String,
impl Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.msg)
impl Error for MyError {}
impl IntoResponse for MyError {
fn into_response(self) -> axum::response::Response {
self.msg.into_response()
async fn route_get_index() -> Result<Html<String>, MyError> {
Ok(Html(String::new()))
async fn route_get_configuration(
State(state): State<SharedState>,
) -> Result<Json<AnnotationServerConfig>, MyError> {
let state = &mut state.read().unwrap();
Ok(Json(state.config.clone()))
#[derive(Deserialize)]
struct QueryQuery {
query: String,
entity: String,
async fn run_sparql(
config: &AnnotationServerConfig,
query_config: &QueryConfig,
entity: &str,
) -> Result<String, MyError> {
let query_path = config.assets_dir.join(&query_config.query_file);
println!("reading {}", query_path.display());
let query = read_to_string(&query_path).await.map_err(|_| MyError {
msg: format!("could not read {}", query_path.display()),
})?;
let query = query.replace("%%ENTITY%%", entity);
println!("querying: {}", config.sparql_endpoint);
println!("{}", query);
let mut url = Url::parse(&config.sparql_endpoint).unwrap();
url.query_pairs_mut().append_pair("query", &query);
let client = reqwest::Client::new();
let res = client
.get(url)
.basic_auth(&config.sparql_user, Some(&config.sparql_password))
.send()
.await
.unwrap();
let status = res.status();
let text = res.text().await.unwrap();
if status != StatusCode::OK {
return Err(MyError { msg: text });
println!("sparql result: {}", status);
println!("sparql result: {}", text);
Ok(text)
async fn run_template(
sparql_output: String,
let template_path = config.assets_dir.join(&query_config.template_file);
let template = read_to_string(&template_path).await.map_err(|_| MyError {
msg: format!("could not read {}", template_path.display()),
println!("template {}", template);
.post(&config.template_url)
.form(&[("data", sparql_output), ("query", template)])
async fn route_get_query(
Query(query): Query<QueryQuery>,
) -> Result<Html<String>, MyError> {
let config: AnnotationServerConfig = {
let state = state.read().unwrap();
state.config.clone()
let query_config = if let Some(query_config) = config.query_configs.get(&query.query) {
query_config
} else {
return Err(MyError {
msg: format!("{} is not known", query.query),
});
println!("{} {} {:?}", query.query, query.entity, query_config);
let sparql_output = run_sparql(&config, query_config, &query.entity).await?;
let template_output = run_template(&config, query_config, sparql_output).await?;
Ok(Html(template_output))
pub fn create_app(
) -> Result<axum::routing::IntoMakeService<Router>, Box<dyn Error>> {
let assets_dir = &config.assets_dir;
let serve_dir =
ServeDir::new(assets_dir).not_found_service(ServeFile::new(assets_dir.join("404.html")));
let cors = CorsLayer::new()
// allow `GET` and `POST` when accessing the resource
.allow_methods([Method::GET, Method::POST])
// allow requests from any origin
.allow_origin(Any);
let shared_state = Arc::new(RwLock::new(AppState { config }));
Ok(Router::new()
.route("/", get(route_get_index))
.route("/query", get(route_get_query))
.route("/configuration.json", get(route_get_configuration))
.nest_service("/assets", serve_dir)
.layer(cors)
.with_state(Arc::clone(&shared_state))
.into_make_service())