feat: New V3 version
This commit is contained in:
@@ -15,7 +15,7 @@ uniffi = { version = "0.27" }
|
|||||||
uniffi-cli = ["uniffi/cli"]
|
uniffi-cli = ["uniffi/cli"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "uniffi-bindgen"
|
name = "uniffi-bindgen"
|
||||||
@@ -26,4 +26,4 @@ opt-level = "z"
|
|||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
strip = true
|
strip = false
|
||||||
|
|||||||
16
src/api.rs
Normal file
16
src/api.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use crate::models::{ApiResponse, SuplError};
|
||||||
|
|
||||||
|
pub fn fetch_api(provider_url: &str) -> Result<ApiResponse, SuplError> {
|
||||||
|
let trimmed = provider_url.trim_end_matches('/');
|
||||||
|
let url = format!("{}/versioned/v3", trimmed);
|
||||||
|
|
||||||
|
minreq::get(&url)
|
||||||
|
.send()
|
||||||
|
.map_err(|e| SuplError::NetworkError {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})?
|
||||||
|
.json()
|
||||||
|
.map_err(|e| SuplError::ParseError {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
97
src/bin/test.rs
Normal file
97
src/bin/test.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use jecna_supl_client::{AbsenceEntry, JecnaSuplClient};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let client = JecnaSuplClient::new();
|
||||||
|
|
||||||
|
println!("Fetching schedule for E4...");
|
||||||
|
match client.get_schedule("E4".to_string()) {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("Last updated: {}", result.status.last_updated);
|
||||||
|
|
||||||
|
if result.schedule.is_empty() {
|
||||||
|
println!("No substitution schedule found for the upcoming days.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (date, day) in result.schedule {
|
||||||
|
println!("\nDate: {}", date);
|
||||||
|
println!("In work: {}", day.info.in_work);
|
||||||
|
if !day.takes_place.is_empty() {
|
||||||
|
println!("Extra info: {}", day.takes_place);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Changes:");
|
||||||
|
for (i, change) in day.changes.iter().enumerate() {
|
||||||
|
if let Some(c) = change {
|
||||||
|
println!(" Lesson {}: {}", i + 1, c.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Absences:");
|
||||||
|
for entry in day.absence {
|
||||||
|
print_absence(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Error fetching schedule: {:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nFetching all teacher absences...");
|
||||||
|
match client.get_teacher_absence() {
|
||||||
|
Ok(result) => {
|
||||||
|
for (date, entries) in result.absences {
|
||||||
|
println!("\nDate: {}", date);
|
||||||
|
for entry in entries {
|
||||||
|
print_absence(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Error fetching teacher absences: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_absence(entry: AbsenceEntry) {
|
||||||
|
match entry {
|
||||||
|
AbsenceEntry::WholeDay {
|
||||||
|
teacher,
|
||||||
|
teacher_code,
|
||||||
|
} => {
|
||||||
|
println!(" {} ({}): Whole day", teacher, teacher_code);
|
||||||
|
}
|
||||||
|
AbsenceEntry::Single {
|
||||||
|
teacher,
|
||||||
|
teacher_code,
|
||||||
|
hours,
|
||||||
|
} => {
|
||||||
|
println!(" {} ({}): Lesson {}", teacher, teacher_code, hours);
|
||||||
|
}
|
||||||
|
AbsenceEntry::Range {
|
||||||
|
teacher,
|
||||||
|
teacher_code,
|
||||||
|
hours,
|
||||||
|
} => {
|
||||||
|
println!(
|
||||||
|
" {} ({}): Lessons {}-{}",
|
||||||
|
teacher, teacher_code, hours.from, hours.to
|
||||||
|
);
|
||||||
|
}
|
||||||
|
AbsenceEntry::Exkurze {
|
||||||
|
teacher,
|
||||||
|
teacher_code,
|
||||||
|
} => {
|
||||||
|
println!(" {} ({}): Excursion", teacher, teacher_code);
|
||||||
|
}
|
||||||
|
AbsenceEntry::Zastoupen {
|
||||||
|
teacher,
|
||||||
|
teacher_code,
|
||||||
|
zastupuje,
|
||||||
|
} => {
|
||||||
|
println!(
|
||||||
|
" {} ({}): Represented by {} ({})",
|
||||||
|
teacher, teacher_code, zastupuje.teacher, zastupuje.teacher_code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
AbsenceEntry::Invalid { original } => {
|
||||||
|
println!(" Invalid entry: {}", original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/lib.rs
138
src/lib.rs
@@ -1,57 +1,14 @@
|
|||||||
use chrono::{Datelike, Local, NaiveDate, Weekday};
|
mod api;
|
||||||
use serde::Deserialize;
|
mod models;
|
||||||
use std::collections::HashMap;
|
mod schedule;
|
||||||
|
mod teacher_absence;
|
||||||
|
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
pub use models::*;
|
||||||
|
|
||||||
uniffi::setup_scaffolding!();
|
uniffi::setup_scaffolding!();
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
|
||||||
pub enum SuplError {
|
|
||||||
#[error("Network error: {reason}")]
|
|
||||||
NetworkError { reason: String },
|
|
||||||
#[error("Parse error: {reason}")]
|
|
||||||
ParseError { reason: String },
|
|
||||||
#[error("Invalid date format: {reason}")]
|
|
||||||
DateFormatError { reason: String },
|
|
||||||
#[error("Internal runtime error: {reason}")]
|
|
||||||
RuntimeError { reason: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct ApiResponse {
|
|
||||||
schedule: Vec<HashMap<String, serde_json::Value>>,
|
|
||||||
props: Vec<DayProp>,
|
|
||||||
status: Status,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct DayProp {
|
|
||||||
date: String,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
priprava: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, uniffi::Record)]
|
|
||||||
pub struct Status {
|
|
||||||
#[serde(rename = "lastUpdated")]
|
|
||||||
pub last_updated: String,
|
|
||||||
|
|
||||||
#[serde(rename = "currentUpdateSchedule")]
|
|
||||||
pub current_update_schedule: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(uniffi::Record)]
|
|
||||||
pub struct SuplResult {
|
|
||||||
pub status: Status,
|
|
||||||
pub schedule: Vec<DailySchedule>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(uniffi::Record)]
|
|
||||||
pub struct DailySchedule {
|
|
||||||
pub date: String,
|
|
||||||
pub lessons: Vec<Option<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(uniffi::Object)]
|
#[derive(uniffi::Object)]
|
||||||
pub struct JecnaSuplClient {
|
pub struct JecnaSuplClient {
|
||||||
provider_url: RwLock<String>,
|
provider_url: RwLock<String>,
|
||||||
@@ -72,81 +29,12 @@ impl JecnaSuplClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_schedule(&self, class_name: String) -> Result<SuplResult, SuplError> {
|
pub fn get_schedule(&self, class_name: String) -> Result<SuplResult, SuplError> {
|
||||||
let url = {
|
let provider = self.provider_url.read().unwrap();
|
||||||
let provider = self.provider_url.read().unwrap();
|
schedule::get_schedule_impl(&provider, class_name)
|
||||||
let trimmed = provider.trim_end_matches('/');
|
}
|
||||||
format!("{}/versioned/v2", trimmed)
|
|
||||||
};
|
|
||||||
|
|
||||||
let resp: ApiResponse = minreq::get(&url)
|
pub fn get_teacher_absence(&self) -> Result<TeacherAbsenceResult, SuplError> {
|
||||||
.send()
|
let provider = self.provider_url.read().unwrap();
|
||||||
.map_err(|e| SuplError::NetworkError {
|
teacher_absence::get_teacher_absence_impl(&provider)
|
||||||
reason: e.to_string(),
|
|
||||||
})?
|
|
||||||
.json()
|
|
||||||
.map_err(|e| SuplError::ParseError {
|
|
||||||
reason: e.to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let today = Local::now().date_naive();
|
|
||||||
let current_weekday = today.weekday();
|
|
||||||
|
|
||||||
let start_date;
|
|
||||||
let end_date;
|
|
||||||
|
|
||||||
match current_weekday {
|
|
||||||
Weekday::Sat | Weekday::Sun => {
|
|
||||||
let days_until_mon = 7 - current_weekday.num_days_from_monday();
|
|
||||||
let next_mon = today + chrono::Duration::days(days_until_mon as i64);
|
|
||||||
start_date = next_mon;
|
|
||||||
end_date = next_mon + chrono::Duration::days(4);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let days_from_mon = current_weekday.num_days_from_monday();
|
|
||||||
let curr_mon = today - chrono::Duration::days(days_from_mon as i64);
|
|
||||||
start_date = curr_mon;
|
|
||||||
end_date = curr_mon + chrono::Duration::days(4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut output = Vec::new();
|
|
||||||
|
|
||||||
for (i, day_prop) in resp.props.iter().enumerate() {
|
|
||||||
let date = NaiveDate::parse_from_str(&day_prop.date, "%Y-%m-%d").map_err(|e| {
|
|
||||||
SuplError::DateFormatError {
|
|
||||||
reason: e.to_string(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if date >= start_date && date <= end_date {
|
|
||||||
if let Some(day_schedule_map) = resp.schedule.get(i) {
|
|
||||||
if let Some(class_schedule_val) = day_schedule_map.get(&class_name) {
|
|
||||||
match serde_json::from_value::<Vec<Option<String>>>(
|
|
||||||
class_schedule_val.clone(),
|
|
||||||
) {
|
|
||||||
Ok(lessons) => {
|
|
||||||
output.push(DailySchedule {
|
|
||||||
date: day_prop.date.clone(),
|
|
||||||
lessons,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(SuplError::ParseError {
|
|
||||||
reason: format!(
|
|
||||||
"Invalid schedule format for class {}: {}",
|
|
||||||
class_name, e
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SuplResult {
|
|
||||||
status: resp.status,
|
|
||||||
schedule: output,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
127
src/models.rs
Normal file
127
src/models.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||||
|
pub enum SuplError {
|
||||||
|
#[error("Network error: {reason}")]
|
||||||
|
NetworkError { reason: String },
|
||||||
|
#[error("Parse error: {reason}")]
|
||||||
|
ParseError { reason: String },
|
||||||
|
#[error("Invalid date format: {reason}")]
|
||||||
|
DateFormatError { reason: String },
|
||||||
|
#[error("Internal runtime error: {reason}")]
|
||||||
|
RuntimeError { reason: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, uniffi::Enum, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum AbsenceEntry {
|
||||||
|
#[serde(rename = "wholeDay")]
|
||||||
|
WholeDay {
|
||||||
|
teacher: String,
|
||||||
|
#[serde(rename = "teacherCode")]
|
||||||
|
teacher_code: String,
|
||||||
|
},
|
||||||
|
#[serde(rename = "single")]
|
||||||
|
Single {
|
||||||
|
teacher: String,
|
||||||
|
#[serde(rename = "teacherCode")]
|
||||||
|
teacher_code: String,
|
||||||
|
hours: u16,
|
||||||
|
},
|
||||||
|
#[serde(rename = "range")]
|
||||||
|
Range {
|
||||||
|
teacher: String,
|
||||||
|
#[serde(rename = "teacherCode")]
|
||||||
|
teacher_code: String,
|
||||||
|
hours: AbsenceRange,
|
||||||
|
},
|
||||||
|
#[serde(rename = "exkurze")]
|
||||||
|
Exkurze {
|
||||||
|
teacher: String,
|
||||||
|
#[serde(rename = "teacherCode")]
|
||||||
|
teacher_code: String,
|
||||||
|
},
|
||||||
|
#[serde(rename = "zastoupen")]
|
||||||
|
Zastoupen {
|
||||||
|
teacher: String,
|
||||||
|
#[serde(rename = "teacherCode")]
|
||||||
|
teacher_code: String,
|
||||||
|
zastupuje: SubstituteInfo,
|
||||||
|
},
|
||||||
|
#[serde(rename = "invalid")]
|
||||||
|
Invalid { original: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, uniffi::Record, Clone)]
|
||||||
|
pub struct AbsenceRange {
|
||||||
|
pub from: u16,
|
||||||
|
pub to: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, uniffi::Record, Clone)]
|
||||||
|
pub struct SubstituteInfo {
|
||||||
|
pub teacher: String,
|
||||||
|
#[serde(rename = "teacherCode")]
|
||||||
|
pub teacher_code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, uniffi::Record, Clone)]
|
||||||
|
pub struct ChangeEntry {
|
||||||
|
pub text: String,
|
||||||
|
#[serde(rename = "backgroundColor")]
|
||||||
|
pub background_color: Option<String>,
|
||||||
|
#[serde(rename = "willBeSpecified")]
|
||||||
|
pub will_be_specified: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ApiResponse {
|
||||||
|
pub status: Status,
|
||||||
|
pub schedule: HashMap<String, DailyData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub struct DailyData {
|
||||||
|
pub info: DayInfo,
|
||||||
|
pub changes: HashMap<String, Vec<Option<ChangeEntry>>>,
|
||||||
|
pub absence: Vec<AbsenceEntry>,
|
||||||
|
#[serde(rename = "takesPlace")]
|
||||||
|
pub takes_place: String,
|
||||||
|
#[serde(rename = "reservedRooms")]
|
||||||
|
pub reserved_rooms: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, uniffi::Record, Clone)]
|
||||||
|
pub struct DayInfo {
|
||||||
|
#[serde(rename = "inWork")]
|
||||||
|
pub in_work: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, uniffi::Record, Clone)]
|
||||||
|
pub struct Status {
|
||||||
|
#[serde(rename = "lastUpdated")]
|
||||||
|
pub last_updated: String,
|
||||||
|
|
||||||
|
#[serde(rename = "currentUpdateSchedule")]
|
||||||
|
pub current_update_schedule: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(uniffi::Record)]
|
||||||
|
pub struct SuplResult {
|
||||||
|
pub status: Status,
|
||||||
|
pub schedule: HashMap<String, DailySchedule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(uniffi::Record)]
|
||||||
|
pub struct DailySchedule {
|
||||||
|
pub info: DayInfo,
|
||||||
|
pub changes: Vec<Option<ChangeEntry>>,
|
||||||
|
pub absence: Vec<AbsenceEntry>,
|
||||||
|
pub takes_place: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(uniffi::Record)]
|
||||||
|
pub struct TeacherAbsenceResult {
|
||||||
|
pub absences: HashMap<String, Vec<AbsenceEntry>>,
|
||||||
|
}
|
||||||
29
src/schedule.rs
Normal file
29
src/schedule.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::api::fetch_api;
|
||||||
|
use crate::models::{DailySchedule, SuplError, SuplResult};
|
||||||
|
|
||||||
|
pub fn get_schedule_impl(provider_url: &str, class_name: String) -> Result<SuplResult, SuplError> {
|
||||||
|
let resp = fetch_api(provider_url)?;
|
||||||
|
|
||||||
|
let mut schedule_output = HashMap::new();
|
||||||
|
|
||||||
|
for (date, daily_data) in resp.schedule {
|
||||||
|
if let Some(class_changes) = daily_data.changes.get(&class_name) {
|
||||||
|
schedule_output.insert(
|
||||||
|
date,
|
||||||
|
DailySchedule {
|
||||||
|
info: daily_data.info,
|
||||||
|
changes: class_changes.clone(),
|
||||||
|
absence: daily_data.absence,
|
||||||
|
takes_place: daily_data.takes_place,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SuplResult {
|
||||||
|
status: resp.status,
|
||||||
|
schedule: schedule_output,
|
||||||
|
})
|
||||||
|
}
|
||||||
16
src/teacher_absence.rs
Normal file
16
src/teacher_absence.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::api::fetch_api;
|
||||||
|
use crate::models::{SuplError, TeacherAbsenceResult};
|
||||||
|
|
||||||
|
pub fn get_teacher_absence_impl(provider_url: &str) -> Result<TeacherAbsenceResult, SuplError> {
|
||||||
|
let resp = fetch_api(provider_url)?;
|
||||||
|
|
||||||
|
let mut output = HashMap::new();
|
||||||
|
|
||||||
|
for (date, daily_data) in resp.schedule {
|
||||||
|
output.insert(date, daily_data.absence);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(TeacherAbsenceResult { absences: output })
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user