@tishlang/tish 1.0.7 → 1.0.11
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/Cargo.toml +43 -0
- package/LICENSE +13 -0
- package/README.md +66 -0
- package/crates/js_to_tish/Cargo.toml +9 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +61 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +608 -0
- package/crates/js_to_tish/src/transform/stmt.rs +474 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +44 -0
- package/crates/tish/src/main.rs +585 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/integration_test.rs +726 -0
- package/crates/tish_ast/Cargo.toml +7 -0
- package/crates/tish_ast/src/ast.rs +494 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +5 -0
- package/crates/tish_build_utils/src/lib.rs +175 -0
- package/crates/tish_builtins/Cargo.toml +12 -0
- package/crates/tish_builtins/src/array.rs +410 -0
- package/crates/tish_builtins/src/globals.rs +197 -0
- package/crates/tish_builtins/src/helpers.rs +38 -0
- package/crates/tish_builtins/src/lib.rs +14 -0
- package/crates/tish_builtins/src/math.rs +80 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +253 -0
- package/crates/tish_bytecode/Cargo.toml +15 -0
- package/crates/tish_bytecode/src/chunk.rs +97 -0
- package/crates/tish_bytecode/src/compiler.rs +1361 -0
- package/crates/tish_bytecode/src/encoding.rs +100 -0
- package/crates/tish_bytecode/src/lib.rs +19 -0
- package/crates/tish_bytecode/src/opcode.rs +110 -0
- package/crates/tish_bytecode/src/peephole.rs +159 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/shortcircuit.rs +49 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +21 -0
- package/crates/tish_compile/src/codegen.rs +3316 -0
- package/crates/tish_compile/src/lib.rs +71 -0
- package/crates/tish_compile/src/resolve.rs +631 -0
- package/crates/tish_compile/src/types.rs +304 -0
- package/crates/tish_compile_js/Cargo.toml +16 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +794 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/js_intrinsics.rs +82 -0
- package/crates/tish_compile_js/src/lib.rs +27 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +32 -0
- package/crates/tish_compiler_wasm/Cargo.toml +19 -0
- package/crates/tish_compiler_wasm/src/lib.rs +55 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +462 -0
- package/crates/tish_core/Cargo.toml +11 -0
- package/crates/tish_core/src/console_style.rs +128 -0
- package/crates/tish_core/src/json.rs +327 -0
- package/crates/tish_core/src/lib.rs +15 -0
- package/crates/tish_core/src/macros.rs +37 -0
- package/crates/tish_core/src/uri.rs +115 -0
- package/crates/tish_core/src/value.rs +376 -0
- package/crates/tish_cranelift/Cargo.toml +17 -0
- package/crates/tish_cranelift/src/lib.rs +41 -0
- package/crates/tish_cranelift/src/link.rs +120 -0
- package/crates/tish_cranelift/src/lower.rs +77 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +19 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +43 -0
- package/crates/tish_eval/Cargo.toml +26 -0
- package/crates/tish_eval/src/eval.rs +3205 -0
- package/crates/tish_eval/src/http.rs +122 -0
- package/crates/tish_eval/src/lib.rs +59 -0
- package/crates/tish_eval/src/natives.rs +301 -0
- package/crates/tish_eval/src/promise.rs +173 -0
- package/crates/tish_eval/src/regex.rs +298 -0
- package/crates/tish_eval/src/timers.rs +111 -0
- package/crates/tish_eval/src/value.rs +224 -0
- package/crates/tish_eval/src/value_convert.rs +85 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +884 -0
- package/crates/tish_jsx_web/Cargo.toml +7 -0
- package/crates/tish_jsx_web/README.md +18 -0
- package/crates/tish_jsx_web/src/lib.rs +157 -0
- package/crates/tish_jsx_web/vendor/Lattish.tish +347 -0
- package/crates/tish_lexer/Cargo.toml +7 -0
- package/crates/tish_lexer/src/lib.rs +430 -0
- package/crates/tish_lexer/src/token.rs +155 -0
- package/crates/tish_lint/Cargo.toml +17 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +77 -0
- package/crates/tish_lint/src/lib.rs +278 -0
- package/crates/tish_llvm/Cargo.toml +11 -0
- package/crates/tish_llvm/src/lib.rs +106 -0
- package/crates/tish_lsp/Cargo.toml +22 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/main.rs +615 -0
- package/crates/tish_native/Cargo.toml +14 -0
- package/crates/tish_native/src/build.rs +102 -0
- package/crates/tish_native/src/lib.rs +237 -0
- package/crates/tish_opt/Cargo.toml +11 -0
- package/crates/tish_opt/src/lib.rs +896 -0
- package/crates/tish_parser/Cargo.toml +9 -0
- package/crates/tish_parser/src/lib.rs +123 -0
- package/crates/tish_parser/src/parser.rs +1714 -0
- package/crates/tish_runtime/Cargo.toml +26 -0
- package/crates/tish_runtime/src/http.rs +308 -0
- package/crates/tish_runtime/src/http_fetch.rs +453 -0
- package/crates/tish_runtime/src/lib.rs +1004 -0
- package/crates/tish_runtime/src/native_promise.rs +26 -0
- package/crates/tish_runtime/src/promise.rs +77 -0
- package/crates/tish_runtime/src/promise_io.rs +41 -0
- package/crates/tish_runtime/src/timers.rs +125 -0
- package/crates/tish_runtime/src/ws.rs +725 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +99 -0
- package/crates/tish_vm/Cargo.toml +31 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +1399 -0
- package/crates/tish_wasm/Cargo.toml +13 -0
- package/crates/tish_wasm/src/lib.rs +358 -0
- package/crates/tish_wasm_runtime/Cargo.toml +25 -0
- package/crates/tish_wasm_runtime/src/lib.rs +36 -0
- package/justfile +260 -0
- package/package.json +8 -3
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
//! Web Fetch–aligned Response, ReadableStream, reader.read(), text()/json().
|
|
2
|
+
|
|
3
|
+
use std::cell::RefCell;
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use std::pin::Pin;
|
|
6
|
+
use std::rc::Rc;
|
|
7
|
+
use std::sync::{Arc, Mutex};
|
|
8
|
+
|
|
9
|
+
use bytes::Bytes;
|
|
10
|
+
use futures::Stream;
|
|
11
|
+
use futures::StreamExt;
|
|
12
|
+
use tish_core::{NativeFn, TishOpaque, TishPromise, Value};
|
|
13
|
+
|
|
14
|
+
use crate::http::{build_error_response, extract_body, extract_headers, extract_method};
|
|
15
|
+
|
|
16
|
+
// --- Promises (Send payloads only; Value built on awaiting thread) ---
|
|
17
|
+
|
|
18
|
+
struct FetchResponsePromise {
|
|
19
|
+
rx: Mutex<Option<tokio::sync::oneshot::Receiver<Result<reqwest::Response, String>>>>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl TishPromise for FetchResponsePromise {
|
|
23
|
+
fn block_until_settled(&self) -> std::result::Result<Value, Value> {
|
|
24
|
+
let rx = self.rx.lock().unwrap().take();
|
|
25
|
+
if let Some(rx) = rx {
|
|
26
|
+
let r = crate::http::block_on_http(rx);
|
|
27
|
+
match r {
|
|
28
|
+
Ok(Ok(resp)) => Ok(response_value_from_reqwest(resp)),
|
|
29
|
+
Ok(Err(e)) => Ok(build_error_response(&e)),
|
|
30
|
+
Err(_) => Err(Value::String("Promise dropped".into())),
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
Err(Value::String("Promise already consumed".into()))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
struct FetchAllResponsesPromise {
|
|
39
|
+
rx: Mutex<Option<tokio::sync::oneshot::Receiver<Result<Vec<Result<reqwest::Response, String>>, String>>>>,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl TishPromise for FetchAllResponsesPromise {
|
|
43
|
+
fn block_until_settled(&self) -> std::result::Result<Value, Value> {
|
|
44
|
+
let rx = self.rx.lock().unwrap().take();
|
|
45
|
+
if let Some(rx) = rx {
|
|
46
|
+
let r = crate::http::block_on_http(rx);
|
|
47
|
+
match r {
|
|
48
|
+
Ok(Ok(vec)) => {
|
|
49
|
+
let out: Vec<Value> = vec
|
|
50
|
+
.into_iter()
|
|
51
|
+
.map(|x| x.map(response_value_from_reqwest).unwrap_or_else(|e| build_error_response(&e)))
|
|
52
|
+
.collect();
|
|
53
|
+
Ok(Value::Array(Rc::new(RefCell::new(out))))
|
|
54
|
+
}
|
|
55
|
+
Ok(Err(e)) => Ok(build_error_response(&e)),
|
|
56
|
+
Err(_) => Err(Value::String("Promise dropped".into())),
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
Err(Value::String("Promise already consumed".into()))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
enum ReadChunk {
|
|
65
|
+
Done,
|
|
66
|
+
Bytes(Vec<u8>),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
struct ReadChunkPromise {
|
|
70
|
+
rx: Mutex<Option<tokio::sync::oneshot::Receiver<Result<ReadChunk, String>>>>,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
impl TishPromise for ReadChunkPromise {
|
|
74
|
+
fn block_until_settled(&self) -> std::result::Result<Value, Value> {
|
|
75
|
+
let rx = self.rx.lock().unwrap().take();
|
|
76
|
+
if let Some(rx) = rx {
|
|
77
|
+
let r = crate::http::block_on_http(rx);
|
|
78
|
+
match r {
|
|
79
|
+
Ok(Ok(ReadChunk::Done)) => {
|
|
80
|
+
let mut o = HashMap::new();
|
|
81
|
+
o.insert(Arc::from("done"), Value::Bool(true));
|
|
82
|
+
o.insert(Arc::from("value"), Value::Null);
|
|
83
|
+
Ok(Value::Object(Rc::new(RefCell::new(o))))
|
|
84
|
+
}
|
|
85
|
+
Ok(Ok(ReadChunk::Bytes(b))) => {
|
|
86
|
+
let arr: Vec<Value> = b.iter().map(|u| Value::Number(*u as f64)).collect();
|
|
87
|
+
let mut o = HashMap::new();
|
|
88
|
+
o.insert(Arc::from("done"), Value::Bool(false));
|
|
89
|
+
o.insert(
|
|
90
|
+
Arc::from("value"),
|
|
91
|
+
Value::Array(Rc::new(RefCell::new(arr))),
|
|
92
|
+
);
|
|
93
|
+
Ok(Value::Object(Rc::new(RefCell::new(o))))
|
|
94
|
+
}
|
|
95
|
+
Ok(Err(e)) => Err({
|
|
96
|
+
let mut obj = HashMap::new();
|
|
97
|
+
obj.insert(Arc::from("error"), Value::String(e.into()));
|
|
98
|
+
Value::Object(Rc::new(RefCell::new(obj)))
|
|
99
|
+
}),
|
|
100
|
+
Err(_) => Err(Value::String("Promise dropped".into())),
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
Err(Value::String("Promise already consumed".into()))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
struct JsonTextPromise {
|
|
109
|
+
rx: Mutex<Option<tokio::sync::oneshot::Receiver<Result<String, String>>>>,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
impl TishPromise for JsonTextPromise {
|
|
113
|
+
fn block_until_settled(&self) -> std::result::Result<Value, Value> {
|
|
114
|
+
let rx = self.rx.lock().unwrap().take();
|
|
115
|
+
if let Some(rx) = rx {
|
|
116
|
+
let r = crate::http::block_on_http(rx);
|
|
117
|
+
match r {
|
|
118
|
+
Ok(Ok(s)) => match tish_core::json_parse(&s) {
|
|
119
|
+
Ok(v) => Ok(v),
|
|
120
|
+
Err(e) => Err({
|
|
121
|
+
let mut obj = HashMap::new();
|
|
122
|
+
obj.insert(Arc::from("error"), Value::String(e.into()));
|
|
123
|
+
Value::Object(Rc::new(RefCell::new(obj)))
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
Ok(Err(e)) => Err({
|
|
127
|
+
let mut obj = HashMap::new();
|
|
128
|
+
obj.insert(Arc::from("error"), Value::String(e.into()));
|
|
129
|
+
Value::Object(Rc::new(RefCell::new(obj)))
|
|
130
|
+
}),
|
|
131
|
+
Err(_) => Err(Value::String("Promise dropped".into())),
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
Err(Value::String("Promise already consumed".into()))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Body ---
|
|
140
|
+
|
|
141
|
+
pub struct HttpBody {
|
|
142
|
+
state: Mutex<BodyState>,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
enum BodyState {
|
|
146
|
+
Fresh(Option<reqwest::Response>),
|
|
147
|
+
ReadInProgress,
|
|
148
|
+
Gone,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl HttpBody {
|
|
152
|
+
pub fn new(response: reqwest::Response) -> Self {
|
|
153
|
+
Self {
|
|
154
|
+
state: Mutex::new(BodyState::Fresh(Some(response))),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn take_stream(
|
|
159
|
+
&self,
|
|
160
|
+
) -> Result<Pin<Box<dyn Stream<Item = Result<Bytes, reqwest::Error>> + Send>>, String> {
|
|
161
|
+
let mut g = self.state.lock().unwrap();
|
|
162
|
+
match &mut *g {
|
|
163
|
+
BodyState::Fresh(r) => {
|
|
164
|
+
let resp = r.take().ok_or_else(|| "Response body already consumed".to_string())?;
|
|
165
|
+
*g = BodyState::ReadInProgress;
|
|
166
|
+
Ok(Box::pin(resp.bytes_stream()))
|
|
167
|
+
}
|
|
168
|
+
BodyState::ReadInProgress => Err("ReadableStream is locked; getReader() already called".into()),
|
|
169
|
+
BodyState::Gone => Err("Response body already consumed".into()),
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pub fn take_text_async(
|
|
174
|
+
&self,
|
|
175
|
+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String, String>> + Send + '_>> {
|
|
176
|
+
let resp = {
|
|
177
|
+
let mut g = self.state.lock().unwrap();
|
|
178
|
+
match &mut *g {
|
|
179
|
+
BodyState::Fresh(r) => match r.take() {
|
|
180
|
+
Some(resp) => {
|
|
181
|
+
*g = BodyState::Gone;
|
|
182
|
+
Ok(resp)
|
|
183
|
+
}
|
|
184
|
+
None => Err("Response body already consumed".into()),
|
|
185
|
+
},
|
|
186
|
+
BodyState::ReadInProgress => Err(
|
|
187
|
+
"Cannot call text(): body is locked by ReadableStreamDefaultReader".into(),
|
|
188
|
+
),
|
|
189
|
+
BodyState::Gone => Err("Response body already consumed".into()),
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
Box::pin(async move {
|
|
193
|
+
match resp {
|
|
194
|
+
Ok(r) => r.text().await.map_err(|e| e.to_string()),
|
|
195
|
+
Err(e) => Err(e),
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn mark_gone_after_stream(&self) {
|
|
201
|
+
let mut g = self.state.lock().unwrap();
|
|
202
|
+
*g = BodyState::Gone;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
pub struct HttpReadableStream {
|
|
207
|
+
body: Arc<HttpBody>,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
impl TishOpaque for HttpReadableStream {
|
|
211
|
+
fn type_name(&self) -> &'static str {
|
|
212
|
+
"ReadableStream"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fn get_method(&self, name: &str) -> Option<NativeFn> {
|
|
216
|
+
if name != "getReader" {
|
|
217
|
+
return None;
|
|
218
|
+
}
|
|
219
|
+
let body = Arc::clone(&self.body);
|
|
220
|
+
Some(Rc::new(move |_args: &[Value]| match body.take_stream() {
|
|
221
|
+
Ok(stream) => {
|
|
222
|
+
let inner = Arc::new(tokio::sync::Mutex::new(StreamSlot { stream }));
|
|
223
|
+
Value::Opaque(Arc::new(HttpStreamReader {
|
|
224
|
+
inner,
|
|
225
|
+
body: Arc::clone(&body),
|
|
226
|
+
}))
|
|
227
|
+
}
|
|
228
|
+
Err(e) => {
|
|
229
|
+
let mut m = HashMap::new();
|
|
230
|
+
m.insert(Arc::from("error"), Value::String(e.into()));
|
|
231
|
+
Value::Object(Rc::new(RefCell::new(m)))
|
|
232
|
+
}
|
|
233
|
+
}))
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
struct StreamSlot {
|
|
238
|
+
stream: Pin<Box<dyn Stream<Item = Result<Bytes, reqwest::Error>> + Send>>,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
pub struct HttpStreamReader {
|
|
242
|
+
inner: Arc<tokio::sync::Mutex<StreamSlot>>,
|
|
243
|
+
body: Arc<HttpBody>,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
impl TishOpaque for HttpStreamReader {
|
|
247
|
+
fn type_name(&self) -> &'static str {
|
|
248
|
+
"ReadableStreamDefaultReader"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fn get_method(&self, name: &str) -> Option<NativeFn> {
|
|
252
|
+
if name != "read" {
|
|
253
|
+
return None;
|
|
254
|
+
}
|
|
255
|
+
let inner = Arc::clone(&self.inner);
|
|
256
|
+
let body = Arc::clone(&self.body);
|
|
257
|
+
Some(Rc::new(move |_args: &[Value]| {
|
|
258
|
+
let inner = Arc::clone(&inner);
|
|
259
|
+
let body = Arc::clone(&body);
|
|
260
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
261
|
+
crate::http::RUNTIME.with(|rt| {
|
|
262
|
+
rt.spawn(async move {
|
|
263
|
+
let mut slot = inner.lock().await;
|
|
264
|
+
match slot.stream.next().await {
|
|
265
|
+
None => {
|
|
266
|
+
body.mark_gone_after_stream();
|
|
267
|
+
let _ = tx.send(Ok(ReadChunk::Done));
|
|
268
|
+
}
|
|
269
|
+
Some(Ok(b)) => {
|
|
270
|
+
let _ = tx.send(Ok(ReadChunk::Bytes(b.to_vec())));
|
|
271
|
+
}
|
|
272
|
+
Some(Err(e)) => {
|
|
273
|
+
let _ = tx.send(Err(e.to_string()));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
Value::Promise(Arc::new(ReadChunkPromise {
|
|
279
|
+
rx: Mutex::new(Some(rx)),
|
|
280
|
+
}))
|
|
281
|
+
}))
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn headers_to_value(headers: &reqwest::header::HeaderMap) -> Value {
|
|
286
|
+
let mut headers_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(headers.len());
|
|
287
|
+
for (key, value) in headers.iter() {
|
|
288
|
+
if let Ok(v) = value.to_str() {
|
|
289
|
+
headers_obj.insert(Arc::from(key.as_str()), Value::String(v.into()));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
Value::Object(Rc::new(RefCell::new(headers_obj)))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
pub fn response_value_from_reqwest(response: reqwest::Response) -> Value {
|
|
296
|
+
let status = response.status().as_u16() as f64;
|
|
297
|
+
let ok = response.status().is_success();
|
|
298
|
+
let headers_val = headers_to_value(response.headers());
|
|
299
|
+
let body_holder = Arc::new(HttpBody::new(response));
|
|
300
|
+
let stream = Arc::new(HttpReadableStream {
|
|
301
|
+
body: Arc::clone(&body_holder),
|
|
302
|
+
});
|
|
303
|
+
let body_stream_val = Value::Opaque(stream);
|
|
304
|
+
let bh_text = Arc::clone(&body_holder);
|
|
305
|
+
let bh_json = Arc::clone(&body_holder);
|
|
306
|
+
let text_fn: NativeFn = Rc::new(move |_args: &[Value]| {
|
|
307
|
+
let bh = Arc::clone(&bh_text);
|
|
308
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
309
|
+
crate::http::RUNTIME.with(|rt| {
|
|
310
|
+
rt.spawn(async move {
|
|
311
|
+
let r = bh.take_text_async().await;
|
|
312
|
+
let _ = tx.send(r);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
crate::promise_io::string_result_promise(rx)
|
|
316
|
+
});
|
|
317
|
+
let json_fn: NativeFn = Rc::new(move |_args: &[Value]| {
|
|
318
|
+
let bh = Arc::clone(&bh_json);
|
|
319
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
320
|
+
crate::http::RUNTIME.with(|rt| {
|
|
321
|
+
rt.spawn(async move {
|
|
322
|
+
let r = bh.take_text_async().await;
|
|
323
|
+
let _ = tx.send(r);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
Value::Promise(Arc::new(JsonTextPromise {
|
|
327
|
+
rx: Mutex::new(Some(rx)),
|
|
328
|
+
}))
|
|
329
|
+
});
|
|
330
|
+
let mut obj: HashMap<Arc<str>, Value> = HashMap::new();
|
|
331
|
+
obj.insert(Arc::from("status"), Value::Number(status));
|
|
332
|
+
obj.insert(Arc::from("ok"), Value::Bool(ok));
|
|
333
|
+
obj.insert(Arc::from("headers"), headers_val);
|
|
334
|
+
obj.insert(Arc::from("body"), body_stream_val);
|
|
335
|
+
obj.insert(Arc::from("text"), Value::Function(text_fn));
|
|
336
|
+
obj.insert(Arc::from("json"), Value::Function(json_fn));
|
|
337
|
+
Value::Object(Rc::new(RefCell::new(obj)))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async fn send_request_parts(
|
|
341
|
+
url: String,
|
|
342
|
+
method: String,
|
|
343
|
+
headers: Vec<(String, String)>,
|
|
344
|
+
body: Option<String>,
|
|
345
|
+
) -> Result<reqwest::Response, String> {
|
|
346
|
+
let client = reqwest::Client::new();
|
|
347
|
+
let mut req = match method.as_str() {
|
|
348
|
+
"GET" => client.get(&url),
|
|
349
|
+
"POST" => client.post(&url),
|
|
350
|
+
"PUT" => client.put(&url),
|
|
351
|
+
"DELETE" => client.delete(&url),
|
|
352
|
+
"PATCH" => client.patch(&url),
|
|
353
|
+
"HEAD" => client.head(&url),
|
|
354
|
+
_ => client.get(&url),
|
|
355
|
+
};
|
|
356
|
+
for (key, value) in headers {
|
|
357
|
+
req = req.header(key, value);
|
|
358
|
+
}
|
|
359
|
+
if let Some(body) = body {
|
|
360
|
+
req = req.body(body);
|
|
361
|
+
}
|
|
362
|
+
req.send().await.map_err(|e| e.to_string())
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
pub fn fetch_promise_from_args(args: Vec<Value>) -> Value {
|
|
366
|
+
let url = match args.first() {
|
|
367
|
+
Some(Value::String(s)) => s.to_string(),
|
|
368
|
+
Some(v) => v.to_display_string(),
|
|
369
|
+
None => {
|
|
370
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
371
|
+
let _ = tx.send(Err("fetch requires a URL".into()));
|
|
372
|
+
return Value::Promise(Arc::new(FetchResponsePromise {
|
|
373
|
+
rx: Mutex::new(Some(rx)),
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
let method = extract_method(args.get(1));
|
|
378
|
+
let headers = extract_headers(args.get(1));
|
|
379
|
+
let body = extract_body(args.get(1));
|
|
380
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
381
|
+
crate::http::RUNTIME.with(|rt| {
|
|
382
|
+
rt.spawn(async move {
|
|
383
|
+
let r = send_request_parts(url, method, headers, body).await;
|
|
384
|
+
let _ = tx.send(r);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
Value::Promise(Arc::new(FetchResponsePromise {
|
|
388
|
+
rx: Mutex::new(Some(rx)),
|
|
389
|
+
}))
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
pub fn fetch_all_promise_from_args(args: Vec<Value>) -> Value {
|
|
393
|
+
let requests = match args.first() {
|
|
394
|
+
Some(Value::Array(arr)) => arr.borrow().clone(),
|
|
395
|
+
_ => {
|
|
396
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
397
|
+
let _ = tx.send(Err("fetchAll requires an array of request objects".into()));
|
|
398
|
+
return Value::Promise(Arc::new(FetchAllResponsesPromise {
|
|
399
|
+
rx: Mutex::new(Some(rx)),
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
let mut parts: Vec<(String, String, Vec<(String, String)>, Option<String>)> = Vec::new();
|
|
404
|
+
for req in requests {
|
|
405
|
+
let (url, opt) = match &req {
|
|
406
|
+
Value::String(s) => (s.to_string(), None),
|
|
407
|
+
Value::Object(obj) => {
|
|
408
|
+
let obj_ref = obj.borrow();
|
|
409
|
+
match obj_ref
|
|
410
|
+
.get(&Arc::from("url"))
|
|
411
|
+
.map(|v| v.to_display_string())
|
|
412
|
+
{
|
|
413
|
+
Some(u) => (u, Some(req.clone())),
|
|
414
|
+
None => {
|
|
415
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
416
|
+
let _ = tx.send(Err("Each request object must have a 'url' property".into()));
|
|
417
|
+
return Value::Promise(Arc::new(FetchAllResponsesPromise {
|
|
418
|
+
rx: Mutex::new(Some(rx)),
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
_ => {
|
|
424
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
425
|
+
let _ = tx.send(Err(
|
|
426
|
+
"Each request must be a string URL or request object".into(),
|
|
427
|
+
));
|
|
428
|
+
return Value::Promise(Arc::new(FetchAllResponsesPromise {
|
|
429
|
+
rx: Mutex::new(Some(rx)),
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
let method = extract_method(opt.as_ref());
|
|
434
|
+
let headers = extract_headers(opt.as_ref());
|
|
435
|
+
let body = extract_body(opt.as_ref());
|
|
436
|
+
parts.push((url, method, headers, body));
|
|
437
|
+
}
|
|
438
|
+
let (tx, rx) = tokio::sync::oneshot::channel();
|
|
439
|
+
crate::http::RUNTIME.with(|rt| {
|
|
440
|
+
rt.spawn(async move {
|
|
441
|
+
let futs: Vec<_> = parts
|
|
442
|
+
.into_iter()
|
|
443
|
+
.map(|(url, m, h, b)| send_request_parts(url, m, h, b))
|
|
444
|
+
.collect();
|
|
445
|
+
let results = futures::future::join_all(futs).await;
|
|
446
|
+
let mapped: Vec<Result<reqwest::Response, String>> = results.into_iter().collect();
|
|
447
|
+
let _ = tx.send(Ok(mapped));
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
Value::Promise(Arc::new(FetchAllResponsesPromise {
|
|
451
|
+
rx: Mutex::new(Some(rx)),
|
|
452
|
+
}))
|
|
453
|
+
}
|