├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── bootstrap │ ├── assets │ │ └── favicon.ico │ ├── config.json │ ├── data │ │ ├── schema.json │ │ └── site.json │ └── templates │ │ ├── index.html │ │ └── post.html ├── certs │ ├── cert.txt │ └── key.txt ├── demo │ ├── assets │ │ ├── cors.json │ │ └── favicon.ico │ ├── config.json │ ├── config.toml │ ├── data │ │ ├── chemistry.json │ │ └── starwars │ │ │ ├── films.json │ │ │ ├── people.json │ │ │ ├── planets.json │ │ │ ├── species.json │ │ │ ├── starships.json │ │ │ └── vehicles.json │ └── templates │ │ ├── base.html │ │ ├── chemistry │ │ ├── data.html │ │ ├── element.html │ │ └── table.html │ │ ├── cli.html │ │ ├── cors.html │ │ ├── debug │ │ ├── form.html │ │ └── result.html │ │ ├── index.html │ │ ├── notes │ │ ├── create.html │ │ ├── edit.html │ │ ├── read.html │ │ └── update.html │ │ └── starwars.html └── tests │ ├── assets │ ├── .hidden.txt │ ├── .secret │ │ └── hello.txt │ ├── favicon.ico │ ├── index.html │ └── tests │ │ ├── .hi.txt │ │ ├── data.json │ │ └── deep │ │ └── msg.txt │ ├── config.toml │ ├── templates │ ├── blank.html │ ├── httpbin.html │ └── tests │ │ └── data.txt │ └── test.hurl ├── favicon.ico └── src ├── app ├── context.rs ├── mod.rs ├── modify.rs └── proxy.rs ├── assets.rs ├── config.rs ├── debug.rs ├── main.rs └── templates ├── command.rs ├── fetch.rs ├── file.rs ├── format.rs ├── mod.rs └── parse.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/data/notes/** 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.17" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "is_terminal_polyfill", 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle" 52 | version = "1.0.9" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 55 | 56 | [[package]] 57 | name = "anstyle-parse" 58 | version = "0.2.6" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 61 | dependencies = [ 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-query" 67 | version = "1.1.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 70 | dependencies = [ 71 | "windows-sys 0.59.0", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-wincon" 76 | version = "3.0.6" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 79 | dependencies = [ 80 | "anstyle", 81 | "windows-sys 0.59.0", 82 | ] 83 | 84 | [[package]] 85 | name = "arc-swap" 86 | version = "1.7.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 89 | 90 | [[package]] 91 | name = "async-trait" 92 | version = "0.1.83" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 95 | dependencies = [ 96 | "proc-macro2", 97 | "quote", 98 | "syn", 99 | ] 100 | 101 | [[package]] 102 | name = "atomic-waker" 103 | version = "1.1.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 106 | 107 | [[package]] 108 | name = "autocfg" 109 | version = "1.4.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 112 | 113 | [[package]] 114 | name = "axum" 115 | version = "0.7.7" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" 118 | dependencies = [ 119 | "async-trait", 120 | "axum-core", 121 | "bytes", 122 | "futures-util", 123 | "http", 124 | "http-body", 125 | "http-body-util", 126 | "hyper", 127 | "hyper-util", 128 | "itoa", 129 | "matchit", 130 | "memchr", 131 | "mime", 132 | "percent-encoding", 133 | "pin-project-lite", 134 | "rustversion", 135 | "serde", 136 | "serde_json", 137 | "serde_path_to_error", 138 | "serde_urlencoded", 139 | "sync_wrapper 1.0.1", 140 | "tokio", 141 | "tower 0.5.1", 142 | "tower-layer", 143 | "tower-service", 144 | "tracing", 145 | ] 146 | 147 | [[package]] 148 | name = "axum-core" 149 | version = "0.4.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" 152 | dependencies = [ 153 | "async-trait", 154 | "bytes", 155 | "futures-util", 156 | "http", 157 | "http-body", 158 | "http-body-util", 159 | "mime", 160 | "pin-project-lite", 161 | "rustversion", 162 | "sync_wrapper 1.0.1", 163 | "tower-layer", 164 | "tower-service", 165 | "tracing", 166 | ] 167 | 168 | [[package]] 169 | name = "axum-server" 170 | version = "0.7.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "56bac90848f6a9393ac03c63c640925c4b7c8ca21654de40d53f55964667c7d8" 173 | dependencies = [ 174 | "arc-swap", 175 | "bytes", 176 | "futures-util", 177 | "http", 178 | "http-body", 179 | "http-body-util", 180 | "hyper", 181 | "hyper-util", 182 | "openssl", 183 | "pin-project-lite", 184 | "tokio", 185 | "tokio-openssl", 186 | "tower 0.4.13", 187 | "tower-service", 188 | ] 189 | 190 | [[package]] 191 | name = "backtrace" 192 | version = "0.3.74" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 195 | dependencies = [ 196 | "addr2line", 197 | "cfg-if", 198 | "libc", 199 | "miniz_oxide", 200 | "object", 201 | "rustc-demangle", 202 | "windows-targets", 203 | ] 204 | 205 | [[package]] 206 | name = "base64" 207 | version = "0.22.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 210 | 211 | [[package]] 212 | name = "bitflags" 213 | version = "2.6.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 216 | 217 | [[package]] 218 | name = "bumpalo" 219 | version = "3.16.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 222 | 223 | [[package]] 224 | name = "bytes" 225 | version = "1.8.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 228 | 229 | [[package]] 230 | name = "cc" 231 | version = "1.1.31" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" 234 | dependencies = [ 235 | "shlex", 236 | ] 237 | 238 | [[package]] 239 | name = "cfg-if" 240 | version = "1.0.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 243 | 244 | [[package]] 245 | name = "chrono" 246 | version = "0.4.38" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 249 | dependencies = [ 250 | "android-tzdata", 251 | "iana-time-zone", 252 | "js-sys", 253 | "num-traits", 254 | "wasm-bindgen", 255 | "windows-targets", 256 | ] 257 | 258 | [[package]] 259 | name = "clap" 260 | version = "4.5.20" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 263 | dependencies = [ 264 | "clap_builder", 265 | "clap_derive", 266 | ] 267 | 268 | [[package]] 269 | name = "clap_builder" 270 | version = "4.5.20" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 273 | dependencies = [ 274 | "anstream", 275 | "anstyle", 276 | "clap_lex", 277 | "strsim", 278 | ] 279 | 280 | [[package]] 281 | name = "clap_derive" 282 | version = "4.5.18" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 285 | dependencies = [ 286 | "heck", 287 | "proc-macro2", 288 | "quote", 289 | "syn", 290 | ] 291 | 292 | [[package]] 293 | name = "clap_lex" 294 | version = "0.7.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 297 | 298 | [[package]] 299 | name = "colorchoice" 300 | version = "1.0.3" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 303 | 304 | [[package]] 305 | name = "core-foundation" 306 | version = "0.9.4" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 309 | dependencies = [ 310 | "core-foundation-sys", 311 | "libc", 312 | ] 313 | 314 | [[package]] 315 | name = "core-foundation-sys" 316 | version = "0.8.7" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 319 | 320 | [[package]] 321 | name = "encoding_rs" 322 | version = "0.8.35" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 325 | dependencies = [ 326 | "cfg-if", 327 | ] 328 | 329 | [[package]] 330 | name = "equivalent" 331 | version = "1.0.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 334 | 335 | [[package]] 336 | name = "errno" 337 | version = "0.3.9" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 340 | dependencies = [ 341 | "libc", 342 | "windows-sys 0.52.0", 343 | ] 344 | 345 | [[package]] 346 | name = "fastrand" 347 | version = "2.1.1" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 350 | 351 | [[package]] 352 | name = "fnv" 353 | version = "1.0.7" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 356 | 357 | [[package]] 358 | name = "foreign-types" 359 | version = "0.3.2" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 362 | dependencies = [ 363 | "foreign-types-shared", 364 | ] 365 | 366 | [[package]] 367 | name = "foreign-types-shared" 368 | version = "0.1.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 371 | 372 | [[package]] 373 | name = "form_urlencoded" 374 | version = "1.2.1" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 377 | dependencies = [ 378 | "percent-encoding", 379 | ] 380 | 381 | [[package]] 382 | name = "futures-channel" 383 | version = "0.3.31" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 386 | dependencies = [ 387 | "futures-core", 388 | "futures-sink", 389 | ] 390 | 391 | [[package]] 392 | name = "futures-core" 393 | version = "0.3.31" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 396 | 397 | [[package]] 398 | name = "futures-io" 399 | version = "0.3.31" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 402 | 403 | [[package]] 404 | name = "futures-sink" 405 | version = "0.3.31" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 408 | 409 | [[package]] 410 | name = "futures-task" 411 | version = "0.3.31" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 414 | 415 | [[package]] 416 | name = "futures-util" 417 | version = "0.3.31" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 420 | dependencies = [ 421 | "futures-core", 422 | "futures-io", 423 | "futures-sink", 424 | "futures-task", 425 | "memchr", 426 | "pin-project-lite", 427 | "pin-utils", 428 | "slab", 429 | ] 430 | 431 | [[package]] 432 | name = "getrandom" 433 | version = "0.2.15" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 436 | dependencies = [ 437 | "cfg-if", 438 | "libc", 439 | "wasi", 440 | ] 441 | 442 | [[package]] 443 | name = "gimli" 444 | version = "0.31.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 447 | 448 | [[package]] 449 | name = "glob-match" 450 | version = "0.2.1" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" 453 | 454 | [[package]] 455 | name = "h2" 456 | version = "0.4.6" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" 459 | dependencies = [ 460 | "atomic-waker", 461 | "bytes", 462 | "fnv", 463 | "futures-core", 464 | "futures-sink", 465 | "http", 466 | "indexmap", 467 | "slab", 468 | "tokio", 469 | "tokio-util", 470 | "tracing", 471 | ] 472 | 473 | [[package]] 474 | name = "hashbrown" 475 | version = "0.15.0" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 478 | 479 | [[package]] 480 | name = "heck" 481 | version = "0.5.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 484 | 485 | [[package]] 486 | name = "hermit-abi" 487 | version = "0.3.9" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 490 | 491 | [[package]] 492 | name = "http" 493 | version = "1.1.0" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 496 | dependencies = [ 497 | "bytes", 498 | "fnv", 499 | "itoa", 500 | ] 501 | 502 | [[package]] 503 | name = "http-body" 504 | version = "1.0.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 507 | dependencies = [ 508 | "bytes", 509 | "http", 510 | ] 511 | 512 | [[package]] 513 | name = "http-body-util" 514 | version = "0.1.2" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 517 | dependencies = [ 518 | "bytes", 519 | "futures-util", 520 | "http", 521 | "http-body", 522 | "pin-project-lite", 523 | ] 524 | 525 | [[package]] 526 | name = "httparse" 527 | version = "1.9.5" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 530 | 531 | [[package]] 532 | name = "httpdate" 533 | version = "1.0.3" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 536 | 537 | [[package]] 538 | name = "hyper" 539 | version = "1.5.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" 542 | dependencies = [ 543 | "bytes", 544 | "futures-channel", 545 | "futures-util", 546 | "h2", 547 | "http", 548 | "http-body", 549 | "httparse", 550 | "httpdate", 551 | "itoa", 552 | "pin-project-lite", 553 | "smallvec", 554 | "tokio", 555 | "want", 556 | ] 557 | 558 | [[package]] 559 | name = "hyper-rustls" 560 | version = "0.27.3" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 563 | dependencies = [ 564 | "futures-util", 565 | "http", 566 | "hyper", 567 | "hyper-util", 568 | "rustls", 569 | "rustls-pki-types", 570 | "tokio", 571 | "tokio-rustls", 572 | "tower-service", 573 | ] 574 | 575 | [[package]] 576 | name = "hyper-tls" 577 | version = "0.6.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 580 | dependencies = [ 581 | "bytes", 582 | "http-body-util", 583 | "hyper", 584 | "hyper-util", 585 | "native-tls", 586 | "tokio", 587 | "tokio-native-tls", 588 | "tower-service", 589 | ] 590 | 591 | [[package]] 592 | name = "hyper-util" 593 | version = "0.1.10" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 596 | dependencies = [ 597 | "bytes", 598 | "futures-channel", 599 | "futures-util", 600 | "http", 601 | "http-body", 602 | "hyper", 603 | "pin-project-lite", 604 | "socket2", 605 | "tokio", 606 | "tower-service", 607 | "tracing", 608 | ] 609 | 610 | [[package]] 611 | name = "iana-time-zone" 612 | version = "0.1.61" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 615 | dependencies = [ 616 | "android_system_properties", 617 | "core-foundation-sys", 618 | "iana-time-zone-haiku", 619 | "js-sys", 620 | "wasm-bindgen", 621 | "windows-core", 622 | ] 623 | 624 | [[package]] 625 | name = "iana-time-zone-haiku" 626 | version = "0.1.2" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 629 | dependencies = [ 630 | "cc", 631 | ] 632 | 633 | [[package]] 634 | name = "idna" 635 | version = "0.5.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 638 | dependencies = [ 639 | "unicode-bidi", 640 | "unicode-normalization", 641 | ] 642 | 643 | [[package]] 644 | name = "indexmap" 645 | version = "2.6.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 648 | dependencies = [ 649 | "equivalent", 650 | "hashbrown", 651 | ] 652 | 653 | [[package]] 654 | name = "ipnet" 655 | version = "2.10.1" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 658 | 659 | [[package]] 660 | name = "is_terminal_polyfill" 661 | version = "1.70.1" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 664 | 665 | [[package]] 666 | name = "itoa" 667 | version = "1.0.11" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 670 | 671 | [[package]] 672 | name = "js-sys" 673 | version = "0.3.72" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 676 | dependencies = [ 677 | "wasm-bindgen", 678 | ] 679 | 680 | [[package]] 681 | name = "libc" 682 | version = "0.2.161" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 685 | 686 | [[package]] 687 | name = "linux-raw-sys" 688 | version = "0.4.14" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 691 | 692 | [[package]] 693 | name = "lock_api" 694 | version = "0.4.12" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 697 | dependencies = [ 698 | "autocfg", 699 | "scopeguard", 700 | ] 701 | 702 | [[package]] 703 | name = "log" 704 | version = "0.4.22" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 707 | 708 | [[package]] 709 | name = "matchit" 710 | version = "0.7.3" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 713 | 714 | [[package]] 715 | name = "memchr" 716 | version = "2.7.4" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 719 | 720 | [[package]] 721 | name = "memo-map" 722 | version = "0.3.3" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" 725 | 726 | [[package]] 727 | name = "mime" 728 | version = "0.3.17" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 731 | 732 | [[package]] 733 | name = "mime_guess" 734 | version = "2.0.5" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 737 | dependencies = [ 738 | "mime", 739 | "unicase", 740 | ] 741 | 742 | [[package]] 743 | name = "minijinja" 744 | version = "2.4.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "c9ca8daf4b0b4029777f1bc6e1aedd1aec7b74c276a43bc6f620a8e1a1c0a90e" 747 | dependencies = [ 748 | "memo-map", 749 | "self_cell", 750 | "serde", 751 | ] 752 | 753 | [[package]] 754 | name = "minirps" 755 | version = "0.2.1" 756 | dependencies = [ 757 | "axum", 758 | "axum-server", 759 | "chrono", 760 | "clap", 761 | "glob-match", 762 | "mime_guess", 763 | "minijinja", 764 | "openssl", 765 | "reqwest", 766 | "serde", 767 | "serde_derive", 768 | "serde_json", 769 | "serde_urlencoded", 770 | "tokio", 771 | "toml", 772 | "tower-http", 773 | ] 774 | 775 | [[package]] 776 | name = "miniz_oxide" 777 | version = "0.8.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 780 | dependencies = [ 781 | "adler2", 782 | ] 783 | 784 | [[package]] 785 | name = "mio" 786 | version = "1.0.2" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 789 | dependencies = [ 790 | "hermit-abi", 791 | "libc", 792 | "wasi", 793 | "windows-sys 0.52.0", 794 | ] 795 | 796 | [[package]] 797 | name = "native-tls" 798 | version = "0.2.12" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 801 | dependencies = [ 802 | "libc", 803 | "log", 804 | "openssl", 805 | "openssl-probe", 806 | "openssl-sys", 807 | "schannel", 808 | "security-framework", 809 | "security-framework-sys", 810 | "tempfile", 811 | ] 812 | 813 | [[package]] 814 | name = "num-traits" 815 | version = "0.2.19" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 818 | dependencies = [ 819 | "autocfg", 820 | ] 821 | 822 | [[package]] 823 | name = "object" 824 | version = "0.36.5" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 827 | dependencies = [ 828 | "memchr", 829 | ] 830 | 831 | [[package]] 832 | name = "once_cell" 833 | version = "1.20.2" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 836 | 837 | [[package]] 838 | name = "openssl" 839 | version = "0.10.68" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 842 | dependencies = [ 843 | "bitflags", 844 | "cfg-if", 845 | "foreign-types", 846 | "libc", 847 | "once_cell", 848 | "openssl-macros", 849 | "openssl-sys", 850 | ] 851 | 852 | [[package]] 853 | name = "openssl-macros" 854 | version = "0.1.1" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 857 | dependencies = [ 858 | "proc-macro2", 859 | "quote", 860 | "syn", 861 | ] 862 | 863 | [[package]] 864 | name = "openssl-probe" 865 | version = "0.1.5" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 868 | 869 | [[package]] 870 | name = "openssl-src" 871 | version = "300.4.0+3.4.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" 874 | dependencies = [ 875 | "cc", 876 | ] 877 | 878 | [[package]] 879 | name = "openssl-sys" 880 | version = "0.9.104" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 883 | dependencies = [ 884 | "cc", 885 | "libc", 886 | "openssl-src", 887 | "pkg-config", 888 | "vcpkg", 889 | ] 890 | 891 | [[package]] 892 | name = "parking_lot" 893 | version = "0.12.3" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 896 | dependencies = [ 897 | "lock_api", 898 | "parking_lot_core", 899 | ] 900 | 901 | [[package]] 902 | name = "parking_lot_core" 903 | version = "0.9.10" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 906 | dependencies = [ 907 | "cfg-if", 908 | "libc", 909 | "redox_syscall", 910 | "smallvec", 911 | "windows-targets", 912 | ] 913 | 914 | [[package]] 915 | name = "percent-encoding" 916 | version = "2.3.1" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 919 | 920 | [[package]] 921 | name = "pin-project" 922 | version = "1.1.7" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" 925 | dependencies = [ 926 | "pin-project-internal", 927 | ] 928 | 929 | [[package]] 930 | name = "pin-project-internal" 931 | version = "1.1.7" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" 934 | dependencies = [ 935 | "proc-macro2", 936 | "quote", 937 | "syn", 938 | ] 939 | 940 | [[package]] 941 | name = "pin-project-lite" 942 | version = "0.2.15" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 945 | 946 | [[package]] 947 | name = "pin-utils" 948 | version = "0.1.0" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 951 | 952 | [[package]] 953 | name = "pkg-config" 954 | version = "0.3.31" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 957 | 958 | [[package]] 959 | name = "proc-macro2" 960 | version = "1.0.89" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 963 | dependencies = [ 964 | "unicode-ident", 965 | ] 966 | 967 | [[package]] 968 | name = "quote" 969 | version = "1.0.37" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 972 | dependencies = [ 973 | "proc-macro2", 974 | ] 975 | 976 | [[package]] 977 | name = "redox_syscall" 978 | version = "0.5.7" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 981 | dependencies = [ 982 | "bitflags", 983 | ] 984 | 985 | [[package]] 986 | name = "reqwest" 987 | version = "0.12.8" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" 990 | dependencies = [ 991 | "base64", 992 | "bytes", 993 | "encoding_rs", 994 | "futures-channel", 995 | "futures-core", 996 | "futures-util", 997 | "h2", 998 | "http", 999 | "http-body", 1000 | "http-body-util", 1001 | "hyper", 1002 | "hyper-rustls", 1003 | "hyper-tls", 1004 | "hyper-util", 1005 | "ipnet", 1006 | "js-sys", 1007 | "log", 1008 | "mime", 1009 | "native-tls", 1010 | "once_cell", 1011 | "percent-encoding", 1012 | "pin-project-lite", 1013 | "rustls-pemfile", 1014 | "serde", 1015 | "serde_json", 1016 | "serde_urlencoded", 1017 | "sync_wrapper 1.0.1", 1018 | "system-configuration", 1019 | "tokio", 1020 | "tokio-native-tls", 1021 | "tower-service", 1022 | "url", 1023 | "wasm-bindgen", 1024 | "wasm-bindgen-futures", 1025 | "web-sys", 1026 | "windows-registry", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "ring" 1031 | version = "0.17.8" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1034 | dependencies = [ 1035 | "cc", 1036 | "cfg-if", 1037 | "getrandom", 1038 | "libc", 1039 | "spin", 1040 | "untrusted", 1041 | "windows-sys 0.52.0", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "rustc-demangle" 1046 | version = "0.1.24" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1049 | 1050 | [[package]] 1051 | name = "rustix" 1052 | version = "0.38.38" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" 1055 | dependencies = [ 1056 | "bitflags", 1057 | "errno", 1058 | "libc", 1059 | "linux-raw-sys", 1060 | "windows-sys 0.52.0", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "rustls" 1065 | version = "0.23.16" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" 1068 | dependencies = [ 1069 | "once_cell", 1070 | "rustls-pki-types", 1071 | "rustls-webpki", 1072 | "subtle", 1073 | "zeroize", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "rustls-pemfile" 1078 | version = "2.2.0" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1081 | dependencies = [ 1082 | "rustls-pki-types", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "rustls-pki-types" 1087 | version = "1.10.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" 1090 | 1091 | [[package]] 1092 | name = "rustls-webpki" 1093 | version = "0.102.8" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1096 | dependencies = [ 1097 | "ring", 1098 | "rustls-pki-types", 1099 | "untrusted", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "rustversion" 1104 | version = "1.0.18" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 1107 | 1108 | [[package]] 1109 | name = "ryu" 1110 | version = "1.0.18" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1113 | 1114 | [[package]] 1115 | name = "schannel" 1116 | version = "0.1.26" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 1119 | dependencies = [ 1120 | "windows-sys 0.59.0", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "scopeguard" 1125 | version = "1.2.0" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1128 | 1129 | [[package]] 1130 | name = "security-framework" 1131 | version = "2.11.1" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1134 | dependencies = [ 1135 | "bitflags", 1136 | "core-foundation", 1137 | "core-foundation-sys", 1138 | "libc", 1139 | "security-framework-sys", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "security-framework-sys" 1144 | version = "2.12.0" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 1147 | dependencies = [ 1148 | "core-foundation-sys", 1149 | "libc", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "self_cell" 1154 | version = "1.0.4" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" 1157 | 1158 | [[package]] 1159 | name = "serde" 1160 | version = "1.0.213" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" 1163 | dependencies = [ 1164 | "serde_derive", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "serde_derive" 1169 | version = "1.0.213" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" 1172 | dependencies = [ 1173 | "proc-macro2", 1174 | "quote", 1175 | "syn", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "serde_json" 1180 | version = "1.0.132" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 1183 | dependencies = [ 1184 | "itoa", 1185 | "memchr", 1186 | "ryu", 1187 | "serde", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "serde_path_to_error" 1192 | version = "0.1.16" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" 1195 | dependencies = [ 1196 | "itoa", 1197 | "serde", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "serde_spanned" 1202 | version = "0.6.8" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1205 | dependencies = [ 1206 | "serde", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "serde_urlencoded" 1211 | version = "0.7.1" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1214 | dependencies = [ 1215 | "form_urlencoded", 1216 | "itoa", 1217 | "ryu", 1218 | "serde", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "shlex" 1223 | version = "1.3.0" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1226 | 1227 | [[package]] 1228 | name = "signal-hook-registry" 1229 | version = "1.4.2" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1232 | dependencies = [ 1233 | "libc", 1234 | ] 1235 | 1236 | [[package]] 1237 | name = "slab" 1238 | version = "0.4.9" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1241 | dependencies = [ 1242 | "autocfg", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "smallvec" 1247 | version = "1.13.2" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1250 | 1251 | [[package]] 1252 | name = "socket2" 1253 | version = "0.5.7" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1256 | dependencies = [ 1257 | "libc", 1258 | "windows-sys 0.52.0", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "spin" 1263 | version = "0.9.8" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1266 | 1267 | [[package]] 1268 | name = "strsim" 1269 | version = "0.11.1" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1272 | 1273 | [[package]] 1274 | name = "subtle" 1275 | version = "2.6.1" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1278 | 1279 | [[package]] 1280 | name = "syn" 1281 | version = "2.0.85" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 1284 | dependencies = [ 1285 | "proc-macro2", 1286 | "quote", 1287 | "unicode-ident", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "sync_wrapper" 1292 | version = "0.1.2" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1295 | 1296 | [[package]] 1297 | name = "sync_wrapper" 1298 | version = "1.0.1" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1301 | dependencies = [ 1302 | "futures-core", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "system-configuration" 1307 | version = "0.6.1" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1310 | dependencies = [ 1311 | "bitflags", 1312 | "core-foundation", 1313 | "system-configuration-sys", 1314 | ] 1315 | 1316 | [[package]] 1317 | name = "system-configuration-sys" 1318 | version = "0.6.0" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1321 | dependencies = [ 1322 | "core-foundation-sys", 1323 | "libc", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "tempfile" 1328 | version = "3.13.0" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1331 | dependencies = [ 1332 | "cfg-if", 1333 | "fastrand", 1334 | "once_cell", 1335 | "rustix", 1336 | "windows-sys 0.59.0", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "tinyvec" 1341 | version = "1.8.0" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1344 | dependencies = [ 1345 | "tinyvec_macros", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "tinyvec_macros" 1350 | version = "0.1.1" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1353 | 1354 | [[package]] 1355 | name = "tokio" 1356 | version = "1.41.0" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" 1359 | dependencies = [ 1360 | "backtrace", 1361 | "bytes", 1362 | "libc", 1363 | "mio", 1364 | "parking_lot", 1365 | "pin-project-lite", 1366 | "signal-hook-registry", 1367 | "socket2", 1368 | "tokio-macros", 1369 | "windows-sys 0.52.0", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "tokio-macros" 1374 | version = "2.4.0" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1377 | dependencies = [ 1378 | "proc-macro2", 1379 | "quote", 1380 | "syn", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "tokio-native-tls" 1385 | version = "0.3.1" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1388 | dependencies = [ 1389 | "native-tls", 1390 | "tokio", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "tokio-openssl" 1395 | version = "0.6.5" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" 1398 | dependencies = [ 1399 | "openssl", 1400 | "openssl-sys", 1401 | "tokio", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "tokio-rustls" 1406 | version = "0.26.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1409 | dependencies = [ 1410 | "rustls", 1411 | "rustls-pki-types", 1412 | "tokio", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "tokio-util" 1417 | version = "0.7.12" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1420 | dependencies = [ 1421 | "bytes", 1422 | "futures-core", 1423 | "futures-sink", 1424 | "pin-project-lite", 1425 | "tokio", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "toml" 1430 | version = "0.8.19" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 1433 | dependencies = [ 1434 | "serde", 1435 | "serde_spanned", 1436 | "toml_datetime", 1437 | "toml_edit", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "toml_datetime" 1442 | version = "0.6.8" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1445 | dependencies = [ 1446 | "serde", 1447 | ] 1448 | 1449 | [[package]] 1450 | name = "toml_edit" 1451 | version = "0.22.22" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1454 | dependencies = [ 1455 | "indexmap", 1456 | "serde", 1457 | "serde_spanned", 1458 | "toml_datetime", 1459 | "winnow", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "tower" 1464 | version = "0.4.13" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1467 | dependencies = [ 1468 | "futures-core", 1469 | "futures-util", 1470 | "pin-project", 1471 | "pin-project-lite", 1472 | "tower-layer", 1473 | "tower-service", 1474 | "tracing", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "tower" 1479 | version = "0.5.1" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" 1482 | dependencies = [ 1483 | "futures-core", 1484 | "futures-util", 1485 | "pin-project-lite", 1486 | "sync_wrapper 0.1.2", 1487 | "tokio", 1488 | "tower-layer", 1489 | "tower-service", 1490 | "tracing", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "tower-http" 1495 | version = "0.6.1" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" 1498 | dependencies = [ 1499 | "bitflags", 1500 | "bytes", 1501 | "http", 1502 | "pin-project-lite", 1503 | "tower-layer", 1504 | "tower-service", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "tower-layer" 1509 | version = "0.3.3" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1512 | 1513 | [[package]] 1514 | name = "tower-service" 1515 | version = "0.3.3" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1518 | 1519 | [[package]] 1520 | name = "tracing" 1521 | version = "0.1.40" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1524 | dependencies = [ 1525 | "log", 1526 | "pin-project-lite", 1527 | "tracing-core", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "tracing-core" 1532 | version = "0.1.32" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1535 | dependencies = [ 1536 | "once_cell", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "try-lock" 1541 | version = "0.2.5" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1544 | 1545 | [[package]] 1546 | name = "unicase" 1547 | version = "2.8.0" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" 1550 | 1551 | [[package]] 1552 | name = "unicode-bidi" 1553 | version = "0.3.17" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 1556 | 1557 | [[package]] 1558 | name = "unicode-ident" 1559 | version = "1.0.13" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1562 | 1563 | [[package]] 1564 | name = "unicode-normalization" 1565 | version = "0.1.24" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1568 | dependencies = [ 1569 | "tinyvec", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "untrusted" 1574 | version = "0.9.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1577 | 1578 | [[package]] 1579 | name = "url" 1580 | version = "2.5.2" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1583 | dependencies = [ 1584 | "form_urlencoded", 1585 | "idna", 1586 | "percent-encoding", 1587 | ] 1588 | 1589 | [[package]] 1590 | name = "utf8parse" 1591 | version = "0.2.2" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1594 | 1595 | [[package]] 1596 | name = "vcpkg" 1597 | version = "0.2.15" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1600 | 1601 | [[package]] 1602 | name = "want" 1603 | version = "0.3.1" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1606 | dependencies = [ 1607 | "try-lock", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "wasi" 1612 | version = "0.11.0+wasi-snapshot-preview1" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1615 | 1616 | [[package]] 1617 | name = "wasm-bindgen" 1618 | version = "0.2.95" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 1621 | dependencies = [ 1622 | "cfg-if", 1623 | "once_cell", 1624 | "wasm-bindgen-macro", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "wasm-bindgen-backend" 1629 | version = "0.2.95" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 1632 | dependencies = [ 1633 | "bumpalo", 1634 | "log", 1635 | "once_cell", 1636 | "proc-macro2", 1637 | "quote", 1638 | "syn", 1639 | "wasm-bindgen-shared", 1640 | ] 1641 | 1642 | [[package]] 1643 | name = "wasm-bindgen-futures" 1644 | version = "0.4.45" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" 1647 | dependencies = [ 1648 | "cfg-if", 1649 | "js-sys", 1650 | "wasm-bindgen", 1651 | "web-sys", 1652 | ] 1653 | 1654 | [[package]] 1655 | name = "wasm-bindgen-macro" 1656 | version = "0.2.95" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 1659 | dependencies = [ 1660 | "quote", 1661 | "wasm-bindgen-macro-support", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "wasm-bindgen-macro-support" 1666 | version = "0.2.95" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 1669 | dependencies = [ 1670 | "proc-macro2", 1671 | "quote", 1672 | "syn", 1673 | "wasm-bindgen-backend", 1674 | "wasm-bindgen-shared", 1675 | ] 1676 | 1677 | [[package]] 1678 | name = "wasm-bindgen-shared" 1679 | version = "0.2.95" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 1682 | 1683 | [[package]] 1684 | name = "web-sys" 1685 | version = "0.3.72" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 1688 | dependencies = [ 1689 | "js-sys", 1690 | "wasm-bindgen", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "windows-core" 1695 | version = "0.52.0" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1698 | dependencies = [ 1699 | "windows-targets", 1700 | ] 1701 | 1702 | [[package]] 1703 | name = "windows-registry" 1704 | version = "0.2.0" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1707 | dependencies = [ 1708 | "windows-result", 1709 | "windows-strings", 1710 | "windows-targets", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "windows-result" 1715 | version = "0.2.0" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1718 | dependencies = [ 1719 | "windows-targets", 1720 | ] 1721 | 1722 | [[package]] 1723 | name = "windows-strings" 1724 | version = "0.1.0" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1727 | dependencies = [ 1728 | "windows-result", 1729 | "windows-targets", 1730 | ] 1731 | 1732 | [[package]] 1733 | name = "windows-sys" 1734 | version = "0.52.0" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1737 | dependencies = [ 1738 | "windows-targets", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "windows-sys" 1743 | version = "0.59.0" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1746 | dependencies = [ 1747 | "windows-targets", 1748 | ] 1749 | 1750 | [[package]] 1751 | name = "windows-targets" 1752 | version = "0.52.6" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1755 | dependencies = [ 1756 | "windows_aarch64_gnullvm", 1757 | "windows_aarch64_msvc", 1758 | "windows_i686_gnu", 1759 | "windows_i686_gnullvm", 1760 | "windows_i686_msvc", 1761 | "windows_x86_64_gnu", 1762 | "windows_x86_64_gnullvm", 1763 | "windows_x86_64_msvc", 1764 | ] 1765 | 1766 | [[package]] 1767 | name = "windows_aarch64_gnullvm" 1768 | version = "0.52.6" 1769 | source = "registry+https://github.com/rust-lang/crates.io-index" 1770 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1771 | 1772 | [[package]] 1773 | name = "windows_aarch64_msvc" 1774 | version = "0.52.6" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1777 | 1778 | [[package]] 1779 | name = "windows_i686_gnu" 1780 | version = "0.52.6" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1783 | 1784 | [[package]] 1785 | name = "windows_i686_gnullvm" 1786 | version = "0.52.6" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1789 | 1790 | [[package]] 1791 | name = "windows_i686_msvc" 1792 | version = "0.52.6" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1795 | 1796 | [[package]] 1797 | name = "windows_x86_64_gnu" 1798 | version = "0.52.6" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1801 | 1802 | [[package]] 1803 | name = "windows_x86_64_gnullvm" 1804 | version = "0.52.6" 1805 | source = "registry+https://github.com/rust-lang/crates.io-index" 1806 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1807 | 1808 | [[package]] 1809 | name = "windows_x86_64_msvc" 1810 | version = "0.52.6" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1813 | 1814 | [[package]] 1815 | name = "winnow" 1816 | version = "0.6.20" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1819 | dependencies = [ 1820 | "memchr", 1821 | ] 1822 | 1823 | [[package]] 1824 | name = "zeroize" 1825 | version = "1.8.1" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1828 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minirps" 3 | version = "0.2.1" 4 | edition = "2021" 5 | authors = ["Marco Di Pillo Tomic "] 6 | license = "MIT" 7 | description = "Mini reverse proxy server written in rust" 8 | readme = "README.md" 9 | homepage = "https://github.com/marcodpt/minirps" 10 | repository = "https://github.com/marcodpt/minirps" 11 | keywords = [ 12 | "reverse-proxy", 13 | "server", 14 | "single-binary", 15 | "axum", 16 | "minijinja" 17 | ] 18 | categories = ["web-programming::http-server"] 19 | 20 | [dependencies] 21 | openssl = { version = "0.10", features = ["vendored"] } 22 | tokio = { version = "1", features = ["full"] } 23 | tower-http = { version = "0", features = ["cors"] } 24 | axum = { version = "0", features = ["matched-path", "original-uri", "query"] } 25 | axum-server = { version = "0", features = ["tls-openssl"] } 26 | clap = {version = "4", features = ["derive"]} 27 | toml = "0" 28 | serde = "1" 29 | serde_derive = "1" 30 | serde_json = "1" 31 | serde_urlencoded = "0" 32 | mime_guess = "2" 33 | minijinja = { version = "2", features = ["loader"] } 34 | reqwest = { version = "0", features = ["blocking"] } 35 | glob-match = "0" 36 | chrono = "0" 37 | 38 | [profile.release] 39 | opt-level = 3 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Marco Tomic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![](favicon.ico) Mini RPS 2 | 3 | [![Version](https://img.shields.io/crates/v/minirps)](https://crates.io/crates/minirps) 4 | [![Downloads](https://img.shields.io/crates/d/minirps)](https://crates.io/crates/minirps) 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/marcodpt/minirps/blob/main/LICENSE) 6 | 7 | Mini [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy) server 8 | written in rust 9 | 10 | ## ❤️ Features 11 | - Static file server. 12 | - HTTPS 13 | - [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 14 | - The optional configuration file can be written in 15 | [JSON](https://www.json.org/json-en.html) or 16 | [TOML](https://toml.io/en/). 17 | - [minijinja](https://github.com/mitsuhiko/minijinja) templates with custom 18 | functions: 19 | - read, write and remove files from the filesystem. 20 | - Send http requests in the template. 21 | - Execute commands in the template. 22 | - [Reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy). 23 | - Modify the response headers and status in the template. 24 | - Parse and format to: 25 | - [JSON](https://www.json.org/json-en.html) 26 | - [TOML](https://toml.io/en/) 27 | - [FormData](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) 28 | - Safe rust and good code organization. 29 | - No panics after startup (every panic is a bug). 30 | - Extensively tested with [hurl](https://github.com/Orange-OpenSource/hurl). 31 | - Good debugging experience with the server displaying requests in the 32 | terminal and error messages in templates for humans. 33 | - Designed following the principles of 34 | [UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy). 35 | 36 | ## 💻 Install 37 | ``` 38 | cargo install minirps 39 | ``` 40 | 41 | Alternatively you can use one of the precompiled binaries available with each 42 | release (currently generic Linux only). 43 | 44 | ## 🎮 Usage 45 | ### Help 46 | ``` 47 | minirps -h 48 | ``` 49 | 50 | ### Simple static file server 51 | ``` 52 | minirps path/to/static/folder 53 | ``` 54 | 55 | ### Serve hidden files 56 | ``` 57 | minirps -a path/to/static/folder 58 | ``` 59 | 60 | ### Ignore markdown files in root folder 61 | ``` 62 | minirps -i "/*.md" path/to/static/folder 63 | ``` 64 | 65 | ### Ignore any markdown files 66 | ``` 67 | minirps -i "/**/*.md" path/to/static/folder 68 | ``` 69 | 70 | ### Running on port 4000 instead of 3000 71 | ``` 72 | minirps -p 4000 path/to/static/folder 73 | ``` 74 | 75 | ### Using https instead of http 76 | ``` 77 | minirps path/to/static/folder -c path/to/cert.pem -k path/to/key.pem 78 | ``` 79 | 80 | ### Allow [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) from all origins 81 | ``` 82 | minirps -o path/to/static/folder 83 | ``` 84 | 85 | ### Start the server with a config file 86 | The supported formats are JSON and TOML. 87 | ``` 88 | minirps -f path/to/config/file 89 | ``` 90 | 91 | ### Send HTML template response instead of API response 92 | Here it is assumed that there are 93 | [minijinja](https://github.com/mitsuhiko/minijinja) templates `users.html` 94 | and `edit_user.html` 95 | 96 | config.toml 97 | ```toml 98 | templates = "path/to/templates/folder" 99 | assets = "path/to/static/folder" 100 | port = 4000 101 | cert = "path/to/cert.pem" 102 | key = "path/to/key.pem" 103 | cors = [] 104 | 105 | [[routes]] 106 | method = "GET" 107 | path = "/api/users" 108 | template = "users.html" 109 | 110 | [[routes]] 111 | method = "GET" 112 | path = "/api/users/:id" 113 | template = "edit_user.html" 114 | 115 | [[routes]] 116 | method = "POST" 117 | path = "/api/users/:id" 118 | template = "edit_user.html" 119 | ``` 120 | 121 | Alternatively you can use a JSON file 122 | 123 | config.json 124 | ```json 125 | { 126 | "templates": "path/to/templates/folder", 127 | "assets": "path/to/static/folder", 128 | "port": 4000, 129 | "cert": "path/to/cert.pem", 130 | "key": "path/to/key.pem", 131 | "cors": [], 132 | "routes": [ 133 | { 134 | "method": "GET", 135 | "path": "/api/users", 136 | "template": "users.html" 137 | }, { 138 | "method": "GET", 139 | "path": "/api/users/:id", 140 | "template": "edit_user.html" 141 | }, { 142 | "method": "POST", 143 | "path": "/api/users/:id", 144 | "template": "edit_user.html" 145 | } 146 | ] 147 | } 148 | ``` 149 | 150 | ## 💯 Examples 151 | 152 | ### Demo 153 | ``` 154 | minirps -f examples/demo/config.toml 155 | ``` 156 | alternatively 157 | ``` 158 | minirps -f examples/demo/config.json 159 | ``` 160 | 161 | Here it was implemented: 162 | - Command Line: use of the command line through a 163 | [minijinja](https://github.com/mitsuhiko/minijinja) custom function. 164 | - Periodic Table: A periodic table web interface was built from a JSON file. 165 | - Star Wars API: Web interface for [swapi](https://swapi.dev/) Star Wars API. 166 | - Note taking app: An example using the file system to save and read data. 167 | - Form Data: Sending and reading examples. 168 | - CORS: A working demo of a CORS request, needs both servers running. 169 | 170 | ### Test 171 | In this example, a static server and some routes are built to test the use of 172 | reverse proxy and templates automatically using 173 | [hurl](https://github.com/Orange-OpenSource/hurl). 174 | 175 | ``` 176 | minirps -f examples/tests/config.toml 177 | ``` 178 | 179 | ``` 180 | hurl --test examples/tests/test.hurl 181 | ``` 182 | 183 | ## 📢 Motivation 184 | The objective of this project is to deliver an http server in a single 185 | self-contained binary. 186 | 187 | Where the basics should be obtained without any configuration file: 188 | - static file server. 189 | - HTTPS 190 | - CORS 191 | 192 | And where other reverse proxy functionalities are obtained with simple 193 | configurations. 194 | 195 | Templates have the ability to send requests, read and write files and execute 196 | commands. 197 | 198 | This way they can interact with resources such as databases without the need 199 | for a complete scripting language such as php, python, ruby... 200 | 201 | A small, highly extensible server, without having to manage operating system 202 | versions, dependencies and packages. 203 | 204 | It simply works! 205 | 206 | ## 📖 Docs 207 | ### config 208 | Command line arguments take priority over config file if both are present. 209 | 210 | Command line argument paths are relative to the current working directory. 211 | 212 | `config` paths are relative to your own directory. 213 | 214 | Currently, any changes to `config`, the server must be restarted for them 215 | to be applied. 216 | 217 | #### port: integer? 218 | Optional integer port number to run the server on, default: 3000 219 | 220 | #### all: bool 221 | Whether to display hidden files. 222 | 223 | In case of confirmation via the command line or `config` file they will be 224 | displayed. 225 | 226 | #### ignore: [string]? 227 | List of files to ignore using glob expressions. 228 | 229 | If the -i option is passed on the command line it will be appended to the list. 230 | 231 | The routes must be considered in relation to the assets folder and not the 232 | working directory. 233 | 234 | For a complete reference of glob expressions and possible bugs check this 235 | [library](https://github.com/devongovett/glob-match). 236 | 237 | #### cors: [string]? 238 | Optional array of strings representing allowed origins for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests. 239 | 240 | An empty array allows all origins. 241 | 242 | If this variable is not defined,[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) will be disabled. 243 | 244 | #### cert: string? 245 | Optional string with the public key file path for the https server. 246 | 247 | Only if the `cert` and `key` are available will the server run over https. 248 | 249 | #### key: string? 250 | Optional string with the private key file path for the https server. 251 | 252 | Only if the `cert` and `key` are available will the server run over https. 253 | 254 | #### assets: string? 255 | Optional string with the static files folder path. 256 | 257 | #### templates: string? 258 | Optional string with the path to the 259 | [minijinja](https://github.com/mitsuhiko/minijinja) templates folder. 260 | 261 | #### data: string? 262 | Optional string with the path where templates can `read`, `write` and `remove` 263 | files. If not passed, these functions will be unavailable to templates. 264 | 265 | #### routes: [{method, path, template}] 266 | Optional array of objects that define routes: 267 | 268 | - `method` string: one of the http methods: 269 | - GET 270 | - POST 271 | - DELETE 272 | - PUT 273 | - PATCH 274 | - HEAD 275 | - OPTIONS 276 | - TRACE 277 | - CONNECT 278 | - `path` string: the path associated with the route, `:var` is 279 | acceptable for setting path variables (ex: /api/user/:id). 280 | - `template` string: the template path associated with this route within the 281 | `templates` folder. 282 | 283 | ### Template variables 284 | 285 | #### method: string 286 | The `method` associated with this `route`. It is useful when the same template 287 | is used in many `routes`. 288 | 289 | #### url: string 290 | It is the junction of the `path` and the `route` `query`. 291 | 292 | ``` 293 | http://localhost:3000/api/users?name=john#me => /api/users?name=john 294 | ``` 295 | 296 | #### route: string 297 | It is the `route` as declared in the `config` file. 298 | 299 | ``` 300 | /api/user/:id 301 | ``` 302 | 303 | #### path: string 304 | The associated `path` passed by the client in the request. 305 | 306 | ``` 307 | http://localhost:3000/api/users?name=john => /api/users 308 | ``` 309 | 310 | #### query: string? 311 | The associated `query` string passed by the client in the request. 312 | 313 | ``` 314 | http://localhost:3000/api/users?name=john => name=john 315 | ``` 316 | 317 | #### params: {name: value} 318 | The associated object of the `path` `params` associated with the client 319 | request on a given `route`. 320 | 321 | - `name` string: The name of the parameter as declared in the `route`. 322 | - `value` string: The value of the parameter passed in the `path`. 323 | 324 | ``` 325 | /api/user/:id => http://localhost:3000/api/user/25 => {"id": "25"} 326 | ``` 327 | 328 | #### vars: {name: value} 329 | The associated object of the `query` params associated with the client request. 330 | 331 | - `name` string: The name of the parameter passed in the `query`. 332 | - `value` string: The value of the parameter passed in the `query`. 333 | 334 | ``` 335 | http://localhost:3000/api/users?name=john => {"name": "john"} 336 | ``` 337 | 338 | #### headers: {name: value} 339 | The associated object of the headers passed by the client in the request. 340 | 341 | Note that all header keys are in **lowercase**. 342 | 343 | - `name` string: The name of the header passed in the request. 344 | - `value` string: The value of the header passed in the request. 345 | 346 | ``` 347 | Content-Type: text/plain => {"content-type": "text/plain"} 348 | ``` 349 | 350 | #### body: binary 351 | The body passed by the client in the request. 352 | 353 | ### Template return state 354 | Variables that, if defined, modify the behavior of the server response. 355 | 356 | It only works if they are **declared outside the blocks** 357 | to be returned in the template's global state. 358 | 359 | #### modify {status, headers: {name: value}} 360 | The response body is always the result of the template, and this variable 361 | allows you to modify the status code and headers. 362 | 363 | - `status` (integer?): The new response status code, if not passed, will use 364 | 200 by default. 365 | - `headers` ({name: value}?): The headers that should be changed in the 366 | response. 367 | 368 | An example of a redirect. 369 | ```jinja 370 | {% set modify = {"status": 303, "headers": {"Location": "/new/location"}} %} 371 | ``` 372 | 373 | #### proxy {url, method, headers: {name, value}, body} 374 | Uses a proxy instead of the template result. 375 | 376 | - `url` (string): The proxy URL, is required. 377 | - `method` (string?): The method used for the proxy request. By default, the 378 | method passed in the original request. 379 | - `headers` ({name: value}?): The headers that should be changed in the 380 | proxy request. By default, do not change any header. 381 | - `body` (binary?): The body of the proxy request. By default, 382 | the original body. 383 | 384 | A simple proxy that retains the request method, headers, body and path and just 385 | directs it to another host. 386 | ```jinja 387 | {% set proxy = {"url": "https://another.host.ip"~url} %} 388 | ``` 389 | 390 | ### Custom functions 391 | 392 | #### command (cmd) -> {code, stdout, stdin} 393 | Executes a command passed in the template. 394 | 395 | This function does not raise errors, in case of failure it returns the 396 | `code` `999999`, and the error message. 397 | 398 | - `cmd` string: The command to be executed by the system. 399 | - `code` integer: The response code, in general zero indicates OK, and a 400 | number greater than zero the error code. 401 | - `stdout` binary: The standard output of the executed command. 402 | - `stderr` binary: The error message returned. 403 | 404 | List files in the current directory on UNIX systems. 405 | ```jinja 406 | {% set res = command("ls -l") %} 407 | {% set output = res.stdout | parse("text") %} 408 | ``` 409 | 410 | #### read (file) -> data 411 | Reads the contents of a file, if it does not exist returns `None`. 412 | 413 | This function does not raise errors, any read error will return `None`. 414 | 415 | It will only be available if the `config` file contains the `data` 416 | property with the folder that contains the files that can be read and modified. 417 | 418 | - `file` string: The path of the file to read. 419 | - `data` binary?: The contents of the file or `None` in case of errors. 420 | 421 | ```jinja 422 | {% set content = read("some/file.json") | parse("json") %} 423 | ``` 424 | 425 | #### read (dir: string) -> [{...info}] 426 | This function also works with a directory, which in this case will return an 427 | array with information about the files contained in it. 428 | 429 | - `dir` string: If the path passed is a directory. 430 | 431 | **info** 432 | - `accessed` string: Last access date (%Y-%m-%d %H:%M:%S). 433 | - `created` string: Creation date (%Y-%m-%d %H:%M:%S). 434 | - `modified` string: Modification date (%Y-%m-%d %H:%M:%S). 435 | - `is_dir` bool: True if it is a directory. 436 | - `is_file` bool: True if it is a file. 437 | - `is_symlink` bool: True if it is a symbolic link. 438 | - `name` string: Entry name. 439 | - `len` u64: Size in bytes. 440 | 441 | ```jinja 442 | {% set content = read("some/dir") %} 443 | {% for entry in content %} 444 | {{entry.name}} 445 | {% endfor %} 446 | ``` 447 | 448 | #### write (file, data) -> error 449 | Writes to a file. If necessary, create folders for the file. Always overwrites 450 | content if it exists. 451 | 452 | If an error occur, the error text will be returned, otherwise `None`. 453 | Therefore, it does not raise errors. 454 | 455 | It will only be available if the `config` file contains the `data` 456 | property with the folder that contains the files that can be read and modified. 457 | 458 | - `file` string: The file path. 459 | - `data` binary: The raw data to be written. 460 | - `error` string?: Error message or `None`. 461 | 462 | ```jinja 463 | {% set data = "Hello world!" %} 464 | {{write("some/file.txt", data | bytes)}} 465 | ``` 466 | 467 | #### remove (entry) -> error 468 | Removes a file or directory recursively. 469 | 470 | If an error occur, the error text will be returned, otherwise `None`. 471 | Therefore, it does not raise errors. 472 | 473 | It will only be available if the `config` file contains the `data` 474 | property with the folder that contains the files that can be read and modified. 475 | 476 | - `entry` string: The path of the file or directory to be removed. 477 | - `error` string?: Error message or `None`. 478 | 479 | ```jinja 480 | {{remove("some/dir")}} 481 | ``` 482 | 483 | ```jinja 484 | {{remove("some/file.txt")}} 485 | ``` 486 | 487 | #### {method} (url, body) -> {status, headers, body} 488 | Sends a synchronous request to an external resource. 489 | 490 | This function does not raise errors, any error in the request will be returned 491 | `status` code `400` with the `body` containing the error message. 492 | 493 | - `url` string: The URL of the request. 494 | - `body` binary: The body of the request. 495 | - `status` integer: The HTTP status code of the response. 496 | - `headers` {`name` string: `value` string}: Response headers. 497 | - `body` binary: Response body. 498 | - `method`: 499 | - `get` (url) -> {status, headers, body} 500 | - `delete` (url) -> {status, headers, body} 501 | - `head` (url) -> {status, headers, body} 502 | - `options` (url) -> {status, headers, body} 503 | - `post` (url, body) -> {status, headers, body} 504 | - `put` (url, body) -> {status, headers, body} 505 | - `patch` (url, body) -> {status, headers, body} 506 | 507 | ```jinja 508 | {% set response = get("https://some/api") %} 509 | {% set data = response.body | parse("json") %} 510 | ``` 511 | 512 | ```jinja 513 | {% set body = "some data" %} 514 | {% set response = post("https://some/api", body | bytes) %} 515 | {% set message = response.body | parse("text") %} 516 | ``` 517 | 518 | #### log (message) -> () 519 | Prints a message from the template on the terminal. 520 | 521 | - `message` string: The content of the message. 522 | 523 | ```jinja 524 | {{ log("hi!") }} 525 | ``` 526 | 527 | ### Custom filters 528 | 529 | #### parse (data, encoding) -> result 530 | Converts the raw data returned from some function to a template variable using 531 | the passed encoding. 532 | 533 | This function raises an `error` if you use an unsupported encoding or if the 534 | decoding fails. 535 | 536 | Returning the request with `status` code `500` in case of error. 537 | 538 | - `data` binary: Raw data returned from some function. 539 | - `encoding` string: The encoding to be used when reading the data. 540 | Supported encodings: 541 | - form: [FormData](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) 542 | - json: [JSON](https://www.json.org/json-en.html) 543 | - toml: [TOML](https://toml.io/en/) 544 | - text: It just transforms the data into text. 545 | - `result`: A value supported by the template with associated data. 546 | 547 | ```jinja 548 | {% set data = read("some/file.txt") | parse("text") %} 549 | ``` 550 | 551 | ```jinja 552 | {% set response = get("https://some/api") %} 553 | {% set data = response.body | parse("json") %} 554 | ``` 555 | 556 | #### format (data, encoding) -> text 557 | Converts a template variable to a formatted string. 558 | 559 | This function raises an `error` if you use an unsupported encoding or if the 560 | encoding fails. 561 | 562 | Returning the request with `status` code `500` in case of error. 563 | 564 | - `data`: Any template variable. 565 | - `encoding` string: The type of encoding to be adopted when formatting the 566 | text. Supported encodings: 567 | - form: [FormData](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) 568 | - json: [JSON](https://www.json.org/json-en.html) 569 | - toml: [TOML](https://toml.io/en/) 570 | - debug: Uses rust pretty print formatter. 571 | - `text` string: The text after encoding. 572 | 573 | ```jinja 574 | {% set data = {"name": "John", "age": 30} %} 575 | {% set text = data | format("form") %} 576 | {{text}} 577 | ``` 578 | 579 | ``` 580 | name=John&age=30 581 | ``` 582 | 583 | #### bytes (data) -> raw 584 | Converts text to binary format. 585 | 586 | - `data` string: Any text. 587 | - `raw` binary: Text converted to binary. 588 | 589 | ```jinja 590 | {% set error = write('hello.txt', 'Hello World!' | bytes) %} 591 | ``` 592 | 593 | ```jinja 594 | {% set response = post('http://myip/some/api', 'Hello World!' | bytes) %} 595 | ``` 596 | 597 | ## 📦 Releases 598 | Currently, only binaries for generic versions of Linux are distributed across 599 | releases. 600 | ``` 601 | sudo apt install pkg-config libssl-dev musl-tools 602 | rustup update 603 | rustup target add x86_64-unknown-linux-musl 604 | cargo update 605 | cargo build --release --target x86_64-unknown-linux-musl 606 | ``` 607 | 608 | ## 🤝 Contributing 609 | It's a very simple project. 610 | Any contribution, any feedback is greatly appreciated. 611 | 612 | ## ⭐ Support 613 | If this project was useful to you, consider giving it a star on github, it's a 614 | way to increase evidence and attract more contributors. 615 | 616 | ## 🙏 Acknowledgment 617 | This work would not be possible if it were not for these related projects: 618 | - [minijinja](https://github.com/mitsuhiko/minijinja) 619 | - [axum](https://github.com/tokio-rs/axum) 620 | - [reqwest](https://github.com/seanmonstar/reqwest) 621 | - [hurl](https://github.com/Orange-OpenSource/hurl) 622 | - [serde](https://github.com/serde-rs/serde) 623 | - [clap](https://github.com/clap-rs/clap) 624 | - [glob-match](https://github.com/devongovett/glob-match) 625 | 626 | A huge thank you to all the people who contributed to these projects. 627 | -------------------------------------------------------------------------------- /examples/bootstrap/assets/favicon.ico: -------------------------------------------------------------------------------- 1 | ../../../favicon.ico -------------------------------------------------------------------------------- /examples/bootstrap/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "data", 3 | "assets": "assets", 4 | "templates": "templates", 5 | "routes": [ 6 | { 7 | "method": "GET", 8 | "path": "/", 9 | "template": "index.html" 10 | }, { 11 | "method": "POST", 12 | "path": "/", 13 | "template": "post.html" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/bootstrap/data/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "title": { 5 | "type": "string", 6 | "title": "Title", 7 | "description": "Site title." 8 | }, 9 | "description": { 10 | "type": "string", 11 | "title": "Description", 12 | "description": "Site description." 13 | }, 14 | "lang": { 15 | "type": "string", 16 | "title": "Language", 17 | "description": "Site language." 18 | }, 19 | "theme": { 20 | "type": "string", 21 | "title": "Theme", 22 | "description": "Bootswatch theme.", 23 | "options": [ 24 | {"value": "", "label": "Default"}, 25 | {"value": "cerulean", "label": "Cerulean"}, 26 | {"value": "cosmo", "label": "Cosmo"}, 27 | {"value": "cyborg", "label": "Cyborg"}, 28 | {"value": "darkly", "label": "Darkly"}, 29 | {"value": "flatly", "label": "Flatly"}, 30 | {"value": "journal", "label": "Journal"}, 31 | {"value": "litera", "label": "Litera"}, 32 | {"value": "lumen", "label": "Lumen"}, 33 | {"value": "lux", "label": "Lux"}, 34 | {"value": "materia", "label": "Materia"}, 35 | {"value": "minty", "label": "Minty"}, 36 | {"value": "morph", "label": "Morph"}, 37 | {"value": "pulse", "label": "Pulse"}, 38 | {"value": "quartz", "label": "Quartz"}, 39 | {"value": "sandstone", "label": "Sandstone"}, 40 | {"value": "simplex", "label": "Simplex"}, 41 | {"value": "sketchy", "label": "Sketchy"}, 42 | {"value": "slate", "label": "Slate"}, 43 | {"value": "solar", "label": "Solar"}, 44 | {"value": "spacelab", "label": "Spacelab"}, 45 | {"value": "superhero", "label": "Superhero"}, 46 | {"value": "united", "label": "United"}, 47 | {"value": "vapor", "label": "Vapor"}, 48 | {"value": "yeti", "label": "Yeti"}, 49 | {"value": "zephyr", "label": "Zephyr"} 50 | ] 51 | }, 52 | "icon": { 53 | "type": "string", 54 | "title": "Icon", 55 | "description": "Site icon." 56 | }, 57 | "height": { 58 | "type": "integer", 59 | "title": "Height (px)", 60 | "description": "Icon height in pixels." 61 | }, 62 | "navbar": { 63 | "type": "string", 64 | "title": "Navbar", 65 | "description": "Navbar theme.", 66 | "options": [ 67 | {"value": "bg-dark navbar-dark", "label": "Dark"}, 68 | {"value": "bg-dark navbar-light", "label": "Dark Inverted"}, 69 | {"value": "bg-light navbar-light", "label": "Light"}, 70 | {"value": "bg-light navbar-dark", "label": "Light Inverted"}, 71 | {"value": "bg-primary navbar-dark", "label": "Primary"}, 72 | {"value": "bg-primary navbar-light", "label": "Primary Inverted"}, 73 | {"value": "bg-secondary navbar-dark", "label": "Secondary"}, 74 | {"value": "bg-secondary navbar-light", "label": "Secondary Inverted"}, 75 | {"value": "bg-success navbar-dark", "label": "Success"}, 76 | {"value": "bg-success navbar-light", "label": "Success Inverted"}, 77 | {"value": "bg-danger navbar-dark", "label": "Danger"}, 78 | {"value": "bg-danger navbar-light", "label": "Danger Inverted"}, 79 | {"value": "bg-warning navbar-dark", "label": "Warning"}, 80 | {"value": "bg-warning navbar-light", "label": "Warning Inverted"}, 81 | {"value": "bg-info navbar-dark", "label": "Info"}, 82 | {"value": "bg-info navbar-light", "label": "Info Inverted"} 83 | ] 84 | }, 85 | "footer": { 86 | "type": "string", 87 | "title": "Footer", 88 | "description": "Footer theme.", 89 | "options": [ 90 | {"value": "text-body-secondary", "label": "Raw"}, 91 | {"value": "text-bg-dark", "label": "Dark"}, 92 | {"value": "text-bg-light", "label": "Light"}, 93 | {"value": "text-bg-primary", "label": "Primary"}, 94 | {"value": "text-bg-secondary", "label": "Secondary"}, 95 | {"value": "text-bg-success", "label": "Success"}, 96 | {"value": "text-bg-danger", "label": "Danger"}, 97 | {"value": "text-bg-warning", "label": "Warning"}, 98 | {"value": "text-bg-info", "label": "Info"} 99 | ] 100 | } 101 | }, 102 | "required": [ 103 | "title", 104 | "description", 105 | "lang", 106 | "theme", 107 | "icon", 108 | "height", 109 | "navbar", 110 | "footer" 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /examples/bootstrap/data/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Site builder", 3 | "footer": "text-bg-light", 4 | "height": "24", 5 | "icon": "favicon.ico", 6 | "lang": "en", 7 | "navbar": "bg-light navbar-light", 8 | "theme": "", 9 | "title": "Mini RPS" 10 | } -------------------------------------------------------------------------------- /examples/bootstrap/templates/index.html: -------------------------------------------------------------------------------- 1 | {% set site = read('site.json') | parse('json') %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{site.title}} 9 | 10 | 11 | 18 | 26 | 31 | 32 | 33 | 51 |
52 |

{{site.description}}

53 |
54 | {% set schema = read('schema.json') | parse('json') %} 55 | {% set P = schema.properties %} 56 | {% for k in schema.required %} 57 |
58 |
59 | 63 |
64 |
65 | {% if P[k].options %} 66 | 77 | {% else %} 78 | 84 | {% endif %} 85 |
86 |
87 | {% endfor %} 88 |
89 | 93 |
94 |
95 |
96 | {% if site.footer -%} 97 | 113 | {%- endif %} 114 | 115 | 116 | -------------------------------------------------------------------------------- /examples/bootstrap/templates/post.html: -------------------------------------------------------------------------------- 1 | {{write("site.json", body | parse("form") | format("json") | bytes)}} 2 | {% set modify = {"status": 303, "headers": {"Location": "/"}} %} 3 | -------------------------------------------------------------------------------- /examples/certs/cert.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkzCCAnugAwIBAgIUXVYkRCrM/ge03DVymDtXCuybp7gwDQYJKoZIhvcNAQEL 3 | BQAwWTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X 5 | DTIxMDczMTE0MjIxMloXDTIyMDczMTE0MjIxMlowWTELMAkGA1UEBhMCVVMxEzAR 6 | BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 7 | IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 8 | MIIBCgKCAQEA02V5ZjmqLB/VQwTarrz/35qsa83L+DbAoa0001+jVmmC+G9Nufi0 9 | daroFWj/Uicv2fZWETU8JoZKUrX4BK9og5cg5rln/CtBRWCUYIwRgY9R/CdBGPn4 10 | kp+XkSJaCw74ZIyLy/Zfux6h8ES1m9YRnBza+s7U+ImRBRf4MRPtXQ3/mqJxAZYq 11 | dOnKnvssRyD2qutgVTAxwMUvJWIivRhRYDj7WOpS4CEEeQxP1iH1/T5P7FdtTGdT 12 | bVBABCA8JhL96uFGPpOYHcM/7R5EIA3yZ5FNg931QzoDITjtXGtQ6y9/l/IYkWm6 13 | J67RWcN0IoTsZhz0WNU4gAeslVtJLofn8QIDAQABo1MwUTAdBgNVHQ4EFgQUzFnK 14 | NfS4LAYuKeWwHbzooER0yZ0wHwYDVR0jBBgwFoAUzFnKNfS4LAYuKeWwHbzooER0 15 | yZ0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAk4O+e9jia59W 16 | ZwetN4GU7OWcYhmOgSizRSs6u7mTfp62LDMt96WKU3THksOnZ44HnqWQxsSfdFVU 17 | XJD12tjvVU8Z4FWzQajcHeemUYiDze8EAh6TnxnUcOrU8IcwiKGxCWRY/908jnWg 18 | +MMscfMCMYTRdeTPqD8fGzAlUCtmyzH6KLE3s4Oo/r5+NR+Uvrwpdvb7xe0MwwO9 19 | Q/zR4N8ep/HwHVEObcaBofE1ssZLksX7ZgCP9wMgXRWpNAtC5EWxMbxYjBfWFH24 20 | fDJlBMiGJWg8HHcxK7wQhFh+fuyNzE+xEWPsI9VL1zDftd9x8/QsOagyEOnY8Vxr 21 | AopvZ09uEQ== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /examples/certs/key.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTZXlmOaosH9VD 3 | BNquvP/fmqxrzcv4NsChrTTTX6NWaYL4b025+LR1qugVaP9SJy/Z9lYRNTwmhkpS 4 | tfgEr2iDlyDmuWf8K0FFYJRgjBGBj1H8J0EY+fiSn5eRIloLDvhkjIvL9l+7HqHw 5 | RLWb1hGcHNr6ztT4iZEFF/gxE+1dDf+aonEBlip06cqe+yxHIPaq62BVMDHAxS8l 6 | YiK9GFFgOPtY6lLgIQR5DE/WIfX9Pk/sV21MZ1NtUEAEIDwmEv3q4UY+k5gdwz/t 7 | HkQgDfJnkU2D3fVDOgMhOO1ca1DrL3+X8hiRabonrtFZw3QihOxmHPRY1TiAB6yV 8 | W0kuh+fxAgMBAAECggEADltu8k1qTFLhJgsXWxTFAAe+PBgfCT2WuaRM2So+qqjB 9 | 12Of0MieYPt5hbK63HaC3nfHgqWt7yPhulpXfOH45C8IcgMXl93MMg0MJr58leMI 10 | +2ojFrIrerHSFm5R1TxwDEwrVm/mMowzDWFtQCc6zPJ8wNn5RuP48HKfTZ3/2fjw 11 | zEjSwPO2wFMfo1EJNTjlI303lFbdFBs67NaX6puh30M7Tn+gznHKyO5a7F57wkIt 12 | fkgnEy/sgMedQlwX7bRpUoD6f0fZzV8Qz4cHFywtYErczZJh3VGitJoO/VCIDdty 13 | RPXOAqVDd7EpP1UUehZlKVWZ0OZMEfRgKbRCel5abQKBgQDwgwrIQ5+BiZv6a0VT 14 | ETeXB+hRbvBinRykNo/RvLc3j1enRh9/zO/ShadZIXgOAiM1Jnr5Gp8KkNGca6K1 15 | myhtad7xYPODYzNXXp6T1OPgZxHZLIYzVUj6ypXeV64Te5ZiDaJ1D49czsq+PqsQ 16 | XRcgBJSNpFtDFiXWpjXWfx8PxwKBgQDhAnLY5Sl2eeQo+ud0MvjwftB/mN2qCzJY 17 | 5AlQpRI4ThWxJgGPuHTR29zVa5iWNYuA5LWrC1y/wx+t5HKUwq+5kxvs+npYpDJD 18 | ZX/w0Glc6s0Jc/mFySkbw9B2LePedL7lRF5OiAyC6D106Sc9V2jlL4IflmOzt4CD 19 | ZTNbLtC6hwKBgHfIzBXxl/9sCcMuqdg1Ovp9dbcZCaATn7ApfHd5BccmHQGyav27 20 | k7XF2xMJGEHhzqcqAxUNrSgV+E9vTBomrHvRvrd5Ec7eGTPqbBA0d0nMC5eeFTh7 21 | wV0miH20LX6Gjt9G6yJiHYSbeV5G1+vOcTYBEft5X/qJjU7aePXbWh0BAoGBAJlV 22 | 5tgCCuhvFloK6fHYzqZtdT6O+PfpW20SMXrgkvMF22h2YvgDFrDwqKRUB47NfHzg 23 | 3yBpxNH1ccA5/w97QO8w3gX3h6qicpJVOAPusu6cIBACFZfjRv1hyszOZwvw+Soa 24 | Fj5kHkqTY1YpkREPYS9V2dIW1Wjic1SXgZDw7VM/AoGAP/cZ3ZHTSCDTFlItqy5C 25 | rIy2AiY0WJsx+K0qcvtosPOOwtnGjWHb1gdaVdfX/IRkSsX4PAOdnsyidNC5/l/m 26 | y8oa+5WEeGFclWFhr4dnTA766o8HrM2UjIgWWYBF2VKdptGnHxFeJWFUmeQC/xeW 27 | w37pCS7ykL+7gp7V0WShYsw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/demo/assets/cors.json: -------------------------------------------------------------------------------- 1 | { 2 | "x": 3, 3 | "y": "cat", 4 | "z": true, 5 | "info": "Some CORS JSON", 6 | "id": 38 7 | } 8 | -------------------------------------------------------------------------------- /examples/demo/assets/favicon.ico: -------------------------------------------------------------------------------- 1 | ../../../favicon.ico -------------------------------------------------------------------------------- /examples/demo/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8080, 3 | "assets": "assets", 4 | "data": "data", 5 | "templates": "templates", 6 | "routes": [ 7 | { 8 | "method": "GET", 9 | "path": "/", 10 | "template": "index.html" 11 | }, { 12 | "method": "GET", 13 | "path": "/debug", 14 | "template": "debug/form.html" 15 | }, { 16 | "method": "GET", 17 | "path": "/debug/:name", 18 | "template": "debug/result.html" 19 | }, { 20 | "method": "POST", 21 | "path": "/debug/:name", 22 | "template": "debug/result.html" 23 | }, { 24 | "method": "GET", 25 | "path": "/cli", 26 | "template": "cli.html" 27 | }, { 28 | "method": "POST", 29 | "path": "/cli", 30 | "template": "cli.html" 31 | }, { 32 | "method": "GET", 33 | "path": "/chemistry", 34 | "template": "chemistry/table.html" 35 | }, { 36 | "method": "GET", 37 | "path": "/chemistry/:element", 38 | "template": "chemistry/element.html" 39 | }, { 40 | "method": "GET", 41 | "path": "/chemistry/data", 42 | "template": "chemistry/data.html" 43 | }, { 44 | "method": "GET", 45 | "path": "/starwars/:resource", 46 | "template": "starwars.html" 47 | }, { 48 | "method": "GET", 49 | "path": "/notes", 50 | "template": "notes/read.html" 51 | }, { 52 | "method": "POST", 53 | "path": "/notes", 54 | "template": "notes/create.html" 55 | }, { 56 | "method": "GET", 57 | "path": "/notes/:title", 58 | "template": "notes/edit.html" 59 | }, { 60 | "method": "POST", 61 | "path": "/notes/:title", 62 | "template": "notes/update.html" 63 | }, { 64 | "method": "GET", 65 | "path": "/cors", 66 | "template": "cors.html" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /examples/demo/config.toml: -------------------------------------------------------------------------------- 1 | # minirps -f config.toml 2 | 3 | port = 8081 4 | assets = "assets" 5 | data = "data" 6 | templates = "templates" 7 | cors = ["http://localhost:8080"] 8 | 9 | [[routes]] 10 | method = "GET" 11 | path = "/" 12 | template = "index.html" 13 | 14 | [[routes]] 15 | method = "GET" 16 | path = "/debug" 17 | template = "debug/form.html" 18 | 19 | [[routes]] 20 | method = "GET" 21 | path = "/debug/:name" 22 | template = "debug/result.html" 23 | 24 | [[routes]] 25 | method = "POST" 26 | path = "/debug/:name" 27 | template = "debug/result.html" 28 | 29 | [[routes]] 30 | method = "GET" 31 | path = "/cli" 32 | template = "cli.html" 33 | 34 | [[routes]] 35 | method = "POST" 36 | path = "/cli" 37 | template = "cli.html" 38 | 39 | [[routes]] 40 | method = "GET" 41 | path = "/chemistry" 42 | template = "chemistry/table.html" 43 | 44 | [[routes]] 45 | method = "GET" 46 | path = "/chemistry/:element" 47 | template = "chemistry/element.html" 48 | 49 | [[routes]] 50 | method = "GET" 51 | path = "/chemistry/data" 52 | template = "chemistry/data.html" 53 | 54 | [[routes]] 55 | method = "GET" 56 | path = "/starwars/:resource" 57 | template = "starwars.html" 58 | 59 | [[routes]] 60 | method = "GET" 61 | path = "/notes" 62 | template = "notes/read.html" 63 | 64 | [[routes]] 65 | method = "POST" 66 | path = "/notes" 67 | template = "notes/create.html" 68 | 69 | [[routes]] 70 | method = "GET" 71 | path = "/notes/:title" 72 | template = "notes/edit.html" 73 | 74 | [[routes]] 75 | method = "POST" 76 | path = "/notes/:title" 77 | template = "notes/update.html" 78 | 79 | [[routes]] 80 | method = "GET" 81 | path = "/cors" 82 | template = "cors.html" 83 | -------------------------------------------------------------------------------- /examples/demo/data/starwars/films.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "title": "Film", 4 | "description": "A Star Wars film", 5 | "type": "object", 6 | "properties": { 7 | "title": { 8 | "title": "Title", 9 | "type": "string", 10 | "description": "The title of this film." 11 | }, 12 | "episode_id": { 13 | "title": "Episode", 14 | "type": "integer", 15 | "description": "The episode number of this film." 16 | }, 17 | "director": { 18 | "title": "Director", 19 | "type": "string", 20 | "description": "The director of this film." 21 | }, 22 | "producer": { 23 | "title": "Producer", 24 | "type": "string", 25 | "description": "The producer(s) of this film." 26 | }, 27 | "release_date": { 28 | "title": "Release", 29 | "type": "string", 30 | "format": "date", 31 | "description": "The release date at original creator country." 32 | }, 33 | "characters": { 34 | "title": "Characters", 35 | "type": "array", 36 | "description": "The people resources featured within this film." 37 | }, 38 | "planets": { 39 | "title": "Planets", 40 | "type": "array", 41 | "description": "The planet resources featured within this film." 42 | }, 43 | "starships": { 44 | "title": "Starships", 45 | "type": "array", 46 | "description": "The starship resources featured within this film." 47 | }, 48 | "vehicles": { 49 | "title": "Vehicles", 50 | "type": "array", 51 | "description": "The vehicle resources featured within this film." 52 | }, 53 | "species": { 54 | "title": "Species", 55 | "type": "array", 56 | "description": "The species resources featured within this film." 57 | } 58 | }, 59 | "required": [ 60 | "title", 61 | "episode_id", 62 | "director", 63 | "producer", 64 | "release_date", 65 | "characters", 66 | "planets", 67 | "starships", 68 | "vehicles", 69 | "species" 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /examples/demo/data/starwars/people.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "title": "People", 4 | "description": "A person within the Star Wars universe", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "title": "Name", 9 | "type": "string", 10 | "description": "The name of this person." 11 | }, 12 | "birth_year": { 13 | "title": "Birth (Y)", 14 | "type": "string", 15 | "description": "The birth year of this person. BBY (Before the Battle of Yavin) or ABY (After the Battle of Yavin)." 16 | }, 17 | "gender": { 18 | "title": "Gender", 19 | "type": "string", 20 | "description": "The gender of this person (if known)." 21 | }, 22 | "height": { 23 | "title": "Height (m)", 24 | "type": "string", 25 | "description": "The height of this person in meters." 26 | }, 27 | "mass": { 28 | "title": "Mass (Kg)", 29 | "type": "string", 30 | "description": "The mass of this person in kilograms." 31 | }, 32 | "eye_color": { 33 | "title": "Eye Color", 34 | "type": "string", 35 | "description": "The eye color of this person." 36 | }, 37 | "hair_color": { 38 | "title": "Hair Color", 39 | "type": "string", 40 | "description": "The hair color of this person." 41 | }, 42 | "skin_color": { 43 | "title": "Skin Color", 44 | "type": "string", 45 | "description": "The skin color of this person." 46 | }, 47 | "films": { 48 | "title": "Films", 49 | "type": "array", 50 | "description": "An array of urls of film resources that this person has been in." 51 | }, 52 | "species": { 53 | "title": "Species", 54 | "type": "array", 55 | "description": "The url of the species resource that this person is." 56 | }, 57 | "starships": { 58 | "title": "Starships", 59 | "type": "array", 60 | "description": "An array of starship resources that this person has piloted" 61 | }, 62 | "vehicles": { 63 | "title": "Vehicles", 64 | "type": "array", 65 | "description": "An array of vehicle resources that this person has piloted" 66 | } 67 | }, 68 | "required": [ 69 | "name", 70 | "birth_year", 71 | "gender", 72 | "height", 73 | "mass", 74 | "eye_color", 75 | "hair_color", 76 | "skin_color", 77 | "films", 78 | "species", 79 | "starships", 80 | "vehicles" 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /examples/demo/data/starwars/planets.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "title": "Planet", 4 | "description": "A planet.", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "title": "Name", 9 | "type": "string", 10 | "description": "The name of this planet." 11 | }, 12 | "climate": { 13 | "title": "Climate", 14 | "type": "string", 15 | "description": "The climate of this planet. Comma-seperated if diverse." 16 | }, 17 | "terrain": { 18 | "title": "Terrain", 19 | "type": "string", 20 | "description": "the terrain of this planet. Comma-seperated if diverse." 21 | }, 22 | "population": { 23 | "title": "Population", 24 | "type": "string", 25 | "description": "The average population of sentient beings inhabiting this planet." 26 | }, 27 | "diameter": { 28 | "title": "Diameter (Km)", 29 | "type": "string", 30 | "description": "The diameter of this planet in kilometers." 31 | }, 32 | "gravity": { 33 | "title": "Gravity (g)", 34 | "type": "string", 35 | "description": "A number denoting the gravity of this planet. Where 1 is normal." 36 | }, 37 | "orbital_period": { 38 | "title": "Orbital Period (days)", 39 | "type": "string", 40 | "description": "The number of standard days it takes for this planet to complete a single orbit of its local star." 41 | }, 42 | "rotation_period": { 43 | "title": "Rotation Period (hours)", 44 | "type": "string", 45 | "description": "The number of standard hours it takes for this planet to complete a single rotation on its axis." 46 | }, 47 | "surface_water": { 48 | "title": "Surface Water (%)", 49 | "type": "string", 50 | "description": "The percentage of the planet surface that is naturally occuring water or bodies of water." 51 | }, 52 | "films": { 53 | "title": "Films", 54 | "type": "array", 55 | "description": "An array of Film URL Resources that this planet has appeared in." 56 | }, 57 | "residents": { 58 | "title": "Residents", 59 | "type": "array", 60 | "description": "An array of People URL Resources that live on this planet." 61 | } 62 | }, 63 | "required": [ 64 | "name", 65 | "climate", 66 | "terrain", 67 | "population", 68 | "diameter", 69 | "gravity", 70 | "orbital_period", 71 | "rotation_period", 72 | "surface_water", 73 | "films", 74 | "residents" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /examples/demo/data/starwars/species.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "title": "Specie", 4 | "description": "A species within the Star Wars universe", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "title": "Name", 9 | "type": "string", 10 | "description": "The name of this species." 11 | }, 12 | "classification": { 13 | "title": "Classification", 14 | "type": "string", 15 | "description": "The classification of this species." 16 | }, 17 | "designation": { 18 | "title": "Designation", 19 | "type": "string", 20 | "description": "The designation of this species." 21 | }, 22 | "average_height": { 23 | "title": "Height (AVG cm)", 24 | "type": "string", 25 | "description": "The average height of this person in centimeters." 26 | }, 27 | "average_lifespan": { 28 | "title": "Lifespan (AVG years)", 29 | "type": "string", 30 | "description": "The average lifespan of this species in years." 31 | }, 32 | "language": { 33 | "title": "Language", 34 | "type": "string", 35 | "description": "The language commonly spoken by this species." 36 | }, 37 | "eye_colors": { 38 | "title": "Eye Colors", 39 | "type": "string", 40 | "description": "A comma-seperated string of common eye colors for this species, none if this species does not typically have eyes." 41 | }, 42 | "hair_colors": { 43 | "title": "Hair Colors", 44 | "type": "string", 45 | "description": "A comma-seperated string of common hair colors for this species, none if this species does not typically have hair." 46 | }, 47 | "skin_colors": { 48 | "title": "Skin Colors", 49 | "type": "string", 50 | "description": "A comma-seperated string of common skin colors for this species, none if this species does not typically have skin." 51 | }, 52 | "films": { 53 | "title": "Films", 54 | "type": "array", 55 | "description": " An array of Film URL Resources that this species has appeared in." 56 | }, 57 | "people": { 58 | "title": "People", 59 | "type": "array", 60 | "description": "An array of People URL Resources that are a part of this species." 61 | } 62 | }, 63 | "required": [ 64 | "name", 65 | "classification", 66 | "designation", 67 | "average_height", 68 | "average_lifespan", 69 | "language", 70 | "eye_colors", 71 | "hair_colors", 72 | "skin_colors", 73 | "films", 74 | "people" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /examples/demo/data/starwars/starships.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "title": "Starship", 4 | "description": "A Starship", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "title": "Name", 9 | "type": "string", 10 | "description": "The name of this starship. The common name, such as Death Star." 11 | }, 12 | "model": { 13 | "title": "Model", 14 | "type": "string", 15 | "description": "The model or official name of this starship. Such as T-65 X-wing or DS-1 Orbital Battle Station." 16 | }, 17 | "manufacturer": { 18 | "title": "Manufacturer", 19 | "type": "string", 20 | "description": "The manufacturer of this starship. Comma seperated if more than one." 21 | }, 22 | "starship_class": { 23 | "title": "Class", 24 | "type": "string", 25 | "description": "The class of this starship, such as Starfighter or Deep Space Mobile Battlestation." 26 | }, 27 | "MGLT": { 28 | "title": "MGLT", 29 | "type": "string", 30 | "description": "The Maximum number of Megalights this starship can travel in a standard hour. A Megalight is a standard unit of distance and has never been defined before within the Star Wars universe. This figure is only really useful for measuring the difference in speed of starships. We can assume it is similar to AU, the distance between our Sun (Sol) and Earth." 31 | }, 32 | "passengers": { 33 | "title": "Passengers", 34 | "type": "string", 35 | "description": "The number of non-essential people this starship can transport." 36 | }, 37 | "crew":{ 38 | "title": "Crew", 39 | "type": "string", 40 | "description": "The number of personnel needed to run or pilot this starship." 41 | }, 42 | "length": { 43 | "title": "Length (m)", 44 | "type": "string", 45 | "description": "The length of this starship in meters." 46 | }, 47 | "cost_in_credits": { 48 | "title": "Cost (credits)", 49 | "type": "string", 50 | "description": "The cost of this starship new, in galactic credits." 51 | }, 52 | "cargo_capacity": { 53 | "title": "Capacity (kg)", 54 | "type": "string", 55 | "description": "The maximum number of kilograms that this starship can transport." 56 | }, 57 | "consumables": { 58 | "title": "Consumables", 59 | "type": "string", 60 | "description": "The maximum length of time that this starship can provide consumables for its entire crew without having to resupply." 61 | }, 62 | "hyperdrive_rating" : { 63 | "title": "HyperDrive Rating", 64 | "type": "string", 65 | "description": "The class of this starships hyperdrive." 66 | }, 67 | "max_atmosphering_speed": { 68 | "title": "Max ATM speed", 69 | "type": "string", 70 | "description": "The maximum speed of this starship in atmosphere. n/a if this starship is incapable of atmosphering flight." 71 | }, 72 | "films": { 73 | "title": "Films", 74 | "type": "array", 75 | "description": "An array of Film URL Resources that this starship has appeared in." 76 | }, 77 | "pilots": { 78 | "title": "Pilots", 79 | "type": "array", 80 | "description": "An array of People URL Resources that this starship has been piloted by." 81 | } 82 | }, 83 | "required": [ 84 | "name", 85 | "model", 86 | "manufacturer", 87 | "starship_class", 88 | "MGLT", 89 | "passengers", 90 | "crew", 91 | "length", 92 | "cost_in_credits", 93 | "cargo_capacity", 94 | "consumables", 95 | "hyperdrive_rating", 96 | "max_atmosphering_speed", 97 | "films", 98 | "pilots" 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /examples/demo/data/starwars/vehicles.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "title": "Vehicle", 4 | "description": "A vehicle.", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "title": "Name", 9 | "type": "string", 10 | "description": "The name of this vehicle. The common name, such as Sand Crawler." 11 | }, 12 | "model": { 13 | "title": "Model", 14 | "type": "string", 15 | "description": "The model or official name of this vehicle. Such as All Terrain Attack Transport." 16 | }, 17 | "manufacturer": { 18 | "title": "Manufacturer", 19 | "type": "string", 20 | "description": "The manufacturer of this vehicle. Comma seperated if more than one." 21 | }, 22 | "vehicle_class": { 23 | "title": "Class", 24 | "type": "string", 25 | "description": "The class of this vehicle, such as Wheeled." 26 | }, 27 | "passengers": { 28 | "title": "Passengers", 29 | "type": "string", 30 | "description": "The number of non-essential people this vehicle can transport." 31 | }, 32 | "crew": { 33 | "title": "Crew", 34 | "type": "string", 35 | "description": "The number of personnel needed to run or pilot this vehicle." 36 | }, 37 | "length": { 38 | "title": "Length (m)", 39 | "type": "string", 40 | "description": "The length of this vehicle in meters." 41 | }, 42 | "cost_in_credits": { 43 | "title": "Cost (credits)", 44 | "type": "string", 45 | "description": "The cost of this vehicle new, in galactic credits." 46 | }, 47 | "cargo_capacity": { 48 | "title": "Capacity (Kg)", 49 | "type": "string", 50 | "description": "The maximum number of kilograms that this vehicle can transport." 51 | }, 52 | "consumables": { 53 | "title": "Consumables", 54 | "type": "string", 55 | "description": "The maximum length of time that this vehicle can provide consumables for its entire crew without having to resupply." 56 | }, 57 | "max_atmosphering_speed": { 58 | "title": "Max ATM speed", 59 | "type": "string", 60 | "description": "The maximum speed of this vehicle in atmosphere." 61 | }, 62 | "films": { 63 | "title": "Films", 64 | "type": "array", 65 | "description": "An array of Film URL Resources that this vehicle has appeared in." 66 | }, 67 | "pilots": { 68 | "title": "Pilots", 69 | "type": "array", 70 | "description": "An array of People URL Resources that this vehicle has been piloted by." 71 | } 72 | }, 73 | "required": [ 74 | "name", 75 | "model", 76 | "manufacturer", 77 | "vehicle_class", 78 | "passengers", 79 | "crew", 80 | "length", 81 | "cost_in_credits", 82 | "cargo_capacity", 83 | "consumables", 84 | "max_atmosphering_speed", 85 | "films", 86 | "pilots" 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /examples/demo/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock title %}MiniRPS 9 | 10 | 11 | 15 |
16 | {% block main %}{% endblock main %} 17 |
18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/demo/templates/chemistry/data.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}(JSON) Periodic Table - {% endblock title %} 3 | {% block main %} 4 |

Periodic Table (JSON)

5 | {% if vars.enc %} 6 | {% set data = read("/chemistry.json") | parse("json") | format(vars.enc) %} 7 | {% else %} 8 | {% set data = read("/chemistry.json") | parse("text") %} 9 | {% endif %} 10 |
{{data}}
11 | {% endblock main %} 12 | -------------------------------------------------------------------------------- /examples/demo/templates/chemistry/element.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}({{params.element}}) Periodic Table - {% endblock title %} 3 | {% block main %} 4 | {% set data = read("/chemistry.json") | parse("json") %} 5 | {% for el in data.elements %} 6 | {% if el.Symbol == params.element %} 7 |

{{el.EnglishName}} ({{el.Symbol}})

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
#{{el.AtomicNumber}}
Symbol{{el.Symbol}}
Name{{el.EnglishName}}
Configuration{{el.Configuration}}
Discovery{{el.Discovery}}
Discovered By{{el.DiscoveredBy}}
34 | {% endif %} 35 | {% endfor %} 36 | {% endblock main %} 37 | -------------------------------------------------------------------------------- /examples/demo/templates/chemistry/table.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Periodic Table - {% endblock title %} 3 | {% block main %} 4 |

Periodic Table

5 | {% set data = read("/chemistry.json") | parse("json") %} 6 | {% set table = data.elements %} 7 | 8 | 9 | {% for period in range(0, table | map(attribute="Period")| max) %} 10 | 11 | {% for group in range(0, table | map(attribute="Group")| max) %} 12 | 19 | {% endfor %} 20 | 21 | {% endfor %} 22 | 23 | 24 | 25 | 28 | 31 | 34 | 35 | 36 |
13 | {% for el in table %} 14 | {% if el.Group == group + 1 and el.Period == period + 1 %} 15 | {{el.Symbol}} 16 | {% endif %} 17 | {% endfor %} 18 |
26 | Raw Data 27 | 29 | JSON 30 | 32 | TOML 33 |
37 | {% endblock main %} 38 | -------------------------------------------------------------------------------- /examples/demo/templates/cli.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}CLI - {% endblock title %} 3 | {% block main %} 4 |

Command Line

5 | {% set data = body | parse("form") %} 6 |
7 | 13 |
14 | {% if data and data.command %} 15 | {% set res = command(data.command) %} 16 |

Status Code: {{res.code}}

17 |

Output

18 |
{{res.stdout | parse("text")}}
19 |

Error

20 |
{{res.stderr | parse("text")}}
21 | {% endif %} 22 | {% endblock main %} 23 | -------------------------------------------------------------------------------- /examples/demo/templates/cors.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}CORS - {% endblock title %} 3 | {% block main %} 4 |

CORS test => fetch http://localhost:8081/cors.json

5 | Raw Data 6 |
7 | 8 | minirps -f examples/demo/config.toml 9 | 10 |

11 | 20 | {% endblock main %} 21 | -------------------------------------------------------------------------------- /examples/demo/templates/debug/form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Form {{vars.method | default("GET")}} - {% endblock title %} 3 | {% block main %} 4 |

Form {{vars.method | default("GET")}}

5 |
9 | 10 | 16 | 17 | 23 | 24 | 30 | 31 | 36 | 37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 |
55 | {% endblock main %} 56 | -------------------------------------------------------------------------------- /examples/demo/templates/debug/result.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Debug {{params.name}} - {% endblock title %} 3 | {% block main %} 4 |

Debug {{params.name}}

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 66 | 67 | 78 | 79 |
Method{{method}}
Headers 13 | 14 | {% for name in headers | list %} 15 | 16 | 17 | 18 | 19 | {% endfor %} 20 |
{{name}}{{headers[name]}}
21 |
URL{{url}}
Route{{route}}
Path{{path}}
Params 38 | 39 | {% for name in params | list %} 40 | 41 | 42 | 43 | 44 | {% endfor %} 45 |
{{name}}{{params[name]}}
46 |
Query{{query}}
Vars 55 | 56 | {% for name in vars | list %} 57 | 58 | 59 | 60 | 61 | {% endfor %} 62 |
{{name}}{{vars[name]}}
63 |
Body 68 | 69 | {% set data = body | parse("form") %} 70 | {% for name in data | list %} 71 | 72 | 73 | 74 | 75 | {% endfor %} 76 |
{{name}}{{data[name]}}
77 |
80 | {% endblock main %} 81 | -------------------------------------------------------------------------------- /examples/demo/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Home - {% endblock title %} 3 | {% block main %} 4 |

Home

5 | 47 | {% endblock main %} 48 | -------------------------------------------------------------------------------- /examples/demo/templates/notes/create.html: -------------------------------------------------------------------------------- 1 | {% set data = body | parse("form") %} 2 | {% set file = read('notes/'~data.title) %} 3 | {% if not file is none %} 4 | {% set error = 'Note already exists!' %} 5 | {% endif %} 6 | {% if not error %} 7 | {% set error = write('notes/'~data.title, data.content | bytes) %} 8 | {% endif %} 9 | {% if not error %} 10 | {% set modify = { 11 | "status": 303, 12 | "headers": { 13 | "Location": "/notes" 14 | } 15 | } %} 16 | {% else %} 17 | {% extends "base.html" %} 18 | {% block title %}Notes Create - {% endblock title %} 19 | {% block main %} 20 |

Error creating note!

21 |
{{error}}
22 | {% endblock main %} 23 | {% endif %} 24 | -------------------------------------------------------------------------------- /examples/demo/templates/notes/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Note: {{params.title}} - {% endblock title %} 3 | {% block main %} 4 |

Edit Note

5 | {% set file = read('notes/'~params.title) %} 6 | {% set content = file | parse("text") %} 7 |

{{params.title}}

8 |
9 | 13 | 14 |
15 | {% endblock main %} 16 | -------------------------------------------------------------------------------- /examples/demo/templates/notes/read.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Notes - {% endblock title %} 3 | {% block main %} 4 |

Notes taking app

5 | {% set dir = read('notes') | sort(attribute="created") %} 6 | {% for note in dir %} 7 |

{{note.name}}

8 | {{note.created}} 9 | Edit 10 |

{{read('notes/'~note.name) | parse("text")}}

11 | {% endfor %} 12 |
13 | 14 | 22 | 23 | 28 | 29 | 30 |
31 | {% endblock main %} 32 | -------------------------------------------------------------------------------- /examples/demo/templates/notes/update.html: -------------------------------------------------------------------------------- 1 | {% set data = body | parse("form") %} 2 | {% if data.content %} 3 | {{write('notes/'~params.title, data.content | bytes)}} 4 | {% else %} 5 | {{remove('notes/'~params.title)}} 6 | {% endif %} 7 | {% set modify = { 8 | "status": 303, 9 | "headers": { 10 | "Location": "/notes" 11 | } 12 | } %} 13 | -------------------------------------------------------------------------------- /examples/demo/templates/starwars.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{params.resource}} - {% endblock title %} 3 | {% block main %} 4 | {% set dir = read('starwars') %} 5 | {% set schema = read('/starwars/'~params.resource~'.json') | parse("json") %} 6 | {% set P = schema.properties %} 7 | {% set data = get('https://swapi.dev/api/'~params.resource).body | parse("json") %} 8 |

Star Wars {{schema.title}}

9 | 18 | {% for row in data.results %} 19 | 20 | {% for key in schema.required %} 21 | 22 | 25 | 32 | 33 | {% endfor %} 34 |
23 | {{P[key].title}} 24 | 26 | {% if P[key].type != "array" %} 27 | {{row[key]}} 28 | {% else %} 29 | {{row[key] | length}} 30 | {% endif %} 31 |
35 |
36 |
37 |
38 | {% endfor %} 39 | {% endblock main %} 40 | -------------------------------------------------------------------------------- /examples/tests/assets/.hidden.txt: -------------------------------------------------------------------------------- 1 | A hidden file 2 | -------------------------------------------------------------------------------- /examples/tests/assets/.secret/hello.txt: -------------------------------------------------------------------------------- 1 | Hello from secret dir! 2 | -------------------------------------------------------------------------------- /examples/tests/assets/favicon.ico: -------------------------------------------------------------------------------- 1 | ../../../favicon.ico -------------------------------------------------------------------------------- /examples/tests/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | MiniRPS Demo 9 | 10 | 11 |
12 | 18 |

MiniRPS Demo

19 |

20 | Sample static server, with all data available in the nav. 21 |

22 |

Hidden files tests

23 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/tests/assets/tests/.hi.txt: -------------------------------------------------------------------------------- 1 | Hi! 2 | -------------------------------------------------------------------------------- /examples/tests/assets/tests/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "x": 3, 3 | "y": "cat", 4 | "z": true, 5 | "method": "POST", 6 | "id": 38, 7 | "type": "text/plain" 8 | } 9 | -------------------------------------------------------------------------------- /examples/tests/assets/tests/deep/msg.txt: -------------------------------------------------------------------------------- 1 | Deep message! 2 | -------------------------------------------------------------------------------- /examples/tests/config.toml: -------------------------------------------------------------------------------- 1 | port = 4000 2 | assets = "assets" 3 | templates = "templates" 4 | 5 | ## blank route 6 | [[routes]] 7 | method = "GET" 8 | path = "/blank" 9 | template = "blank.html" 10 | 11 | ## blank route with params 12 | [[routes]] 13 | method = "PUT" 14 | path = "/blank/:id" 15 | template = "blank.html" 16 | 17 | ## dynamic route 18 | [[routes]] 19 | method = "GET" 20 | path = "/set/:method/:id" 21 | template = "httpbin.html" 22 | 23 | ## extract data showcase 24 | [[routes]] 25 | method = "POST" 26 | path = "/vars/:id" 27 | template = "tests/data.txt" 28 | -------------------------------------------------------------------------------- /examples/tests/templates/blank.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcodpt/minirps/cefc05e6894adf491b6607c18137eb1cf0a1ad52/examples/tests/templates/blank.html -------------------------------------------------------------------------------- /examples/tests/templates/httpbin.html: -------------------------------------------------------------------------------- 1 | {% set url = "https://httpbin.org/anything/"~params.id %} 2 | {% if query %} 3 | {% set url = url~"?"~query %} 4 | {% endif %} 5 | {% set 6 | proxy = { 7 | "method": params.method, 8 | "url": url, 9 | "headers": { 10 | "X-Set": "some-data", 11 | "X-Var": vars.x | default(""), 12 | "Host": "httpbin.org" 13 | } 14 | } 15 | %} 16 | -------------------------------------------------------------------------------- /examples/tests/templates/tests/data.txt: -------------------------------------------------------------------------------- 1 | {%- set data = get('http://localhost:4000/tests/data.json') -%} 2 | {%- set json = data.body | parse("json") -%} 3 | {%- set body = body | parse("text") -%} 4 | {%- set modify = { 5 | "headers": { 6 | "Content-Type": headers['x-set-content'] 7 | } 8 | } -%} 9 | path: {{path}} 10 | query: {{query}} 11 | headers['content-type']: {{headers['content-type']}} 12 | params.id: {{params.id}} 13 | vars.x: {{vars.x}} 14 | body: {{body}} 15 | 16 | status: {{data.status}} 17 | crazy['content-type']: {{data.headers['content-type']}} 18 | json.id: {{json.id}} 19 | -------------------------------------------------------------------------------- /examples/tests/test.hurl: -------------------------------------------------------------------------------- 1 | # Checking assets folder 2 | 3 | GET http://localhost:4000/ 4 | HTTP/1.1 200 5 | Content-Type: text/html 6 | [Captures] 7 | home: body 8 | [Asserts] 9 | body contains "MiniRPS Demo" 10 | body == {{home}} 11 | 12 | GET http://localhost:4000/index.html 13 | HTTP/1.1 200 14 | Content-Type: text/html 15 | [Asserts] 16 | body == {{home}} 17 | 18 | GET http://localhost:4000/favicon.ico 19 | HTTP/1.1 200 20 | Content-Type: image/x-icon 21 | Content-Length: 15086 22 | [Asserts] 23 | bytes count == 15086 24 | 25 | GET http://localhost:4000/tests/data.json 26 | HTTP/1.1 200 27 | Content-Type: application/json 28 | ``` 29 | { 30 | "x": 3, 31 | "y": "cat", 32 | "z": true, 33 | "method": "POST", 34 | "id": 38, 35 | "type": "text/plain" 36 | } 37 | ``` 38 | 39 | GET http://localhost:4000/tests/deep/msg.txt 40 | HTTP/1.1 200 41 | Content-Type: text/plain 42 | ``` 43 | Deep message! 44 | ``` 45 | 46 | GET http://localhost:4000/.hidden.txt 47 | HTTP/1.1 404 48 | [Asserts] 49 | body == "" 50 | 51 | GET http://localhost:4000/.secret/hello.txt 52 | HTTP/1.1 404 53 | [Asserts] 54 | body == "" 55 | 56 | GET http://localhost:4000/tests/.hi.txt 57 | HTTP/1.1 404 58 | [Asserts] 59 | body == "" 60 | 61 | # Checking blank route 62 | 63 | GET http://localhost:4000/blank 64 | ``` 65 | Some body data 66 | ``` 67 | HTTP/1.1 200 68 | [Asserts] 69 | body == "" 70 | 71 | POST http://localhost:4000/blank 72 | ``` 73 | Some body data 74 | ``` 75 | HTTP/1.1 405 76 | [Asserts] 77 | body == "" 78 | 79 | PUT http://localhost:4000/blank 80 | ``` 81 | Some body data 82 | ``` 83 | HTTP/1.1 405 84 | [Asserts] 85 | body == "" 86 | 87 | DELETE http://localhost:4000/blank 88 | ``` 89 | Some body data 90 | ``` 91 | HTTP/1.1 405 92 | [Asserts] 93 | body == "" 94 | 95 | GET http://localhost:4000/blank/34 96 | ``` 97 | Some body data 98 | ``` 99 | HTTP/1.1 405 100 | [Asserts] 101 | body == "" 102 | 103 | POST http://localhost:4000/blank/34 104 | ``` 105 | Some body data 106 | ``` 107 | HTTP/1.1 405 108 | [Asserts] 109 | body == "" 110 | 111 | PUT http://localhost:4000/blank/34 112 | ``` 113 | Some body data 114 | ``` 115 | HTTP/1.1 200 116 | [Asserts] 117 | body == "" 118 | 119 | DELETE http://localhost:4000/blank/34 120 | ``` 121 | Some body data 122 | ``` 123 | HTTP/1.1 405 124 | [Asserts] 125 | body == "" 126 | 127 | # Extracting data showcase 128 | POST http://localhost:4000/vars/345?x=12 129 | Content-Type: text/html 130 | X-Set-Content: text/plain 131 | `

Data

` 132 | HTTP/1.1 200 133 | Content-Type: text/plain 134 | [Asserts] 135 | body contains "path: /vars/345" 136 | body contains "query: x=12" 137 | body contains "headers['content-type']: text/html" 138 | body contains "params.id: 345" 139 | body contains "vars.x: 12" 140 | body contains "body:

Data

" 141 | body contains "status: 200" 142 | body contains "crazy['content-type']: application/json" 143 | body contains "json.id: 38" 144 | 145 | # Extracting data showcase 146 | POST http://localhost:4000/vars/test?x=60 147 | Content-Type: text/html 148 | X-Set-Content: text/plain 149 | `

Data

` 150 | HTTP/1.1 200 151 | Content-Type: text/plain 152 | [Asserts] 153 | body contains "path: /vars/test" 154 | body contains "query: x=60" 155 | body contains "headers['content-type']: text/html" 156 | body contains "params.id: test" 157 | body contains "vars.x: 60" 158 | body contains "body:

Data

" 159 | body contains "status: 200" 160 | body contains "crazy['content-type']: application/json" 161 | body contains "json.id: 38" 162 | 163 | # Dynamic route 164 | GET http://localhost:4000/set/POST/pet?x=dog 165 | `Some body data` 166 | HTTP/1.1 200 167 | Content-Type: application/json 168 | [Asserts] 169 | jsonpath "$.method" == "POST" 170 | jsonpath "$.url" == "https://httpbin.org/anything/pet?x=dog" 171 | jsonpath "$.args.x" == "dog" 172 | jsonpath "$.data" == "Some body data" 173 | jsonpath "$.headers.X-Set" == "some-data" 174 | jsonpath "$.headers.X-Var" == "dog" 175 | 176 | GET http://localhost:4000/set/PUT/70 177 | `Some body data` 178 | HTTP/1.1 200 179 | Content-Type: application/json 180 | [Asserts] 181 | jsonpath "$.method" == "PUT" 182 | jsonpath "$.url" == "https://httpbin.org/anything/70" 183 | jsonpath "$.args" isEmpty 184 | jsonpath "$.data" == "Some body data" 185 | jsonpath "$.headers.X-Set" == "some-data" 186 | jsonpath "$.headers.X-Var" == "" 187 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcodpt/minirps/cefc05e6894adf491b6607c18137eb1cf0a1ad52/favicon.ico -------------------------------------------------------------------------------- /src/app/context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use serde_derive::Serialize; 3 | use axum::http::{Uri, HeaderMap, Method}; 4 | use axum::body::Bytes; 5 | use axum::extract::MatchedPath; 6 | 7 | #[derive(Serialize)] 8 | pub struct Context { 9 | pub method: String, 10 | pub url: String, 11 | route: String, 12 | path: String, 13 | query: String, 14 | params: HashMap, 15 | vars: HashMap, 16 | pub headers: HashMap, 17 | pub body: Vec 18 | } 19 | 20 | impl Context { 21 | pub fn new ( 22 | route: MatchedPath, 23 | params: HashMap, 24 | vars: HashMap, 25 | method: Method, 26 | url: Uri, 27 | raw_headers: HeaderMap, 28 | body: Bytes 29 | ) -> Context { 30 | let mut headers: HashMap = HashMap::new(); 31 | for (key, value) in raw_headers.iter() { 32 | if let Ok(value) = value.to_str() { 33 | headers.insert(key.to_string(), value.to_string()); 34 | } 35 | } 36 | Context { 37 | method: method.as_str().to_string(), 38 | url: url.to_string(), 39 | route: route.as_str().to_string(), 40 | path: url.path().to_string(), 41 | query: url.query().unwrap_or("").to_string(), 42 | params, 43 | vars, 44 | headers, 45 | body: body.to_vec() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/mod.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | mod proxy; 3 | mod modify; 4 | 5 | use std::error::Error; 6 | use std::collections::HashMap; 7 | use minijinja::{Environment}; 8 | use axum::{ 9 | extract::{Path, Query, State, OriginalUri, MatchedPath}, 10 | body::{Bytes, Body}, 11 | http::{Method, StatusCode, HeaderMap, HeaderName, HeaderValue, header}, 12 | }; 13 | use context::Context; 14 | use proxy::Proxy; 15 | use modify::Modify; 16 | use crate::debug::debug; 17 | use mime_guess; 18 | 19 | type Env = Environment<'static>; 20 | #[derive(Clone)] 21 | pub struct AppState { 22 | env: Env, 23 | template: String, 24 | mime: Option 25 | } 26 | 27 | impl AppState { 28 | pub fn new (env: &Env, template: &str) -> AppState { 29 | AppState { 30 | env: env.clone(), 31 | template: template.to_string(), 32 | mime: match mime_guess::from_path(template).first_raw() { 33 | Some(mime) => match HeaderValue::from_str(mime) { 34 | Ok(mime) => Some(mime), 35 | Err(_) => None 36 | }, 37 | None => None 38 | } 39 | } 40 | } 41 | 42 | pub async fn run (&self, 43 | ctx: &Context 44 | ) -> Result<(StatusCode, HeaderMap, Body), Box> { 45 | let tpl = self.env.get_template(&self.template)?; 46 | let (tpl, state) = match tpl.render_and_return_state(ctx) { 47 | Ok(result) => result, 48 | Err(err) => { 49 | let mut info = format!("Fail to render template!\n{:#}", err); 50 | let mut err = &err as &dyn Error; 51 | while let Some(next_err) = err.source() { 52 | info = format!("{}\n\n{:#}", info, next_err); 53 | err = next_err; 54 | } 55 | return Err(info.into()); 56 | } 57 | }; 58 | 59 | let mut status = StatusCode::OK; 60 | let mut headers = HeaderMap::new(); 61 | let mut body: Body = tpl.into(); 62 | 63 | if let Some(proxy) = state.lookup("proxy") { 64 | (status, headers, body) = Proxy::new( 65 | &ctx.method, 66 | &ctx.headers, 67 | &ctx.body, 68 | &proxy 69 | ).await?; 70 | headers.remove(header::TRANSFER_ENCODING); 71 | } else if let Some(mime) = &self.mime { 72 | headers.insert(header::CONTENT_TYPE, mime.clone()); 73 | } 74 | 75 | if let Some(modify) = state.lookup("modify") { 76 | if let Ok(modify) = Modify::new(&modify) { 77 | if let Some(modify_status) = modify.status { 78 | if let Ok( 79 | modify_status 80 | ) = StatusCode::from_u16(modify_status) { 81 | status = modify_status; 82 | } 83 | } 84 | 85 | if let Some(modify_headers) = modify.headers { 86 | for (name, value) in modify_headers.iter() { 87 | if let (Ok(name), Ok(value)) = ( 88 | HeaderName::from_bytes(name.as_bytes()), 89 | HeaderValue::from_str(value) 90 | ) { 91 | headers.insert(name, value); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | if let Ok(server) = HeaderValue::from_str("minirps") { 99 | headers.insert(header::SERVER, server); 100 | } 101 | 102 | Ok((status, headers, body)) 103 | } 104 | } 105 | 106 | pub async fn handler ( 107 | state: State, 108 | OriginalUri(url): OriginalUri, 109 | Path(params): Path>, 110 | Query(vars): Query>, 111 | route: MatchedPath, 112 | headers: HeaderMap, 113 | method: Method, 114 | body: Bytes, 115 | ) -> (StatusCode, HeaderMap, Body) { 116 | let ctx = Context::new(route, params, vars, method, url, headers, body); 117 | debug(&ctx.method, &ctx.url, None, ""); 118 | match state.run(&ctx).await { 119 | Ok(response) => { 120 | debug(&ctx.method, &ctx.url, Some(response.0.as_u16()), ""); 121 | response 122 | }, 123 | Err(err) => { 124 | let error = err.to_string(); 125 | let status = StatusCode::INTERNAL_SERVER_ERROR; 126 | debug(&ctx.method, &ctx.url, Some(status.as_u16()), &error); 127 | (status, HeaderMap::new(), error.into()) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/app/modify.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::collections::HashMap; 3 | use minijinja::Value; 4 | use serde_derive::Deserialize; 5 | use serde::Deserialize; 6 | 7 | #[derive(Deserialize)] 8 | pub struct Modify { 9 | pub status: Option, 10 | pub headers: Option> 11 | } 12 | 13 | impl Modify { 14 | pub fn new (modify: &Value) -> Result> { 15 | Ok(Modify::deserialize(modify)?) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/proxy.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::collections::HashMap; 3 | use serde::Deserialize; 4 | use serde_derive::Deserialize; 5 | use minijinja::Value; 6 | use axum::http::{StatusCode, HeaderMap}; 7 | use axum::body::Body; 8 | use reqwest::{Request, RequestBuilder, Client}; 9 | use crate::debug::debug; 10 | 11 | #[derive(Deserialize)] 12 | pub struct Proxy { 13 | method: Option, 14 | url: String, 15 | headers: Option>, 16 | body: Option> 17 | } 18 | 19 | impl Proxy { 20 | pub async fn new ( 21 | method: &str, 22 | headers: &HashMap, 23 | body: &Vec, 24 | proxy: &Value 25 | ) -> Result<(StatusCode, HeaderMap, Body), Box> { 26 | let proxy = Proxy::deserialize(proxy)?; 27 | let method = proxy.method.unwrap_or(method.to_string()); 28 | 29 | debug(&method, &proxy.url, None, ""); 30 | let mut r = RequestBuilder::from_parts(Client::new(), 31 | Request::new(method.parse()?, proxy.url.parse()?) 32 | ); 33 | if let Some(headers) = proxy.headers { 34 | for (name, value) in headers.iter() { 35 | r = r.header(name, value); 36 | } 37 | } 38 | for (name, value) in headers.iter() { 39 | r = r.header(name.clone(), value.clone()); 40 | } 41 | let response = match r.body( 42 | proxy.body.unwrap_or(body.to_vec()) 43 | ).send().await { 44 | Ok(response) => { 45 | debug( 46 | &method, 47 | &proxy.url, 48 | Some(response.status().as_u16()), 49 | "" 50 | ); 51 | response 52 | }, 53 | Err(err) => { 54 | debug(&method, &proxy.url, Some(500), &err.to_string()); 55 | return Err(err.into()); 56 | } 57 | }; 58 | 59 | Ok(( 60 | response.status(), 61 | response.headers().clone(), 62 | response.bytes().await?.into() 63 | )) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/assets.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::path::{PathBuf, Path}; 3 | use std::fs::read; 4 | use axum::{ 5 | response::Response, 6 | http::StatusCode, 7 | http::header::{HeaderValue, CONTENT_TYPE}, 8 | }; 9 | use glob_match::glob_match; 10 | use mime_guess; 11 | use crate::debug::debug; 12 | 13 | #[derive(Clone)] 14 | pub struct Assets { 15 | all: bool, 16 | ignore: Vec, 17 | dir: PathBuf 18 | } 19 | 20 | impl Assets { 21 | pub fn new ( 22 | dir: PathBuf, 23 | all: bool, 24 | ignore: Vec 25 | ) -> Result> { 26 | let p = dir.as_path(); 27 | if !p.is_dir() { 28 | Err(format!("assets is not a dir: {}", p.display()).into()) 29 | } else { 30 | Ok(Assets { 31 | dir, 32 | all, 33 | ignore 34 | }) 35 | } 36 | } 37 | 38 | fn getter (&self, path_str: &str) -> Result { 39 | let path = Path::new(path_str); 40 | let dir = self.dir.as_path(); 41 | let mut file = dir.join(path); 42 | let mut response: Response; 43 | 44 | if file.is_dir() { 45 | file = file.join("index.html"); 46 | } 47 | 48 | if !file.starts_with(dir) || !file.is_file() { 49 | return Err(StatusCode::NOT_FOUND); 50 | } 51 | 52 | let path_str = path.to_str().unwrap_or(""); 53 | for glob in &self.ignore { 54 | if glob_match(&glob, path_str) { 55 | return Err(StatusCode::NOT_FOUND); 56 | } 57 | } 58 | 59 | if !self.all { 60 | for component in path.components() { 61 | let name = component.as_os_str().to_str().unwrap_or(""); 62 | 63 | if name.len() == 0 || ( 64 | name.len() > 1 && 65 | name.as_bytes()[0] == b'.' 66 | ) { 67 | return Err(StatusCode::NOT_FOUND); 68 | } 69 | } 70 | } 71 | 72 | match read(&file) { 73 | Err(_) => { 74 | return Err(StatusCode::INTERNAL_SERVER_ERROR); 75 | }, 76 | Ok(body) => { 77 | response = Response::new(body.into()); 78 | } 79 | }; 80 | 81 | let mime = mime_guess::from_path(&file).first_raw().unwrap_or(""); 82 | 83 | if mime.len() > 0 { 84 | match HeaderValue::from_str(mime) { 85 | Ok(mime) => { 86 | response.headers_mut().insert(CONTENT_TYPE, mime); 87 | }, 88 | Err(_) => {} 89 | }; 90 | } 91 | 92 | Ok(response) 93 | } 94 | 95 | pub fn get (&self, path_str: &str) -> Result { 96 | let path = format!("/{}", path_str); 97 | match self.getter(path_str) { 98 | Ok(response) => { 99 | debug("GET", &path, Some(200), ""); 100 | Ok(response) 101 | }, 102 | Err(status) => { 103 | debug("GET", &path, Some(status.as_u16()), ""); 104 | Err(status) 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use toml; 2 | use serde_json; 3 | use serde_derive::Deserialize; 4 | use std::error::Error; 5 | use std::path::{Path, PathBuf}; 6 | use std::ffi::OsStr; 7 | use std::fs::read_to_string; 8 | 9 | #[derive(Deserialize, Clone, Debug)] 10 | pub struct Route { 11 | pub method: String, 12 | pub path: String, 13 | pub template: String 14 | } 15 | 16 | #[derive(Deserialize, Clone, Debug, Default)] 17 | pub struct Config { 18 | pub all: Option, 19 | pub ignore: Option>, 20 | pub cors: Option>, 21 | pub port: Option, 22 | pub cert: Option, 23 | pub key: Option, 24 | pub assets: Option, 25 | pub templates: Option, 26 | pub data: Option, 27 | pub routes: Option> 28 | } 29 | 30 | impl Config { 31 | pub fn new(file: Option<&Path>) -> Result> { 32 | match file { 33 | None => Ok(Default::default()), 34 | Some(file) => { 35 | let data = match read_to_string(file) { 36 | Ok(data) => data, 37 | Err(err) => { 38 | return Err(format!( 39 | "Unable to read <{}>\n{:#}", 40 | file.display(), err 41 | ).into()); 42 | } 43 | }; 44 | 45 | let mut config: Config = match file.extension().unwrap_or( 46 | OsStr::new("") 47 | ).to_str() { 48 | Some("json") => { 49 | match serde_json::from_str(&data) { 50 | Ok(config) => config, 51 | Err(err) => { 52 | return Err(format!( 53 | "Unable to parse config file <{}>!\n{:#}", 54 | file.display(), err 55 | ).into()); 56 | } 57 | } 58 | }, 59 | Some("toml") => { 60 | match toml::from_str(&data) { 61 | Ok(config) => config, 62 | Err(err) => { 63 | return Err(format!( 64 | "Unable to parse config file <{}>!\n{:#}", 65 | file.display(), err 66 | ).into()); 67 | } 68 | } 69 | }, 70 | _ => { 71 | return Err(format!( 72 | "Configuration file <{}> must be .json or .toml", 73 | file.display() 74 | ).into()); 75 | } 76 | }; 77 | 78 | if let Some(dir) = file.parent() { 79 | if let Some(templates) = config.templates { 80 | config.templates = Some(dir.join(templates)); 81 | } 82 | if let Some(data) = config.data { 83 | config.data = Some(dir.join(data)); 84 | } 85 | if let Some(assets) = config.assets { 86 | config.assets = Some(dir.join(assets)); 87 | } 88 | if let Some(cert) = config.cert { 89 | config.cert = Some(dir.join(cert)); 90 | } 91 | if let Some(key) = config.key { 92 | config.key = Some(dir.join(key)); 93 | } 94 | } 95 | 96 | Ok(config) 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Local, DateTime}; 2 | 3 | pub fn time_string (time: DateTime) -> String { 4 | time.format("%Y-%m-%d %H:%M:%S").to_string() 5 | } 6 | 7 | pub fn debug ( 8 | method: &str, 9 | path: &str, 10 | status: Option, 11 | error: &str 12 | ) -> () { 13 | println!("[{}] {} {} {}{}", 14 | time_string(Local::now()), 15 | method, 16 | path, 17 | match status { 18 | Some(status) => status.to_string(), 19 | None => String::from("...") 20 | }, 21 | if error.len() > 0 {format!("\n{}", error)} else {String::new()} 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod templates; 2 | mod assets; 3 | mod config; 4 | mod app; 5 | mod debug; 6 | 7 | use std::error::Error; 8 | use std::path::PathBuf; 9 | use std::collections::HashMap; 10 | use std::net::SocketAddr; 11 | use clap::{Parser}; 12 | use tower_http::cors::{Any, CorsLayer}; 13 | use axum::{ 14 | extract::Path, 15 | routing::{get, on, Router}, 16 | http::{Method, header::{HeaderValue}} 17 | }; 18 | use axum_server::tls_openssl::OpenSSLConfig; 19 | use crate::assets::Assets; 20 | use crate::config::Config; 21 | use crate::app::{AppState, handler}; 22 | 23 | #[derive(Parser)] 24 | #[command(author, version, about, long_about = None)] 25 | struct Cli { 26 | /// config file path. (Accept: .json, .toml) 27 | #[clap(short='f', long)] 28 | config: Option, 29 | 30 | /// static files folder path. 31 | #[clap()] 32 | assets: Option, 33 | 34 | /// port number to run the server on. 35 | #[clap(short, long)] 36 | port: Option, 37 | 38 | /// public key file path. 39 | #[clap(short, long)] 40 | cert: Option, 41 | 42 | /// private key file path. 43 | #[clap(short, long)] 44 | key: Option, 45 | 46 | /// allow CORS from all origins. 47 | #[clap(short='o', long)] 48 | allow_cors: bool, 49 | 50 | /// all files, include hidden files 51 | #[clap(short, long)] 52 | all: bool, 53 | 54 | /// ignore files based on glob match 55 | #[clap(short, long)] 56 | ignore: Option, 57 | } 58 | 59 | fn init () -> Result<(Router, u16, Option), Box> { 60 | let cli = Cli::parse(); 61 | let config = Config::new(cli.config.as_deref())?; 62 | let mut app = Router::new(); 63 | 64 | if let Some(assets) = cli.assets.or(config.assets) { 65 | let mut ignore: Vec = Vec::new(); 66 | let has_home = assets.as_path().join("index.html").is_file(); 67 | if let Some(glob) = cli.ignore { 68 | ignore.push(glob); 69 | if let Some(globs) = config.ignore { 70 | ignore.append(&mut globs.clone()); 71 | } 72 | } 73 | 74 | let loader = Assets::new( 75 | assets, 76 | cli.all || config.all.unwrap_or(false), 77 | ignore 78 | )?; 79 | if has_home { 80 | let loader2 = loader.clone(); 81 | app = app.route("/", get(|| async move { 82 | loader2.get("") 83 | })); 84 | } 85 | app = app.route("/*file", get(| 86 | Path(params): Path>, 87 | | async move { 88 | loader.get(params.get("file").map_or("", |v| v)) 89 | })); 90 | } 91 | 92 | if let (Some(templates), Some(routes)) = ( 93 | config.templates, config.routes 94 | ) { 95 | let env = templates::new(templates, config.data)?; 96 | for route in &routes { 97 | app = app.route(&route.path, on( 98 | Method::from_bytes(route.method.as_bytes())?.try_into()?, 99 | handler 100 | ).with_state(AppState::new(&env, &route.template))); 101 | } 102 | } 103 | 104 | 105 | let cors = if cli.allow_cors {Some(Vec::new())} else {config.cors}; 106 | if let Some(origins) = cors { 107 | let mut layer = CorsLayer::new().allow_methods(Any); 108 | 109 | if origins.len() == 0 { 110 | layer = layer.allow_origin(Any); 111 | } 112 | for origin in origins { 113 | layer = layer.allow_origin(origin.parse::()?); 114 | } 115 | 116 | app = app.layer(layer); 117 | } 118 | 119 | let port = cli.port.unwrap_or(config.port.unwrap_or(3000)); 120 | 121 | let mut ssl: Option = None; 122 | if let (Some(cert), Some(key)) = ( 123 | cli.cert.or(config.cert), cli.key.or(config.key) 124 | ) { 125 | ssl = Some(OpenSSLConfig::from_pem_file(cert, key)?); 126 | } 127 | 128 | Ok((app, port, ssl)) 129 | } 130 | 131 | #[tokio::main] 132 | async fn main() -> () { 133 | let (app, port, ssl) = match init() { 134 | Ok(server) => server, 135 | Err(err) => { 136 | println!("{}", err); 137 | return (); 138 | } 139 | }; 140 | let addr = SocketAddr::from(([127, 0, 0, 1], port)); 141 | 142 | let server = match ssl { 143 | Some(ssl) => { 144 | println!("Server started at https://localhost:{}", port); 145 | axum_server::bind_openssl(addr, ssl) 146 | .serve(app.into_make_service()).await 147 | }, 148 | None => { 149 | println!("Server started at http://localhost:{}", port); 150 | axum_server::bind(addr) 151 | .serve(app.into_make_service()).await 152 | } 153 | }; 154 | 155 | match server { 156 | Ok(_) => (), 157 | Err(err) => { 158 | println!("Fail to start server!\n{}", err.to_string()); 159 | () 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/templates/command.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Serialize; 2 | use std::process::Command; 3 | use minijinja::Value; 4 | 5 | #[derive(Serialize)] 6 | struct Output { 7 | code: i32, 8 | stdout: Vec, 9 | stderr: Vec 10 | } 11 | 12 | pub fn command(command: String) -> Value { 13 | let result = match Command::new("sh").arg("-c").arg(&command).output() { 14 | Ok(result) => result, 15 | Err(err) => { 16 | return Value::from_serialize(Output { 17 | code: 999999, 18 | stdout: format!( 19 | "Fail to execute command!" 20 | ).as_bytes().to_vec(), 21 | stderr: err.to_string().as_bytes().to_vec() 22 | }); 23 | } 24 | }; 25 | 26 | Value::from_serialize(Output { 27 | code: result.status.code().unwrap_or(0), 28 | stdout: result.stdout, 29 | stderr: result.stderr 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/templates/fetch.rs: -------------------------------------------------------------------------------- 1 | use minijinja::Value; 2 | use reqwest::{Method, Url}; 3 | use reqwest::blocking::{Client, Response}; 4 | use serde_derive::Serialize; 5 | use std::collections::HashMap; 6 | use crate::debug::debug; 7 | use tokio::task::block_in_place; 8 | 9 | #[derive(Serialize)] 10 | struct Res { 11 | status: u16, 12 | headers: HashMap, 13 | body: Vec 14 | } 15 | 16 | impl Res { 17 | fn new (response: Response) -> Value { 18 | let status = response.status().as_u16(); 19 | 20 | let mut headers = HashMap::new(); 21 | for key in response.headers().keys() { 22 | if let Some(value) = response.headers().get(key) { 23 | if let Ok(value) = value.to_str() { 24 | headers.insert(key.to_string(), value.to_string()); 25 | } 26 | } 27 | } 28 | 29 | match response.bytes() { 30 | Ok(body) => Value::from_serialize(Res { 31 | status, 32 | headers, 33 | body: body.to_vec() 34 | }), 35 | Err(_) => Value::from_serialize(Res { 36 | status, 37 | headers, 38 | body: Vec::new() 39 | }) 40 | } 41 | } 42 | 43 | fn err (message: String) -> Value { 44 | Value::from_serialize(Res { 45 | status: 400, 46 | headers: HashMap::new(), 47 | body: message.as_bytes().to_vec() 48 | }) 49 | } 50 | } 51 | 52 | fn fetch ( 53 | method: &str, 54 | url: &str, 55 | body: Option<&Vec> 56 | ) -> Value { 57 | let method: Method = match method.parse() { 58 | Ok(method) => method, 59 | Err(err) => { 60 | return Res::err(format!("Invalid method!\n{:#?}", err)); 61 | } 62 | }; 63 | 64 | let url: Url = match url.parse() { 65 | Ok(url) => url, 66 | Err(err) => { 67 | return Res::err(format!("Invalid URL!\n{:#?}", err)); 68 | } 69 | }; 70 | 71 | let m = method.as_str().to_string(); 72 | let p = url.to_string(); 73 | let mut request = Client::new().request(method, url); 74 | if let Some(body) = body { 75 | request = request.body(body.to_vec()); 76 | } 77 | 78 | debug(&m, &p, None, ""); 79 | block_in_place(move || { 80 | match request.send() { 81 | Ok(response) => { 82 | debug(&m, &p, Some(200), ""); 83 | Res::new(response) 84 | }, 85 | Err(err) => { 86 | let error = err.to_string(); 87 | debug(&m, &p, Some(500), &error); 88 | Res::err(format!("Request fail!\n{}", &error)) 89 | } 90 | } 91 | }) 92 | } 93 | 94 | pub fn get (url: &str) -> Value { 95 | fetch("GET", url, None) 96 | } 97 | 98 | pub fn delete (url: &str) -> Value { 99 | fetch("DELETE", url, None) 100 | } 101 | 102 | pub fn head (url: &str) -> Value { 103 | fetch("HEAD", url, None) 104 | } 105 | 106 | pub fn options (url: &str) -> Value { 107 | fetch("OPTIONS", url, None) 108 | } 109 | 110 | pub fn post (url: &str, body: &Vec) -> Value { 111 | fetch("POST", url, Some(body)) 112 | } 113 | 114 | pub fn put (url: &str, body: &Vec) -> Value { 115 | fetch("PUT", url, Some(body)) 116 | } 117 | 118 | pub fn patch (url: &str, body: &Vec) -> Value { 119 | fetch("PATCH", url, Some(body)) 120 | } 121 | -------------------------------------------------------------------------------- /src/templates/file.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::Path; 3 | use std::path::PathBuf; 4 | use std::error::Error; 5 | use minijinja::Value; 6 | use serde_derive::Serialize; 7 | use crate::debug::time_string; 8 | 9 | #[derive(Serialize)] 10 | struct File { 11 | accessed: String, 12 | created: String, 13 | modified: String, 14 | is_dir: bool, 15 | is_file: bool, 16 | is_symlink: bool, 17 | name: String, 18 | len: u64 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct IO { 23 | dir: PathBuf 24 | } 25 | 26 | impl IO { 27 | pub fn new (dir: PathBuf) -> Result> { 28 | let p = dir.as_path(); 29 | if p.is_dir() { 30 | Ok(IO {dir}) 31 | } else { 32 | Err(format!("Data must be a directory: {}", p.display()).into()) 33 | } 34 | } 35 | 36 | fn get_path (&self, path: &str) -> Option { 37 | let dir = self.dir.as_path(); 38 | let mut path = Path::new(path); 39 | if let Ok(p) = path.strip_prefix("/") { 40 | path = p 41 | } 42 | let path = dir.join(path); 43 | 44 | if path.starts_with(dir) { 45 | Some(path) 46 | } else { 47 | None 48 | } 49 | } 50 | 51 | pub fn read (&self, entry: &str) -> Option { 52 | let path = match self.get_path(entry) { 53 | Some(path) => path, 54 | None => { 55 | return None 56 | } 57 | }; 58 | let path = path.as_path(); 59 | 60 | if path.try_exists().unwrap_or(false) { 61 | if path.is_dir() { 62 | let mut files: Vec = Vec::new(); 63 | match fs::read_dir(path) { 64 | Ok(entries) => { 65 | for entry in entries { 66 | if let Ok(entry) = entry { 67 | let p = entry.path(); 68 | let mut fname = String::new(); 69 | if let Some(name) = p.file_name() { 70 | if let Some(name) = name.to_str() { 71 | fname = name.to_string(); 72 | } 73 | } 74 | let mut len: u64 = 0; 75 | let mut accessed = String::new(); 76 | let mut created = String::new(); 77 | let mut modified = String::new(); 78 | if let Ok(meta) = p.metadata() { 79 | len = meta.len(); 80 | if let Ok(time) = meta.accessed() { 81 | accessed = time_string(time.into()); 82 | } 83 | if let Ok(time) = meta.created() { 84 | created = time_string(time.into()); 85 | } 86 | if let Ok(time) = meta.modified() { 87 | modified = time_string(time.into()); 88 | } 89 | } 90 | files.push(File { 91 | accessed, 92 | created, 93 | modified, 94 | is_dir: p.is_dir(), 95 | is_file: p.is_file(), 96 | is_symlink: p.is_symlink(), 97 | name: fname, 98 | len 99 | }); 100 | } 101 | } 102 | Some(Value::from_serialize(files)) 103 | }, 104 | Err(_) => None 105 | } 106 | } else { 107 | match fs::read(path) { 108 | Ok(data) => Some(data.into()), 109 | Err(_) => None 110 | } 111 | } 112 | } else { 113 | None 114 | } 115 | } 116 | 117 | pub fn write (&self, file: &str, data: &Vec) -> Option { 118 | let path = match self.get_path(file) { 119 | Some(path) => path, 120 | None => { 121 | return Some(format!("Unable to resolve file: {}", file)) 122 | } 123 | }; 124 | let path = path.as_path(); 125 | 126 | if let Some(dir) = path.parent() { 127 | if !dir.exists() { 128 | if let Err(err) = fs::create_dir_all(dir) { 129 | return Some(format!("Unable to create dir: {}\n{:#}", 130 | dir.display(), err 131 | )); 132 | } 133 | } 134 | } 135 | 136 | match fs::write(path, data) { 137 | Ok(_) => None, 138 | Err(err) => Some(format!("Unable to write file: {}\n{:#}", 139 | file, err 140 | )) 141 | } 142 | } 143 | 144 | pub fn remove (&self, entry: &str) -> Option { 145 | let path = match self.get_path(entry) { 146 | Some(path) => path, 147 | None => { 148 | return Some(format!("Unable to resolve path: {}", entry)) 149 | } 150 | }; 151 | let path = path.as_path(); 152 | 153 | if path.is_dir() { 154 | match fs::remove_dir_all(path) { 155 | Ok(_) => None, 156 | Err(err) => Some(format!( 157 | "Unable to remove dir: {}\n{:#}", entry, err 158 | )) 159 | } 160 | } else { 161 | match fs::remove_file(path) { 162 | Ok(_) => None, 163 | Err(err) => Some(format!( 164 | "Unable to remove file: {}\n{:#}", entry, err 165 | )) 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/templates/format.rs: -------------------------------------------------------------------------------- 1 | use minijinja::{Error, ErrorKind::InvalidOperation, Value}; 2 | use serde_json; 3 | use serde_urlencoded; 4 | use toml; 5 | 6 | pub fn format ( 7 | value: &Value, 8 | encoding: &str 9 | ) -> Result { 10 | match encoding { 11 | "form" => { 12 | match serde_urlencoded::to_string::(value.clone().into()) { 13 | Ok(data) => Ok(data), 14 | Err(err) => Err(Error::new( 15 | InvalidOperation, 16 | format!("Unable to format Form Data!\n{:#}", err) 17 | )) 18 | } 19 | }, 20 | "json" => { 21 | match serde_json::to_string_pretty(value.into()) { 22 | Ok(data) => Ok(data), 23 | Err(err) => Err(Error::new( 24 | InvalidOperation, 25 | format!("Unable to format JSON!\n{:#}", err) 26 | )) 27 | } 28 | }, 29 | "toml" => { 30 | match toml::to_string_pretty(value.into()) { 31 | Ok(data) => Ok(data), 32 | Err(err) => Err(Error::new( 33 | InvalidOperation, 34 | format!("Unable to format TOML!\n{:#}", err) 35 | )) 36 | } 37 | }, 38 | "debug" => { 39 | Ok(format!("{:#?}", value)) 40 | }, 41 | encoding => { 42 | Err(Error::new( 43 | InvalidOperation, 44 | format!("Format {} not implemented!", encoding) 45 | )) 46 | } 47 | } 48 | } 49 | 50 | pub fn bytes (text: &str) -> Vec { 51 | text.as_bytes().to_vec() 52 | } 53 | -------------------------------------------------------------------------------- /src/templates/mod.rs: -------------------------------------------------------------------------------- 1 | mod parse; 2 | mod command; 3 | mod file; 4 | mod fetch; 5 | mod format; 6 | 7 | use std::error::Error; 8 | use minijinja::{Environment, path_loader, Value}; 9 | use parse::parse; 10 | use format::{format, bytes}; 11 | use command::command; 12 | use file::IO; 13 | use fetch::{get, delete, head, options, post, put, patch}; 14 | use std::path::{PathBuf}; 15 | 16 | pub fn new ( 17 | dir: PathBuf, 18 | data: Option 19 | ) -> Result, Box> { 20 | let mut env = Environment::new(); 21 | 22 | env.set_loader(path_loader(dir)); 23 | 24 | env.add_filter("parse", parse); 25 | env.add_filter("format", format); 26 | env.add_filter("bytes", bytes); 27 | env.add_function("log", |message: &str| -> () { 28 | println!("{}", message); 29 | () 30 | }); 31 | env.add_function("command", command); 32 | if let Some(data) = data { 33 | let io1 = IO::new(data)?; 34 | let io2 = io1.clone(); 35 | let io3 = io1.clone(); 36 | env.add_function("read", move |entry: &str| -> Option { 37 | io1.read(entry) 38 | }); 39 | env.add_function("write", move | 40 | file: &str, 41 | data: &Vec 42 | | -> Option { 43 | io2.write(file, data) 44 | }); 45 | env.add_function("remove", move |entry: &str| -> Option { 46 | io3.remove(entry) 47 | }); 48 | } 49 | env.add_function("get", get); 50 | env.add_function("delete", delete); 51 | env.add_function("head", head); 52 | env.add_function("options", options); 53 | env.add_function("post", post); 54 | env.add_function("put", put); 55 | env.add_function("patch", patch); 56 | 57 | Ok(env) 58 | } 59 | -------------------------------------------------------------------------------- /src/templates/parse.rs: -------------------------------------------------------------------------------- 1 | use serde_json; 2 | use serde_urlencoded; 3 | use toml; 4 | use std::str::from_utf8; 5 | use minijinja::{Error, ErrorKind::InvalidOperation, Value}; 6 | 7 | pub fn parse ( 8 | data: Vec, 9 | encoding: &str 10 | ) -> Result { 11 | let text = match from_utf8(&data) { 12 | Ok(text) => text, 13 | Err(err) => { 14 | return Err(Error::new( 15 | InvalidOperation, 16 | format!("Unable to parse binary data into utf8!\n{:#}", err) 17 | )) 18 | } 19 | }; 20 | 21 | match encoding { 22 | "form" => { 23 | match serde_urlencoded::from_str::(text) { 24 | Ok(value) => Ok(value), 25 | Err(err) => Err(Error::new( 26 | InvalidOperation, 27 | format!("Failed to parse from Form Data!\n{}", 28 | err.to_string() 29 | ) 30 | )) 31 | } 32 | }, 33 | "json" => { 34 | match serde_json::from_str::(text) { 35 | Ok(value) => Ok(value), 36 | Err(err) => Err(Error::new( 37 | InvalidOperation, 38 | format!("Failed to parse from JSON!\n{}", err.to_string()) 39 | )) 40 | } 41 | }, 42 | "toml" => { 43 | match toml::from_str::(text) { 44 | Ok(value) => Ok(value), 45 | Err(err) => Err(Error::new( 46 | InvalidOperation, 47 | format!("Failed to parse from TOML!\n{}", err.to_string()) 48 | )) 49 | } 50 | }, 51 | "text" => { 52 | Ok(Value::from(text)) 53 | }, 54 | encoding => Err(Error::new( 55 | InvalidOperation, 56 | format!("{} encoding not implemented!", encoding) 57 | )) 58 | } 59 | } 60 | --------------------------------------------------------------------------------