Clone in a terminal:
Commit 7185d98
Johan Mattsson - 2026-11-02T11:28:10+00:00
7185d98cf6748886bdd5af006f6fe923e0a865e3johan.mattsson.m@gmail.com

New call setup page

Changed files (8)

Diff

diff --git a/Cargo.lock b/Cargo.lockindex a3d040d..761f624 100644--- a/Cargo.lock+++ b/Cargo.lock@@ -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",diff --git a/Cargo.toml b/Cargo.tomlindex 76922cb..2200d92 100644--- a/Cargo.toml+++ b/Cargo.toml@@ -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",diff --git a/index.html b/index.htmlindex 8f59a43..270294f 100644--- a/index.html+++ b/index.html@@ -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>diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lockindex 157a5dc..d6b534e 100644--- a/src-tauri/Cargo.lock+++ b/src-tauri/Cargo.lock@@ -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"diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.tomlindex fd2715b..d2e1863 100644--- a/src-tauri/Cargo.toml+++ b/src-tauri/Cargo.toml@@ -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-configurationdiff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rsindex 4a277ef..bba41f4 100644--- a/src-tauri/src/lib.rs+++ b/src-tauri/src/lib.rs@@ -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"); }diff --git a/src/lib.rs b/src/lib.rsindex 193551a..cc89ce9 100644--- a/src/lib.rs+++ b/src/lib.rs@@ -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="{&quot;name&quot;:&quot;Example&quot;}" 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))             }         }     });diff --git a/src/styles.css b/src/styles.cssindex 57a6a4c..f0874ed 100644--- a/src/styles.css+++ b/src/styles.css@@ -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;   } }

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112