runestick_http/
lib.rs

1//! HTTP module for runestick based on reqwest.
2//!
3//! ## Usage
4//!
5//! Add the following to your `Cargo.toml`:
6//!
7//! ```toml
8//! runestick = "0.2"
9//! runestick-http = "0.2"
10//! # not necessary, but useful
11//! runestick-json = "0.2"
12//! ```
13//!
14//! Install it into your context:
15//!
16//! ```rust
17//! # fn main() -> runestick::Result<()> {
18//! let mut context = runestick::Context::with_default_packages()?;
19//! context.install(&runestick_http::module()?)?;
20//! context.install(&runestick_json::module()?)?;
21//! # Ok(())
22//! # }
23//! ```
24//!
25//! Use it in Rune:
26//!
27//! ```rust,ignore
28//! use http;
29//! use json;
30//!
31//! fn main() {
32//!     let client = http::Client::new();
33//!     let response = client.get("http://worldtimeapi.org/api/ip");
34//!     let text = response.text();
35//!     let json = json::from_string(text);
36//!
37//!     let timezone = json["timezone"];
38//!
39//!     if timezone is String {
40//!         dbg(timezone);
41//!     }
42//!
43//!     let body = json::to_bytes(#{"hello": "world"});
44//!
45//!     let response = client.post("https://postman-echo.com/post")
46//!         .body_bytes(body)
47//!         .send();
48//!
49//!     let response = json::from_string(response.text());
50//!     dbg(response);
51//! }
52//! ```
53
54use runestick::Bytes;
55use std::fmt;
56use std::fmt::Write as _;
57
58#[derive(Debug)]
59pub struct Error {
60    inner: reqwest::Error,
61}
62
63impl From<reqwest::Error> for Error {
64    fn from(inner: reqwest::Error) -> Self {
65        Self { inner }
66    }
67}
68
69#[derive(Debug)]
70struct Client {
71    client: reqwest::Client,
72}
73
74#[derive(Debug)]
75pub struct Response {
76    response: reqwest::Response,
77}
78
79#[derive(Debug)]
80pub struct StatusCode {
81    inner: reqwest::StatusCode,
82}
83
84impl StatusCode {
85    fn display(&self, buf: &mut String) -> fmt::Result {
86        write!(buf, "{}", self.inner)
87    }
88}
89
90impl Response {
91    async fn text(self) -> Result<String, Error> {
92        let text = self.response.text().await?;
93        Ok(text)
94    }
95
96    /// Get the status code of the response.
97    fn status(&self) -> StatusCode {
98        let inner = self.response.status();
99
100        StatusCode { inner }
101    }
102}
103
104#[derive(Debug)]
105pub struct RequestBuilder {
106    request: reqwest::RequestBuilder,
107}
108
109impl RequestBuilder {
110    /// Send the request being built.
111    async fn send(self) -> Result<Response, Error> {
112        let response = self.request.send().await?;
113        Ok(Response { response })
114    }
115
116    /// Modify a header in the request.
117    fn header(self, key: &str, value: &str) -> Self {
118        Self {
119            request: self.request.header(key, value),
120        }
121    }
122
123    /// Set the request body from bytes.
124    async fn body_bytes(self, bytes: Bytes) -> Result<Self, Error> {
125        let bytes = bytes.into_vec();
126
127        Ok(Self {
128            request: self.request.body(bytes),
129        })
130    }
131}
132
133impl Client {
134    fn new() -> Self {
135        Self {
136            client: reqwest::Client::new(),
137        }
138    }
139
140    /// Construct a builder to GET the given URL.
141    async fn get(&self, url: &str) -> Result<RequestBuilder, Error> {
142        let request = self.client.get(url);
143        Ok(RequestBuilder { request })
144    }
145
146    /// Construct a builder to POST to the given URL.
147    async fn post(&self, url: &str) -> Result<RequestBuilder, Error> {
148        let request = self.client.post(url);
149        Ok(RequestBuilder { request })
150    }
151}
152
153/// Shorthand for generating a get request.
154async fn get(url: &str) -> Result<Response, Error> {
155    Ok(Response {
156        response: reqwest::get(url).await?,
157    })
158}
159
160runestick::decl_external!(Error);
161runestick::decl_external!(Client);
162runestick::decl_external!(Response);
163runestick::decl_external!(RequestBuilder);
164runestick::decl_external!(StatusCode);
165
166/// Construct the http library.
167pub fn module() -> Result<runestick::Module, runestick::ContextError> {
168    let mut module = runestick::Module::new(&["http"]);
169
170    module.ty(&["Client"]).build::<Client>()?;
171    module.ty(&["Response"]).build::<Response>()?;
172    module.ty(&["RequestBuilder"]).build::<RequestBuilder>()?;
173    module.ty(&["StatusCode"]).build::<StatusCode>()?;
174    module.ty(&["Error"]).build::<Error>()?;
175
176    module.function(&["Client", "new"], Client::new)?;
177    module.async_function(&["get"], get)?;
178
179    module.async_inst_fn("get", Client::get)?;
180    module.async_inst_fn("post", Client::post)?;
181
182    module.async_inst_fn("text", Response::text)?;
183    module.inst_fn("status", Response::status)?;
184
185    module.async_inst_fn("send", RequestBuilder::send)?;
186    module.inst_fn("header", RequestBuilder::header)?;
187    module.async_inst_fn("body_bytes", RequestBuilder::body_bytes)?;
188
189    module.inst_fn(runestick::STRING_DISPLAY, StatusCode::display)?;
190    Ok(module)
191}