Skip to content

Commit 5fd1c83

Browse files
authored
new function: fetch_with_meta (#827)
* new function: fetch_with_meta closes #792 * fmt * clippy auto * test fetch_with_meta * fix tests * update deps * retry failed deps downloads * better json serializing * test fetch_with_meta error handling * add logging * fix tests closes #792
1 parent b11136a commit 5fd1c83

File tree

8 files changed

+289
-55
lines changed

8 files changed

+289
-55
lines changed

Cargo.lock

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.rs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use actix_rt::spawn;
2+
use actix_rt::time::sleep;
23
use libflate::gzip;
34
use std::collections::hash_map::DefaultHasher;
45
use std::fs::File;
@@ -99,21 +100,37 @@ fn copy_cached_to_opened_file(source: &Path, outfile: &mut impl std::io::Write)
99100
}
100101

101102
async fn download_url_to_path(client: &awc::Client, url: &str, path: &Path) {
102-
let mut resp = client.get(url).send().await.unwrap_or_else(|err| {
103-
let path = make_url_path(url);
104-
panic!(
105-
"We need to download external frontend dependencies to build the static frontend. \n\
106-
Could not download static asset. You can manually download the file with: \n\
107-
curl {url:?} > {path:?} \n\
108-
{err}"
109-
)
110-
});
111-
if resp.status() != 200 {
112-
panic!("Received {} status code from {}", resp.status(), url);
103+
let mut attempt = 1;
104+
let max_attempts = 2;
105+
106+
loop {
107+
match client.get(url).send().await {
108+
Ok(mut resp) => {
109+
if resp.status() != 200 {
110+
panic!("Received {} status code from {}", resp.status(), url);
111+
}
112+
let bytes = resp.body().limit(128 * 1024 * 1024).await.unwrap();
113+
std::fs::write(path, &bytes)
114+
.expect("Failed to write external frontend dependency to local file");
115+
break;
116+
}
117+
Err(err) => {
118+
if attempt >= max_attempts {
119+
let path = make_url_path(url);
120+
panic!(
121+
"We need to download external frontend dependencies to build the static frontend. \n\
122+
Could not download static asset after {} attempts. You can manually download the file with: \n\
123+
curl {url:?} > {path:?} \n\
124+
{err}",
125+
max_attempts
126+
);
127+
}
128+
sleep(Duration::from_secs(1)).await;
129+
println!("cargo:warning=Retrying download of {url} after {err}.");
130+
attempt += 1;
131+
}
132+
}
113133
}
114-
let bytes = resp.body().limit(128 * 1024 * 1024).await.unwrap();
115-
std::fs::write(path, &bytes)
116-
.expect("Failed to write external frontend dependency to local file");
117134
}
118135

119136
// Given a filename, creates a new unique filename based on the file contents

examples/official-site/sqlpage/migrations/40_fetch.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ The fetch function accepts either a URL string, or a JSON object with the follow
8888
- `username`: Optional username for HTTP Basic Authentication. Introduced in version 0.33.0.
8989
- `password`: Optional password for HTTP Basic Authentication. Only used if username is provided. Introduced in version 0.33.0.
9090
91+
# Error handling and reading response headers
92+
93+
If the request fails, this function throws an error, that will be displayed to the user.
94+
The response headers are not available for inspection.
95+
96+
If you need to handle errors or inspect the response headers, use [`sqlpage.fetch_with_meta`](?function=fetch_with_meta).
9197
'
9298
);
9399
INSERT INTO sqlpage_function_parameters (
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
INSERT INTO sqlpage_functions (
2+
"name",
3+
"introduced_in_version",
4+
"icon",
5+
"description_md"
6+
)
7+
VALUES (
8+
'fetch_with_meta',
9+
'0.34.0',
10+
'transfer-vertical',
11+
'Sends an HTTP request and returns detailed metadata about the response, including status code, headers, and body.
12+
13+
This function is similar to [`fetch`](?function=fetch), but returns a JSON object containing detailed information about the response.
14+
The returned object has the following structure:
15+
```json
16+
{
17+
"status": 200,
18+
"headers": {
19+
"content-type": "text/html",
20+
"content-length": "1234"
21+
},
22+
"body": "a string, or a json object, depending on the content type",
23+
"error": "error message if any"
24+
}
25+
```
26+
27+
If the request fails or encounters an error (e.g., network issues, invalid UTF-8 response), instead of throwing an error,
28+
the function returns a JSON object with an "error" field containing the error message.
29+
30+
### Example: Basic Usage
31+
32+
```sql
33+
-- Make a request and get detailed response information
34+
set response = sqlpage.fetch_with_meta(''https://pokeapi.co/api/v2/pokemon/ditto'');
35+
36+
-- redirect the user to an error page if the request failed
37+
select ''redirect'' as component, ''error.sql'' as url
38+
where
39+
json_extract($response, ''$.error'') is not null
40+
or json_extract($response, ''$.status'') != 200;
41+
42+
-- Extract data from the response json body
43+
select ''card'' as component;
44+
select
45+
json_extract($response, ''$.body.name'') as title,
46+
json_extract($response, ''$.body.abilities[0].ability.name'') as description
47+
from $response;
48+
```
49+
50+
### Example: Advanced Request with Authentication
51+
52+
```sql
53+
set request = json_object(
54+
''method'', ''POST'',
55+
''url'', ''https://sqlpage.free.beeceptor.com'',
56+
''headers'', json_object(
57+
''Content-Type'', ''application/json'',
58+
''Authorization'', ''Bearer '' || sqlpage.environment_variable(''API_TOKEN'')
59+
),
60+
''body'', json_object(
61+
''key'', ''value''
62+
)
63+
);
64+
set response = sqlpage.fetch_with_meta($request);
65+
66+
-- Check response content type
67+
select ''debug'' as component, $response as response;
68+
```
69+
70+
The function accepts the same parameters as the [`fetch` function](?function=fetch).'
71+
);
72+
73+
INSERT INTO sqlpage_function_parameters (
74+
"function",
75+
"index",
76+
"name",
77+
"description_md",
78+
"type"
79+
)
80+
VALUES (
81+
'fetch_with_meta',
82+
1,
83+
'url',
84+
'Either a string containing an URL to request, or a json object in the standard format of the request interface of the web fetch API.',
85+
'TEXT'
86+
);

0 commit comments

Comments
 (0)