1
+ use bytes:: Bytes ;
1
2
use pgrx:: pg_sys;
2
3
use semver:: { Version , VersionReq } ;
3
4
use sha2:: { Digest , Sha256 } ;
4
5
use std:: collections:: HashMap ;
5
6
use std:: fs;
7
+ use std:: path:: { Path , PathBuf } ;
6
8
use warg_client as warg;
7
9
use wasmtime:: component:: * ;
8
10
use wasmtime:: { Config , Engine , Store } ;
@@ -41,8 +43,6 @@ fn load_component_from_file(
41
43
Component :: from_file ( engine, file_path) . map_err ( |_| WasmFdwError :: InvalidWasmComponent )
42
44
}
43
45
44
- // Download wasm component package from warg registry or custom url.
45
- // The url protoal can be 'file://', 'warg(s)://' or 'http(s)://'.
46
46
fn download_component (
47
47
rt : & Runtime ,
48
48
engine : & Engine ,
@@ -51,78 +51,153 @@ fn download_component(
51
51
version : & str ,
52
52
checksum : Option < & str > ,
53
53
) -> WasmFdwResult < Component > {
54
+ // handle local file paths
54
55
if let Some ( file_path) = url. strip_prefix ( "file://" ) {
55
56
return load_component_from_file ( engine, file_path) ;
56
57
}
57
58
59
+ // handle warg registry URLs
58
60
if url. starts_with ( "warg://" ) || url. starts_with ( "wargs://" ) {
59
- let url = url
60
- . replacen ( "warg://" , "http://" , 1 )
61
- . replacen ( "wargs://" , "https://" , 1 ) ;
62
-
63
- // download from warg registry
64
- let config = warg:: Config {
65
- disable_interactive : true ,
66
- ..Default :: default ( )
67
- } ;
68
- let client = rt. block_on ( warg:: FileSystemClient :: new_with_config (
69
- Some ( & url) ,
70
- & config,
71
- None ,
72
- ) ) ?;
73
-
74
- let pkg_name = warg_protocol:: registry:: PackageName :: new ( name) ?;
75
- let ver = semver:: VersionReq :: parse ( version) ?;
76
- let pkg = rt
77
- . block_on ( client. download ( & pkg_name, & ver) ) ?
78
- . ok_or ( format ! ( "{}@{} not found on {}" , name, version, url) ) ?;
79
-
80
- return load_component_from_file ( engine, pkg. path ) ;
61
+ return download_from_warg ( rt, engine, url, name, version) ;
81
62
}
82
63
83
- // otherwise, download from custom url if it is not in local cache
64
+ // handle direct URLs with caching
65
+ download_from_url ( rt, engine, url, name, version, checksum)
66
+ }
67
+
68
+ fn download_from_warg (
69
+ rt : & Runtime ,
70
+ engine : & Engine ,
71
+ url : & str ,
72
+ name : & str ,
73
+ version : & str ,
74
+ ) -> WasmFdwResult < Component > {
75
+ let url = url
76
+ . replacen ( "warg://" , "http://" , 1 )
77
+ . replacen ( "wargs://" , "https://" , 1 ) ;
78
+
79
+ let config = warg:: Config {
80
+ disable_interactive : true ,
81
+ ..Default :: default ( )
82
+ } ;
83
+
84
+ let client = rt. block_on ( warg:: FileSystemClient :: new_with_config (
85
+ Some ( & url) ,
86
+ & config,
87
+ None ,
88
+ ) ) ?;
89
+
90
+ let pkg_name = warg_protocol:: registry:: PackageName :: new ( name)
91
+ . map_err ( |e| format ! ( "invalid package name '{}': {}" , name, e) ) ?;
92
+
93
+ let ver = semver:: VersionReq :: parse ( version)
94
+ . map_err ( |e| format ! ( "invalid version requirement '{}': {}" , version, e) ) ?;
95
+
96
+ let pkg = rt
97
+ . block_on ( client. download ( & pkg_name, & ver) ) ?
98
+ . ok_or_else ( || format ! ( "{}@{} not found on {}" , name, version, url) ) ?;
99
+
100
+ load_component_from_file ( engine, pkg. path )
101
+ }
102
+
103
+ fn download_from_url (
104
+ rt : & Runtime ,
105
+ engine : & Engine ,
106
+ url : & str ,
107
+ name : & str ,
108
+ version : & str ,
109
+ checksum : Option < & str > ,
110
+ ) -> WasmFdwResult < Component > {
111
+ // validate URL
112
+ let url = url
113
+ . parse :: < reqwest:: Url > ( )
114
+ . map_err ( |e| format ! ( "invalid URL '{}': {}" , url, e) ) ?;
115
+
116
+ // calculate cache path
117
+ let cache_path = get_cache_path ( url. as_str ( ) , name, version) ?;
118
+
119
+ // return cached component if it exists and is valid
120
+ if cache_path. exists ( ) {
121
+ if let Ok ( component) = load_component_from_file ( engine, & cache_path) {
122
+ return Ok ( component) ;
123
+ }
124
+ // if loading fails, remove invalid cache file
125
+ let _ = fs:: remove_file ( & cache_path) ;
126
+ }
127
+
128
+ // ensure checksum is provided for remote downloads
129
+ let checksum = checksum
130
+ . ok_or_else ( || "package checksum must be specified for remote downloads" . to_string ( ) ) ?;
131
+
132
+ // download and verify component
133
+ let bytes = download_and_verify ( rt, url, checksum) ?;
134
+
135
+ // save to cache
136
+ save_to_cache ( & cache_path, & bytes) ?;
137
+
138
+ // load component
139
+ load_component_from_file ( engine, & cache_path) . inspect_err ( |_| {
140
+ let _ = fs:: remove_file ( & cache_path) ;
141
+ } )
142
+ }
84
143
85
- // calculate file name hash and make up cache path
144
+ fn get_cache_path ( url : & str , name : & str , version : & str ) -> WasmFdwResult < PathBuf > {
86
145
let hash = Sha256 :: digest ( format ! (
87
146
"{}:{}:{}@{}" ,
88
147
unsafe { pg_sys:: GetUserId ( ) . as_u32( ) } ,
89
148
url,
90
149
name,
91
150
version
92
151
) ) ;
152
+
93
153
let file_name = hex:: encode ( hash) ;
94
- let mut path = dirs:: cache_dir ( ) . expect ( "no cache dir found" ) ;
154
+ let mut path = dirs:: cache_dir ( ) . ok_or_else ( || "no cache directory found" . to_string ( ) ) ?;
155
+
95
156
path. push ( file_name) ;
96
157
path. set_extension ( "wasm" ) ;
97
158
98
- if !path. exists ( ) {
99
- // package checksum must be specified
100
- let option_checksum = checksum. ok_or ( "package checksum option not specified" . to_owned ( ) ) ?;
159
+ Ok ( path)
160
+ }
101
161
102
- // download component wasm from remote and check its checksum
103
- let resp = rt. block_on ( reqwest:: get ( url) ) ?;
104
- let bytes = rt. block_on ( resp. bytes ( ) ) ?;
105
- let bytes_checksum = hex:: encode ( Sha256 :: digest ( & bytes) ) ;
106
- if bytes_checksum != option_checksum {
107
- return Err ( "package checksum not match" . to_string ( ) . into ( ) ) ;
108
- }
162
+ fn download_and_verify (
163
+ rt : & Runtime ,
164
+ url : reqwest:: Url ,
165
+ expected_checksum : & str ,
166
+ ) -> WasmFdwResult < Bytes > {
167
+ let resp = rt
168
+ . block_on ( reqwest:: get ( url. clone ( ) ) )
169
+ . map_err ( |_| "failed to download component" . to_string ( ) ) ?;
170
+
171
+ if !resp. status ( ) . is_success ( ) {
172
+ return Err ( "component download failed - server error"
173
+ . to_string ( )
174
+ . into ( ) ) ;
175
+ }
109
176
110
- // save the component wasm to local cache
111
- if let Some ( parent) = path. parent ( ) {
112
- // create all parent directories if they do not exist
113
- fs:: create_dir_all ( parent) ?;
114
- }
115
- fs:: write ( & path, bytes) ?;
177
+ let bytes = rt
178
+ . block_on ( resp. bytes ( ) )
179
+ . map_err ( |_| "failed to read component data" . to_string ( ) ) ?;
180
+
181
+ let actual_checksum = hex:: encode ( Sha256 :: digest ( & bytes) ) ;
182
+ if actual_checksum != expected_checksum {
183
+ return Err ( "component verification failed" . to_string ( ) . into ( ) ) ;
116
184
}
117
185
118
- load_component_from_file ( engine, & path) . inspect_err ( |_| {
119
- // remove the cache file if it cannot be loaded as component
120
- let _ = fs:: remove_file ( & path) ;
121
- } )
186
+ Ok ( bytes)
187
+ }
188
+
189
+ fn save_to_cache ( path : & Path , bytes : & [ u8 ] ) -> WasmFdwResult < ( ) > {
190
+ if let Some ( parent) = path. parent ( ) {
191
+ fs:: create_dir_all ( parent) . map_err ( |_| "cache access error" . to_string ( ) ) ?;
192
+ }
193
+
194
+ fs:: write ( path, bytes) . map_err ( |_| "cache write error" . to_string ( ) ) ?;
195
+
196
+ Ok ( ( ) )
122
197
}
123
198
124
199
#[ wrappers_fdw(
125
- version = "0.1.3 " ,
200
+ version = "0.1.4 " ,
126
201
author = "Supabase" ,
127
202
website = "https://github.com/supabase/wrappers/tree/main/wrappers/src/fdw/wasm_fdw" ,
128
203
error_type = "WasmFdwError"
0 commit comments