Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Working on implementing the Studies API endpoint #2

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod games;
pub mod messaging;
pub mod openings;
pub mod puzzles;
pub mod studies;
pub mod tablebase;
pub mod users;

Expand Down
67 changes: 67 additions & 0 deletions src/api/studies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::str::FromStr;

use async_std::stream::StreamExt;
use tracing::error;

use crate::client::LichessApi;
use crate::error::Result;
use crate::model::studies::import_pgn_into_study::PostRequest;
use crate::model::studies::*;

use self::import_pgn_into_study::StudyImportPgnChapters;

impl LichessApi<reqwest::Client> {
pub async fn export_one_chapter(
&self,
request: GetRequest,
) -> Result<StudyChapter> {
let mut stream = self.get_pgn(request).await?;
let mut study_chapter = StudyChapter {
headers: vec![],
sections: vec![],
};

let mut current_section = StudySection::default();
let mut inside_comment = false;
let mut switch_section_next = false;

while let Some(s) = stream.next().await {
let line = s?;
println!("Got line: {}", line);
if line.starts_with('[') {
match StudyHeader::from_str(&line) {
Ok(h) => study_chapter.headers.push(h),
Err(e) => error!("Can't parse study header: {e}. Ignoring line '{line}'."),
}
} else {
for c in line.chars() {
if c == '{' || c == '}' {
inside_comment = if c == '{' { true } else { false };
if switch_section_next {
study_chapter.sections.push(current_section);
current_section = StudySection::default();
switch_section_next = false;
} else {
switch_section_next = true;
}
} else if inside_comment {
current_section.comment.push(c);
} else {
current_section.pgn.push(c);
}
}
if inside_comment {
current_section.comment.push('\n');
} else {
current_section.pgn.push('\n');
}
}
}

Ok(study_chapter)
}

pub async fn import_pgn_into_study(&self, request: PostRequest) -> Result<StudyImportPgnChapters> {
self.get_single_model(request).await
}
}
1 change: 1 addition & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod games;
pub mod messaging;
pub mod openings;
pub mod puzzles;
pub mod studies;
pub mod tablebase;
pub mod users;

Expand Down
13 changes: 13 additions & 0 deletions src/model/studies/export_one_chapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use super::GetStudyQuery;

pub type GetRequest = crate::model::Request<GetStudyQuery>;

impl GetRequest {
pub fn new(study_id: String, chapter_id: String, query: GetStudyQuery) -> Self {
Self {
path: format!("/api/study/{}/{}.pgn", study_id, chapter_id),
query: Some(query),
..Default::default()
}
}
}
41 changes: 41 additions & 0 deletions src/model/studies/import_pgn_into_study.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use crate::model::{Body, Request};

use crate::model::VariantKey;

#[derive(Default, Clone, Debug, Serialize)]
#[skip_serializing_none]
pub struct ImportPgnBody {
pub name: String,
pub pgn: String,
pub variant: Option<VariantKey>,
pub orientation: Option<String>,
}

#[derive(Default, Clone, Debug, Deserialize)]
pub struct StudyImportPgnChapters {
pub chapters: Vec<StudyChapterListItem>,
}

#[derive(Default, Clone, Debug, Deserialize)]
pub struct StudyChapterListItem {
pub id: String,
pub name: String,
}

#[derive(Default, Clone, Debug, Serialize)]
pub struct PostQuery;

pub type PostRequest = Request<PostQuery, ImportPgnBody>;

impl PostRequest {
pub fn new(study_id: String, import_pgn_body: ImportPgnBody) -> Self {
Self {
method: http::Method::POST,
path: format!("/api/study/{}/import-pgn", study_id),
body: Body::Form(import_pgn_body),
..Default::default()
}
}
}
102 changes: 102 additions & 0 deletions src/model/studies/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
pub mod export_one_chapter;
pub mod import_pgn_into_study;

use std::str::FromStr;

use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;

pub type GetRequest = crate::model::Request<GetStudyQuery>;

#[derive(Default, Clone, Debug, Serialize)]
#[skip_serializing_none]
pub struct GetStudyQuery {
pub clocks: Option<bool>,
pub comments: Option<bool>,
pub variations: Option<bool>,
pub source: Option<bool>,
pub orientation: Option<bool>
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct Study {
pub chapters: Vec<StudyChapter>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StudyHeader {
pub header_type: StudyHeaderType,
pub header_value: String,
}

impl FromStr for StudyHeader {
type Err = &'static str;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let s = match value.strip_prefix('[') {
Some(s) => s,
None => return Err("Header does not begin with '[' character."),
};
let mut s_split = s.split_whitespace();
let header_type = match s_split.next().unwrap() {
"Event" => StudyHeaderType::Event,
"Site" => StudyHeaderType::Site,
"Result" => StudyHeaderType::Result,
"Variant" => StudyHeaderType::Variant,
"ECO" => StudyHeaderType::ECO,
"Opening" => StudyHeaderType::Opening,
"Annotator" => StudyHeaderType::Annotator,
"UTCDate" => StudyHeaderType::UTCDate,
"UTCTime" => StudyHeaderType::UTCTime,
"ChapterMode" => StudyHeaderType::ChapterMode,
_ => StudyHeaderType::Unknown
};

let mut header_value = String::new();
while let Some(next_s) = s_split.next() {
let mut next_string = next_s.to_string();
if next_string.starts_with('"') {
next_string.remove(0);
}
if next_string.ends_with(']') {
next_string.pop();
}
if next_string.ends_with('"') {
next_string.pop();
}
header_value += &next_string;
}

Ok(StudyHeader {
header_type: header_type,
header_value: header_value,
})
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum StudyHeaderType {
Event,
Site,
Result,
Variant,
ECO,
Opening,
Annotator,
UTCDate,
UTCTime,
ChapterMode,
Unknown
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct StudyChapter {
pub headers: Vec<StudyHeader>,
pub sections: Vec<StudySection>,
}


#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct StudySection {
pub comment: String,
pub pgn: String,
}
Loading