use crate::{
error::Result,
http::Requester,
pagination::PageStream,
params::wrap_params,
resources::{
account::Account,
course::Course,
outcome::Outcome,
params::{course_params::CreateCourseParams, user_params::CreateUserParams},
user::{CurrentUser, User, UserId},
},
};
use reqwest::Client;
use std::sync::Arc;
use url::Url;
pub struct Canvas {
pub(crate) requester: Arc<Requester>,
}
impl Canvas {
pub fn new(base_url: &str, access_token: &str) -> Result<Self> {
Self::with_client(base_url, access_token, Client::new())
}
pub fn with_client(base_url: &str, access_token: &str, client: Client) -> Result<Self> {
let base_url = validate_base_url(base_url)?;
let api_url = base_url.join("api/v1/")?;
Ok(Self {
requester: Arc::new(Requester::new(
api_url,
access_token.trim().to_string(),
client,
)),
})
}
pub async fn get_course(&self, course_id: u64) -> Result<Course> {
let mut course: Course = self
.requester
.get(&format!("courses/{course_id}"), &[])
.await?;
course.requester = Some(Arc::clone(&self.requester));
Ok(course)
}
pub fn get_courses(&self) -> PageStream<Course> {
PageStream::new_with_injector(
Arc::clone(&self.requester),
"courses",
vec![],
|mut c: Course, req| {
c.requester = Some(Arc::clone(&req));
c
},
)
}
pub async fn create_course(
&self,
account_id: u64,
params: CreateCourseParams,
) -> Result<Course> {
let form = wrap_params("course", ¶ms);
let mut course: Course = self
.requester
.post(&format!("accounts/{account_id}/courses"), &form)
.await?;
course.requester = Some(Arc::clone(&self.requester));
Ok(course)
}
pub async fn delete_course(&self, course_id: u64) -> Result<Course> {
let params = vec![("event".to_string(), "delete".to_string())];
let mut course: Course = self
.requester
.delete(&format!("courses/{course_id}"), ¶ms)
.await?;
course.requester = Some(Arc::clone(&self.requester));
Ok(course)
}
pub async fn get_user(&self, user_id: UserId) -> Result<User> {
let id = user_id.to_path_segment();
let mut user: User = self.requester.get(&format!("users/{id}"), &[]).await?;
user.requester = Some(Arc::clone(&self.requester));
Ok(user)
}
pub async fn get_current_user(&self) -> Result<CurrentUser> {
self.requester.get("users/self", &[]).await
}
pub async fn create_user(&self, account_id: u64, params: CreateUserParams) -> Result<User> {
let form = wrap_params("user", ¶ms);
let mut user: User = self
.requester
.post(&format!("accounts/{account_id}/users"), &form)
.await?;
user.requester = Some(Arc::clone(&self.requester));
Ok(user)
}
pub async fn get_account(&self, account_id: u64) -> Result<Account> {
let mut account: Account = self
.requester
.get(&format!("accounts/{account_id}"), &[])
.await?;
account.requester = Some(Arc::clone(&self.requester));
Ok(account)
}
pub async fn get_outcome(&self, outcome_id: u64) -> Result<Outcome> {
let mut outcome: Outcome = self
.requester
.get(&format!("outcomes/{outcome_id}"), &[])
.await?;
outcome.requester = Some(Arc::clone(&self.requester));
Ok(outcome)
}
pub fn get_accounts(&self) -> PageStream<Account> {
PageStream::new_with_injector(
Arc::clone(&self.requester),
"accounts",
vec![],
|mut a: Account, req| {
a.requester = Some(Arc::clone(&req));
a
},
)
}
#[cfg(feature = "graphql")]
pub fn graphql(&self) -> crate::graphql::GraphQL {
crate::graphql::GraphQL {
requester: Arc::clone(&self.requester),
}
}
}
fn validate_base_url(raw: &str) -> Result<Url> {
let trimmed = raw.trim().trim_end_matches('/');
if trimmed.contains("/api/v1") {
return Err(crate::error::CanvasError::ApiError {
status: 0,
message: "base_url should not include /api/v1".into(),
});
}
Ok(Url::parse(&format!("{trimmed}/"))?)
}