Skip to content

Commit

Permalink
feat: migrate http request and response types from ic-response-verifi…
Browse files Browse the repository at this point in the history
…cation to ic-http-certification

BREAKING CHANGE:
  • Loading branch information
nathanosdev committed Dec 4, 2023
1 parent fbc7fb2 commit 78e7b4a
Show file tree
Hide file tree
Showing 33 changed files with 524 additions and 497 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ getrandom = { version = "0.2", features = ["js"] }


ic-certification = { path = "./packages/ic-certification", default-features = false, version = "1.3.0" }
ic-http-certification = { path = "./packages/ic-http-certification", version = "1.3.0" }
ic-certification-testing = { path = "./packages/ic-certification-testing" }
ic-representation-independent-hash = { path = "./packages/ic-representation-independent-hash", version = "1.3.0" }
ic-certificate-verification = { path = "./packages/ic-certificate-verification", version = "1.3.0" }
Expand Down
4 changes: 0 additions & 4 deletions examples/nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ try {
console.log(`Error parsing cbor: ${error.message}`);
break;

case ResponseVerificationErrorCode.MalformedUrl:
console.log(`Invalid URL provided: ${error.message}`);
break;

case ResponseVerificationErrorCode.CertificateVerificationFailed:
console.log(`Certificate verification failed: ${error.message}`);
break;
Expand Down
1 change: 1 addition & 0 deletions examples/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
ic-response-verification.workspace = true
ic-http-certification.workspace = true
candid.workspace = true
hex.workspace = true
serde.workspace = true
Expand Down
23 changes: 4 additions & 19 deletions examples/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
use candid::{CandidType, Decode, Deserialize, Principal};
use ic_response_verification::types::{Request, Response};
use candid::{Decode, Principal};
use ic_http_certification::{HttpRequest, HttpResponse};
use ic_response_verification::{verify_request_response_pair, MIN_VERIFICATION_VERSION};

#[derive(Debug, Clone, CandidType, Deserialize)]
struct HttpRequest {
pub url: String,
pub headers: Vec<(String, String)>,
#[serde(with = "serde_bytes")]
pub body: Vec<u8>,
}

#[derive(Debug, Clone, CandidType, Deserialize)]
pub struct HttpResponse {
pub headers: Vec<(String, String)>,
#[serde(with = "serde_bytes")]
pub body: Vec<u8>,
}

fn main() {
let request_hex = "4449444C046D7B6C02007101716D016C04EFD6E40271E1EDEB4A71A2F5ED880400C6A4A19806020103012F03474554000704486F73742372646D78362D6A616161612D61616161612D61616164712D6361692E6963302E617070066163636570748701746578742F68746D6C2C6170706C69636174696F6E2F7868746D6C2B786D6C2C6170706C69636174696F6E2F786D6C3B713D302E392C696D6167652F617669662C696D6167652F776562702C696D6167652F61706E672C2A2F2A3B713D302E382C6170706C69636174696F6E2F7369676E65642D65786368616E67653B763D62333B713D302E39097365632D63682D756128224368726F6D69756D223B763D22313037222C20224E6F743D413F4272616E64223B763D22323422107365632D63682D75612D6D6F62696C65023F30127365632D63682D75612D706C6174666F726D092257696E646F77732219757067726164652D696E7365637572652D726571756573747301310A757365722D6167656E74744D6F7A696C6C612F352E30202857696E646F7773204E542031302E303B2057696E36343B2078363429204170706C655765624B69742F3533372E333620284B48544D4C2C206C696B65204765636B6F29204368726F6D652F3130372E302E353330342E313037205361666172692F3533372E3336";
let request_candid = hex::decode(request_hex).expect("Could not decode request from hex");
let http_request =
Decode!(&request_candid, HttpRequest).expect("Could not decode request from candid");
let request = Request {
let request = HttpRequest {
method: "GET".into(),
url: http_request.url,
headers: http_request.headers,
Expand All @@ -39,7 +24,7 @@ fn main() {
let http_response =
Decode!(&response_candid, HttpResponse).expect("Could not decode response from candid");

let response = Response {
let response = HttpResponse {
status_code: 200,
headers: http_response.headers,
body: http_response.body,
Expand Down
4 changes: 0 additions & 4 deletions examples/service-worker/src/sw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ self.addEventListener('activate', async () => {
console.log(`Error parsing cbor: ${error.message}`);
break;

case ResponseVerificationErrorCode.MalformedUrl:
console.log(`Invalid URL provided: ${error.message}`);
break;

case ResponseVerificationErrorCode.CertificateVerificationFailed:
console.log(`Certificate verification failed: ${error.message}`);
break;
Expand Down
4 changes: 0 additions & 4 deletions examples/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,6 @@ window.addEventListener('load', async () => {
console.log(`Error parsing cbor: ${error.message}`);
break;

case ResponseVerificationErrorCode.MalformedUrl:
console.log(`Invalid URL provided: ${error.message}`);
break;

case ResponseVerificationErrorCode.CertificateVerificationFailed:
console.log(`Certificate verification failed: ${error.message}`);
break;
Expand Down
7 changes: 7 additions & 0 deletions packages/ic-http-certification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@ repository.workspace = true
license.workspace = true
homepage.workspace = true

[dependencies]
candid.workspace = true
serde.workspace = true
http.workspace = true
urlencoding.workspace = true
thiserror.workspace = true

[dev-dependencies]
rstest.workspace = true
4 changes: 2 additions & 2 deletions packages/ic-http-certification/src/cel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The CEL modules contains functions and builders for creating CEL expression
//! definitions and conveting them into their `String` representation.
//! The CEL module contains functions and builders for creating CEL expression
//! definitions and converting them into their `String` representation.
mod cel_builder;
pub use cel_builder::*;
Expand Down
17 changes: 17 additions & 0 deletions packages/ic-http-certification/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! The error module contains types for common errors that may be thrown
//! by other modules in this crate.
/// HTTP certification result type.
pub type HttpCertificationResult<T = ()> = Result<T, HttpCertificationError>;

/// HTTP certification error type.
#[derive(thiserror::Error, Debug)]
pub enum HttpCertificationError {
/// The URL was malformed and could not be parsed correctly.
#[error(r#"Failed to parse url: "{0}""#)]
MalformedUrl(String),

/// Error converting UTF-8 string.
#[error(r#"Error converting UTF8 string bytes: "{0}""#)]
Utf8ConversionError(#[from] std::string::FromUtf8Error),
}
2 changes: 2 additions & 0 deletions packages/ic-http-certification/src/http/header_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// An HTTP header field, represented as a tuple of (name, value).
pub type HeaderField = (String, String);
113 changes: 113 additions & 0 deletions packages/ic-http-certification/src/http/http_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use crate::{HeaderField, HttpCertificationError, HttpCertificationResult};
use candid::{CandidType, Deserialize};
use http::Uri;

/// A Candid-encodable representation of an HTTP request.
/// This struct is used by canisters that implement the HTTP interface required by the HTTP Gateway Protocol.
#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)]
pub struct HttpRequest {
/// HTTP request method.
pub method: String,
/// Request URL.
pub url: String,
/// HTTP request headers.
pub headers: Vec<HeaderField>,
/// Request body as an array of bytes.
pub body: Vec<u8>,
}

impl HttpRequest {
/// Returns the path of the request URL, without domain, query parameters or fragments.
pub fn get_path<'a>(&'a self) -> HttpCertificationResult<String> {
let uri = self
.url
.parse::<Uri>()
.map_err(|_| HttpCertificationError::MalformedUrl(self.url.clone()))?;

let decoded_path = urlencoding::decode(uri.path()).map(|path| path.into_owned())?;
Ok(decoded_path)
}

/// Returns the query parameters of the request URL, if any, as a string.
pub fn get_query<'a>(&'a self) -> HttpCertificationResult<Option<String>> {
self.url
.parse::<Uri>()
.map(|uri| uri.query().map(|uri| uri.to_owned()))
.map_err(|_| HttpCertificationError::MalformedUrl(self.url.clone()))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn request_get_uri() {
let req = HttpRequest {
method: "GET".to_string(),
url: "https://canister.com/sample-asset.txt".to_string(),
headers: vec![],
body: vec![],
};

let path = req.get_path().unwrap();
let query = req.get_query().unwrap();

assert_eq!(path, "/sample-asset.txt");
assert!(query.is_none());
}

#[test]
fn request_get_encoded_uri() {
let test_requests = [
(
HttpRequest {
method: "GET".to_string(),
url: "https://canister.com/%73ample-asset.txt".to_string(),
headers: vec![],
body: vec![],
},
"/sample-asset.txt",
"",
),
(
HttpRequest {
method: "GET".to_string(),
url: "https://canister.com/path/123?foo=test%20component&bar=1".to_string(),
headers: vec![],
body: vec![],
},
"/path/123",
"foo=test%20component&bar=1",
),
(
HttpRequest {
method: "GET".to_string(),
url: "https://canister.com/a%20file.txt".to_string(),
headers: vec![],
body: vec![],
},
"/a file.txt",
"",
),
(
HttpRequest {
method: "GET".to_string(),
url: "https://canister.com/mujin0722/3888-zjfrd-tqaaa-aaaaf-qakia-cai/%E6%97%A0%E8%AE%BA%E7%BE%8E%E8%81%94%E5%82%A8%E6%98%AF%E5%90%A6%E5%8A%A0%E6%81%AFbtc%E4%BB%8D%E5%B0%86%E5%9B%9E%E5%88%B07%E4%B8%87%E5%88%80".to_string(),
headers: vec![],
body: vec![],
},
"/mujin0722/3888-zjfrd-tqaaa-aaaaf-qakia-cai/无论美联储是否加息btc仍将回到7万刀",
"",
),
];

for (req, expected_path, expected_query) in test_requests.iter() {
let path = req.get_path().unwrap();
let query = req.get_query().unwrap();

assert_eq!(path, *expected_path);
assert_eq!(query.unwrap_or_default(), *expected_query);
}
}
}
14 changes: 14 additions & 0 deletions packages/ic-http-certification/src/http/http_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::HeaderField;
use candid::{CandidType, Deserialize};

/// A Candid-encodable representation of an HTTP response.
/// This struct is used by canisters that implement the HTTP interface required by the HTTP Gateway Protocol.
#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)]
pub struct HttpResponse {
/// HTTP response status code.
pub status_code: u16,
/// HTTP response headers.
pub headers: Vec<HeaderField>,
/// Response body as an array of bytes.
pub body: Vec<u8>,
}
11 changes: 11 additions & 0 deletions packages/ic-http-certification/src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! The HTTP module contains types for representing HTTP requests and responses in Rust.
//! These types are Candid-encodable and are used by canisters that implement the
//! HTTP interface required by the HTTP Gateway Protocol.
mod header_field;
mod http_request;
mod http_response;

pub use header_field::*;
pub use http_request::*;
pub use http_response::*;
4 changes: 4 additions & 0 deletions packages/ic-http-certification/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,7 @@ Typically these requests have been routed through `raw` Internet Computer URLs i

pub mod cel;
pub use cel::{DefaultCelBuilder, DefaultResponseCertification};
pub mod error;
pub use error::*;
pub mod http;
pub use crate::http::*;
1 change: 1 addition & 0 deletions packages/ic-response-verification-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ ic-agent.workspace = true
ic-utils.workspace = true
tokio.workspace = true
ic-response-verification.workspace = true
ic-http-certification.workspace = true
anyhow.workspace = true
9 changes: 5 additions & 4 deletions packages/ic-response-verification-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::agent::create_agent;
use anyhow::{anyhow, Result};
use ic_agent::export::Principal;
use ic_agent::Agent;
use ic_response_verification::types::{Request, Response, VerificationInfo};
use ic_http_certification::{HttpRequest, HttpResponse};
use ic_response_verification::types::VerificationInfo;
use ic_utils::call::SyncCall;
use ic_utils::interfaces::http_request::HeaderField;
use ic_utils::interfaces::HttpRequestCanister;
Expand Down Expand Up @@ -150,7 +151,7 @@ async fn perform_test(
path: &str,
certificate_version: Option<&u16>,
agent: &Agent,
) -> Result<(VerificationInfo, Response)> {
) -> Result<(VerificationInfo, HttpResponse)> {
let canister_id = Principal::from_text(canister_id)?;
let canister_interface = HttpRequestCanister::create(agent, canister_id);

Expand All @@ -159,13 +160,13 @@ async fn perform_test(
.call()
.await?;

let request = Request {
let request = HttpRequest {
method: "GET".into(),
headers: vec![],
url: path.into(),
body: vec![],
};
let response = Response {
let response = HttpResponse {
headers: response
.headers
.iter()
Expand Down
1 change: 1 addition & 0 deletions packages/ic-response-verification-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ wasm-opt = ["-Oz", "--enable-mutable-globals"]

[dependencies]
ic-response-verification = { workspace = true, features = ["js"] }
ic-http-certification.workspace = true
console_error_panic_hook.workspace = true
js-sys.workspace = true
wasm-bindgen.workspace = true
Expand Down
Loading

0 comments on commit 78e7b4a

Please sign in to comment.