extern crate serde_json;
use chrono::*;
use serde_json::Value;
use std::fmt;
use std::str::FromStr;
use crate::error::Error;
use crate::util::{decode_array, into_map, remove};
#[derive(Debug, Clone)]
pub enum TournamentIncludes {
All,
Matches,
Participants,
}
#[derive(Debug, Clone)]
pub enum RankedBy {
MatchWins,
GameWins,
PointsScored,
PointsDifference,
Custom,
}
impl fmt::Display for RankedBy {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
RankedBy::MatchWins => {
fmt.write_str("match wins")?;
}
RankedBy::GameWins => {
fmt.write_str("game wins")?;
}
RankedBy::PointsScored => {
fmt.write_str("points scored")?;
}
RankedBy::PointsDifference => {
fmt.write_str("points difference")?;
}
RankedBy::Custom => {
fmt.write_str("custom")?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TournamentId {
Url(String, String),
Id(u64),
}
impl fmt::Display for TournamentId {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
TournamentId::Url(ref subdomain, ref tournament_url) => {
if subdomain.is_empty() {
fmt.write_str(tournament_url)?;
} else {
fmt.write_str(&format!("{}-{}", subdomain, tournament_url))?;
}
}
TournamentId::Id(ref id) => {
fmt.write_str(&id.to_string())?;
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GamePoints {
pub match_win: f64,
pub match_tie: f64,
pub game_win: f64,
pub game_tie: f64,
pub bye: Option<f64>,
}
impl GamePoints {
pub fn new(
match_win: f64,
match_tie: f64,
game_win: f64,
game_tie: f64,
bye: Option<f64>,
) -> GamePoints {
GamePoints {
match_win,
match_tie,
game_win,
game_tie,
bye,
}
}
pub fn decode(
mut map: &mut serde_json::Map<String, Value>,
prefix: &str,
) -> Result<GamePoints, Error> {
let mut bye = None;
if let Ok(bye_pts) = remove(&mut map, &format!("{}pts_for_bye", prefix)) {
if let Ok(b) = bye_pts.as_str().unwrap_or("").to_owned().parse::<f64>() {
bye = Some(b);
}
}
Ok(GamePoints {
match_win: remove(&mut map, &format!("{}pts_for_match_win", prefix))?
.as_str()
.unwrap_or("")
.to_owned()
.parse::<f64>()
.unwrap_or(0f64),
match_tie: remove(&mut map, &format!("{}pts_for_match_tie", prefix))?
.as_str()
.unwrap_or("")
.to_owned()
.parse::<f64>()
.unwrap_or(0f64),
game_win: remove(&mut map, &format!("{}pts_for_game_win", prefix))?
.as_str()
.unwrap_or("")
.to_owned()
.parse::<f64>()
.unwrap_or(0f64),
game_tie: remove(&mut map, &format!("{}pts_for_game_tie", prefix))?
.as_str()
.unwrap_or("")
.to_owned()
.parse::<f64>()
.unwrap_or(0f64),
bye,
})
}
}
impl Default for GamePoints {
fn default() -> GamePoints {
GamePoints {
match_win: 0.5f64,
match_tie: 1.0f64,
game_win: 0.0f64,
game_tie: 0.0f64,
bye: None,
}
}
}
#[derive(Debug, Clone)]
pub struct TournamentCreate {
pub name: String,
pub tournament_type: TournamentType,
pub url: String,
pub subdomain: String,
pub description: String,
pub open_signup: bool,
pub hold_third_place_match: bool,
pub swiss_points: GamePoints,
pub swiss_rounds: u64,
pub ranked_by: RankedBy,
pub round_robin_points: GamePoints,
pub show_rounds: bool,
pub private: bool,
pub game_name: Option<String>,
pub notify_users_when_matches_open: bool,
pub notify_users_when_the_tournament_ends: bool,
pub sequential_pairings: bool,
pub signup_cap: u64,
pub start_at: Option<DateTime<Utc>>,
pub check_in_duration: u64,
pub grand_finals_modifier: Option<String>,
}
impl TournamentCreate {
pub fn new() -> TournamentCreate {
TournamentCreate {
name: String::default(),
tournament_type: TournamentType::SingleElimination,
url: String::default(),
subdomain: String::default(),
description: String::default(),
open_signup: false,
hold_third_place_match: false,
swiss_points: GamePoints::new(0.5f64, 1.0f64, 0.0f64, 0.0f64, Some(0.0f64)),
swiss_rounds: 0,
ranked_by: RankedBy::PointsScored,
round_robin_points: GamePoints::default(),
show_rounds: false,
private: false,
game_name: None,
notify_users_when_matches_open: true,
notify_users_when_the_tournament_ends: true,
sequential_pairings: false,
signup_cap: 4,
start_at: None,
check_in_duration: 60,
grand_finals_modifier: None,
}
}
builder_s!(name);
builder!(tournament_type, TournamentType);
builder_s!(url);
builder_s!(subdomain);
builder_s!(description);
builder!(open_signup, bool);
builder!(hold_third_place_match, bool);
builder!(swiss_points, GamePoints);
builder!(swiss_rounds, u64);
builder!(ranked_by, RankedBy);
builder!(round_robin_points, GamePoints);
builder!(show_rounds, bool);
builder!(private, bool);
builder_so!(game_name);
builder!(notify_users_when_matches_open, bool);
builder!(notify_users_when_the_tournament_ends, bool);
builder!(sequential_pairings, bool);
builder!(signup_cap, u64);
builder!(check_in_duration, u64);
builder!(grand_finals_modifier, Option<String>);
}
impl Default for TournamentCreate {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Tournament {
pub accept_attachments: bool,
pub allow_participant_match_reporting: bool,
pub anonymous_voting: bool,
pub created_at: DateTime<FixedOffset>,
pub created_by_api: bool,
pub credit_capped: bool,
pub description: String,
pub game_id: u64,
pub group_stages_enabled: bool,
pub hide_forum: bool,
pub hide_seeds: bool,
pub hold_third_place_match: bool,
pub id: TournamentId,
pub max_predictions_per_user: u64,
pub name: String,
pub notify_users_when_matches_open: bool,
pub notify_users_when_the_tournament_ends: bool,
pub open_signup: bool,
pub participants_count: u64,
pub prediction_method: u64,
pub private: bool,
pub progress_meter: u64,
pub swiss_points: GamePoints,
pub quick_advance: bool,
pub require_score_agreement: bool,
pub round_robin_points: GamePoints,
pub sequential_pairings: bool,
pub show_rounds: bool,
pub started_at: Option<DateTime<FixedOffset>>, pub swiss_rounds: u64,
pub teams: bool,
pub tournament_type: TournamentType,
pub updated_at: DateTime<FixedOffset>,
pub url: String,
pub description_source: String,
pub full_challonge_url: String,
pub live_image_url: String,
pub review_before_finalizing: bool,
pub accepting_predictions: bool,
pub participants_locked: bool,
pub game_name: String,
pub participants_swappable: bool,
pub team_convertable: bool,
pub group_stages_were_started: bool,
}
impl Tournament {
pub fn decode(value: Value) -> Result<Tournament, Error> {
let mut value = into_map(value)?;
let t = remove(&mut value, "tournament")?;
let mut tv = into_map(t)?;
let mut started_at = None;
if let Some(dt_str) = remove(&mut tv, "started_at")?.as_str() {
if let Ok(dt) = DateTime::parse_from_rfc3339(dt_str) {
started_at = Some(dt);
}
}
Ok(Tournament {
accept_attachments: remove(&mut tv, "accept_attachments")?
.as_bool()
.unwrap_or(false),
allow_participant_match_reporting: remove(
&mut tv,
"allow_participant_match_reporting",
)?
.as_bool()
.unwrap_or(false),
anonymous_voting: remove(&mut tv, "anonymous_voting")?
.as_bool()
.unwrap_or(false),
created_at: DateTime::parse_from_rfc3339(
remove(&mut tv, "created_at")?.as_str().unwrap_or(""),
)
.unwrap(),
created_by_api: remove(&mut tv, "created_by_api")?
.as_bool()
.unwrap_or(false),
credit_capped: remove(&mut tv, "credit_capped")?.as_bool().unwrap_or(false),
description: remove(&mut tv, "description")?
.as_str()
.unwrap_or("")
.to_string(),
game_id: remove(&mut tv, "game_id")?.as_u64().unwrap_or(0),
id: TournamentId::Id(remove(&mut tv, "id")?.as_u64().unwrap_or(0)),
name: remove(&mut tv, "name")?.as_str().unwrap_or("").to_string(),
group_stages_enabled: remove(&mut tv, "group_stages_enabled")?
.as_bool()
.unwrap_or(false),
hide_forum: remove(&mut tv, "hide_forum")?.as_bool().unwrap_or(false),
hide_seeds: remove(&mut tv, "hide_seeds")?.as_bool().unwrap_or(false),
hold_third_place_match: remove(&mut tv, "hold_third_place_match")?
.as_bool()
.unwrap_or(false),
max_predictions_per_user: remove(&mut tv, "max_predictions_per_user")?
.as_u64()
.unwrap_or(0),
notify_users_when_matches_open: remove(&mut tv, "notify_users_when_matches_open")?
.as_bool()
.unwrap_or(false),
notify_users_when_the_tournament_ends: remove(
&mut tv,
"notify_users_when_the_tournament_ends",
)?
.as_bool()
.unwrap_or(false),
open_signup: remove(&mut tv, "open_signup")?.as_bool().unwrap_or(false),
participants_count: remove(&mut tv, "participants_count")?.as_u64().unwrap_or(0),
prediction_method: remove(&mut tv, "prediction_method")?.as_u64().unwrap_or(0),
private: remove(&mut tv, "private")?.as_bool().unwrap_or(false),
progress_meter: remove(&mut tv, "progress_meter")?.as_u64().unwrap_or(0),
swiss_points: GamePoints::decode(&mut tv, "").unwrap(),
quick_advance: remove(&mut tv, "quick_advance")?.as_bool().unwrap_or(false),
require_score_agreement: remove(&mut tv, "require_score_agreement")?
.as_bool()
.unwrap_or(false),
round_robin_points: GamePoints::decode(&mut tv, "rr_").unwrap(),
sequential_pairings: remove(&mut tv, "sequential_pairings")?
.as_bool()
.unwrap_or(false),
show_rounds: remove(&mut tv, "show_rounds")?.as_bool().unwrap_or(false),
started_at,
swiss_rounds: remove(&mut tv, "swiss_rounds")?.as_u64().unwrap_or(0),
teams: remove(&mut tv, "teams")?.as_bool().unwrap_or(false),
tournament_type: TournamentType::from_str(
remove(&mut tv, "tournament_type")?.as_str().unwrap_or(""),
)
.unwrap_or(TournamentType::SingleElimination),
updated_at: DateTime::parse_from_rfc3339(
remove(&mut tv, "updated_at")?.as_str().unwrap(),
)
.unwrap(),
url: remove(&mut tv, "url")?.as_str().unwrap_or("").to_string(),
description_source: remove(&mut tv, "description_source")?
.as_str()
.unwrap_or("")
.to_string(),
full_challonge_url: remove(&mut tv, "full_challonge_url")?
.as_str()
.unwrap_or("")
.to_string(),
live_image_url: remove(&mut tv, "live_image_url")?
.as_str()
.unwrap_or("")
.to_string(),
review_before_finalizing: remove(&mut tv, "review_before_finalizing")?
.as_bool()
.unwrap_or(false),
accepting_predictions: remove(&mut tv, "accepting_predictions")?
.as_bool()
.unwrap_or(false),
participants_locked: remove(&mut tv, "participants_locked")?
.as_bool()
.unwrap_or(false),
game_name: remove(&mut tv, "game_name")?
.as_str()
.unwrap_or("")
.to_string(),
participants_swappable: remove(&mut tv, "participants_swappable")?
.as_bool()
.unwrap_or(false),
team_convertable: remove(&mut tv, "team_convertable")?
.as_bool()
.unwrap_or(false),
group_stages_were_started: remove(&mut tv, "group_stages_were_started")?
.as_bool()
.unwrap_or(false),
})
}
}
#[derive(Debug, Clone)]
pub struct Index(pub Vec<Tournament>);
impl Index {
pub fn decode(value: Value) -> Result<Index, Error> {
Ok(Index(decode_array(value, Tournament::decode)?))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TournamentType {
SingleElimination,
DoubleElimination,
RoundRobin,
Swiss,
}
impl TournamentType {
pub fn to_get_param<'a>(&self) -> &'a str {
match *self {
TournamentType::SingleElimination => "single_elimination",
TournamentType::DoubleElimination => "double_elimination",
TournamentType::RoundRobin => "round_robin",
TournamentType::Swiss => "swiss",
}
}
}
impl fmt::Display for TournamentType {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
TournamentType::SingleElimination => fmt.write_str("single elimination"),
TournamentType::DoubleElimination => fmt.write_str("double elimination"),
TournamentType::RoundRobin => fmt.write_str("round robin"),
TournamentType::Swiss => fmt.write_str("swiss"),
}
}
}
impl FromStr for TournamentType {
type Err = ();
fn from_str(s: &str) -> Result<TournamentType, ()> {
match s {
"single_elimination" => Ok(TournamentType::SingleElimination),
"single elimination" => Ok(TournamentType::SingleElimination),
"double_elimination" => Ok(TournamentType::DoubleElimination),
"double elimination" => Ok(TournamentType::DoubleElimination),
"round_robin" => Ok(TournamentType::RoundRobin),
"round robin" => Ok(TournamentType::RoundRobin),
"swiss" => Ok(TournamentType::Swiss),
_ => Err(()),
}
}
}
#[derive(Debug, Clone)]
pub enum TournamentState {
All,
Pending,
InProgress,
Ended,
}
impl fmt::Display for TournamentState {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
TournamentState::All => {
fmt.write_str("all")?;
}
TournamentState::Pending => {
fmt.write_str("pending")?;
}
TournamentState::InProgress => {
fmt.write_str("in_progress")?;
}
TournamentState::Ended => {
fmt.write_str("ended")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
extern crate serde_json;
use crate::tournament::{Tournament, TournamentId, TournamentType};
#[test]
fn test_tournament_parse() {
let string = r#"{
"tournament": {
"accept_attachments": false,
"allow_participant_match_reporting": true,
"anonymous_voting": false,
"category": null,
"check_in_duration": null,
"completed_at": null,
"created_at": "2015-01-19T16:47:30-05:00",
"created_by_api": false,
"credit_capped": false,
"description": "sample description",
"game_id": 600,
"group_stages_enabled": false,
"hide_forum": false,
"hide_seeds": false,
"hold_third_place_match": false,
"id": 1086875,
"max_predictions_per_user": 1,
"name": "Sample Tournament 1",
"notify_users_when_matches_open": true,
"notify_users_when_the_tournament_ends": true,
"open_signup": false,
"participants_count": 4,
"prediction_method": 0,
"predictions_opened_at": null,
"private": false,
"progress_meter": 0,
"pts_for_bye": "1.0",
"pts_for_game_tie": "0.0",
"pts_for_game_win": "0.0",
"pts_for_match_tie": "0.5",
"pts_for_match_win": "1.0",
"quick_advance": false,
"ranked_by": "match wins",
"require_score_agreement": false,
"rr_pts_for_game_tie": "0.0",
"rr_pts_for_game_win": "0.0",
"rr_pts_for_match_tie": "0.5",
"rr_pts_for_match_win": "1.0",
"sequential_pairings": false,
"show_rounds": true,
"signup_cap": null,
"start_at": null,
"started_at": "2015-01-19T16:57:17-05:00",
"started_checking_in_at": null,
"state": "underway",
"swiss_rounds": 0,
"teams": false,
"tie_breaks": [
"match wins vs tied",
"game wins",
"points scored"
],
"tournament_type": "single elimination",
"updated_at": "2015-01-19T16:57:17-05:00",
"url": "sample_tournament_1",
"description_source": "sample description source",
"subdomain": null,
"full_challonge_url": "http://challonge.com/sample_tournament_1",
"live_image_url": "http://images.challonge.com/sample_tournament_1.png",
"sign_up_url": null,
"review_before_finalizing": true,
"accepting_predictions": false,
"participants_locked": true,
"game_name": "Table Tennis",
"participants_swappable": false,
"team_convertable": false,
"group_stages_were_started": false
}
}"#;
let json_r = serde_json::from_str(string);
assert!(json_r.is_ok());
let json = json_r.unwrap();
if let Ok(t) = Tournament::decode(json) {
assert!(!t.accept_attachments);
assert!(t.allow_participant_match_reporting);
assert!(!t.anonymous_voting);
assert!(!t.created_by_api);
assert_eq!(t.description, "sample description");
assert!(!t.credit_capped);
assert_eq!(t.game_id, 600);
if let TournamentId::Id(num) = t.id {
assert_eq!(num, 1086875);
} else {
unreachable!();
}
assert_eq!(t.name, "Sample Tournament 1");
assert!(!t.group_stages_enabled);
assert!(!t.hide_forum);
assert!(!t.hide_seeds);
assert!(!t.hold_third_place_match);
assert_eq!(t.max_predictions_per_user, 1);
assert!(t.notify_users_when_matches_open);
assert!(t.notify_users_when_the_tournament_ends);
assert!(!t.open_signup);
assert_eq!(t.participants_count, 4);
assert_eq!(t.prediction_method, 0);
assert!(!t.private);
assert_eq!(t.progress_meter, 0);
assert_eq!(t.swiss_points.bye.map(|b| b as u64), Some(1));
assert_eq!(t.swiss_points.game_tie as u64, 0);
assert_eq!(t.swiss_points.game_win as u64, 0);
assert!(t.swiss_points.match_tie > 0.5f64 - f64::EPSILON);
assert!(t.swiss_points.match_tie < 0.5f64 + f64::EPSILON);
assert_eq!(t.swiss_points.match_win as u64, 1);
assert!(!t.quick_advance);
assert!(!t.require_score_agreement);
assert_eq!(t.round_robin_points.game_tie as u64, 0);
assert_eq!(t.round_robin_points.game_win as u64, 0);
assert!(t.round_robin_points.match_tie > 0.5f64 - f64::EPSILON);
assert!(t.round_robin_points.match_tie < 0.5f64 + f64::EPSILON);
assert_eq!(t.round_robin_points.match_win as u64, 1);
assert!(!t.sequential_pairings);
assert!(t.show_rounds);
assert_eq!(t.swiss_rounds, 0);
assert!(!t.teams);
assert_eq!(t.tournament_type, TournamentType::SingleElimination);
assert_eq!(t.url, "sample_tournament_1");
assert_eq!(t.description_source, "sample description source");
assert_eq!(
t.full_challonge_url,
"http://challonge.com/sample_tournament_1"
);
assert_eq!(
t.live_image_url,
"http://images.challonge.com/sample_tournament_1.png"
);
assert!(t.review_before_finalizing);
assert!(!t.accepting_predictions);
assert!(t.participants_locked);
assert_eq!(t.game_name, "Table Tennis");
assert!(!t.participants_swappable);
assert!(!t.team_convertable);
assert!(!t.group_stages_were_started);
} else {
unreachable!();
}
}
}