Skip to content

Commit

Permalink
feat: Assemble match info response (Part II: Teams & Matchinfo) (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonsan committed Jan 23, 2022
1 parent fadf5d1 commit 29a5d01
Show file tree
Hide file tree
Showing 12 changed files with 621 additions and 201 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ thiserror = "1.0.24"
# Logging
log = "0.4.14"
simple-log = "1.0.1"
tracing = "0.1.25"
tracing-subscriber = "0.2.16"
tracing-tree = "0.1.8"

# CLI
structopt = "0.3.21"
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# transparencies-backend-rs
# transparencies-backend-rs [![AGPLv3+](https://www.gnu.org/graphics/agplv3-88x31.png)](https://www.gnu.org/licenses/agpl.txt)

## A backend for dynamic stream overlays written in Rust


## License
**GNU AGPLv3** or later; see [LICENSE](LICENSE).
25 changes: 13 additions & 12 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

### Additions
- [X] Call from `handler` into `data_processing` with `MatchInfoRequest` data
- [ ] GET API data from aoe2net
- [ ] make all requests that are needed for getting all valuable information for matchinfo
- [ ] Add a language parameter to call from the frontend to use translations
- [ ] GET and CACHE (in-memory DB, `Arc<Mutex<T>>`) commonly used translations (ENG, ESP, GER, ITA, FRA, POR) at system startup and
- [X] GET API data from aoe2net
- [X] make all requests that are needed for getting all valuable information for matchinfo
- [X] Add a language parameter to call from the frontend to use translations
- [X] GET and CACHE (in-memory DB, `Arc<Mutex<T>>`) commonly used translations (ENG, ESP, GER, ITA, FRA, POR) at system startup and
let them be updated every now and then
- spawn another thread for this task and don't use the github one (client encapsulation, easier debugging)
- don't use static types for this, to be less error prone if AoE2net updates something at these endpoints, we don't want to have
Expand All @@ -20,24 +20,26 @@
- [x] periodically:
- [x] at the start of the server
- [x] once every 10 minutes
- [ ] **WIP** Merge various data sources into a `MatchInfo` datastructure for giving back to client
- [X] Merge various data sources into a `MatchInfo` datastructure for giving back to client
- [x] await json from polska for new matchinfo DS for merging/exposing to our frontend
- [x] Q: What's the best way in Rust to automatically map Datastructures

### Error Handling
- [ ] **WIP** Implement good error handling
- [ ] **WIP** use crates error types for better `Error handling` e.g. `reqwest::Error`
- [X] use crates error types for better `Error handling` e.g. `reqwest::Error`
- [ ] use `claim` for better error reports
- [ ] **WIP** use `thiserror` in library part
- [X] use `thiserror` in library part
- [ ] use `eyre` consistently for results with reports in binary part (?)
- [ ] use `.map_err` and return HTTP status codes
- [ ] implement `todo!()`s
- [ ] don't overwrite `aoc_ref_data` if not able to parse it in thread, so we have at least one working version
- [ ] Send `log entry` to Client for better error handling on client-side
- [ ] Special cases done right?
- [ ] Data structure does not match with data from aoe2net
- [ ] Q: take a look for a `serde` attribute to mark fields in structs that are not as important for our processing,
- [X] Data structure does not match with data from aoe2net
- [X] Q: take a look for a `serde` attribute to mark fields in structs that are not as important for our processing,
so we don't throw a parsing error if non-essential fields don't match/exist
- **A:** We only parse `Players` of `last_match` into some losely-typed datastructure for easier handling,
the rest is `serde_json::Value` and parsing on the run
- [ ] New players without ranking
- [ ] Deranked players
- [ ] Coop games
Expand All @@ -55,9 +57,8 @@
- [ ] what (other) architectural changes need to be made to support many clients on our api(?)
- [ ] async stuff done right?
- [ ] use <https://docs.rs/reqwest/0.11.0/reqwest/struct.Url.html#method.join> for `base_path` and joining files for DS: `reqwest::Url`
- [ ] structured logging: use `tracing` crate in addition to `log` and refactor accordingly
__Note:__ already partly done
- [ ] use [tracing-tree](https://github.com/transparencies/tracing-tree) for structured summaries of tracing
- [X] structured logging: use `tracing` crate in addition to `log` and refactor accordingly
- [X] use [tracing-tree](https://github.com/transparencies/tracing-tree) for structured summaries of tracing

### Documentation
- [ ] **WIP** create good documentation (!!!)
Expand Down
6 changes: 4 additions & 2 deletions rustfmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ combine_control_expr = false
format_code_in_doc_comments = true
format_macro_matchers = true
hard_tabs = false
#imports_granularity = "Crate"
merge_imports = true
# Nightly
imports_granularity = "Crate"
# Stable
# merge_imports = true
normalize_comments = true
wrap_comments = true
normalize_doc_attributes = true
Expand Down
27 changes: 19 additions & 8 deletions src/bin/transparencies-server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Executable for managing aoe-reference-data files.
//! Backend for dynamic stream overlays
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
Expand All @@ -17,13 +17,7 @@ extern crate transparencies_backend_rs;

use eyre::Error;
use human_panic::setup_panic;
use log::{
debug,
error,
info,
trace,
warn,
};
use log::error;
use simple_log::LogConfigBuilder;
use std::{
env,
Expand Down Expand Up @@ -74,6 +68,18 @@ use transparencies_backend_rs::{
},
};

use tracing::{
debug,
info,
trace,
warn,
};
use tracing_subscriber::{
prelude::*,
Registry,
};
use tracing_tree::HierarchicalLayer;

#[tokio::main]
async fn main() {
// Install the panic and error report handlers
Expand All @@ -84,9 +90,14 @@ async fn main() {
if env::var_os("RUST_LOG").is_none() {
// TODO Deactivate Debug logs
env::set_var("RUST_LOG", "transparencies=debug");
env::set_var("RUST_BACKTRACE", "1");
// Access logs
// env::set_var("RUST_LOG", "transparencies=info");
}
// install global collector configured based on RUST_LOG env var.
// tracing_subscriber::fmt::init();
let subscriber = Registry::default().with(HierarchicalLayer::new(2));
tracing::subscriber::set_global_default(subscriber).unwrap();

// Human Panic. Only enabled when *not* debugging.
#[cfg(not(debug_assertions))]
Expand Down
18 changes: 16 additions & 2 deletions src/domain/data_processing/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ pub enum ProcessingError {
#[error("Responder experienced an error.")]
ResponderMalfunction(#[from] ResponderError),
#[error("Parsing of Integer data failed")]
IntParsingError(#[from] ParseIntError),
ProcessIntParsingError(#[from] ParseIntError),
#[error("Conversion to String failed.")]
SerdeStringConversionError(#[from] serde_json::Error),
#[error("Dividing by Zero is not allowed.")]
DividingByZeroError,
#[error("Haven't found a rating for player id: {0}")]
LookupRatingNotFound(i64),
#[error("Haven't found a leaderboard value for player id: {0}")]
LeaderboardNotFound(i64),
#[error("Haven't found a translation for {0}: {1}")]
TranslationError(String, usize),
}

#[derive(Error, Debug)]
Expand All @@ -32,7 +40,13 @@ pub enum ResponderError {
#[error("HTTP-Client experienced an error.")]
HttpClientError(#[from] reqwest::Error),
#[error("Parsing of Integer data failed")]
IntParsingError(#[from] ParseIntError),
RespondIntParsingError(#[from] ParseIntError),
#[error("Haven't found a translation for {0}: {1}")]
TranslationError(String, usize),
#[error("Couldn't get the value of the translation string: {0} at given index {1}")]
TranslationPosError(String, usize),
#[error("Couldn't get the value of the translation string, because it has already been moved.")]
TranslationHasBeenMovedError,
}

#[derive(Error, Debug)]
Expand Down
117 changes: 107 additions & 10 deletions src/domain/data_processing/match_data_responder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ use crate::domain::types::{
InMemoryDb,
MatchDataResponses,
};
use log::{
debug,
error,
info,
trace,
warn,
};
use aoe2net::Aoe2netStringObj;
use ron::ser::{
to_writer_pretty,
PrettyConfig,
Expand All @@ -35,6 +29,12 @@ use std::{
time::Duration,
};
use tokio::sync::Mutex;
use tracing::{
debug,
info,
trace,
warn,
};

use super::error::ResponderError;

Expand Down Expand Up @@ -82,10 +82,102 @@ impl MatchDataResponses {
)
}

pub fn get_rating_type(&self) -> Result<String> {
pub fn get_rating_type_id(&self) -> Result<usize> {
self.aoe2net.player_last_match.as_ref().map_or_else(
|| Err(ResponderError::NotFound("rating type".to_string())),
|val| Ok(val["last_match"]["rating_type"].to_string()),
|val| {
Ok(val["last_match"]["rating_type"]
.to_string()
.parse::<usize>()?)
},
)
}

pub fn get_translation_for_language(&mut self) -> Result<Value> {
let mut translation: Option<serde_json::Value> = None;

trace!(
"Length of self.db.aoe2net_languages is: {:?}",
self.db.aoe2net_languages.len()
);

if self.db.aoe2net_languages.len() == 1 {
for (language, translation_value) in
self.db.aoe2net_languages.clone().drain().take(1)
{
translation = Some(translation_value);
trace!("Translation that was used: {:?}", language);
}
}
else {
return Err(ResponderError::TranslationHasBeenMovedError);
}
Ok(translation.expect("Translation should never be None value."))
}

pub fn get_translated_string_from_id(
&self,
first: &str,
id: usize,
) -> Result<String> {
trace!("Getting translated string in {:?} with id: {:?}", first, id);
let language =
if let Ok(lang) = self.clone().get_translation_for_language() {
lang
}
else {
return Err(ResponderError::NotFound(
"translation file".to_string(),
));
};

let translated_vec = serde_json::from_str::<Vec<Aoe2netStringObj>>(
&serde_json::to_string(&language[first]).unwrap_or_else(|_| {
panic!(format!(
"Conversion of language[{:?}] to string failed.",
first.to_string(),
))
}),
)
.expect("Conversion from translated string failed.");

let mut translated_string: Option<String> = None;

for obj_string in &translated_vec {
if *obj_string.id() == id {
translated_string = Some(obj_string.string().to_string())
}
}

if translated_string.is_none() {
return Err(ResponderError::TranslationPosError(
format!("[{:?}]", first.to_string(),),
id,
));
}

Ok(translated_string.unwrap())
}

pub fn get_map_type_id(&self) -> Result<usize> {
self.aoe2net.player_last_match.as_ref().map_or_else(
|| Err(ResponderError::NotFound("map type".to_string())),
|val| {
Ok(val["last_match"]["map_type"]
.to_string()
.parse::<usize>()?)
},
)
}

pub fn get_game_type_id(&self) -> Result<usize> {
self.aoe2net.player_last_match.as_ref().map_or_else(
|| Err(ResponderError::NotFound("game type".to_string())),
|val| {
Ok(val["last_match"]["game_type"]
.to_string()
.parse::<usize>()?)
},
)
}

Expand Down Expand Up @@ -163,6 +255,11 @@ impl MatchDataResponses {
let mut language: String = STANDARD_LANGUAGE.to_string();
let mut game: String = STANDARD_GAME.to_string();

let mut db_cloned: InMemoryDb;
{
// Just clone and drop the lock
db_cloned = in_memory_db.lock().await.clone();
}
// Set `language` to Query value if specified
if let Some(lang) = par.language {
language = lang;
Expand All @@ -175,7 +272,7 @@ impl MatchDataResponses {

// Include github response
let mut responses = MatchDataResponses {
db: in_memory_db.lock().await.with_language(&language),
db: db_cloned.retain_language(&language),
..Default::default()
};

Expand Down
Loading

0 comments on commit 29a5d01

Please sign in to comment.