mirror of
https://github.com/stoatchat/rust-a2.git
synced 2026-06-30 21:47:56 -04:00
v0.7 (#63)
This commit is contained in:
@@ -36,6 +36,10 @@ jobs:
|
||||
cmd: clippy
|
||||
args: -- -D clippy::all
|
||||
cache: {}
|
||||
- name: "Clippy (ring feature)"
|
||||
cmd: clippy
|
||||
args: --no-default-features --features ring -- -D clippy::all
|
||||
cache: {}
|
||||
- name: "Formatting"
|
||||
cmd: fmt
|
||||
args: -- --check
|
||||
@@ -44,6 +48,10 @@ jobs:
|
||||
cmd: test
|
||||
args: --all-features
|
||||
cache: { sharedKey: "tests" }
|
||||
- name: "Unit Tests (ring feature)"
|
||||
cmd: test
|
||||
args: --no-default-features --features ring
|
||||
cache: { sharedKey: "tests-ring" }
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
sccache-path: /home/runner/.cache/sccache
|
||||
|
||||
+4
-6
@@ -13,13 +13,11 @@
|
||||
# Generated by Cargo
|
||||
/target/
|
||||
|
||||
# IntelliJ IDEA
|
||||
/.idea/
|
||||
# Editors
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
Cargo.lock
|
||||
.vscode
|
||||
.atom
|
||||
|
||||
# Certificate files
|
||||
*.der
|
||||
|
||||
Generated
+923
@@ -0,0 +1,923 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "a2"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"argparse",
|
||||
"base64 0.20.0",
|
||||
"erased-serde",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-alpn",
|
||||
"openssl",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argparse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f8ebf5827e4ac4fd5946560e6a99776ea73b596d80898f357007317a7141e47"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-alpn"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f485f87658b5ac391dbed0379d50928de16b54bffee0a85234dc6a92fbe534f6"
|
||||
dependencies = [
|
||||
"hyper",
|
||||
"log",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
+17
-12
@@ -4,7 +4,7 @@ version = "0.6.2"
|
||||
authors = [
|
||||
"Harry Bairstow <harry@walletconnect.com>",
|
||||
"Julius de Bruijn <julius@nauk.io>",
|
||||
"Sergey Tkachenko <seriy.tkachenko@gmail.com>",
|
||||
"Sergey Tkachenko <seriy.tkachenko@gmail.com>",
|
||||
]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
@@ -13,24 +13,29 @@ keywords = ["apns", "apple", "push", "async", "http2"]
|
||||
repository = "https://github.com/walletconnect/a2.git"
|
||||
homepage = "https://github.com/walletconnect/a2"
|
||||
documentation = "https://docs.rs/a2"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["openssl"]
|
||||
tracing = ["dep:tracing"]
|
||||
ring = ["dep:ring", "pem"]
|
||||
|
||||
[dependencies]
|
||||
serde = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
erased-serde = "0.3"
|
||||
serde_derive = "1"
|
||||
serde_json = "1"
|
||||
thiserror = "1"
|
||||
openssl = "0.10"
|
||||
futures = "0.3"
|
||||
openssl = { version = "0.10", optional = true }
|
||||
hyper = { version = "0.14", default-features = false, features = ["client", "http2"] }
|
||||
hyper-alpn = "0.4"
|
||||
http = "0.2"
|
||||
base64 = "0.13"
|
||||
log = "0.4"
|
||||
hyper = { version = "0.14", features = ["client", "http2", "tcp"] }
|
||||
hyper-alpn = "0.3"
|
||||
base64 = "0.20"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
pem = { version = "1.0", optional = true }
|
||||
ring = { version = "0.16", features = ["std"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
argparse = "0.2"
|
||||
pretty_env_logger = "0.4"
|
||||
indoc = "1"
|
||||
tracing-subscriber = "0.3"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
hyper = { version = "0.14", features = ["client", "http2", "tcp"] }
|
||||
|
||||
@@ -8,7 +8,7 @@ HTTP/2 Apple Push Notification Service for Rust using Tokio and async sending.
|
||||
|
||||
## Requirements
|
||||
|
||||
Needs a Tokio executor version 0.2 or later and Rust compiler version 1.39.0 or later.
|
||||
Needs a Tokio executor version 1.0 or later and Rust compiler version 1.60.0 or later.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -27,6 +27,8 @@ Needs a Tokio executor version 0.2 or later and Rust compiler version 1.39.0 or
|
||||
* Supports `.p8` private keys to connect using authentication tokens.
|
||||
* If using authentication tokens, handles signature renewing for Apple's guidelines
|
||||
and caching for maximum performance.
|
||||
* Cryptography primitives are provided either by openssl or
|
||||
[ring](https://github.com/briansmith/ring).
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use a2::{Client, Endpoint, NotificationBuilder, NotificationOptions, PlainNotificationBuilder};
|
||||
use a2::{Client, DefaultNotificationBuilder, NotificationBuilder, NotificationOptions};
|
||||
use argparse::{ArgumentParser, Store, StoreOption, StoreTrue};
|
||||
use pretty_env_logger;
|
||||
use std::fs::File;
|
||||
use tokio;
|
||||
|
||||
// An example client connectiong to APNs with a certificate and key
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::init();
|
||||
tracing_subscriber::fmt().init();
|
||||
|
||||
let mut certificate_file = String::new();
|
||||
let mut password = String::new();
|
||||
@@ -34,18 +32,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
ap.parse_args_or_exit();
|
||||
}
|
||||
|
||||
// Read the private key and certificate from the disk
|
||||
let mut certificate = File::open(certificate_file).unwrap();
|
||||
|
||||
// Which service to call, test or production?
|
||||
let endpoint = if sandbox {
|
||||
Endpoint::Sandbox
|
||||
} else {
|
||||
Endpoint::Production
|
||||
};
|
||||
|
||||
// Connecting to APNs using a client certificate
|
||||
let client = Client::certificate(&mut certificate, &password, endpoint).unwrap();
|
||||
let new_client = || -> Result<Client, Box<dyn std::error::Error + Sync + Send>> {
|
||||
#[cfg(feature = "openssl")]
|
||||
{
|
||||
// Which service to call, test or production?
|
||||
let endpoint = if sandbox {
|
||||
a2::Endpoint::Sandbox
|
||||
} else {
|
||||
a2::Endpoint::Production
|
||||
};
|
||||
|
||||
let mut certificate = std::fs::File::open(certificate_file)?;
|
||||
Ok(Client::certificate(&mut certificate, &password, endpoint)?)
|
||||
}
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
{
|
||||
Err("ring does not support loading of certificates".into())
|
||||
}
|
||||
};
|
||||
let client = new_client()?;
|
||||
|
||||
let options = NotificationOptions {
|
||||
apns_topic: topic.as_ref().map(|s| &**s),
|
||||
@@ -53,9 +59,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
};
|
||||
|
||||
// Notification payload
|
||||
let mut builder = PlainNotificationBuilder::new(message.as_ref());
|
||||
builder.set_sound("default");
|
||||
builder.set_badge(1u32);
|
||||
let builder = DefaultNotificationBuilder::new()
|
||||
.set_body(message.as_ref())
|
||||
.set_sound("default")
|
||||
.set_badge(1u32);
|
||||
|
||||
let payload = builder.build(device_token.as_ref(), options);
|
||||
let response = client.send(payload).await?;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use argparse::{ArgumentParser, Store, StoreOption, StoreTrue};
|
||||
use pretty_env_logger;
|
||||
use std::fs::File;
|
||||
use tokio;
|
||||
|
||||
use a2::{Client, Endpoint, NotificationBuilder, NotificationOptions, PlainNotificationBuilder};
|
||||
use a2::{Client, DefaultNotificationBuilder, Endpoint, NotificationBuilder, NotificationOptions};
|
||||
|
||||
// An example client connectiong to APNs with a JWT token
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::init();
|
||||
tracing_subscriber::fmt().init();
|
||||
|
||||
let mut key_file = String::new();
|
||||
let mut team_id = String::new();
|
||||
@@ -57,9 +56,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
};
|
||||
|
||||
// Notification payload
|
||||
let mut builder = PlainNotificationBuilder::new(message.as_ref());
|
||||
builder.set_sound("default");
|
||||
builder.set_badge(1u32);
|
||||
let builder = DefaultNotificationBuilder::new()
|
||||
.set_body(message.as_ref())
|
||||
.set_sound("default")
|
||||
.set_badge(1u32);
|
||||
|
||||
let payload = builder.build(device_token.as_ref(), options);
|
||||
let response = client.send(payload).await?;
|
||||
|
||||
+54
-55
@@ -9,11 +9,9 @@ use crate::request::payload::Payload;
|
||||
use crate::response::Response;
|
||||
use http::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use hyper::{self, Body, Client as HttpClient, StatusCode};
|
||||
use openssl::pkcs12::Pkcs12;
|
||||
use std::future::Future;
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
use std::time::Duration;
|
||||
use std::{fmt, str};
|
||||
|
||||
/// The APNs service endpoint to connect.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -43,6 +41,7 @@ impl fmt::Display for Endpoint {
|
||||
/// the notification and responds with a status OK. In any other case the future
|
||||
/// fails. If APNs gives a reason for the failure, the returned `Err`
|
||||
/// holds the response for handling.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
endpoint: Endpoint,
|
||||
signer: Option<Signer>,
|
||||
@@ -65,6 +64,9 @@ impl Client {
|
||||
/// Create a connection to APNs using the provider client certificate which
|
||||
/// you obtain from your [Apple developer
|
||||
/// account](https://developer.apple.com/account/).
|
||||
///
|
||||
/// Only works with the `openssl` feature.
|
||||
#[cfg(feature = "openssl")]
|
||||
pub fn certificate<R>(certificate: &mut R, password: &str, endpoint: Endpoint) -> Result<Client, Error>
|
||||
where
|
||||
R: Read,
|
||||
@@ -72,7 +74,7 @@ impl Client {
|
||||
let mut cert_der: Vec<u8> = Vec::new();
|
||||
certificate.read_to_end(&mut cert_der)?;
|
||||
|
||||
let pkcs = Pkcs12::from_der(&cert_der)?.parse(password)?;
|
||||
let pkcs = openssl::pkcs12::Pkcs12::from_der(&cert_der)?.parse(password)?;
|
||||
let connector = AlpnConnector::with_client_cert(&pkcs.cert.to_pem()?, &pkcs.pkey.private_key_to_pem_pkcs8()?)?;
|
||||
|
||||
Ok(Self::new(connector, None, endpoint))
|
||||
@@ -98,34 +100,33 @@ impl Client {
|
||||
/// Send a notification payload.
|
||||
///
|
||||
/// See [ErrorReason](enum.ErrorReason.html) for possible errors.
|
||||
pub fn send(&self, payload: Payload<'_>) -> impl Future<Output = Result<Response, Error>> + 'static {
|
||||
#[cfg_attr(feature = "tracing", ::tracing::instrument)]
|
||||
pub async fn send(&self, payload: Payload<'_>) -> Result<Response, Error> {
|
||||
let request = self.build_request(payload);
|
||||
let requesting = self.http_client.request(request);
|
||||
|
||||
async move {
|
||||
let response = requesting.await?;
|
||||
let response = requesting.await?;
|
||||
|
||||
let apns_id = response
|
||||
.headers()
|
||||
.get("apns-id")
|
||||
.and_then(|s| s.to_str().ok())
|
||||
.map(String::from);
|
||||
let apns_id = response
|
||||
.headers()
|
||||
.get("apns-id")
|
||||
.and_then(|s| s.to_str().ok())
|
||||
.map(String::from);
|
||||
|
||||
match response.status() {
|
||||
StatusCode::OK => Ok(Response {
|
||||
match response.status() {
|
||||
StatusCode::OK => Ok(Response {
|
||||
apns_id,
|
||||
error: None,
|
||||
code: response.status().as_u16(),
|
||||
}),
|
||||
status => {
|
||||
let body = hyper::body::to_bytes(response).await?;
|
||||
|
||||
Err(ResponseError(Response {
|
||||
apns_id,
|
||||
error: None,
|
||||
code: response.status().as_u16(),
|
||||
}),
|
||||
status => {
|
||||
let body = hyper::body::to_bytes(response).await?;
|
||||
|
||||
Err(ResponseError(Response {
|
||||
apns_id,
|
||||
error: serde_json::from_slice(&body).ok(),
|
||||
code: status.as_u16(),
|
||||
}))
|
||||
}
|
||||
error: serde_json::from_slice(&body).ok(),
|
||||
code: status.as_u16(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +142,7 @@ impl Client {
|
||||
if let Some(ref apns_priority) = payload.options.apns_priority {
|
||||
builder = builder.header("apns-priority", apns_priority.to_string().as_bytes());
|
||||
}
|
||||
if let Some(ref apns_id) = payload.options.apns_id {
|
||||
if let Some(apns_id) = payload.options.apns_id {
|
||||
builder = builder.header("apns-id", apns_id.as_bytes());
|
||||
}
|
||||
if let Some(ref apns_expiration) = payload.options.apns_expiration {
|
||||
@@ -150,7 +151,7 @@ impl Client {
|
||||
if let Some(ref apns_collapse_id) = payload.options.apns_collapse_id {
|
||||
builder = builder.header("apns-collapse-id", apns_collapse_id.value.to_string().as_bytes());
|
||||
}
|
||||
if let Some(ref apns_topic) = payload.options.apns_topic {
|
||||
if let Some(apns_topic) = payload.options.apns_topic {
|
||||
builder = builder.header("apns-topic", apns_topic.as_bytes());
|
||||
}
|
||||
if let Some(ref signer) = self.signer {
|
||||
@@ -172,25 +173,23 @@ impl Client {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::request::notification::DefaultNotificationBuilder;
|
||||
use crate::request::notification::NotificationBuilder;
|
||||
use crate::request::notification::PlainNotificationBuilder;
|
||||
use crate::request::notification::{CollapseId, NotificationOptions, Priority};
|
||||
use crate::signer::Signer;
|
||||
use http::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use hyper::Method;
|
||||
use hyper_alpn::AlpnConnector;
|
||||
|
||||
const PRIVATE_KEY: &'static str = indoc!(
|
||||
"-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8g/n6j9roKvnUkwu
|
||||
lCEIvbDqlUhA5FOzcakkG90E8L+hRANCAATKS2ZExEybUvchRDuKBftotMwVEus3
|
||||
jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ
|
||||
-----END PRIVATE KEY-----"
|
||||
);
|
||||
const PRIVATE_KEY: &'static str = "-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8g/n6j9roKvnUkwu
|
||||
lCEIvbDqlUhA5FOzcakkG90E8L+hRANCAATKS2ZExEybUvchRDuKBftotMwVEus3
|
||||
jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ
|
||||
-----END PRIVATE KEY-----";
|
||||
|
||||
#[test]
|
||||
fn test_production_request_uri() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload);
|
||||
@@ -201,7 +200,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sandbox_request_uri() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Sandbox);
|
||||
let request = client.build_request(payload);
|
||||
@@ -212,7 +211,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_method() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload);
|
||||
@@ -222,7 +221,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_content_type() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload);
|
||||
@@ -232,7 +231,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_content_length() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload.clone());
|
||||
@@ -244,7 +243,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_authorization_with_no_signer() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload);
|
||||
@@ -262,7 +261,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), Some(signer), Endpoint::Production);
|
||||
let request = client.build_request(payload);
|
||||
@@ -272,7 +271,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_default_priority() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload);
|
||||
@@ -283,7 +282,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_normal_priority() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build(
|
||||
"a_test_id",
|
||||
@@ -302,7 +301,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_high_priority() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build(
|
||||
"a_test_id",
|
||||
@@ -321,7 +320,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_default_apns_id() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
|
||||
@@ -334,7 +333,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_an_apns_id() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build(
|
||||
"a_test_id",
|
||||
@@ -353,7 +352,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_default_apns_expiration() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
|
||||
@@ -366,7 +365,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_an_apns_expiration() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build(
|
||||
"a_test_id",
|
||||
@@ -385,7 +384,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_default_apns_collapse_id() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
|
||||
@@ -398,7 +397,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_an_apns_collapse_id() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build(
|
||||
"a_test_id",
|
||||
@@ -417,7 +416,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_default_apns_topic() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
|
||||
@@ -430,7 +429,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_with_an_apns_topic() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
|
||||
let payload = builder.build(
|
||||
"a_test_id",
|
||||
@@ -449,7 +448,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_request_body() {
|
||||
let builder = PlainNotificationBuilder::new("test");
|
||||
let builder = DefaultNotificationBuilder::new();
|
||||
let payload = builder.build("a_test_id", Default::default());
|
||||
let client = Client::new(AlpnConnector::new(), None, Endpoint::Production);
|
||||
let request = client.build_request(payload.clone());
|
||||
|
||||
+14
-2
@@ -1,6 +1,6 @@
|
||||
//! Error and result module
|
||||
|
||||
use crate::response::Response;
|
||||
use crate::{response::Response, signer::SignerError};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -16,7 +16,7 @@ pub enum Error {
|
||||
|
||||
/// Couldn't generate an APNs token with the given key.
|
||||
#[error("Error creating a signature: {0}")]
|
||||
SignerError(#[from] openssl::error::ErrorStack),
|
||||
SignerError(#[from] SignerError),
|
||||
|
||||
/// APNs couldn't accept the notification. Contains
|
||||
/// [Response](response/struct.Response.html) with additional
|
||||
@@ -38,4 +38,16 @@ pub enum Error {
|
||||
/// Error reading the certificate or private key.
|
||||
#[error("Error in reading a certificate file: {0}")]
|
||||
ReadError(#[from] io::Error),
|
||||
|
||||
/// Unexpected private key (only EC keys are supported).
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
#[error("Unexpected private key: {0}")]
|
||||
UnexpectedKey(#[from] ring::error::KeyRejected),
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
||||
Self::SignerError(SignerError::OpenSSL(e))
|
||||
}
|
||||
}
|
||||
|
||||
+20
-23
@@ -13,11 +13,8 @@
|
||||
//!
|
||||
//! ## Payload
|
||||
//!
|
||||
//! Building the notification payload should be done with the corresponding builders:
|
||||
//!
|
||||
//! * [PlainNotificationBuilder](request/notification/struct.PlainNotificationBuilder.html) for text only messages.
|
||||
//! * [SilentNotificationBuilder](request/notification/struct.SilentNotificationBuilder.html) for silent notifications with custom data.
|
||||
//! * [LocalizedNotificationBuilder](request/notification/struct.LocalizedNotificationBuilder.html) for localized rich notifications.
|
||||
//! Building the notification payload should be done with the [DefaultNotificationBuilder](request/notification/struct.DefaultNotificationBuilder.html) for most use-cases.
|
||||
//! There is also the [WebNotificationBuilder](request/notification/struct.WebNotificationBuilder.html) in the case you need to send notifications to safari
|
||||
//!
|
||||
//! The payload generated by the builder [can hold a custom data
|
||||
//! section](request/payload/struct.Payload.html#method.add_custom_data),
|
||||
@@ -34,14 +31,15 @@
|
||||
//! ## Example sending a plain notification using token authentication:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use a2::{PlainNotificationBuilder, NotificationBuilder, Client, Endpoint};
|
||||
//! # use a2::{DefaultNotificationBuilder, NotificationBuilder, Client, Endpoint};
|
||||
//! # use std::fs::File;
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
//! let mut builder = PlainNotificationBuilder::new("Hi there");
|
||||
//! builder.set_badge(420);
|
||||
//! builder.set_category("cat1");
|
||||
//! builder.set_sound("ping.flac");
|
||||
//! let mut builder = DefaultNotificationBuilder::new()
|
||||
//! .set_body("Hi there")
|
||||
//! .set_badge(420)
|
||||
//! .set_category("cat1")
|
||||
//! .set_sound("ping.flac");
|
||||
//!
|
||||
//! let payload = builder.build("device-token-from-the-user", Default::default());
|
||||
//! let mut file = File::open("/path/to/private_key.p8")?;
|
||||
@@ -61,10 +59,12 @@
|
||||
//! ## Example sending a silent notification with custom data using certificate authentication:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! #[macro_use] extern crate serde_derive;
|
||||
//! #[macro_use] extern crate serde;
|
||||
//! # #[cfg(all(feature = "openssl", not(feature = "ring")))]
|
||||
//! # {
|
||||
//!
|
||||
//! use a2::{
|
||||
//! Client, Endpoint, SilentNotificationBuilder, NotificationBuilder, NotificationOptions,
|
||||
//! Client, Endpoint, DefaultNotificationBuilder, NotificationBuilder, NotificationOptions,
|
||||
//! Priority,
|
||||
//! };
|
||||
//! use std::fs::File;
|
||||
@@ -82,7 +82,8 @@
|
||||
//! is_paying_user: false,
|
||||
//! };
|
||||
//!
|
||||
//! let mut payload = SilentNotificationBuilder::new()
|
||||
//! let mut payload = DefaultNotificationBuilder::new()
|
||||
//! .set_content_available()
|
||||
//! .build("device-token-from-the-user",
|
||||
//! NotificationOptions {
|
||||
//! apns_priority: Some(Priority::Normal),
|
||||
@@ -103,22 +104,18 @@
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
#[cfg(not(any(feature = "openssl", feature = "ring")))]
|
||||
compile_error!("either feature \"openssl\" or feature \"ring\" has to be enabled");
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
pub mod request;
|
||||
@@ -126,8 +123,8 @@ pub mod response;
|
||||
mod signer;
|
||||
|
||||
pub use crate::request::notification::{
|
||||
CollapseId, LocalizedNotificationBuilder, NotificationBuilder, NotificationOptions, PlainNotificationBuilder,
|
||||
Priority, SilentNotificationBuilder, WebNotificationBuilder, WebPushAlert,
|
||||
CollapseId, DefaultNotificationBuilder, NotificationBuilder, NotificationOptions, Priority, WebNotificationBuilder,
|
||||
WebPushAlert,
|
||||
};
|
||||
|
||||
pub use crate::response::{ErrorBody, ErrorReason, Response};
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
//! The `aps` notification content builders
|
||||
|
||||
mod localized;
|
||||
mod default;
|
||||
mod options;
|
||||
mod plain;
|
||||
mod silent;
|
||||
mod web;
|
||||
|
||||
pub use self::localized::{LocalizedAlert, LocalizedNotificationBuilder};
|
||||
pub use self::default::{DefaultAlert, DefaultNotificationBuilder};
|
||||
pub use self::options::{CollapseId, NotificationOptions, Priority};
|
||||
pub use self::plain::PlainNotificationBuilder;
|
||||
pub use self::silent::SilentNotificationBuilder;
|
||||
pub use self::web::{WebNotificationBuilder, WebPushAlert};
|
||||
|
||||
use crate::request::payload::Payload;
|
||||
|
||||
@@ -0,0 +1,720 @@
|
||||
use crate::request::notification::{NotificationBuilder, NotificationOptions};
|
||||
use crate::request::payload::{APSAlert, Payload, APS};
|
||||
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DefaultAlert<'a> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
title: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
subtitle: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
body: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
title_loc_key: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
title_loc_args: Option<Vec<Cow<'a, str>>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
action_loc_key: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
loc_key: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
loc_args: Option<Vec<Cow<'a, str>>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
launch_image: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// A builder to create an APNs payload.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("Hi there")
|
||||
/// .set_subtitle("From bob")
|
||||
/// .set_body("What's up?")
|
||||
/// .set_badge(420)
|
||||
/// .set_category("cat1")
|
||||
/// .set_sound("prööt")
|
||||
/// .set_mutable_content()
|
||||
/// .set_action_loc_key("PLAY")
|
||||
/// .set_launch_image("foo.jpg")
|
||||
/// .set_loc_args(&["argh", "narf"])
|
||||
/// .set_title_loc_key("STOP")
|
||||
/// .set_title_loc_args(&["herp", "derp"])
|
||||
/// .set_loc_key("PAUSE")
|
||||
/// .set_loc_args(&["narf", "derp"]);
|
||||
/// let payload = builder.build("device_id", Default::default())
|
||||
/// .to_json_string().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DefaultNotificationBuilder<'a> {
|
||||
alert: DefaultAlert<'a>,
|
||||
badge: Option<u32>,
|
||||
sound: Option<&'a str>,
|
||||
category: Option<&'a str>,
|
||||
mutable_content: u8,
|
||||
content_available: Option<u8>,
|
||||
has_edited_alert: bool,
|
||||
}
|
||||
|
||||
impl<'a> DefaultNotificationBuilder<'a> {
|
||||
/// Creates a new builder with the minimum amount of content.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let payload = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_body("a body")
|
||||
/// .build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new() -> DefaultNotificationBuilder<'a> {
|
||||
DefaultNotificationBuilder {
|
||||
alert: DefaultAlert {
|
||||
title: None,
|
||||
subtitle: None,
|
||||
body: None,
|
||||
title_loc_key: None,
|
||||
title_loc_args: None,
|
||||
action_loc_key: None,
|
||||
loc_key: None,
|
||||
loc_args: None,
|
||||
launch_image: None,
|
||||
},
|
||||
badge: None,
|
||||
sound: None,
|
||||
category: None,
|
||||
mutable_content: 0,
|
||||
content_available: None,
|
||||
has_edited_alert: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the title of the notification.
|
||||
/// Apple Watch displays this string in the short look notification interface.
|
||||
/// Specify a string that’s quickly understood by the user.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_title(mut self, title: &'a str) -> Self {
|
||||
self.alert.title = Some(title);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Used to set the subtitle which should provide additional information that explains the purpose of the notification.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_subtitle("a subtitle");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"subtitle\":\"a subtitle\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_subtitle(mut self, subtitle: &'a str) -> Self {
|
||||
self.alert.subtitle = Some(subtitle);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the content of the alert message.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_body("a body");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_body(mut self, body: &'a str) -> Self {
|
||||
self.alert.body = Some(body);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// A number to show on a badge on top of the app icon.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_badge(4);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"badge\":4,\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_badge(mut self, badge: u32) -> Self {
|
||||
self.badge = Some(badge);
|
||||
self
|
||||
}
|
||||
|
||||
/// File name of the custom sound to play when receiving the notification.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_sound("ping");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\"},\"mutable-content\":0,\"sound\":\"ping\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_sound(mut self, sound: &'a str) -> Self {
|
||||
self.sound = Some(sound);
|
||||
self
|
||||
}
|
||||
|
||||
/// When a notification includes the category key, the system displays the
|
||||
/// actions for that category as buttons in the banner or alert interface.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_category("cat1");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\"},\"category\":\"cat1\",\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_category(mut self, category: &'a str) -> Self {
|
||||
self.category = Some(category);
|
||||
self
|
||||
}
|
||||
|
||||
/// The localization key for the notification title.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_title_loc_key("play");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\",\"title-loc-key\":\"play\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_title_loc_key(mut self, key: &'a str) -> Self {
|
||||
self.alert.title_loc_key = Some(key);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Arguments for the title localization.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_title_loc_args(&["foo", "bar"]);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\",\"title-loc-args\":[\"foo\",\"bar\"]},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_title_loc_args<S>(mut self, args: &'a [S]) -> Self
|
||||
where
|
||||
S: Into<Cow<'a, str>> + AsRef<str>,
|
||||
{
|
||||
let converted = args.iter().map(|a| a.as_ref().into()).collect();
|
||||
|
||||
self.alert.title_loc_args = Some(converted);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// The localization key for the action.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_action_loc_key("stop");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"action-loc-key\":\"stop\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_action_loc_key(mut self, key: &'a str) -> Self {
|
||||
self.alert.action_loc_key = Some(key);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// The localization key for the push message body.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_loc_key("lol");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"loc-key\":\"lol\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_loc_key(mut self, key: &'a str) -> Self {
|
||||
self.alert.loc_key = Some(key);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Arguments for the content localization.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_loc_args(&["omg", "foo"]);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"loc-args\":[\"omg\",\"foo\"],\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_loc_args<S>(mut self, args: &'a [S]) -> Self
|
||||
where
|
||||
S: Into<Cow<'a, str>> + AsRef<str>,
|
||||
{
|
||||
let converted = args.iter().map(|a| a.as_ref().into()).collect();
|
||||
|
||||
self.alert.loc_args = Some(converted);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Image to display in the rich notification.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_launch_image("cat.png");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"launch-image\":\"cat.png\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_launch_image(mut self, image: &'a str) -> Self {
|
||||
self.alert.launch_image = Some(image);
|
||||
self.has_edited_alert = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow client to modify push content before displaying.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_mutable_content();
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\"},\"mutable-content\":1}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_mutable_content(mut self) -> Self {
|
||||
self.mutable_content = 1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Used for adding custom data to push notifications
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = DefaultNotificationBuilder::new()
|
||||
/// .set_title("a title")
|
||||
/// .set_content_available();
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"title\":\"a title\"},\"content-available\":1,\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_content_available(mut self) -> Self {
|
||||
self.content_available = Some(1);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NotificationBuilder<'a> for DefaultNotificationBuilder<'a> {
|
||||
fn build(self, device_token: &'a str, options: NotificationOptions<'a>) -> Payload<'a> {
|
||||
Payload {
|
||||
aps: APS {
|
||||
alert: match self.has_edited_alert {
|
||||
true => Some(APSAlert::Default(self.alert)),
|
||||
false => None,
|
||||
},
|
||||
badge: self.badge,
|
||||
sound: self.sound,
|
||||
content_available: self.content_available,
|
||||
category: self.category,
|
||||
mutable_content: Some(self.mutable_content),
|
||||
url_args: None,
|
||||
},
|
||||
device_token,
|
||||
options,
|
||||
data: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for DefaultNotificationBuilder<'a> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_notification_with_minimal_required_values() {
|
||||
let payload = DefaultNotificationBuilder::new()
|
||||
.set_title("the title")
|
||||
.set_body("the body")
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"alert": {
|
||||
"title": "the title",
|
||||
"body": "the body",
|
||||
},
|
||||
"mutable-content": 0
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_notification_with_full_data() {
|
||||
let builder = DefaultNotificationBuilder::new()
|
||||
.set_title("the title")
|
||||
.set_body("the body")
|
||||
.set_badge(420)
|
||||
.set_category("cat1")
|
||||
.set_sound("prööt")
|
||||
.set_mutable_content()
|
||||
.set_action_loc_key("PLAY")
|
||||
.set_launch_image("foo.jpg")
|
||||
.set_loc_args(&["argh", "narf"])
|
||||
.set_title_loc_key("STOP")
|
||||
.set_title_loc_args(&["herp", "derp"])
|
||||
.set_loc_key("PAUSE")
|
||||
.set_loc_args(&["narf", "derp"]);
|
||||
|
||||
let payload = builder
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"alert": {
|
||||
"action-loc-key": "PLAY",
|
||||
"body": "the body",
|
||||
"launch-image": "foo.jpg",
|
||||
"loc-args": ["narf", "derp"],
|
||||
"loc-key": "PAUSE",
|
||||
"title": "the title",
|
||||
"title-loc-args": ["herp", "derp"],
|
||||
"title-loc-key": "STOP"
|
||||
},
|
||||
"badge": 420,
|
||||
"category": "cat1",
|
||||
"mutable-content": 1,
|
||||
"sound": "prööt"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notification_with_custom_data_1() {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SubData {
|
||||
nothing: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TestData {
|
||||
key_str: &'static str,
|
||||
key_num: u32,
|
||||
key_bool: bool,
|
||||
key_struct: SubData,
|
||||
}
|
||||
|
||||
let test_data = TestData {
|
||||
key_str: "foo",
|
||||
key_num: 42,
|
||||
key_bool: false,
|
||||
key_struct: SubData { nothing: "here" },
|
||||
};
|
||||
|
||||
let mut payload = DefaultNotificationBuilder::new()
|
||||
.set_title("the title")
|
||||
.set_body("the body")
|
||||
.build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_num": 42,
|
||||
"key_bool": false,
|
||||
"key_struct": {
|
||||
"nothing": "here"
|
||||
}
|
||||
},
|
||||
"aps": {
|
||||
"alert": {
|
||||
"title": "the title",
|
||||
"body": "the body",
|
||||
},
|
||||
"mutable-content": 0,
|
||||
},
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload.to_json_string().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notification_with_custom_data_2() {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SubData {
|
||||
nothing: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TestData {
|
||||
key_str: &'static str,
|
||||
key_num: u32,
|
||||
key_bool: bool,
|
||||
key_struct: SubData,
|
||||
}
|
||||
|
||||
let test_data = TestData {
|
||||
key_str: "foo",
|
||||
key_num: 42,
|
||||
key_bool: false,
|
||||
key_struct: SubData { nothing: "here" },
|
||||
};
|
||||
|
||||
let mut payload = DefaultNotificationBuilder::new()
|
||||
.set_body("kulli")
|
||||
.build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let payload_json = payload.to_json_string().unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_num": 42,
|
||||
"key_bool": false,
|
||||
"key_struct": {
|
||||
"nothing": "here"
|
||||
}
|
||||
},
|
||||
"aps": {
|
||||
"alert": {
|
||||
"body": "kulli"
|
||||
},
|
||||
"mutable-content": 0
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload_json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_notification_with_no_content() {
|
||||
let payload = DefaultNotificationBuilder::new()
|
||||
.set_content_available()
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"content-available": 1,
|
||||
"mutable-content": 0
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_notification_with_custom_data() {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SubData {
|
||||
nothing: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TestData {
|
||||
key_str: &'static str,
|
||||
key_num: u32,
|
||||
key_bool: bool,
|
||||
key_struct: SubData,
|
||||
}
|
||||
|
||||
let test_data = TestData {
|
||||
key_str: "foo",
|
||||
key_num: 42,
|
||||
key_bool: false,
|
||||
key_struct: SubData { nothing: "here" },
|
||||
};
|
||||
|
||||
let mut payload = DefaultNotificationBuilder::new()
|
||||
.set_content_available()
|
||||
.build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"content-available": 1,
|
||||
"mutable-content": 0
|
||||
},
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_num": 42,
|
||||
"key_bool": false,
|
||||
"key_struct": {
|
||||
"nothing": "here"
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload.to_json_string().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_notification_with_custom_hashmap() {
|
||||
let mut test_data = BTreeMap::new();
|
||||
test_data.insert("key_str", "foo");
|
||||
test_data.insert("key_str2", "bar");
|
||||
|
||||
let mut payload = DefaultNotificationBuilder::new()
|
||||
.set_content_available()
|
||||
.build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"content-available": 1,
|
||||
"mutable-content": 0,
|
||||
},
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_str2": "bar"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload.to_json_string().unwrap());
|
||||
}
|
||||
}
|
||||
@@ -1,444 +0,0 @@
|
||||
use crate::request::notification::{NotificationBuilder, NotificationOptions};
|
||||
use crate::request::payload::{APSAlert, Payload, APS};
|
||||
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct LocalizedAlert<'a> {
|
||||
title: &'a str,
|
||||
body: &'a str,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
title_loc_key: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
title_loc_args: Option<Vec<Cow<'a, str>>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
action_loc_key: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
loc_key: Option<&'a str>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
loc_args: Option<Vec<Cow<'a, str>>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
launch_image: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// A builder to create a localized APNs payload.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("Hi there", "What's up?");
|
||||
/// builder.set_badge(420);
|
||||
/// builder.set_category("cat1");
|
||||
/// builder.set_sound("prööt");
|
||||
/// builder.set_mutable_content();
|
||||
/// builder.set_action_loc_key("PLAY");
|
||||
/// builder.set_launch_image("foo.jpg");
|
||||
/// builder.set_loc_args(&["argh", "narf"]);
|
||||
/// builder.set_title_loc_key("STOP");
|
||||
/// builder.set_title_loc_args(&["herp", "derp"]);
|
||||
/// builder.set_loc_key("PAUSE");
|
||||
/// builder.set_loc_args(&["narf", "derp"]);
|
||||
/// let payload = builder.build("device_id", Default::default())
|
||||
/// .to_json_string().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct LocalizedNotificationBuilder<'a> {
|
||||
alert: LocalizedAlert<'a>,
|
||||
badge: Option<u32>,
|
||||
sound: Option<&'a str>,
|
||||
category: Option<&'a str>,
|
||||
mutable_content: u8,
|
||||
}
|
||||
|
||||
impl<'a> LocalizedNotificationBuilder<'a> {
|
||||
/// Creates a new builder with the minimum amount of content.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let payload = LocalizedNotificationBuilder::new("a title", "a body")
|
||||
/// .build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(title: &'a str, body: &'a str) -> LocalizedNotificationBuilder<'a> {
|
||||
LocalizedNotificationBuilder {
|
||||
alert: LocalizedAlert {
|
||||
title,
|
||||
body,
|
||||
title_loc_key: None,
|
||||
title_loc_args: None,
|
||||
action_loc_key: None,
|
||||
loc_key: None,
|
||||
loc_args: None,
|
||||
launch_image: None,
|
||||
},
|
||||
badge: None,
|
||||
sound: None,
|
||||
category: None,
|
||||
mutable_content: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// A number to show on a badge on top of the app icon.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_badge(4);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\"},\"badge\":4,\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_badge(&mut self, badge: u32) -> &mut Self {
|
||||
self.badge = Some(badge);
|
||||
self
|
||||
}
|
||||
|
||||
/// File name of the custom sound to play when receiving the notification.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_sound("ping");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\"},\"mutable-content\":0,\"sound\":\"ping\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_sound(&mut self, sound: &'a str) -> &mut Self {
|
||||
self.sound = Some(sound);
|
||||
self
|
||||
}
|
||||
|
||||
/// When a notification includes the category key, the system displays the
|
||||
/// actions for that category as buttons in the banner or alert interface.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_category("cat1");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\"},\"category\":\"cat1\",\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_category(&mut self, category: &'a str) -> &mut Self {
|
||||
self.category = Some(category);
|
||||
self
|
||||
}
|
||||
|
||||
/// The localization key for the notification title.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_title_loc_key("play");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\",\"title-loc-key\":\"play\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_title_loc_key(&mut self, key: &'a str) -> &mut Self {
|
||||
self.alert.title_loc_key = Some(key);
|
||||
self
|
||||
}
|
||||
|
||||
/// Arguments for the title localization.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_title_loc_args(&["foo", "bar"]);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\",\"title-loc-args\":[\"foo\",\"bar\"]},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_title_loc_args<S>(&mut self, args: &'a [S]) -> &mut Self
|
||||
where
|
||||
S: Into<Cow<'a, str>> + AsRef<str>,
|
||||
{
|
||||
let converted = args.iter().map(|a| a.as_ref().into()).collect();
|
||||
|
||||
self.alert.title_loc_args = Some(converted);
|
||||
self
|
||||
}
|
||||
|
||||
/// The localization key for the action.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_action_loc_key("stop");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"action-loc-key\":\"stop\",\"body\":\"a body\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_action_loc_key(&mut self, key: &'a str) -> &mut Self {
|
||||
self.alert.action_loc_key = Some(key);
|
||||
self
|
||||
}
|
||||
|
||||
/// The localization key for the push message body.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_loc_key("lol");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"loc-key\":\"lol\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_loc_key(&mut self, key: &'a str) -> &mut Self {
|
||||
self.alert.loc_key = Some(key);
|
||||
self
|
||||
}
|
||||
|
||||
/// Arguments for the content localization.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_loc_args(&["omg", "foo"]);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"loc-args\":[\"omg\",\"foo\"],\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_loc_args<S>(&mut self, args: &'a [S]) -> &mut Self
|
||||
where
|
||||
S: Into<Cow<'a, str>> + AsRef<str>,
|
||||
{
|
||||
let converted = args.iter().map(|a| a.as_ref().into()).collect();
|
||||
|
||||
self.alert.loc_args = Some(converted);
|
||||
self
|
||||
}
|
||||
|
||||
/// Image to display in the rich notification.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_launch_image("cat.png");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"launch-image\":\"cat.png\",\"title\":\"a title\"},\"mutable-content\":0}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_launch_image(&mut self, image: &'a str) -> &mut Self {
|
||||
self.alert.launch_image = Some(image);
|
||||
self
|
||||
}
|
||||
|
||||
/// Allow client to modify push content before displaying.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{LocalizedNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = LocalizedNotificationBuilder::new("a title", "a body");
|
||||
/// builder.set_mutable_content();
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":{\"body\":\"a body\",\"title\":\"a title\"},\"mutable-content\":1}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_mutable_content(&mut self) -> &mut Self {
|
||||
self.mutable_content = 1;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NotificationBuilder<'a> for LocalizedNotificationBuilder<'a> {
|
||||
fn build(self, device_token: &'a str, options: NotificationOptions<'a>) -> Payload<'a> {
|
||||
Payload {
|
||||
aps: APS {
|
||||
alert: Some(APSAlert::Localized(self.alert)),
|
||||
badge: self.badge,
|
||||
sound: self.sound,
|
||||
content_available: None,
|
||||
category: self.category,
|
||||
mutable_content: Some(self.mutable_content),
|
||||
url_args: None,
|
||||
},
|
||||
device_token,
|
||||
options,
|
||||
data: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_localized_notification_with_minimal_required_values() {
|
||||
let payload = LocalizedNotificationBuilder::new("the title", "the body")
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"alert": {
|
||||
"title": "the title",
|
||||
"body": "the body",
|
||||
},
|
||||
"mutable-content": 0
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_localized_notification_with_full_data() {
|
||||
let mut builder = LocalizedNotificationBuilder::new("the title", "the body");
|
||||
|
||||
builder.set_badge(420);
|
||||
builder.set_category("cat1");
|
||||
builder.set_sound("prööt");
|
||||
builder.set_mutable_content();
|
||||
builder.set_action_loc_key("PLAY");
|
||||
builder.set_launch_image("foo.jpg");
|
||||
builder.set_loc_args(&["argh", "narf"]);
|
||||
builder.set_title_loc_key("STOP");
|
||||
builder.set_title_loc_args(&["herp", "derp"]);
|
||||
builder.set_loc_key("PAUSE");
|
||||
builder.set_loc_args(&["narf", "derp"]);
|
||||
|
||||
let payload = builder
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"alert": {
|
||||
"action-loc-key": "PLAY",
|
||||
"body": "the body",
|
||||
"launch-image": "foo.jpg",
|
||||
"loc-args": ["narf", "derp"],
|
||||
"loc-key": "PAUSE",
|
||||
"title": "the title",
|
||||
"title-loc-args": ["herp", "derp"],
|
||||
"title-loc-key": "STOP"
|
||||
},
|
||||
"badge": 420,
|
||||
"category": "cat1",
|
||||
"mutable-content": 1,
|
||||
"sound": "prööt"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plain_notification_with_custom_data() {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SubData {
|
||||
nothing: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TestData {
|
||||
key_str: &'static str,
|
||||
key_num: u32,
|
||||
key_bool: bool,
|
||||
key_struct: SubData,
|
||||
}
|
||||
|
||||
let test_data = TestData {
|
||||
key_str: "foo",
|
||||
key_num: 42,
|
||||
key_bool: false,
|
||||
key_struct: SubData { nothing: "here" },
|
||||
};
|
||||
|
||||
let mut payload =
|
||||
LocalizedNotificationBuilder::new("the title", "the body").build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_num": 42,
|
||||
"key_bool": false,
|
||||
"key_struct": {
|
||||
"nothing": "here"
|
||||
}
|
||||
},
|
||||
"aps": {
|
||||
"alert": {
|
||||
"title": "the title",
|
||||
"body": "the body",
|
||||
},
|
||||
"mutable-content": 0
|
||||
},
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload.to_json_string().unwrap());
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ impl<'a> CollapseId<'a> {
|
||||
}
|
||||
|
||||
/// Headers to specify options to the notification.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NotificationOptions<'a> {
|
||||
/// A canonical UUID that identifies the notification. If there is an error
|
||||
/// sending the notification, APNs uses this value to identify the
|
||||
@@ -62,18 +62,6 @@ pub struct NotificationOptions<'a> {
|
||||
pub apns_collapse_id: Option<CollapseId<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Default for NotificationOptions<'a> {
|
||||
fn default() -> NotificationOptions<'a> {
|
||||
NotificationOptions {
|
||||
apns_id: None,
|
||||
apns_expiration: None,
|
||||
apns_priority: None,
|
||||
apns_topic: None,
|
||||
apns_collapse_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The importance how fast to bring the notification for the user..
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Priority {
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
use crate::request::notification::{NotificationBuilder, NotificationOptions};
|
||||
use crate::request::payload::{APSAlert, Payload, APS};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A builder to create a simple APNs notification payload.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{NotificationBuilder, PlainNotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = PlainNotificationBuilder::new("Hi there");
|
||||
/// builder.set_badge(420);
|
||||
/// builder.set_category("cat1");
|
||||
/// builder.set_sound("prööt");
|
||||
/// let payload = builder.build("device_id", Default::default())
|
||||
/// .to_json_string().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct PlainNotificationBuilder<'a> {
|
||||
body: &'a str,
|
||||
badge: Option<u32>,
|
||||
sound: Option<&'a str>,
|
||||
category: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> PlainNotificationBuilder<'a> {
|
||||
/// Creates a new builder with the minimum amount of content.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{PlainNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let payload = PlainNotificationBuilder::new("a body")
|
||||
/// .build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":\"a body\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(body: &'a str) -> PlainNotificationBuilder<'a> {
|
||||
PlainNotificationBuilder {
|
||||
body,
|
||||
badge: None,
|
||||
sound: None,
|
||||
category: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A number to show on a badge on top of the app icon.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{PlainNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = PlainNotificationBuilder::new("a body");
|
||||
/// builder.set_badge(4);
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":\"a body\",\"badge\":4}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_badge(&mut self, badge: u32) -> &mut Self {
|
||||
self.badge = Some(badge);
|
||||
self
|
||||
}
|
||||
|
||||
/// File name of the custom sound to play when receiving the notification.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{PlainNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = PlainNotificationBuilder::new("a body");
|
||||
/// builder.set_sound("meow");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":\"a body\",\"sound\":\"meow\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_sound(&mut self, sound: &'a str) -> &mut Self {
|
||||
self.sound = Some(sound);
|
||||
self
|
||||
}
|
||||
|
||||
/// When a notification includes the category key, the system displays the
|
||||
/// actions for that category as buttons in the banner or alert interface.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{PlainNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut builder = PlainNotificationBuilder::new("a body");
|
||||
/// builder.set_category("cat1");
|
||||
/// let payload = builder.build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"alert\":\"a body\",\"category\":\"cat1\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn set_category(&mut self, category: &'a str) -> &mut Self {
|
||||
self.category = Some(category);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NotificationBuilder<'a> for PlainNotificationBuilder<'a> {
|
||||
fn build(self, device_token: &'a str, options: NotificationOptions<'a>) -> Payload<'a> {
|
||||
Payload {
|
||||
aps: APS {
|
||||
alert: Some(APSAlert::Plain(self.body)),
|
||||
badge: self.badge,
|
||||
sound: self.sound,
|
||||
content_available: None,
|
||||
category: self.category,
|
||||
mutable_content: None,
|
||||
url_args: None,
|
||||
},
|
||||
device_token,
|
||||
options,
|
||||
data: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_plain_notification_with_text_only() {
|
||||
let payload = PlainNotificationBuilder::new("kulli")
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"alert": "kulli",
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plain_notification_with_full_data() {
|
||||
let mut builder = PlainNotificationBuilder::new("Hi there");
|
||||
builder.set_badge(420);
|
||||
builder.set_category("cat1");
|
||||
builder.set_sound("prööt");
|
||||
|
||||
let payload = builder
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"alert": "Hi there",
|
||||
"badge": 420,
|
||||
"category": "cat1",
|
||||
"sound": "prööt"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plain_notification_with_custom_data() {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SubData {
|
||||
nothing: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TestData {
|
||||
key_str: &'static str,
|
||||
key_num: u32,
|
||||
key_bool: bool,
|
||||
key_struct: SubData,
|
||||
}
|
||||
|
||||
let test_data = TestData {
|
||||
key_str: "foo",
|
||||
key_num: 42,
|
||||
key_bool: false,
|
||||
key_struct: SubData { nothing: "here" },
|
||||
};
|
||||
|
||||
let mut payload = PlainNotificationBuilder::new("kulli").build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let payload_json = payload.to_json_string().unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_num": 42,
|
||||
"key_bool": false,
|
||||
"key_struct": {
|
||||
"nothing": "here"
|
||||
}
|
||||
},
|
||||
"aps": {
|
||||
"alert": "kulli",
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload_json);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
use crate::request::notification::{NotificationBuilder, NotificationOptions};
|
||||
use crate::request::payload::{Payload, APS};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A builder to create an APNs silent notification payload which can be used to
|
||||
/// send custom data to the user's phone if the user hasn't been running the app
|
||||
/// for a while. The custom data needs to be implementing `Serialize` from Serde.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::collections::HashMap;
|
||||
/// # use a2::request::notification::{NotificationBuilder, SilentNotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let mut test_data = HashMap::new();
|
||||
/// test_data.insert("a", "value");
|
||||
///
|
||||
/// let mut payload = SilentNotificationBuilder::new()
|
||||
/// .build("device_id", Default::default());
|
||||
///
|
||||
/// payload.add_custom_data("custom", &test_data);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"content-available\":1},\"custom\":{\"a\":\"value\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct SilentNotificationBuilder {
|
||||
content_available: u8,
|
||||
}
|
||||
|
||||
impl SilentNotificationBuilder {
|
||||
/// Creates a new builder.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{SilentNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// let payload = SilentNotificationBuilder::new()
|
||||
/// .build("token", Default::default());
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"content-available\":1}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new() -> SilentNotificationBuilder {
|
||||
SilentNotificationBuilder { content_available: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SilentNotificationBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> NotificationBuilder<'a> for SilentNotificationBuilder {
|
||||
fn build(self, device_token: &'a str, options: NotificationOptions<'a>) -> Payload<'a> {
|
||||
Payload {
|
||||
aps: APS {
|
||||
alert: None,
|
||||
badge: None,
|
||||
sound: None,
|
||||
content_available: Some(self.content_available),
|
||||
category: None,
|
||||
mutable_content: None,
|
||||
url_args: None,
|
||||
},
|
||||
device_token,
|
||||
options,
|
||||
data: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn test_silent_notification_with_no_content() {
|
||||
let payload = SilentNotificationBuilder::new()
|
||||
.build("device-token", Default::default())
|
||||
.to_json_string()
|
||||
.unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"content-available": 1
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_notification_with_custom_data() {
|
||||
#[derive(Serialize, Debug)]
|
||||
struct SubData {
|
||||
nothing: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TestData {
|
||||
key_str: &'static str,
|
||||
key_num: u32,
|
||||
key_bool: bool,
|
||||
key_struct: SubData,
|
||||
}
|
||||
|
||||
let test_data = TestData {
|
||||
key_str: "foo",
|
||||
key_num: 42,
|
||||
key_bool: false,
|
||||
key_struct: SubData { nothing: "here" },
|
||||
};
|
||||
|
||||
let mut payload = SilentNotificationBuilder::new().build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"content-available": 1
|
||||
},
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_num": 42,
|
||||
"key_bool": false,
|
||||
"key_struct": {
|
||||
"nothing": "here"
|
||||
}
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload.to_json_string().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_silent_notification_with_custom_hashmap() {
|
||||
let mut test_data = BTreeMap::new();
|
||||
test_data.insert("key_str", "foo");
|
||||
test_data.insert("key_str2", "bar");
|
||||
|
||||
let mut payload = SilentNotificationBuilder::new().build("device-token", Default::default());
|
||||
|
||||
payload.add_custom_data("custom", &test_data).unwrap();
|
||||
|
||||
let expected_payload = json!({
|
||||
"aps": {
|
||||
"content-available": 1
|
||||
},
|
||||
"custom": {
|
||||
"key_str": "foo",
|
||||
"key_str2": "bar"
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
|
||||
assert_eq!(expected_payload, payload.to_json_string().unwrap());
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ impl<'a> WebNotificationBuilder<'a> {
|
||||
WebNotificationBuilder {
|
||||
alert,
|
||||
sound: None,
|
||||
url_args: url_args,
|
||||
url_args,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-14
@@ -1,7 +1,7 @@
|
||||
//! Payload with `aps` and custom data
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::request::notification::{LocalizedAlert, NotificationOptions, WebPushAlert};
|
||||
use crate::request::notification::{DefaultAlert, NotificationOptions, WebPushAlert};
|
||||
use erased_serde::Serialize;
|
||||
use serde_json::{self, Value};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -30,10 +30,11 @@ impl<'a> Payload<'a> {
|
||||
/// Using a `HashMap`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use a2::request::notification::{SilentNotificationBuilder, NotificationBuilder};
|
||||
/// # use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// # use std::collections::HashMap;
|
||||
/// # fn main() {
|
||||
/// let mut payload = SilentNotificationBuilder::new()
|
||||
/// let mut payload = DefaultNotificationBuilder::new()
|
||||
/// .set_content_available()
|
||||
/// .build("token", Default::default());
|
||||
/// let mut custom_data = HashMap::new();
|
||||
///
|
||||
@@ -41,7 +42,7 @@ impl<'a> Payload<'a> {
|
||||
/// payload.add_custom_data("foo_data", &custom_data).unwrap();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"content-available\":1},\"foo_data\":{\"foo\":\"bar\"}}",
|
||||
/// "{\"aps\":{\"content-available\":1,\"mutable-content\":0},\"foo_data\":{\"foo\":\"bar\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
@@ -50,24 +51,26 @@ impl<'a> Payload<'a> {
|
||||
/// Using a custom struct:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// # use a2::request::notification::{SilentNotificationBuilder, NotificationBuilder};
|
||||
/// # fn main() {
|
||||
/// #[macro_use] extern crate serde;
|
||||
/// use a2::request::notification::{DefaultNotificationBuilder, NotificationBuilder};
|
||||
/// fn main() {
|
||||
/// #[derive(Serialize)]
|
||||
/// struct CompanyData {
|
||||
/// foo: &'static str,
|
||||
/// }
|
||||
///
|
||||
/// let mut payload = SilentNotificationBuilder::new().build("token", Default::default());
|
||||
/// let mut payload = DefaultNotificationBuilder::new()
|
||||
/// .set_content_available()
|
||||
/// .build("token", Default::default());
|
||||
/// let mut custom_data = CompanyData { foo: "bar" };
|
||||
///
|
||||
/// payload.add_custom_data("foo_data", &custom_data).unwrap();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// "{\"aps\":{\"content-available\":1},\"foo_data\":{\"foo\":\"bar\"}}",
|
||||
/// "{\"aps\":{\"content-available\":1,\"mutable-content\":0},\"foo_data\":{\"foo\":\"bar\"}}",
|
||||
/// &payload.to_json_string().unwrap()
|
||||
/// );
|
||||
/// # }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn add_custom_data(&mut self, root_key: &'a str, data: &dyn Serialize) -> Result<&mut Self, Error> {
|
||||
self.data.insert(root_key, serde_json::to_value(data)?);
|
||||
@@ -126,10 +129,8 @@ pub struct APS<'a> {
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum APSAlert<'a> {
|
||||
/// Text-only notification.
|
||||
Plain(&'a str),
|
||||
/// A rich localized notification.
|
||||
Localized(LocalizedAlert<'a>),
|
||||
/// A notification that supports all of the iOS features
|
||||
Default(DefaultAlert<'a>),
|
||||
/// Safari web push notification
|
||||
WebPush(WebPushAlert<'a>),
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ pub struct Response {
|
||||
}
|
||||
|
||||
/// The response body from APNs. Only available for errors.
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct ErrorBody {
|
||||
/// The error indicating the reason for the failure.
|
||||
pub reason: ErrorReason,
|
||||
@@ -42,7 +42,7 @@ pub struct ErrorBody {
|
||||
}
|
||||
|
||||
/// A description what went wrong with the push notification.
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum ErrorReason {
|
||||
/// The collapse identifier exceeds the maximum allowed size.
|
||||
BadCollapseId,
|
||||
|
||||
+120
-40
@@ -1,19 +1,24 @@
|
||||
use crate::error::Error;
|
||||
use base64::encode;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
sync::RwLock,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
use openssl::{
|
||||
ec::EcKey,
|
||||
hash::MessageDigest,
|
||||
pkey::{PKey, Private},
|
||||
sign::Signer as SslSigner,
|
||||
};
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
use ring::{rand, signature};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct Signature {
|
||||
key: String,
|
||||
issued_at: i64,
|
||||
@@ -21,11 +26,12 @@ struct Signature {
|
||||
|
||||
/// For signing requests when using token-based authentication. Re-uses the same
|
||||
/// signature for a certain amount of time.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Signer {
|
||||
signature: RwLock<Signature>,
|
||||
signature: Arc<RwLock<Signature>>,
|
||||
key_id: String,
|
||||
team_id: String,
|
||||
secret: PKey<Private>,
|
||||
secret: Arc<Secret>,
|
||||
expire_after_s: Duration,
|
||||
}
|
||||
|
||||
@@ -46,10 +52,55 @@ struct JwtPayload<'a> {
|
||||
iat: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Secret {
|
||||
#[cfg(feature = "openssl")]
|
||||
OpenSSL(PKey<Private>),
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
Ring {
|
||||
signing_key: signature::EcdsaKeyPair,
|
||||
rng: rand::SystemRandom,
|
||||
},
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
#[cfg(feature = "openssl")]
|
||||
fn new_openssl(pem_key: &[u8]) -> Result<Self, Error> {
|
||||
let ec_key = EcKey::private_key_from_pem(pem_key)?;
|
||||
let secret = PKey::from_ec_key(ec_key)?;
|
||||
Ok(Self::OpenSSL(secret))
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
fn new_ring(pem_key: &[u8]) -> Result<Self, Error> {
|
||||
let der = pem::parse(pem_key).map_err(SignerError::Pem)?;
|
||||
let alg = &signature::ECDSA_P256_SHA256_FIXED_SIGNING;
|
||||
let signing_key = signature::EcdsaKeyPair::from_pkcs8(alg, &der.contents)?;
|
||||
let rng = rand::SystemRandom::new();
|
||||
Ok(Self::Ring { signing_key, rng })
|
||||
}
|
||||
|
||||
fn from_pem<R>(mut pk_pem: R) -> Result<Secret, Error>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
let mut pem_key: Vec<u8> = Vec::new();
|
||||
pk_pem.read_to_end(&mut pem_key)?;
|
||||
#[cfg(feature = "openssl")]
|
||||
{
|
||||
Self::new_openssl(&pem_key)
|
||||
}
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
{
|
||||
Self::new_ring(&pem_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer {
|
||||
/// Creates a signer with a pkcs8 private key, APNs key id and team id.
|
||||
/// Can fail if the key is not valid or there is a problem with system OpenSSL.
|
||||
pub fn new<S, T, R>(mut pk_pem: R, key_id: S, team_id: T, signature_ttl: Duration) -> Result<Signer, Error>
|
||||
pub fn new<S, T, R>(pk_pem: R, key_id: S, team_id: T, signature_ttl: Duration) -> Result<Signer, Error>
|
||||
where
|
||||
S: Into<String>,
|
||||
T: Into<String>,
|
||||
@@ -58,24 +109,19 @@ impl Signer {
|
||||
let key_id: String = key_id.into();
|
||||
let team_id: String = team_id.into();
|
||||
|
||||
let mut pem_key: Vec<u8> = Vec::new();
|
||||
pk_pem.read_to_end(&mut pem_key)?;
|
||||
|
||||
let ec_key = EcKey::private_key_from_pem(&pem_key)?;
|
||||
let secret = Secret::from_pem(pk_pem)?;
|
||||
|
||||
let issued_at = get_time();
|
||||
let secret = PKey::from_ec_key(ec_key)?;
|
||||
|
||||
let signature = RwLock::new(Signature {
|
||||
key: Self::create_signature(&secret, &key_id, &team_id, issued_at)?,
|
||||
issued_at,
|
||||
});
|
||||
|
||||
let signer = Signer {
|
||||
signature,
|
||||
signature: Arc::new(signature),
|
||||
key_id,
|
||||
team_id,
|
||||
secret,
|
||||
secret: Arc::new(secret),
|
||||
expire_after_s: signature_ttl,
|
||||
};
|
||||
|
||||
@@ -94,17 +140,20 @@ impl Signer {
|
||||
|
||||
let signature = self.signature.read().unwrap();
|
||||
|
||||
trace!(
|
||||
"Signer::with_signature found signature for {}/{} valid for {}s",
|
||||
self.key_id,
|
||||
self.team_id,
|
||||
self.expire_after_s.as_secs(),
|
||||
);
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
tracing::trace!(
|
||||
"Signer::with_signature found signature for {}/{} valid for {}s",
|
||||
self.key_id,
|
||||
self.team_id,
|
||||
self.expire_after_s.as_secs(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(f(&signature.key))
|
||||
}
|
||||
|
||||
fn create_signature(secret: &PKey<Private>, key_id: &str, team_id: &str, issued_at: i64) -> Result<String, Error> {
|
||||
fn create_signature(secret: &Secret, key_id: &str, team_id: &str, issued_at: i64) -> Result<String, Error> {
|
||||
let headers = JwtHeader {
|
||||
alg: JwtAlg::ES256,
|
||||
kid: key_id,
|
||||
@@ -115,28 +164,28 @@ impl Signer {
|
||||
iat: issued_at,
|
||||
};
|
||||
|
||||
let encoded_header = encode(&serde_json::to_string(&headers)?);
|
||||
let encoded_payload = encode(&serde_json::to_string(&payload)?);
|
||||
let encoded_header = encode(serde_json::to_string(&headers)?);
|
||||
let encoded_payload = encode(serde_json::to_string(&payload)?);
|
||||
let signing_input = format!("{}.{}", encoded_header, encoded_payload);
|
||||
|
||||
let mut signer = SslSigner::new(MessageDigest::sha256(), secret)?;
|
||||
signer.update(signing_input.as_bytes())?;
|
||||
let signature_payload = secret.sign(&signing_input)?;
|
||||
|
||||
let signature_payload = signer.sign_to_vec()?;
|
||||
|
||||
Ok(format!("{}.{}", signing_input, encode(&signature_payload)))
|
||||
Ok(format!("{}.{}", signing_input, encode(signature_payload)))
|
||||
}
|
||||
|
||||
fn renew(&self) -> Result<(), Error> {
|
||||
let issued_at = get_time();
|
||||
|
||||
trace!(
|
||||
"Signer::renew for k_id {} t_id {} issued {} valid for {}s",
|
||||
self.key_id,
|
||||
self.team_id,
|
||||
issued_at,
|
||||
self.expire_after_s.as_secs(),
|
||||
);
|
||||
#[cfg(feature = "tracing")]
|
||||
{
|
||||
tracing::trace!(
|
||||
"Signer::renew for k_id {} t_id {} issued {} valid for {}s",
|
||||
self.key_id,
|
||||
self.team_id,
|
||||
issued_at,
|
||||
self.expire_after_s.as_secs(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut signature = self.signature.write().unwrap();
|
||||
|
||||
@@ -155,6 +204,39 @@ impl Signer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
fn sign(&self, signing_input: &String) -> Result<Vec<u8>, SignerError> {
|
||||
match self {
|
||||
#[cfg(feature = "openssl")]
|
||||
Secret::OpenSSL(key) => {
|
||||
let mut signer = SslSigner::new(MessageDigest::sha256(), key)?;
|
||||
signer.update(signing_input.as_bytes())?;
|
||||
let signature_payload = signer.sign_to_vec()?;
|
||||
Ok(signature_payload)
|
||||
}
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
Secret::Ring { signing_key, rng } => {
|
||||
let signature_payload = signing_key.sign(rng, signing_input.as_bytes())?;
|
||||
Ok(signature_payload.as_ref().to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Failed to sign payload
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SignerError {
|
||||
#[cfg(feature = "openssl")]
|
||||
#[error(transparent)]
|
||||
OpenSSL(#[from] openssl::error::ErrorStack),
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
#[error(transparent)]
|
||||
Pem(#[from] pem::PemError),
|
||||
#[cfg(all(not(feature = "openssl"), feature = "ring"))]
|
||||
#[error(transparent)]
|
||||
Ring(#[from] ring::error::Unspecified),
|
||||
}
|
||||
|
||||
fn get_time() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
@@ -166,13 +248,11 @@ fn get_time() -> i64 {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const PRIVATE_KEY: &'static str = indoc!(
|
||||
"-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8g/n6j9roKvnUkwu
|
||||
lCEIvbDqlUhA5FOzcakkG90E8L+hRANCAATKS2ZExEybUvchRDuKBftotMwVEus3
|
||||
jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ
|
||||
-----END PRIVATE KEY-----"
|
||||
);
|
||||
const PRIVATE_KEY: &'static str = "-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8g/n6j9roKvnUkwu
|
||||
lCEIvbDqlUhA5FOzcakkG90E8L+hRANCAATKS2ZExEybUvchRDuKBftotMwVEus3
|
||||
jDwmlD1Gg0yJt1e38djFwsxsfr5q2hv0Rj9fTEqAPr8H7mGm0wKxZ7iQ
|
||||
-----END PRIVATE KEY-----";
|
||||
|
||||
#[test]
|
||||
fn test_signature_caching() {
|
||||
|
||||
Reference in New Issue
Block a user