Commit 7185d98
New call setup page
Changed files (8)
modifiedCargo.lockmodifiedCargo.tomlmodifiedindex.htmlmodifiedsrc-tauri/Cargo.lockmodifiedsrc-tauri/Cargo.tomlmodifiedsrc-tauri/src/lib.rsmodifiedsrc/lib.rsmodifiedsrc/styles.css
Diff
@@ -405,9 +405,9 @@ dependencies = [ [[package]] name = "kaja-web"-version = "1.0.3"+version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "bc3203211c54861fc57d5059ad27498f2fcc420bdde37f20409a01fe4554f4e2"+checksum = "2c140915814758529ff1d8fe9c6ca30e21b62573571f136f02469b569666ccf3" dependencies = [ "gloo", "gloo-console",@@ -8,7 +8,7 @@ path = "src/lib.rs" crate-type = ["cdylib"] [dependencies]-kaja-web = "=1.0.3"+kaja-web = "1.0.7" serde = { version = "1", features = ["derive"] } tauri-wasm = { version = "0.2", features = ["serde"] } wasm-bindgen = "0.2"@@ -19,6 +19,8 @@ inventory = "0.3" web-sys = { version = "0.3", features = [ "Element", "HtmlElement",+ "HtmlTextAreaElement",+ "HtmlSelectElement", "NodeList", "CustomElementRegistry", "ElementDefinitionOptions",@@ -4,7 +4,7 @@ <meta charset="UTF-8" /> <link rel="stylesheet" href="/src/styles.css" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />- <title>Tauri App</title>+ <title>REST Call</title> <script type="module"> import init from "./pkg/main.js"; await init();@@ -12,6 +12,6 @@ </head> <body>- <greet-app></greet-app>+ <rest-call-app></rest-call-app> </body> </html>@@ -221,6 +221,12 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64"+version = "0.22.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"++[[package]]+name = "base64" version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"@@ -455,6 +461,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]]+name = "cfg_aliases"+version = "0.2.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"++[[package]]+name = "chacha20"+version = "0.10.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"+dependencies = [+ "cfg-if",+ "cpufeatures 0.3.1",+ "rand_core",+]++[[package]] name = "chrono" version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -545,6 +568,15 @@ dependencies = [ ] [[package]]+name = "cpufeatures"+version = "0.3.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"+dependencies = [+ "libc",+]++[[package]] name = "crc32fast" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -1220,6 +1252,19 @@ dependencies = [ [[package]] name = "getrandom"+version = "0.2.17"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"+dependencies = [+ "cfg-if",+ "js-sys",+ "libc",+ "wasi",+ "wasm-bindgen",+]++[[package]]+name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"@@ -1237,8 +1282,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if",+ "js-sys", "libc", "r-efi 6.0.0",+ "rand_core",+ "wasm-bindgen", ] [[package]]@@ -1494,6 +1542,22 @@ dependencies = [ ] [[package]]+name = "hyper-rustls"+version = "0.27.10"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"+dependencies = [+ "http",+ "hyper",+ "hyper-util",+ "rustls",+ "tokio",+ "tokio-rustls",+ "tower-service",+ "webpki-roots",+]++[[package]] name = "hyper-util" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -1974,6 +2038,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" [[package]]+name = "lru-slab"+version = "0.1.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"++[[package]] name = "markup5ever" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -2643,6 +2713,62 @@ dependencies = [ ] [[package]]+name = "quinn"+version = "0.11.12"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"+dependencies = [+ "bytes",+ "cfg_aliases",+ "pin-project-lite",+ "quinn-proto",+ "quinn-udp",+ "rustc-hash",+ "rustls",+ "socket2",+ "thiserror 2.0.21",+ "tokio",+ "tracing",+ "web-time",+]++[[package]]+name = "quinn-proto"+version = "0.11.19"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"+dependencies = [+ "bytes",+ "getrandom 0.4.3",+ "lru-slab",+ "rand",+ "rand_pcg",+ "ring",+ "rustc-hash",+ "rustls",+ "rustls-pki-types",+ "slab",+ "thiserror 2.0.21",+ "tinyvec",+ "tracing",+ "web-time",+]++[[package]]+name = "quinn-udp"+version = "0.5.16"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"+dependencies = [+ "cfg_aliases",+ "libc",+ "once_cell",+ "socket2",+ "tracing",+ "windows-sys 0.61.2",+]++[[package]] name = "quote" version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -2664,6 +2790,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]]+name = "rand"+version = "0.10.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"+dependencies = [+ "chacha20",+ "getrandom 0.4.3",+ "rand_core",+]++[[package]]+name = "rand_core"+version = "0.10.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"++[[package]]+name = "rand_pcg"+version = "0.10.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"+dependencies = [+ "rand_core",+]++[[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -2739,6 +2891,44 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest"+version = "0.12.28"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"+dependencies = [+ "base64 0.22.1",+ "bytes",+ "futures-core",+ "http",+ "http-body",+ "http-body-util",+ "hyper",+ "hyper-rustls",+ "hyper-util",+ "js-sys",+ "log",+ "percent-encoding",+ "pin-project-lite",+ "quinn",+ "rustls",+ "rustls-pki-types",+ "serde",+ "serde_json",+ "serde_urlencoded",+ "sync_wrapper",+ "tokio",+ "tokio-rustls",+ "tower",+ "tower-http",+ "tower-service",+ "url",+ "wasm-bindgen",+ "wasm-bindgen-futures",+ "web-sys",+ "webpki-roots",+]++[[package]]+name = "reqwest" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"@@ -2772,6 +2962,20 @@ dependencies = [ ] [[package]]+name = "ring"+version = "0.17.14"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"+dependencies = [+ "cc",+ "cfg-if",+ "getrandom 0.2.17",+ "libc",+ "untrusted",+ "windows-sys 0.52.0",+]++[[package]] name = "rustc-hash" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -2800,12 +3004,53 @@ dependencies = [ ] [[package]]+name = "rustls"+version = "0.23.46"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"+dependencies = [+ "once_cell",+ "ring",+ "rustls-pki-types",+ "rustls-webpki",+ "subtle",+ "zeroize",+]++[[package]]+name = "rustls-pki-types"+version = "1.15.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"+dependencies = [+ "web-time",+ "zeroize",+]++[[package]]+name = "rustls-webpki"+version = "0.103.16"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281"+dependencies = [+ "ring",+ "rustls-pki-types",+ "untrusted",+]++[[package]] name = "rustversion" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]]+name = "ryu"+version = "1.0.23"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"++[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -2996,6 +3241,18 @@ dependencies = [ ] [[package]]+name = "serde_urlencoded"+version = "0.7.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"+dependencies = [+ "form_urlencoded",+ "itoa",+ "ryu",+ "serde",+]++[[package]] name = "serde_with" version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -3066,7 +3323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if",- "cpufeatures",+ "cpufeatures 0.2.17", "digest", ] @@ -3211,6 +3468,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]]+name = "subtle"+version = "2.6.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"++[[package]] name = "swift-rs" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -3373,7 +3636,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle",- "reqwest",+ "reqwest 0.13.5", "serde", "serde_json", "serde_repr",@@ -3398,6 +3661,7 @@ dependencies = [ name = "tauri-app" version = "0.1.0" dependencies = [+ "reqwest 0.12.28", "serde", "serde_json", "tauri",@@ -3729,6 +3993,16 @@ dependencies = [ ] [[package]]+name = "tokio-rustls"+version = "0.26.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db"+dependencies = [+ "rustls",+ "tokio",+]++[[package]] name = "tokio-util" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -3977,6 +4251,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]]+name = "untrusted"+version = "0.9.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"++[[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -4239,6 +4519,15 @@ dependencies = [ ] [[package]]+name = "webpki-roots"+version = "1.0.11"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171"+dependencies = [+ "rustls-pki-types",+]++[[package]] name = "webview2-com" version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -4426,7 +4715,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [- "windows-targets",+ "windows-targets 0.42.2",+]++[[package]]+name = "windows-sys"+version = "0.52.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"+dependencies = [+ "windows-targets 0.52.6", ] [[package]]@@ -4444,13 +4742,29 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [- "windows_aarch64_gnullvm",- "windows_aarch64_msvc",- "windows_i686_gnu",- "windows_i686_msvc",- "windows_x86_64_gnu",- "windows_x86_64_gnullvm",- "windows_x86_64_msvc",+ "windows_aarch64_gnullvm 0.42.2",+ "windows_aarch64_msvc 0.42.2",+ "windows_i686_gnu 0.42.2",+ "windows_i686_msvc 0.42.2",+ "windows_x86_64_gnu 0.42.2",+ "windows_x86_64_gnullvm 0.42.2",+ "windows_x86_64_msvc 0.42.2",+]++[[package]]+name = "windows-targets"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"+dependencies = [+ "windows_aarch64_gnullvm 0.52.6",+ "windows_aarch64_msvc 0.52.6",+ "windows_i686_gnu 0.52.6",+ "windows_i686_gnullvm",+ "windows_i686_msvc 0.52.6",+ "windows_x86_64_gnu 0.52.6",+ "windows_x86_64_gnullvm 0.52.6",+ "windows_x86_64_msvc 0.52.6", ] [[package]]@@ -4478,42 +4792,90 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]]+name = "windows_aarch64_gnullvm"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"++[[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]]+name = "windows_aarch64_msvc"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"++[[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]]+name = "windows_i686_gnu"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"++[[package]]+name = "windows_i686_gnullvm"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"++[[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]]+name = "windows_i686_msvc"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"++[[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]]+name = "windows_x86_64_gnu"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"++[[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]]+name = "windows_x86_64_gnullvm"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"++[[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]]+name = "windows_x86_64_msvc"+version = "0.52.6"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"++[[package]] name = "winnow" version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -4732,6 +5094,12 @@ dependencies = [ ] [[package]]+name = "zeroize"+version = "1.9.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"++[[package]] name = "zerotrie" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -22,6 +22,7 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1"+reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } # Read the optimization guideline for more details: https://tauri.app/concept/size/#cargo-configuration@@ -1,14 +1,84 @@-// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/+use reqwest::{+ Client, Method, Url,+ header::{HeaderMap, HeaderName, HeaderValue},+};+use serde::{Deserialize, Serialize};++#[derive(Deserialize)]+struct RequestHeader {+ key: String,+ value: String,+}++#[derive(Serialize)]+struct PostResponse {+ status: u16,+ body: String,+}+ #[tauri::command]-fn greet(name: &str) -> String {- format!("Hello, {}! You've been greeted from Rust!", name)+async fn send_request(+ method: String,+ domain: String,+ endpoint: String,+ body: String,+ headers: Vec<RequestHeader>,+) -> Result<PostResponse, String> {+ let method = Method::from_bytes(method.as_bytes())+ .map_err(|error| format!("Invalid HTTP method: {error}"))?;+ let url = build_url(&domain, &endpoint)?;+ let headers = parse_headers(headers)?;++ let response = Client::new()+ .request(method, url)+ .headers(headers)+ .body(body)+ .send()+ .await+ .map_err(|error| format!("HTTP request failed: {error}"))?;++ let status = response.status().as_u16();+ let body = response+ .text()+ .await+ .map_err(|error| format!("Could not read response body: {error}"))?;++ Ok(PostResponse { status, body })+}++fn build_url(domain: &str, endpoint: &str) -> Result<Url, String> {+ let domain = domain.trim();+ if domain.is_empty() {+ return Err("Domain is required.".to_owned());+ }++ let base = Url::parse(domain).map_err(|error| format!("Invalid domain: {error}"))?;+ if !matches!(base.scheme(), "http" | "https") || !base.has_host() {+ return Err("Domain must be an absolute http or https URL.".to_owned());+ }++ base.join(endpoint.trim())+ .map_err(|error| format!("Invalid endpoint: {error}"))+}++fn parse_headers(headers: Vec<RequestHeader>) -> Result<HeaderMap, String> {+ let mut header_map = HeaderMap::new();+ for header in headers {+ let name = HeaderName::from_bytes(header.key.as_bytes())+ .map_err(|error| format!("Invalid header name: {error}"))?;+ let value = HeaderValue::from_str(&header.value)+ .map_err(|error| format!("Invalid value for header {name}: {error}"))?;+ header_map.insert(name, value);+ }++ Ok(header_map) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init())- .invoke_handler(tauri::generate_handler![greet])+ .invoke_handler(tauri::generate_handler![send_request]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }@@ -1,42 +1,103 @@+use std::cell::Cell;+ use kaja_web::prelude::*;-use wasm_bindgen::prelude::wasm_bindgen;+use wasm_bindgen::{JsCast, prelude::wasm_bindgen}; use wasm_bindgen_futures::spawn_local; +thread_local! {+ static HEADER_ROW_COUNT: Cell<usize> = const { Cell::new(1) };+}++#[derive(serde::Serialize)]+struct RequestHeader {+ key: String,+ value: String,+}+ #[derive(serde::Serialize)]-struct GreetArgs {- name: String,+struct PostRequestArgs {+ method: String,+ domain: String,+ endpoint: String,+ body: String,+ headers: Vec<RequestHeader>, } -#[component("greet-app")]-struct GreetApp {}+#[component("rest-call-app")]+struct RestCallApp {} -impl Component for GreetAppComponent {+impl Component for RestCallAppComponent { fn connected(&self) { self.render(); } } -impl GreetAppComponent {+impl RestCallAppComponent { fn render(&self) { let id = self.get_id(); let content = html! {{ <main class="container">- <h1>Welcome to Tauri</h1>+ <header class="page-header">+ <h1>REST Call</h1>+ <p>Send a REST request to any HTTP server.</p>+ </header> - <div class="row">- <a href="https://tauri.app" target="_blank">- <img src="/assets/tauri.svg" class="logo tauri" alt="Tauri logo" />- </a>- </div>+ <section class="request-form" aria-label="POST request">+ <div class="field">+ <label for="method-input">Method</label>+ <select id="method-input">+ <option value="GET" selected>GET</option>+ <option value="POST">POST</option>+ <option value="PUT">PUT</option>+ <option value="PATCH">PATCH</option>+ <option value="DELETE">DELETE</option>+ <option value="HEAD">HEAD</option>+ <option value="OPTIONS">OPTIONS</option>+ </select>+ </div> - <p>Click on the Tauri logo to learn more about the framework</p>+ <div class="field">+ <label for="domain-input">Domain</label>+ <input id="domain-input" type="url" placeholder="https://api.example.com" />+ </div> - <div class="row" id="greet-form">- <input id="greet-input" placeholder="Enter a name..." />- <button type="button" onclick="greet()">Greet</button>- </div>- <p id="greet-result"></p>+ <div class="field">+ <label for="endpoint-input">Endpoint</label>+ <input id="endpoint-input" type="text" placeholder="/v1/resources" />+ </div>++ <div class="field">+ <label for="body-input">Request body</label>+ <textarea id="body-input" spellcheck="false" placeholder="{"name":"Example"}" onkeydown="if (event.key === 'Tab') { event.preventDefault(); this.setRangeText(' ', this.selectionStart, this.selectionEnd, 'end'); }"></textarea>+ <small>Press Tab to insert four spaces.</small>+ </div>++ <div class="field">+ <span class="field-label">Headers</span>+ <div id="header-list" class="header-list">+ <div id="header-row-0" class="header-row">+ <div class="header-fields">+ <div>+ <label for="header-key-input-0">Key</label>+ <input id="header-key-input-0" type="text" placeholder="Content-Type" />+ </div>+ <div>+ <label for="header-value-input-0">Value</label>+ <input id="header-value-input-0" type="text" placeholder="application/json" />+ </div>+ </div>+ <button onclick="removeHeader(0)">Remove</button>+ </div>+ </div>+ <button onclick="addHeader()">Add header</button>+ </div>++ <button onclick="postRequest()">Send request</button>+ </section>++ <p id="request-status" role="status" aria-live="polite"></p>+ <pre id="request-result"></pre> </main> }}; @@ -46,31 +107,146 @@ impl GreetAppComponent { } } -#[callback(greet)]-fn greet_callback() {- let name = try_get_value("#greet-input");+#[callback(addHeader)]+fn add_header_callback() {+ let added = HEADER_ROW_COUNT.with(|count| {+ let index = count.get();+ let row = html! {{+ <div id="header-row-$index" class="header-row">+ <div class="header-fields">+ <div>+ <label for="header-key-input-$index">Key</label>+ <input id="header-key-input-$index" type="text">+ </div>+ <div>+ <label for="header-value-input-$index">Value</label>+ <input id="header-value-input-$index" type="text">+ </div>+ </div>+ <button onclick="removeHeader($index)">Remove</button>+ </div>+ }}; - if name.is_empty() {- log!("No name provided.");- return;+ let Some(header_list) = get_element("#header-list") else {+ return false;+ };++ if header_list.insert_adjacent_html("beforeend", &row).is_ok() {+ count.set(index + 1);+ true+ } else {+ false+ }+ });++ if !added {+ try_inner_text("#request-status", "Could not add a header field."); }+} - spawn_local(async move {- let result = async {- let payload = GreetArgs { name };- let args = tauri_wasm::args(&payload)?;- tauri_wasm::invoke("greet").with_args(args).await+#[callback(removeHeader)]+fn remove_header_callback(index: usize) {+ let selector = format!("#header-row-{index}");+ if let Some(row) = get_element(&selector) {+ row.remove();+ }+}++fn try_get_textarea_value(selector: &str) -> String {+ get_element(selector)+ .and_then(|element| element.dyn_into::<web_sys::HtmlTextAreaElement>().ok())+ .map(|textarea| textarea.value())+ .unwrap_or_default()+}++fn try_get_select_value(selector: &str) -> String {+ get_element(selector)+ .and_then(|element| element.dyn_into::<web_sys::HtmlSelectElement>().ok())+ .map(|select| select.value())+ .unwrap_or_default()+}++fn collect_headers() -> std::result::Result<Vec<RequestHeader>, String> {+ HEADER_ROW_COUNT.with(|count| {+ let mut headers = Vec::new();++ for index in 0..count.get() {+ let key = try_get_value(&format!("#header-key-input-{index}"))+ .trim()+ .to_owned();+ let value = try_get_value(&format!("#header-value-input-{index}"))+ .trim()+ .to_owned();++ if key.is_empty() && value.is_empty() {+ continue;+ }++ if key.is_empty() || value.is_empty() {+ return Err(format!("Header {} needs both a key and value.", index + 1));+ }++ headers.push(RequestHeader { key, value }); }- .await; - match result {- Ok(value) => {- let message = value.as_string().unwrap_or_default();- try_inner_text("#greet-result", &message);+ Ok(headers)+ })+}++#[callback(postRequest)]+fn post_request_callback() {+ let headers = match collect_headers() {+ Ok(headers) => headers,+ Err(message) => {+ try_inner_text("#request-status", &message);+ return;+ }+ };++ let payload = PostRequestArgs {+ method: try_get_select_value("#method-input"),+ domain: try_get_value("#domain-input").trim().to_owned(),+ endpoint: try_get_value("#endpoint-input").trim().to_owned(),+ body: try_get_textarea_value("#body-input"),+ headers,+ };++ if payload.domain.is_empty() {+ try_inner_text("#request-status", "Enter a domain to send the request.");+ return;+ }++ try_inner_text(+ "#request-status",+ &format!("Sending {} request…", payload.method),+ );+ try_inner_text("#request-result", "");++ spawn_local(async move {+ let args = match tauri_wasm::args(&payload) {+ Ok(args) => args,+ Err(error) => {+ try_inner_text(+ "#request-status",+ &format!("Could not prepare request: {:?}", error),+ );+ return; }+ };++ match tauri_wasm::invoke("send_request").with_args(args).await {+ Ok(value) => match js_sys::JSON::stringify(&value) {+ Ok(response) => {+ try_inner_text("#request-status", "Request completed.");+ try_inner_text("#request-result", &response.as_string().unwrap_or_default());+ }+ Err(error) => try_inner_text(+ "#request-status",+ &format!("Could not read response: {:?}", error),+ ),+ }, Err(error) => {- let message = format!("Error: {:?}", error);- try_inner_text("#greet-result", &message);+ try_inner_text("#request-status", &format!("Request failed: {:?}", error)) } } });@@ -1,110 +1,258 @@- :root { font-family: Inter, Avenir, Helvetica, Arial, sans-serif;- font-size: 16px;- line-height: 24px;- font-weight: 400;-- color: #0f0f0f;- background-color: #f6f6f6;-+ color: #1f2937;+ background: #f5f7fa; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;- -webkit-text-size-adjust: 100%;+}++body {+ margin: 0;+ min-width: 320px; } .container {+ max-width: 680px;+ margin: 0 auto;+ padding: 72px 24px;+}++.page-header {+ margin-bottom: 28px;+}++h1 { margin: 0;- padding-top: 10vh;- display: flex;- flex-direction: column;- justify-content: center;- text-align: center;+ font-size: 2rem;+ line-height: 1.2;+ letter-spacing: -0.02em;+}++.page-header p {+ margin: 8px 0 0;+ color: #64748b; } -.logo {- height: 6em;- padding: 1.5em;- will-change: filter;- transition: 0.75s;+.request-form {+ display: grid;+ gap: 20px;+ padding: 28px;+ background: #ffffff;+ border: 1px solid #e2e8f0;+ border-radius: 12px;+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06); } -.logo.tauri:hover {- filter: drop-shadow(0 0 2em #24c8db);+.field {+ display: grid;+ gap: 7px; } -.row {- display: flex;- justify-content: center;+.field label,+.field-label {+ font-size: 0.9rem;+ font-weight: 600; } -a {- font-weight: 500;- color: #646cff;- text-decoration: inherit;+.header-list {+ display: grid;+ gap: 16px;+ padding: 16px;+ background: #f8fafc;+ border: 1px solid #e2e8f0;+ border-radius: 8px; } -a:hover {- color: #535bf2;+.header-row {+ display: grid;+ grid-template-columns: minmax(0, 1fr) auto;+ align-items: end;+ gap: 16px;+ padding-bottom: 16px;+ border-bottom: 1px solid #e2e8f0; } -h1 {- text-align: center;+.header-row:last-child {+ padding-bottom: 0;+ border-bottom: 0;+}++.header-row > button {+ margin: 0 0 1px;+}++.header-fields {+ display: grid;+ grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);+ gap: 16px;+}++.header-fields > div {+ display: grid;+ gap: 6px;+}++.header-list + button {+ margin-top: 4px;+}++.field small {+ color: #64748b;+ font-size: 0.8rem;+ line-height: 1.35; } input,+select,+textarea, button {- border-radius: 8px;- border: 1px solid transparent;- padding: 0.6em 1.2em;- font-size: 1em;- font-weight: 500;- font-family: inherit;- color: #0f0f0f;- background-color: #ffffff;- transition: border-color 0.25s;- box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);+ box-sizing: border-box;+ border-radius: 7px;+ font: inherit;+}++input,+select,+textarea {+ width: 100%;+ padding: 10px 12px;+ color: inherit;+ background: #ffffff;+ border: 1px solid #cbd5e1;+ outline: none;+}++textarea {+ min-height: 160px;+ resize: vertical;+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;+ line-height: 1.5;+}++input::placeholder,+textarea::placeholder {+ color: #94a3b8;+}++input:focus,+select:focus,+textarea:focus {+ border-color: #2563eb;+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12); } button {+ justify-self: start; cursor: pointer;+ padding: 10px 16px;+ color: #ffffff;+ background: #2563eb;+ border: 1px solid #2563eb;+ font-weight: 600; } button:hover {- border-color: #396cd8;+ background: #1d4ed8;+ border-color: #1d4ed8; }-button:active {- border-color: #396cd8;- background-color: #e8e8e8;++button:focus-visible {+ outline: 3px solid rgba(37, 99, 235, 0.35);+ outline-offset: 2px; } -input,-button {- outline: none;+#request-status {+ min-height: 24px;+ margin: 16px 0 0;+ color: #475569; } -#greet-input {- margin-right: 5px;+#request-result {+ max-height: 280px;+ margin: 12px 0 0;+ padding: 14px;+ overflow: auto;+ color: #334155;+ background: #eef2f7;+ border: 1px solid #e2e8f0;+ border-radius: 8px;+ text-align: left;+ white-space: pre-wrap;+ overflow-wrap: anywhere;+}++#request-result:empty {+ display: none; } @media (prefers-color-scheme: dark) { :root {- color: #f6f6f6;- background-color: #2f2f2f;+ color: #e2e8f0;+ background: #111827;+ }++ .page-header p,+ .field small,+ #request-status {+ color: #94a3b8; } - a:hover {- color: #24c8db;+ .request-form {+ background: #1f2937;+ border-color: #334155;+ box-shadow: none; } input,+ select,+ textarea {+ color: inherit;+ background: #111827;+ border-color: #475569;+ }++ .header-list {+ background: #182233;+ border-color: #334155;+ }++ .header-row {+ border-color: #334155;+ }++ #request-result {+ color: #cbd5e1;+ background: #111827;+ border-color: #334155;+ }+ button {- color: #ffffff;- background-color: #0f0f0f98;+ background: #1d4ed8;+ border-color: #1d4ed8;+ }++ button:hover {+ background: #2563eb;+ border-color: #2563eb; }- button:active {- background-color: #0f0f0f69;+}++@media (max-width: 520px) {+ .container {+ padding: 40px 16px;+ }++ .request-form {+ padding: 20px;+ }++ .header-row {+ grid-template-columns: minmax(0, 1fr) auto;+ }++ .header-fields {+ grid-template-columns: 1fr; } }