Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
120a3dc8d0 | ||
44472bef1e | |||
f1021295f7 | |||
![]() |
1c42b86f90 | ||
![]() |
7aebbca98d | ||
![]() |
8f456f0cd5 | ||
![]() |
87e89317f5 | ||
![]() |
7cc675c129 | ||
![]() |
62a972594d | ||
![]() |
0950da48d6 | ||
![]() |
790ffb4032 | ||
![]() |
53c6301be2 | ||
![]() |
6907e4baf1 | ||
![]() |
50e0b97a7f |
17
.4nk-sync.yml
Normal file
17
.4nk-sync.yml
Normal file
@ -0,0 +1,17 @@
|
||||
template:
|
||||
repo: nicolas.cantu/4NK_project_template
|
||||
host: git.4nkweb.com
|
||||
sync:
|
||||
include:
|
||||
- .gitea/
|
||||
- .cursor/
|
||||
- docs/
|
||||
- AGENTS.md
|
||||
- CONTRIBUTING.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
- SECURITY.md
|
||||
- CHANGELOG.md
|
||||
exclude:
|
||||
- target/
|
||||
- storage/
|
||||
- .git/
|
7
.cursor/rules.md
Normal file
7
.cursor/rules.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Règles Cursor du projet
|
||||
|
||||
- Compiler régulièrement: `cargo build`.
|
||||
- Lancer les tests souvent: `cargo test`.
|
||||
- Mettre à jour la documentation (`docs/`) à chaque changement fonctionnel.
|
||||
- Respecter le style Rust, `cargo fmt` et `cargo clippy -D warnings`.
|
||||
- PRs doivent inclure tests et docs.
|
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
||||
target
|
||||
.git
|
||||
storage
|
||||
**/*.log
|
||||
**/*.tmp
|
||||
**/*.swp
|
44
.github/workflows/dev.yml
vendored
Normal file
44
.github/workflows/dev.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Build and Push to Registry
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
|
||||
env:
|
||||
REGISTRY: git.4nkweb.com
|
||||
IMAGE_NAME: 4nk/sdk_storage
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up SSH agent
|
||||
uses: webfactory/ssh-agent@v0.9.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.USER }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
ssh: default
|
||||
build-args: |
|
||||
CONF=${{ secrets.CONF }}
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ gitea.sha }}
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,2 +1,13 @@
|
||||
/target
|
||||
/storage
|
||||
/dist
|
||||
/.env
|
||||
/.env.local
|
||||
/.env.production
|
||||
/.env.test
|
||||
.vscode/
|
||||
.idea/
|
||||
*.log
|
||||
*.tmp
|
||||
*.swp
|
||||
*.swo
|
||||
|
5
AGENTS.md
Normal file
5
AGENTS.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Agents & Automations
|
||||
|
||||
- Compilation régulière: `cargo build`.
|
||||
- Lancement des tests: `cargo test`.
|
||||
- Mise à jour de la documentation dès qu'une fonctionnalité change (`docs/`).
|
18
CHANGELOG.md
Normal file
18
CHANGELOG.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.0
|
||||
- Ajout Dockerfile multi-stage et `.dockerignore`
|
||||
- CI: workflows build/test, release et build/push Docker
|
||||
- Documentation étendue dans `docs/` (architecture, guides, API JSON)
|
||||
- Tests renforcés: conflit de clé et suppression des expirés
|
||||
|
||||
## 0.2.2
|
||||
- Endpoint `/health` ajouté et testé
|
||||
- Spec JSON/API et docs monitoring mises à jour
|
||||
- CI Docker: tagging multi-tags (`latest` et `vX.Y.Z` sur tag)
|
||||
- Version et déploiements alignés
|
||||
|
||||
## 0.1.0
|
||||
- Refactor vers `src/lib.rs` et service `StorageService`
|
||||
- Ajout `docs/` (README) et `tests/` (test intégration service)
|
||||
- API HTTP Tide conservée; nettoyage TTL périodique 60s
|
3
CODEOWNERS
Normal file
3
CODEOWNERS
Normal file
@ -0,0 +1,3 @@
|
||||
# Code owners par défaut du dépôt
|
||||
# Ajustez ces lignes si vous utilisez des équipes/alias différents.
|
||||
* @nicolas.cantu
|
9
CODE_OF_CONDUCT.md
Normal file
9
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Code de Conduite
|
||||
|
||||
Nous nous engageons à offrir une communauté ouverte, accueillante et respectueuse.
|
||||
|
||||
- Pas de harcèlement.
|
||||
- Respect des avis techniques et des personnes.
|
||||
- Suivre les consignes des mainteneurs.
|
||||
|
||||
Signalez tout problème via les issues du dépôt.
|
8
CONTRIBUTING.md
Normal file
8
CONTRIBUTING.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Contribuer à sdk_storage
|
||||
|
||||
Merci de proposer des issues et des Pull Requests.
|
||||
|
||||
- Discutez via une issue avant une modification majeure.
|
||||
- Travaillez sur une branche `feature/...`.
|
||||
- Ajoutez systématiquement des tests (`tests/`) et mettez à jour la documentation (`docs/`).
|
||||
- Assurez-vous que `cargo fmt`, `cargo clippy` et `cargo test` passent localement.
|
615
Cargo.lock
generated
615
Cargo.lock
generated
@ -2,6 +2,21 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.3.2"
|
||||
@ -56,6 +71,15 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@ -236,6 +260,18 @@ dependencies = [
|
||||
"pin-project-lite 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-native-tls"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"native-tls",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.3.0"
|
||||
@ -365,6 +401,21 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.11"
|
||||
@ -507,6 +558,17 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.10"
|
||||
@ -536,6 +598,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -557,6 +629,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
@ -592,6 +673,33 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"hashbrown",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d126179d86aee4556e54f5f3c6bf6d9884e7cc52cef82f77ee6f90a7747616d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"config",
|
||||
"crossbeam-queue",
|
||||
"num_cpus",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
@ -618,6 +726,19 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.5"
|
||||
@ -696,6 +817,21 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[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 = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@ -705,6 +841,21 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@ -712,6 +863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -720,6 +872,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
@ -765,6 +928,12 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
@ -777,9 +946,13 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite 0.2.15",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
@ -817,6 +990,18 @@ dependencies = [
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.3.1"
|
||||
@ -827,6 +1012,12 @@ dependencies = [
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.3.0"
|
||||
@ -839,6 +1030,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
@ -851,6 +1048,12 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@ -893,8 +1096,14 @@ version = "6.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1947510dc91e2bf586ea5ffb412caad7673264e14bb39fb9078da114a94ce1a5"
|
||||
dependencies = [
|
||||
"async-h1",
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"cfg-if 1.0.0",
|
||||
"dashmap",
|
||||
"deadpool",
|
||||
"futures",
|
||||
"http-types",
|
||||
"log",
|
||||
]
|
||||
@ -927,6 +1136,12 @@ version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
@ -1115,6 +1330,28 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.2",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@ -1140,10 +1377,29 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.164"
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if 1.0.0",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
@ -1163,6 +1419,16 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
@ -1179,6 +1445,70 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b"
|
||||
dependencies = [
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@ -1188,6 +1518,25 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
@ -1200,12 +1549,69 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if 1.0.0",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@ -1261,6 +1667,12 @@ dependencies = [
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.8.0"
|
||||
@ -1336,6 +1748,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
@ -1407,12 +1825,56 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "route-recognizer"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@ -1455,17 +1917,58 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sdk_storage"
|
||||
version = "0.1.0"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"env_logger",
|
||||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"surf",
|
||||
"tempfile",
|
||||
"tide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
@ -1628,6 +2131,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "stdweb"
|
||||
version = "0.4.20"
|
||||
@ -1683,6 +2192,28 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "surf"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "718b1ae6b50351982dedff021db0def601677f2120938b070eadb10ba4038dd7"
|
||||
dependencies = [
|
||||
"async-native-tls",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"cfg-if 1.0.0",
|
||||
"futures-util",
|
||||
"getrandom 0.2.15",
|
||||
"http-client",
|
||||
"http-types",
|
||||
"log",
|
||||
"mime_guess",
|
||||
"once_cell",
|
||||
"pin-project-lite 0.2.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sval"
|
||||
version = "2.13.2"
|
||||
@ -1794,6 +2325,29 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"fastrand 2.2.0",
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 0.38.41",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@ -1885,6 +2439,20 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite 0.2.15",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
@ -1913,6 +2481,12 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
@ -1989,6 +2563,12 @@ dependencies = [
|
||||
"sval_serde",
|
||||
]
|
||||
|
||||
[[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.5"
|
||||
@ -2013,6 +2593,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.95"
|
||||
@ -2108,6 +2697,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@ -2271,6 +2869,15 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
|
17
Cargo.toml
17
Cargo.toml
@ -1,11 +1,16 @@
|
||||
[package]
|
||||
name = "sdk_storage"
|
||||
version = "0.1.0"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tide = "0.16.0"
|
||||
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
hex = "0.4.3"
|
||||
tide = "0.16"
|
||||
async-std = { version = "1", features = ["attributes"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
hex = "0.4"
|
||||
env_logger = "0.10"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
surf = { version = "2", default-features = false, features = ["h1-client"] }
|
||||
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM rust:1 as builder
|
||||
WORKDIR /app
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src ./src
|
||||
RUN cargo build --release
|
||||
|
||||
FROM debian:stable-slim
|
||||
RUN useradd -m -u 10001 appuser && \
|
||||
apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/target/release/sdk_storage /usr/local/bin/sdk_storage
|
||||
RUN mkdir -p /app/storage && chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
EXPOSE 8081
|
||||
ENV RUST_LOG=info
|
||||
ENTRYPOINT ["/usr/local/bin/sdk_storage"]
|
||||
CMD ["--permanent"]
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# sdk_storage
|
||||
|
||||
Voir la documentation détaillée dans `docs/`.
|
||||
|
||||
## Démarrage rapide
|
||||
|
||||
- Construire: `cargo build`
|
||||
- Lancer: `cargo run -- --permanent` (clé sans TTL = permanente)
|
||||
- Tester: `cargo test`
|
||||
|
||||
## API
|
||||
|
||||
- POST `/store` { key(hex64), value(hex), ttl? (s) }
|
||||
- GET `/retrieve/:key`
|
||||
|
||||
## Contribution
|
||||
|
||||
Voir `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
|
||||
|
||||
## Licence
|
||||
|
||||
Voir `LICENSE` (MIT).
|
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Politique de Sécurité
|
||||
|
||||
- Ne divulguez pas publiquement les vulnérabilités.
|
||||
- Ouvrez une issue privée si possible ou contactez les mainteneurs.
|
||||
- Merci d'inclure des étapes de reproduction et l'impact.
|
39
docs/README.md
Normal file
39
docs/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Documentation du projet sdk_storage
|
||||
|
||||
Ce dossier documente l'API HTTP, l'architecture et les décisions techniques.
|
||||
|
||||
## API
|
||||
|
||||
- POST `/store` : stocke une valeur hex pour une clé hex 64 chars, `ttl` optionnel (secondes). Quand `--permanent` est passé au binaire, l'absence de `ttl` rend la donnée permanente.
|
||||
- GET `/retrieve/:key` : retourne `{ key, value }` où `value` est encodée en hex.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Service `StorageService` (voir `src/lib.rs`) encapsule la logique de stockage, récupération et nettoyage TTL.
|
||||
- `src/main.rs` démarre Tide avec état `StorageService` et une boucle de nettoyage périodique (60s).
|
||||
|
||||
Voir aussi:
|
||||
- `architecture.md`
|
||||
- `configuration.md`
|
||||
- `guides_principaux.md`
|
||||
- `guides_techniques.md`
|
||||
- `guides_test.md`
|
||||
- `tests_monitoring.md`
|
||||
- `reseau_de_relais.md`
|
||||
- `developpement.md`
|
||||
- `depannage.md`
|
||||
- `performance.md`
|
||||
- `api_json_spec.md`
|
||||
- `api_contrats.md`
|
||||
- `release_guide.md`
|
||||
- `ci_docker_registry.md`
|
||||
|
||||
## REX technique
|
||||
|
||||
- Docker
|
||||
- Build local: `docker build -t sdk_storage:local .`
|
||||
- Run: `docker run --rm -p 8081:8081 -v $PWD/storage:/app/storage sdk_storage:local`
|
||||
- Par défaut `--permanent` est activé via CMD, override possible: `docker run ... sdk_storage -- --permanent`
|
||||
|
||||
- Refactor initial de la logique depuis `main.rs` vers `lib.rs` pour testabilité et séparation des responsabilités.
|
||||
- Durées TTL maintenant validées dans le handler, calcul d'expiration converti en `SystemTime` avant l'appel service.
|
21
docs/api_contrats.md
Normal file
21
docs/api_contrats.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Contrats API
|
||||
|
||||
## Garanties de Contrat
|
||||
- Content-Type JSON, réponses structurées.
|
||||
- Clé: 64 hex (validation stricte), sinon 400.
|
||||
- Valeur: hex valide, sinon 400.
|
||||
- Conflit de clé: 409 si la clé existe déjà.
|
||||
- TTL: min 60, max 31 536 000; par défaut 86 400 si non `--permanent`.
|
||||
- Récupération:
|
||||
- 200 avec `{ key, value }` si trouvée.
|
||||
- 400 si clé invalide.
|
||||
- 404 si absente.
|
||||
|
||||
## Couverture de Tests
|
||||
- Stockage et récupération (succès).
|
||||
- Conflit de clé.
|
||||
- Suppression des expirés via nettoyage.
|
||||
- HTTP `/store`: succès, conflit, clé invalide, valeur invalide.
|
||||
- HTTP `/retrieve`: succès, clé invalide, clé absente.
|
||||
|
||||
Voir `api_json_spec.md` pour les schémas et contraintes détaillés.
|
113
docs/api_json_spec.md
Normal file
113
docs/api_json_spec.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Spécification JSON des API
|
||||
|
||||
## Généralités
|
||||
- Content-Type: `application/json; charset=utf-8`
|
||||
- Encodage des valeurs: chaînes hexadécimales minuscules (0-9a-f).
|
||||
- Modèle d'erreur: corps `{ "message": string }` avec code HTTP approprié.
|
||||
|
||||
## POST /store
|
||||
- Objet requête: `StoreRequest`
|
||||
- Objet réponse (succès/erreur): `ApiResponse`
|
||||
|
||||
### Schéma JSON (StoreRequest)
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "StoreRequest",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "value"],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Clé hexadécimale 64 caractères (32 octets).",
|
||||
"pattern": "^[0-9a-fA-F]{64}$"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "Valeur encodée en hexadécimal.",
|
||||
"pattern": "^[0-9a-fA-F]+$"
|
||||
},
|
||||
"ttl": {
|
||||
"type": "integer",
|
||||
"minimum": 60,
|
||||
"maximum": 31536000,
|
||||
"description": "Durée de vie en secondes. Si absent: défaut 86400 sauf si mode --permanent (aucune expiration)."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Règles de validation et sémantique
|
||||
- `key`: exactement 64 caractères hex (
|
||||
32 octets).
|
||||
- `value`: chaîne hex valide, longueur paire recommandée (représentation d'octets).
|
||||
- `ttl`:
|
||||
- min: 60, max: 31 536 000.
|
||||
- si absent et binaire lancé sans `--permanent`: valeur par défaut 86 400.
|
||||
- si absent et binaire lancé avec `--permanent`: aucune expiration.
|
||||
|
||||
### Réponses
|
||||
- 200 OK: `ApiResponse` (message de succès)
|
||||
- 400 Bad Request: `ApiResponse` (clé/ttl/valeur invalides)
|
||||
- 409 Conflict: `ApiResponse` (clé déjà existante)
|
||||
- 500 Internal Server Error: `ApiResponse`
|
||||
|
||||
### Schéma JSON (ApiResponse)
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ApiResponse",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["message"],
|
||||
"properties": {
|
||||
"message": { "type": "string" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GET /retrieve/:key
|
||||
## GET /health
|
||||
- Aucune donnée d'entrée.
|
||||
- Réponse 200 avec `ApiResponse` `{ "message": "ok" }`.
|
||||
|
||||
- Paramètre de chemin: `key` (hex 64).
|
||||
- Objet réponse (succès): `RetrieveResponse`
|
||||
- Objet réponse (erreur): `ApiResponse`
|
||||
|
||||
### Contraintes
|
||||
- `key` doit respecter `^[0-9a-fA-F]{64}$`.
|
||||
|
||||
### Réponses
|
||||
- 200 OK: `RetrieveResponse`
|
||||
- 400 Bad Request: `ApiResponse` (clé invalide)
|
||||
- 404 Not Found: `ApiResponse` (clé inconnue)
|
||||
- 500 Internal Server Error: `ApiResponse`
|
||||
|
||||
### Schéma JSON (RetrieveResponse)
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "RetrieveResponse",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["key", "value"],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Clé hexadécimale 64 caractères.",
|
||||
"pattern": "^[0-9a-fA-F]{64}$"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "Valeur encodée en hexadécimal.",
|
||||
"pattern": "^[0-9a-fA-F]+$"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Codes d'état et messages
|
||||
- Les messages d'erreur sont informatifs mais ne divulguent pas d'informations sensibles.
|
||||
- Les champs `message` sont destinés à l'humain; ne pas les parser côté client.
|
13
docs/architecture.md
Normal file
13
docs/architecture.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Architecture
|
||||
|
||||
## Flux de Données
|
||||
|
||||
- Entrées: requêtes HTTP `/store`, `/retrieve/:key`.
|
||||
- Traitements: validation clés/TTL, encodage hex, stockage FS hiérarchique, métadonnées TTL.
|
||||
- Sorties: réponses JSON normalisées.
|
||||
|
||||
## Composants
|
||||
|
||||
- Service `StorageService` (I/O disque, TTL, nettoyage).
|
||||
- Serveur HTTP Tide (routes, état partagé).
|
||||
- Nettoyage périodique (60s) basé sur fichiers `.meta`.
|
11
docs/ci_docker_registry.md
Normal file
11
docs/ci_docker_registry.md
Normal file
@ -0,0 +1,11 @@
|
||||
# CI - Variables pour Registre Docker
|
||||
|
||||
Le workflow `.gitea/workflows/docker.yml` requiert ces secrets:
|
||||
|
||||
- `DOCKER_REGISTRY`: registre cible (ex: registry.git.4nkweb.com/4nk)
|
||||
- `DOCKER_USERNAME`: utilisateur du registre
|
||||
- `DOCKER_PASSWORD`: mot de passe/token
|
||||
|
||||
Configurer ces secrets dans les paramètres du dépôt (ou de l’organisation) côté Gitea.
|
||||
|
||||
La cible d’image est `DOCKER_REGISTRY/sdk_storage:latest` (et multi-arch si activé).
|
8
docs/configuration.md
Normal file
8
docs/configuration.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Configuration
|
||||
|
||||
## Services Disponibles
|
||||
- HTTP sur port 8081
|
||||
|
||||
## Variables d'Environnement
|
||||
- `RUST_LOG` (optionnel): niveau de logs.
|
||||
- Pour Docker, monter `/app/storage` si persistance souhaitée.
|
15
docs/demarrage_rapide.md
Normal file
15
docs/demarrage_rapide.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Démarrage Rapide
|
||||
|
||||
## Prérequis
|
||||
- Rust stable et Cargo
|
||||
- Optionnel: Docker
|
||||
|
||||
## Installation
|
||||
- `cargo build`
|
||||
|
||||
## Lancement
|
||||
- `cargo run -- --permanent`
|
||||
|
||||
## Docker (optionnel)
|
||||
- Build: `docker build -t sdk_storage:local .`
|
||||
- Run: `docker run --rm -p 8081:8081 -v $PWD/storage:/app/storage sdk_storage:local`
|
12
docs/depannage.md
Normal file
12
docs/depannage.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Dépannage
|
||||
|
||||
## Problèmes Courants
|
||||
1. Ports déjà utilisés: changer le port de publication Docker.
|
||||
2. Permissions storage: vérifier l'UID/GID et droits du volume.
|
||||
3. Clés invalides: s'assurer d'un hex 64 caractères.
|
||||
|
||||
## Logs détaillés
|
||||
- Exécuter avec `RUST_LOG=info`.
|
||||
|
||||
## Healthchecks
|
||||
- Ajouter une route `/health` (évolution possible) ou ping sur `/retrieve` avec clé connue.
|
13
docs/developpement.md
Normal file
13
docs/developpement.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Développement
|
||||
|
||||
## Structure du Projet
|
||||
- `src/lib.rs`: service métier
|
||||
- `src/main.rs`: serveur HTTP Tide
|
||||
- `tests/`: scénarios d'intégration
|
||||
|
||||
## Ajout d'un Nouveau Service
|
||||
- Créer une abstraction dédiée dans `src/lib.rs` ou module séparé.
|
||||
- Câbler dans `main.rs` via `tide::with_state` si nécessaire.
|
||||
|
||||
## Modification de la Configuration
|
||||
- Mettre à jour `docs/configuration.md` et secrets CI/CD.
|
5
docs/guides_principaux.md
Normal file
5
docs/guides_principaux.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Guides Principaux
|
||||
|
||||
- Concepts de base: clés hex 64, valeurs hex, TTL en secondes.
|
||||
- API: `/store` (POST), `/retrieve/:key` (GET).
|
||||
- Persistance: système de fichiers, sous-dossiers par préfixe de clé.
|
6
docs/guides_techniques.md
Normal file
6
docs/guides_techniques.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Guides Techniques
|
||||
|
||||
- `StorageService`: abstraction des opérations de stockage.
|
||||
- TTL: sérialisé dans `*.meta` (UNIX timestamp secondes).
|
||||
- Nettoyage: parcours des dossiers, suppression données expirées.
|
||||
- Journalisation: sorties standard, intégration possible avec superviseur.
|
5
docs/guides_test.md
Normal file
5
docs/guides_test.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Guides de Test
|
||||
|
||||
- Tests unitaires recommandés sur `StorageService` via répertoires temporaires.
|
||||
- Tests d'intégration HTTP optionnels via client HTTP.
|
||||
- Stratégies: cas TTL min/max, clés invalides, conflits de clé.
|
9
docs/performance.md
Normal file
9
docs/performance.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Performance
|
||||
|
||||
## Ressources Recommandées
|
||||
- Disque rapide si grand volume d'écritures.
|
||||
- Mémoire suffisante pour buffers I/O.
|
||||
|
||||
## Optimisations
|
||||
- Paramétrer la taille des blocs et la stratégie de fsync selon contraintes.
|
||||
- Éviter les collisions de clés, supervision du cleanup TTL.
|
21
docs/release_guide.md
Normal file
21
docs/release_guide.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Guide de Release
|
||||
|
||||
## Préparation
|
||||
- S’assurer que la suite `cargo test` est verte.
|
||||
- Mettre à jour `CHANGELOG.md` avec la version cible.
|
||||
|
||||
## Tagging
|
||||
- Créer un tag annoté: `git tag -a vX.Y.Z -m 'vX.Y.Z'`
|
||||
- Pousser le tag: `git push origin vX.Y.Z`
|
||||
|
||||
## CI
|
||||
- Build binaire (workflow release) déclenché sur tag `v*.*.*`.
|
||||
- Build/push image Docker via workflow `docker.yml` (variables `DOCKER_*` requises).
|
||||
|
||||
## Assets
|
||||
- Binaires disponibles via artefacts CI.
|
||||
- Images Docker taggées `latest` (et potentiellement `vX.Y.Z` selon configuration).
|
||||
|
||||
## Post-release
|
||||
- Mettre à jour la documentation si nécessaire.
|
||||
- Ouvrir une issue pour les améliorations/retours.
|
10
docs/reseau_de_relais.md
Normal file
10
docs/reseau_de_relais.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Réseau de Relais
|
||||
|
||||
## Architecture Mesh
|
||||
- À définir selon déploiement.
|
||||
|
||||
## Ajout de Nœuds Externes
|
||||
- Procédure à documenter si nécessaire.
|
||||
|
||||
## Configuration Externe
|
||||
- Ports, sécurité, endpoints à exposer.
|
9
docs/tests_monitoring.md
Normal file
9
docs/tests_monitoring.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Tests et Monitoring
|
||||
|
||||
## Tests
|
||||
- Unitaires et intégration via `cargo test`.
|
||||
|
||||
## Monitoring
|
||||
- Healthcheck HTTP: endpoint `/health` retourne `{ "message": "ok" }` et code 200.
|
||||
- Exposer métriques avec un reverse proxy/sidecar si nécessaire.
|
||||
- Configurer l'orchestrateur pour vérifier périodiquement `/health`.
|
263
src/lib.rs
Normal file
263
src/lib.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use async_std::fs::{create_dir_all, read_dir, read_to_string, remove_file, File};
|
||||
use async_std::io::WriteExt;
|
||||
use async_std::path::Path;
|
||||
use async_std::stream::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use tide::{log, Request, Response, StatusCode};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StorageService {
|
||||
storage_dir: String,
|
||||
}
|
||||
|
||||
impl StorageService {
|
||||
pub fn new<S: Into<String>>(storage_dir: S) -> Self {
|
||||
Self {
|
||||
storage_dir: storage_dir.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_path(&self, key: &str) -> String {
|
||||
let dir_name = format!("{}/{}", self.storage_dir, &key[..2]);
|
||||
let file_path = format!("{}/{}", dir_name, &key[2..]);
|
||||
file_path
|
||||
}
|
||||
|
||||
pub async fn store_data(
|
||||
&self,
|
||||
key: &str,
|
||||
value: &[u8],
|
||||
expires_at: Option<SystemTime>,
|
||||
) -> Result<(), tide::Error> {
|
||||
let file_name = self.get_file_path(key);
|
||||
let file_path = Path::new(&file_name);
|
||||
|
||||
if file_path.exists().await {
|
||||
return Err(tide::Error::from_str(StatusCode::Conflict, "Key already exists"));
|
||||
}
|
||||
|
||||
create_dir_all(file_path.parent().ok_or(tide::Error::from_str(
|
||||
StatusCode::InternalServerError,
|
||||
"File path doesn't have parent",
|
||||
))?)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
|
||||
let metadata_path = format!("{}.meta", file_name);
|
||||
|
||||
let mut file = File::create(&file_path)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
file.write_all(value)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
|
||||
let metadata = Metadata {
|
||||
expires_at: expires_at.map(system_time_to_unix),
|
||||
};
|
||||
|
||||
let metadata_json = serde_json::to_string(&metadata)
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
let mut meta_file = File::create(&metadata_path)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
meta_file
|
||||
.write_all(metadata_json.as_bytes())
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn retrieve_data(&self, key: &str) -> Result<Vec<u8>, String> {
|
||||
let file_path = format!("{}/{}/{}", self.storage_dir, &key[..2], &key[2..]);
|
||||
|
||||
let mut file = File::open(&file_path)
|
||||
.await
|
||||
.map_err(|_| "Key not found.".to_string())?;
|
||||
let mut buffer = Vec::new();
|
||||
async_std::io::ReadExt::read_to_end(&mut file, &mut buffer)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub async fn cleanup_expired_files_once(&self) -> Result<(), String> {
|
||||
let mut entries = read_dir(&self.storage_dir)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read storage dir: {}", e))?;
|
||||
let now = system_time_to_unix(SystemTime::now());
|
||||
while let Some(entry) = entries.next().await {
|
||||
let e = entry.map_err(|e| format!("entry returned error: {}", e))?;
|
||||
let path = e.path();
|
||||
if path.is_dir().await {
|
||||
if let Ok(mut sub_entries) = read_dir(&path).await {
|
||||
while let Some(sub_entry) = sub_entries.next().await {
|
||||
if let Ok(sub_entry) = sub_entry {
|
||||
let file_path = sub_entry.path();
|
||||
if file_path.extension() == Some("meta".as_ref()) {
|
||||
self.handle_file_cleanup(now, &file_path).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_file_cleanup(&self, now: u64, meta_path: &Path) -> Result<(), String> {
|
||||
let meta_content = read_to_string(meta_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read metadata: {}", e.to_string()))?;
|
||||
let metadata: Metadata = serde_json::from_str(&meta_content)
|
||||
.map_err(|e| format!("Failed to parse metadata: {}", e.to_string()))?;
|
||||
|
||||
if metadata.expires_at.is_some() && metadata.expires_at.unwrap() < now {
|
||||
let data_file_path = meta_path.with_extension("");
|
||||
remove_file(&data_file_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to remove data file: {}", e.to_string()))?;
|
||||
remove_file(meta_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to remove metadata file: {}", e.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Metadata {
|
||||
pub expires_at: Option<u64>,
|
||||
}
|
||||
|
||||
pub fn system_time_to_unix(system_time: SystemTime) -> u64 {
|
||||
system_time
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("SystemTime before UNIX_EPOCH!")
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
pub fn unix_to_system_time(unix_timestamp: u64) -> SystemTime {
|
||||
UNIX_EPOCH + Duration::from_secs(unix_timestamp)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct StoreRequest {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
pub ttl: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ApiResponse { pub message: String }
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct RetrieveResponse { pub key: String, pub value: String }
|
||||
|
||||
pub async fn handle_health(_req: Request<StorageService>) -> tide::Result<Response> {
|
||||
Ok(Response::builder(StatusCode::Ok)
|
||||
.body(serde_json::to_value(&ApiResponse { message: "ok".into() })?)
|
||||
.build())
|
||||
}
|
||||
|
||||
pub async fn handle_store(mut req: Request<StorageService>, no_ttl_permanent: bool) -> tide::Result<Response> {
|
||||
// Extract key from URL parameter
|
||||
let key: String = req.param("key")?.to_string();
|
||||
|
||||
// Validate key format
|
||||
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body("Invalid key: must be a 32 bytes hex string.".to_string())
|
||||
.build());
|
||||
}
|
||||
|
||||
// Get TTL from query parameter (optional)
|
||||
let ttl: Option<u64> = req.url().query_pairs()
|
||||
.find(|(key, _)| key == "ttl")
|
||||
.and_then(|(_, value)| value.parse().ok());
|
||||
log::info!("ttl: {:?}", ttl);
|
||||
|
||||
let live_for: Option<Duration> = if let Some(ttl) = ttl {
|
||||
if ttl < 60 {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!("Invalid ttl: must be at least {} seconds.", 60))
|
||||
.build());
|
||||
} else if ttl > 31_536_000 {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!("Invalid ttl: must be at most {} seconds.", 31_536_000))
|
||||
.build());
|
||||
}
|
||||
Some(Duration::from_secs(ttl))
|
||||
} else if no_ttl_permanent {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_secs(86_400))
|
||||
};
|
||||
|
||||
let expires_at: Option<SystemTime> = match live_for {
|
||||
Some(lf) => Some(
|
||||
SystemTime::now()
|
||||
.checked_add(lf)
|
||||
.ok_or(tide::Error::from_str(StatusCode::BadRequest, "Invalid ttl"))?
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
// Read binary data directly from request body
|
||||
let value_bytes = match req.body_bytes().await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!("Failed to read request body: {}", e))
|
||||
.build());
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("received {} bytes", value_bytes.len());
|
||||
|
||||
let svc = req.state();
|
||||
match svc.store_data(&key, &value_bytes, expires_at).await {
|
||||
Ok(()) => Ok(Response::builder(StatusCode::Ok)
|
||||
.body(serde_json::to_value(&ApiResponse {
|
||||
message: "Data stored successfully.".to_string(),
|
||||
})?)
|
||||
.build()),
|
||||
Err(e) => Ok(Response::builder(e.status())
|
||||
.body(serde_json::to_value(&ApiResponse {
|
||||
message: e.to_string(),
|
||||
})?)
|
||||
.build()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_retrieve(req: Request<StorageService>) -> tide::Result<Response> {
|
||||
let key: String = req.param("key")?.to_string();
|
||||
|
||||
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body("Invalid key: must be a 32 bytes hex string.".to_string())
|
||||
.build());
|
||||
}
|
||||
|
||||
let svc = req.state();
|
||||
match svc.retrieve_data(&key).await {
|
||||
Ok(value) => {
|
||||
Ok(Response::builder(StatusCode::Ok)
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.body(value)
|
||||
.build())
|
||||
}
|
||||
Err(e) => Ok(Response::builder(StatusCode::NotFound).body(e).build()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_app(no_ttl_permanent: bool, storage_dir: impl Into<String>) -> tide::Server<StorageService> {
|
||||
let svc = StorageService::new(storage_dir);
|
||||
let mut app = tide::with_state(svc);
|
||||
app.at("/health").get(handle_health);
|
||||
app.at("/store/:key").post(move |req| handle_store(req, no_ttl_permanent));
|
||||
app.at("/retrieve/:key").get(handle_retrieve);
|
||||
app
|
||||
}
|
307
src/main.rs
307
src/main.rs
@ -1,305 +1,44 @@
|
||||
use async_std::fs::{create_dir_all, read_dir, read_to_string, remove_file, File};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use std::env;
|
||||
|
||||
use async_std::io::WriteExt;
|
||||
use async_std::path::Path;
|
||||
use async_std::stream::StreamExt;
|
||||
use async_std::task;
|
||||
use tide::{Request, Response, StatusCode};
|
||||
use async_std::fs::create_dir_all;
|
||||
use sdk_storage::{StorageService, create_app};
|
||||
use tide::log;
|
||||
|
||||
const STORAGE_DIR: &str = "./storage";
|
||||
const PORT: u16 = 8081;
|
||||
const MIN_TTL: u64 = 60; // 1 minute
|
||||
const DEFAULT_TTL: u64 = 86400; // 1 day
|
||||
const MAX_TTL: u64 = 31_536_000; // 1 year, to be discussed
|
||||
const PORT: u16 = 8080;
|
||||
const DEFAULT_TTL: u64 = 86400;
|
||||
|
||||
/// Scans storage and removes expired files
|
||||
async fn cleanup_expired_files() {
|
||||
loop {
|
||||
// Traverse storage directory
|
||||
let mut entries = match read_dir(STORAGE_DIR).await {
|
||||
Ok(entry) => entry,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read storage dir: {}", e);
|
||||
task::sleep(Duration::from_secs(60)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let now = system_time_to_unix(SystemTime::now());
|
||||
while let Some(entry) = entries.next().await {
|
||||
let e = match entry {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
eprintln!("entry returned error: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let path = e.path();
|
||||
if path.is_dir().await {
|
||||
if let Ok(mut sub_entries) = read_dir(&path).await {
|
||||
while let Some(sub_entry) = sub_entries.next().await {
|
||||
if let Ok(sub_entry) = sub_entry {
|
||||
let file_path = sub_entry.path();
|
||||
if file_path.extension() == Some("meta".as_ref()) {
|
||||
if let Err(err) = handle_file_cleanup(now, &file_path).await {
|
||||
eprintln!("Error cleaning file {:?}: {}", file_path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sleep for 1 minute before next cleanup
|
||||
task::sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Metadata {
|
||||
expires_at: Option<u64>,
|
||||
}
|
||||
|
||||
/// Converts a `SystemTime` to a UNIX timestamp (seconds since UNIX epoch).
|
||||
fn system_time_to_unix(system_time: SystemTime) -> u64 {
|
||||
system_time
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("SystemTime before UNIX_EPOCH!")
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
/// Converts a UNIX timestamp (seconds since UNIX epoch) back to `SystemTime`.
|
||||
fn unix_to_system_time(unix_timestamp: u64) -> SystemTime {
|
||||
UNIX_EPOCH + Duration::from_secs(unix_timestamp)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct StoreRequest {
|
||||
key: String,
|
||||
value: String,
|
||||
ttl: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ApiResponse {
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RetrieveResponse {
|
||||
key: String,
|
||||
value: String,
|
||||
}
|
||||
|
||||
async fn get_file_path(key: &str) -> String {
|
||||
let dir_name = format!("{}/{}", STORAGE_DIR, &key[..2]);
|
||||
let file_path = format!("{}/{}", dir_name, &key[2..]);
|
||||
|
||||
file_path
|
||||
}
|
||||
|
||||
/// Store data on the filesystem
|
||||
async fn store_data(key: &str, value: &[u8], expires_at: Option<SystemTime>) -> Result<(), tide::Error> {
|
||||
let file_name = get_file_path(key).await;
|
||||
let file_path = Path::new(&file_name);
|
||||
|
||||
// Check if key exists
|
||||
if file_path.exists().await {
|
||||
return Err(tide::Error::from_str(
|
||||
StatusCode::Conflict,
|
||||
"Key already exists",
|
||||
));
|
||||
}
|
||||
|
||||
create_dir_all(file_path.parent().ok_or(tide::Error::from_str(
|
||||
StatusCode::InternalServerError,
|
||||
"File path doesn't have parent",
|
||||
))?)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
|
||||
let metadata_path = format!("{}.meta", file_name);
|
||||
|
||||
let mut file = File::create(&file_path)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
file.write_all(value)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
|
||||
let metadata = Metadata {
|
||||
expires_at: expires_at.map(|e| system_time_to_unix(e)),
|
||||
};
|
||||
|
||||
let metadata_json = serde_json::to_string(&metadata)
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
let mut meta_file = File::create(&metadata_path)
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
meta_file
|
||||
.write_all(metadata_json.as_bytes())
|
||||
.await
|
||||
.map_err(|e| tide::Error::new(StatusCode::InternalServerError, e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn retrieve_data(key: &str) -> Result<Vec<u8>, String> {
|
||||
let file_path = format!("{}/{}/{}", STORAGE_DIR, &key[..2], &key[2..]);
|
||||
|
||||
let mut file = File::open(&file_path)
|
||||
.await
|
||||
.map_err(|_| "Key not found.".to_string())?;
|
||||
let mut buffer = Vec::new();
|
||||
async_std::io::ReadExt::read_to_end(&mut file, &mut buffer)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Handler for the /store endpoint
|
||||
async fn handle_store(mut req: Request<()>, no_ttl_permanent: bool) -> tide::Result<Response> {
|
||||
// Parse the JSON body
|
||||
let data: StoreRequest = match req.body_json().await {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!("Invalid request: {}", e))
|
||||
.build());
|
||||
}
|
||||
};
|
||||
|
||||
// Validate the key
|
||||
if data.key.len() != 64 || !data.key.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body("Invalid key: must be a 32 bytes hex string.".to_string())
|
||||
.build());
|
||||
}
|
||||
|
||||
// Validate the ttl
|
||||
let live_for: Option<Duration> = if let Some(ttl) = data.ttl {
|
||||
if ttl < MIN_TTL {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!(
|
||||
"Invalid ttl: must be at least {} seconds.",
|
||||
MIN_TTL
|
||||
))
|
||||
.build());
|
||||
} else if ttl > MAX_TTL {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!("Invalid ttl: must be at most {} seconds.", MAX_TTL))
|
||||
.build());
|
||||
}
|
||||
Some(Duration::from_secs(ttl))
|
||||
} else if no_ttl_permanent {
|
||||
// When no_ttl_permanent is true, requests without TTL are permanent
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_secs(DEFAULT_TTL))
|
||||
};
|
||||
|
||||
let expires_at: Option<SystemTime> = if let Some(live_for) = live_for {
|
||||
let now = SystemTime::now();
|
||||
Some(now
|
||||
.checked_add(live_for)
|
||||
.ok_or(tide::Error::from_str(StatusCode::BadRequest, "Invalid ttl"))?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Decode the value from Base64
|
||||
let value_bytes = match hex::decode(&data.value) {
|
||||
Ok(value) => value,
|
||||
Err(e) => {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body(format!("Invalid request: {}", e))
|
||||
.build());
|
||||
}
|
||||
};
|
||||
|
||||
// Store the data
|
||||
match store_data(&data.key, &value_bytes, expires_at).await {
|
||||
Ok(()) => Ok(Response::builder(StatusCode::Ok)
|
||||
.body(serde_json::to_value(&ApiResponse {
|
||||
message: "Data stored successfully.".to_string(),
|
||||
})?)
|
||||
.build()),
|
||||
Err(e) => Ok(Response::builder(e.status())
|
||||
.body(serde_json::to_value(&ApiResponse {
|
||||
message: e.to_string(),
|
||||
})?)
|
||||
.build()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_retrieve(req: Request<()>) -> tide::Result<Response> {
|
||||
let key: String = req.param("key")?.to_string();
|
||||
|
||||
if key.len() != 64 || !key.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Ok(Response::builder(StatusCode::BadRequest)
|
||||
.body("Invalid key: must be a 32 bytes hex string.".to_string())
|
||||
.build());
|
||||
}
|
||||
|
||||
match retrieve_data(&key).await {
|
||||
Ok(value) => {
|
||||
let encoded_value = hex::encode(value);
|
||||
Ok(Response::builder(StatusCode::Ok)
|
||||
.body(serde_json::to_value(&RetrieveResponse {
|
||||
key,
|
||||
value: encoded_value,
|
||||
})?)
|
||||
.build())
|
||||
}
|
||||
Err(e) => Ok(Response::builder(StatusCode::NotFound).body(e).build()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks a metadata file and deletes the associated data file if expired
|
||||
async fn handle_file_cleanup(now: u64, meta_path: &Path) -> Result<(), String> {
|
||||
let meta_content = read_to_string(meta_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read metadata: {}", e.to_string()))?;
|
||||
let metadata: Metadata = serde_json::from_str(&meta_content)
|
||||
.map_err(|e| format!("Failed to parse metadata: {}", e.to_string()))?;
|
||||
|
||||
if metadata.expires_at.is_some() && metadata.expires_at.unwrap() < now {
|
||||
let data_file_path = meta_path.with_extension("");
|
||||
remove_file(&data_file_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to remove data file: {}", e.to_string()))?;
|
||||
remove_file(meta_path)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to remove metadata file: {}", e.to_string()))?;
|
||||
println!("Removed expired file: {:?}", data_file_path);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
// Initialize logging
|
||||
env_logger::init();
|
||||
log::info!("Starting server");
|
||||
// Parse command line arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let no_ttl_permanent = args.iter().any(|arg| arg == "--permanent");
|
||||
|
||||
|
||||
if no_ttl_permanent {
|
||||
println!("No-TTL requests will be treated as permanent");
|
||||
} else {
|
||||
println!("No-TTL requests will use default TTL of {} seconds", DEFAULT_TTL);
|
||||
}
|
||||
|
||||
create_dir_all(STORAGE_DIR)
|
||||
.await
|
||||
.expect("Failed to create storage directory.");
|
||||
|
||||
task::spawn(cleanup_expired_files());
|
||||
let svc = StorageService::new(STORAGE_DIR);
|
||||
create_dir_all(STORAGE_DIR).await.expect("Failed to create storage directory.");
|
||||
|
||||
let mut app = tide::new();
|
||||
app.at("/store").post(move |req| handle_store(req, no_ttl_permanent));
|
||||
app.at("/retrieve/:key").get(handle_retrieve);
|
||||
// background cleanup loop
|
||||
let svc_clone = svc.clone();
|
||||
task::spawn(async move {
|
||||
loop {
|
||||
if let Err(e) = svc_clone.cleanup_expired_files_once().await {
|
||||
eprintln!("cleanup error: {}", e);
|
||||
}
|
||||
task::sleep(std::time::Duration::from_secs(60)).await;
|
||||
}
|
||||
});
|
||||
|
||||
let mut app = create_app(no_ttl_permanent, STORAGE_DIR);
|
||||
app.listen(format!("0.0.0.0:{}", PORT)).await?;
|
||||
|
||||
println!("Server running at http://0.0.0.0:{}", PORT);
|
||||
|
135
tests/http_api.rs
Normal file
135
tests/http_api.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use sdk_storage::{StorageService, unix_to_system_time, create_app};
|
||||
use tempfile::TempDir;
|
||||
use surf::Client;
|
||||
|
||||
#[async_std::test]
|
||||
async fn store_and_retrieve_hex_in_tempdir() {
|
||||
let td = TempDir::new().unwrap();
|
||||
let dir_path = td.path().to_string_lossy().to_string();
|
||||
let svc = StorageService::new(dir_path);
|
||||
|
||||
let key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
let value = b"hello";
|
||||
let expires = Some(unix_to_system_time(60 + sdk_storage::system_time_to_unix(std::time::SystemTime::now())));
|
||||
|
||||
svc.store_data(key, value, expires).await.unwrap();
|
||||
let got = svc.retrieve_data(key).await.unwrap();
|
||||
assert_eq!(got, value);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn conflict_on_duplicate_key() {
|
||||
let td = TempDir::new().unwrap();
|
||||
let dir_path = td.path().to_string_lossy().to_string();
|
||||
let svc = StorageService::new(dir_path);
|
||||
|
||||
let key = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
||||
let value = b"data";
|
||||
|
||||
svc.store_data(key, value, None).await.unwrap();
|
||||
let err = svc.store_data(key, value, None).await.err().expect("should error");
|
||||
assert_eq!(err.status(), tide::StatusCode::Conflict);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn cleanup_removes_expired() {
|
||||
let td = TempDir::new().unwrap();
|
||||
let dir_path = td.path().to_string_lossy().to_string();
|
||||
let svc = StorageService::new(dir_path.clone());
|
||||
|
||||
let key = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
|
||||
let value = b"x";
|
||||
// expiration très proche: maintenant - 1s
|
||||
let past = sdk_storage::system_time_to_unix(std::time::SystemTime::now()).saturating_sub(1);
|
||||
let expires = Some(unix_to_system_time(past));
|
||||
|
||||
svc.store_data(key, value, expires).await.unwrap();
|
||||
// cleanup one-shot
|
||||
svc.cleanup_expired_files_once().await.unwrap();
|
||||
let res = svc.retrieve_data(key).await;
|
||||
assert!(res.is_err(), "expired key should be removed");
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn http_store_success_and_conflicts_and_invalids() {
|
||||
// app with permanent=false so default TTL applies when missing
|
||||
let td = TempDir::new().unwrap();
|
||||
let storage = td.path().to_string_lossy().to_string();
|
||||
let mut app = create_app(false, storage);
|
||||
let listener = async_std::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
async_std::task::spawn(async move { app.listen(listener).await.unwrap() });
|
||||
|
||||
let client = Client::new();
|
||||
let base = format!("http://{}", addr);
|
||||
|
||||
// success
|
||||
let key = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
|
||||
let body = serde_json::json!({"key": key, "value": "01aa", "ttl": 120});
|
||||
let res = client.post(format!("{}/store", base)).body_json(&body).unwrap().await.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
|
||||
// conflict
|
||||
let res2 = client.post(format!("{}/store", base)).body_json(&body).unwrap().await.unwrap();
|
||||
assert_eq!(res2.status(), 409);
|
||||
|
||||
// invalid key
|
||||
let bad = serde_json::json!({"key": "xyz", "value": "01"});
|
||||
let res3 = client.post(format!("{}/store", base)).body_json(&bad).unwrap().await.unwrap();
|
||||
assert_eq!(res3.status(), 400);
|
||||
|
||||
// invalid value
|
||||
let badv = serde_json::json!({"key": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "value": "zz"});
|
||||
let res4 = client.post(format!("{}/store", base)).body_json(&badv).unwrap().await.unwrap();
|
||||
assert_eq!(res4.status(), 400);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn http_retrieve_success_and_invalid_and_notfound() {
|
||||
let td = TempDir::new().unwrap();
|
||||
let storage = td.path().to_string_lossy().to_string();
|
||||
let mut app = create_app(true, storage.clone());
|
||||
let listener = async_std::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
async_std::task::spawn(async move { app.listen(listener).await.unwrap() });
|
||||
|
||||
let client = Client::new();
|
||||
let base = format!("http://{}", addr);
|
||||
|
||||
// prepare stored value (permanent mode)
|
||||
let svc = StorageService::new(storage);
|
||||
let key = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
||||
svc.store_data(key, b"hi", None).await.unwrap();
|
||||
|
||||
// success
|
||||
let mut res = client.get(format!("{}/retrieve/{}", base, key)).await.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
let v: serde_json::Value = res.body_json().await.unwrap();
|
||||
assert_eq!(v["value"].as_str().unwrap(), hex::encode("hi"));
|
||||
|
||||
// invalid key
|
||||
let res2 = client.get(format!("{}/retrieve/{}", base, "bad")).await.unwrap();
|
||||
assert_eq!(res2.status(), 400);
|
||||
|
||||
// not found
|
||||
let k2 = "1111111111111111111111111111111111111111111111111111111111111111";
|
||||
let res3 = client.get(format!("{}/retrieve/{}", base, k2)).await.unwrap();
|
||||
assert_eq!(res3.status(), 404);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn http_health_ok() {
|
||||
let td = TempDir::new().unwrap();
|
||||
let storage = td.path().to_string_lossy().to_string();
|
||||
let mut app = create_app(true, storage);
|
||||
let listener = async_std::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
async_std::task::spawn(async move { app.listen(listener).await.unwrap() });
|
||||
|
||||
let client = Client::new();
|
||||
let base = format!("http://{}", addr);
|
||||
let mut res = client.get(format!("{}/health", base)).await.unwrap();
|
||||
assert!(res.status().is_success());
|
||||
let v: serde_json::Value = res.body_json().await.unwrap();
|
||||
assert_eq!(v["message"].as_str().unwrap(), "ok");
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user