Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/http-plugin-rs-cd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: http-plugin-rs-cd

on:
pull_request:
types: [closed]
branches:
- main
paths:
- "implementations/rs/**"

jobs:
cd:
name: http-plugin-rs-cd
if: |
github.event.pull_request.merged &&
endsWith(github.event.pull_request.title, '/release/rs') &&
github.event.pull_request.user.login != 'github-actions'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Read .nvmrc
run: echo ::set-output name=NVMRC::$(cat .nvmrc)
id: nvm

- name: Setup Node.js
uses: actions/setup-node@master
with:
node-version: '${{ steps.nvm.outputs.NVMRC }}'

- name: Install dependencies
run: yarn install --nonInteractive --frozen-lockfile --prefer-offline
working-directory: ./implementations/rs

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true

- name: Publish
run: cargo publish --token {{ env.CRATES_TOKEN }}
working-directory: ./implementations/rs
env:
CRATES_TOKEN: ${{ secrets.POLYWRAP_BUILD_BOT_CRATES_PAT }}
43 changes: 43 additions & 0 deletions .github/workflows/http-plugin-rs-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: http-plugin-rs-ci

on:
pull_request:
paths:
- "implementations/rs/**"

jobs:
ci:
name: http-plugin-rs-ci
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Read .nvmrc
run: echo ::set-output name=NVMRC::$(cat .nvmrc)
id: nvm
working-directory: ./implementations/rs

- name: Setup Node.js
uses: actions/setup-node@master
with:
node-version: '${{ steps.nvm.outputs.NVMRC }}'

- name: Install dependencies
run: yarn install --nonInteractive --frozen-lockfile --prefer-offline
working-directory: ./implementations/rs

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true

- name: Build
run: cargo build
working-directory: ./implementations/rs

- name: Test
run: cargo test
working-directory: ./implementations/rs
1 change: 1 addition & 0 deletions implementations/rs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!src/wrap
21 changes: 21 additions & 0 deletions implementations/rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "polywrap_http_plugin"
version = "0.0.1-beta.4"
license = "MIT"
description = "Polywrap rust HTTP plugin"
edition = "2021"

[dependencies]
polywrap_core = { version = "0.1.6-beta" }
polywrap_plugin = { version = "0.1.6-beta" }
polywrap_msgpack = { version = "0.1.6-beta" }
polywrap_resolvers = { version = "0.1.6-beta" }
wrap_manifest_schemas = { version = "0.1.6-beta" }

ureq = "2.5.0"
serde = {version = "1.0.145", features = ["derive"]}
base64 = "0.13.0"
multipart = "0.17.0"

[dev-dependencies]
polywrap_client = { version = "0.1.6-beta" }
11 changes: 11 additions & 0 deletions implementations/rs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@polywrap/http-rs",
"description": "Polywrap HTTP Plugin in Rust",
"version": "0.1.0",
"scripts": {
"codegen": "npx polywrap codegen"
},
"devDependencies": {
"polywrap": "0.10.3"
}
}
7 changes: 7 additions & 0 deletions implementations/rs/polywrap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
format: 0.3.0
project:
type: plugin/rust
name: Http
source:
module: ./Cargo.toml
schema: ./src/schema.graphql
131 changes: 131 additions & 0 deletions implementations/rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use crate::{wrap::wrap_info::get_manifest, parse_request::parse_request, parse_response::parse_response};
use multipart::client::lazy::Multipart;
use polywrap_core::invoker::Invoker;
use polywrap_plugin::{error::PluginError, implementor::plugin_impl, JSON};
use ureq::{Request as UreqRequest, Response as UreqResponse};
use std::{io::Cursor, sync::Arc};
use wrap::{
module::{ArgsGet, ArgsPost, Module},
types::{Response, ResponseType, FormDataEntry},
};
pub mod wrap;
pub mod parse_response;
pub mod parse_request;

pub enum RequestMethod {
GET,
POST,
}

#[derive(Debug)]
pub struct HttpPlugin;

#[plugin_impl]
impl Module for HttpPlugin {
fn get(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general for wrappers (like we talked about on the call), I'd love to see more unit tests where we mock the deps (e.g. the subinvocations or in this case the network call).
This not only helps us test the code more thoroughly and without the client dep, but also I find it helps while developing.

&mut self,
args: &ArgsGet,
_: Arc<dyn Invoker>,
) -> Result<Option<Response>, PluginError> {
let response = parse_request(&args.url, args.request.clone(), RequestMethod::GET)
.unwrap()
.call()
.map_err(|e| PluginError::InvocationError {
exception: e.to_string(),
})?;

let response_type = if let Some(r) = &args.request {
r.response_type
} else {
ResponseType::TEXT
};

let parsed_response = parse_response(response, response_type)?;

Ok(Some(parsed_response))
}

fn post(
&mut self,
args: &ArgsPost,
_: Arc<dyn Invoker>,
) -> Result<Option<Response>, PluginError> {
let request = parse_request(
&args.url,
args.request.clone(),
RequestMethod::POST,
)
.unwrap();

let response_type = if let Some(r) = &args.request {
r.response_type
} else {
ResponseType::TEXT
};

let response = if let Some(r) = &args.request {
if let Some(body) = &r.body {
handle_json(request, body)?
} else if let Some(form_data) = &r.form_data {
handle_form_data(request, form_data)?
} else {
request.call().map_err(|e| PluginError::InvocationError {
exception: e.to_string(),
})?
}
} else {
request.call().map_err(|e| PluginError::InvocationError {
exception: e.to_string(),
})?
};
let parsed_response = parse_response(response, response_type)?;

Ok(Some(parsed_response))
}
}

fn handle_form_data(request: UreqRequest, form_data: &Vec<FormDataEntry>) -> Result<UreqResponse, PluginError> {
let mut multipart = Multipart::new();
for entry in form_data.iter() {
if entry._type.is_some() {
if let Some(v) = &entry.value {
let buf = base64::decode(v).unwrap();
let cursor = Cursor::new(buf);
let file_name = if let Some(f) = &entry.file_name {
Some(f.as_str())
} else {
None
};
multipart.add_stream(entry.name.as_str(), cursor, file_name, None);
};
} else {
if let Some(v) = &entry.value {
multipart.add_text(entry.name.as_str(), v);
};
}
}
// Send the request with the multipart/form-data
let mdata = multipart.prepare().unwrap();
request
.set(
"Content-Type",
&format!("multipart/form-data; boundary={}", mdata.boundary()),
)
.send(mdata)
.map_err(|e| PluginError::InvocationError {
exception: e.to_string(),
})
}

fn handle_json(request: UreqRequest, body: &String) -> Result<UreqResponse, PluginError> {
let value = JSON::from_str::<JSON::Value>(body.as_str());
if let Ok(json) = value {
request
.send_json(json)
.map_err(|e| PluginError::InvocationError {
exception: e.to_string(),
})
} else {
return Err(PluginError::JSONError(value.unwrap_err()));
}
}
30 changes: 30 additions & 0 deletions implementations/rs/src/parse_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use polywrap_plugin::error::PluginError;

use crate::{RequestMethod, wrap::types::Request};

pub fn parse_request(
url: &str,
request: Option<Request>,
method: RequestMethod,
) -> Result<ureq::Request, PluginError> {
let mut request_builder = match method {
RequestMethod::GET => ureq::get(url),
RequestMethod::POST => ureq::post(url),
};

if let Some(request) = request {
if let Some(url_params) = request.url_params {
for (key, value) in url_params.0.iter() {
request_builder = request_builder.query(key, value);
}
};

if let Some(headers) = request.headers {
for (name, value) in headers.0.iter() {
request_builder = request_builder.set(name, value)
}
}
}

Ok(request_builder)
}
40 changes: 40 additions & 0 deletions implementations/rs/src/parse_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::collections::BTreeMap;

use polywrap_plugin::{Map, error::PluginError};

use crate::wrap::types::{ResponseType, Response};

pub fn parse_response(
response: ureq::Response,
encoding: ResponseType,
) -> Result<Response, PluginError> {
let headers = response
.headers_names()
.iter()
.map(|header_name| {
(
header_name.to_string(),
response.header(header_name).unwrap().to_string(),
)
})
.collect::<BTreeMap<String, String>>();
let headers = Map(headers);
let status = response.status();
let status_text = response.status_text().to_string();

let mut reader = response.into_reader();
let mut data = vec![];
reader.read_to_end(&mut data).unwrap();

let data = match encoding {
ResponseType::BINARY => base64::encode(data),
_ => String::from_utf8_lossy(&data).to_string(),
};

Ok(Response {
status: status.into(),
status_text,
headers: Some(headers),
body: Some(data),
})
}
1 change: 1 addition & 0 deletions implementations/rs/src/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#import * from "ens/wraps.eth:http@1.1.0"
7 changes: 7 additions & 0 deletions implementations/rs/src/wrap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// NOTE: This is an auto-generated file.
/// All modifications will be overwritten.

pub mod types;
#[path = "wrap.info.rs"]
pub mod wrap_info;
pub mod module;
26 changes: 26 additions & 0 deletions implementations/rs/src/wrap/module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// NOTE: This is an auto-generated file.
/// All modifications will be overwritten.

use std::sync::Arc;
use polywrap_core::invoker::Invoker;
use polywrap_plugin::{error::PluginError, module::PluginModule};
use serde::{Serialize, Deserialize};
use super::types::*;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ArgsGet {
pub url: String,
pub request: Option<Request>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ArgsPost {
pub url: String,
pub request: Option<Request>,
}

pub trait Module: PluginModule {
fn get(&mut self, args: &ArgsGet, invoker: Arc<dyn Invoker>) -> Result<Option<Response>, PluginError>;

fn post(&mut self, args: &ArgsPost, invoker: Arc<dyn Invoker>) -> Result<Option<Response>, PluginError>;
}
Loading