vovk-rust 0.0.1-beta.4 → 0.0.1-beta.6
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/client-templates/rsPkg/Cargo.toml.ejs +4 -1
- package/client-templates/rsSrc/http_request.rs +136 -128
- package/client-templates/rsSrc/lib.rs.ejs +8 -4
- package/index.d.ts +10 -10
- package/index.js +2 -7
- package/package.json +1 -1
|
@@ -9,7 +9,10 @@ const data = {
|
|
|
9
9
|
dependencies: {
|
|
10
10
|
serde: { version: "1.0", features: ["derive"] },
|
|
11
11
|
serde_json: "1.0",
|
|
12
|
-
reqwest: { version: "0.12", features: ["
|
|
12
|
+
reqwest: { version: "0.12", features: ["json", "multipart", "stream"] },
|
|
13
|
+
tokio: { version: "1", features: ["macros", "rt-multi-thread", "io-util"] },
|
|
14
|
+
"futures-util": "0.3",
|
|
15
|
+
"tokio-util": { version: "0.7", features: ["codec"] },
|
|
13
16
|
jsonschema: "0.17",
|
|
14
17
|
urlencoding: "2.1",
|
|
15
18
|
once_cell: "1.17"
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
use serde::{Serialize, de::DeserializeOwned};
|
|
2
|
-
use reqwest::
|
|
3
|
-
use reqwest::
|
|
4
|
-
use reqwest::blocking::multipart;
|
|
5
|
-
use core::panic;
|
|
2
|
+
use reqwest::{Client, Method};
|
|
3
|
+
use reqwest::multipart;
|
|
6
4
|
use std::collections::HashMap;
|
|
7
5
|
use std::error::Error;
|
|
8
6
|
use std::fmt;
|
|
7
|
+
use std::pin::Pin;
|
|
9
8
|
use jsonschema::JSONSchema;
|
|
10
9
|
use serde_json::Value;
|
|
11
10
|
use urlencoding;
|
|
12
11
|
use crate::read_full_schema;
|
|
13
12
|
use once_cell::sync::Lazy;
|
|
13
|
+
use futures_util::{Stream, StreamExt, TryStreamExt};
|
|
14
|
+
use tokio_util::codec::{FramedRead, LinesCodec};
|
|
15
|
+
use tokio_util::io::StreamReader;
|
|
14
16
|
|
|
15
17
|
// Custom error type for HTTP exceptions
|
|
16
18
|
#[derive(Debug, Serialize)]
|
|
@@ -49,7 +51,7 @@ fn prepare_request<B, Q, P>(
|
|
|
49
51
|
headers: Option<&HashMap<String, String>>,
|
|
50
52
|
api_root: Option<&str>,
|
|
51
53
|
disable_client_validation: bool,
|
|
52
|
-
) -> Result<(reqwest::
|
|
54
|
+
) -> Result<(reqwest::RequestBuilder, String), Box<dyn Error + Send + Sync>>
|
|
53
55
|
where
|
|
54
56
|
B: Serialize + ?Sized,
|
|
55
57
|
Q: Serialize + ?Sized,
|
|
@@ -230,7 +232,7 @@ where
|
|
|
230
232
|
|
|
231
233
|
// Main request function for regular (non-streaming) responses
|
|
232
234
|
#[allow(dead_code)]
|
|
233
|
-
pub fn http_request<T, B, Q, P>(
|
|
235
|
+
pub async fn http_request<T, B, Q, P>(
|
|
234
236
|
default_api_root: &str,
|
|
235
237
|
segment_name: &str,
|
|
236
238
|
controller_name: &str,
|
|
@@ -249,7 +251,6 @@ where
|
|
|
249
251
|
Q: Serialize + ?Sized,
|
|
250
252
|
P: Serialize + ?Sized,
|
|
251
253
|
{
|
|
252
|
-
// Prepare the request using the helper function
|
|
253
254
|
let (request, _) = prepare_request(
|
|
254
255
|
default_api_root,
|
|
255
256
|
segment_name,
|
|
@@ -267,67 +268,76 @@ where
|
|
|
267
268
|
status_code: 0,
|
|
268
269
|
cause: None,
|
|
269
270
|
})?;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
let response = request.send().map_err(|e| HttpException {
|
|
271
|
+
|
|
272
|
+
let response = request.send().await.map_err(|e| HttpException {
|
|
273
273
|
message: e.to_string(),
|
|
274
274
|
status_code: 0,
|
|
275
275
|
cause: None,
|
|
276
276
|
})?;
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
|
|
278
|
+
let status = response.status();
|
|
279
|
+
let status_code = status.as_u16() as i32;
|
|
280
|
+
|
|
279
281
|
let content_type = response
|
|
280
282
|
.headers()
|
|
281
283
|
.get("Content-Type")
|
|
282
|
-
.and_then(|v| v.to_str().ok())
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
message: e.to_string(),
|
|
307
|
-
status_code: 0,
|
|
308
|
-
cause: None,
|
|
309
|
-
})?;
|
|
310
|
-
Ok(typed_value)
|
|
284
|
+
.and_then(|v| v.to_str().ok())
|
|
285
|
+
.unwrap_or("")
|
|
286
|
+
.to_string();
|
|
287
|
+
|
|
288
|
+
if content_type.contains("application/json") {
|
|
289
|
+
let value: Value = response.json().await.map_err(|e| HttpException {
|
|
290
|
+
message: e.to_string(),
|
|
291
|
+
status_code,
|
|
292
|
+
cause: None,
|
|
293
|
+
})?;
|
|
294
|
+
|
|
295
|
+
if status.is_client_error() || status.is_server_error() || value.get("isError").is_some() {
|
|
296
|
+
let message = value
|
|
297
|
+
.get("message")
|
|
298
|
+
.and_then(|m| m.as_str())
|
|
299
|
+
.unwrap_or("Unknown error")
|
|
300
|
+
.to_string();
|
|
301
|
+
let cause = value.get("cause").cloned();
|
|
302
|
+
|
|
303
|
+
return Err(HttpException {
|
|
304
|
+
message,
|
|
305
|
+
status_code,
|
|
306
|
+
cause,
|
|
307
|
+
});
|
|
311
308
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
309
|
+
|
|
310
|
+
serde_json::from_value::<T>(value).map_err(|e| HttpException {
|
|
311
|
+
message: e.to_string(),
|
|
312
|
+
status_code,
|
|
313
|
+
cause: None,
|
|
314
|
+
})
|
|
315
|
+
} else {
|
|
316
|
+
let text = response.text().await.map_err(|e| HttpException {
|
|
317
|
+
message: e.to_string(),
|
|
318
|
+
status_code,
|
|
319
|
+
cause: None,
|
|
320
|
+
})?;
|
|
321
|
+
|
|
322
|
+
if status.is_client_error() || status.is_server_error() {
|
|
323
|
+
return Err(HttpException {
|
|
324
|
+
message: text.clone(),
|
|
325
|
+
status_code,
|
|
321
326
|
cause: None,
|
|
322
|
-
})
|
|
323
|
-
Ok(typed_value)
|
|
327
|
+
});
|
|
324
328
|
}
|
|
329
|
+
|
|
330
|
+
serde_json::from_str::<T>(&text).map_err(|e| HttpException {
|
|
331
|
+
message: e.to_string(),
|
|
332
|
+
status_code,
|
|
333
|
+
cause: None,
|
|
334
|
+
})
|
|
325
335
|
}
|
|
326
336
|
}
|
|
327
337
|
|
|
328
338
|
// Request function specifically for streaming responses
|
|
329
339
|
#[allow(dead_code)]
|
|
330
|
-
pub fn http_request_stream<T, B, Q, P>(
|
|
340
|
+
pub async fn http_request_stream<T, B, Q, P>(
|
|
331
341
|
default_api_root: &str,
|
|
332
342
|
segment_name: &str,
|
|
333
343
|
controller_name: &str,
|
|
@@ -339,14 +349,13 @@ pub fn http_request_stream<T, B, Q, P>(
|
|
|
339
349
|
headers: Option<&HashMap<String, String>>,
|
|
340
350
|
api_root: Option<&str>,
|
|
341
351
|
disable_client_validation: bool,
|
|
342
|
-
) -> Result<Box<dyn
|
|
352
|
+
) -> Result<Pin<Box<dyn Stream<Item = Result<T, HttpException>> + Send>>, HttpException>
|
|
343
353
|
where
|
|
344
354
|
T: DeserializeOwned + 'static,
|
|
345
355
|
B: Serialize + ?Sized,
|
|
346
356
|
Q: Serialize + ?Sized,
|
|
347
357
|
P: Serialize + ?Sized,
|
|
348
358
|
{
|
|
349
|
-
// Prepare the request using the helper function
|
|
350
359
|
let (request, _) = prepare_request(
|
|
351
360
|
default_api_root,
|
|
352
361
|
segment_name,
|
|
@@ -364,43 +373,86 @@ where
|
|
|
364
373
|
status_code: 0,
|
|
365
374
|
cause: None,
|
|
366
375
|
})?;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
let response = request.send().map_err(|e| HttpException {
|
|
376
|
+
|
|
377
|
+
let response = request.send().await.map_err(|e| HttpException {
|
|
370
378
|
message: e.to_string(),
|
|
371
379
|
status_code: 0,
|
|
372
380
|
cause: None,
|
|
373
381
|
})?;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
let
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
382
|
+
|
|
383
|
+
let status = response.status();
|
|
384
|
+
let status_code = status.as_u16() as i32;
|
|
385
|
+
|
|
386
|
+
if !status.is_success() {
|
|
387
|
+
let message = response
|
|
388
|
+
.text()
|
|
389
|
+
.await
|
|
390
|
+
.unwrap_or_else(|_| "Streaming request failed".to_string());
|
|
391
|
+
|
|
392
|
+
return Err(HttpException {
|
|
393
|
+
message,
|
|
394
|
+
status_code,
|
|
395
|
+
cause: None,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let byte_stream = response
|
|
400
|
+
.bytes_stream()
|
|
401
|
+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
|
|
402
|
+
|
|
403
|
+
let reader = StreamReader::new(byte_stream);
|
|
404
|
+
let lines = FramedRead::new(reader, LinesCodec::new());
|
|
405
|
+
|
|
406
|
+
let json_stream = lines.filter_map(move |line| async move {
|
|
407
|
+
match line {
|
|
408
|
+
Ok(content) => {
|
|
409
|
+
let trimmed = content.trim();
|
|
410
|
+
if trimmed.is_empty() {
|
|
411
|
+
return None;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
match serde_json::from_str::<Value>(trimmed) {
|
|
415
|
+
Ok(value) => Some(Ok(value)),
|
|
416
|
+
Err(e) => Some(Err(HttpException {
|
|
417
|
+
message: e.to_string(),
|
|
418
|
+
status_code,
|
|
419
|
+
cause: None,
|
|
420
|
+
})),
|
|
395
421
|
}
|
|
396
|
-
},
|
|
397
|
-
Err(e) => {
|
|
398
|
-
panic!("Error reading from stream: {}", e);
|
|
399
422
|
}
|
|
423
|
+
Err(e) => Some(Err(HttpException {
|
|
424
|
+
message: e.to_string(),
|
|
425
|
+
status_code,
|
|
426
|
+
cause: None,
|
|
427
|
+
})),
|
|
400
428
|
}
|
|
401
429
|
});
|
|
402
|
-
|
|
403
|
-
|
|
430
|
+
|
|
431
|
+
let typed_stream = json_stream.map(move |result| {
|
|
432
|
+
result.and_then(|value| {
|
|
433
|
+
if value.get("isError").is_some() {
|
|
434
|
+
let message = value["message"]
|
|
435
|
+
.as_str()
|
|
436
|
+
.unwrap_or("Unknown error")
|
|
437
|
+
.to_string();
|
|
438
|
+
let cause = value.get("cause").cloned();
|
|
439
|
+
|
|
440
|
+
Err(HttpException {
|
|
441
|
+
message,
|
|
442
|
+
status_code,
|
|
443
|
+
cause,
|
|
444
|
+
})
|
|
445
|
+
} else {
|
|
446
|
+
serde_json::from_value::<T>(value).map_err(|e| HttpException {
|
|
447
|
+
message: e.to_string(),
|
|
448
|
+
status_code,
|
|
449
|
+
cause: None,
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
Ok(Box::pin(typed_stream))
|
|
404
456
|
}
|
|
405
457
|
|
|
406
458
|
// Helper function to build query strings from nested JSON
|
|
@@ -442,47 +494,3 @@ fn build_query_string(data: &Value, prefix: &str) -> String {
|
|
|
442
494
|
}
|
|
443
495
|
}
|
|
444
496
|
|
|
445
|
-
// Struct and Iterator implementation for streaming JSONL responses
|
|
446
|
-
struct JsonlStream {
|
|
447
|
-
reader: std::io::BufReader<reqwest::blocking::Response>,
|
|
448
|
-
buffer: String,
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
impl Iterator for JsonlStream {
|
|
452
|
-
type Item = Result<Value, Box<dyn Error>>;
|
|
453
|
-
|
|
454
|
-
fn next(&mut self) -> Option<Self::Item> {
|
|
455
|
-
use std::io::BufRead;
|
|
456
|
-
|
|
457
|
-
self.buffer.clear();
|
|
458
|
-
match self.reader.read_line(&mut self.buffer) {
|
|
459
|
-
Ok(0) => None, // End of stream
|
|
460
|
-
Ok(_) => {
|
|
461
|
-
let line = self.buffer.trim();
|
|
462
|
-
if line.is_empty() {
|
|
463
|
-
return self.next(); // Skip empty lines
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
match serde_json::from_str::<Value>(line) {
|
|
467
|
-
Ok(value) => {
|
|
468
|
-
if value.get("isError").is_some() {
|
|
469
|
-
let message = value["message"]
|
|
470
|
-
.as_str()
|
|
471
|
-
.unwrap_or("Unknown error")
|
|
472
|
-
.to_string();
|
|
473
|
-
Some(Err(Box::new(HttpException {
|
|
474
|
-
message,
|
|
475
|
-
status_code: 0,
|
|
476
|
-
cause: None,
|
|
477
|
-
})))
|
|
478
|
-
} else {
|
|
479
|
-
Some(Ok(value))
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
Err(e) => Some(Err(Box::new(e))),
|
|
483
|
-
}
|
|
484
|
-
},
|
|
485
|
-
Err(e) => Some(Err(Box::new(e))),
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
@@ -15,7 +15,11 @@ pub use crate::http_request::HttpException;
|
|
|
15
15
|
pub mod <%= t._.snakeCase(controllerSchema.rpcModuleName) %> {
|
|
16
16
|
#[allow(unused_imports)]
|
|
17
17
|
use crate::http_request::{HttpException, http_request, http_request_stream};
|
|
18
|
+
#[allow(unused_imports)]
|
|
19
|
+
use futures_util::Stream;
|
|
18
20
|
use std::collections::HashMap;
|
|
21
|
+
#[allow(unused_imports)]
|
|
22
|
+
use std::pin::Pin;
|
|
19
23
|
<% Object.entries(controllerSchema.handlers).forEach(([handlerNameOriginal, handlerSchema]) => {
|
|
20
24
|
const { validation, openapi, path, httpMethod } = handlerSchema;
|
|
21
25
|
const handlerName = t._.snakeCase(handlerNameOriginal);
|
|
@@ -42,14 +46,14 @@ vars.convertJSONSchemasToRustTypes({
|
|
|
42
46
|
validation?.query?.description ? `Query: ${validation?.query?.description}`: '',
|
|
43
47
|
validation?.output?.description ? `Returns: ${validation?.output?.description}`: ''
|
|
44
48
|
]).filter(Boolean).map((s) => s.split('\n')).flat().map((s) => ' '.repeat(4) + '/// ' + s).join('\n') %>
|
|
45
|
-
pub fn <%= handlerName %>(
|
|
46
|
-
body: <%- validation?.body ? (validation?.body?.['x-isForm'] ?'reqwest::
|
|
49
|
+
pub async fn <%= handlerName %>(
|
|
50
|
+
body: <%- validation?.body ? (validation?.body?.['x-isForm'] ?'reqwest::multipart::Form' : `${handlerName}_::body`): '()' %>,
|
|
47
51
|
query: <%- validation?.query ? `${handlerName}_::query` : '()' %>,
|
|
48
52
|
params: <%- validation?.params ? `${handlerName}_::params` : '()' %>,
|
|
49
53
|
headers: Option<&HashMap<String, String>>,
|
|
50
54
|
api_root: Option<&str>,
|
|
51
55
|
disable_client_validation: bool,
|
|
52
|
-
) -> <%- validation?.output ? `Result<${handlerName}_::output, HttpException>` : validation?.iteration ? `Result<Box<dyn
|
|
56
|
+
) -> <%- validation?.output ? `Result<${handlerName}_::output, HttpException>` : validation?.iteration ? `Result<Pin<Box<dyn Stream<Item = Result<${handlerName}_::iteration, HttpException>> + Send>>, HttpException>` : 'Result<serde_json::Value, HttpException>' %>{
|
|
53
57
|
let result = <%= validation?.iteration ? 'http_request_stream' : 'http_request' %>::<
|
|
54
58
|
<%- [
|
|
55
59
|
validation?.output ? `${handlerName}_::output` : validation?.iteration ? `${handlerName}_::iteration` : 'serde_json::Value',
|
|
@@ -69,7 +73,7 @@ vars.convertJSONSchemasToRustTypes({
|
|
|
69
73
|
headers,
|
|
70
74
|
api_root,
|
|
71
75
|
disable_client_validation,
|
|
72
|
-
);
|
|
76
|
+
).await;
|
|
73
77
|
|
|
74
78
|
result
|
|
75
79
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { VovkJSONSchemaBase } from 'vovk';
|
|
2
2
|
export declare function indent(level: number, pad?: number): string;
|
|
3
|
-
export declare function generateDocComment(schema:
|
|
4
|
-
export declare function resolveRef(ref: string, rootSchema:
|
|
3
|
+
export declare function generateDocComment(schema: VovkJSONSchemaBase, level: number, pad?: number): string;
|
|
4
|
+
export declare function resolveRef(ref: string, rootSchema: VovkJSONSchemaBase): VovkJSONSchemaBase | undefined;
|
|
5
5
|
export declare function getModulePath(path: string[]): string;
|
|
6
|
-
export declare function toRustType(schema:
|
|
7
|
-
export declare function generateEnum(schema:
|
|
8
|
-
export declare function generateVariantEnum(schema:
|
|
9
|
-
export declare function generateAllOfType(schemas:
|
|
10
|
-
export declare function processObject(schema:
|
|
11
|
-
export declare function processPrimitive(schema:
|
|
6
|
+
export declare function toRustType(schema: VovkJSONSchemaBase, path: string[], rootSchema?: VovkJSONSchemaBase): string;
|
|
7
|
+
export declare function generateEnum(schema: VovkJSONSchemaBase, name: string, level: number, pad?: number): string;
|
|
8
|
+
export declare function generateVariantEnum(schema: VovkJSONSchemaBase, name: string, path: string[], level: number, rootSchema: VovkJSONSchemaBase, pad?: number): string;
|
|
9
|
+
export declare function generateAllOfType(schemas: VovkJSONSchemaBase[], name: string, path: string[], level: number, rootSchema: VovkJSONSchemaBase, pad?: number): string;
|
|
10
|
+
export declare function processObject(schema: VovkJSONSchemaBase, path: string[], level: number, rootSchema?: VovkJSONSchemaBase, pad?: number): string;
|
|
11
|
+
export declare function processPrimitive(schema: VovkJSONSchemaBase, name: string, level: number, pad?: number): string;
|
|
12
12
|
export declare function convertJSONSchemasToRustTypes({ schemas, pad, rootName, }: {
|
|
13
|
-
schemas: Record<string,
|
|
13
|
+
schemas: Record<string, VovkJSONSchemaBase | undefined>;
|
|
14
14
|
pad?: number;
|
|
15
15
|
rootName: string;
|
|
16
16
|
}): string;
|
package/index.js
CHANGED
|
@@ -187,14 +187,9 @@ export function generateEnum(schema, name, level, pad = 0) {
|
|
|
187
187
|
code += `${indentFn(level)}#[derive(Debug, Serialize, Deserialize, Clone)]\n`;
|
|
188
188
|
code += `${indentFn(level)}#[allow(non_camel_case_types)]\n`;
|
|
189
189
|
code += `${indentFn(level)}pub enum ${name} {\n`;
|
|
190
|
-
schema.enum
|
|
190
|
+
schema.enum?.forEach((value) => {
|
|
191
191
|
// Create valid Rust enum variant
|
|
192
192
|
const variant = value?.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
193
|
-
// Add documentation if available in enumDescriptions
|
|
194
|
-
if (schema.enumDescriptions && schema.enumDescriptions[index]) {
|
|
195
|
-
const description = schema.enumDescriptions[index];
|
|
196
|
-
code += `${indentFn(level + 1)}/// ${description}\n`;
|
|
197
|
-
}
|
|
198
193
|
code += `${indentFn(level + 1)}#[serde(rename = "${value}")]\n`;
|
|
199
194
|
code += `${indentFn(level + 1)}${variant},\n`;
|
|
200
195
|
});
|
|
@@ -269,7 +264,7 @@ export function generateAllOfType(schemas, name, path, level, rootSchema, pad =
|
|
|
269
264
|
};
|
|
270
265
|
}
|
|
271
266
|
if (schema.required) {
|
|
272
|
-
mergedSchema.required = [...mergedSchema.required, ...schema.required];
|
|
267
|
+
mergedSchema.required = [...(mergedSchema.required ?? []), ...schema.required];
|
|
273
268
|
}
|
|
274
269
|
});
|
|
275
270
|
// Process the merged schema as a regular object
|