chore: first working version with integration test
This commit is contained in:
parent
f869b0f067
commit
122c38a0da
19 changed files with 266 additions and 1214 deletions
16
.github/workflows/audit.yml
vendored
Normal file
16
.github/workflows/audit.yml
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: Security audit
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
push:
|
||||
paths:
|
||||
- "**/Cargo.toml"
|
||||
- "**/Cargo.lock"
|
||||
jobs:
|
||||
security_audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: taiki-e/install-action@cargo-deny
|
||||
- name: Scan for vulnerabilities
|
||||
run: cargo deny check advisories
|
||||
48
.github/workflows/changelog.yml
vendored
Normal file
48
.github/workflows/changelog.yml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
name: Generate changelog
|
||||
# on:
|
||||
# push:
|
||||
# tags:
|
||||
# - "v[0-9]+.[0-9]+.[0-9]+"
|
||||
jobs:
|
||||
changelog:
|
||||
name: Generate changelog
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_body: ${{ steps.git-cliff.outputs.content }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate a changelog
|
||||
uses: orhun/git-cliff-action@v2
|
||||
id: git-cliff
|
||||
with:
|
||||
config: cliff.toml
|
||||
args: -vv --latest --strip header
|
||||
env:
|
||||
OUTPUT: CHANGES.md
|
||||
|
||||
# use release body in the same job
|
||||
- name: Upload the binary releases
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: binary_release.zip
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
body: ${{ steps.git-cliff.outputs.content }}
|
||||
|
||||
# use release body in another job
|
||||
upload:
|
||||
name: Upload the release
|
||||
needs: changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Upload the binary releases
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: binary_release.zip
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
body: ${{ needs.changelog.outputs.release_body }}
|
||||
53
.github/workflows/general.yml
vendored
Normal file
53
.github/workflows/general.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Rust
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Enforce formatting
|
||||
run: cargo fmt --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Linting
|
||||
run: cargo clippy -- -D warnings
|
||||
|
||||
coverage:
|
||||
name: Code coverage
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: xd009642/tarpaulin
|
||||
options: --security-opt seccomp=unconfined
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Generate code coverage
|
||||
run: |
|
||||
cargo tarpaulin --verbose --workspace
|
||||
630
Cargo.lock
generated
630
Cargo.lock
generated
|
|
@ -17,36 +17,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.68"
|
||||
|
|
@ -72,7 +42,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
|
|||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
|
|
@ -140,12 +110,6 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
|
|
@ -170,75 +134,6 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa"
|
||||
dependencies = [
|
||||
"cookie 0.16.2",
|
||||
"idna 0.2.3",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5a18f35792056f8c7c2de9c002e7e4fe44c7b5f66e7d99f46468dbb730a7ea7"
|
||||
dependencies = [
|
||||
"cookie 0.16.2",
|
||||
"idna 0.3.0",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
|
|
@ -255,41 +150,6 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.32"
|
||||
|
|
@ -299,12 +159,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
|
|
@ -380,17 +234,6 @@ version = "0.3.28"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
|
|
@ -410,22 +253,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -446,7 +276,7 @@ dependencies = [
|
|||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
|
@ -459,30 +289,12 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
|
|
@ -505,34 +317,12 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpc-test"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81a425cb8352fb5080b3622e8a4265c63e75bedd68a4c19a83f7d4c88f9c9667"
|
||||
dependencies = [
|
||||
"cookie 0.16.2",
|
||||
"http",
|
||||
"reqwest",
|
||||
"reqwest_cookie_store",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
|
|
@ -576,56 +366,6 @@ dependencies = [
|
|||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
|
|
@ -643,19 +383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.1",
|
||||
"serde",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -699,29 +427,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa"
|
||||
dependencies = [
|
||||
"lazy-regex-proc_macros",
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex-proc_macros"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
|
@ -732,19 +437,10 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
name = "learn_axum"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"httpc-test",
|
||||
"lazy-regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"strum_macros",
|
||||
"hyper",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
"tower-cookies",
|
||||
"tower-http",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -775,12 +471,6 @@ version = "0.4.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.0"
|
||||
|
|
@ -799,16 +489,6 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
|
@ -847,15 +527,6 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
|
|
@ -887,7 +558,7 @@ version = "0.10.55"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
|
|
@ -992,12 +663,6 @@ version = "0.3.27"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.63"
|
||||
|
|
@ -1007,22 +672,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
|
||||
dependencies = [
|
||||
"idna 0.3.0",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.29"
|
||||
|
|
@ -1032,74 +681,15 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.18"
|
||||
|
|
@ -1108,8 +698,6 @@ checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"cookie 0.16.2",
|
||||
"cookie_store 0.16.2",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
|
|
@ -1139,19 +727,6 @@ dependencies = [
|
|||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest_cookie_store"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06b407c05de7a0f7e4cc2a56af5e9bd6468e509124e81078ce1f8bc2ed3536bf"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cookie 0.16.2",
|
||||
"cookie_store 0.19.1",
|
||||
"reqwest",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
|
@ -1164,7 +739,7 @@ version = "0.37.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25693a73057a1b4cb56179dd3c7ea21a7c6c5ee7d85781f5749b46f34b79c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
|
|
@ -1205,7 +780,7 @@ version = "2.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
|
|
@ -1227,20 +802,6 @@ name = "serde"
|
|||
version = "1.0.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
|
|
@ -1274,35 +835,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
|
|
@ -1347,25 +879,6 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.22"
|
||||
|
|
@ -1397,53 +910,6 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
|
@ -1529,48 +995,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-cookies"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40f38d941a2ffd8402b36e02ae407637a9caceb693aaf2edc910437db0f36984"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"cookie 0.17.0",
|
||||
"futures-util",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
|
||||
dependencies = [
|
||||
"bitflags 2.3.3",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
|
|
@ -1610,15 +1034,6 @@ version = "0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
|
|
@ -1647,32 +1062,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 0.4.0",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
|
@ -1786,15 +1185,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
|
|
|
|||
35
Cargo.toml
35
Cargo.toml
|
|
@ -3,24 +3,31 @@ name = "learn_axum"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "learn_axum"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
# Serde / json
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_with = "3"
|
||||
# Axum
|
||||
hyper = { version = "0.14.27", features = ["full"] }
|
||||
# # Serde / json
|
||||
# serde = { version = "1.0", features = ["derive"] }
|
||||
# serde_json = "1"
|
||||
# serde_with = "3"
|
||||
# # Axum
|
||||
axum = { version = "0.6.20" }
|
||||
tower-http = { version = "0.4.4", features = ["fs"] }
|
||||
tower-cookies = "0.9"
|
||||
# Others
|
||||
lazy-regex = "3"
|
||||
async-trait = "0.1"
|
||||
strum_macros = "0.25"
|
||||
uuid = { version = "1", features = ["v4", "fast-rng"] }
|
||||
# tower-http = { version = "0.4.4", features = ["fs"] }
|
||||
# tower-cookies = "0.9"
|
||||
# # Others
|
||||
# lazy-regex = "3"
|
||||
# async-trait = "0.1"
|
||||
# strum_macros = "0.25"
|
||||
# uuid = { version = "1", features = ["v4", "fast-rng"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1"
|
||||
httpc-test = "0.1.5"
|
||||
reqwest = "0.11"
|
||||
|
|
|
|||
8
migrations/20220318150330_create_subscriptions_table.sql
Normal file
8
migrations/20220318150330_create_subscriptions_table.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-- Create Subscriptions Table
|
||||
CREATE TABLE subscriptions (
|
||||
id UUID NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
subscribed_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
46
scripts/init_db.sh
Executable file
46
scripts/init_db.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env bash
|
||||
set -x
|
||||
set -eo pipefail
|
||||
|
||||
if ! [ -x "$(command -v psql)" ]; then
|
||||
echo >&2 "Error: psql is not installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v sqlx)" ]; then
|
||||
echo >&2 "Error: sqlx is not installed."
|
||||
echo >&2 "Use:"
|
||||
echo >&2 " cargo install --version=0.5.7 sqlx-cli --no-default-features --features postgres"
|
||||
echo >&2 "to install it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_USER=${POSTGRES_USER:=postgres}
|
||||
DB_PASSWORD="${POSTGRES_PASSWORD:=password}"
|
||||
DB_NAME="${POSTGRES_DB:=newsletter}"
|
||||
DB_PORT="${POSTGRES_PORT:=5432}"
|
||||
|
||||
# Allow to skip container run if a containerized Postgres database is already running
|
||||
if [[ -z "${SKIP_DB_RUN}" ]]
|
||||
then
|
||||
podman run \
|
||||
-e POSTGRES_USER=${DB_USER} \
|
||||
-e POSTGRES_PASSWORD=${DB_PASSWORD} \
|
||||
-e POSTGRES_DB=${DB_NAME} \
|
||||
-p "${DB_PORT}":5432 \
|
||||
-d postgres \
|
||||
postgres -N 1000
|
||||
fi
|
||||
|
||||
until PGPASSWORD="${DB_PASSWORD}" psql -h "localhost" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do
|
||||
>&2 echo "Postgres is still unavailable - sleeping"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
>&2 echo "Postgres is up and running on port ${DB_PORT} - running migrations now!"
|
||||
|
||||
export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_NAME}
|
||||
sqlx database create
|
||||
sqlx migrate run
|
||||
|
||||
>&2 echo "Postgres has been migrated, ready to go!"
|
||||
18
src/ctx.rs
18
src/ctx.rs
|
|
@ -1,18 +0,0 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Ctx {
|
||||
user_id: u64,
|
||||
}
|
||||
|
||||
// Constructor
|
||||
impl Ctx {
|
||||
pub fn new(user_id: u64) -> Self {
|
||||
Self { user_id }
|
||||
}
|
||||
}
|
||||
|
||||
// Property Accessors.
|
||||
impl Ctx {
|
||||
pub fn user_id(&self) -> u64 {
|
||||
self.user_id
|
||||
}
|
||||
}
|
||||
67
src/error.rs
67
src/error.rs
|
|
@ -1,67 +0,0 @@
|
|||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use serde::Serialize;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, strum_macros::AsRefStr)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum Error {
|
||||
LoginFail,
|
||||
|
||||
// -- Auth errors.
|
||||
AuthFailNoAuthTokenCookie,
|
||||
AuthFailTokenWrongFormat,
|
||||
AuthFailCtxNotInRequestExt,
|
||||
|
||||
// -- Model errors.
|
||||
PropertyDeleteFailIdNotFound { id: u64 },
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn client_status_and_error(&self) -> (StatusCode, ClientError) {
|
||||
match self {
|
||||
// -- Login.
|
||||
Self::LoginFail => (StatusCode::UNAUTHORIZED, ClientError::LOGIN_FAIL),
|
||||
|
||||
// -- Auth.
|
||||
Self::AuthFailNoAuthTokenCookie
|
||||
| Self::AuthFailTokenWrongFormat
|
||||
| Self::AuthFailCtxNotInRequestExt => (StatusCode::FORBIDDEN, ClientError::NO_AUTH),
|
||||
|
||||
// -- Model.
|
||||
Self::PropertyDeleteFailIdNotFound { .. } => {
|
||||
(StatusCode::BAD_REQUEST, ClientError::INVALID_PARAMS)
|
||||
}
|
||||
|
||||
// -- Fallback.
|
||||
_ => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ClientError::SERVICE_ERROR,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> Response {
|
||||
println!("->> {:<12} - {self:?}", "INTO_RESPONSE");
|
||||
|
||||
// Create a placeholder Axum response.
|
||||
let mut response = StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||
|
||||
// Insert the Error into the response.
|
||||
response.extensions_mut().insert(self);
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, strum_macros::AsRefStr)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum ClientError {
|
||||
LOGIN_FAIL,
|
||||
NO_AUTH,
|
||||
INVALID_PARAMS,
|
||||
SERVICE_ERROR,
|
||||
}
|
||||
24
src/lib.rs
Normal file
24
src/lib.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#![allow(unused)]
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::http::{Method, Uri};
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use axum::routing::{get, get_service, IntoMakeService};
|
||||
use axum::Server;
|
||||
use axum::{middleware, Json, Router};
|
||||
use hyper::server::conn::AddrIncoming;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpListener;
|
||||
|
||||
pub type App = Server<AddrIncoming, IntoMakeService<Router>>;
|
||||
|
||||
/// API routes
|
||||
fn app() -> Router {
|
||||
Router::new().route("/health_check", get(|| async {}))
|
||||
}
|
||||
|
||||
/// Start the server
|
||||
pub fn run(listener: TcpListener) -> hyper::Result<App> {
|
||||
let app = app();
|
||||
let server = Server::from_tcp(listener)?.serve(app.into_make_service());
|
||||
Ok(server)
|
||||
}
|
||||
70
src/log.rs
70
src/log.rs
|
|
@ -1,70 +0,0 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::error::ClientError;
|
||||
use crate::{Error, Result};
|
||||
use axum::http::{Method, Uri};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use serde_with::skip_serializing_none;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn log_request(
|
||||
uuid: Uuid,
|
||||
req_method: Method,
|
||||
uri: Uri,
|
||||
ctx: Option<Ctx>,
|
||||
service_error: Option<&Error>,
|
||||
client_error: Option<ClientError>,
|
||||
) -> Result<()> {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
|
||||
let error_type = service_error.map(|se| se.as_ref().to_string());
|
||||
let error_data = serde_json::to_value(service_error)
|
||||
.ok()
|
||||
.and_then(|mut v| v.get_mut("data").map(|v| v.take()));
|
||||
|
||||
// Create the RequestLogLine.
|
||||
let log_line = RequestLogLine {
|
||||
uuid: uuid.to_string(),
|
||||
timestamp: timestamp.to_string(),
|
||||
|
||||
req_path: uri.path().to_string(),
|
||||
req_method: req_method.to_string(),
|
||||
|
||||
user_id: ctx.map(|ctx| ctx.user_id()),
|
||||
|
||||
client_error_type: client_error.map(|e| e.as_ref().to_string()),
|
||||
|
||||
error_type,
|
||||
error_data,
|
||||
};
|
||||
|
||||
println!(" ->> log request: \n{}", json!(log_line));
|
||||
|
||||
// TODO: Send to cloud-watch or something.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize)]
|
||||
struct RequestLogLine {
|
||||
uuid: String, // uuid string formatted
|
||||
timestamp: String, // (should be iso8601)
|
||||
|
||||
// -- User and context attributes.
|
||||
user_id: Option<u64>,
|
||||
|
||||
// -- http request attributes.
|
||||
req_path: String,
|
||||
req_method: String,
|
||||
|
||||
// -- Errors attributes.
|
||||
client_error_type: Option<String>,
|
||||
error_type: Option<String>,
|
||||
error_data: Option<Value>,
|
||||
}
|
||||
111
src/main.rs
111
src/main.rs
|
|
@ -1,108 +1,9 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use crate::model::ModelController;
|
||||
|
||||
use self::ctx::Ctx;
|
||||
pub use self::error::{Error, Result};
|
||||
use self::log::log_request;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::http::{Method, Uri};
|
||||
use axum::response::{Html, IntoResponse, Response};
|
||||
use axum::routing::{get, get_service};
|
||||
use axum::Server;
|
||||
use axum::{middleware, Json, Router};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use tower_cookies::CookieManagerLayer;
|
||||
use tower_http::services::ServeDir;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod ctx;
|
||||
mod error;
|
||||
mod log;
|
||||
mod model;
|
||||
mod web;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HelloParams {
|
||||
name: Option<String>,
|
||||
}
|
||||
use learn_axum::run;
|
||||
use std::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let mc = ModelController::new().await?;
|
||||
|
||||
let routes_apis = web::routes_properties::routes(mc.clone())
|
||||
.route_layer(middleware::from_fn(web::mw_auth::mw_require_auth));
|
||||
|
||||
let routes_all = Router::new()
|
||||
.merge(Router::new().route("/health_check", get(|| async {})))
|
||||
.merge(web::routes_login::routes())
|
||||
.nest("/api", routes_apis)
|
||||
.layer(middleware::map_response(main_response_mapper))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
mc.clone(),
|
||||
web::mw_auth::mw_ctx_resolver,
|
||||
))
|
||||
.layer(CookieManagerLayer::new()) // must be above? the auth routes
|
||||
.fallback_service(routes_static());
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
println!("->> Listening on {addr}\n");
|
||||
Server::bind(&addr)
|
||||
.serve(routes_all.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Map the response to add headers, etc.
|
||||
///
|
||||
/// * `res`: the response to map
|
||||
async fn main_response_mapper(
|
||||
ctx: Option<Ctx>,
|
||||
uri: Uri,
|
||||
req_method: Method,
|
||||
res: Response,
|
||||
) -> Response {
|
||||
println!("->> {:<12} - main_response_mapper", "RES_MAPPER");
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
// -- Get the eventual response error.
|
||||
let service_error = res.extensions().get::<Error>();
|
||||
let client_status_error = service_error.map(|se| se.client_status_and_error());
|
||||
|
||||
// -- If client error, build the new response
|
||||
let error_response = client_status_error
|
||||
.as_ref()
|
||||
.map(|(status_code, client_error)| {
|
||||
let client_error_body = json!({
|
||||
"error": {
|
||||
"type": client_error.as_ref(),
|
||||
"req_uuid": uuid.to_string(),
|
||||
}
|
||||
});
|
||||
|
||||
println!(" ->> client_error_body: {client_error_body}");
|
||||
|
||||
// Build the new response from the client error body.
|
||||
(*status_code, Json(client_error_body)).into_response()
|
||||
});
|
||||
|
||||
// -- Build and log the server log line.
|
||||
let client_error = client_status_error.unzip().1;
|
||||
log_request(uuid, req_method, uri, ctx, service_error, client_error).await;
|
||||
|
||||
println!();
|
||||
error_response.unwrap_or(res)
|
||||
}
|
||||
|
||||
/// Serve static files
|
||||
// FIXME: remove
|
||||
fn routes_static() -> Router {
|
||||
Router::new().nest_service("/", get_service(ServeDir::new("./")))
|
||||
async fn main() -> hyper::Result<()> {
|
||||
let addr = format!("127.0.0.1:{}", "3000");
|
||||
let listener = TcpListener::bind(addr).expect("Unable to bind to port");
|
||||
run(listener)?.await
|
||||
}
|
||||
|
|
|
|||
69
src/model.rs
69
src/model.rs
|
|
@ -1,69 +0,0 @@
|
|||
//! Simplistic model layer
|
||||
//! (with mock-store layer)
|
||||
|
||||
use crate::{ctx::Ctx, Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// region: --- Property Types
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Property {
|
||||
pub id: u64,
|
||||
pub creator_id: u64,
|
||||
pub address: String,
|
||||
pub contact: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PropertyForCreate {
|
||||
pub address: String,
|
||||
pub contact: String,
|
||||
}
|
||||
|
||||
// endregion: --- Property Types
|
||||
|
||||
// region: --- Model Controller
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ModelController {
|
||||
property_store: Arc<Mutex<Vec<Option<Property>>>>,
|
||||
}
|
||||
|
||||
// Constructor
|
||||
impl ModelController {
|
||||
pub async fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
property_store: Arc::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// CRUD implementation
|
||||
impl ModelController {
|
||||
pub async fn create_property(&self, ctx: Ctx, property: PropertyForCreate) -> Result<Property> {
|
||||
let mut store = self.property_store.lock().unwrap();
|
||||
let id = store.len() as u64;
|
||||
let property = Property {
|
||||
id,
|
||||
creator_id: ctx.user_id(),
|
||||
address: property.address,
|
||||
contact: property.contact,
|
||||
};
|
||||
store.push(Some(property.clone()));
|
||||
Ok(property)
|
||||
}
|
||||
|
||||
pub async fn list_properties(&self, ctx: Ctx) -> Result<Vec<Property>> {
|
||||
let store = self.property_store.lock().unwrap();
|
||||
let properties = store.iter().filter_map(|p| p.clone()).collect();
|
||||
Ok(properties)
|
||||
}
|
||||
|
||||
pub async fn delete_property(&self, ctx: Ctx, id: u64) -> Result<Property> {
|
||||
let mut store = self.property_store.lock().unwrap();
|
||||
let property = store.get_mut(id as usize).and_then(|p| p.take());
|
||||
property.ok_or(Error::PropertyDeleteFailIdNotFound { id })
|
||||
}
|
||||
}
|
||||
|
||||
// endregion: --- Model Controller
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
pub mod mw_auth;
|
||||
pub mod routes_login;
|
||||
pub mod routes_properties;
|
||||
|
||||
/// The cookie name for the auth token
|
||||
pub const AUTH_TOKEN: &str = "auth-token";
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
use async_trait::async_trait;
|
||||
use axum::extract::FromRequestParts;
|
||||
use axum::http::request::Parts;
|
||||
use axum::http::Request;
|
||||
use axum::middleware::Next;
|
||||
use axum::response::Response;
|
||||
use axum::RequestPartsExt;
|
||||
use lazy_regex::regex_captures;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
use crate::web::AUTH_TOKEN;
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub async fn mw_ctx_resolver<B>(
|
||||
cookies: Cookies,
|
||||
mut req: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Result<Response> {
|
||||
println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE");
|
||||
|
||||
let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string());
|
||||
|
||||
// Compute Result<Ctx>.
|
||||
let result_ctx = match auth_token
|
||||
.ok_or(Error::AuthFailNoAuthTokenCookie)
|
||||
.and_then(parse_token)
|
||||
{
|
||||
Ok((user_id, _exp, _sign)) => {
|
||||
// TODO: Token components validations.
|
||||
Ok(Ctx::new(user_id))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
// Remove the cookie if something went wrong other than NoAuthTokenCookie.
|
||||
if result_ctx.is_err() && !matches!(result_ctx, Err(Error::AuthFailNoAuthTokenCookie)) {
|
||||
cookies.remove(Cookie::named(AUTH_TOKEN))
|
||||
}
|
||||
|
||||
// Store the ctx_result in the request extension.
|
||||
req.extensions_mut().insert(result_ctx);
|
||||
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
/// Middleware to require authentication.
|
||||
pub async fn mw_require_auth<B>(
|
||||
ctx: Result<Ctx>,
|
||||
req: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Result<Response> {
|
||||
println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE");
|
||||
|
||||
ctx?;
|
||||
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
// region: --- Ctx Extractor
|
||||
|
||||
#[async_trait]
|
||||
impl<S: Send + Sync> FromRequestParts<S> for Ctx {
|
||||
type Rejection = Error;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
|
||||
println!("->> {:<12} - Ctx", "EXTRACTOR");
|
||||
|
||||
parts
|
||||
.extensions
|
||||
.get::<Result<Ctx>>()
|
||||
.ok_or(Error::AuthFailCtxNotInRequestExt)?
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion: --- Ctx Extractor
|
||||
|
||||
/// Parse a token of format `user-[user-id].[expiration].[signature]`
|
||||
/// Returns (user-id, expiration, signature)
|
||||
fn parse_token(token: String) -> Result<(u64, String, String)> {
|
||||
let (_whole, user_id, exp, sign) = regex_captures!(
|
||||
r#"^user-(\d+)\.(.+)\.(.+)"#, // a literal regex
|
||||
&token
|
||||
)
|
||||
.ok_or(Error::AuthFailTokenWrongFormat)?;
|
||||
|
||||
let user_id: u64 = user_id
|
||||
.parse()
|
||||
.map_err(|_| Error::AuthFailTokenWrongFormat)?;
|
||||
|
||||
Ok((user_id, exp.to_string(), sign.to_string()))
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
use crate::{web, Error, Result};
|
||||
use axum::routing::post;
|
||||
use axum::{Json, Router};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new().route("/api/login", post(api_login))
|
||||
}
|
||||
|
||||
async fn api_login(cookies: Cookies, payload: Json<LoginPayload>) -> Result<Json<Value>> {
|
||||
println!("->> {:<12} - api_login", "HANDLER");
|
||||
|
||||
if payload.username != "demo1" || payload.password != "demo1" {
|
||||
return Err(Error::LoginFail);
|
||||
}
|
||||
|
||||
// FIXME: Implement real auth-token generation/signature.
|
||||
cookies.add(Cookie::new(web::AUTH_TOKEN, "user-1.exp.sign"));
|
||||
|
||||
let body = Json(json!({
|
||||
"result": {
|
||||
"success": true
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LoginPayload {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
use crate::ctx::Ctx;
|
||||
use crate::model::{ModelController, Property, PropertyForCreate};
|
||||
use crate::Result;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::routing::{delete, post};
|
||||
use axum::{Json, Router};
|
||||
|
||||
pub fn routes(mc: ModelController) -> Router {
|
||||
Router::new()
|
||||
.route("/properties", post(create_property).get(list_properties))
|
||||
.route("/properties/:id", delete(delete_property))
|
||||
.with_state(mc)
|
||||
}
|
||||
|
||||
// region: --- REST Handlers
|
||||
|
||||
async fn create_property(
|
||||
State(mc): State<ModelController>,
|
||||
ctx: Ctx,
|
||||
Json(property_fc): Json<PropertyForCreate>,
|
||||
) -> Result<Json<Property>> {
|
||||
println!("->> {:<12} - create_property", "HANDLER");
|
||||
let property = mc.create_property(ctx, property_fc).await?;
|
||||
Ok(Json(property))
|
||||
}
|
||||
|
||||
async fn list_properties(
|
||||
State(mc): State<ModelController>,
|
||||
ctx: Ctx,
|
||||
) -> Result<Json<Vec<Property>>> {
|
||||
println!("->> {:<12} - list_properties", "HANDLER");
|
||||
let properties = mc.list_properties(ctx).await?;
|
||||
Ok(Json(properties))
|
||||
}
|
||||
|
||||
async fn delete_property(
|
||||
State(mc): State<ModelController>,
|
||||
ctx: Ctx,
|
||||
Path(id): Path<u64>,
|
||||
) -> Result<Json<Property>> {
|
||||
println!("->> {:<12} - delete_property", "HANDLER");
|
||||
let property = mc.delete_property(ctx, id).await?;
|
||||
Ok(Json(property))
|
||||
}
|
||||
|
||||
// endregion: --- REST Handlers
|
||||
34
tests/health_check.rs
Normal file
34
tests/health_check.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::net::{SocketAddr, TcpListener};
|
||||
|
||||
struct TestApp {
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn health_check_works() {
|
||||
let TestApp { addr, .. } = spawn_app().await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.get(format!("http://{addr}/health_check"))
|
||||
.send()
|
||||
.await
|
||||
.expect("Failed to execute request.");
|
||||
assert!(response.status().is_success());
|
||||
assert_eq!(Some(0), response.content_length());
|
||||
}
|
||||
|
||||
// fn spawn_app() {
|
||||
// let server = learn_axum::run().expect("Failed to bind address.");
|
||||
// tokio::spawn(server);
|
||||
// }
|
||||
async fn spawn_app() -> TestApp {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to random port");
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server = learn_axum::run(listener).expect("Failed to bind to address");
|
||||
|
||||
tokio::spawn(server);
|
||||
|
||||
TestApp { addr }
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
use anyhow::Result;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_quick_dev() -> Result<()> {
|
||||
let hc = httpc_test::new_client("http://localhost:3000")?;
|
||||
hc.do_get("/hello?name=jen").await?.print().await?;
|
||||
|
||||
let hc = httpc_test::new_client("http://localhost:3000")?;
|
||||
hc.do_get("/hello2/mike").await?.print().await?;
|
||||
|
||||
hc.do_get("/src/main.rs").await?.print().await?;
|
||||
|
||||
hc.do_get("/src/blub.rs").await?.print().await?;
|
||||
|
||||
let req_login = hc.do_post(
|
||||
"/api/login",
|
||||
json!(
|
||||
{
|
||||
"username": "demo1",
|
||||
"password": "demowrong"
|
||||
}
|
||||
),
|
||||
);
|
||||
req_login.await?.print().await?;
|
||||
let req_login = hc.do_post(
|
||||
"/api/login",
|
||||
json!(
|
||||
{
|
||||
"username": "demo1",
|
||||
"password": "demo1"
|
||||
}
|
||||
),
|
||||
);
|
||||
req_login.await?.print().await?;
|
||||
|
||||
hc.do_get("/hello2/mike").await?.print().await?;
|
||||
|
||||
let req_create_property = hc.do_post(
|
||||
"/api/properties",
|
||||
json!(
|
||||
{
|
||||
"address": "Lolilat Street 1",
|
||||
"contact": "01234 567890"
|
||||
}
|
||||
),
|
||||
);
|
||||
req_create_property.await?.print().await?;
|
||||
let req_create_property = hc.do_post(
|
||||
"/api/properties",
|
||||
json!(
|
||||
{
|
||||
"address": "Lolilat Street 2",
|
||||
"contact": "01243 217890"
|
||||
}
|
||||
),
|
||||
);
|
||||
req_create_property.await?.print().await?;
|
||||
let req_get_properties = hc.do_get("/api/properties").await?;
|
||||
req_get_properties.print().await?;
|
||||
let req_delete_property = hc.do_delete("/api/properties/1").await?;
|
||||
req_delete_property.print().await?;
|
||||
let req_get_properties = hc.do_get("/api/properties").await?;
|
||||
req_get_properties.print().await?;
|
||||
let req_delete_property = hc.do_delete("/api/properties/0").await?;
|
||||
req_delete_property.print().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue