initial commit

This commit is contained in:
Martin Algesten
2017-12-12 04:40:30 +01:00
parent f1b9318994
commit 4df9876c24
15 changed files with 2475 additions and 5 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
target
rls

512
Cargo.lock generated
View File

@@ -1,4 +1,512 @@
[[package]]
name = "superagent"
version = "0.1.0"
name = "ascii"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "base64"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chrono"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "chunked_transfer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dns-lookup"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"socket2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "idna"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lazy_static"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "matches"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "mime"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "mime_guess"
version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "percent-encoding"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "phf"
version = "0.7.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf_codegen"
version = "0.7.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf_generator"
version = "0.7.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "phf_shared"
version = "0.7.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "qstring"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ring"
version = "0.13.0-alpha5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustls"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.13.0-alpha5 (registry+https://github.com/rust-lang/crates.io-index)",
"sct 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"webpki 0.18.0-alpha4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "safemem"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "sct"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ring 0.13.0-alpha5 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_json"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "siphasher"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "socket2"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicase"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-normalization"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "untrusted"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ureq"
version = "0.1.0"
dependencies = [
"ascii 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"dns-lookup 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"qstring 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rustls 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"webpki 0.18.0-alpha4 (registry+https://github.com/rust-lang/crates.io-index)",
"webpki-roots 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "url"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "version_check"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "webpki"
version = "0.18.0-alpha4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ring 0.13.0-alpha5 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "webpki-roots"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"webpki 0.18.0-alpha4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ascii 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3c943947ef9a60212bd70d62f5775333c7739cf44b1b4abcbf4a57976f1867"
"checksum base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "85415d2594767338a74a30c1d370b2f3262ec1b4ed2d7bba5b3faf4de40467d9"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9"
"checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d"
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
"checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
"checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
"checksum dns-lookup 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "54810764899241c707428f4a1989351f30c0c2bda5ea07ff2e43148f8935039f"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
"checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
"checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
"checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac"
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
"checksum mime_guess 1.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b7e2b09d08313f84e0fb82d13a4d859109a17543fe9af3b6d941dc1431f7de79"
"checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45"
"checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "7d37a244c75a9748e049225155f56dbcb98fe71b192fd25fd23cb914b5ad62f2"
"checksum phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4048fe7dd7a06b8127ecd6d3803149126e9b33c7558879846da3a63f734f2b"
"checksum phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "05a079dd052e7b674d21cb31cbb6c05efd56a2cd2827db7692e2f1a507ebd998"
"checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930"
"checksum qstring 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d3f5d9b355cd0db9ef4b46075e6dd578bf9700448ca7f3b8af1481b3c495d4"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
"checksum ring 0.13.0-alpha5 (registry+https://github.com/rust-lang/crates.io-index)" = "3845516753f91b4511f9b17c917ea6fa4bc5a7853a9947b0f66731aff51cdef5"
"checksum rustls 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ab72e4883a4fc9fd5cd462a51c55d79f6a7b5c9483e8d73a2b7bca0b18430bcd"
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
"checksum sct 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4540aed8d71a5de961a8902cf356e28122bd62695eb5be1c214f84d8704097c"
"checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95"
"checksum serde_json 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "fc97cccc2959f39984524026d760c08ef0dd5f0f5948c8d31797dbfae458c875"
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
"checksum socket2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "06dc9f86ee48652b7c80f3d254e3b9accb67a928c562c64d10d7b016d3d98dab"
"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25"
"checksum untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70afa43c8c5d23a53a3c39ec9b56232c5badc19f6bb5ad529c1d6448a7241365"
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum webpki 0.18.0-alpha4 (registry+https://github.com/rust-lang/crates.io-index)" = "724897af4bb44f3e0142b9cca300eb15f61b9b34fa559440bed8c43f2ff7afc0"
"checksum webpki-roots 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "edbd75d6abf044ef0c9d7ec92b9e8c518bcd93a15bb7bd9a92239e035248fc17"
"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,6 +1,20 @@
[package]
name = "superagent"
name = "ureq"
version = "0.1.0"
authors = ["Martin Algesten <martin@algesten.se>"]
[dependencies]
ascii = "0.9"
base64 = "*"
chrono = "0.4"
chunked_transfer = "0.3"
dns-lookup = "0.9.1"
encoding = "0.2"
lazy_static = "1"
mime_guess = "1"
qstring = "0.5"
rustls = "0.12"
serde_json = "1"
url = "1.6"
webpki = "0.18.0-alpha3"
webpki-roots = "0.14.0"

315
src/agent.rs Normal file
View File

@@ -0,0 +1,315 @@
use std::str::FromStr;
use std::sync::Mutex;
use header::Header;
use util::*;
// to get to share private fields
include!("request.rs");
include!("response.rs");
include!("conn.rs");
#[derive(Debug, Default, Clone)]
pub struct Agent {
pub headers: Vec<Header>,
pub auth: Option<(String, String)>,
pub pool: Arc<Mutex<Option<ConnectionPool>>>,
}
impl Agent {
pub fn new() -> Agent {
Default::default()
}
/// Create a new agent after treating it as a builder.
/// This actually clones the internal state to a new one and instantiates
/// a new connection pool that is reused between connects.
pub fn build(&self) -> Self {
Agent {
headers: self.headers.clone(),
auth: self.auth.clone(),
pool: Arc::new(Mutex::new(Some(ConnectionPool::new()))),
}
}
/// Set a header field that will be present in all requests using the agent.
///
/// ```
/// let agent = ureq::agent()
/// .set("X-API-Key", "foobar")
/// .set("Accept", "application/json")
/// .build();
///
/// let r = agent
/// .get("/my-page")
/// .call();
///
/// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap());
/// } else {
/// println!("Oh no error!");
/// }
/// ```
pub fn set<K, V>(&mut self, header: K, value: V) -> &mut Agent
where
K: Into<String>,
V: Into<String>,
{
add_agent_header(self, header.into(), value.into());
self
}
/// Set many headers that will be present in all requests using the agent.
///
/// ```
/// #[macro_use]
/// extern crate ureq;
///
/// fn main() {
/// let agent = ureq::agent()
/// .set_map(map!{
/// "X-API-Key" => "foobar",
/// "Accept" => "application/json"
/// })
/// .build();
///
/// let r = agent
/// .get("/my_page")
/// .call();
///
/// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap());
/// }
/// }
/// ```
pub fn set_map<K, V, I>(&mut self, headers: I) -> &mut Agent
where
K: Into<String>,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
for (k, v) in headers.into_iter() {
add_agent_header(self, k.into(), v.into());
}
self
}
/// Basic auth that will be present in all requests using the agent.
///
/// ```
/// let agent = ureq::agent()
/// .auth("martin", "rubbermashgum")
/// .build();
///
/// let r = agent
/// .get("/my_page")
/// .call();
/// println!("{:?}", r);
/// ```
pub fn auth<S, T>(&mut self, user: S, pass: T) -> &mut Agent
where
S: Into<String>,
T: Into<String>,
{
let u = user.into();
let p = pass.into();
let pass = basic_auth(&u, &p);
self.auth_kind("Basic", pass)
}
/// Auth of other kinds such as `Digest`, `Token` etc, that will be present
/// in all requests using the agent.
///
/// ```
/// let agent = ureq::agent()
/// .auth_kind("token", "secret")
/// .build();
///
/// let r = agent
/// .get("/my_page")
/// .call();
/// ```
pub fn auth_kind<S, T>(&mut self, kind: S, pass: T) -> &mut Agent
where
S: Into<String>,
T: Into<String>,
{
self.auth = Some((kind.into(), pass.into()));
self
}
/// Request by providing the HTTP verb such as `GET`, `POST`...
///
/// ```
/// let agent = ureq::agent();
///
/// let r = agent
/// .request("GET", "/my_page")
/// .call();
/// println!("{:?}", r);
/// ```
pub fn request<M, S>(&self, method: M, path: S) -> Request
where
M: Into<String>,
S: Into<String>,
{
Request::new(&self, method.into(), path.into())
}
pub fn get<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("GET", path)
}
pub fn head<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("HEAD", path)
}
pub fn post<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("POST", path)
}
pub fn put<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("PUT", path)
}
pub fn delete<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("DELETE", path)
}
pub fn trace<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("TRACE", path)
}
pub fn options<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("OPTIONS", path)
}
pub fn connect<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("CONNECT", path)
}
pub fn patch<S>(&self, path: S) -> Request
where
S: Into<String>,
{
self.request("PATCH", path)
}
}
fn add_agent_header(agent: &mut Agent, k: String, v: String) {
if let Ok(h) = Header::from_str(&format!("{}: {}", k, v)) {
agent.headers.push(h);
}
}
#[cfg(test)]
mod tests {
use super::*;
///////////////////// AGENT TESTS //////////////////////////////
#[test]
fn agent_implements_send() {
let mut agent = Agent::new();
::std::thread::spawn(move || {
agent.set("Foo", "Bar");
});
}
//////////////////// REQUEST TESTS /////////////////////////////
#[test]
fn request_implements_send() {
let agent = Agent::new();
let mut request = Request::new(&agent, "GET".to_string(), "/foo".to_string());
::std::thread::spawn(move || {
request.set("Foo", "Bar");
});
}
//////////////////// RESPONSE TESTS /////////////////////////////
#[test]
fn content_type_without_charset() {
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\nOK";
let resp = s.parse::<Response>().unwrap();
assert_eq!("application/json", resp.content_type());
}
#[test]
fn content_type_with_charset() {
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=iso-8859-4\r\n\r\nOK";
let resp = s.parse::<Response>().unwrap();
assert_eq!("application/json", resp.content_type());
}
#[test]
fn content_type_default() {
let s = "HTTP/1.1 200 OK\r\n\r\nOK";
let resp = s.parse::<Response>().unwrap();
assert_eq!("text/plain", resp.content_type());
}
#[test]
fn charset() {
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=iso-8859-4\r\n\r\nOK";
let resp = s.parse::<Response>().unwrap();
assert_eq!("iso-8859-4", resp.charset());
}
#[test]
fn charset_default() {
let s = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\nOK";
let resp = s.parse::<Response>().unwrap();
assert_eq!("utf-8", resp.charset());
}
#[test]
fn chunked_transfer() {
let s = "HTTP/1.1 200 OK\r\nTransfer-Encoding: Chunked\r\n\r\n3\r\nhel\r\nb\r\nlo world!!!\r\n0\r\n\r\n";
let resp = s.parse::<Response>().unwrap();
assert_eq!("hello world!!!", resp.into_string().unwrap());
}
#[test]
fn parse_simple_json() {
let s = format!("HTTP/1.1 200 OK\r\n\r\n{{\"hello\":\"world\"}}");
let resp = s.parse::<Response>().unwrap();
let v = resp.into_json().unwrap();
assert_eq!(
v,
"{\"hello\":\"world\"}"
.parse::<serde_json::Value>()
.unwrap()
);
}
#[test]
fn parse_borked_header() {
let s = format!("HTTP/1.1 BORKED\r\n");
let resp: Response = s.parse::<Response>().unwrap_err().into();
assert_eq!(resp.http_version(), "HTTP/1.1");
assert_eq!(*resp.status(), 500);
assert_eq!(resp.status_text(), "Bad Status");
assert_eq!(resp.content_type(), "text/plain");
let v = resp.into_string().unwrap();
assert_eq!(v, "Bad Status\n");
}
}

193
src/conn.rs Normal file
View File

@@ -0,0 +1,193 @@
use dns_lookup;
use rustls;
use std::io::Write;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::net::TcpStream;
use std::time::Duration;
use stream::Stream;
use url::Url;
use webpki;
use webpki_roots;
const CHUNK_SIZE: usize = 1024 * 1024;
#[derive(Debug, Default, Clone)]
pub struct ConnectionPool {}
impl ConnectionPool {
fn new() -> Self {
ConnectionPool {}
}
fn connect(
&mut self,
request: &Request,
method: &str,
url: &Url,
redirects: u32,
payload: Payload,
) -> Result<Response, Error> {
//
// open connection
let mut stream = match url.scheme() {
"http" => connect_http(request, &url),
"https" => connect_https(request, &url),
_ => Err(Error::UnknownScheme(url.scheme().to_string())),
}?;
// send the request start + headers
let mut prelude: Vec<u8> = vec![];
write!(prelude, "{} {} HTTP/1.1\r\n", method, url.path())?;
if !request.has("host") {
write!(prelude, "Host: {}\r\n", url.host().unwrap())?;
}
for header in request.headers.iter() {
write!(prelude, "{}: {}\r\n", header.name(), header.value())?;
}
write!(prelude, "\r\n")?;
stream.write_all(&mut prelude[..])?;
// start reading the response to check it it's a redirect
let mut resp = Response::from_read(&mut stream);
// handle redirects
if resp.redirect() {
if redirects == 0 {
return Err(Error::TooManyRedirects);
}
// the location header
let location = resp.get("location");
if let Some(location) = location {
// join location header to current url in case it it relative
let new_url = url.join(location)
.map_err(|_| Error::BadUrl(format!("Bad redirection: {}", location)))?;
// perform the redirect differently depending on 3xx code.
return match resp.status {
301 | 302 | 303 => {
send_payload(&request, payload, &mut stream)?;
self.connect(request, "GET", &new_url, redirects - 1, Payload::Empty)
}
307 | 308 | _ => {
self.connect(request, method, &new_url, redirects - 1, payload)
}
};
}
}
// send the payload (which can be empty now depending on redirects)
send_payload(&request, payload, &mut stream)?;
// since it is not a redirect, give away the incoming stream to the response object
resp.set_reader(stream);
// release the response
Ok(resp)
}
}
fn connect_http(request: &Request, url: &Url) -> Result<Stream, Error> {
//
let hostname = url.host_str().unwrap();
let port = url.port().unwrap_or(80);
connect_host(request, hostname, port).map(|tcp| Stream::Http(tcp))
}
fn connect_https(request: &Request, url: &Url) -> Result<Stream, Error> {
//
let hostname = url.host_str().unwrap();
let port = url.port().unwrap_or(443);
// TODO let user override TLS roots.
let mut config = rustls::ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
let rc_config = Arc::new(config);
let socket = connect_host(request, hostname, port)?;
webpki::DNSNameRef::try_from_ascii_str(&hostname)
.map_err(|_| Error::ConnectionFailed(format!("Invalid TLS name: {}", hostname)))
.map(|webpki| rustls::ClientSession::new(&rc_config, webpki))
.map(|client| Stream::Https(client, socket))
}
fn connect_host(request: &Request, hostname: &str, port: u16) -> Result<TcpStream, Error> {
//
let ips: Vec<IpAddr> =
dns_lookup::lookup_host(hostname).map_err(|e| Error::DnsFailed(format!("{}", e)))?;
if ips.len() == 0 {
return Err(Error::DnsFailed(format!("No ip address for {}", hostname)));
}
// pick first ip, or should we randomize?
let sock_addr = SocketAddr::new(ips[0], port);
// connect with a configured timeout.
let stream = match request.timeout {
0 => TcpStream::connect(&sock_addr),
_ => TcpStream::connect_timeout(&sock_addr, Duration::from_millis(request.timeout as u64)),
}.map_err(|err| Error::ConnectionFailed(format!("{}", err)))?;
// rust's absurd api returns Err if we set 0.
if request.timeout_read > 0 {
stream
.set_read_timeout(Some(Duration::from_millis(request.timeout_read as u64)))
.ok();
}
if request.timeout_write > 0 {
stream
.set_write_timeout(Some(Duration::from_millis(request.timeout_write as u64)))
.ok();
}
Ok(stream)
}
fn send_payload(request: &Request, payload: Payload, stream: &mut Stream) -> IoResult<()> {
//
let (size, reader) = payload.into_read();
let do_chunk = request.get("transfer-encoding")
// if the user has set an encoding header, obey that.
.map(|enc| enc.eq_ignore_ascii_case("chunked"))
// if the content has a size
.ok_or_else(|| size.
// or if the user set a content-length header
or_else(||
request.get("content-length").map(|len| len.parse::<usize>().unwrap_or(0)))
// and that size is larger than 1MB, chunk,
.map(|size| size > CHUNK_SIZE))
// otherwise, assume chunking since it can be really big.
.unwrap_or(true);
if do_chunk {
pipe(reader, chunked_transfer::Encoder::new(stream))?;
} else {
pipe(reader, stream)?;
}
Ok(())
}
fn pipe<R, W>(mut reader: R, mut writer: W) -> IoResult<()>
where
R: Read,
W: Write,
{
let mut buf = [0_u8; CHUNK_SIZE];
loop {
let len = reader.read(&mut buf)?;
if len == 0 {
break;
}
writer.write_all(&buf[0..len])?;
}
Ok(())
}

61
src/error.rs Normal file
View File

@@ -0,0 +1,61 @@
use std::io::Error as IoError;
#[derive(Debug)]
pub enum Error {
BadUrl(String),
UnknownScheme(String),
DnsFailed(String),
ConnectionFailed(String),
TooManyRedirects,
BadStatus,
BadHeader,
Io(IoError),
}
impl Error {
pub fn status(&self) -> u16 {
match self {
Error::BadUrl(_) => 400,
Error::UnknownScheme(_) => 400,
Error::DnsFailed(_) => 400,
Error::ConnectionFailed(_) => 500,
Error::TooManyRedirects => 400,
Error::BadStatus => 500,
Error::BadHeader => 500,
Error::Io(_) => 500,
}
}
pub fn status_text(&self) -> &str {
match self {
Error::BadUrl(e) => {
println!("{}", e);
"Bad URL"
},
Error::UnknownScheme(_) => "Unknown Scheme",
Error::DnsFailed(_) => "Dns Failed",
Error::ConnectionFailed(_) => "Connection Failed",
Error::TooManyRedirects => "Too Many Redirects",
Error::BadStatus => "Bad Status",
Error::BadHeader => "Bad Header",
Error::Io(_) => "Network Error",
}
}
pub fn body_text(&self) -> String {
match self {
Error::BadUrl(url) => format!("Bad URL: {}", url),
Error::UnknownScheme(scheme) => format!("Unknown Scheme: {}", scheme),
Error::DnsFailed(err) => format!("Dns Failed: {}", err),
Error::ConnectionFailed(err) => format!("Connection Failed: {}", err),
Error::TooManyRedirects => "Too Many Redirects".to_string(),
Error::BadStatus => "Bad Status".to_string(),
Error::BadHeader => "Bad Header".to_string(),
Error::Io(ioe) => format!("Network Error: {}", ioe),
}
}
}
impl From<IoError> for Error {
fn from(err: IoError) -> Error {
Error::Io(err)
}
}

58
src/header.rs Normal file
View File

@@ -0,0 +1,58 @@
use ascii::AsciiString;
use error::Error;
use std::str::FromStr;
#[derive(Debug, Clone)]
/// Wrapper type for a header line.
pub struct Header {
line: AsciiString,
index: usize,
}
impl Header {
/// The header name.
///
/// ```
/// let header = "X-Forwarded-For: 127.0.0.1".parse::<ureq::Header>().unwrap();
/// assert_eq!("X-Forwarded-For", header.name());
/// ```
pub fn name(&self) -> &str {
&self.line.as_str()[0..self.index]
}
/// The header value.
///
/// ```
/// let header = "X-Forwarded-For: 127.0.0.1".parse::<ureq::Header>().unwrap();
/// assert_eq!("127.0.0.1", header.value());
/// ```
pub fn value(&self) -> &str {
&self.line.as_str()[self.index + 1..].trim()
}
/// Compares the given str to the header name ignoring case.
///
/// ```
/// let header = "X-Forwarded-For: 127.0.0.1".parse::<ureq::Header>().unwrap();
/// assert!(header.is_name("x-forwarded-for"));
/// ```
pub fn is_name(&self, other: &str) -> bool {
self.name().eq_ignore_ascii_case(other)
}
}
impl FromStr for Header {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
//
let line = AsciiString::from_str(s).map_err(|_| Error::BadHeader)?;
let index = s.find(":").ok_or_else(|| Error::BadHeader)?;
// no value?
if index >= s.len() {
return Err(Error::BadHeader);
}
Ok(Header { line, index })
}
}

View File

@@ -1,7 +1,144 @@
extern crate ascii;
extern crate base64;
extern crate chrono;
extern crate chunked_transfer;
extern crate dns_lookup;
extern crate encoding;
#[macro_use]
extern crate lazy_static;
extern crate mime_guess;
extern crate qstring;
extern crate rustls;
extern crate serde_json;
extern crate url;
extern crate webpki;
extern crate webpki_roots;
#[macro_use]
mod agent;
mod error;
mod header;
mod stream;
mod util;
pub use agent::{Agent, Request, Response};
pub use header::Header;
// re-export
pub use serde_json::{to_value, Map, Value};
/// Agents keep state between requests.
///
/// By default, no state, such as cookies, is kept between requests.
/// But by creating an agent as entry point for the request, we
/// can keep state.
///
/// ```
/// let agent = ureq::agent();
///
/// let auth = agent
/// .post("/login")
/// .auth("martin", "rubbermashgum")
/// .call(); // blocks. puts auth cookies in agent.
///
/// if !auth.ok() {
/// println!("Noes!");
/// }
///
/// let secret = agent
/// .get("/my-protected-page")
/// .call(); // blocks and waits for request.
///
/// if !secret.ok() {
/// println!("Wot?!");
/// }
///
/// println!("Secret is: {}", secret.into_string().unwrap());
/// ```
pub fn agent() -> Agent {
Agent::new()
}
pub fn request<M, S>(method: M, path: S) -> Request
where
M: Into<String>,
S: Into<String>,
{
Agent::new().request(method, path)
}
pub fn get<S>(path: S) -> Request
where
S: Into<String>,
{
request("GET", path)
}
pub fn head<S>(path: S) -> Request
where
S: Into<String>,
{
request("HEAD", path)
}
pub fn post<S>(path: S) -> Request
where
S: Into<String>,
{
request("POST", path)
}
pub fn put<S>(path: S) -> Request
where
S: Into<String>,
{
request("PUT", path)
}
pub fn delete<S>(path: S) -> Request
where
S: Into<String>,
{
request("DELETE", path)
}
pub fn trace<S>(path: S) -> Request
where
S: Into<String>,
{
request("TRACE", path)
}
pub fn options<S>(path: S) -> Request
where
S: Into<String>,
{
request("OPTIONS", path)
}
pub fn connect<S>(path: S) -> Request
where
S: Into<String>,
{
request("CONNECT", path)
}
pub fn patch<S>(path: S) -> Request
where
S: Into<String>,
{
request("PATCH", path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
fn connect_http_google() {
let resp = get("http://www.google.com/").call();
println!("{:?}", resp);
assert_eq!("text/html; charset=ISO-8859-1", resp.get("content-type").unwrap());
assert_eq!("text/html", resp.content_type());
}
#[test]
fn connect_https_google() {
let resp = get("https://www.google.com/").call();
println!("{:?}", resp);
assert_eq!("text/html; charset=ISO-8859-1", resp.get("content-type").unwrap());
assert_eq!("text/html", resp.content_type());
}
}

513
src/request.rs Normal file
View File

@@ -0,0 +1,513 @@
use qstring::QString;
use serde_json;
use std::sync::Arc;
lazy_static! {
static ref URL_BASE: Url = { Url::parse("http://localhost/").expect("Failed to parse URL_BASE") };
}
#[derive(Clone, Default)]
pub struct Request {
pool: Arc<Mutex<Option<ConnectionPool>>>,
// via agent
method: String,
path: String,
// from request itself
headers: Vec<Header>,
auth: Option<(String, String)>,
query: QString,
timeout: u32,
timeout_read: u32,
timeout_write: u32,
redirects: u32,
}
enum Payload {
Empty,
Text(String),
JSON(serde_json::Value),
Reader(Box<Read + 'static>),
}
impl Default for Payload {
fn default() -> Payload {
Payload::Empty
}
}
impl Payload {
fn into_read(self) -> (Option<usize>, Box<Read + 'static>) {
match self {
Payload::Empty => (Some(0), Box::new(VecRead::from_str(""))),
Payload::Text(s) => {
let read = VecRead::from_str(&s);
(Some(read.len()), Box::new(read))
}
Payload::JSON(v) => {
let vec = serde_json::to_vec(&v).expect("Bad JSON in payload");
let read = VecRead::from_vec(vec);
(Some(read.len()), Box::new(read))
}
Payload::Reader(read) => (None, read),
}
}
}
impl Request {
fn new(agent: &Agent, method: String, path: String) -> Request {
Request {
pool: Arc::clone(&agent.pool),
method,
path,
headers: agent.headers.clone(),
auth: agent.auth.clone(),
redirects: 5,
..Default::default()
}
}
/// "Builds" this request which is effectively the same as cloning.
/// This is needed when we use a chain of request builders, but
/// don't want to send the request at the end of the chain.
///
/// ```
/// let r = ureq::get("/my_page")
/// .set("X-Foo-Bar", "Baz")
/// .build();
/// ```
pub fn build(&self) -> Request {
self.clone()
}
/// Executes the request and blocks the caller until done.
///
/// Use `.timeout()` and `.timeout_read()` to avoid blocking forever.
///
/// ```
/// let r = ureq::get("/my_page")
/// .timeout(10_000) // max 10 seconds
/// .call();
///
/// println!("{:?}", r);
/// ```
pub fn call(&self) -> Response {
self.do_call(Payload::Empty)
}
fn do_call(&self, payload: Payload) -> Response {
let mut lock = self.pool.lock().unwrap();
self.to_url()
.and_then(|url| {
if lock.is_none() {
// create a one off pool.
ConnectionPool::new().connect(self, &self.method, &url, self.redirects, payload)
} else {
// reuse connection pool.
lock.as_mut().unwrap().connect(
self,
&self.method,
&url,
self.redirects,
payload,
)
}
})
.unwrap_or_else(|e| e.into())
}
/// Send data a json value.
///
/// ```
/// #[macro_use]
/// extern crate ureq;
///
/// fn main() {
/// let r = ureq::post("/my_page")
/// .send_json(json!({ "name": "martin", "rust": true }));
/// println!("{:?}", r);
/// }
/// ```
pub fn send_json(&self, data: serde_json::Value) -> Response {
self.do_call(Payload::JSON(data))
}
/// Send data as a string.
///
/// ```
/// let r = ureq::post("/my_page")
/// .content_type("text/plain")
/// .send_str("Hello World!");
/// println!("{:?}", r);
/// ```
pub fn send_str<S>(&self, data: S) -> Response
where
S: Into<String>,
{
let text = data.into();
self.do_call(Payload::Text(text))
}
/// Send data from a reader.
///
///
///
pub fn send<R>(&self, reader: R) -> Response
where
R: Read + Send + 'static,
{
self.do_call(Payload::Reader(Box::new(reader)))
}
/// Set a header field.
///
/// ```
/// let r = ureq::get("/my_page")
/// .set("X-API-Key", "foobar")
/// .set("Accept", "application/json")
/// .call();
///
/// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap());
/// } else {
/// println!("Oh no error!");
/// }
/// ```
pub fn set<K, V>(&mut self, header: K, value: V) -> &mut Request
where
K: Into<String>,
V: Into<String>,
{
add_request_header(self, header.into(), value.into());
self
}
/// Returns the value for a set header.
///
/// ```
/// let req = ureq::get("/my_page")
/// .set("X-API-Key", "foobar")
/// .build();
/// assert_eq!("foobar", req.get("x-api-Key").unwrap());
/// ```
pub fn get<'a>(&self, name: &'a str) -> Option<&str> {
self.headers
.iter()
.find(|h| h.is_name(name))
.map(|h| h.value())
}
/// Tells if the header has been set.
///
/// ```
/// let req = ureq::get("/my_page")
/// .set("X-API-Key", "foobar")
/// .build();
/// assert_eq!(true, req.has("x-api-Key"));
/// ```
pub fn has<'a>(&self, name: &'a str) -> bool {
self.get(name).is_some()
}
/// Set many headers.
///
/// ```
/// #[macro_use]
/// extern crate ureq;
///
/// fn main() {
/// let r = ureq::get("/my_page")
/// .set_map(map!{
/// "X-API-Key" => "foobar",
/// "Accept" => "application/json"
/// })
/// .call();
///
/// if r.ok() {
/// println!("yay got {}", r.into_json().unwrap());
/// }
/// }
/// ```
pub fn set_map<K, V, I>(&mut self, headers: I) -> &mut Request
where
K: Into<String>,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
for (k, v) in headers.into_iter() {
add_request_header(self, k.into(), v.into());
}
self
}
/// Set a query parameter.
///
/// For example, to set `?format=json&dest=/login`
///
/// ```
/// let r = ureq::get("/my_page")
/// .query("format", "json")
/// .query("dest", "/login")
/// .call();
///
/// println!("{:?}", r);
/// ```
pub fn query<K, V>(&mut self, param: K, value: V) -> &mut Request
where
K: Into<String>,
V: Into<String>,
{
self.query.add_pair((param.into(), value.into()));
self
}
/// Set many query parameters.
///
/// For example, to set `?format=json&dest=/login`
///
/// ```
/// #[macro_use]
/// extern crate ureq;
///
/// fn main() {
/// let r = ureq::get("/my_page")
/// .query_map(map!{
/// "format" => "json",
/// "dest" => "/login"
/// })
/// .call();
///
/// println!("{:?}", r);
/// }
/// ```
pub fn query_map<K, V, I>(&mut self, params: I) -> &mut Request
where
K: Into<String>,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
for (k, v) in params.into_iter() {
self.query.add_pair((k.into(), v.into()));
}
self
}
/// Set query parameters as a string.
///
/// For example, to set `?format=json&dest=/login`
///
/// ```
/// let r = ureq::get("/my_page")
/// .query_str("?format=json&dest=/login")
/// .call();
/// println!("{:?}", r);
/// ```
pub fn query_str<S>(&mut self, query: S) -> &mut Request
where
S: Into<String>,
{
let s = query.into();
self.query.add_str(&s);
self
}
/// Set the `Content-Type` header.
///
/// The default is `application/json`.
///
/// As a short-hand the `.content_type()` method accepts the
/// canonicalized MIME type name complete with
/// type/subtype, or simply the extension name such as
/// "xml", "json", "png".
///
/// These are all the same.
///
/// ```
/// ureq::post("/my_page")
/// .set("Content-Type", "text/plain")
/// .call();
///
/// ureq::post("/my_page")
/// .content_type("text/plain")
/// .call();
///
/// ureq::post("/my_page")
/// .content_type("txt")
/// .call();
/// ```
pub fn content_type<S>(&mut self, c: S) -> &mut Request
where
S: Into<String>,
{
self.set("Content-Type", mime_of(c))
}
/// Sets the `Accept` header in the same way as `content_type()`.
///
/// The short-hand `.accept()` method accepts the
/// canonicalized MIME type name complete with
/// type/subtype, or simply the extension name such as
/// "xml", "json", "png".
///
/// These are all the same.
///
/// ```
/// ureq::get("/my_page")
/// .set("Accept", "text/plain")
/// .call();
///
/// ureq::get("/my_page")
/// .accept("text/plain")
/// .call();
///
/// ureq::get("/my_page")
/// .accept("txt")
/// .call();
/// ```
pub fn accept<S>(&mut self, accept: S) -> &mut Request
where
S: Into<String>,
{
self.set("Accept", mime_of(accept))
}
/// Timeout for the socket connection to be successful.
///
/// The default is `0`, which means a request can block forever.
///
/// ```
/// let r = ureq::get("/my_page")
/// .timeout(1_000) // wait max 1 second to connect
/// .call();
/// println!("{:?}", r);
/// ```
pub fn timeout(&mut self, millis: u32) -> &mut Request {
self.timeout = millis;
self
}
/// Timeout for the individual reads of the socket.
///
/// The default is `0`, which means it can block forever.
///
/// ```
/// let r = ureq::get("/my_page")
/// .timeout_read(1_000) // wait max 1 second for the read
/// .call();
/// println!("{:?}", r);
/// ```
pub fn timeout_read(&mut self, millis: u32) -> &mut Request {
self.timeout_read = millis;
self
}
/// Timeout for the individual writes to the socket.
///
/// The default is `0`, which means it can block forever.
///
/// ```
/// let r = ureq::get("/my_page")
/// .timeout_write(1_000) // wait max 1 second for sending.
/// .call();
/// println!("{:?}", r);
/// ```
pub fn timeout_write(&mut self, millis: u32) -> &mut Request {
self.timeout_write = millis;
self
}
/// Basic auth.
///
/// These are the same
///
/// ```
/// let r1 = ureq::get("http://localhost/my_page")
/// .auth("martin", "rubbermashgum")
/// .call();
/// println!("{:?}", r1);
///
/// let r2 = ureq::get("http://martin:rubbermashgum@localhost/my_page").call();
/// println!("{:?}", r2);
/// ```
pub fn auth<S, T>(&mut self, user: S, pass: T) -> &mut Request
where
S: Into<String>,
T: Into<String>,
{
let u = user.into();
let p = pass.into();
let pass = basic_auth(&u, &p);
self.auth_kind("Basic", pass)
}
/// Auth of other kinds such as `Digest`, `Token` etc.
///
/// ```
/// let r = ureq::get("http://localhost/my_page")
/// .auth_kind("token", "secret")
/// .call();
/// println!("{:?}", r);
/// ```
pub fn auth_kind<S, T>(&mut self, kind: S, pass: T) -> &mut Request
where
S: Into<String>,
T: Into<String>,
{
self.auth = Some((kind.into(), pass.into()));
self
}
/// How many redirects to follow.
///
/// Defaults to `5`.
///
/// ```
/// let r = ureq::get("/my_page")
/// .redirects(10)
/// .call();
/// println!("{:?}", r);
/// ```
pub fn redirects(&mut self, n: u32) -> &mut Request {
self.redirects = n;
self
}
// pub fn retry(&self, times: u16) -> Request {
// unimplemented!()
// }
// pub fn sortQuery(&self) -> Request {
// unimplemented!()
// }
// pub fn sortQueryBy(&self, by: Box<Fn(&str, &str) -> usize>) -> Request {
// unimplemented!()
// }
// pub fn ca<S>(&self, accept: S) -> Request
// where S: Into<String> {
// unimplemented!()
// }
// pub fn cert<S>(&self, accept: S) -> Request
// where S: Into<String> {
// unimplemented!()
// }
// pub fn key<S>(&self, accept: S) -> Request
// where S: Into<String> {
// unimplemented!()
// }
// pub fn pfx<S>(&self, accept: S) -> Request // TODO what type? u8?
// where S: Into<String> {
// unimplemented!()
// }
fn to_url(&self) -> Result<Url, Error> {
URL_BASE
.join(&self.path)
.map_err(|e| Error::BadUrl(format!("{}", e)))
}
}
fn add_request_header(request: &mut Request, k: String, v: String) {
if let Ok(h) = Header::from_str(&format!("{}: {}", k, v)) {
request.headers.push(h)
}
}

277
src/response.rs Normal file
View File

@@ -0,0 +1,277 @@
use ascii::AsciiString;
use chunked_transfer;
use encoding::label::encoding_from_whatwg_label;
use encoding::DecoderTrap;
use std::io::Error as IoError;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Result as IoResult;
use error::Error;
const DEFAULT_CONTENT_TYPE: &'static str = "text/plain";
const DEFAULT_CHARACTER_SET: &'static str = "utf-8";
// buffered
// "text/", "/json", or "x-www-form-urlencoded"
pub struct Response {
status_line: AsciiString,
index: (usize, usize), // index into status_line where we split: HTTP/1.1 200 OK
status: u16,
headers: Vec<Header>,
reader: Option<Box<Read + Send + 'static>>,
}
impl ::std::fmt::Debug for Response {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {
write!(
f,
"Response[status: {}, status_text: {}]",
self.status(),
self.status_text()
)
}
}
impl Response {
/// The entire status line like: HTTP/1.1 200 OK
pub fn status_line(&self) -> &str {
self.status_line.as_str()
}
/// The http version: HTTP/1.1
pub fn http_version(&self) -> &str {
&self.status_line.as_str()[0..self.index.0]
}
/// The status as a u16: 200
pub fn status(&self) -> &u16 {
&self.status
}
/// The status text: OK
pub fn status_text(&self) -> &str {
&self.status_line.as_str()[self.index.1 + 1..].trim()
}
/// The header corresponding header value for the give name, if any.
pub fn get<'a>(&self, name: &'a str) -> Option<&str> {
self.headers
.iter()
.find(|h| h.is_name(name))
.map(|h| h.value())
}
/// Tells if the response has the named header.
pub fn has<'a>(&self, name: &'a str) -> bool {
self.get(name).is_some()
}
/// All headers corresponding values for the give name, or empty vector.
pub fn get_all<'a>(&self, name: &'a str) -> Vec<&str> {
self.headers
.iter()
.filter(|h| h.is_name(name))
.map(|h| h.value())
.collect()
}
/// Whether the response status is: 200 <= status <= 299
pub fn ok(&self) -> bool {
self.status >= 200 && self.status <= 299
}
pub fn redirect(&self) -> bool {
self.status >= 300 && self.status <= 399
}
/// Whether the response status is: 400 <= status <= 499
pub fn client_error(&self) -> bool {
self.status >= 400 && self.status <= 499
}
/// Whether the response status is: 500 <= status <= 599
pub fn server_error(&self) -> bool {
self.status >= 500 && self.status <= 599
}
/// Whether the response status is: 400 <= status <= 599
pub fn error(&self) -> bool {
self.client_error() || self.server_error()
}
/// The content type part of the "Content-Type" header without
/// the charset.
///
/// Example:
///
/// ```
/// let resp = ureq::get("https://www.google.com/").call();
/// assert_eq!("text/html; charset=ISO-8859-1", resp.get("content-type").unwrap());
/// assert_eq!("text/html", resp.content_type());
/// ```
pub fn content_type(&self) -> &str {
self.get("content-type")
.map(|header| {
header
.find(";")
.map(|index| &header[0..index])
.unwrap_or(header)
})
.unwrap_or(DEFAULT_CONTENT_TYPE)
}
pub fn charset(&self) -> &str {
self.get("content-type")
.and_then(|header| {
header.find(";").and_then(|semi| {
(&header[semi + 1..])
.find("=")
.map(|equal| (&header[semi + equal + 2..]).trim())
})
})
.unwrap_or(DEFAULT_CHARACTER_SET)
}
pub fn into_reader(self) -> impl Read {
let is_chunked = self.get("transfer-encoding")
.map(|enc| enc.eq_ignore_ascii_case("chunked"))
.unwrap_or(false);
let reader = self.reader.expect("No reader in response?!");
match is_chunked {
true => Box::new(chunked_transfer::Decoder::new(reader)),
false => reader,
}
}
pub fn into_string(self) -> IoResult<String> {
let encoding = encoding_from_whatwg_label(self.charset())
.or_else(|| encoding_from_whatwg_label(DEFAULT_CHARACTER_SET))
.unwrap();
let mut buf: Vec<u8> = vec![];
self.into_reader().read_to_end(&mut buf)?;
Ok(encoding.decode(&buf, DecoderTrap::Replace).unwrap())
}
pub fn into_json(self) -> IoResult<serde_json::Value> {
let reader = self.into_reader();
serde_json::from_reader(reader).map_err(|e| {
IoError::new(
ErrorKind::InvalidData,
format!("Failed to read JSON: {}", e),
)
})
}
pub fn new(status: u16, status_text: &str, body: &str) -> Self {
let r = format!("HTTP/1.1 {} {}\r\n\r\n{}\n", status, status_text, body);
(r.as_ref() as &str)
.parse::<Response>()
.unwrap_or_else(|e| e.into())
}
pub fn from_read(reader: impl Read) -> Self
{
Self::do_from_read(reader).unwrap_or_else(|e| e.into())
}
fn do_from_read(mut reader: impl Read) -> Result<Response, Error>
{
//
// HTTP/1.1 200 OK\r\n
let status_line = read_next_line(&mut reader).map_err(|_| Error::BadStatus)?;
let (index, status) = parse_status_line(status_line.as_str())?;
let mut headers: Vec<Header> = Vec::new();
loop {
let line = read_next_line(&mut reader).map_err(|_| Error::BadHeader)?;
if line.len() == 0 {
break;
}
if let Ok(header) = line.as_str().parse::<Header>() {
headers.push(header);
}
}
Ok(Response {
status_line,
index,
status,
headers,
reader: None,
})
}
fn set_reader<R>(&mut self, reader: R) where R: Read + Send + 'static {
self.reader = Some(Box::new(reader));
}
}
fn parse_status_line(line: &str) -> Result<((usize, usize), u16), Error> {
// HTTP/1.1 200 OK\r\n
let mut split = line.splitn(3, ' ');
let http_version = split.next().ok_or_else(|| Error::BadStatus)?;
if http_version.len() < 5 {
return Err(Error::BadStatus);
}
let index1 = http_version.len();
let status = split.next().ok_or_else(|| Error::BadStatus)?;
if status.len() < 3 {
return Err(Error::BadStatus);
}
let index2 = index1 + status.len();
let status = status.parse::<u16>().map_err(|_| Error::BadStatus)?;
let status_text = split.next().ok_or_else(|| Error::BadStatus)?;
if status_text.len() == 0 {
return Err(Error::BadStatus);
}
Ok(((index1, index2), status))
}
impl FromStr for Response {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut read = VecRead::from_str(s);
let mut resp = Self::do_from_read(&mut read)?;
resp.set_reader(read);
Ok(resp)
}
}
impl Into<Response> for Error {
fn into(self) -> Response {
Response::new(self.status(), self.status_text(), &self.body_text())
}
}
// application/x-www-form-urlencoded, application/json, and multipart/form-data
fn read_next_line<R: Read>(reader: &mut R) -> IoResult<AsciiString> {
let mut buf = Vec::new();
let mut prev_byte_was_cr = false;
loop {
let byte = reader.bytes().next();
let byte = match byte {
Some(b) => try!(b),
None => return Err(IoError::new(ErrorKind::ConnectionAborted, "Unexpected EOF")),
};
if byte == b'\n' && prev_byte_was_cr {
buf.pop(); // removing the '\r'
return AsciiString::from_ascii(buf)
.map_err(|_| IoError::new(ErrorKind::InvalidInput, "Header is not in ASCII"));
}
prev_byte_was_cr = byte == b'\r';
buf.push(byte);
}
}

34
src/stream.rs Normal file
View File

@@ -0,0 +1,34 @@
use rustls;
use std::io::Read;
use std::io::Result;
use std::io::Write;
use std::net::TcpStream;
pub enum Stream {
Http(TcpStream),
Https(rustls::ClientSession, TcpStream),
}
impl Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self {
Stream::Http(sock) => sock.read(buf),
Stream::Https(sess, sock) => rustls::Stream::new(sess, sock).read(buf),
}
}
}
impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
match self {
Stream::Http(sock) => sock.write(buf),
Stream::Https(sess, sock) => rustls::Stream::new(sess, sock).write(buf),
}
}
fn flush(&mut self) -> Result<()> {
match self {
Stream::Http(sock) => sock.flush(),
Stream::Https(sess, sock) => rustls::Stream::new(sess, sock).flush(),
}
}
}

10
src/util/macros.rs Normal file
View File

@@ -0,0 +1,10 @@
#[macro_export]
macro_rules! map(
{ $($key:expr => $value:expr),* } => {
{
let mut m = ::std::collections::HashMap::new();
$(m.insert($key, $value);)+
m
}
};
);

29
src/util/mod.rs Normal file
View File

@@ -0,0 +1,29 @@
#[allow(dead_code)]
mod macros;
mod serde_macros;
mod vecread;
use base64;
use mime_guess::get_mime_type_str;
pub use util::vecread::VecRead;
pub fn basic_auth(user: &str, pass: &str) -> String {
let safe = match user.find(":") {
Some(idx) => &user[..idx],
None => user,
};
base64::encode(&format!("{}:{}", safe, pass))
}
pub fn mime_of<S: Into<String>>(s: S) -> String {
let s = s.into();
match &s[..] {
"json" => "application/json",
"form" => "application/x-www-form-urlencoded",
_ => match get_mime_type_str(&s) {
Some(mime) => mime,
None => "foo",
},
}.to_string()
}

286
src/util/serde_macros.rs Normal file
View File

@@ -0,0 +1,286 @@
// this file is borrowed in its entirety until macro_reexport stabilizes.
// https://github.com/rust-lang/rust/issues/29638
// Copyright 2017 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/// Construct a `serde_json::Value` from a JSON literal.
///
/// ```rust
/// # #[macro_use]
/// # extern crate serde_json;
/// #
/// # fn main() {
/// let value = json!({
/// "code": 200,
/// "success": true,
/// "payload": {
/// "features": [
/// "serde",
/// "json"
/// ]
/// }
/// });
/// # }
/// ```
///
/// Variables or expressions can be interpolated into the JSON literal. Any type
/// interpolated into an array element or object value must implement Serde's
/// `Serialize` trait, while any type interpolated into a object key must
/// implement `Into<String>`. If the `Serialize` implementation of the
/// interpolated type decides to fail, or if the interpolated type contains a
/// map with non-string keys, the `json!` macro will panic.
///
/// ```rust
/// # #[macro_use]
/// # extern crate serde_json;
/// #
/// # fn main() {
/// let code = 200;
/// let features = vec!["serde", "json"];
///
/// let value = json!({
/// "code": code,
/// "success": code == 200,
/// "payload": {
/// features[0]: features[1]
/// }
/// });
/// # }
/// ```
///
/// Trailing commas are allowed inside both arrays and objects.
///
/// ```rust
/// # #[macro_use]
/// # extern crate serde_json;
/// #
/// # fn main() {
/// let value = json!([
/// "notice",
/// "the",
/// "trailing",
/// "comma -->",
/// ]);
/// # }
/// ```
#[macro_export]
macro_rules! json {
// Hide distracting implementation details from the generated rustdoc.
($($json:tt)+) => {
json_internal!($($json)+)
};
}
// Rocket relies on this because they export their own `json!` with a different
// doc comment than ours, and various Rust bugs prevent them from calling our
// `json!` from their `json!` so they call `json_internal!` directly. Check with
// @SergioBenitez before making breaking changes to this macro.
//
// Changes are fine as long as `json_internal!` does not call any new helper
// macros and can still be invoked as `json_internal!($($json)+)`.
#[macro_export]
#[doc(hidden)]
macro_rules! json_internal {
//////////////////////////////////////////////////////////////////////////
// TT muncher for parsing the inside of an array [...]. Produces a vec![...]
// of the elements.
//
// Must be invoked as: json_internal!(@array [] $($tt)*)
//////////////////////////////////////////////////////////////////////////
// Done with trailing comma.
(@array [$($elems:expr,)*]) => {
vec![$($elems,)*]
};
// Done without trailing comma.
(@array [$($elems:expr),*]) => {
vec![$($elems),*]
};
// Next element is `null`.
(@array [$($elems:expr,)*] null $($rest:tt)*) => {
json_internal!(@array [$($elems,)* json_internal!(null)] $($rest)*)
};
// Next element is `true`.
(@array [$($elems:expr,)*] true $($rest:tt)*) => {
json_internal!(@array [$($elems,)* json_internal!(true)] $($rest)*)
};
// Next element is `false`.
(@array [$($elems:expr,)*] false $($rest:tt)*) => {
json_internal!(@array [$($elems,)* json_internal!(false)] $($rest)*)
};
// Next element is an array.
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
json_internal!(@array [$($elems,)* json_internal!([$($array)*])] $($rest)*)
};
// Next element is a map.
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
json_internal!(@array [$($elems,)* json_internal!({$($map)*})] $($rest)*)
};
// Next element is an expression followed by comma.
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
json_internal!(@array [$($elems,)* json_internal!($next),] $($rest)*)
};
// Last element is an expression with no trailing comma.
(@array [$($elems:expr,)*] $last:expr) => {
json_internal!(@array [$($elems,)* json_internal!($last)])
};
// Comma after the most recent element.
(@array [$($elems:expr),*] , $($rest:tt)*) => {
json_internal!(@array [$($elems,)*] $($rest)*)
};
//////////////////////////////////////////////////////////////////////////
// TT muncher for parsing the inside of an object {...}. Each entry is
// inserted into the given map variable.
//
// Must be invoked as: json_internal!(@object $map () ($($tt)*) ($($tt)*))
//
// We require two copies of the input tokens so that we can match on one
// copy and trigger errors on the other copy.
//////////////////////////////////////////////////////////////////////////
// Done.
(@object $object:ident () () ()) => {};
// Insert the current entry followed by trailing comma.
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
$object.insert(($($key)+).into(), $value);
json_internal!(@object $object () ($($rest)*) ($($rest)*));
};
// Insert the last entry without trailing comma.
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
$object.insert(($($key)+).into(), $value);
};
// Next value is `null`.
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!(null)) $($rest)*);
};
// Next value is `true`.
(@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!(true)) $($rest)*);
};
// Next value is `false`.
(@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!(false)) $($rest)*);
};
// Next value is an array.
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!([$($array)*])) $($rest)*);
};
// Next value is a map.
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!({$($map)*})) $($rest)*);
};
// Next value is an expression followed by comma.
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!($value)) , $($rest)*);
};
// Last value is an expression with no trailing comma.
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
json_internal!(@object $object [$($key)+] (json_internal!($value)));
};
// Missing value for last entry. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
// "unexpected end of macro invocation"
json_internal!();
};
// Missing colon and value for last entry. Trigger a reasonable error
// message.
(@object $object:ident ($($key:tt)+) () $copy:tt) => {
// "unexpected end of macro invocation"
json_internal!();
};
// Misplaced colon. Trigger a reasonable error message.
(@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `:`".
unimplemented!($colon);
};
// Found a comma inside a key. Trigger a reasonable error message.
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `,`".
unimplemented!($comma);
};
// Key is fully parenthesized. This avoids clippy double_parens false
// positives because the parenthesization may be necessary here.
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*));
};
// Munch a token into the current key.
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
json_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
};
//////////////////////////////////////////////////////////////////////////
// The main implementation.
//
// Must be invoked as: json_internal!($($json)+)
//////////////////////////////////////////////////////////////////////////
(null) => {
$crate::Value::Null
};
(true) => {
$crate::Value::Bool(true)
};
(false) => {
$crate::Value::Bool(false)
};
([]) => {
$crate::Value::Array(vec![])
};
([ $($tt:tt)+ ]) => {
$crate::Value::Array(json_internal!(@array [] $($tt)+))
};
({}) => {
$crate::Value::Object($crate::Map::new())
};
({ $($tt:tt)+ }) => {
$crate::Value::Object({
let mut object = $crate::Map::new();
json_internal!(@object object () ($($tt)+) ($($tt)+));
object
})
};
// Any Serialize type: numbers, strings, struct literals, variables etc.
// Must be below every other rule.
($other:expr) => {
$crate::to_value(&$other).unwrap()
};
}

34
src/util/vecread.rs Normal file
View File

@@ -0,0 +1,34 @@
use std::io::Read;
use std::io::Result;
pub struct VecRead {
bytes: Vec<u8>,
index: usize,
}
impl VecRead {
pub fn new(bytes: &[u8]) -> Self {
Self::from_vec(bytes.to_owned())
}
pub fn from_vec(bytes: Vec<u8>) -> Self {
VecRead {
bytes,
index: 0,
}
}
pub fn from_str(s: &str) -> Self {
Self::new(s.as_bytes())
}
pub fn len(&self) -> usize {
self.bytes.len()
}
}
impl Read for VecRead {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let len = buf.len().min(self.bytes.len() - self.index);
(&mut buf[0..len]).copy_from_slice(&self.bytes[self.index..self.index + len]);
self.index += len;
Ok(len)
}
}