├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── docker-publish.yml │ └── publish-crates.yml ├── .gitignore ├── .rusty-hook.toml ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── build.rs ├── examples └── local_file.rs ├── resources └── fixtures │ ├── repo.json │ ├── secrets.json │ ├── segments.json │ ├── segments_update.json │ ├── toggles.json │ └── toggles_update.json ├── rust-toolchain.toml ├── rustfmt.toml └── src ├── base.rs ├── http ├── handler.rs └── mod.rs ├── lib.rs ├── main.rs ├── realtime.rs ├── repo.rs └── secrets.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Install nightly 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: nightly 24 | override: true 25 | 26 | - name: Build 27 | run: cargo build --verbose 28 | 29 | - name: Run Test 30 | run: cargo test --verbose 31 | 32 | test: 33 | runs-on: ubuntu-latest 34 | container: 35 | image: xd009642/tarpaulin:develop-nightly 36 | options: --security-opt seccomp=unconfined 37 | steps: 38 | - name: checkout repository 39 | uses: actions/checkout@v2 40 | 41 | - name: generate code coverage 42 | run: | 43 | cargo +nightly tarpaulin --verbose --workspace --timeout 120 --out Xml 44 | 45 | - name: upload to codecov.io 46 | uses: codecov/codecov-action@v2 47 | with: 48 | fail_ci_if_error: true 49 | 50 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push_to_registry: 13 | name: Push Docker image to Docker Hub 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check out the repo 17 | uses: actions/checkout@v3 18 | - name: Set up Docker Buildx 19 | uses: crazy-max/ghaction-docker-buildx@v3 20 | - 21 | name: Cache Docker layers 22 | uses: actions/cache@v2 23 | id: cache 24 | with: 25 | path: /tmp/.buildx-cache 26 | key: ${{ runner.os }}-buildx-${{ github.sha }} 27 | restore-keys: | 28 | ${{ runner.os }}-buildx- 29 | - 30 | name: Login to DockerHub 31 | uses: docker/login-action@v1 32 | with: 33 | username: ${{ secrets.DOCKER_USERNAME }} 34 | password: ${{ secrets.DOCKER_PASSWORD }} 35 | - 36 | name: Docker Buildx (push) 37 | run: | 38 | docker buildx build \ 39 | --cache-from "type=local,src=/tmp/.buildx-cache" \ 40 | --platform linux/amd64 \ 41 | --output "type=image,push=true" \ 42 | --tag featureprobe/server:latest \ 43 | --tag featureprobe/server:${GITHUB_REF#refs/tags/} \ 44 | --file ./Dockerfile ./ 45 | - 46 | name: Inspect image 47 | run: | 48 | docker buildx imagetools inspect featureprobe/server 49 | -------------------------------------------------------------------------------- /.github/workflows/publish-crates.yml: -------------------------------------------------------------------------------- 1 | name: Publish Crates 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | override: true 20 | - uses: katyo/publish-crates@v1 21 | with: 22 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | .idea 4 | .vscode 5 | -------------------------------------------------------------------------------- /.rusty-hook.toml: -------------------------------------------------------------------------------- 1 | [hooks] 2 | pre-push = "cargo fmt -- --check && cargo clippy -- -Dwarnings && make test" 3 | 4 | [logging] 5 | verbose = true 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler32" 7 | version = "1.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.7.6" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 16 | dependencies = [ 17 | "getrandom", 18 | "once_cell", 19 | "version_check", 20 | ] 21 | 22 | [[package]] 23 | name = "aho-corasick" 24 | version = "0.7.19" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 27 | dependencies = [ 28 | "memchr", 29 | ] 30 | 31 | [[package]] 32 | name = "anyhow" 33 | version = "1.0.70" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" 36 | 37 | [[package]] 38 | name = "async-stream" 39 | version = "0.3.3" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" 42 | dependencies = [ 43 | "async-stream-impl", 44 | "futures-core", 45 | ] 46 | 47 | [[package]] 48 | name = "async-stream-impl" 49 | version = "0.3.3" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" 52 | dependencies = [ 53 | "proc-macro2", 54 | "quote", 55 | "syn", 56 | ] 57 | 58 | [[package]] 59 | name = "async-trait" 60 | version = "0.1.58" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" 63 | dependencies = [ 64 | "proc-macro2", 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "autocfg" 71 | version = "1.1.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 74 | 75 | [[package]] 76 | name = "axum" 77 | version = "0.5.17" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" 80 | dependencies = [ 81 | "async-trait", 82 | "axum-core", 83 | "bitflags", 84 | "bytes", 85 | "futures-util", 86 | "headers", 87 | "http", 88 | "http-body", 89 | "hyper", 90 | "itoa", 91 | "matchit", 92 | "memchr", 93 | "mime", 94 | "percent-encoding", 95 | "pin-project-lite", 96 | "serde", 97 | "serde_json", 98 | "serde_urlencoded", 99 | "sync_wrapper", 100 | "tokio", 101 | "tower", 102 | "tower-http 0.3.4", 103 | "tower-layer", 104 | "tower-service", 105 | ] 106 | 107 | [[package]] 108 | name = "axum-core" 109 | version = "0.2.9" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" 112 | dependencies = [ 113 | "async-trait", 114 | "bytes", 115 | "futures-util", 116 | "http", 117 | "http-body", 118 | "mime", 119 | "tower-layer", 120 | "tower-service", 121 | ] 122 | 123 | [[package]] 124 | name = "axum-extra" 125 | version = "0.2.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "bb752c1940e365de8be991c909da4af5e07fe84afaaa8c57abd957f1555d8665" 128 | dependencies = [ 129 | "axum", 130 | "axum-macros", 131 | "bytes", 132 | "http", 133 | "mime", 134 | "percent-encoding", 135 | "pin-project-lite", 136 | "serde", 137 | "tower", 138 | "tower-http 0.2.5", 139 | "tower-layer", 140 | "tower-service", 141 | ] 142 | 143 | [[package]] 144 | name = "axum-macros" 145 | version = "0.2.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "6293dae2ec708e679da6736e857cf8532886ef258e92930f38279c12641628b8" 148 | dependencies = [ 149 | "heck", 150 | "proc-macro2", 151 | "quote", 152 | "syn", 153 | ] 154 | 155 | [[package]] 156 | name = "backoff" 157 | version = "0.4.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" 160 | dependencies = [ 161 | "getrandom", 162 | "instant", 163 | "rand", 164 | ] 165 | 166 | [[package]] 167 | name = "base64" 168 | version = "0.13.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 171 | 172 | [[package]] 173 | name = "base64" 174 | version = "0.21.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 177 | 178 | [[package]] 179 | name = "bitflags" 180 | version = "1.3.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 183 | 184 | [[package]] 185 | name = "block-buffer" 186 | version = "0.10.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 189 | dependencies = [ 190 | "generic-array", 191 | ] 192 | 193 | [[package]] 194 | name = "bumpalo" 195 | version = "3.11.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 198 | 199 | [[package]] 200 | name = "byteorder" 201 | version = "1.4.3" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 204 | 205 | [[package]] 206 | name = "bytes" 207 | version = "1.2.1" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 210 | 211 | [[package]] 212 | name = "cc" 213 | version = "1.0.74" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" 216 | dependencies = [ 217 | "jobserver", 218 | ] 219 | 220 | [[package]] 221 | name = "cfg-if" 222 | version = "1.0.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 225 | 226 | [[package]] 227 | name = "ci_info" 228 | version = "0.10.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "24f638c70e8c5753795cc9a8c07c44da91554a09e4cf11a7326e8161b0a3c45e" 231 | dependencies = [ 232 | "envmnt", 233 | ] 234 | 235 | [[package]] 236 | name = "config" 237 | version = "0.13.2" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "11f1667b8320afa80d69d8bbe40830df2c8a06003d86f73d8e003b2c48df416d" 240 | dependencies = [ 241 | "async-trait", 242 | "json5", 243 | "lazy_static", 244 | "nom", 245 | "pathdiff", 246 | "ron", 247 | "rust-ini", 248 | "serde", 249 | "serde_json", 250 | "toml", 251 | "yaml-rust", 252 | ] 253 | 254 | [[package]] 255 | name = "core-foundation" 256 | version = "0.9.3" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 259 | dependencies = [ 260 | "core-foundation-sys", 261 | "libc", 262 | ] 263 | 264 | [[package]] 265 | name = "core-foundation-sys" 266 | version = "0.8.3" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 269 | 270 | [[package]] 271 | name = "cpufeatures" 272 | version = "0.2.5" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 275 | dependencies = [ 276 | "libc", 277 | ] 278 | 279 | [[package]] 280 | name = "crypto-common" 281 | version = "0.1.6" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 284 | dependencies = [ 285 | "generic-array", 286 | "typenum", 287 | ] 288 | 289 | [[package]] 290 | name = "ctor" 291 | version = "0.1.26" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" 294 | dependencies = [ 295 | "quote", 296 | "syn", 297 | ] 298 | 299 | [[package]] 300 | name = "dashmap" 301 | version = "5.4.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" 304 | dependencies = [ 305 | "cfg-if", 306 | "hashbrown", 307 | "lock_api", 308 | "once_cell", 309 | "parking_lot_core", 310 | ] 311 | 312 | [[package]] 313 | name = "digest" 314 | version = "0.10.5" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" 317 | dependencies = [ 318 | "block-buffer", 319 | "crypto-common", 320 | ] 321 | 322 | [[package]] 323 | name = "dlv-list" 324 | version = "0.3.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" 327 | 328 | [[package]] 329 | name = "encoding_rs" 330 | version = "0.8.31" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 333 | dependencies = [ 334 | "cfg-if", 335 | ] 336 | 337 | [[package]] 338 | name = "engineio-rs" 339 | version = "0.1.5" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "60e43e52ae86016c23b8002b9cb5d81342030933fb8f5de0baac9d9bf03d81ed" 342 | dependencies = [ 343 | "adler32", 344 | "async-stream", 345 | "async-trait", 346 | "base64 0.13.1", 347 | "bytes", 348 | "dashmap", 349 | "futures-util", 350 | "http", 351 | "httparse", 352 | "reqwest", 353 | "serde", 354 | "serde_json", 355 | "thiserror", 356 | "tokio", 357 | "tokio-tungstenite", 358 | "tokio-util", 359 | "tracing", 360 | "tungstenite", 361 | ] 362 | 363 | [[package]] 364 | name = "enum-iterator" 365 | version = "1.1.3" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" 368 | dependencies = [ 369 | "enum-iterator-derive", 370 | ] 371 | 372 | [[package]] 373 | name = "enum-iterator-derive" 374 | version = "1.1.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" 377 | dependencies = [ 378 | "proc-macro2", 379 | "quote", 380 | "syn", 381 | ] 382 | 383 | [[package]] 384 | name = "envmnt" 385 | version = "0.8.4" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059" 388 | dependencies = [ 389 | "fsio", 390 | "indexmap", 391 | ] 392 | 393 | [[package]] 394 | name = "feature-probe-event" 395 | version = "1.1.3" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "9ef547aae6c8bf890c41149452fdd3bcd5a971054079b42a089fd5752ee8b31c" 398 | dependencies = [ 399 | "axum", 400 | "headers", 401 | "parking_lot", 402 | "reqwest", 403 | "serde", 404 | "serde_json", 405 | "thiserror", 406 | "tokio", 407 | "tracing", 408 | "url", 409 | ] 410 | 411 | [[package]] 412 | name = "feature-probe-server" 413 | version = "2.0.1" 414 | dependencies = [ 415 | "anyhow", 416 | "axum", 417 | "axum-extra", 418 | "base64 0.13.1", 419 | "config", 420 | "feature-probe-event", 421 | "feature-probe-server-sdk", 422 | "futures", 423 | "log", 424 | "parking_lot", 425 | "reqwest", 426 | "rusty-hook", 427 | "serde", 428 | "serde_json", 429 | "socketio-rs", 430 | "thiserror", 431 | "time", 432 | "tokio", 433 | "tracing", 434 | "tracing-core", 435 | "tracing-log", 436 | "tracing-subscriber", 437 | "url", 438 | "vergen", 439 | ] 440 | 441 | [[package]] 442 | name = "feature-probe-server-sdk" 443 | version = "1.2.12" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "b00a38fd3da26c8517b851a4e648056dd7e5d8ac23236c00804e12b46d401b07" 446 | dependencies = [ 447 | "anyhow", 448 | "byteorder", 449 | "dashmap", 450 | "futures-util", 451 | "headers", 452 | "http", 453 | "lazy_static", 454 | "minstant", 455 | "parking_lot", 456 | "rand", 457 | "regex", 458 | "reqwest", 459 | "semver", 460 | "serde", 461 | "serde_json", 462 | "sha1", 463 | "thiserror", 464 | "tokio", 465 | "tracing", 466 | "url", 467 | ] 468 | 469 | [[package]] 470 | name = "fnv" 471 | version = "1.0.7" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 474 | 475 | [[package]] 476 | name = "form_urlencoded" 477 | version = "1.1.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 480 | dependencies = [ 481 | "percent-encoding", 482 | ] 483 | 484 | [[package]] 485 | name = "fsio" 486 | version = "0.1.3" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3" 489 | 490 | [[package]] 491 | name = "futures" 492 | version = "0.3.25" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 495 | dependencies = [ 496 | "futures-channel", 497 | "futures-core", 498 | "futures-executor", 499 | "futures-io", 500 | "futures-sink", 501 | "futures-task", 502 | "futures-util", 503 | ] 504 | 505 | [[package]] 506 | name = "futures-channel" 507 | version = "0.3.25" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 510 | dependencies = [ 511 | "futures-core", 512 | "futures-sink", 513 | ] 514 | 515 | [[package]] 516 | name = "futures-core" 517 | version = "0.3.25" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 520 | 521 | [[package]] 522 | name = "futures-executor" 523 | version = "0.3.25" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 526 | dependencies = [ 527 | "futures-core", 528 | "futures-task", 529 | "futures-util", 530 | ] 531 | 532 | [[package]] 533 | name = "futures-io" 534 | version = "0.3.25" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 537 | 538 | [[package]] 539 | name = "futures-macro" 540 | version = "0.3.25" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 543 | dependencies = [ 544 | "proc-macro2", 545 | "quote", 546 | "syn", 547 | ] 548 | 549 | [[package]] 550 | name = "futures-sink" 551 | version = "0.3.25" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 554 | 555 | [[package]] 556 | name = "futures-task" 557 | version = "0.3.25" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 560 | 561 | [[package]] 562 | name = "futures-util" 563 | version = "0.3.25" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 566 | dependencies = [ 567 | "futures-channel", 568 | "futures-core", 569 | "futures-io", 570 | "futures-macro", 571 | "futures-sink", 572 | "futures-task", 573 | "memchr", 574 | "pin-project-lite", 575 | "pin-utils", 576 | "slab", 577 | ] 578 | 579 | [[package]] 580 | name = "generic-array" 581 | version = "0.14.6" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 584 | dependencies = [ 585 | "typenum", 586 | "version_check", 587 | ] 588 | 589 | [[package]] 590 | name = "getopts" 591 | version = "0.2.21" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 594 | dependencies = [ 595 | "unicode-width", 596 | ] 597 | 598 | [[package]] 599 | name = "getrandom" 600 | version = "0.2.8" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 603 | dependencies = [ 604 | "cfg-if", 605 | "libc", 606 | "wasi 0.11.0+wasi-snapshot-preview1", 607 | ] 608 | 609 | [[package]] 610 | name = "getset" 611 | version = "0.1.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" 614 | dependencies = [ 615 | "proc-macro-error", 616 | "proc-macro2", 617 | "quote", 618 | "syn", 619 | ] 620 | 621 | [[package]] 622 | name = "git2" 623 | version = "0.14.4" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" 626 | dependencies = [ 627 | "bitflags", 628 | "libc", 629 | "libgit2-sys", 630 | "log", 631 | "url", 632 | ] 633 | 634 | [[package]] 635 | name = "h2" 636 | version = "0.3.15" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" 639 | dependencies = [ 640 | "bytes", 641 | "fnv", 642 | "futures-core", 643 | "futures-sink", 644 | "futures-util", 645 | "http", 646 | "indexmap", 647 | "slab", 648 | "tokio", 649 | "tokio-util", 650 | "tracing", 651 | ] 652 | 653 | [[package]] 654 | name = "hashbrown" 655 | version = "0.12.3" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 658 | dependencies = [ 659 | "ahash", 660 | ] 661 | 662 | [[package]] 663 | name = "headers" 664 | version = "0.3.8" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" 667 | dependencies = [ 668 | "base64 0.13.1", 669 | "bitflags", 670 | "bytes", 671 | "headers-core", 672 | "http", 673 | "httpdate", 674 | "mime", 675 | "sha1", 676 | ] 677 | 678 | [[package]] 679 | name = "headers-core" 680 | version = "0.2.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" 683 | dependencies = [ 684 | "http", 685 | ] 686 | 687 | [[package]] 688 | name = "heck" 689 | version = "0.4.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 692 | 693 | [[package]] 694 | name = "hermit-abi" 695 | version = "0.1.19" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 698 | dependencies = [ 699 | "libc", 700 | ] 701 | 702 | [[package]] 703 | name = "http" 704 | version = "0.2.8" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 707 | dependencies = [ 708 | "bytes", 709 | "fnv", 710 | "itoa", 711 | ] 712 | 713 | [[package]] 714 | name = "http-body" 715 | version = "0.4.5" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 718 | dependencies = [ 719 | "bytes", 720 | "http", 721 | "pin-project-lite", 722 | ] 723 | 724 | [[package]] 725 | name = "http-range-header" 726 | version = "0.3.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" 729 | 730 | [[package]] 731 | name = "httparse" 732 | version = "1.8.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 735 | 736 | [[package]] 737 | name = "httpdate" 738 | version = "1.0.2" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 741 | 742 | [[package]] 743 | name = "hyper" 744 | version = "0.14.22" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" 747 | dependencies = [ 748 | "bytes", 749 | "futures-channel", 750 | "futures-core", 751 | "futures-util", 752 | "h2", 753 | "http", 754 | "http-body", 755 | "httparse", 756 | "httpdate", 757 | "itoa", 758 | "pin-project-lite", 759 | "socket2", 760 | "tokio", 761 | "tower-service", 762 | "tracing", 763 | "want", 764 | ] 765 | 766 | [[package]] 767 | name = "hyper-rustls" 768 | version = "0.23.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" 771 | dependencies = [ 772 | "http", 773 | "hyper", 774 | "rustls", 775 | "tokio", 776 | "tokio-rustls", 777 | ] 778 | 779 | [[package]] 780 | name = "idna" 781 | version = "0.3.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 784 | dependencies = [ 785 | "unicode-bidi", 786 | "unicode-normalization", 787 | ] 788 | 789 | [[package]] 790 | name = "indexmap" 791 | version = "1.9.1" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 794 | dependencies = [ 795 | "autocfg", 796 | "hashbrown", 797 | ] 798 | 799 | [[package]] 800 | name = "instant" 801 | version = "0.1.12" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 804 | dependencies = [ 805 | "cfg-if", 806 | ] 807 | 808 | [[package]] 809 | name = "ipnet" 810 | version = "2.5.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 813 | 814 | [[package]] 815 | name = "itoa" 816 | version = "1.0.4" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 819 | 820 | [[package]] 821 | name = "jobserver" 822 | version = "0.1.25" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" 825 | dependencies = [ 826 | "libc", 827 | ] 828 | 829 | [[package]] 830 | name = "js-sys" 831 | version = "0.3.60" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 834 | dependencies = [ 835 | "wasm-bindgen", 836 | ] 837 | 838 | [[package]] 839 | name = "json5" 840 | version = "0.4.1" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" 843 | dependencies = [ 844 | "pest", 845 | "pest_derive", 846 | "serde", 847 | ] 848 | 849 | [[package]] 850 | name = "lazy_static" 851 | version = "1.4.0" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 854 | 855 | [[package]] 856 | name = "libc" 857 | version = "0.2.137" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 860 | 861 | [[package]] 862 | name = "libgit2-sys" 863 | version = "0.13.4+1.4.2" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" 866 | dependencies = [ 867 | "cc", 868 | "libc", 869 | "libz-sys", 870 | "pkg-config", 871 | ] 872 | 873 | [[package]] 874 | name = "libz-sys" 875 | version = "1.1.8" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" 878 | dependencies = [ 879 | "cc", 880 | "libc", 881 | "pkg-config", 882 | "vcpkg", 883 | ] 884 | 885 | [[package]] 886 | name = "linked-hash-map" 887 | version = "0.5.6" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 890 | 891 | [[package]] 892 | name = "lock_api" 893 | version = "0.4.9" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 896 | dependencies = [ 897 | "autocfg", 898 | "scopeguard", 899 | "serde", 900 | ] 901 | 902 | [[package]] 903 | name = "log" 904 | version = "0.4.17" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 907 | dependencies = [ 908 | "cfg-if", 909 | ] 910 | 911 | [[package]] 912 | name = "matchers" 913 | version = "0.1.0" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 916 | dependencies = [ 917 | "regex-automata", 918 | ] 919 | 920 | [[package]] 921 | name = "matchit" 922 | version = "0.5.0" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" 925 | 926 | [[package]] 927 | name = "memchr" 928 | version = "2.5.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 931 | 932 | [[package]] 933 | name = "mime" 934 | version = "0.3.16" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 937 | 938 | [[package]] 939 | name = "minimal-lexical" 940 | version = "0.2.1" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 943 | 944 | [[package]] 945 | name = "minstant" 946 | version = "0.1.2" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "bc5dcfca9a0725105ac948b84cfeb69c3942814c696326743797215413f854b9" 949 | dependencies = [ 950 | "ctor", 951 | "libc", 952 | "wasi 0.7.0", 953 | ] 954 | 955 | [[package]] 956 | name = "mio" 957 | version = "0.8.5" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 960 | dependencies = [ 961 | "libc", 962 | "log", 963 | "wasi 0.11.0+wasi-snapshot-preview1", 964 | "windows-sys 0.42.0", 965 | ] 966 | 967 | [[package]] 968 | name = "nias" 969 | version = "0.5.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" 972 | 973 | [[package]] 974 | name = "nom" 975 | version = "7.1.1" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 978 | dependencies = [ 979 | "memchr", 980 | "minimal-lexical", 981 | ] 982 | 983 | [[package]] 984 | name = "ntapi" 985 | version = "0.4.0" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" 988 | dependencies = [ 989 | "winapi", 990 | ] 991 | 992 | [[package]] 993 | name = "nu-ansi-term" 994 | version = "0.46.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 997 | dependencies = [ 998 | "overload", 999 | "winapi", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "num_cpus" 1004 | version = "1.14.0" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 1007 | dependencies = [ 1008 | "hermit-abi", 1009 | "libc", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "num_threads" 1014 | version = "0.1.6" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 1017 | dependencies = [ 1018 | "libc", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "once_cell" 1023 | version = "1.16.0" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 1026 | 1027 | [[package]] 1028 | name = "openssl-probe" 1029 | version = "0.1.5" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1032 | 1033 | [[package]] 1034 | name = "ordered-multimap" 1035 | version = "0.4.3" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" 1038 | dependencies = [ 1039 | "dlv-list", 1040 | "hashbrown", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "overload" 1045 | version = "0.1.1" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1048 | 1049 | [[package]] 1050 | name = "parking_lot" 1051 | version = "0.12.1" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1054 | dependencies = [ 1055 | "lock_api", 1056 | "parking_lot_core", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "parking_lot_core" 1061 | version = "0.9.4" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" 1064 | dependencies = [ 1065 | "cfg-if", 1066 | "libc", 1067 | "redox_syscall", 1068 | "smallvec", 1069 | "windows-sys 0.42.0", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "pathdiff" 1074 | version = "0.2.1" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" 1077 | 1078 | [[package]] 1079 | name = "percent-encoding" 1080 | version = "2.2.0" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 1083 | 1084 | [[package]] 1085 | name = "pest" 1086 | version = "2.4.0" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" 1089 | dependencies = [ 1090 | "thiserror", 1091 | "ucd-trie", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "pest_derive" 1096 | version = "2.4.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" 1099 | dependencies = [ 1100 | "pest", 1101 | "pest_generator", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "pest_generator" 1106 | version = "2.4.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" 1109 | dependencies = [ 1110 | "pest", 1111 | "pest_meta", 1112 | "proc-macro2", 1113 | "quote", 1114 | "syn", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "pest_meta" 1119 | version = "2.4.0" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" 1122 | dependencies = [ 1123 | "once_cell", 1124 | "pest", 1125 | "sha1", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "pin-project" 1130 | version = "1.0.12" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 1133 | dependencies = [ 1134 | "pin-project-internal", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "pin-project-internal" 1139 | version = "1.0.12" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 1142 | dependencies = [ 1143 | "proc-macro2", 1144 | "quote", 1145 | "syn", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "pin-project-lite" 1150 | version = "0.2.9" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1153 | 1154 | [[package]] 1155 | name = "pin-utils" 1156 | version = "0.1.0" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1159 | 1160 | [[package]] 1161 | name = "pkg-config" 1162 | version = "0.3.26" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 1165 | 1166 | [[package]] 1167 | name = "ppv-lite86" 1168 | version = "0.2.16" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 1171 | 1172 | [[package]] 1173 | name = "proc-macro-error" 1174 | version = "1.0.4" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1177 | dependencies = [ 1178 | "proc-macro-error-attr", 1179 | "proc-macro2", 1180 | "quote", 1181 | "syn", 1182 | "version_check", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "proc-macro-error-attr" 1187 | version = "1.0.4" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1190 | dependencies = [ 1191 | "proc-macro2", 1192 | "quote", 1193 | "version_check", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "proc-macro2" 1198 | version = "1.0.47" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 1201 | dependencies = [ 1202 | "unicode-ident", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "quote" 1207 | version = "1.0.21" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 1210 | dependencies = [ 1211 | "proc-macro2", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "rand" 1216 | version = "0.8.5" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1219 | dependencies = [ 1220 | "libc", 1221 | "rand_chacha", 1222 | "rand_core", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "rand_chacha" 1227 | version = "0.3.1" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1230 | dependencies = [ 1231 | "ppv-lite86", 1232 | "rand_core", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "rand_core" 1237 | version = "0.6.4" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1240 | dependencies = [ 1241 | "getrandom", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "redox_syscall" 1246 | version = "0.2.16" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1249 | dependencies = [ 1250 | "bitflags", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "regex" 1255 | version = "1.6.0" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 1258 | dependencies = [ 1259 | "aho-corasick", 1260 | "memchr", 1261 | "regex-syntax", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "regex-automata" 1266 | version = "0.1.10" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1269 | dependencies = [ 1270 | "regex-syntax", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "regex-syntax" 1275 | version = "0.6.27" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 1278 | 1279 | [[package]] 1280 | name = "reqwest" 1281 | version = "0.11.16" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" 1284 | dependencies = [ 1285 | "base64 0.21.0", 1286 | "bytes", 1287 | "encoding_rs", 1288 | "futures-core", 1289 | "futures-util", 1290 | "h2", 1291 | "http", 1292 | "http-body", 1293 | "hyper", 1294 | "hyper-rustls", 1295 | "ipnet", 1296 | "js-sys", 1297 | "log", 1298 | "mime", 1299 | "once_cell", 1300 | "percent-encoding", 1301 | "pin-project-lite", 1302 | "rustls", 1303 | "rustls-pemfile", 1304 | "serde", 1305 | "serde_json", 1306 | "serde_urlencoded", 1307 | "tokio", 1308 | "tokio-rustls", 1309 | "tokio-util", 1310 | "tower-service", 1311 | "url", 1312 | "wasm-bindgen", 1313 | "wasm-bindgen-futures", 1314 | "wasm-streams", 1315 | "web-sys", 1316 | "webpki-roots", 1317 | "winreg", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "ring" 1322 | version = "0.16.20" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1325 | dependencies = [ 1326 | "cc", 1327 | "libc", 1328 | "once_cell", 1329 | "spin", 1330 | "untrusted", 1331 | "web-sys", 1332 | "winapi", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "ron" 1337 | version = "0.7.1" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" 1340 | dependencies = [ 1341 | "base64 0.13.1", 1342 | "bitflags", 1343 | "serde", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "rust-ini" 1348 | version = "0.18.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" 1351 | dependencies = [ 1352 | "cfg-if", 1353 | "ordered-multimap", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "rustc_version" 1358 | version = "0.4.0" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1361 | dependencies = [ 1362 | "semver", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "rustls" 1367 | version = "0.20.7" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" 1370 | dependencies = [ 1371 | "log", 1372 | "ring", 1373 | "sct", 1374 | "webpki", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "rustls-native-certs" 1379 | version = "0.6.2" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" 1382 | dependencies = [ 1383 | "openssl-probe", 1384 | "rustls-pemfile", 1385 | "schannel", 1386 | "security-framework", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "rustls-pemfile" 1391 | version = "1.0.1" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" 1394 | dependencies = [ 1395 | "base64 0.13.1", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "rustversion" 1400 | version = "1.0.9" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 1403 | 1404 | [[package]] 1405 | name = "rusty-hook" 1406 | version = "0.11.2" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "96cee9be61be7e1cbadd851e58ed7449c29c620f00b23df937cb9cbc04ac21a3" 1409 | dependencies = [ 1410 | "ci_info", 1411 | "getopts", 1412 | "nias", 1413 | "toml", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "ryu" 1418 | version = "1.0.11" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 1421 | 1422 | [[package]] 1423 | name = "schannel" 1424 | version = "0.1.20" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 1427 | dependencies = [ 1428 | "lazy_static", 1429 | "windows-sys 0.36.1", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "scopeguard" 1434 | version = "1.1.0" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1437 | 1438 | [[package]] 1439 | name = "sct" 1440 | version = "0.7.0" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 1443 | dependencies = [ 1444 | "ring", 1445 | "untrusted", 1446 | ] 1447 | 1448 | [[package]] 1449 | name = "security-framework" 1450 | version = "2.7.0" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" 1453 | dependencies = [ 1454 | "bitflags", 1455 | "core-foundation", 1456 | "core-foundation-sys", 1457 | "libc", 1458 | "security-framework-sys", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "security-framework-sys" 1463 | version = "2.6.1" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1466 | dependencies = [ 1467 | "core-foundation-sys", 1468 | "libc", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "semver" 1473 | version = "1.0.14" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" 1476 | 1477 | [[package]] 1478 | name = "serde" 1479 | version = "1.0.147" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 1482 | dependencies = [ 1483 | "serde_derive", 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "serde_derive" 1488 | version = "1.0.147" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 1491 | dependencies = [ 1492 | "proc-macro2", 1493 | "quote", 1494 | "syn", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "serde_json" 1499 | version = "1.0.89" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" 1502 | dependencies = [ 1503 | "itoa", 1504 | "ryu", 1505 | "serde", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "serde_urlencoded" 1510 | version = "0.7.1" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1513 | dependencies = [ 1514 | "form_urlencoded", 1515 | "itoa", 1516 | "ryu", 1517 | "serde", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "sha-1" 1522 | version = "0.10.0" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" 1525 | dependencies = [ 1526 | "cfg-if", 1527 | "cpufeatures", 1528 | "digest", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "sha1" 1533 | version = "0.10.5" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1536 | dependencies = [ 1537 | "cfg-if", 1538 | "cpufeatures", 1539 | "digest", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "sharded-slab" 1544 | version = "0.1.4" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1547 | dependencies = [ 1548 | "lazy_static", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "signal-hook-registry" 1553 | version = "1.4.0" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1556 | dependencies = [ 1557 | "libc", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "slab" 1562 | version = "0.4.7" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1565 | dependencies = [ 1566 | "autocfg", 1567 | ] 1568 | 1569 | [[package]] 1570 | name = "smallvec" 1571 | version = "1.10.0" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1574 | 1575 | [[package]] 1576 | name = "socket2" 1577 | version = "0.4.7" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 1580 | dependencies = [ 1581 | "libc", 1582 | "winapi", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "socketio-rs" 1587 | version = "0.1.7" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "c60b864fa09bca26848d762ebb95322337d29670bba53192f528a0c444b816b8" 1590 | dependencies = [ 1591 | "async-stream", 1592 | "backoff", 1593 | "base64 0.13.1", 1594 | "bytes", 1595 | "dashmap", 1596 | "engineio-rs", 1597 | "futures-util", 1598 | "parking_lot", 1599 | "regex", 1600 | "serde", 1601 | "serde_json", 1602 | "thiserror", 1603 | "tokio", 1604 | "tokio-util", 1605 | "tracing", 1606 | "url", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "spin" 1611 | version = "0.5.2" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1614 | 1615 | [[package]] 1616 | name = "syn" 1617 | version = "1.0.103" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 1620 | dependencies = [ 1621 | "proc-macro2", 1622 | "quote", 1623 | "unicode-ident", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "sync_wrapper" 1628 | version = "0.1.1" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" 1631 | 1632 | [[package]] 1633 | name = "sysinfo" 1634 | version = "0.26.7" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "c375d5fd899e32847b8566e10598d6e9f1d9b55ec6de3cdf9e7da4bdc51371bc" 1637 | dependencies = [ 1638 | "cfg-if", 1639 | "core-foundation-sys", 1640 | "libc", 1641 | "ntapi", 1642 | "once_cell", 1643 | "winapi", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "thiserror" 1648 | version = "1.0.37" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 1651 | dependencies = [ 1652 | "thiserror-impl", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "thiserror-impl" 1657 | version = "1.0.37" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 1660 | dependencies = [ 1661 | "proc-macro2", 1662 | "quote", 1663 | "syn", 1664 | ] 1665 | 1666 | [[package]] 1667 | name = "thread_local" 1668 | version = "1.1.4" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1671 | dependencies = [ 1672 | "once_cell", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "time" 1677 | version = "0.3.17" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 1680 | dependencies = [ 1681 | "itoa", 1682 | "libc", 1683 | "num_threads", 1684 | "serde", 1685 | "time-core", 1686 | "time-macros", 1687 | ] 1688 | 1689 | [[package]] 1690 | name = "time-core" 1691 | version = "0.1.0" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 1694 | 1695 | [[package]] 1696 | name = "time-macros" 1697 | version = "0.2.6" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 1700 | dependencies = [ 1701 | "time-core", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "tinyvec" 1706 | version = "1.6.0" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1709 | dependencies = [ 1710 | "tinyvec_macros", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "tinyvec_macros" 1715 | version = "0.1.0" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1718 | 1719 | [[package]] 1720 | name = "tokio" 1721 | version = "1.24.2" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" 1724 | dependencies = [ 1725 | "autocfg", 1726 | "bytes", 1727 | "libc", 1728 | "memchr", 1729 | "mio", 1730 | "num_cpus", 1731 | "parking_lot", 1732 | "pin-project-lite", 1733 | "signal-hook-registry", 1734 | "socket2", 1735 | "tokio-macros", 1736 | "windows-sys 0.42.0", 1737 | ] 1738 | 1739 | [[package]] 1740 | name = "tokio-macros" 1741 | version = "1.8.0" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 1744 | dependencies = [ 1745 | "proc-macro2", 1746 | "quote", 1747 | "syn", 1748 | ] 1749 | 1750 | [[package]] 1751 | name = "tokio-rustls" 1752 | version = "0.23.4" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 1755 | dependencies = [ 1756 | "rustls", 1757 | "tokio", 1758 | "webpki", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "tokio-tungstenite" 1763 | version = "0.17.2" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" 1766 | dependencies = [ 1767 | "futures-util", 1768 | "log", 1769 | "rustls", 1770 | "rustls-native-certs", 1771 | "tokio", 1772 | "tokio-rustls", 1773 | "tungstenite", 1774 | "webpki", 1775 | ] 1776 | 1777 | [[package]] 1778 | name = "tokio-util" 1779 | version = "0.7.4" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 1782 | dependencies = [ 1783 | "bytes", 1784 | "futures-core", 1785 | "futures-sink", 1786 | "pin-project-lite", 1787 | "tokio", 1788 | "tracing", 1789 | ] 1790 | 1791 | [[package]] 1792 | name = "toml" 1793 | version = "0.5.9" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 1796 | dependencies = [ 1797 | "serde", 1798 | ] 1799 | 1800 | [[package]] 1801 | name = "tower" 1802 | version = "0.4.13" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1805 | dependencies = [ 1806 | "futures-core", 1807 | "futures-util", 1808 | "pin-project", 1809 | "pin-project-lite", 1810 | "tokio", 1811 | "tower-layer", 1812 | "tower-service", 1813 | "tracing", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "tower-http" 1818 | version = "0.2.5" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "aba3f3efabf7fb41fae8534fc20a817013dd1c12cb45441efb6c82e6556b4cd8" 1821 | dependencies = [ 1822 | "bitflags", 1823 | "bytes", 1824 | "futures-core", 1825 | "futures-util", 1826 | "http", 1827 | "http-body", 1828 | "http-range-header", 1829 | "pin-project-lite", 1830 | "tower-layer", 1831 | "tower-service", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "tower-http" 1836 | version = "0.3.4" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" 1839 | dependencies = [ 1840 | "bitflags", 1841 | "bytes", 1842 | "futures-core", 1843 | "futures-util", 1844 | "http", 1845 | "http-body", 1846 | "http-range-header", 1847 | "pin-project-lite", 1848 | "tower", 1849 | "tower-layer", 1850 | "tower-service", 1851 | ] 1852 | 1853 | [[package]] 1854 | name = "tower-layer" 1855 | version = "0.3.2" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1858 | 1859 | [[package]] 1860 | name = "tower-service" 1861 | version = "0.3.2" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1864 | 1865 | [[package]] 1866 | name = "tracing" 1867 | version = "0.1.37" 1868 | source = "registry+https://github.com/rust-lang/crates.io-index" 1869 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1870 | dependencies = [ 1871 | "cfg-if", 1872 | "log", 1873 | "pin-project-lite", 1874 | "tracing-attributes", 1875 | "tracing-core", 1876 | ] 1877 | 1878 | [[package]] 1879 | name = "tracing-attributes" 1880 | version = "0.1.23" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1883 | dependencies = [ 1884 | "proc-macro2", 1885 | "quote", 1886 | "syn", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "tracing-core" 1891 | version = "0.1.30" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1894 | dependencies = [ 1895 | "once_cell", 1896 | "valuable", 1897 | ] 1898 | 1899 | [[package]] 1900 | name = "tracing-log" 1901 | version = "0.1.3" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1904 | dependencies = [ 1905 | "lazy_static", 1906 | "log", 1907 | "tracing-core", 1908 | ] 1909 | 1910 | [[package]] 1911 | name = "tracing-subscriber" 1912 | version = "0.3.16" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 1915 | dependencies = [ 1916 | "matchers", 1917 | "nu-ansi-term", 1918 | "once_cell", 1919 | "regex", 1920 | "sharded-slab", 1921 | "smallvec", 1922 | "thread_local", 1923 | "time", 1924 | "tracing", 1925 | "tracing-core", 1926 | "tracing-log", 1927 | ] 1928 | 1929 | [[package]] 1930 | name = "try-lock" 1931 | version = "0.2.3" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1934 | 1935 | [[package]] 1936 | name = "tungstenite" 1937 | version = "0.17.3" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" 1940 | dependencies = [ 1941 | "base64 0.13.1", 1942 | "byteorder", 1943 | "bytes", 1944 | "http", 1945 | "httparse", 1946 | "log", 1947 | "rand", 1948 | "rustls", 1949 | "rustls-native-certs", 1950 | "sha-1", 1951 | "thiserror", 1952 | "url", 1953 | "utf-8", 1954 | "webpki", 1955 | ] 1956 | 1957 | [[package]] 1958 | name = "typenum" 1959 | version = "1.15.0" 1960 | source = "registry+https://github.com/rust-lang/crates.io-index" 1961 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1962 | 1963 | [[package]] 1964 | name = "ucd-trie" 1965 | version = "0.1.5" 1966 | source = "registry+https://github.com/rust-lang/crates.io-index" 1967 | checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" 1968 | 1969 | [[package]] 1970 | name = "unicode-bidi" 1971 | version = "0.3.8" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1974 | 1975 | [[package]] 1976 | name = "unicode-ident" 1977 | version = "1.0.5" 1978 | source = "registry+https://github.com/rust-lang/crates.io-index" 1979 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 1980 | 1981 | [[package]] 1982 | name = "unicode-normalization" 1983 | version = "0.1.22" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1986 | dependencies = [ 1987 | "tinyvec", 1988 | ] 1989 | 1990 | [[package]] 1991 | name = "unicode-width" 1992 | version = "0.1.10" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1995 | 1996 | [[package]] 1997 | name = "untrusted" 1998 | version = "0.7.1" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 2001 | 2002 | [[package]] 2003 | name = "url" 2004 | version = "2.3.1" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 2007 | dependencies = [ 2008 | "form_urlencoded", 2009 | "idna", 2010 | "percent-encoding", 2011 | ] 2012 | 2013 | [[package]] 2014 | name = "utf-8" 2015 | version = "0.7.6" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2018 | 2019 | [[package]] 2020 | name = "valuable" 2021 | version = "0.1.0" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2024 | 2025 | [[package]] 2026 | name = "vcpkg" 2027 | version = "0.2.15" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2030 | 2031 | [[package]] 2032 | name = "vergen" 2033 | version = "7.4.2" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "73ba753d713ec3844652ad2cb7eb56bc71e34213a14faddac7852a10ba88f61e" 2036 | dependencies = [ 2037 | "anyhow", 2038 | "cfg-if", 2039 | "enum-iterator", 2040 | "getset", 2041 | "git2", 2042 | "rustc_version", 2043 | "rustversion", 2044 | "sysinfo", 2045 | "thiserror", 2046 | "time", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "version_check" 2051 | version = "0.9.4" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2054 | 2055 | [[package]] 2056 | name = "want" 2057 | version = "0.3.0" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 2060 | dependencies = [ 2061 | "log", 2062 | "try-lock", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "wasi" 2067 | version = "0.7.0" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" 2070 | 2071 | [[package]] 2072 | name = "wasi" 2073 | version = "0.11.0+wasi-snapshot-preview1" 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" 2075 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2076 | 2077 | [[package]] 2078 | name = "wasm-bindgen" 2079 | version = "0.2.83" 2080 | source = "registry+https://github.com/rust-lang/crates.io-index" 2081 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 2082 | dependencies = [ 2083 | "cfg-if", 2084 | "wasm-bindgen-macro", 2085 | ] 2086 | 2087 | [[package]] 2088 | name = "wasm-bindgen-backend" 2089 | version = "0.2.83" 2090 | source = "registry+https://github.com/rust-lang/crates.io-index" 2091 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 2092 | dependencies = [ 2093 | "bumpalo", 2094 | "log", 2095 | "once_cell", 2096 | "proc-macro2", 2097 | "quote", 2098 | "syn", 2099 | "wasm-bindgen-shared", 2100 | ] 2101 | 2102 | [[package]] 2103 | name = "wasm-bindgen-futures" 2104 | version = "0.4.33" 2105 | source = "registry+https://github.com/rust-lang/crates.io-index" 2106 | checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" 2107 | dependencies = [ 2108 | "cfg-if", 2109 | "js-sys", 2110 | "wasm-bindgen", 2111 | "web-sys", 2112 | ] 2113 | 2114 | [[package]] 2115 | name = "wasm-bindgen-macro" 2116 | version = "0.2.83" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 2119 | dependencies = [ 2120 | "quote", 2121 | "wasm-bindgen-macro-support", 2122 | ] 2123 | 2124 | [[package]] 2125 | name = "wasm-bindgen-macro-support" 2126 | version = "0.2.83" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 2129 | dependencies = [ 2130 | "proc-macro2", 2131 | "quote", 2132 | "syn", 2133 | "wasm-bindgen-backend", 2134 | "wasm-bindgen-shared", 2135 | ] 2136 | 2137 | [[package]] 2138 | name = "wasm-bindgen-shared" 2139 | version = "0.2.83" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 2142 | 2143 | [[package]] 2144 | name = "wasm-streams" 2145 | version = "0.2.3" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" 2148 | dependencies = [ 2149 | "futures-util", 2150 | "js-sys", 2151 | "wasm-bindgen", 2152 | "wasm-bindgen-futures", 2153 | "web-sys", 2154 | ] 2155 | 2156 | [[package]] 2157 | name = "web-sys" 2158 | version = "0.3.60" 2159 | source = "registry+https://github.com/rust-lang/crates.io-index" 2160 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 2161 | dependencies = [ 2162 | "js-sys", 2163 | "wasm-bindgen", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "webpki" 2168 | version = "0.22.0" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 2171 | dependencies = [ 2172 | "ring", 2173 | "untrusted", 2174 | ] 2175 | 2176 | [[package]] 2177 | name = "webpki-roots" 2178 | version = "0.22.5" 2179 | source = "registry+https://github.com/rust-lang/crates.io-index" 2180 | checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" 2181 | dependencies = [ 2182 | "webpki", 2183 | ] 2184 | 2185 | [[package]] 2186 | name = "winapi" 2187 | version = "0.3.9" 2188 | source = "registry+https://github.com/rust-lang/crates.io-index" 2189 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2190 | dependencies = [ 2191 | "winapi-i686-pc-windows-gnu", 2192 | "winapi-x86_64-pc-windows-gnu", 2193 | ] 2194 | 2195 | [[package]] 2196 | name = "winapi-i686-pc-windows-gnu" 2197 | version = "0.4.0" 2198 | source = "registry+https://github.com/rust-lang/crates.io-index" 2199 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2200 | 2201 | [[package]] 2202 | name = "winapi-x86_64-pc-windows-gnu" 2203 | version = "0.4.0" 2204 | source = "registry+https://github.com/rust-lang/crates.io-index" 2205 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2206 | 2207 | [[package]] 2208 | name = "windows-sys" 2209 | version = "0.36.1" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 2212 | dependencies = [ 2213 | "windows_aarch64_msvc 0.36.1", 2214 | "windows_i686_gnu 0.36.1", 2215 | "windows_i686_msvc 0.36.1", 2216 | "windows_x86_64_gnu 0.36.1", 2217 | "windows_x86_64_msvc 0.36.1", 2218 | ] 2219 | 2220 | [[package]] 2221 | name = "windows-sys" 2222 | version = "0.42.0" 2223 | source = "registry+https://github.com/rust-lang/crates.io-index" 2224 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 2225 | dependencies = [ 2226 | "windows_aarch64_gnullvm", 2227 | "windows_aarch64_msvc 0.42.0", 2228 | "windows_i686_gnu 0.42.0", 2229 | "windows_i686_msvc 0.42.0", 2230 | "windows_x86_64_gnu 0.42.0", 2231 | "windows_x86_64_gnullvm", 2232 | "windows_x86_64_msvc 0.42.0", 2233 | ] 2234 | 2235 | [[package]] 2236 | name = "windows_aarch64_gnullvm" 2237 | version = "0.42.0" 2238 | source = "registry+https://github.com/rust-lang/crates.io-index" 2239 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 2240 | 2241 | [[package]] 2242 | name = "windows_aarch64_msvc" 2243 | version = "0.36.1" 2244 | source = "registry+https://github.com/rust-lang/crates.io-index" 2245 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 2246 | 2247 | [[package]] 2248 | name = "windows_aarch64_msvc" 2249 | version = "0.42.0" 2250 | source = "registry+https://github.com/rust-lang/crates.io-index" 2251 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 2252 | 2253 | [[package]] 2254 | name = "windows_i686_gnu" 2255 | version = "0.36.1" 2256 | source = "registry+https://github.com/rust-lang/crates.io-index" 2257 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 2258 | 2259 | [[package]] 2260 | name = "windows_i686_gnu" 2261 | version = "0.42.0" 2262 | source = "registry+https://github.com/rust-lang/crates.io-index" 2263 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 2264 | 2265 | [[package]] 2266 | name = "windows_i686_msvc" 2267 | version = "0.36.1" 2268 | source = "registry+https://github.com/rust-lang/crates.io-index" 2269 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 2270 | 2271 | [[package]] 2272 | name = "windows_i686_msvc" 2273 | version = "0.42.0" 2274 | source = "registry+https://github.com/rust-lang/crates.io-index" 2275 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 2276 | 2277 | [[package]] 2278 | name = "windows_x86_64_gnu" 2279 | version = "0.36.1" 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" 2281 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 2282 | 2283 | [[package]] 2284 | name = "windows_x86_64_gnu" 2285 | version = "0.42.0" 2286 | source = "registry+https://github.com/rust-lang/crates.io-index" 2287 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 2288 | 2289 | [[package]] 2290 | name = "windows_x86_64_gnullvm" 2291 | version = "0.42.0" 2292 | source = "registry+https://github.com/rust-lang/crates.io-index" 2293 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 2294 | 2295 | [[package]] 2296 | name = "windows_x86_64_msvc" 2297 | version = "0.36.1" 2298 | source = "registry+https://github.com/rust-lang/crates.io-index" 2299 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 2300 | 2301 | [[package]] 2302 | name = "windows_x86_64_msvc" 2303 | version = "0.42.0" 2304 | source = "registry+https://github.com/rust-lang/crates.io-index" 2305 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 2306 | 2307 | [[package]] 2308 | name = "winreg" 2309 | version = "0.10.1" 2310 | source = "registry+https://github.com/rust-lang/crates.io-index" 2311 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 2312 | dependencies = [ 2313 | "winapi", 2314 | ] 2315 | 2316 | [[package]] 2317 | name = "yaml-rust" 2318 | version = "0.4.5" 2319 | source = "registry+https://github.com/rust-lang/crates.io-index" 2320 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2321 | dependencies = [ 2322 | "linked-hash-map", 2323 | ] 2324 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "feature-probe-server" 4 | version = "2.0.1" 5 | license = "Apache-2.0" 6 | authors = ["maintain@featureprobe.com"] 7 | description = "FeatureProbe Server for evaluating feature toggles" 8 | build = "build.rs" 9 | 10 | [[bin]] 11 | name = "feature_probe_server" 12 | path = "src/main.rs" 13 | 14 | [lib] 15 | name = "feature_probe_server" 16 | path = "src/lib.rs" 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [features] 21 | default = ["realtime"] 22 | unstable = [] 23 | realtime = ["socketio-rs"] 24 | 25 | [build-dependencies] 26 | vergen = "7" 27 | 28 | [dependencies] 29 | anyhow = "1.0" 30 | axum = { version = "0.5", features = ["headers"] } 31 | axum-extra = { version = "0.2", features = ["typed-routing"] } 32 | base64 = "0.13" 33 | config = "0.13" 34 | futures = "0.3" 35 | log = "0.4" 36 | parking_lot = "0.12" 37 | reqwest = { version = "0.11", default-features = false, features = [ 38 | "rustls-tls", 39 | ] } 40 | serde = { version = "1.0", features = ["derive"] } 41 | serde_json = "1.0" 42 | thiserror = "1.0" 43 | tokio = { version = "1", features = ["full"] } 44 | time = { version = "0.3", features = ["formatting", "local-offset", "macros"] } 45 | tracing = "0.1" 46 | tracing-core = "0.1" 47 | tracing-log = "0.1" 48 | tracing-subscriber = { version = "0.3", features = [ 49 | "local-time", 50 | "env-filter", 51 | ] } 52 | url = "2.3" 53 | socketio-rs = { optional = true, version = "0.1.7", default-features = false, features = ["server"] } 54 | feature-probe-server-sdk = { version="1.2.12", features = [ 55 | "internal", 56 | "use_tokio", 57 | ], default-features = false } 58 | feature-probe-event = { version="1.1.3", features = [ 59 | "use_tokio", 60 | "collector", 61 | ], default-features = false } 62 | 63 | [dev-dependencies] 64 | rusty-hook = "^0.11.2" 65 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.64.0 as build 2 | 3 | RUN rustup target add x86_64-unknown-linux-musl 4 | RUN apt update && apt install -y musl-tools musl-dev build-essential 5 | RUN update-ca-certificates 6 | 7 | 8 | WORKDIR /app 9 | COPY . /app 10 | WORKDIR /app/server 11 | 12 | RUN rustc -V 13 | RUN cargo build --release --verbose 14 | 15 | FROM debian:buster-slim 16 | COPY --from=build /app/server/target/release/feature_probe_server . 17 | 18 | CMD [ "./feature_probe_server" ] 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 FeatureProbe 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build_date = `date +%Y%m%d%H%M` 2 | commit = `git rev-parse HEAD` 3 | version = `git rev-parse --short HEAD` 4 | 5 | .PHONY: release 6 | build: 7 | cargo build --verbose 8 | release: 9 | cargo build --release --verbose 10 | test: 11 | cargo test --release --verbose && \ 12 | cargo test --release --verbose --features realtime --no-default-features 13 | example: 14 | cargo build --examples --release --verbose 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FeatureProbe Server 2 | 3 | [![Top Language](https://img.shields.io/github/languages/top/FeatureProbe/server-sdk-rust)](https://github.com/FeatureProbe/feature-probe-server/search?l=rust) 4 | [![codecov](https://codecov.io/gh/featureprobe/feature-probe-server/branch/main/graph/badge.svg?token=TAN3AU4CK2)](https://codecov.io/gh/featureprobe/feature-probe-serve) 5 | [![Github Star](https://img.shields.io/github/stars/FeatureProbe/server-sdk-rust)](https://github.com/FeatureProbe/server-sdk-rust/stargazers) 6 | [![Apache-2.0 license](https://img.shields.io/github/license/FeatureProbe/FeatureProbe)](https://github.com/FeatureProbe/FeatureProbe/blob/main/LICENSE) 7 | 8 | ## [中文文档](https://docs.featureprobe.io/zh-CN/) 9 | 10 | FeatureProbe Server(also called the Evaluation Server) is a key component to make the FeatureProbe service workable. 11 | It provides toggle configurations and rules to the server-side SDKs, 12 | and it provides evaluation results to the client-side SDKs. 13 | 14 | ![featureProbe Architecture](https://github.com/FeatureProbe/featureprobe/blob/main/pictures/feature_probe_architecture.png) 15 | 16 | The Evaluation Server directly/indirectly decides variation results based on the targeting user's situation and makes 17 | the gradually rolling out, service degradation or A/B testing possible. 18 | 19 | 20 | ## Getting Started 21 | Installing the Evaluation Service is the prerequisite of running FeatureProbe service. 22 | 23 | ### Using Docker Composer to Install Main Services 24 | 25 | We recommend booting up the Evaluation Server along with other core components by using a docker composer file. 26 | 27 | Here is an example to help to boot up FeatureProbe Server, API, UI and db with a simple docker-compose up command. 28 | Check it out at [FeatureProbe Official Compose File](https://github.com/FeatureProbe/featureprobe). 29 | 30 | Or you can simply run below command to clone and boot up the Docker composer components. 31 | ``` bash 32 | git clone https://github.com/FeatureProbe/featureprobe.git 33 | cd featureprobe 34 | docker compose up 35 | ``` 36 | 37 | ### Installing Evaluation Server Independently with a Docker Image 38 | 39 | You can alternatively install and run API with a Docker image. To run, binding the exposed port 4007 to the host, use: 40 | ``` 41 | $ docker run -d -p 4007:4007 --name fp-api featureprobe/server 42 | ``` 43 | 44 | 45 | 46 | ## Contributing 47 | We are working on continue evolving FeatureProbe core, making it flexible and easier to use. 48 | Development of FeatureProbe happens in the open on GitHub, and we are grateful to the 49 | community for contributing bugfixes and improvements. 50 | 51 | Please read [CONTRIBUTING](https://github.com/FeatureProbe/featureprobe/blob/master/CONTRIBUTING.md) 52 | for details on our code of conduct, and the process for taking part in improving FeatureProbe. 53 | 54 | 55 | ## License 56 | 57 | This project is licensed under the Apache 2.0 License - see the [LICENSE](LICENSE) file for details. 58 | 59 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use vergen::{vergen, Config}; 2 | 3 | fn main() { 4 | // Generate the default 'cargo:' instruction output 5 | let _ = vergen(Config::default()); 6 | } 7 | -------------------------------------------------------------------------------- /examples/local_file.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Duration}; 2 | 3 | use feature_probe_server::{ 4 | base::ServerConfig, 5 | http::{serve_http, FpHttpHandler, LocalFileHttpHandlerForTest}, 6 | repo::SdkRepository, 7 | }; 8 | 9 | #[cfg(feature = "realtime")] 10 | use feature_probe_server::realtime::RealtimeSocket; 11 | use feature_probe_server_sdk::Url; 12 | 13 | #[tokio::main] 14 | async fn main() { 15 | // mock fp api 16 | let api_port = 9991; 17 | tokio::spawn(serve_http::( 18 | api_port, 19 | LocalFileHttpHandlerForTest::default(), 20 | )); 21 | 22 | let server_sdk_key = "server-sdk-key1".to_owned(); 23 | let client_sdk_key = "client-sdk-key1".to_owned(); 24 | 25 | // start fp server 26 | let fp_port = 9990; 27 | let toggles_url = Url::parse(&format!( 28 | "http://0.0.0.0:{}/api/server-sdk/toggles", 29 | api_port 30 | )) 31 | .unwrap(); 32 | let events_url = Url::parse(&format!("http://0.0.0.0:{}/api/events", api_port)).unwrap(); 33 | let analysis_url = 34 | Some(Url::parse(&format!("http://0.0.0.0:{}/analysis/events", api_port)).unwrap()); 35 | let refresh_seconds = Duration::from_secs(1); 36 | let config = ServerConfig { 37 | toggles_url, 38 | events_url: events_url.clone(), 39 | keys_url: None, 40 | analysis_url: None, 41 | refresh_interval: refresh_seconds, 42 | client_sdk_key: Some(client_sdk_key.clone()), 43 | server_sdk_key: Some(server_sdk_key.clone()), 44 | server_port: 9000, 45 | #[cfg(feature = "realtime")] 46 | realtime_port: 9100, 47 | #[cfg(feature = "realtime")] 48 | realtime_path: "/server/realtime".to_owned(), 49 | }; 50 | 51 | #[cfg(feature = "realtime")] 52 | let realtime_socket = RealtimeSocket::serve(config.realtime_port, &config.realtime_path); 53 | let repo = SdkRepository::new( 54 | config, 55 | #[cfg(feature = "realtime")] 56 | realtime_socket, 57 | ); 58 | repo.sync(client_sdk_key, server_sdk_key, 1); 59 | let repo = Arc::new(repo); 60 | let feature_probe_server = FpHttpHandler { 61 | repo: repo.clone(), 62 | events_url, 63 | analysis_url, 64 | events_timeout: refresh_seconds, 65 | http_client: Default::default(), 66 | }; 67 | tokio::spawn(serve_http::(fp_port, feature_probe_server)); 68 | 69 | tokio::signal::ctrl_c().await.expect("shut down"); 70 | } 71 | -------------------------------------------------------------------------------- /resources/fixtures/repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "segments": { 4 | "some_segment1-fjoaefjaam": { 5 | "key": "some_segment1", 6 | "uniqueId": "some_segment1-fjoaefjaam", 7 | "version": 2, 8 | "rules": [ 9 | { 10 | "conditions": [ 11 | { 12 | "type": "string", 13 | "subject": "city", 14 | "predicate": "is one of", 15 | "objects": [ 16 | "4" 17 | ] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | "toggles": { 25 | "bool_toggle": { 26 | "key": "bool_toggle", 27 | "enabled": true, 28 | "forClient": true, 29 | "version": 1, 30 | "disabledServe": { 31 | "select": 1 32 | }, 33 | "defaultServe": { 34 | "select": 0 35 | }, 36 | "rules": [ 37 | { 38 | "serve": { 39 | "select": 0 40 | }, 41 | "conditions": [ 42 | { 43 | "type": "string", 44 | "subject": "city", 45 | "predicate": "is one of", 46 | "objects": [ 47 | "1", 48 | "2", 49 | "3" 50 | ] 51 | } 52 | ] 53 | }, 54 | { 55 | "serve": { 56 | "select": 1 57 | }, 58 | "conditions": [ 59 | { 60 | "type": "segment", 61 | "predicate": "in", 62 | "objects": [ 63 | "some_segment1-fjoaefjaam" 64 | ] 65 | } 66 | ] 67 | } 68 | ], 69 | "variations": [ 70 | true, 71 | false 72 | ] 73 | }, 74 | "number_toggle": { 75 | "key": "number_toggle", 76 | "forClient": true, 77 | "enabled": true, 78 | "version": 1, 79 | "disabledServe": { 80 | "select": 1 81 | }, 82 | "defaultServe": { 83 | "select": 0 84 | }, 85 | "rules": [ 86 | { 87 | "serve": { 88 | "select": 0 89 | }, 90 | "conditions": [ 91 | { 92 | "type": "string", 93 | "subject": "city", 94 | "predicate": "is one of", 95 | "objects": [ 96 | "1", 97 | "2", 98 | "3" 99 | ] 100 | } 101 | ] 102 | }, 103 | { 104 | "serve": { 105 | "select": 1 106 | }, 107 | "conditions": [ 108 | { 109 | "type": "segment", 110 | "predicate": "in", 111 | "objects": [ 112 | "some_segment1-fjoaefjaam" 113 | ] 114 | } 115 | ] 116 | } 117 | ], 118 | "variations": [ 119 | 1, 120 | 2 121 | ] 122 | }, 123 | "string_toggle": { 124 | "key": "string_toggle", 125 | "forClient": true, 126 | "enabled": true, 127 | "version": 1, 128 | "disabledServe": { 129 | "select": 1 130 | }, 131 | "defaultServe": { 132 | "select": 0 133 | }, 134 | "rules": [ 135 | { 136 | "serve": { 137 | "select": 0 138 | }, 139 | "conditions": [ 140 | { 141 | "type": "string", 142 | "subject": "city", 143 | "predicate": "is one of", 144 | "objects": [ 145 | "1", 146 | "2", 147 | "3" 148 | ] 149 | } 150 | ] 151 | }, 152 | { 153 | "serve": { 154 | "select": 1 155 | }, 156 | "conditions": [ 157 | { 158 | "type": "segment", 159 | "predicate": "in", 160 | "objects": [ 161 | "some_segment1-fjoaefjaam" 162 | ] 163 | } 164 | ] 165 | } 166 | ], 167 | "variations": [ 168 | "1", 169 | "2" 170 | ] 171 | }, 172 | "json_toggle": { 173 | "key": "json_toggle", 174 | "enabled": true, 175 | "forClient": true, 176 | "version": 1, 177 | "disabledServe": { 178 | "select": 1 179 | }, 180 | "defaultServe": { 181 | "split": { 182 | "distribution": [ 183 | [ 184 | [ 185 | 0, 186 | 3333 187 | ] 188 | ], 189 | [ 190 | [ 191 | 3333, 192 | 6666 193 | ] 194 | ], 195 | [ 196 | [ 197 | 6666, 198 | 10000 199 | ] 200 | ] 201 | ], 202 | "salt": "some_salt" 203 | } 204 | }, 205 | "rules": [ 206 | { 207 | "serve": { 208 | "select": 0 209 | }, 210 | "conditions": [ 211 | { 212 | "type": "string", 213 | "subject": "city", 214 | "predicate": "is one of", 215 | "objects": [ 216 | "1", 217 | "2", 218 | "3" 219 | ] 220 | } 221 | ] 222 | }, 223 | { 224 | "serve": { 225 | "select": 1 226 | }, 227 | "conditions": [ 228 | { 229 | "type": "segment", 230 | "predicate": "in", 231 | "objects": [ 232 | "some_segment1-fjoaefjaam" 233 | ] 234 | } 235 | ] 236 | } 237 | ], 238 | "variations": [ 239 | { 240 | "variation_0": "c2", 241 | "v": "v1" 242 | }, 243 | { 244 | "variation_1": "v2" 245 | }, 246 | { 247 | "variation_2": "v3" 248 | } 249 | ] 250 | }, 251 | "multi_condition_toggle": { 252 | "key": "multi_condition_toggle", 253 | "enabled": true, 254 | "forClient": true, 255 | "version": 1, 256 | "disabledServe": { 257 | "select": 1 258 | }, 259 | "defaultServe": { 260 | "select": 1 261 | }, 262 | "rules": [ 263 | { 264 | "serve": { 265 | "select": 0 266 | }, 267 | "conditions": [ 268 | { 269 | "type": "string", 270 | "subject": "city", 271 | "predicate": "is one of", 272 | "objects": [ 273 | "1", 274 | "2", 275 | "3" 276 | ] 277 | }, 278 | { 279 | "type": "string", 280 | "subject": "os", 281 | "predicate": "is one of", 282 | "objects": [ 283 | "mac", 284 | "linux" 285 | ] 286 | } 287 | ] 288 | } 289 | ], 290 | "variations": [ 291 | { 292 | "variation_0": "" 293 | }, 294 | { 295 | "disabled_key": "disabled_value" 296 | } 297 | ] 298 | }, 299 | "disabled_toggle": { 300 | "key": "disabled_toggle", 301 | "enabled": false, 302 | "forClient": true, 303 | "version": 1, 304 | "disabledServe": { 305 | "select": 1 306 | }, 307 | "defaultServe": { 308 | "select": 0 309 | }, 310 | "rules": [], 311 | "variations": [ 312 | {}, 313 | { 314 | "disabled_key": "disabled_value" 315 | } 316 | ] 317 | }, 318 | "server_toggle": { 319 | "key": "server_toggle", 320 | "enabled": false, 321 | "forClient": false, 322 | "version": 1, 323 | "disabledServe": { 324 | "select": 1 325 | }, 326 | "defaultServe": { 327 | "select": 0 328 | }, 329 | "rules": [], 330 | "variations": [ 331 | {}, 332 | { 333 | "disabled_key": "disabled_value" 334 | } 335 | ] 336 | } 337 | }, 338 | "events": { 339 | "c52d1e0fd380f432a21f4743d59b26a0": { 340 | "name": "c52d1e0fd380f432a21f4743d59b26a0", 341 | "type": "PAGE_VIEW", 342 | "matcher": "SIMPLE", 343 | "url": "https://127.0.0.1/test" 344 | }, 345 | "b62d1e0fd380f432a21f47sss29b26a0": { 346 | "name": "b62d1e0fd380f432a21f47sss29b26a0", 347 | "type": "CLICK", 348 | "matcher": "SIMPLE", 349 | "url": "https://127.0.0.1/test", 350 | "selector": "#DEMO" 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /resources/fixtures/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "mapping": { 3 | "client-sdk-key1": "server-sdk-key1", 4 | "client-sdk-key2": "server-sdk-key2" 5 | } 6 | } -------------------------------------------------------------------------------- /resources/fixtures/segments.json: -------------------------------------------------------------------------------- 1 | { 2 | "segments": { 3 | "some_segment1-fjoaefjaam": { 4 | "key": "some_segment1", 5 | "uniqueId": "some_segment1-fjoaefjaam", 6 | "version": 2, 7 | "rules": [ 8 | { 9 | "conditions": [ 10 | { 11 | "type": "string", 12 | "subject": "city", 13 | "predicate": "in", 14 | "objects": [ 15 | "4" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /resources/fixtures/segments_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "some_segment1-fjoaefjaam": { 3 | "key": "some_segment1", 4 | "uniqueId": "some_segment1-fjoaefjaam", 5 | "version": 2, 6 | "rules": [ 7 | { 8 | "conditions": [ 9 | { 10 | "type": "string", 11 | "subject": "city", 12 | "predicate": "in", 13 | "objects": [ 14 | "4" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /resources/fixtures/toggles.json: -------------------------------------------------------------------------------- 1 | { 2 | "toggles": { 3 | "toggle_1": { 4 | "key": "toggle_1", 5 | "enabled": true, 6 | "forClient": true, 7 | "version": 1, 8 | "disabledServe": { 9 | "select": 1 10 | }, 11 | "defaultServe": { 12 | "split": { 13 | "distribution": [ 14 | [ 15 | [ 16 | 0, 17 | 3333 18 | ] 19 | ], 20 | [ 21 | [ 22 | 3333, 23 | 6666 24 | ] 25 | ], 26 | [ 27 | [ 28 | 6666, 29 | 10000 30 | ] 31 | ] 32 | ], 33 | "bucketBy": "user_set_key", 34 | "salt": "some_salt" 35 | } 36 | }, 37 | "rules": [ 38 | { 39 | "serve": { 40 | "select": 0 41 | }, 42 | "conditions": [ 43 | { 44 | "type": "string", 45 | "subject": "city", 46 | "predicate": "in", 47 | "objects": [ 48 | "1", 49 | "2", 50 | "3" 51 | ] 52 | } 53 | ] 54 | }, 55 | { 56 | "serve": { 57 | "select": 1 58 | }, 59 | "conditions": [ 60 | { 61 | "type": "segment", 62 | "predicate": "in", 63 | "object": [ 64 | "some_segment1-fjoaefjaam" 65 | ] 66 | } 67 | ] 68 | } 69 | ], 70 | "variations": [ 71 | { 72 | "variation_0": "c2", 73 | "v": "v1" 74 | }, 75 | { 76 | "variation_1": "v2" 77 | }, 78 | { 79 | "variation_2": "v3" 80 | } 81 | ] 82 | }, 83 | "multi_condition_toggle": { 84 | "key": "multi_condition_toggle", 85 | "enabled": true, 86 | "forClient": true, 87 | "version": 1, 88 | "disabledServe": { 89 | "select": 1 90 | }, 91 | "defaultServe": { 92 | "select": 1 93 | }, 94 | "rules": [ 95 | { 96 | "serve": { 97 | "select": 0 98 | }, 99 | "conditions": [ 100 | { 101 | "type": "string", 102 | "subject": "city", 103 | "predicate": "in", 104 | "objects": [ 105 | "1", 106 | "2", 107 | "3" 108 | ] 109 | }, 110 | { 111 | "type": "string", 112 | "subject": "os", 113 | "predicate": "in", 114 | "objects": [ 115 | "mac", 116 | "linux" 117 | ] 118 | } 119 | ] 120 | } 121 | ], 122 | "variations": [ 123 | { 124 | "variation_0": "" 125 | }, 126 | { 127 | "disabled_key": "disabled_value" 128 | } 129 | ] 130 | }, 131 | "disabled_toggle": { 132 | "key": "disabled_toggle", 133 | "enabled": false, 134 | "forClient": false, 135 | "version": 1, 136 | "disabledServe": { 137 | "select": 1 138 | }, 139 | "defaultServe": { 140 | "select": 0 141 | }, 142 | "rules": [], 143 | "variations": [ 144 | {}, 145 | { 146 | "disabled_key": "disabled_value" 147 | } 148 | ] 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /resources/fixtures/toggles_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk-key1": { 3 | "toggle1": { 4 | "key": "toggle1", 5 | "enabled": false, 6 | "forClient": true, 7 | "version": 1, 8 | "disabledServe": { 9 | "select": 1 10 | }, 11 | "defaultServe": { 12 | "select": 0 13 | }, 14 | "rules": [], 15 | "variations": [ 16 | {}, 17 | { 18 | "toggle1": "toggle1" 19 | } 20 | ] 21 | }, 22 | "toggle2": { 23 | "key": "toggle2", 24 | "enabled": false, 25 | "forClient": true, 26 | "version": 1, 27 | "disabledServe": { 28 | "select": 1 29 | }, 30 | "defaultServe": { 31 | "select": 0 32 | }, 33 | "rules": [], 34 | "variations": [ 35 | {}, 36 | { 37 | "toggle2": "toggle2" 38 | } 39 | ] 40 | } 41 | }, 42 | "sdk-key2": { 43 | "toggle3": { 44 | "key": "toggle3", 45 | "enabled": false, 46 | "forClient": true, 47 | "version": 1, 48 | "disabledServe": { 49 | "select": 1 50 | }, 51 | "defaultServe": { 52 | "select": 0 53 | }, 54 | "rules": [], 55 | "variations": [ 56 | {}, 57 | { 58 | "toggle3": "toggle3" 59 | } 60 | ] 61 | }, 62 | "toggle2": { 63 | "key": "toggle2", 64 | "enabled": false, 65 | "forClient": true, 66 | "version": 1, 67 | "disabledServe": { 68 | "select": 1 69 | }, 70 | "defaultServe": { 71 | "select": 0 72 | }, 73 | "rules": [], 74 | "variations": [ 75 | {}, 76 | { 77 | "toggle2": "toggle2" 78 | } 79 | ] 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | version = "1.64.0" 3 | components = [ 4 | "cargo", 5 | ] 6 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" -------------------------------------------------------------------------------- /src/base.rs: -------------------------------------------------------------------------------- 1 | use feature_probe_server_sdk::Url; 2 | use log::warn; 3 | use serde::Deserialize; 4 | use std::{fmt::Display, time::Duration}; 5 | use thiserror::Error; 6 | use tracing_core::{Event, Subscriber}; 7 | use tracing_log::NormalizeEvent; 8 | use tracing_subscriber::fmt::{ 9 | format::{self, FormatEvent, FormatFields}, 10 | time::{FormatTime, SystemTime}, 11 | FmtContext, 12 | }; 13 | use tracing_subscriber::registry::LookupSpan; 14 | 15 | #[derive(Debug, Error, Deserialize, PartialEq, Eq)] 16 | pub enum FPServerError { 17 | #[error("not found {0}")] 18 | NotFound(String), 19 | #[error("user base64 decode error")] 20 | UserDecodeError, 21 | #[error("config error: {0}")] 22 | ConfigError(String), 23 | #[error("not ready error: {0}")] 24 | NotReady(String), 25 | #[error("json error: {0}")] 26 | JsonError(String), 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub struct ServerConfig { 31 | pub toggles_url: Url, 32 | pub events_url: Url, 33 | pub keys_url: Option, 34 | pub analysis_url: Option, 35 | pub refresh_interval: Duration, 36 | pub server_sdk_key: Option, 37 | pub client_sdk_key: Option, 38 | pub server_port: u16, 39 | #[cfg(feature = "realtime")] 40 | pub realtime_port: u16, 41 | #[cfg(feature = "realtime")] 42 | pub realtime_path: String, 43 | } 44 | 45 | impl ServerConfig { 46 | pub fn try_parse(config: config::Config) -> Result { 47 | let toggles_url = match config.get_string("toggles_url") { 48 | Err(_) => { 49 | return Err(FPServerError::ConfigError( 50 | "NOT SET FP_TOGGLES_URL".to_owned(), 51 | )) 52 | } 53 | Ok(url) => match Url::parse(&url) { 54 | Err(e) => { 55 | return Err(FPServerError::ConfigError(format!( 56 | "INVALID FP_TOGGLES_URL: {}", 57 | e, 58 | ))) 59 | } 60 | Ok(u) => u, 61 | }, 62 | }; 63 | 64 | let events_url = match config.get_string("events_url") { 65 | Err(_) => { 66 | return Err(FPServerError::ConfigError( 67 | "NOT SET FP_EVENTS_URL".to_owned(), 68 | )) 69 | } 70 | Ok(url) => match Url::parse(&url) { 71 | Err(e) => { 72 | return Err(FPServerError::ConfigError(format!( 73 | "INVALID FP_EVENTS_URL: {}", 74 | e, 75 | ))) 76 | } 77 | Ok(u) => u, 78 | }, 79 | }; 80 | let client_sdk_key = config.get_string("client_sdk_key").ok(); 81 | let server_sdk_key = config.get_string("server_sdk_key").ok(); 82 | 83 | let keys_url = match config.get_string("keys_url") { 84 | Ok(url) => match Url::parse(&url) { 85 | Err(e) => { 86 | return Err(FPServerError::ConfigError(format!( 87 | "INVALID FP_KEYS_URL: {}", 88 | e, 89 | ))) 90 | } 91 | Ok(u) => Some(u), 92 | }, 93 | Err(_) => { 94 | if client_sdk_key.is_none() { 95 | return Err(FPServerError::ConfigError( 96 | "NOT SET FP_CLIENT_SDK_KEY".to_owned(), 97 | )); 98 | } 99 | if server_sdk_key.is_none() { 100 | return Err(FPServerError::ConfigError( 101 | "NOT SET FP_SERVER_SDK_KEY".to_owned(), 102 | )); 103 | } 104 | None 105 | } 106 | }; 107 | 108 | let analysis_url = match config.get_string("analysis_url") { 109 | Ok(url) => match Url::parse(&url) { 110 | Err(e) => { 111 | return Err(FPServerError::ConfigError(format!( 112 | "INVALID FP_ANALYSIS_URL: {}", 113 | e, 114 | ))) 115 | } 116 | Ok(u) => Some(u), 117 | }, 118 | Err(_) => { 119 | warn!("NOT SET FP_ANALYSIS_URL"); 120 | None 121 | } 122 | }; 123 | 124 | let refresh_interval = match config.get_int("refresh_seconds") { 125 | Err(_) => { 126 | return Err(FPServerError::ConfigError( 127 | "NOT SET FP_REFRESH_SECONDS".to_owned(), 128 | )) 129 | } 130 | Ok(interval) => Duration::from_secs(interval as u64), 131 | }; 132 | let server_port = match config.get_int("server_port") { 133 | Err(_) => 9000, // default port 134 | Ok(port) => port as u16, 135 | }; 136 | 137 | #[cfg(feature = "realtime")] 138 | let realtime_port = match config.get_int("realtime_port") { 139 | Err(_) => 9100, // default port 140 | Ok(port) => port as u16, 141 | }; 142 | 143 | #[cfg(feature = "realtime")] 144 | let realtime_path = match config.get_string("realtime_path") { 145 | Err(_) => "/server/realtime".to_owned(), // default port 146 | Ok(path) => path, 147 | }; 148 | 149 | Ok(ServerConfig { 150 | toggles_url, 151 | events_url, 152 | analysis_url, 153 | keys_url, 154 | refresh_interval, 155 | client_sdk_key, 156 | server_sdk_key, 157 | server_port, 158 | #[cfg(feature = "realtime")] 159 | realtime_port, 160 | #[cfg(feature = "realtime")] 161 | realtime_path, 162 | }) 163 | } 164 | } 165 | 166 | impl Display for ServerConfig { 167 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 168 | let keys_url = match &self.keys_url { 169 | None => "None".to_owned(), 170 | Some(s) => s.to_string(), 171 | }; 172 | let analysis_url = match &self.analysis_url { 173 | None => "None".to_owned(), 174 | Some(s) => s.to_string(), 175 | }; 176 | write!(f, "server_port {}, toggles_url {}, events_url {}, analysis_url {}, keys_url {}, refresh_interval {:?}, client_sdk_key {:?}, server_sdk_key {:?}", 177 | self.server_port, 178 | self.toggles_url, 179 | self.events_url, 180 | analysis_url, 181 | keys_url, 182 | self.refresh_interval, 183 | self.client_sdk_key, 184 | self.server_sdk_key, 185 | ) 186 | } 187 | } 188 | 189 | #[derive(Debug)] 190 | pub struct LogFormatter { 191 | timer: T, 192 | } 193 | 194 | impl LogFormatter 195 | where 196 | T2: FormatTime, 197 | { 198 | #[allow(dead_code)] 199 | pub fn with_timer(timer: T2) -> Self { 200 | LogFormatter { timer } 201 | } 202 | } 203 | 204 | impl FormatEvent for LogFormatter 205 | where 206 | C: Subscriber + for<'a> LookupSpan<'a>, 207 | N: for<'a> FormatFields<'a> + 'static, 208 | T: FormatTime, 209 | { 210 | fn format_event( 211 | &self, 212 | ctx: &FmtContext<'_, C, N>, 213 | mut writer: format::Writer<'_>, 214 | event: &Event<'_>, 215 | ) -> std::fmt::Result { 216 | let normalized = event.normalized_metadata(); 217 | let meta = normalized.as_ref().unwrap_or_else(|| event.metadata()); 218 | write!(writer, "[{}][", meta.level())?; 219 | if self.timer.format_time(&mut writer).is_err() { 220 | write!(writer, "")?; 221 | } 222 | write!(writer, "]")?; 223 | if let Some(m) = meta.module_path() { 224 | write!(writer, "[{}", m)?; 225 | } 226 | if let Some(l) = meta.line() { 227 | write!(writer, ":{}", l)?; 228 | } 229 | write!(writer, "]")?; 230 | 231 | ctx.field_format().format_fields(writer.by_ref(), event)?; 232 | writeln!(writer) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/http/handler.rs: -------------------------------------------------------------------------------- 1 | use super::{cors_headers, ClientParams, SdkAuthorization, ToggleUpdateParams}; 2 | #[cfg(feature = "unstable")] 3 | use super::{SecretsParams, SegmentUpdateParams}; 4 | use crate::FPServerError::{NotFound, NotReady}; 5 | use crate::{repo::SdkRepository, FPServerError}; 6 | use axum::{ 7 | async_trait, 8 | extract::Query, 9 | http::{HeaderMap, HeaderValue, StatusCode}, 10 | response::{IntoResponse, Response}, 11 | Json, TypedHeader, 12 | }; 13 | use feature_probe_event::collector::{EventHandler, FPEventError}; 14 | use feature_probe_server_sdk::{FPUser, Repository, SyncType, Url}; 15 | use log::info; 16 | use parking_lot::Mutex; 17 | use reqwest::{ 18 | header::{self, AUTHORIZATION, USER_AGENT}, 19 | Client, Method, 20 | }; 21 | use serde_json::Value; 22 | use std::{ 23 | collections::{HashMap, VecDeque}, 24 | fs, 25 | path::PathBuf, 26 | sync::Arc, 27 | time::Duration, 28 | }; 29 | use tracing::debug; 30 | 31 | #[async_trait] 32 | pub trait HttpHandler { 33 | async fn server_sdk_toggles( 34 | &self, 35 | TypedHeader(SdkAuthorization(sdk_key)): TypedHeader, 36 | ) -> Result; 37 | 38 | async fn client_sdk_toggles( 39 | &self, 40 | Query(params): Query, 41 | TypedHeader(SdkAuthorization(sdk_key)): TypedHeader, 42 | ) -> Result; 43 | 44 | async fn client_sdk_events( 45 | &self, 46 | TypedHeader(SdkAuthorization(sdk_key)): TypedHeader, 47 | ) -> Result; 48 | 49 | async fn update_toggles( 50 | &self, 51 | Json(params): Json, 52 | ) -> Result; 53 | 54 | #[cfg(feature = "unstable")] 55 | async fn update_segments( 56 | &self, 57 | Json(params): Json, 58 | ) -> Result; 59 | 60 | #[cfg(feature = "unstable")] 61 | async fn check_secrets( 62 | &self, 63 | Json(_params): Json, 64 | ) -> Result>, FPServerError>; 65 | 66 | async fn all_secrets(&self) -> Result>, FPServerError>; 67 | } 68 | 69 | #[derive(Clone)] 70 | pub struct FpHttpHandler { 71 | pub repo: Arc, 72 | pub http_client: Arc, 73 | pub events_url: Url, 74 | pub analysis_url: Option, 75 | pub events_timeout: Duration, 76 | } 77 | 78 | #[async_trait] 79 | impl HttpHandler for FpHttpHandler { 80 | async fn server_sdk_toggles( 81 | &self, 82 | TypedHeader(SdkAuthorization(sdk_key)): TypedHeader, 83 | ) -> Result { 84 | match self.repo.server_sdk_repo_string(&sdk_key) { 85 | Ok(body) => Ok(( 86 | StatusCode::OK, 87 | [(header::CONTENT_TYPE, "application/json")], 88 | body, 89 | ) 90 | .into_response()), 91 | Err(e) => match e { 92 | NotReady(_) => Ok(( 93 | StatusCode::SERVICE_UNAVAILABLE, 94 | [(header::CONTENT_TYPE, "application/json")], 95 | "{}", 96 | ) 97 | .into_response()), 98 | NotFound(_) => Ok(( 99 | StatusCode::OK, 100 | [(header::CONTENT_TYPE, "application/json")], 101 | serde_json::to_string(&Repository::default()).unwrap(), 102 | ) 103 | .into_response()), 104 | _ => Err(e), 105 | }, 106 | } 107 | } 108 | 109 | async fn client_sdk_toggles( 110 | &self, 111 | Query(params): Query, 112 | TypedHeader(SdkAuthorization(sdk_key)): TypedHeader, 113 | ) -> Result { 114 | let user = decode_user(params.user)?; 115 | match self.repo.client_sdk_eval_string(&sdk_key, &user) { 116 | Ok(body) => Ok((StatusCode::OK, cors_headers(), body).into_response()), 117 | Err(e) => match e { 118 | NotReady(_) => { 119 | Ok((StatusCode::SERVICE_UNAVAILABLE, cors_headers(), "{}").into_response()) 120 | } 121 | NotFound(_) => Ok((StatusCode::OK, cors_headers(), "{}").into_response()), 122 | _ => Err(e), 123 | }, 124 | } 125 | } 126 | 127 | async fn client_sdk_events( 128 | &self, 129 | TypedHeader(SdkAuthorization(sdk_key)): TypedHeader, 130 | ) -> Result { 131 | match self.repo.client_sdk_events_string(&sdk_key) { 132 | Ok(body) => Ok((StatusCode::OK, cors_headers(), body).into_response()), 133 | Err(e) => match e { 134 | NotReady(_) => { 135 | Ok((StatusCode::SERVICE_UNAVAILABLE, cors_headers(), "{}").into_response()) 136 | } 137 | NotFound(_) => Ok((StatusCode::OK, cors_headers(), "{}").into_response()), 138 | _ => Err(e), 139 | }, 140 | } 141 | } 142 | 143 | async fn update_toggles( 144 | &self, 145 | Json(params): Json, 146 | ) -> Result { 147 | let sdk_key = params.sdk_key; 148 | match self.repo.client_sync_now(&sdk_key, SyncType::Realtime) { 149 | Ok(_sdk_key) => Ok((StatusCode::OK, cors_headers(), "{}").into_response()), 150 | Err(e) => match e { 151 | NotFound(_) => Ok(( 152 | StatusCode::BAD_REQUEST, 153 | [(header::CONTENT_TYPE, "application/json")], 154 | "{}", 155 | ) 156 | .into_response()), 157 | _ => Err(e), 158 | }, 159 | } 160 | } 161 | 162 | #[cfg(feature = "unstable")] 163 | async fn update_toggles( 164 | &self, 165 | Json(params): Json, 166 | ) -> Result { 167 | self.repo.update_toggles(¶ms.sdk_key, params.toggles)?; 168 | let status = StatusCode::OK; 169 | Ok(status.into_response()) 170 | } 171 | #[cfg(feature = "unstable")] 172 | 173 | async fn update_segments( 174 | &self, 175 | Json(params): Json, 176 | ) -> Result { 177 | self.repo.update_segments(params.segments)?; 178 | let status = StatusCode::OK; 179 | let body = ""; 180 | Ok((status, body).into_response()) 181 | } 182 | 183 | #[cfg(feature = "unstable")] 184 | async fn check_secrets( 185 | &self, 186 | Json(_params): Json, 187 | ) -> Result>, FPServerError> { 188 | Ok(HashMap::new().into()) 189 | } 190 | 191 | async fn all_secrets(&self) -> Result>, FPServerError> { 192 | let secret_keys = self.repo.secret_keys(); 193 | Ok(secret_keys.into()) 194 | } 195 | } 196 | 197 | #[async_trait] 198 | impl EventHandler for FpHttpHandler { 199 | async fn handle_events( 200 | &self, 201 | sdk_key: String, 202 | user_agent: String, 203 | headers: HeaderMap, 204 | data: VecDeque, 205 | ) -> Result { 206 | let http_client = self.http_client.clone(); 207 | let events_url = self.events_url.clone(); 208 | let analysis_url = self.analysis_url.clone(); 209 | let timeout = self.events_timeout; 210 | let server_sdk_key = { 211 | if sdk_key.starts_with("client") { 212 | match self.repo.secret_keys().get(sdk_key.as_str()) { 213 | Some(key) => key.clone(), 214 | None => { 215 | return Ok((StatusCode::UNAUTHORIZED, cors_headers(), "{}").into_response()) 216 | } 217 | } 218 | } else { 219 | sdk_key.clone() 220 | } 221 | }; 222 | let headers = build_header(headers, server_sdk_key, &user_agent); 223 | send_events( 224 | headers, 225 | events_url, 226 | analysis_url, 227 | timeout, 228 | data, 229 | http_client, 230 | ) 231 | .await; 232 | Ok((StatusCode::OK, cors_headers(), "{}").into_response()) 233 | } 234 | } 235 | 236 | async fn send_events( 237 | headers: HeaderMap, 238 | events_url: Url, 239 | analysis_url: Option, 240 | timeout: Duration, 241 | data: VecDeque, 242 | http_client: Arc, 243 | ) { 244 | if let Some(analysis_url) = analysis_url { 245 | let headers = headers.clone(); 246 | let data = data.clone(); 247 | let http_client = http_client.clone(); 248 | tokio::spawn(async move { 249 | do_send_events(headers, analysis_url, timeout, data, http_client).await; 250 | }); 251 | } 252 | 253 | tokio::spawn(async move { 254 | do_send_events(headers, events_url, timeout, data, http_client).await; 255 | }); 256 | } 257 | 258 | async fn do_send_events( 259 | headers: HeaderMap, 260 | url: Url, 261 | timeout: Duration, 262 | data: VecDeque, 263 | http_client: Arc, 264 | ) { 265 | let traffic_request = http_client 266 | .request(Method::POST, url.clone()) 267 | .headers(headers.clone()) 268 | .timeout(timeout) 269 | .json(&data); 270 | 271 | debug!("traffic post data: {data:?}"); 272 | debug!("traffic post req: {traffic_request:?}"); 273 | 274 | match traffic_request.send().await { 275 | Err(e) => info!("traffic post error: {}", e), 276 | Ok(r) => info!("traffic post success: {:?}", r), 277 | }; 278 | } 279 | 280 | fn build_header(headers: HeaderMap, sdk_key: String, user_agent: &str) -> HeaderMap { 281 | let ua = headers.get("ua"); 282 | let auth = SdkAuthorization(sdk_key).encode(); 283 | let mut headers = HeaderMap::with_capacity(3); 284 | headers.append(AUTHORIZATION, auth); 285 | if let Ok(v) = HeaderValue::from_str(user_agent) { 286 | headers.append(USER_AGENT, v); 287 | } 288 | 289 | if let Some(ua) = ua { 290 | headers.append("ua", ua.clone()); 291 | } 292 | headers 293 | } 294 | 295 | fn decode_user(user: String) -> Result { 296 | if let Ok(user) = base64::decode(user) { 297 | if let Ok(user) = String::from_utf8(user) { 298 | if let Ok(user) = serde_json::from_str::(&user) { 299 | return Ok(user); 300 | } 301 | } 302 | } 303 | Err(FPServerError::UserDecodeError) 304 | } 305 | 306 | // used for mocking feature probe API 307 | #[derive(Clone, Default)] 308 | pub struct LocalFileHttpHandlerForTest { 309 | pub version_update: bool, 310 | body: Arc>>, 311 | } 312 | 313 | #[async_trait] 314 | impl HttpHandler for LocalFileHttpHandlerForTest { 315 | async fn server_sdk_toggles( 316 | &self, 317 | TypedHeader(SdkAuthorization(_sdk_key)): TypedHeader, 318 | ) -> Result { 319 | let mut lock = self.body.lock(); 320 | let body = match &*lock { 321 | None => { 322 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 323 | path.push("resources/fixtures/repo.json"); 324 | let body = fs::read_to_string(path).unwrap(); 325 | *lock = Some(body.clone()); 326 | body 327 | } 328 | Some(body) => body.clone(), 329 | }; 330 | 331 | if self.version_update { 332 | // SAFETY: json is valid 333 | let mut repo: Repository = serde_json::from_str(&body).unwrap(); 334 | let version = repo.version.unwrap_or_default(); 335 | repo.version = Some(version + 1); 336 | // SAFETY: repo charset valid 337 | *lock = Some(serde_json::to_string(&repo).unwrap()); 338 | } 339 | 340 | Ok(( 341 | StatusCode::OK, 342 | [(header::CONTENT_TYPE, "application/json")], 343 | body, 344 | ) 345 | .into_response()) 346 | } 347 | 348 | async fn client_sdk_toggles( 349 | &self, 350 | Query(_params): Query, 351 | TypedHeader(SdkAuthorization(_sdk_key)): TypedHeader, 352 | ) -> Result { 353 | let body = "{}".to_owned(); 354 | Ok(( 355 | StatusCode::OK, 356 | [(header::CONTENT_TYPE, "application/json")], 357 | body, 358 | ) 359 | .into_response()) 360 | } 361 | 362 | async fn client_sdk_events( 363 | &self, 364 | TypedHeader(SdkAuthorization(_sdk_key)): TypedHeader, 365 | ) -> Result { 366 | let body = "{}".to_owned(); 367 | Ok(( 368 | StatusCode::OK, 369 | [(header::CONTENT_TYPE, "application/json")], 370 | body, 371 | ) 372 | .into_response()) 373 | } 374 | 375 | async fn update_toggles( 376 | &self, 377 | Json(_params): Json, 378 | ) -> Result { 379 | let body = "{}".to_owned(); 380 | Ok(( 381 | StatusCode::OK, 382 | [(header::CONTENT_TYPE, "application/json")], 383 | body, 384 | ) 385 | .into_response()) 386 | } 387 | 388 | #[cfg(feature = "unstable")] 389 | async fn update_segments( 390 | &self, 391 | Json(_params): Json, 392 | ) -> Result { 393 | let status = StatusCode::OK; 394 | let body = ""; 395 | Ok((status, body).into_response()) 396 | } 397 | 398 | #[cfg(feature = "unstable")] 399 | async fn check_secrets( 400 | &self, 401 | Json(_params): Json, 402 | ) -> Result>, FPServerError> { 403 | Ok(HashMap::new().into()) 404 | } 405 | 406 | async fn all_secrets(&self) -> Result>, FPServerError> { 407 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 408 | path.push("resources/fixtures/secrets.json"); 409 | let json_str = fs::read_to_string(path).unwrap(); 410 | let secret_keys = 411 | serde_json::from_str::>>(&json_str).unwrap(); 412 | let secret_keys = secret_keys.get("mapping").unwrap().to_owned(); 413 | Ok(secret_keys.into()) 414 | } 415 | } 416 | 417 | #[async_trait] 418 | impl EventHandler for LocalFileHttpHandlerForTest { 419 | async fn handle_events( 420 | &self, 421 | _sdk_key: String, 422 | _user_agent: String, 423 | _headers: HeaderMap, 424 | _data: VecDeque, 425 | ) -> Result { 426 | Ok((StatusCode::OK, cors_headers(), "").into_response()) 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /src/http/mod.rs: -------------------------------------------------------------------------------- 1 | mod handler; 2 | 3 | use axum::{ 4 | extract::{Extension, Query}, 5 | handler::Handler, 6 | headers::HeaderName, 7 | http::StatusCode, 8 | response::{Html, IntoResponse, Response}, 9 | routing::{get, post}, 10 | Json, Router, TypedHeader, 11 | }; 12 | use reqwest::header; 13 | 14 | use crate::FPServerError; 15 | use feature_probe_event::collector::{post_events, EventHandler}; 16 | use feature_probe_server_sdk::SdkAuthorization; 17 | #[cfg(feature = "unstable")] 18 | use feature_probe_server_sdk::Segment; 19 | use feature_probe_server_sdk::Toggle; 20 | pub use handler::{FpHttpHandler, HttpHandler, LocalFileHttpHandlerForTest}; 21 | use serde::Deserialize; 22 | use serde_json::json; 23 | use std::collections::HashMap; 24 | use std::net::SocketAddr; 25 | 26 | pub async fn serve_http(port: u16, handler: T) 27 | where 28 | T: HttpHandler + EventHandler + Clone + Send + Sync + 'static, 29 | { 30 | let app = Router::new() 31 | .route("/", get(root_handler)) 32 | .route( 33 | "/api/client-sdk/toggles", 34 | get(client_sdk_toggles::).options(client_cors), 35 | ) 36 | .route( 37 | "/api/client-sdk/events", 38 | get(client_sdk_events::).options(client_cors), 39 | ) 40 | .route("/api/server-sdk/toggles", get(server_sdk_toggles::)) 41 | .route("/api/events", post(post_events::).options(client_cors)) 42 | .route("/internal/all_secrets", get(all_secrets::)) // not for public network 43 | .route("/internal/update_toggles", post(update_toggles::)) 44 | .layer(Extension(handler)) 45 | .fallback(handler_404.into_service()); 46 | 47 | #[cfg(feature = "unstable")] 48 | let app = app 49 | .route("/internal/server/segments", post(update_segments::)) 50 | .route("/intelnal/server/check_secrets", post(check_secrets::)); 51 | 52 | let addr = SocketAddr::from(([0, 0, 0, 0], port)); 53 | axum::Server::bind(&addr) 54 | .serve(app.into_make_service()) 55 | .await 56 | .unwrap(); 57 | } 58 | 59 | async fn client_cors() -> Response { 60 | (StatusCode::OK, cors_headers()).into_response() 61 | } 62 | 63 | async fn client_sdk_toggles( 64 | params: Query, 65 | sdk_key: TypedHeader, 66 | Extension(handler): Extension, 67 | ) -> Result 68 | where 69 | T: HttpHandler + Clone + Send + Sync + 'static, 70 | { 71 | handler.client_sdk_toggles(params, sdk_key).await 72 | } 73 | 74 | async fn client_sdk_events( 75 | sdk_key: TypedHeader, 76 | Extension(handler): Extension, 77 | ) -> Result 78 | where 79 | T: HttpHandler + Clone + Send + Sync + 'static, 80 | { 81 | handler.client_sdk_events(sdk_key).await 82 | } 83 | 84 | async fn server_sdk_toggles( 85 | sdk_key: TypedHeader, 86 | Extension(handler): Extension, 87 | ) -> Result 88 | where 89 | T: HttpHandler + Clone + Send + Sync + 'static, 90 | { 91 | handler.server_sdk_toggles(sdk_key).await 92 | } 93 | 94 | async fn update_toggles( 95 | params: Json, 96 | Extension(handler): Extension, 97 | ) -> Result 98 | where 99 | T: HttpHandler + Clone + Send + Sync + 'static, 100 | { 101 | handler.update_toggles(params).await 102 | } 103 | 104 | #[cfg(feature = "unstable")] 105 | async fn update_segments( 106 | params: Json, 107 | Extension(handler): Extension, 108 | ) -> Result 109 | where 110 | T: HttpHandler + Clone + Send + Sync + 'static, 111 | { 112 | handler.update_segments(params).await 113 | } 114 | 115 | #[cfg(feature = "unstable")] 116 | async fn check_secrets( 117 | params: Json, 118 | Extension(handler): Extension, 119 | ) -> Result>, FPServerError> 120 | where 121 | T: HttpHandler + Clone + Send + Sync + 'static, 122 | { 123 | handler.check_secrets(params).await 124 | } 125 | 126 | async fn all_secrets( 127 | Extension(handler): Extension, 128 | ) -> Result>, FPServerError> 129 | where 130 | T: HttpHandler + Clone + Send + Sync + 'static, 131 | { 132 | handler.all_secrets().await 133 | } 134 | 135 | async fn root_handler() -> Html<&'static str> { 136 | Html("

Feature Probe Server

") 137 | } 138 | 139 | async fn handler_404() -> impl IntoResponse { 140 | (StatusCode::NOT_FOUND, "") 141 | } 142 | 143 | #[derive(Debug, Deserialize)] 144 | pub struct ClientParams { 145 | user: String, 146 | } 147 | 148 | #[allow(unused)] 149 | #[derive(Debug, Deserialize)] 150 | pub struct ToggleUpdateParams { 151 | sdk_key: String, 152 | #[serde(default)] 153 | toggles: HashMap, 154 | #[serde(default)] 155 | version: Option, 156 | } 157 | 158 | #[cfg(feature = "unstable")] 159 | #[derive(Debug, Deserialize)] 160 | pub struct SegmentUpdateParams { 161 | segments: HashMap, 162 | } 163 | 164 | #[cfg(feature = "unstable")] 165 | #[derive(Debug, Deserialize)] 166 | pub struct SecretsParams { 167 | _secrets: HashMap, 168 | } 169 | 170 | impl IntoResponse for FPServerError { 171 | fn into_response(self) -> Response { 172 | let (status, error_message) = match self { 173 | FPServerError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()), 174 | FPServerError::UserDecodeError => (StatusCode::BAD_REQUEST, self.to_string()), 175 | _ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()), 176 | }; 177 | 178 | let body = Json(json!({ 179 | "error": error_message, 180 | })); 181 | 182 | (status, cors_headers(), body).into_response() 183 | } 184 | } 185 | 186 | pub fn cors_headers() -> [(HeaderName, &'static str); 4] { 187 | [ 188 | (header::CONTENT_TYPE, "application/json"), 189 | (header::ACCESS_CONTROL_ALLOW_HEADERS, "*"), 190 | (header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"), 191 | ( 192 | header::ACCESS_CONTROL_ALLOW_METHODS, 193 | "GET, POST, PUT, DELETE, OPTIONS", 194 | ), 195 | ] 196 | } 197 | 198 | #[cfg(test)] 199 | mod tests { 200 | use super::{handler::LocalFileHttpHandlerForTest, *}; 201 | #[cfg(feature = "realtime")] 202 | use crate::realtime::RealtimeSocket; 203 | use crate::{base::ServerConfig, repo::SdkRepository}; 204 | use feature_probe_server_sdk::{FPDetail, FPUser, Repository, SdkAuthorization}; 205 | use reqwest::header::CONTENT_TYPE; 206 | use reqwest::{header::AUTHORIZATION, Client, Error, Method, Url}; 207 | use serde_json::Value; 208 | use std::{fs, path::PathBuf, sync::Arc, time::Duration}; 209 | 210 | #[test] 211 | fn deserialize_toggle_udpate_param() { 212 | let toggle_update_param = r#"{"sdk_key": "key1"}"#; 213 | let p: ToggleUpdateParams = serde_json::from_str(toggle_update_param).unwrap(); 214 | assert_eq!(p.sdk_key, "key1"); 215 | } 216 | 217 | #[tokio::test] 218 | async fn test_fp_server_connect_fp_api() { 219 | let server_sdk_key = "server-sdk-key1".to_owned(); 220 | let client_sdk_key = "client-sdk-key1".to_owned(); 221 | let mock_api_port = 9002; 222 | let fp_server_port = 9003; 223 | setup_mock_api(mock_api_port).await; 224 | let repo = setup_fp_server( 225 | mock_api_port, 226 | fp_server_port, 227 | &client_sdk_key, 228 | &server_sdk_key, 229 | ) 230 | .await; 231 | tokio::time::sleep(Duration::from_millis(100)).await; // wait fp server port listen 232 | let repo_string = repo.server_sdk_repo_string(&server_sdk_key); 233 | assert!(repo_string.is_ok()); 234 | 235 | let resp = http_get( 236 | format!("http://127.0.0.1:{}/api/server-sdk/toggles", fp_server_port), 237 | Some(server_sdk_key.clone()), 238 | ) 239 | .await; 240 | assert!(resp.is_ok()); 241 | let body = resp.unwrap().text().await; 242 | assert!(body.is_ok()); 243 | let body = body.unwrap(); 244 | let r = serde_json::from_str::(&body).unwrap(); 245 | assert_eq!(r, repo_from_test_file()); 246 | 247 | let resp = http_get( 248 | format!("http://127.0.0.1:{}/api/server-sdk/toggles", fp_server_port), 249 | Some("no_exist_server_key".to_owned()), 250 | ) 251 | .await; 252 | assert!(resp.is_ok()); 253 | let body = resp.unwrap().text().await; 254 | assert!(body.is_ok()); 255 | let body = body.unwrap(); 256 | let r = serde_json::from_str::(&body).unwrap(); 257 | assert_eq!(r, Repository::default()); 258 | 259 | let resp = http_get( 260 | format!("http://127.0.0.1:{}/internal/all_secrets", fp_server_port), 261 | None, 262 | ) 263 | .await; 264 | assert!(resp.is_ok()); 265 | 266 | let resp = http_get( 267 | format!("http://127.0.0.1:{}/internal/all_secrets", mock_api_port), 268 | None, 269 | ) 270 | .await; 271 | assert!(resp.is_ok()); 272 | } 273 | 274 | #[tokio::test] 275 | async fn test_server_sdk_toggles() { 276 | let port = 9004; 277 | let handler = LocalFileHttpHandlerForTest::default(); 278 | tokio::spawn(crate::http::serve_http::( 279 | port, handler, 280 | )); 281 | tokio::time::sleep(Duration::from_millis(100)).await; // wait port listen 282 | 283 | let resp = http_get( 284 | format!("http://127.0.0.1:{}/api/server-sdk/toggles", port), 285 | Some("sdk-key".to_owned()), 286 | ) 287 | .await; 288 | assert!(resp.is_ok(), "response invalid"); 289 | let body = resp.unwrap().text().await; 290 | assert!(body.is_ok(), "response body error"); 291 | let body = body.unwrap(); 292 | let r = serde_json::from_str::(&body).unwrap(); 293 | assert_eq!(r, repo_from_test_file()); 294 | } 295 | 296 | #[tokio::test] 297 | async fn test_client_sdk_toggles() { 298 | let server_sdk_key = "server-sdk-key1".to_owned(); 299 | let client_sdk_key = "client-sdk-key1".to_owned(); 300 | let mock_api_port = 9005; 301 | let fp_server_port = 9006; 302 | setup_mock_api(mock_api_port).await; 303 | let _ = setup_fp_server( 304 | mock_api_port, 305 | fp_server_port, 306 | &client_sdk_key, 307 | &server_sdk_key, 308 | ) 309 | .await; 310 | tokio::time::sleep(Duration::from_millis(100)).await; // wait fp server port listen 311 | 312 | let user = FPUser::new().with("city", "1"); 313 | let user_json = serde_json::to_string(&user).unwrap(); 314 | let user_base64 = base64::encode(&user_json); 315 | let resp = http_get( 316 | format!( 317 | "http://127.0.0.1:{}/api/client-sdk/toggles?user={}", 318 | fp_server_port, user_base64 319 | ), 320 | Some(client_sdk_key.clone()), 321 | ) 322 | .await; 323 | assert!(resp.is_ok(), "response invalid"); 324 | let text = resp.unwrap().text().await; 325 | assert!(text.is_ok(), "response text error"); 326 | let text = text.unwrap(); 327 | let toggles = serde_json::from_str::>>(&text); 328 | assert!(toggles.is_ok(), "response can not deserialize"); 329 | let toggles = toggles.unwrap(); 330 | let t = toggles.get("bool_toggle"); 331 | assert!(t.is_some(), "toggle not found"); 332 | let t = t.unwrap(); 333 | assert_eq!(t.rule_index, Some(0)); 334 | assert_eq!(t.value.as_bool(), Some(true)); 335 | 336 | // for test coverage 337 | let resp = http_get( 338 | format!( 339 | "http://127.0.0.1:{}/api/client-sdk/toggles?user={}", 340 | mock_api_port, user_base64 341 | ), 342 | Some(client_sdk_key.clone()), 343 | ) 344 | .await; 345 | assert!(resp.is_ok()) 346 | } 347 | 348 | #[tokio::test] 349 | async fn test_events() { 350 | let server_sdk_key = "server-sdk-key1".to_owned(); 351 | let client_sdk_key = "client-sdk-key1".to_owned(); 352 | let mock_api_port = 9007; 353 | let fp_server_port = 9008; 354 | setup_mock_api(mock_api_port).await; 355 | let _ = setup_fp_server( 356 | mock_api_port, 357 | fp_server_port, 358 | &client_sdk_key, 359 | &server_sdk_key, 360 | ) 361 | .await; 362 | tokio::time::sleep(Duration::from_millis(100)).await; // wait fp server port listen 363 | 364 | let resp = http_post( 365 | format!("http://127.0.0.1:{}/api/events", fp_server_port), 366 | Some(client_sdk_key.clone()), 367 | "[]".to_owned(), 368 | ) 369 | .await; 370 | assert!(resp.is_ok()); 371 | 372 | let resp = http_post( 373 | format!("http://127.0.0.1:{}/api/events", mock_api_port), 374 | Some(client_sdk_key.clone()), 375 | "[]".to_owned(), 376 | ) 377 | .await; 378 | assert!(resp.is_ok()); 379 | } 380 | 381 | async fn http_get(url: String, sdk_key: Option) -> Result { 382 | let url = Url::parse(&url).unwrap(); 383 | let timeout = Duration::from_secs(1); 384 | let mut request = Client::new().request(Method::GET, url); 385 | 386 | if let Some(sdk_key) = sdk_key { 387 | let auth = SdkAuthorization(sdk_key).encode(); 388 | request = request.header(AUTHORIZATION, auth); 389 | } 390 | request = request.timeout(timeout); 391 | request.send().await 392 | } 393 | 394 | async fn http_post( 395 | url: String, 396 | sdk_key: Option, 397 | body: String, 398 | ) -> Result { 399 | let url = Url::parse(&url).unwrap(); 400 | let timeout = Duration::from_secs(1); 401 | let mut request = Client::new() 402 | .request(Method::POST, url) 403 | .header(CONTENT_TYPE, "application/json") 404 | .header("user-agent", "Rust/0.0.0") 405 | .header("ua", "Rust/0.0.0") 406 | .body(body); 407 | 408 | if let Some(sdk_key) = sdk_key { 409 | let auth = SdkAuthorization(sdk_key).encode(); 410 | request = request.header(AUTHORIZATION, auth); 411 | } 412 | request = request.timeout(timeout); 413 | request.send().await 414 | } 415 | 416 | fn repo_from_test_file() -> Repository { 417 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 418 | path.push("resources/fixtures/repo.json"); 419 | let json_str = fs::read_to_string(path).unwrap(); 420 | serde_json::from_str::(&json_str).unwrap() 421 | } 422 | 423 | async fn setup_mock_api(port: u16) { 424 | let mock_feature_probe_api = LocalFileHttpHandlerForTest::default(); 425 | tokio::spawn(crate::http::serve_http::( 426 | port, 427 | mock_feature_probe_api, 428 | )); 429 | tokio::time::sleep(Duration::from_millis(100)).await; 430 | } 431 | 432 | async fn setup_fp_server( 433 | target_port: u16, 434 | listen_port: u16, 435 | client_sdk_key: &str, 436 | server_sdk_key: &str, 437 | ) -> Arc { 438 | let toggles_url = Url::parse(&format!( 439 | "http://127.0.0.1:{}/api/server-sdk/toggles", 440 | target_port 441 | )) 442 | .unwrap(); 443 | let events_url = 444 | Url::parse(&format!("http://127.0.0.1:{}/api/events", target_port)).unwrap(); 445 | let analysis_url = None; 446 | let config = ServerConfig { 447 | toggles_url, 448 | events_url: events_url.clone(), 449 | refresh_interval: Duration::from_secs(1), 450 | analysis_url: None, 451 | keys_url: None, 452 | client_sdk_key: Some(client_sdk_key.to_owned()), 453 | server_sdk_key: Some(server_sdk_key.to_owned()), 454 | server_port: listen_port, 455 | #[cfg(feature = "realtime")] 456 | realtime_port: listen_port + 100, 457 | #[cfg(feature = "realtime")] 458 | realtime_path: "/server/realtime".to_owned(), 459 | }; 460 | 461 | #[cfg(feature = "realtime")] 462 | let rs = RealtimeSocket::serve(config.realtime_port, &config.realtime_path); 463 | 464 | let repo = SdkRepository::new( 465 | config, 466 | #[cfg(feature = "realtime")] 467 | rs, 468 | ); 469 | repo.sync(client_sdk_key.to_owned(), server_sdk_key.to_owned(), 1); 470 | let repo = Arc::new(repo); 471 | let feature_probe_server = FpHttpHandler { 472 | repo: repo.clone(), 473 | events_url, 474 | analysis_url, 475 | events_timeout: Duration::from_secs(10), 476 | http_client: Default::default(), 477 | }; 478 | tokio::spawn(crate::http::serve_http::( 479 | listen_port, 480 | feature_probe_server, 481 | )); 482 | repo 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod base; 2 | pub mod http; 3 | #[cfg(feature = "realtime")] 4 | pub mod realtime; 5 | pub mod repo; 6 | pub mod secrets; 7 | 8 | pub use base::{FPServerError, ServerConfig}; 9 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::base::ServerConfig; 2 | #[cfg(feature = "realtime")] 3 | use crate::realtime::RealtimeSocket; 4 | use crate::repo::SdkRepository; 5 | use anyhow::{bail, Result}; 6 | use base::FPServerError; 7 | use base::LogFormatter; 8 | use config::builder::DefaultState; 9 | use config::{Config, ConfigBuilder}; 10 | use http::FpHttpHandler; 11 | use std::sync::Arc; 12 | use time::macros::format_description; 13 | use time::UtcOffset; 14 | use tracing::info; 15 | use tracing_subscriber::fmt::layer; 16 | use tracing_subscriber::fmt::time::{OffsetTime, SystemTime}; 17 | use tracing_subscriber::layer::SubscriberExt; 18 | use tracing_subscriber::util::SubscriberInitExt; 19 | use tracing_subscriber::EnvFilter; 20 | 21 | mod base; 22 | mod http; 23 | #[cfg(feature = "realtime")] 24 | mod realtime; 25 | mod repo; 26 | mod secrets; 27 | 28 | #[tokio::main] 29 | async fn main() -> Result<()> { 30 | let server_config = match init_server_config(None) { 31 | Ok(c) => c, 32 | Err(e) => { 33 | bail!("server config error: {}", e); 34 | } 35 | }; 36 | start(server_config).await?; 37 | tokio::signal::ctrl_c().await.expect("shut down"); 38 | Ok(()) 39 | } 40 | 41 | async fn start(server_config: ServerConfig) -> Result<()> { 42 | init_log(); 43 | info!("FeatureProbe Server Commit: {}", env!("VERGEN_GIT_SHA")); 44 | info!( 45 | "FeatureProbe Server BuildTs: {}", 46 | env!("VERGEN_BUILD_TIMESTAMP") 47 | ); 48 | info!( 49 | "FeatureProbe Server CommitTs: {}", 50 | env!("VERGEN_GIT_COMMIT_TIMESTAMP") 51 | ); 52 | info!( 53 | "FeatureProbe Server Cargo Profile: {}", 54 | env!("VERGEN_CARGO_PROFILE") 55 | ); 56 | info!("FeatureProbe Server Config: {}", server_config); 57 | 58 | #[cfg(feature = "realtime")] 59 | let realtime_socket = { 60 | let realtime_port = server_config.realtime_port; 61 | let realtime_path = &server_config.realtime_path; 62 | RealtimeSocket::serve(realtime_port, realtime_path) 63 | }; 64 | 65 | let server_port = server_config.server_port; 66 | let handler = match init_handler( 67 | server_config, 68 | #[cfg(feature = "realtime")] 69 | realtime_socket, 70 | ) { 71 | Ok(h) => h, 72 | Err(e) => { 73 | bail!("server config error: {}", e); 74 | } 75 | }; 76 | tokio::spawn(crate::http::serve_http::( 77 | server_port, 78 | handler, 79 | )); 80 | 81 | Ok(()) 82 | } 83 | 84 | fn init_server_config( 85 | config: Option>, 86 | ) -> Result { 87 | let config = match config { 88 | Some(c) => c, 89 | None => Config::builder(), 90 | }; 91 | let config = config 92 | .add_source(config::Environment::with_prefix("FP")) 93 | .build() 94 | .map_err(|e| FPServerError::ConfigError(e.to_string()))?; 95 | 96 | ServerConfig::try_parse(config) 97 | } 98 | 99 | fn init_handler( 100 | server_config: ServerConfig, 101 | #[cfg(feature = "realtime")] realtime_socket: RealtimeSocket, 102 | ) -> Result { 103 | let repo = SdkRepository::new( 104 | server_config.clone(), 105 | #[cfg(feature = "realtime")] 106 | realtime_socket, 107 | ); 108 | if let Some(keys_url) = server_config.keys_url { 109 | repo.sync_with(keys_url) 110 | } else if let (Some(ref client_sdk_key), Some(ref server_sdk_key)) = 111 | (server_config.client_sdk_key, server_config.server_sdk_key) 112 | { 113 | repo.sync(client_sdk_key.clone(), server_sdk_key.clone(), 1); 114 | } else { 115 | return Err(FPServerError::ConfigError( 116 | "not set FP_SERVER_SDK and FP_CLIENT_SDK".to_owned(), 117 | )); 118 | } 119 | 120 | Ok(FpHttpHandler { 121 | repo: Arc::new(repo), 122 | http_client: Default::default(), 123 | events_url: server_config.events_url, 124 | analysis_url: server_config.analysis_url, 125 | events_timeout: server_config.refresh_interval, 126 | }) 127 | } 128 | 129 | pub fn init_log() { 130 | let subscriber = tracing_subscriber::registry().with(EnvFilter::from_default_env()); 131 | 132 | if let Ok(offset) = UtcOffset::current_local_offset() { 133 | let format = format_description!( 134 | "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3][offset_hour sign:mandatory][offset_minute]" 135 | ); 136 | let timer = OffsetTime::new(offset, format); 137 | subscriber 138 | .with(layer().event_format(LogFormatter::with_timer(timer))) 139 | .init(); 140 | } else { 141 | subscriber 142 | .with(layer().event_format(LogFormatter::with_timer(SystemTime))) 143 | .init(); 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | use crate::http::LocalFileHttpHandlerForTest; 151 | 152 | #[tokio::test] 153 | async fn test_main() { 154 | let mock_api_port = 9009; 155 | let toggles_url = format!("http://127.0.0.1:{}/api/server-sdk/toggles", mock_api_port); 156 | let events_url = format!("http://127.0.0.1:{}/api/events", mock_api_port); 157 | let server_sdk_key = "server-sdk-key1".to_owned(); 158 | let client_sdk_key = "client-sdk-key1".to_owned(); 159 | let config = Config::builder() 160 | .set_default("toggles_url", toggles_url) 161 | .unwrap() 162 | .set_default("events_url", events_url) 163 | .unwrap() 164 | .set_default("client_sdk_key", client_sdk_key) 165 | .unwrap() 166 | .set_default("server_sdk_key", server_sdk_key) 167 | .unwrap() 168 | .set_default("refresh_seconds", "1") 169 | .unwrap(); 170 | 171 | setup_mock_api(mock_api_port); 172 | 173 | let server_config = init_server_config(Some(config)); 174 | assert!(server_config.is_ok()); 175 | 176 | let server_config = server_config.unwrap(); 177 | let r = start(server_config).await; 178 | assert!(r.is_ok()); 179 | } 180 | 181 | fn setup_mock_api(port: u16) { 182 | let mock_feature_probe_api = LocalFileHttpHandlerForTest::default(); 183 | tokio::spawn(crate::http::serve_http::( 184 | port, 185 | mock_feature_probe_api, 186 | )); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/realtime.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | use std::sync::Arc; 3 | 4 | use futures::{Future, FutureExt}; 5 | use serde_json::Value; 6 | use socketio_rs::{Payload, Server, ServerBuilder, ServerSocket}; 7 | use tracing::{info, trace, warn}; 8 | 9 | type SocketCallback = Pin + Send>>; 10 | 11 | #[derive(Clone)] 12 | pub struct RealtimeSocket { 13 | server: Arc, 14 | port: u16, 15 | // this path shoule be the same as gateway 16 | // if nginx forward to {host}/{path}, so the PATH should be {path} 17 | path: Arc, 18 | } 19 | 20 | impl RealtimeSocket { 21 | pub fn serve(port: u16, path: &str) -> Self { 22 | info!("serve_socektio on port {}", port); 23 | let callback = 24 | |payload: Option, socket: ServerSocket, _| Self::register(payload, socket); 25 | 26 | let server = ServerBuilder::new(port) 27 | .on(path, "register", callback) 28 | .build(); 29 | 30 | let server_clone = server.clone(); 31 | 32 | tokio::spawn(async move { 33 | server_clone.serve().await; 34 | }); 35 | 36 | let path = Arc::new(path.to_owned()); 37 | 38 | Self { server, port, path } 39 | } 40 | 41 | pub async fn notify_sdk( 42 | &self, 43 | server_sdk_key: String, 44 | client_sdk_key: Option, 45 | event: &str, 46 | data: serde_json::Value, 47 | ) { 48 | trace!( 49 | "notify_sdk {} {:?} {} {:?}", 50 | server_sdk_key, 51 | client_sdk_key, 52 | event, 53 | data 54 | ); 55 | 56 | let mut keys: Vec<&str> = vec![&server_sdk_key]; 57 | if let Some(client_sdk_key) = &client_sdk_key { 58 | keys.push(client_sdk_key); 59 | } 60 | 61 | self.server.emit_to(&self.path, keys, event, data).await 62 | } 63 | 64 | fn register(payload: Option, socket: ServerSocket) -> SocketCallback { 65 | async move { 66 | info!("socketio recv {:?}", payload); 67 | if let Some(Payload::Json(value)) = payload { 68 | match value.get("key") { 69 | Some(Value::String(sdk_key)) => socket.join(vec![sdk_key]).await, 70 | _ => { 71 | warn!("unkown register payload") 72 | } 73 | } 74 | } 75 | 76 | let _ = socket.emit("update", serde_json::json!("")).await; 77 | } 78 | .boxed() 79 | } 80 | } 81 | 82 | impl std::fmt::Debug for RealtimeSocket { 83 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 84 | f.debug_tuple("RealtimeSocket").field(&self.port).finish() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/repo.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "realtime")] 2 | use crate::realtime::RealtimeSocket; 3 | use crate::FPServerError; 4 | use crate::{base::ServerConfig, secrets::SecretMapping}; 5 | use feature_probe_server_sdk::{ 6 | EvalDetail, FPConfig, FPUser, FeatureProbe as FPClient, SyncType, Url, 7 | }; 8 | #[cfg(feature = "unstable")] 9 | use feature_probe_server_sdk::{Segment, Toggle}; 10 | use parking_lot::RwLock; 11 | use reqwest::Method; 12 | use serde_json::Value; 13 | use std::{collections::HashMap, sync::Arc}; 14 | use tracing::{debug, error, info}; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct SdkRepository { 18 | inner: Arc, 19 | } 20 | 21 | #[derive(Debug)] 22 | struct Inner { 23 | server_config: ServerConfig, 24 | http_client: reqwest::Client, 25 | sdk_clients: RwLock>, 26 | secret_mapping: RwLock, 27 | #[cfg(feature = "realtime")] 28 | realtime_socket: RealtimeSocket, 29 | } 30 | 31 | impl SdkRepository { 32 | pub fn new( 33 | server_config: ServerConfig, 34 | #[cfg(feature = "realtime")] realtime_socket: RealtimeSocket, 35 | ) -> Self { 36 | Self { 37 | inner: Arc::new(Inner { 38 | server_config, 39 | http_client: Default::default(), 40 | sdk_clients: Default::default(), 41 | secret_mapping: Default::default(), 42 | #[cfg(feature = "realtime")] 43 | realtime_socket, 44 | }), 45 | } 46 | } 47 | 48 | #[cfg(feature = "unstable")] 49 | pub fn update_segments( 50 | &self, 51 | _segments: HashMap, 52 | ) -> Result<(), FPServerError> { 53 | // TODO: 54 | Ok(()) 55 | } 56 | 57 | #[cfg(feature = "unstable")] 58 | pub fn update_toggles( 59 | &self, 60 | _server_sdk_key: &str, 61 | _toggles: HashMap, 62 | ) -> Result<(), FPServerError> { 63 | // TODO: 64 | Ok(()) 65 | } 66 | 67 | pub fn secret_keys(&self) -> HashMap { 68 | let secret_mapping = self.inner.secret_mapping.read(); 69 | secret_mapping.mapping_clone() 70 | } 71 | 72 | pub fn sync(&self, client_sdk_key: String, server_sdk_key: String, version: u128) { 73 | self.inner.sync(&server_sdk_key); 74 | let mut secret_mapping = self.inner.secret_mapping.write(); 75 | secret_mapping.insert(client_sdk_key, server_sdk_key, version); 76 | } 77 | 78 | pub fn sync_with(&self, keys_url: Url) { 79 | self.sync_secret_keys(keys_url); 80 | let inner = self.inner.clone(); 81 | tokio::spawn(async move { 82 | let mut interval = tokio::time::interval(inner.server_config.refresh_interval); 83 | loop { 84 | { 85 | inner.update_clients(); 86 | } 87 | interval.tick().await; 88 | } 89 | }); 90 | } 91 | 92 | fn sync_secret_keys(&self, keys_url: Url) { 93 | let inner = self.inner.clone(); 94 | let mut interval = tokio::time::interval(inner.server_config.refresh_interval); 95 | tokio::spawn(async move { 96 | loop { 97 | let url = keys_url.clone(); 98 | let request = inner 99 | .http_client 100 | .request(Method::GET, url) 101 | .timeout(inner.server_config.refresh_interval); 102 | match request.send().await { 103 | Err(e) => error!("sync_secret_keys error: {}", e), 104 | Ok(resp) => match resp.text().await { 105 | Err(e) => error!("sync_secret_keys: {}", e), 106 | Ok(body) => match serde_json::from_str::(&body) { 107 | Err(e) => error!("sync_secret_keys json error: {}", e), 108 | Ok(r) => { 109 | debug!("sync_secret_keys success. version: {:?}", r.version(),); 110 | inner.update_mapping(r); 111 | } 112 | }, 113 | }, 114 | } 115 | interval.tick().await; 116 | } 117 | }); 118 | } 119 | 120 | pub fn server_sdk_repo_string(&self, server_sdk_key: &str) -> Result { 121 | let secret_mapping = self.inner.secret_mapping.read(); 122 | if secret_mapping.version() == 0 { 123 | return Err(FPServerError::NotReady(server_sdk_key.to_string())); 124 | } 125 | if !secret_mapping.contains_server_sdk_key(server_sdk_key) { 126 | return Err(FPServerError::NotFound(server_sdk_key.to_string())); 127 | } 128 | match self.inner.repo_string(server_sdk_key) { 129 | Ok(repo) => Ok(repo), 130 | Err(e) => Err(e), 131 | } 132 | } 133 | 134 | pub fn client_sdk_eval_string( 135 | &self, 136 | client_sdk_key: &str, 137 | user: &FPUser, 138 | ) -> Result { 139 | let secret_mapping = self.inner.secret_mapping.read(); 140 | if secret_mapping.version() == 0 { 141 | return Err(FPServerError::NotReady(client_sdk_key.to_string())); 142 | } 143 | let server_sdk_key = match secret_mapping.server_sdk_key(client_sdk_key) { 144 | Some(sdk_key) => sdk_key, 145 | None => return Err(FPServerError::NotFound(client_sdk_key.to_string())), 146 | }; 147 | self.inner.all_evaluated_string(server_sdk_key, user) 148 | } 149 | 150 | pub fn client_sdk_events_string(&self, client_sdk_key: &str) -> Result { 151 | let secret_mapping = self.inner.secret_mapping.read(); 152 | if secret_mapping.version() == 0 { 153 | return Err(FPServerError::NotReady(client_sdk_key.to_string())); 154 | } 155 | let server_sdk_key = match secret_mapping.server_sdk_key(client_sdk_key) { 156 | Some(sdk_key) => sdk_key, 157 | None => return Err(FPServerError::NotFound(client_sdk_key.to_string())), 158 | }; 159 | self.inner.all_event_string(server_sdk_key) 160 | } 161 | 162 | pub fn client_sync_now(&self, sdk_key: &str, t: SyncType) -> Result { 163 | let sdk_clients = self.inner.sdk_clients.write(); 164 | let client = match sdk_clients.get(sdk_key) { 165 | Some(client) => client, 166 | None => return Err(FPServerError::NotFound(sdk_key.to_string())), 167 | }; 168 | client.sync_now(t); 169 | Ok(sdk_key.to_string()) 170 | } 171 | 172 | #[cfg(test)] 173 | #[cfg(feature = "unstable")] 174 | fn sdk_client(&self, sdk_key: &str) -> Option { 175 | let sdk_clients = self.inner.sdk_clients.read(); 176 | sdk_clients.get(sdk_key).cloned() 177 | } 178 | } 179 | 180 | impl Inner { 181 | pub fn sync(&self, server_sdk_key: &str) { 182 | let should_sync = { 183 | let sdks = self.sdk_clients.read(); 184 | !sdks.contains_key(server_sdk_key) 185 | }; 186 | 187 | if !should_sync { 188 | return; 189 | } 190 | 191 | let mut mut_sdks = self.sdk_clients.write(); 192 | let config = FPConfig { 193 | server_sdk_key: server_sdk_key.to_owned(), 194 | remote_url: Url::parse("http://nouse.com").unwrap(), 195 | toggles_url: Some(self.server_config.toggles_url.clone()), 196 | refresh_interval: self.server_config.refresh_interval, 197 | http_client: Some(self.http_client.clone()), 198 | ..Default::default() 199 | }; 200 | info!("{:?} added", server_sdk_key); 201 | 202 | #[cfg(feature = "realtime")] 203 | { 204 | let mut client = FPClient::new(config); 205 | self.setup_notify(server_sdk_key, &mut client); 206 | let _ = &mut_sdks.insert(server_sdk_key.to_owned(), client); 207 | } 208 | 209 | #[cfg(not(feature = "realtime"))] 210 | let _ = &mut_sdks.insert(server_sdk_key.to_owned(), FPClient::new(config)); 211 | } 212 | 213 | pub fn remove_client(&self, server_sdk_key: &str) { 214 | let mut sdks = self.sdk_clients.write(); 215 | sdks.remove(server_sdk_key); 216 | } 217 | 218 | pub fn update_clients(&self) { 219 | let secret_mapping = self.secret_mapping.read(); 220 | let clients = self.sdk_clients.read().clone(); 221 | if secret_mapping.version() > 0 { 222 | let server_sdk_keys = secret_mapping.server_sdk_keys(); 223 | for server_sdk_key in &server_sdk_keys { 224 | self.sync(server_sdk_key); 225 | } 226 | 227 | for server_sdk_key in clients.keys() { 228 | if !server_sdk_keys.contains(&server_sdk_key) { 229 | info!("{:?} removed.", server_sdk_key); 230 | self.remove_client(server_sdk_key); 231 | } 232 | } 233 | } 234 | } 235 | 236 | pub fn update_mapping(&self, new: SecretMapping) { 237 | let version = self.secret_mapping.read().version(); 238 | if new.version() > version { 239 | let mut secret_mapping = self.secret_mapping.write(); 240 | secret_mapping.update_mapping(new) 241 | } 242 | } 243 | 244 | #[cfg(feature = "realtime")] 245 | fn setup_notify(&self, server_sdk_key: &str, client: &mut FPClient) { 246 | let sdk_key = server_sdk_key.to_owned(); 247 | let realtime_socket = self.realtime_socket.clone(); 248 | let client_sdk_key = { 249 | let mapping = self.secret_mapping.read(); 250 | mapping.client_sdk_key(server_sdk_key).cloned() 251 | }; 252 | 253 | client.set_update_callback(Box::new(move |_old, _new, _type| { 254 | let server_key = sdk_key.clone(); 255 | let client_key = client_sdk_key.clone(); 256 | let socket = realtime_socket.clone(); 257 | tokio::spawn(async move { 258 | socket 259 | .notify_sdk(server_key, client_key, "update", serde_json::json!("")) 260 | .await; 261 | }); 262 | })); 263 | } 264 | 265 | fn repo_string(&self, sdk_key: &str) -> Result { 266 | let clients = self.sdk_clients.read(); 267 | let client = match clients.get(sdk_key) { 268 | Some(client) if !client.initialized() => { 269 | return Err(FPServerError::NotReady(sdk_key.to_string())) 270 | } 271 | Some(client) => client, 272 | None => return Err(FPServerError::NotReady(sdk_key.to_string())), 273 | }; 274 | let arc_repo = client.repo(); 275 | let repo = arc_repo.read(); 276 | serde_json::to_string(&*repo).map_err(|e| FPServerError::JsonError(e.to_string())) 277 | } 278 | 279 | fn all_evaluated_string(&self, sdk_key: &str, user: &FPUser) -> Result { 280 | let clients = self.sdk_clients.read(); 281 | let client = match clients.get(sdk_key) { 282 | Some(client) if !client.initialized() => { 283 | return Err(FPServerError::NotReady(sdk_key.to_string())) 284 | } 285 | Some(client) => client, 286 | None => return Err(FPServerError::NotReady(sdk_key.to_string())), 287 | }; 288 | let arc_repo = client.repo(); 289 | let repo = arc_repo.read(); 290 | let map: HashMap> = repo 291 | .toggles 292 | .iter() 293 | .filter(|(_, t)| t.is_for_client()) 294 | .map(|(key, toggle)| (key.to_owned(), toggle.eval_detail(user, &repo.segments))) 295 | .collect(); 296 | serde_json::to_string(&map).map_err(|e| FPServerError::JsonError(e.to_string())) 297 | } 298 | 299 | fn all_event_string(&self, sdk_key: &str) -> Result { 300 | let clients = self.sdk_clients.read(); 301 | let client = match clients.get(sdk_key) { 302 | Some(client) if !client.initialized() => { 303 | return Err(FPServerError::NotReady(sdk_key.to_string())) 304 | } 305 | Some(client) => client, 306 | None => return Err(FPServerError::NotReady(sdk_key.to_string())), 307 | }; 308 | let arc_repo = client.repo(); 309 | let repo = arc_repo.read(); 310 | serde_json::to_string(&repo.events).map_err(|e| FPServerError::JsonError(e.to_string())) 311 | } 312 | } 313 | 314 | #[cfg(test)] 315 | mod tests { 316 | 317 | use super::*; 318 | use crate::FPServerError::{NotFound, NotReady}; 319 | use axum::{routing::get, Json, Router, TypedHeader}; 320 | #[cfg(feature = "unstable")] 321 | use feature_probe_server_sdk::FPUser; 322 | use feature_probe_server_sdk::{Repository, SdkAuthorization}; 323 | #[cfg(feature = "unstable")] 324 | use serde_json::json; 325 | use std::{fs, net::SocketAddr, path::PathBuf, time::Duration}; 326 | 327 | #[tokio::test] 328 | async fn test_repo_sync() { 329 | let port = 9590; 330 | setup_mock_api(port); 331 | let client_sdk_key = "client-sdk-key".to_owned(); 332 | let server_sdk_key = "server-sdk-key".to_owned(); 333 | let client_sdk_key2 = "client-sdk-key2".to_owned(); 334 | let server_sdk_key2 = "server-sdk-key2".to_owned(); 335 | let repository = setup_repository(port, &client_sdk_key, &server_sdk_key).await; 336 | 337 | let repo_string = repository.server_sdk_repo_string(&server_sdk_key); 338 | assert!(repo_string.is_ok()); 339 | let r = serde_json::from_str::(&repo_string.unwrap()).unwrap(); 340 | assert_eq!(r, repo_from_test_file()); 341 | 342 | let secret_keys = repository.secret_keys(); 343 | assert_eq!(secret_keys.len(), 1); 344 | assert_eq!(secret_keys.get(&client_sdk_key), Some(&server_sdk_key)); 345 | 346 | // test mapping sync 347 | 348 | let mut mapping = HashMap::new(); 349 | mapping.insert(client_sdk_key2.to_string(), server_sdk_key2.to_string()); 350 | let new = SecretMapping::new(2, mapping); 351 | let clients = { (repository.inner.sdk_clients.read()).clone() }; 352 | assert!(clients.contains_key(&server_sdk_key)); 353 | repository.inner.update_mapping(new); 354 | let secret_mapping = { (repository.inner.secret_mapping.read()).clone() }; 355 | let secret = &secret_mapping.server_sdk_key(&client_sdk_key2); 356 | assert_eq!(secret_mapping.version(), 2); 357 | assert_eq!(secret.unwrap(), &server_sdk_key2.to_string()); 358 | 359 | // test clients sync 360 | repository.inner.update_clients(); 361 | let clients = { (repository.inner.sdk_clients.read()).clone() }; 362 | assert!(!clients.contains_key(&server_sdk_key)); 363 | assert!(clients.contains_key(&server_sdk_key2)); 364 | 365 | let sdk_key = repository.client_sync_now(&server_sdk_key2, SyncType::Polling); 366 | assert!(sdk_key.is_ok()); 367 | } 368 | 369 | #[tokio::test] 370 | async fn test_repo_sync2() { 371 | let port = 9591; 372 | setup_mock_api(port); 373 | let client_sdk_key = "client-sdk-key".to_owned(); 374 | let server_sdk_key = "server-sdk-key".to_owned(); 375 | let non_sdk_key = "non-exist-sdk-key".to_owned(); 376 | let repository = setup_repository2(port).await; 377 | 378 | let repo_string_err = repository.server_sdk_repo_string(&non_sdk_key); 379 | assert_eq!(repo_string_err.err(), Some(NotFound(non_sdk_key))); 380 | let events_string = repository.client_sdk_events_string(&client_sdk_key); 381 | assert!(events_string.is_ok()); 382 | let repo_string = repository.server_sdk_repo_string(&server_sdk_key); 383 | assert!(repo_string.is_ok()); 384 | let r = serde_json::from_str::(&repo_string.unwrap()).unwrap(); 385 | assert!(r == repo_from_test_file()); 386 | let secret_keys = repository.secret_keys(); 387 | let secret_keys_version = repository.inner.secret_mapping.read().version(); 388 | assert!(secret_keys_version == 1); 389 | assert!(secret_keys.len() == 1); 390 | assert!(secret_keys.get(&client_sdk_key) == Some(&server_sdk_key)); 391 | } 392 | 393 | #[tokio::test] 394 | async fn test_not_ready_repo_sync() { 395 | let port = 9592; 396 | setup_mock_api(port); 397 | let client_sdk_key = "client-sdk-key".to_owned(); 398 | let server_sdk_key = "server-sdk-key".to_owned(); 399 | let repository = setup_not_ready_repository(port, &client_sdk_key, &server_sdk_key).await; 400 | 401 | let repo_string_err = repository.server_sdk_repo_string(&server_sdk_key); 402 | assert_eq!(repo_string_err.err(), Some(NotReady(server_sdk_key))); 403 | } 404 | 405 | #[cfg(feature = "unstable")] 406 | #[tokio::test] 407 | async fn test_update_toggles() { 408 | let port = 9592; 409 | setup_mock_api(port); 410 | 411 | let server_sdk_key = "sdk-key1".to_owned(); 412 | let client_sdk_key = "client-sdk-key".to_owned(); 413 | let repository = setup_repository(port, &client_sdk_key, &server_sdk_key).await; 414 | let client = repository.sdk_client(&server_sdk_key); 415 | assert!(client.is_some()); 416 | 417 | let client = client.unwrap(); 418 | let user = FPUser::new().with("city", "4"); 419 | let default: HashMap = HashMap::default(); 420 | let v = client.json_value("json_toggle", &user, json!(default)); 421 | assert!(v.get("variation_1").is_some()); 422 | 423 | let mut map = update_toggles_from_file(); 424 | let update_toggles = map.remove(&server_sdk_key); 425 | assert!(update_toggles.is_some()); 426 | 427 | let update_toggles = update_toggles.unwrap(); 428 | let result = repository.update_toggles(&server_sdk_key, update_toggles); 429 | assert!(result.is_ok()); 430 | } 431 | 432 | async fn setup_repository( 433 | port: u16, 434 | client_sdk_key: &str, 435 | server_sdk_key: &str, 436 | ) -> SdkRepository { 437 | let toggles_url = 438 | Url::parse(&format!("http://127.0.0.1:{}/api/server-sdk/toggles", port)).unwrap(); 439 | let events_url = Url::parse(&format!("http://127.0.0.1:{}/api/events", port)).unwrap(); 440 | let analysis_url = None; 441 | let config = ServerConfig { 442 | toggles_url, 443 | events_url, 444 | analysis_url, 445 | refresh_interval: Duration::from_secs(1), 446 | client_sdk_key: Some(client_sdk_key.to_owned()), 447 | server_sdk_key: Some(server_sdk_key.to_owned()), 448 | keys_url: None, 449 | server_port: port, 450 | #[cfg(feature = "realtime")] 451 | realtime_port: port + 100, 452 | #[cfg(feature = "realtime")] 453 | realtime_path: "/server/realtime".to_owned(), 454 | }; 455 | 456 | #[cfg(feature = "realtime")] 457 | let rs = RealtimeSocket::serve(config.realtime_port, &config.realtime_path); 458 | 459 | let repo = SdkRepository::new( 460 | config, 461 | #[cfg(feature = "realtime")] 462 | rs, 463 | ); 464 | repo.sync(client_sdk_key.to_owned(), server_sdk_key.to_owned(), 1); 465 | tokio::time::sleep(Duration::from_millis(100)).await; 466 | repo 467 | } 468 | 469 | async fn setup_not_ready_repository( 470 | port: u16, 471 | client_sdk_key: &str, 472 | server_sdk_key: &str, 473 | ) -> SdkRepository { 474 | let toggles_url = 475 | Url::parse(&format!("http://127.0.0.1:{}/api/server-sdk/toggles", port)).unwrap(); 476 | let events_url = Url::parse(&format!("http://127.0.0.1:{}/api/events", port)).unwrap(); 477 | let analysis_url = None; 478 | let config = ServerConfig { 479 | toggles_url, 480 | events_url, 481 | analysis_url, 482 | refresh_interval: Duration::from_secs(1), 483 | client_sdk_key: Some(client_sdk_key.to_owned()), 484 | server_sdk_key: Some(server_sdk_key.to_owned()), 485 | keys_url: None, 486 | server_port: port, 487 | #[cfg(feature = "realtime")] 488 | realtime_port: port + 100, 489 | #[cfg(feature = "realtime")] 490 | realtime_path: "/server/realtime".to_owned(), 491 | }; 492 | 493 | #[cfg(feature = "realtime")] 494 | let rs = RealtimeSocket::serve(config.realtime_port, &config.realtime_path); 495 | 496 | let repo = SdkRepository::new( 497 | config, 498 | #[cfg(feature = "realtime")] 499 | rs, 500 | ); 501 | repo.sync(client_sdk_key.to_owned(), server_sdk_key.to_owned(), 0); 502 | tokio::time::sleep(Duration::from_millis(100)).await; 503 | repo 504 | } 505 | 506 | async fn setup_repository2(port: u16) -> SdkRepository { 507 | let toggles_url = 508 | Url::parse(&format!("http://127.0.0.1:{}/api/server-sdk/toggles", port)).unwrap(); 509 | let events_url = Url::parse(&format!("http://127.0.0.1:{}/api/events", port)).unwrap(); 510 | let keys_url = Url::parse(&format!("http://127.0.0.1:{}/api/secret-keys", port)).unwrap(); 511 | let analysis_url = None; 512 | let config = ServerConfig { 513 | toggles_url, 514 | events_url, 515 | analysis_url, 516 | refresh_interval: Duration::from_millis(100), 517 | client_sdk_key: None, 518 | server_sdk_key: None, 519 | keys_url: Some(keys_url.clone()), 520 | server_port: port, 521 | #[cfg(feature = "realtime")] 522 | realtime_port: port + 100, 523 | realtime_path: "/server/realtime".to_owned(), 524 | }; 525 | 526 | #[cfg(feature = "realtime")] 527 | let rs = RealtimeSocket::serve(config.realtime_port, &config.realtime_path); 528 | 529 | let repo = SdkRepository::new( 530 | config, 531 | #[cfg(feature = "realtime")] 532 | rs, 533 | ); 534 | repo.sync_with(keys_url); 535 | tokio::time::sleep(Duration::from_millis(300)).await; 536 | repo 537 | } 538 | 539 | async fn server_sdk_toggles( 540 | TypedHeader(SdkAuthorization(_sdk_key)): TypedHeader, 541 | ) -> Json { 542 | repo_from_test_file().into() 543 | } 544 | 545 | async fn secret_keys() -> String { 546 | r#" { "version": 1, "mapping": { "client-sdk-key": "server-sdk-key" } }"#.to_owned() 547 | } 548 | 549 | fn setup_mock_api(port: u16) { 550 | let app = Router::new() 551 | .route("/api/secret-keys", get(secret_keys)) 552 | .route("/api/server-sdk/toggles", get(server_sdk_toggles)); 553 | let addr = SocketAddr::from(([0, 0, 0, 0], port)); 554 | tokio::spawn(async move { 555 | let _ = axum::Server::bind(&addr) 556 | .serve(app.into_make_service()) 557 | .await; 558 | }); 559 | } 560 | 561 | fn repo_from_test_file() -> Repository { 562 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 563 | path.push("resources/fixtures/repo.json"); 564 | let json_str = fs::read_to_string(path).unwrap(); 565 | serde_json::from_str::(&json_str).unwrap() 566 | } 567 | 568 | #[cfg(feature = "unstable")] 569 | fn update_toggles_from_file() -> HashMap> { 570 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 571 | path.push("resources/fixtures/toggles_update.json"); 572 | let json_str = fs::read_to_string(path).unwrap(); 573 | serde_json::from_str::>>(&json_str).unwrap() 574 | } 575 | } 576 | -------------------------------------------------------------------------------- /src/secrets.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, ops::Deref}; 2 | 3 | use serde::Deserialize; 4 | 5 | type ClientSdkKey = String; 6 | type ServerSdkKey = String; 7 | 8 | #[derive(Deserialize, Debug, Default, Clone)] 9 | pub struct SecretMapping { 10 | version: u128, 11 | mapping: HashMap, 12 | #[serde(skip)] 13 | reverse: HashMap, 14 | } 15 | 16 | impl SecretMapping { 17 | #[cfg(test)] 18 | pub fn new(version: u128, mapping: HashMap) -> Self { 19 | let mut reverse = HashMap::new(); 20 | for (k, v) in &mapping { 21 | reverse.insert(v.clone(), k.clone()); 22 | } 23 | Self { 24 | version, 25 | mapping, 26 | reverse, 27 | } 28 | } 29 | 30 | pub fn version(&self) -> u128 { 31 | self.version 32 | } 33 | 34 | pub fn update_mapping(&mut self, new: SecretMapping) { 35 | if new.version > self.version { 36 | self.version = new.version; 37 | self.mapping = new.mapping; 38 | 39 | let mut reverse = HashMap::new(); 40 | for (k, v) in &self.mapping { 41 | reverse.insert(v.clone(), k.clone()); 42 | } 43 | 44 | self.reverse = reverse; 45 | } 46 | } 47 | 48 | pub fn client_sdk_key(&self, server_sdk_key: &str) -> Option<&String> { 49 | self.reverse.get(server_sdk_key) 50 | } 51 | 52 | pub fn server_sdk_key(&self, client_sdk_key: &str) -> Option<&String> { 53 | self.mapping.get(client_sdk_key) 54 | } 55 | 56 | pub fn server_sdk_keys(&self) -> Vec<&String> { 57 | self.reverse.keys().into_iter().collect() 58 | } 59 | 60 | pub fn mapping_clone(&self) -> HashMap { 61 | self.mapping.clone() 62 | } 63 | 64 | pub fn insert(&mut self, client_sdk_key: String, server_sdk_key: String, version: u128) { 65 | self.version = version; 66 | self.mapping 67 | .insert(client_sdk_key.clone(), server_sdk_key.clone()); 68 | self.reverse.insert(server_sdk_key, client_sdk_key); 69 | } 70 | 71 | pub fn contains_server_sdk_key(&self, server_sdk_key: &str) -> bool { 72 | self.reverse.contains_key(server_sdk_key) 73 | } 74 | } 75 | 76 | impl Deref for SecretMapping { 77 | type Target = HashMap; 78 | 79 | fn deref(&self) -> &Self::Target { 80 | &self.mapping 81 | } 82 | } 83 | --------------------------------------------------------------------------------