diff --git a/docs/luneTypes.d.luau b/docs/luneTypes.d.luau index d0fbbd0..f682294 100644 --- a/docs/luneTypes.d.luau +++ b/docs/luneTypes.d.luau @@ -170,14 +170,16 @@ type NetMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "PATCH This is a dictionary that may contain one or more of the following values: - * `url` - The URL to request. This is always required + * `url` - The URL to send a request to. This is always required * `method` - The HTTP method verb, such as `"GET"`, `"POST"`, `"PATCH"`, `"PUT"`, or `"DELETE"`. Defaults to `"GET"` + * `query` - A table of key-value pairs representing query parameters in the request path * `headers` - A table of key-value pairs representing headers * `body` - The request body ]=] export type NetFetchParams = { url: string, method: NetMethod?, + query: { [string]: string }?, headers: { [string]: string }?, body: string?, } diff --git a/packages/lib/src/globals/net.rs b/packages/lib/src/globals/net.rs index a3e68d7..24de41e 100644 --- a/packages/lib/src/globals/net.rs +++ b/packages/lib/src/globals/net.rs @@ -59,6 +59,9 @@ async fn net_request<'a>(lua: &'static Lua, config: RequestConfig<'a>) -> LuaRes // Create and send the request let client: NetClient = lua.named_registry_value("net.client")?; let mut request = client.request(config.method, &config.url); + for (query, value) in config.query { + request = request.query(&[(query.to_str()?, value.to_str()?)]); + } for (header, value) in config.headers { request = request.header(header.to_str()?, value.to_str()?); } diff --git a/packages/lib/src/lua/net/config.rs b/packages/lib/src/lua/net/config.rs index f03ab5a..a240b36 100644 --- a/packages/lib/src/lua/net/config.rs +++ b/packages/lib/src/lua/net/config.rs @@ -9,6 +9,7 @@ use reqwest::Method; pub struct RequestConfig<'a> { pub url: String, pub method: Method, + pub query: HashMap, LuaString<'a>>, pub headers: HashMap, LuaString<'a>>, pub body: Option>, } @@ -20,6 +21,7 @@ impl<'lua> FromLua<'lua> for RequestConfig<'lua> { return Ok(Self { url: s.to_string_lossy().to_string(), method: Method::GET, + query: HashMap::new(), headers: HashMap::new(), body: None, }); @@ -38,6 +40,18 @@ impl<'lua> FromLua<'lua> for RequestConfig<'lua> { Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(), Err(_) => "GET".to_string(), }; + // Extract query + let query = match tab.raw_get::<_, LuaTable>("query") { + Ok(config_headers) => { + let mut lua_headers = HashMap::new(); + for pair in config_headers.pairs::() { + let (key, value) = pair?.to_owned(); + lua_headers.insert(key, value); + } + lua_headers + } + Err(_) => HashMap::new(), + }; // Extract headers let headers = match tab.raw_get::<_, LuaTable>("headers") { Ok(config_headers) => { @@ -74,6 +88,7 @@ impl<'lua> FromLua<'lua> for RequestConfig<'lua> { return Ok(Self { url, method, + query, headers, body, }); diff --git a/packages/lib/src/tests.rs b/packages/lib/src/tests.rs index 71f0b9a..407a49c 100644 --- a/packages/lib/src/tests.rs +++ b/packages/lib/src/tests.rs @@ -46,6 +46,7 @@ create_tests! { fs_move: "fs/move", net_request_codes: "net/request/codes", net_request_methods: "net/request/methods", + net_request_query: "net/request/query", net_request_redirect: "net/request/redirect", net_json_decode: "net/json/decode", net_json_encode: "net/json/encode", @@ -61,8 +62,8 @@ create_tests! { require_nested: "globals/require/tests/nested", require_parents: "globals/require/tests/parents", require_siblings: "globals/require/tests/siblings", - // TODO: Uncomment this test, it is commented out - // right now to let CI pass so we make a release + // TODO: Uncomment this test, it is commented out right + // now to let CI pass so that we can make a new release // global_coroutine: "globals/coroutine", global_pcall: "globals/pcall", global_type: "globals/type", diff --git a/tests/net/request/query.luau b/tests/net/request/query.luau new file mode 100644 index 0000000..d816ea3 --- /dev/null +++ b/tests/net/request/query.luau @@ -0,0 +1,56 @@ +local QUERY: { [string]: string } = { + Key = "Value", + Hello = "World", + SpaceEmoji = " 🚀 ", +} + +-- Make a request with some basic query params as well +-- as a special non-ascii one that needs url encoding + +local response = net.request({ + url = "https://httpbin.org/anything", + query = QUERY, +}) + +assert( + response.ok, + "Request failed with status " + .. tostring(response.statusCode) + .. " " + .. tostring(response.statusMessage) +) + +-- We should get a json response here with an "args" table which is our query + +local success, json = pcall(net.jsonDecode, response.body) +assert(success, "Failed to decode json response\n" .. tostring(json)) + +local args = if type(json.args) == "table" then json.args else nil +assert(args ~= nil, "Response body did not contain an args table") + +-- The args table should then have the *exact* same contents as our query + +for key, value in QUERY do + local received = args[key] + if received == nil then + error(string.format("Response body did not contain query parameter '%s'", key)) + elseif typeof(received) ~= typeof(value) then + error( + string.format( + "Response body contained query parameter '%s' but it was of type '%s', expected '%s'", + key, + typeof(received), + typeof(value) + ) + ) + elseif received ~= value then + error( + string.format( + "Response body contained query parameter '%s' but it had the value '%s', expected '%s'", + key, + received, + value + ) + ) + end +end