Skip to content

Commit

Permalink
WIP: Added new endpoint /catalogue to return catalogue defined in CAT…
Browse files Browse the repository at this point in the history
…ALOGUE_URL env var, extended with counts.
  • Loading branch information
enola-dkfz committed Apr 3, 2024
1 parent 68d2b84 commit 23d0055
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 6 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "spot"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0"
documentation = "https://github.com/samply/spot"
Expand All @@ -20,7 +20,7 @@ once_cell = "1"
# Logging
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
reqwest = { version = "0.11.20", default-features = false, features = ["stream"] }
reqwest = { version = "0.11.20", default-features = false, features = ["stream", "default-tls"] }
tower-http = { version = "0.4.4", features = ["cors"] }

[build-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/banner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) fn print_banner() {
_ => "SNAPSHOT",
};
info!(
"🌈 Samply.Spot v{} (built {} {}, {}) starting up ...",
"🌈 Samply.Spot v{} (built {} {}, {}) ready to take requests.",
env!("CARGO_PKG_VERSION"),
env!("BUILD_DATE"),
env!("BUILD_TIME"),
Expand Down
100 changes: 100 additions & 0 deletions src/catalogue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use reqwest::Url;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tracing::{debug, info};

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
enum ChildCategoryType {
Equals,
SomethingElse
}

#[derive(Serialize, Deserialize)]
struct ChildCategoryCriterion {
key: String,
count: Option<usize>
}

#[derive(Serialize, Deserialize)]
struct ChildCategory {
key: String,
type_: ChildCategoryType,
criteria: Vec<ChildCategoryCriterion>
}

pub async fn get_extended_json(catalogue_url: Url) -> Value {
debug!("Fetching catalogue from {catalogue_url} ...");
let resp = reqwest::get(catalogue_url).await
.expect("Unable to fetch catalogue from upstream; please check URL specified in config.");

let mut json: Value = resp.json().await
.expect("Unable to parse catalogue from upstream; please check URL specified in config.");

// TODO: Query prism for counts here.

recurse(&mut json);

// println!("{}", serde_json::to_string_pretty(&json).unwrap());

info!("Catalogue built successfully.");

json
}

/// Key order: group key (e.g. patient)
/// \-- stratifier key (e.g. admin_gender)
/// \-- stratum key (e.g. male, other)
fn recurse(json: &mut Value) {
match json {
Value::Null => (),
Value::Bool(_) => (),
Value::Number(_) => (),
Value::String(_) => (),
Value::Array(arr) => {
for ele in arr {
recurse(ele);
}
},
Value::Object(obj) => {
if ! obj.contains_key("childCategories") {
for (_key, child_val) in obj.iter_mut() {
recurse(child_val);
}
} else {
let group_key = obj.get("key").expect("Got JSON element with childCategories but without (group) key. Please check json.");

let children_cats = obj
.get_mut("childCategories")
.unwrap()
.as_array_mut()
.unwrap()
.iter_mut()
.filter(|item| item.get("type").unwrap_or(&Value::Null) == "EQUALS");

for child_cat in children_cats {
let stratifier_key = child_cat.get("key").expect("Got JSON element with childCategory that does not contain a (stratifier) key. Please check json.");
let criteria = child_cat
.get_mut("criteria")
.expect("Got JSON element with childCategory that does not contain a criteria array. Please check json.")
.as_array_mut()
.expect("Got JSON element with childCategory with criteria that are not an array. Please check json.");

for criterion in criteria {
let criterion = criterion.as_object_mut()
.expect("Got JSON where a criterion was not an object. Please check json.");
let stratum_key = criterion.get("key")
.expect("Got JSON where a criterion did not have a key. Please check json.")
.as_str()
.expect("Got JSON where a criterion key was not a string. Please check json.");

// fetch from Prism output
let count_from_prism = 10;

criterion.insert("count".into(), json!(count_from_prism));
}
}
}
},
}
}
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub struct Config {
/// The socket address this server will bind to
#[clap(long, env, default_value = "0.0.0.0:8080")]
pub bind_addr: SocketAddr,

/// URL to catalogue.json file
#[clap(long, env)]
pub catalogue_url: Url
}

fn parse_cors(v: &str) -> Result<AllowOrigin, InvalidHeaderValue> {
Expand Down
23 changes: 20 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use axum::{
extract::{Json, Path, Query},
extract::{Json, Path, Query, State},
http::HeaderValue,
response::{IntoResponse, Response},
routing::{get, post},
Expand All @@ -12,12 +12,14 @@ use config::Config;
use once_cell::sync::Lazy;
use reqwest::{header, Method, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tower_http::cors::CorsLayer;
use tracing::{info, warn, Level};
use tracing_subscriber::{EnvFilter, util::SubscriberInitExt};

mod banner;
mod beam;
mod catalogue;
mod config;

static CONFIG: Lazy<Config> = Lazy::new(Config::parse);
Expand All @@ -30,6 +32,11 @@ static BEAM_CLIENT: Lazy<BeamClient> = Lazy::new(|| {
)
});

#[derive(Clone)]
struct SharedState {
extended_json: Value
}

#[tokio::main]
async fn main() {
tracing_subscriber::FmtSubscriber::builder()
Expand All @@ -40,9 +47,9 @@ async fn main() {

info!("{:#?}", Lazy::force(&CONFIG));


let extended_json = catalogue::get_extended_json(CONFIG.catalogue_url.clone()).await;
let state = SharedState { extended_json };

banner::print_banner();
// TODO: Add check for reachability of beam-proxy

let cors = CorsLayer::new()
Expand All @@ -53,9 +60,13 @@ async fn main() {
let app = Router::new()
.route("/beam", post(handle_create_beam_task))
.route("/beam/:task_id", get(handle_listen_to_beam_tasks))
.route("/catalogue", get(handle_get_catalogue))
.with_state(state)
.layer(axum::middleware::map_response(banner::set_server_header))
.layer(cors);

banner::print_banner();

axum::Server::bind(&CONFIG.bind_addr)
.serve(app.into_make_service())
.await
Expand Down Expand Up @@ -133,3 +144,9 @@ fn convert_response(response: reqwest::Response) -> axum::response::Response {
.unwrap()
.into_response()
}

async fn handle_get_catalogue(
State(state): State<SharedState>
) -> Json<Value> {
Json(state.extended_json)
}

0 comments on commit 23d0055

Please sign in to comment.