convex-rust 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +137 -2
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +200 -0
- package/dist/cli/build.d.ts +146 -0
- package/dist/cli/build.js +902 -0
- package/dist/cli/demangle.d.ts +7 -0
- package/dist/cli/demangle.js +328 -0
- package/dist/cli/diagnostics.d.ts +34 -0
- package/dist/cli/diagnostics.js +296 -0
- package/dist/cli/generate.d.ts +29 -0
- package/dist/cli/generate.js +82 -0
- package/dist/cli/manifest.d.ts +24 -0
- package/dist/cli/manifest.js +75 -0
- package/dist/cli/rustApi.d.ts +78 -0
- package/dist/cli/rustApi.js +609 -0
- package/dist/cli/scaffold.d.ts +22 -0
- package/dist/cli/scaffold.js +114 -0
- package/dist/cli/shim.d.ts +30 -0
- package/dist/cli/shim.js +140 -0
- package/dist/cli/size.d.ts +17 -0
- package/dist/cli/size.js +73 -0
- package/dist/cli/split.d.ts +20 -0
- package/dist/cli/split.js +140 -0
- package/dist/cli/symbols.d.ts +30 -0
- package/dist/cli/symbols.js +133 -0
- package/dist/cli/toolchain.d.ts +53 -0
- package/dist/cli/toolchain.js +167 -0
- package/dist/cli/validators.d.ts +6 -0
- package/dist/cli/validators.js +66 -0
- package/dist/cli/wasisdk.d.ts +82 -0
- package/dist/cli/wasisdk.js +220 -0
- package/dist/cli/wasmHints.d.ts +29 -0
- package/dist/cli/wasmHints.js +172 -0
- package/dist/runtime/base64.d.ts +4 -0
- package/dist/runtime/base64.js +77 -0
- package/dist/runtime/crypto.d.ts +7 -0
- package/dist/runtime/crypto.js +421 -0
- package/dist/runtime/fastjson.d.ts +3 -0
- package/dist/runtime/fastjson.js +170 -0
- package/dist/runtime/host.d.ts +73 -0
- package/dist/runtime/host.js +1230 -0
- package/dist/runtime/http.d.ts +40 -0
- package/dist/runtime/http.js +157 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/intl.d.ts +5 -0
- package/dist/runtime/intl.js +69 -0
- package/dist/runtime/tmpfs.d.ts +129 -0
- package/dist/runtime/tmpfs.js +624 -0
- package/dist/runtime/wasi.d.ts +33 -0
- package/dist/runtime/wasi.js +216 -0
- package/package.json +66 -4
- package/rust/convex_fn/Cargo.toml +32 -0
- package/rust/convex_fn/LICENSE +201 -0
- package/rust/convex_fn/build.rs +3 -0
- package/rust/convex_fn/src/args.rs +416 -0
- package/rust/convex_fn/src/crypto.rs +237 -0
- package/rust/convex_fn/src/ctx.rs +1044 -0
- package/rust/convex_fn/src/dns.rs +356 -0
- package/rust/convex_fn/src/error.rs +179 -0
- package/rust/convex_fn/src/executor.rs +577 -0
- package/rust/convex_fn/src/fanout.rs +106 -0
- package/rust/convex_fn/src/fs.rs +19 -0
- package/rust/convex_fn/src/function_ref.rs +219 -0
- package/rust/convex_fn/src/http.rs +441 -0
- package/rust/convex_fn/src/intl.rs +75 -0
- package/rust/convex_fn/src/lib.rs +242 -0
- package/rust/convex_fn/src/logging.rs +222 -0
- package/rust/convex_fn/src/native.rs +360 -0
- package/rust/convex_fn/src/net.rs +122 -0
- package/rust/convex_fn/src/sys.rs +178 -0
- package/rust/convex_fn/src/values.rs +121 -0
- package/rust/convex_fn/src/wire.rs +579 -0
- package/rust/convex_fn_macros/Cargo.toml +13 -0
- package/rust/convex_fn_macros/LICENSE +201 -0
- package/rust/convex_fn_macros/src/derive.rs +632 -0
- package/rust/convex_fn_macros/src/lib.rs +338 -0
- package/rust/convex_reqwest/Cargo.toml +69 -0
- package/rust/convex_reqwest/LICENSE +201 -0
- package/rust/convex_reqwest/src/blocking.rs +287 -0
- package/rust/convex_reqwest/src/body.rs +152 -0
- package/rust/convex_reqwest/src/client.rs +359 -0
- package/rust/convex_reqwest/src/config_types.rs +150 -0
- package/rust/convex_reqwest/src/error.rs +241 -0
- package/rust/convex_reqwest/src/into_url.rs +57 -0
- package/rust/convex_reqwest/src/lib.rs +42 -0
- package/rust/convex_reqwest/src/multipart.rs +140 -0
- package/rust/convex_reqwest/src/request.rs +358 -0
- package/rust/convex_reqwest/src/response.rs +310 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
use std::{error::Error as StdError, fmt};
|
|
2
|
+
|
|
3
|
+
use http::StatusCode;
|
|
4
|
+
use url::Url;
|
|
5
|
+
|
|
6
|
+
pub type BoxError = Box<dyn StdError + Send + Sync>;
|
|
7
|
+
|
|
8
|
+
/// `Result<T, reqwest::Error>`.
|
|
9
|
+
pub type Result<T> = std::result::Result<T, Error>;
|
|
10
|
+
|
|
11
|
+
/// The errors that may occur when processing a `Request`.
|
|
12
|
+
pub struct Error {
|
|
13
|
+
inner: Box<Inner>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
struct Inner {
|
|
17
|
+
kind: Kind,
|
|
18
|
+
source: Option<BoxError>,
|
|
19
|
+
url: Option<Url>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
23
|
+
pub(crate) enum Kind {
|
|
24
|
+
Builder,
|
|
25
|
+
Request,
|
|
26
|
+
#[allow(dead_code)]
|
|
27
|
+
Redirect,
|
|
28
|
+
Status(StatusCode),
|
|
29
|
+
Body,
|
|
30
|
+
Decode,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Marker source for deadline expiry; `Error::is_timeout` looks for it.
|
|
34
|
+
#[derive(Debug)]
|
|
35
|
+
pub(crate) struct TimedOut;
|
|
36
|
+
|
|
37
|
+
impl fmt::Display for TimedOut {
|
|
38
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
39
|
+
f.write_str("operation timed out")
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
impl StdError for TimedOut {}
|
|
43
|
+
|
|
44
|
+
/// Marker source for a failed host fetch (the closest thing to a connect error).
|
|
45
|
+
#[derive(Debug)]
|
|
46
|
+
pub(crate) struct FetchFailed(pub(crate) String);
|
|
47
|
+
|
|
48
|
+
impl fmt::Display for FetchFailed {
|
|
49
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
50
|
+
f.write_str(&self.0)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
impl StdError for FetchFailed {}
|
|
54
|
+
|
|
55
|
+
#[derive(Debug)]
|
|
56
|
+
struct Message(String);
|
|
57
|
+
impl fmt::Display for Message {
|
|
58
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
59
|
+
f.write_str(&self.0)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
impl StdError for Message {}
|
|
63
|
+
|
|
64
|
+
impl Error {
|
|
65
|
+
pub(crate) fn new<E: Into<BoxError>>(kind: Kind, source: Option<E>) -> Error {
|
|
66
|
+
Error {
|
|
67
|
+
inner: Box::new(Inner {
|
|
68
|
+
kind,
|
|
69
|
+
source: source.map(Into::into),
|
|
70
|
+
url: None,
|
|
71
|
+
}),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
|
|
76
|
+
Error::new(Kind::Builder, Some(e))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub(crate) fn builder_msg(m: impl Into<String>) -> Error {
|
|
80
|
+
Error::builder(Message(m.into()))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
|
|
84
|
+
Error::new(Kind::Request, Some(e))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
|
|
88
|
+
Error::new(Kind::Body, Some(e))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
|
|
92
|
+
Error::new(Kind::Decode, Some(e))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub(crate) fn timeout(kind: Kind) -> Error {
|
|
96
|
+
Error::new(kind, Some(TimedOut))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pub(crate) fn status_code(url: Url, status: StatusCode) -> Error {
|
|
100
|
+
Error::new(Kind::Status(status), None::<BoxError>).with_url(url)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Returns a possible URL related to this error.
|
|
104
|
+
pub fn url(&self) -> Option<&Url> {
|
|
105
|
+
self.inner.url.as_ref()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Returns a mutable reference to the URL related to this error.
|
|
109
|
+
pub fn url_mut(&mut self) -> Option<&mut Url> {
|
|
110
|
+
self.inner.url.as_mut()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Adds a url related to this error (overwriting any existing).
|
|
114
|
+
pub fn with_url(mut self, url: Url) -> Self {
|
|
115
|
+
self.inner.url = Some(url);
|
|
116
|
+
self
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Strips the related url from this error (if, for example, it contains
|
|
120
|
+
/// sensitive information).
|
|
121
|
+
pub fn without_url(mut self) -> Self {
|
|
122
|
+
self.inner.url = None;
|
|
123
|
+
self
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Returns true if the error is from a type `Builder`.
|
|
127
|
+
pub fn is_builder(&self) -> bool {
|
|
128
|
+
matches!(self.inner.kind, Kind::Builder)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Returns true if the error is from a `RedirectPolicy`.
|
|
132
|
+
pub fn is_redirect(&self) -> bool {
|
|
133
|
+
matches!(self.inner.kind, Kind::Redirect)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Returns true if the error is from `Response::error_for_status`.
|
|
137
|
+
pub fn is_status(&self) -> bool {
|
|
138
|
+
matches!(self.inner.kind, Kind::Status(_))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Returns true if the error is related to a timeout.
|
|
142
|
+
pub fn is_timeout(&self) -> bool {
|
|
143
|
+
let mut source = self.inner.source.as_deref().map(|e| e as &dyn StdError);
|
|
144
|
+
while let Some(err) = source {
|
|
145
|
+
if err.is::<TimedOut>() {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
source = err.source();
|
|
149
|
+
}
|
|
150
|
+
false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Returns true if the error is related to the request.
|
|
154
|
+
pub fn is_request(&self) -> bool {
|
|
155
|
+
matches!(self.inner.kind, Kind::Request)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Returns true if the error is related to connect (here: the host's
|
|
159
|
+
/// fetch task failed before a response head arrived).
|
|
160
|
+
pub fn is_connect(&self) -> bool {
|
|
161
|
+
let mut source = self.inner.source.as_deref().map(|e| e as &dyn StdError);
|
|
162
|
+
while let Some(err) = source {
|
|
163
|
+
if err.is::<FetchFailed>() {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
source = err.source();
|
|
167
|
+
}
|
|
168
|
+
false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Returns true if the error is related to the request or response body.
|
|
172
|
+
pub fn is_body(&self) -> bool {
|
|
173
|
+
matches!(self.inner.kind, Kind::Body)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Returns true if the error is related to decoding the response's body.
|
|
177
|
+
pub fn is_decode(&self) -> bool {
|
|
178
|
+
matches!(self.inner.kind, Kind::Decode)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Returns the status code, if the error was generated from a response.
|
|
182
|
+
pub fn status(&self) -> Option<StatusCode> {
|
|
183
|
+
match self.inner.kind {
|
|
184
|
+
Kind::Status(code) => Some(code),
|
|
185
|
+
_ => None,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
impl fmt::Debug for Error {
|
|
191
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
192
|
+
let mut b = f.debug_struct("reqwest::Error");
|
|
193
|
+
b.field("kind", &self.inner.kind);
|
|
194
|
+
if let Some(url) = &self.inner.url {
|
|
195
|
+
b.field("url", &url.as_str());
|
|
196
|
+
}
|
|
197
|
+
if let Some(source) = &self.inner.source {
|
|
198
|
+
b.field("source", source);
|
|
199
|
+
}
|
|
200
|
+
b.finish()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
impl fmt::Display for Error {
|
|
205
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
206
|
+
match self.inner.kind {
|
|
207
|
+
Kind::Builder => f.write_str("builder error")?,
|
|
208
|
+
Kind::Request => f.write_str("error sending request")?,
|
|
209
|
+
Kind::Body => f.write_str("request or response body error")?,
|
|
210
|
+
Kind::Decode => f.write_str("error decoding response body")?,
|
|
211
|
+
Kind::Redirect => f.write_str("error following redirect")?,
|
|
212
|
+
Kind::Status(code) => {
|
|
213
|
+
let prefix = if code.is_client_error() {
|
|
214
|
+
"HTTP status client error"
|
|
215
|
+
} else {
|
|
216
|
+
"HTTP status server error"
|
|
217
|
+
};
|
|
218
|
+
write!(f, "{prefix} ({code})")?;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if let Some(url) = &self.inner.url {
|
|
222
|
+
write!(f, " for url ({url})")?;
|
|
223
|
+
}
|
|
224
|
+
if let Some(e) = &self.inner.source {
|
|
225
|
+
write!(f, ": {e}")?;
|
|
226
|
+
}
|
|
227
|
+
Ok(())
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
impl StdError for Error {
|
|
232
|
+
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
|
233
|
+
self.inner.source.as_ref().map(|e| &**e as _)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
impl From<Error> for std::io::Error {
|
|
238
|
+
fn from(e: Error) -> Self {
|
|
239
|
+
std::io::Error::other(e)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
use url::Url;
|
|
2
|
+
|
|
3
|
+
use crate::error::{Error, Result};
|
|
4
|
+
|
|
5
|
+
/// A trait to try to convert some type into a `Url`.
|
|
6
|
+
pub trait IntoUrl: IntoUrlSealed {}
|
|
7
|
+
|
|
8
|
+
impl IntoUrl for Url {}
|
|
9
|
+
impl IntoUrl for String {}
|
|
10
|
+
impl IntoUrl for &str {}
|
|
11
|
+
impl IntoUrl for &String {}
|
|
12
|
+
|
|
13
|
+
#[doc(hidden)]
|
|
14
|
+
pub trait IntoUrlSealed {
|
|
15
|
+
fn into_url(self) -> Result<Url>;
|
|
16
|
+
fn as_str(&self) -> &str;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl IntoUrlSealed for Url {
|
|
20
|
+
fn into_url(self) -> Result<Url> {
|
|
21
|
+
if self.has_host() {
|
|
22
|
+
Ok(self)
|
|
23
|
+
} else {
|
|
24
|
+
Err(Error::builder_msg("URL scheme is not allowed").with_url(self))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
fn as_str(&self) -> &str {
|
|
28
|
+
self.as_ref()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl IntoUrlSealed for &str {
|
|
33
|
+
fn into_url(self) -> Result<Url> {
|
|
34
|
+
Url::parse(self).map_err(Error::builder)?.into_url()
|
|
35
|
+
}
|
|
36
|
+
fn as_str(&self) -> &str {
|
|
37
|
+
self
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl IntoUrlSealed for &String {
|
|
42
|
+
fn into_url(self) -> Result<Url> {
|
|
43
|
+
(&**self).into_url()
|
|
44
|
+
}
|
|
45
|
+
fn as_str(&self) -> &str {
|
|
46
|
+
self.as_ref()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl IntoUrlSealed for String {
|
|
51
|
+
fn into_url(self) -> Result<Url> {
|
|
52
|
+
(&*self).into_url()
|
|
53
|
+
}
|
|
54
|
+
fn as_str(&self) -> &str {
|
|
55
|
+
self.as_ref()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//! `reqwest`-compatible HTTP client for `"use rust"` actions (DX-06, D-010).
|
|
2
|
+
//!
|
|
3
|
+
//! This crate is named `reqwest` and is swapped in with
|
|
4
|
+
//! `[patch.crates-io] reqwest = { path = ... }` (the Convex CLI writes that
|
|
5
|
+
//! into `convex/rust/.cargo/config.toml`). Vendor crates built on reqwest then
|
|
6
|
+
//! send their requests through `convex_fn`'s `Fetch` task, so they keep the
|
|
7
|
+
//! host's egress policy, limits, metering and logs.
|
|
8
|
+
//!
|
|
9
|
+
//! Notes on fidelity:
|
|
10
|
+
//! - Needs the SDK's tokio runtime (`convex_fn` feature `tokio-rt`, enabled by
|
|
11
|
+
//! this crate): timeouts use `tokio::time`.
|
|
12
|
+
//! - TLS, proxies, pooling, compression, cookies and redirects are the host's;
|
|
13
|
+
//! the builder options for them are accepted and ignored.
|
|
14
|
+
//! - Response bodies arrive in the chunks the host delivers.
|
|
15
|
+
//! - Only UTF-8 text decoding is supported.
|
|
16
|
+
|
|
17
|
+
pub mod blocking;
|
|
18
|
+
mod body;
|
|
19
|
+
mod client;
|
|
20
|
+
mod config_types;
|
|
21
|
+
mod error;
|
|
22
|
+
mod into_url;
|
|
23
|
+
pub mod multipart;
|
|
24
|
+
mod request;
|
|
25
|
+
mod response;
|
|
26
|
+
|
|
27
|
+
pub use bytes::Bytes;
|
|
28
|
+
pub use http::{header, Method, StatusCode, Version};
|
|
29
|
+
pub use url::Url;
|
|
30
|
+
|
|
31
|
+
pub use self::body::Body;
|
|
32
|
+
pub use self::client::{Client, ClientBuilder};
|
|
33
|
+
pub use self::config_types::{redirect, tls, Proxy};
|
|
34
|
+
pub use self::error::{Error, Result};
|
|
35
|
+
pub use self::into_url::IntoUrl;
|
|
36
|
+
pub use self::request::{Request, RequestBuilder};
|
|
37
|
+
pub use self::response::{Response, ResponseBuilderExt};
|
|
38
|
+
|
|
39
|
+
/// Shortcut method to quickly make a `GET` request.
|
|
40
|
+
pub async fn get<T: IntoUrl>(url: T) -> Result<Response> {
|
|
41
|
+
Client::builder().build()?.get(url).send().await
|
|
42
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
//! `multipart/form-data` bodies, buffered (the host fetch task takes one body).
|
|
2
|
+
|
|
3
|
+
use std::{
|
|
4
|
+
borrow::Cow,
|
|
5
|
+
sync::atomic::{AtomicU64, Ordering},
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use http::header::HeaderMap;
|
|
9
|
+
|
|
10
|
+
use crate::error::{Error, Result};
|
|
11
|
+
|
|
12
|
+
/// A multipart/form-data request body.
|
|
13
|
+
#[derive(Debug)]
|
|
14
|
+
pub struct Form {
|
|
15
|
+
boundary: String,
|
|
16
|
+
parts: Vec<(Cow<'static, str>, Part)>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// A field in a multipart form.
|
|
20
|
+
#[derive(Debug)]
|
|
21
|
+
pub struct Part {
|
|
22
|
+
data: Vec<u8>,
|
|
23
|
+
mime: Option<String>,
|
|
24
|
+
file_name: Option<Cow<'static, str>>,
|
|
25
|
+
headers: HeaderMap,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl Default for Form {
|
|
29
|
+
fn default() -> Self {
|
|
30
|
+
Self::new()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl Form {
|
|
35
|
+
pub fn new() -> Form {
|
|
36
|
+
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
37
|
+
let nanos = std::time::SystemTime::now()
|
|
38
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
39
|
+
.map_or(0, |d| d.as_nanos() as u64);
|
|
40
|
+
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
41
|
+
Form {
|
|
42
|
+
boundary: format!("{nanos:016x}{n:016x}"),
|
|
43
|
+
parts: vec![],
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn boundary(&self) -> &str {
|
|
48
|
+
&self.boundary
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn text<T, U>(self, name: T, value: U) -> Form
|
|
52
|
+
where
|
|
53
|
+
T: Into<Cow<'static, str>>,
|
|
54
|
+
U: Into<Cow<'static, str>>,
|
|
55
|
+
{
|
|
56
|
+
self.part(name, Part::text(value))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn part<T: Into<Cow<'static, str>>>(mut self, name: T, part: Part) -> Form {
|
|
60
|
+
self.parts.push((name.into(), part));
|
|
61
|
+
self
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub(crate) fn into_bytes(self) -> Vec<u8> {
|
|
65
|
+
let mut out = Vec::new();
|
|
66
|
+
for (name, part) in &self.parts {
|
|
67
|
+
out.extend_from_slice(format!("--{}\r\n", self.boundary).as_bytes());
|
|
68
|
+
let mut disp = format!("Content-Disposition: form-data; name=\"{}\"", escape(name));
|
|
69
|
+
if let Some(f) = &part.file_name {
|
|
70
|
+
disp.push_str(&format!("; filename=\"{}\"", escape(f)));
|
|
71
|
+
}
|
|
72
|
+
out.extend_from_slice(disp.as_bytes());
|
|
73
|
+
out.extend_from_slice(b"\r\n");
|
|
74
|
+
if let Some(m) = &part.mime {
|
|
75
|
+
out.extend_from_slice(format!("Content-Type: {m}\r\n").as_bytes());
|
|
76
|
+
}
|
|
77
|
+
for (k, v) in &part.headers {
|
|
78
|
+
out.extend_from_slice(k.as_str().as_bytes());
|
|
79
|
+
out.extend_from_slice(b": ");
|
|
80
|
+
out.extend_from_slice(v.as_bytes());
|
|
81
|
+
out.extend_from_slice(b"\r\n");
|
|
82
|
+
}
|
|
83
|
+
out.extend_from_slice(b"\r\n");
|
|
84
|
+
out.extend_from_slice(&part.data);
|
|
85
|
+
out.extend_from_slice(b"\r\n");
|
|
86
|
+
}
|
|
87
|
+
out.extend_from_slice(format!("--{}--\r\n", self.boundary).as_bytes());
|
|
88
|
+
out
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn escape(s: &str) -> String {
|
|
93
|
+
s.replace('\\', "\\\\")
|
|
94
|
+
.replace('"', "\\\"")
|
|
95
|
+
.replace('\r', "%0D")
|
|
96
|
+
.replace('\n', "%0A")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl Part {
|
|
100
|
+
pub fn text<T: Into<Cow<'static, str>>>(value: T) -> Part {
|
|
101
|
+
Part::bytes(value.into().into_owned().into_bytes())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn bytes<T: Into<Cow<'static, [u8]>>>(value: T) -> Part {
|
|
105
|
+
Part {
|
|
106
|
+
data: value.into().into_owned(),
|
|
107
|
+
mime: None,
|
|
108
|
+
file_name: None,
|
|
109
|
+
headers: HeaderMap::new(),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn file_name<T: Into<Cow<'static, str>>>(mut self, filename: T) -> Part {
|
|
114
|
+
self.file_name = Some(filename.into());
|
|
115
|
+
self
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
pub fn mime_str(mut self, mime: &str) -> Result<Part> {
|
|
119
|
+
let m: mime_check::Mime = mime_check::parse(mime)?;
|
|
120
|
+
self.mime = Some(m);
|
|
121
|
+
Ok(self)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub fn headers(mut self, headers: HeaderMap) -> Part {
|
|
125
|
+
self.headers = headers;
|
|
126
|
+
self
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
mod mime_check {
|
|
131
|
+
use super::*;
|
|
132
|
+
pub type Mime = String;
|
|
133
|
+
pub fn parse(s: &str) -> Result<Mime> {
|
|
134
|
+
if s.contains('/') && !s.contains(['\r', '\n']) {
|
|
135
|
+
Ok(s.to_string())
|
|
136
|
+
} else {
|
|
137
|
+
Err(Error::builder_msg(format!("invalid mime type: {s}")))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|