Compare commits
No commits in common. "8738a2a6dfe75944268dbcb8786f32503e4add01" and "7e691d07a6e53bb0c21525757733bc9894e15095" have entirely different histories.
8738a2a6df
...
7e691d07a6
8 changed files with 37 additions and 162 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -73,7 +73,6 @@ name = "cartographer"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
|
@ -6,4 +6,3 @@ edition = "2021"
|
|||
[dependencies]
|
||||
reqwest = { version = "0.12.7", features = ["json"] }
|
||||
tokio = { version = "1.15", features = ["full"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
|
|
|
@ -22,12 +22,13 @@
|
|||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
rust-bin.stable.latest.rust-analyzer
|
||||
rust-bin.stable.latest.default
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
rust-analyzer
|
||||
openssl
|
||||
rust-bin.stable.latest.rustfmt
|
||||
];
|
||||
};
|
||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
pub mod libcanvas;
|
||||
pub use reqwest::Url;
|
|
@ -1,62 +0,0 @@
|
|||
use reqwest::header;
|
||||
use reqwest::Url;
|
||||
use reqwest::{Client, Error};
|
||||
use std::env;
|
||||
pub mod courses;
|
||||
|
||||
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||
|
||||
/// A high level client for interfacing with the Canvas API. Conveniently wraps common Canvas
|
||||
/// operations.
|
||||
pub struct CanvasClient {
|
||||
client: Client,
|
||||
api_url: Url,
|
||||
}
|
||||
|
||||
impl CanvasClient {
|
||||
/// Create a Canvas client with a given API base URL (eg.
|
||||
/// `https://ucsb.instructure.com/api/v1/`) and access token. You must include the trailing
|
||||
/// slash or else API calls will be malformed.
|
||||
pub fn create(token: String, api_url: Url) -> Result<CanvasClient, Error> {
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(
|
||||
header::AUTHORIZATION,
|
||||
format!("Bearer {}", token).parse().unwrap(),
|
||||
);
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent(APP_USER_AGENT)
|
||||
.default_headers(headers)
|
||||
.build()?;
|
||||
|
||||
Ok(CanvasClient { client, api_url })
|
||||
}
|
||||
|
||||
/// Call an API endpoint. Expects the relative path of the endpoint after the base API URL.
|
||||
/// Returns the response as plaintext in a String regardless of its serialization format.
|
||||
///
|
||||
/// # Example
|
||||
/// Gives you the GET response from `https://ucsb.instructure.com/api/v1/courses`.
|
||||
/// ```
|
||||
/// use std::env;
|
||||
/// use cartographer::libcanvas::CanvasClient;
|
||||
/// use cartographer::Url;
|
||||
///
|
||||
/// let client = CanvasClient::create(
|
||||
/// "SAMPLE_TOKEN".to_string(),
|
||||
/// Url::parse("https://ucsb.instructure.com/api/v1/").unwrap()
|
||||
/// ).unwrap();
|
||||
///
|
||||
/// client.get_from_endpoint("courses");
|
||||
/// ```
|
||||
pub async fn get_from_endpoint(&self, endpoint: &str) -> Result<String, reqwest::Error> {
|
||||
let response =
|
||||
self.client
|
||||
.get(self.api_url.join(endpoint).expect(
|
||||
"API endpoint returned error. Perhaps it was malformed or doesn't exist.",
|
||||
))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
response.text().await
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
use super::CanvasClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
impl CanvasClient {
|
||||
pub async fn get_courses(&self) -> Result<Vec<Course>, reqwest::Error> {
|
||||
let response = self
|
||||
.client
|
||||
.get(self.api_url.join("courses").unwrap())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
response.json::<Vec<Course>>().await
|
||||
}
|
||||
}
|
||||
|
||||
// Some time options are ISO 8601 standard times but they are parsed as Strings for now for
|
||||
// simplicity
|
||||
/// Represents a response from the `/courses` API endpoint. Some strings are plaintext and some are
|
||||
/// HTML. Some JSON objects which have not yet been typed are deserialized into plaintext instead.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Course {
|
||||
id: u32,
|
||||
name: String,
|
||||
account_id: u32,
|
||||
uuid: String,
|
||||
start_at: Option<String>,
|
||||
grading_standard_id: Option<u32>,
|
||||
is_public: bool,
|
||||
created_at: String,
|
||||
course_code: String,
|
||||
default_view: Option<String>,
|
||||
root_account_id: u32,
|
||||
enrollment_term_id: u32,
|
||||
term: Option<String>,
|
||||
permissions: Option<Permissions>,
|
||||
course_progress: Option<String>,
|
||||
license: String,
|
||||
public_description: Option<String>,
|
||||
access_restricted_by_date: Option<bool>,
|
||||
blueprint_restrictions: Option<String>,
|
||||
blueprint_restrictions_by_object_type: Option<String>,
|
||||
syllabus_body: Option<String>,
|
||||
needs_grading_count: Option<u32>,
|
||||
grade_passback_setting: Option<String>,
|
||||
end_at: Option<String>,
|
||||
public_syllabus: bool,
|
||||
public_syllabus_to_auth: bool,
|
||||
storage_quota_mb: usize,
|
||||
is_public_to_auth_users: bool,
|
||||
homeroom_course: bool,
|
||||
course_color: Option<String>,
|
||||
friendly_name: Option<String>,
|
||||
apply_assignment_group_weights: bool,
|
||||
calendar: Calendar,
|
||||
time_zone: String,
|
||||
blueprint: bool,
|
||||
template: bool,
|
||||
enrollments: Option<Vec<Enrollment>>,
|
||||
hide_final_grades: bool,
|
||||
workflow_state: String,
|
||||
restrict_enrollments_to_course_dates: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Enrollment {
|
||||
r#type: String,
|
||||
role: String,
|
||||
role_id: u32,
|
||||
user_id: u32,
|
||||
enrollment_state: String,
|
||||
limit_privileges_to_course_section: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Calendar {
|
||||
ics: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Permissions {
|
||||
create_discussion_topic: bool,
|
||||
create_announcement: bool,
|
||||
}
|
43
src/main.rs
43
src/main.rs
|
@ -1,17 +1,40 @@
|
|||
use cartographer::libcanvas::CanvasClient;
|
||||
use reqwest::{Error, Url};
|
||||
use reqwest::header;
|
||||
use reqwest::{Client, Error};
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||
|
||||
fn create_client() -> Result<Client, Error> {
|
||||
let token =
|
||||
env::var("CANVAS_SECRET").expect("Canvas API key is not defined in the environment.");
|
||||
|
||||
// Base URL must have trailing slash or URL `.join()` will not work
|
||||
let client = CanvasClient::create(
|
||||
token,
|
||||
Url::parse("https://ucsb.instructure.com/api/v1/").expect("Could not parse API URL."),
|
||||
)?;
|
||||
println!("{:?}", client.get_courses().await?);
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(
|
||||
header::AUTHORIZATION,
|
||||
format!("Bearer {}", token).parse().unwrap(),
|
||||
);
|
||||
reqwest::Client::builder()
|
||||
.user_agent(APP_USER_AGENT)
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
}
|
||||
|
||||
async fn get_request() -> Result<(), reqwest::Error> {
|
||||
let response = create_client()?
|
||||
.get("https://ucsb.instructure.com/api/v1/courses")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
println!("Status: {}", response.status());
|
||||
|
||||
let body = response.text().await?;
|
||||
println!("Body:\n{}", body);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
get_request().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue