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"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,3 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = { version = "0.12.7", features = ["json"] }
|
reqwest = { version = "0.12.7", features = ["json"] }
|
||||||
tokio = { version = "1.15", features = ["full"] }
|
tokio = { version = "1.15", features = ["full"] }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
|
||||||
|
|
|
@ -22,12 +22,13 @@
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
rust-bin.stable.latest.rust-analyzer
|
|
||||||
rust-bin.stable.latest.default
|
rust-bin.stable.latest.default
|
||||||
|
];
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rust-analyzer
|
||||||
openssl
|
openssl
|
||||||
rust-bin.stable.latest.rustfmt
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
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::header;
|
||||||
use reqwest::{Error, Url};
|
use reqwest::{Client, Error};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
#[tokio::main]
|
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||||
async fn main() -> Result<(), Error> {
|
|
||||||
|
fn create_client() -> Result<Client, Error> {
|
||||||
let token =
|
let token =
|
||||||
env::var("CANVAS_SECRET").expect("Canvas API key is not defined in the environment.");
|
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 mut headers = header::HeaderMap::new();
|
||||||
let client = CanvasClient::create(
|
headers.insert(
|
||||||
token,
|
header::AUTHORIZATION,
|
||||||
Url::parse("https://ucsb.instructure.com/api/v1/").expect("Could not parse API URL."),
|
format!("Bearer {}", token).parse().unwrap(),
|
||||||
)?;
|
);
|
||||||
println!("{:?}", client.get_courses().await?);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue