rusaint/
lib.rs

1#![warn(missing_docs)]
2// TODO: Reduce error size and remove this attr
3#![allow(clippy::result_large_err)]
4//! _빠르고 간편하며 믿을 수 있는 숭실대학교 u-saint 클라이언트_
5//!
6//! <a href="https://github.com/EATSTEAK/rusaint"><img alt="GitHub Badge" src="https://img.shields.io/badge/github-eatsteak/rusaint-8da0cb?style=for-the-badge&labelColor=555555&logo=github"></a>
7//! <a href="https://crates.io/crates/rusaint"><img alt="crates.io" src="https://img.shields.io/crates/v/rusaint.svg?style=for-the-badge&color=fc8d62&logo=rust"></a>
8//! <a href="https://docs.rs/rusaint"><img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-rusaint-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs"></a>
9//! <a href="https://github.com/EATSTEAK/rusaint/LICENSE.md"><img alt="License" src="https://img.shields.io/github/license/EATSTEAK/rusaint?style=for-the-badge"></a>
10//!
11//! ---
12//!
13//! rusaint는 [숭실대학교 u-saint](https://saint.ssu.ac.kr)를 정확하고 빠르게, 간편하게 파싱하고 다양한 환경에서 조작할 수 있는 Rust 기반 비공식 u-saint 클라이언트입니다.
14//!
15//! u-saint의 기반인 [SAP Web Dynpro](https://en.wikipedia.org/wiki/Web_Dynpro)에서 사용하는 Lightspeed 라이브러리의 최소 동작을 구현하여 안전하게 u-saint 내부 요소들을 조작하고 파싱할 수 있습니다.
16//!
17//! - **JS 런타임 없음** — JS 런타임 없이 자체적으로 요청과 응답에 따른 처리를 수행하므로 HTTPS 요청이 가능한 모든 환경에서 실행 가능합니다.
18//! - **빠른 속도** — 네이티브 환경으로 컴파일되는 Rust를 이용하고, 휴리스틱 없이 요청이 완료되면 곧바로 실행되어 빠르게 u-saint 를 조작 및 파싱 가능합니다.
19//! - **멀티플랫폼 지원** — UniFFI를 통한 Kotlin, Swift, Python(예정) 지원 및 Node.js 용 WASM Wrapper(예정)를 제공하여 다양한 플랫폼에서 간편하게 이용할 수 있습니다.
20//! - **간편한 기능 정의** — rusaint 에서 지원하지 않는 u-saint 애플리케이션에 대한 파싱 및 지원을 제공하는 API를 이용해 간편하게 정의할 수 있습니다.
21//! ## 예시
22//!
23//! ```no_run
24//! use rusaint::application::course_grades::{CourseGradesApplication, model::CourseType, model::SemesterGrade};
25//! use rusaint::application::USaintClientBuilder;
26//! use rusaint::webdynpro::element::Element;
27//! use rusaint::RusaintError;
28//! use std::sync::Arc;
29//! use rusaint::USaintSession;
30//! use futures::executor::block_on;
31//!
32//! // 성적 정보를 출력하는 애플리케이션
33//! fn main() {
34//!     block_on(print_grades());
35//!     /* SemesterGrade { year: 2022, semester: "2 학기", attempted_credits: 17.5, earned_credits: 17.5, pf_earned_credits: 0.5, grade_points_average: 4.5, grade_points_sum: 100.0, arithmetic_mean: 100.0, semester_rank: (1, 99), general_rank: (1, 99), academic_probation: false, consult: false, flunked: false }
36//!      */
37//! }
38//!
39//! async fn print_grades() -> Result<(), RusaintError> {
40//!     // USaintSession::with_token(id: &str, token: &str) 을 이용하여 비밀번호 없이 SSO 토큰으로 로그인 할 수 있음
41//!     let session = Arc::new(USaintSession::with_password("20211561", "password").await?);
42//!     let mut app = USaintClientBuilder::new().session(session).build_into::<CourseGradesApplication>().await?;
43//!     let grades: Vec<SemesterGrade> = app.semesters(CourseType::Bachelor).await?;
44//!     for grade in grades {
45//!         println!("{:?}", grade);
46//!     }
47//!     Ok(())
48//! }
49//! ```
50#[cfg(feature = "application")]
51/// rusaint에서 제공하는 기본 u-saint 애플리케이션
52pub mod application;
53#[cfg(feature = "application")]
54mod error;
55#[cfg(feature = "application")]
56pub use error::ApplicationError;
57#[cfg(feature = "application")]
58pub use error::RusaintError;
59#[cfg(feature = "application")]
60pub use error::SsuSsoError;
61#[cfg(feature = "application")]
62mod session;
63
64#[cfg(feature = "application")]
65pub use session::obtain_ssu_sso_token;
66
67#[cfg(feature = "application")]
68pub use session::USaintSession;
69
70#[cfg(feature = "application")]
71/// u-saint 애플리케이션에서 공통으로 사용하는 데이터
72pub mod model;
73
74pub(crate) mod utils;
75/// SAP WebDynpro 클라이언트를 파싱, 모방하는 클라이언트 엔진
76pub mod webdynpro;
77
78#[cfg(feature = "uniffi")]
79uniffi::setup_scaffolding!();
80
81#[cfg(feature = "uniffi")]
82/// `uniffi` 지원을 위한 모듈
83pub mod uniffi_support;
84
85#[cfg(test)]
86#[allow(missing_docs)]
87pub mod global_test_utils {
88    use crate::{USaintSession, model::SemesterType};
89    use anyhow::{Error, Result};
90    use dotenvy::dotenv;
91    use lazy_static::lazy_static;
92    use std::{fs::File, io::BufReader, sync::Arc};
93
94    lazy_static! {
95        pub(crate) static ref TARGET_YEAR: u32 = {
96            dotenv().ok();
97            std::env::var("TARGET_YEAR").unwrap().parse().unwrap()
98        };
99        pub(crate) static ref TARGET_SEMESTER: SemesterType = {
100            dotenv().ok();
101            let semester = std::env::var("TARGET_SEMESTER").unwrap();
102            match semester.to_uppercase().as_str() {
103                "1" | "ONE" => SemesterType::One,
104                "SUMMER" => SemesterType::Summer,
105                "2" | "TWO" => SemesterType::Two,
106                "WINTER" => SemesterType::Winter,
107                _ => panic!("{:?}", Error::msg("Invalid semester")),
108            }
109        };
110    }
111
112    pub async fn get_session() -> Result<Arc<USaintSession>> {
113        let session_file_path =
114            std::env::var("SSO_SESSION_FILE").unwrap_or("session.json".to_string());
115        let f = File::open(&session_file_path)
116            .map_err(|e| Error::msg(format!("Failed to open session file: {e}")))?;
117        let reader = BufReader::new(f);
118        let session: USaintSession = USaintSession::from_json(reader)
119            .map_err(|e| Error::msg(format!("Failed to parse session file: {e}")))?;
120        let session = Arc::new(session);
121        Ok(session)
122    }
123}