stk_http/
lib.rs

1//! HTTP module for stk based on reqwest.
2//!
3//! ## Usage
4//!
5//! Add the following to your `Cargo.toml`:
6//!
7//! ```toml
8//! stk = "0.2"
9//! stk-http = "0.2"
10//! # not necessary, but useful
11//! stk-json = "0.2"
12//! ```
13//!
14//! Install it into your context:
15//!
16//! ```rust
17//! # fn main() -> stk::Result<()> {
18//! let mut context = stk::Context::with_default_packages()?;
19//! context.install(stk_http::module()?)?;
20//! context.install(stk_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 stk::packages::bytes::Bytes;
55
56#[derive(Debug)]
57struct Client {
58    client: reqwest::Client,
59}
60
61#[derive(Debug)]
62pub struct Response {
63    response: reqwest::Response,
64}
65
66impl Response {
67    async fn text(self) -> stk::Result<String> {
68        let text = self.response.text().await?;
69        Ok(text)
70    }
71}
72
73#[derive(Debug)]
74pub struct RequestBuilder {
75    request: reqwest::RequestBuilder,
76}
77
78impl RequestBuilder {
79    /// Send the request being built.
80    async fn send(self) -> stk::Result<Response> {
81        let response = self.request.send().await?;
82        Ok(Response { response })
83    }
84
85    async fn body_bytes(self, bytes: Bytes) -> stk::Result<Self> {
86        let bytes = bytes.into_inner();
87
88        Ok(Self {
89            request: self.request.body(bytes),
90        })
91    }
92}
93
94impl Client {
95    fn new() -> Self {
96        Self {
97            client: reqwest::Client::new(),
98        }
99    }
100
101    /// Construct a builder to GET the given URL.
102    async fn get(&self, url: &str) -> stk::Result<RequestBuilder> {
103        let request = self.client.get(url);
104        Ok(RequestBuilder { request })
105    }
106
107    /// Construct a builder to POST to the given URL.
108    async fn post(&self, url: &str) -> stk::Result<RequestBuilder> {
109        let request = self.client.post(url);
110        Ok(RequestBuilder { request })
111    }
112}
113
114/// Shorthand for generating a get request.
115async fn get(url: &str) -> stk::Result<Response> {
116    Ok(Response {
117        response: reqwest::get(url).await?,
118    })
119}
120
121stk::decl_external!(Client);
122stk::decl_external!(Response);
123stk::decl_external!(RequestBuilder);
124
125/// Construct the http library.
126pub fn module() -> Result<stk::Module, stk::ContextError> {
127    let mut module = stk::Module::new(&["http"]);
128
129    module.ty(&["Client"]).build::<Client>()?;
130    module.ty(&["Response"]).build::<Response>()?;
131    module.ty(&["RequestBuilder"]).build::<RequestBuilder>()?;
132
133    module.function(&["Client", "new"], Client::new)?;
134    module.async_function(&["get"], get)?;
135
136    module.async_inst_fn("get", Client::get)?;
137    module.async_inst_fn("post", Client::post)?;
138
139    module.async_inst_fn("text", Response::text)?;
140
141    module.async_inst_fn("send", RequestBuilder::send)?;
142    module.async_inst_fn("body_bytes", RequestBuilder::body_bytes)?;
143    Ok(module)
144}