Page MenuHomePhabricator

No OneTemporary

diff --git a/Cargo.lock b/Cargo.lock
index 4f15570..cde2b94 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,1505 +1,1507 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.59.0",
]
[[package]]
name = "api"
version = "0.1.0"
dependencies = [
"axum",
"libcollar",
"log",
"store",
"thiserror",
"tokio",
"tower-http",
"tracing",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "axum"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [
"bitflags 2.9.1",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "collar"
version = "0.1.0"
dependencies = [
"api",
"env_logger",
"libcollar",
"log",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "errno"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
]
[[package]]
name = "hyper-util"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"hyper",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "ifconfig"
version = "0.1.0"
dependencies = [
"errno",
"libc",
"log",
"nix",
"thiserror",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libcollar"
version = "0.1.0"
dependencies = [
"ifconfig",
"log",
"ng",
"serde",
"store",
"thiserror",
"tokio",
"tracing",
]
[[package]]
name = "libloading"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.53.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
[[package]]
name = "ng"
version = "0.1.0"
dependencies = [
"bindgen",
"errno",
"libc",
"log",
"socket2",
"thiserror",
]
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [
"lock_api",
"parking_lot_core 0.9.11",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.12",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "prettyplease"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
dependencies = [
"itoa",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]]
name = "smallvec"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "store"
version = "0.1.0"
dependencies = [
"log",
+ "serde",
+ "serde_json",
"sled",
"tempfile",
"thiserror",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "tempfile"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tokio"
version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot 0.12.4",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-http"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [
"bitflags 2.9.1",
"bytes",
"http",
"http-body",
"pin-project-lite",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.1",
]
diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml
index 6c03b5a..02e7aff 100644
--- a/crates/store/Cargo.toml
+++ b/crates/store/Cargo.toml
@@ -1,14 +1,16 @@
[package]
name = "store"
version = "0.1.0"
license.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
log.workspace = true
thiserror.workspace = true
sled = "0.34.7"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
[dev-dependencies]
tempfile = "3.8"
diff --git a/crates/store/src/combined.rs b/crates/store/src/combined.rs
index 605505d..0905601 100644
--- a/crates/store/src/combined.rs
+++ b/crates/store/src/combined.rs
@@ -1,554 +1,563 @@
//! Generic transaction module for atomic operations across multiple stores.
//!
//! # Example
//!
//! This module provides a generic transaction mechanism for atomic operations across
//! different types of stores. Each store implementation can plug into this system
//! by implementing the `TransactionProvider` trait.
//!
//! ```no_run
//! use store::{Transaction, TransactionContext};
//!
//! // Assuming you have stores and trees
//! # fn example_usage() -> store::Result<()> {
//! # let temp_dir = tempfile::tempdir().unwrap();
//! # let db = sled::open(temp_dir.path())?;
//! # let range_store = store::RangeStore::open(&db)?;
//! # let namespace_store = store::NamespaceStore::open(&db)?;
//! # let metadata_tree = db.open_tree("metadata")?;
//!
//! // Create a transaction with the stores you want to include
//! let transaction = Transaction::new()
//! .with_store("ranges", &range_store)
//! .with_store("namespaces", &namespace_store)
//! .with_tree(&metadata_tree);
//!
//! // Execute the transaction
//! let result = transaction.execute(|ctx| {
//! // Access stores by name
//! let range_trees = ctx.store_trees("ranges")?;
//! let namespace_trees = ctx.store_trees("namespaces")?;
//!
//! // Access additional trees by index
//! let metadata = ctx.tree(0)?;
//!
//! metadata.insert("operation", "test")
//! .map_err(|e| store::Error::StoreError(e))?;
//!
//! Ok(())
//! })?;
//! # Ok(())
//! # }
//! ```
use crate::{Result, Error};
use sled::Transactional;
use std::collections::HashMap;
/// Helper function to convert transaction errors
fn convert_transaction_error<T>(e: sled::transaction::ConflictableTransactionError<T>, default_error: Error) -> Error {
match e {
sled::transaction::ConflictableTransactionError::Storage(storage_err) => Error::StoreError(storage_err),
sled::transaction::ConflictableTransactionError::Abort(_) => default_error,
_ => Error::StoreError(sled::Error::Unsupported("Unknown transaction error".to_string())),
}
}
/// Trait for types that can provide trees to a transaction
pub trait TransactionProvider {
/// Return the trees that should be included in a transaction
fn transaction_trees(&self) -> Vec<&sled::Tree>;
}
/// Implement TransactionProvider for individual trees
impl TransactionProvider for sled::Tree {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
vec![self]
}
}
/// Implement TransactionProvider for RangeStore
impl TransactionProvider for crate::RangeStore {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
vec![&self.names, &self.map, &self.assign]
}
}
/// Implement TransactionProvider for NamespaceStore
impl TransactionProvider for crate::NamespaceStore {
fn transaction_trees(&self) -> Vec<&sled::Tree> {
vec![&self.names, &self.spaces]
}
}
+/// Implement TransactionProvider for NetworkStore
+impl TransactionProvider for crate::NetworkStore {
+ fn transaction_trees(&self) -> Vec<&sled::Tree> {
+ let mut trees = self.namespaces.transaction_trees();
+ trees.push(&self.networks);
+ trees
+ }
+}
+
/// Generic transaction context provided to transaction operations
pub struct TransactionContext<'ctx> {
store_map: HashMap<String, (usize, usize)>, // name -> (start_idx, end_idx)
trees: Vec<&'ctx sled::transaction::TransactionalTree>,
transactional_trees: &'ctx [sled::transaction::TransactionalTree],
additional_trees_start: usize,
}
impl<'ctx> TransactionContext<'ctx> {
/// Create a new transaction context
fn new(
store_map: HashMap<String, (usize, usize)>,
trees: Vec<&'ctx sled::transaction::TransactionalTree>,
transactional_trees: &'ctx [sled::transaction::TransactionalTree],
additional_trees_start: usize,
) -> Self {
Self {
store_map,
trees,
transactional_trees,
additional_trees_start,
}
}
/// Get trees for a store by name
pub fn store_trees(&self, store_name: &str) -> Result<&[&sled::transaction::TransactionalTree]> {
let (start_idx, end_idx) = self.store_map
.get(store_name)
.ok_or_else(|| Error::StoreError(sled::Error::Unsupported(format!("Store '{}' not found in transaction", store_name))))?;
Ok(&self.trees[*start_idx..*end_idx])
}
/// Access additional trees by index
pub fn tree(&self, index: usize) -> Result<&sled::transaction::TransactionalTree> {
self.trees.get(self.additional_trees_start + index)
.copied()
.ok_or_else(|| Error::StoreError(sled::Error::Unsupported(format!("Tree at index {} not found", index))))
}
/// Access a raw transactional tree by its absolute index
pub fn raw_tree(&self, index: usize) -> Result<&sled::transaction::TransactionalTree> {
self.trees.get(index)
.copied()
.ok_or_else(|| Error::StoreError(sled::Error::Unsupported(format!("Raw tree at index {} not found", index))))
}
/// Access the entire slice of transactional trees
pub fn all_trees(&self) -> &[sled::transaction::TransactionalTree] {
self.transactional_trees
}
/// Get store map for debugging or extension purposes
pub fn store_map(&self) -> &HashMap<String, (usize, usize)> {
&self.store_map
}
}
/// Generic transaction struct for atomic operations across multiple stores
pub struct Transaction<'a> {
stores: HashMap<String, &'a dyn TransactionProvider>,
additional_trees: Vec<&'a sled::Tree>,
}
impl<'a> Transaction<'a> {
/// Create a new empty transaction
pub fn new() -> Self {
Self {
stores: HashMap::new(),
additional_trees: Vec::new(),
}
}
/// Add a store with a name identifier
pub fn with_store<T: TransactionProvider>(mut self, name: &str, store: &'a T) -> Self {
self.stores.insert(name.to_string(), store);
self
}
/// Add a single tree to the transaction
pub fn with_tree(mut self, tree: &'a sled::Tree) -> Self {
self.additional_trees.push(tree);
self
}
/// Execute a transaction with the configured stores
pub fn execute<F, R>(&self, operations: F) -> Result<R>
where
F: Fn(&TransactionContext) -> Result<R>,
{
// Collect all trees for the transaction
let mut all_trees = Vec::new();
let mut store_map = HashMap::new();
// Add trees from stores
for (name, store) in &self.stores {
let start_idx = all_trees.len();
all_trees.extend(store.transaction_trees());
let end_idx = all_trees.len();
store_map.insert(name.clone(), (start_idx, end_idx));
}
// Add additional trees
let additional_trees_start = all_trees.len();
all_trees.extend(&self.additional_trees);
// Execute the transaction
let result = all_trees.transaction(|trees| {
let context = TransactionContext::new(
store_map.clone(),
trees.into_iter().collect(),
trees,
additional_trees_start,
);
operations(&context).map_err(|e| match e {
Error::StoreError(store_err) => sled::transaction::ConflictableTransactionError::Storage(store_err),
_ => sled::transaction::ConflictableTransactionError::Abort(()),
})
}).map_err(|e| match e {
sled::transaction::TransactionError::Abort(()) => Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string())),
sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
})?;
Ok(result)
}
}
impl<'a> Default for Transaction<'a> {
fn default() -> Self {
Self::new()
}
}
/// Legacy alias for backward compatibility
pub type CombinedTransaction<'a> = Transaction<'a>;
/// Legacy alias for backward compatibility
pub type CombinedTransactionContext<'ctx> = TransactionContext<'ctx>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{RangeStore, NamespaceStore};
use tempfile::tempdir;
fn create_test_stores() -> Result<(RangeStore, NamespaceStore, sled::Db)> {
let temp_dir = tempdir().unwrap();
let db = sled::open(temp_dir.path())?;
let range_store = RangeStore::open(&db)?;
let namespace_store = NamespaceStore::open(&db)?;
Ok((range_store, namespace_store, db))
}
#[test]
fn test_generic_transaction_basic() -> Result<()> {
let (range_store, namespace_store, db) = create_test_stores()?;
// Setup: define range and namespace
range_store.define("test_range", 100)?;
namespace_store.define("test_namespace")?;
// Create additional tree for testing
let extra_tree = db.open_tree("extra")?;
let transaction = Transaction::new()
.with_store("ranges", &range_store)
.with_store("namespaces", &namespace_store)
.with_tree(&extra_tree);
// Execute transaction using generic interface
transaction.execute(|ctx| {
// Access range store trees
let range_trees = ctx.store_trees("ranges")?;
assert_eq!(range_trees.len(), 3); // names, map, assign
// Access namespace store trees
let namespace_trees = ctx.store_trees("namespaces")?;
assert_eq!(namespace_trees.len(), 2); // names, spaces
// Use additional tree
let tree = ctx.tree(0)?;
tree.insert("test_key", "test_value")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
Ok(())
})?;
// Verify the additional tree was modified
let value = extra_tree.get("test_key")?;
assert_eq!(value, Some("test_value".as_bytes().into()));
Ok(())
}
#[test]
fn test_transaction_with_single_store() -> Result<()> {
let (range_store, _, _) = create_test_stores()?;
// Setup
range_store.define("test_range", 50)?;
let transaction = Transaction::new()
.with_store("ranges", &range_store);
// Execute transaction with just one store
transaction.execute(|ctx| {
let range_trees = ctx.store_trees("ranges")?;
assert_eq!(range_trees.len(), 3);
// Verify we can access the trees
let _names_tree = range_trees[0];
let _map_tree = range_trees[1];
let _assign_tree = range_trees[2];
Ok(())
})?;
Ok(())
}
#[test]
fn test_transaction_with_only_trees() -> Result<()> {
let (_, _, db) = create_test_stores()?;
let tree1 = db.open_tree("tree1")?;
let tree2 = db.open_tree("tree2")?;
let transaction = Transaction::new()
.with_tree(&tree1)
.with_tree(&tree2);
transaction.execute(|ctx| {
let t1 = ctx.tree(0)?;
let t2 = ctx.tree(1)?;
t1.insert("key1", "value1")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
t2.insert("key2", "value2")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
Ok(())
})?;
// Verify the trees were modified
let value1 = tree1.get("key1")?;
assert_eq!(value1, Some("value1".as_bytes().into()));
let value2 = tree2.get("key2")?;
assert_eq!(value2, Some("value2".as_bytes().into()));
Ok(())
}
#[test]
fn test_multiple_stores_same_type() -> Result<()> {
let (range_store1, _, db) = create_test_stores()?;
let range_store2 = RangeStore::open(&db)?;
// Setup both stores
range_store1.define("range1", 50)?;
range_store2.define("range2", 100)?;
let transaction = Transaction::new()
.with_store("ranges1", &range_store1)
.with_store("ranges2", &range_store2);
transaction.execute(|ctx| {
let trees1 = ctx.store_trees("ranges1")?;
let trees2 = ctx.store_trees("ranges2")?;
assert_eq!(trees1.len(), 3);
assert_eq!(trees2.len(), 3);
// Verify different stores have different trees
assert_ne!(trees1[0] as *const _, trees2[0] as *const _);
Ok(())
})?;
Ok(())
}
#[test]
fn test_store_not_found_error() -> Result<()> {
let (range_store, _, _) = create_test_stores()?;
let transaction = Transaction::new()
.with_store("ranges", &range_store);
let result: Result<()> = transaction.execute(|ctx| {
// Try to access a store that doesn't exist
let _trees = ctx.store_trees("nonexistent")?;
Ok(())
});
assert!(result.is_err());
Ok(())
}
#[test]
fn test_tree_index_out_of_bounds() -> Result<()> {
let (_, _, db) = create_test_stores()?;
let tree = db.open_tree("single_tree")?;
let transaction = Transaction::new()
.with_tree(&tree);
let result: Result<()> = transaction.execute(|ctx| {
// Try to access tree at index that doesn't exist
let _tree = ctx.tree(5)?;
Ok(())
});
assert!(result.is_err());
Ok(())
}
#[test]
fn test_transaction_rollback() -> Result<()> {
let (_, _, db) = create_test_stores()?;
let tree = db.open_tree("rollback_test")?;
// First, insert some initial data
tree.insert("initial", "data")?;
let transaction = Transaction::new()
.with_tree(&tree);
// Execute a transaction that should fail
let result: Result<()> = transaction.execute(|ctx| {
let t = ctx.tree(0)?;
// Insert some data
t.insert("temp", "value")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
// Force an error to trigger rollback
Err(Error::StoreError(sled::Error::Unsupported("Forced error".to_string())))
});
assert!(result.is_err());
// Verify rollback - temp key should not exist
let temp_value = tree.get("temp")?;
assert_eq!(temp_value, None);
// But initial data should still be there
let initial_value = tree.get("initial")?;
assert_eq!(initial_value, Some("data".as_bytes().into()));
Ok(())
}
#[test]
fn test_complex_multi_store_transaction() -> Result<()> {
let (range_store, namespace_store, db) = create_test_stores()?;
// Setup
range_store.define("ip_pool", 100)?;
namespace_store.define("users")?;
let metadata_tree = db.open_tree("metadata")?;
let logs_tree = db.open_tree("logs")?;
let transaction = Transaction::new()
.with_store("ranges", &range_store)
.with_store("namespaces", &namespace_store)
.with_tree(&metadata_tree)
.with_tree(&logs_tree);
// Complex transaction
transaction.execute(|ctx| {
let range_trees = ctx.store_trees("ranges")?;
let namespace_trees = ctx.store_trees("namespaces")?;
let metadata = ctx.tree(0)?;
let logs = ctx.tree(1)?;
// Verify we have the right number of trees
assert_eq!(range_trees.len(), 3);
assert_eq!(namespace_trees.len(), 2);
// Use metadata tree
metadata.insert("operation", "complex_transaction")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
// Use logs tree
logs.insert("log_entry_1", "Started complex operation")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
Ok(())
})?;
// Verify all operations succeeded
let op_value = metadata_tree.get("operation")?;
assert_eq!(op_value, Some("complex_transaction".as_bytes().into()));
let log_value = logs_tree.get("log_entry_1")?;
assert_eq!(log_value, Some("Started complex operation".as_bytes().into()));
Ok(())
}
#[test]
fn test_raw_tree_access() -> Result<()> {
let (range_store, namespace_store, db) = create_test_stores()?;
let extra_tree = db.open_tree("extra")?;
let transaction = Transaction::new()
.with_store("ranges", &range_store)
.with_store("namespaces", &namespace_store)
.with_tree(&extra_tree);
transaction.execute(|ctx| {
// Test raw tree access by absolute index
let tree0 = ctx.raw_tree(0)?; // First range store tree
let tree3 = ctx.raw_tree(3)?; // First namespace store tree
let tree5 = ctx.raw_tree(5)?; // Extra tree
// All should be valid trees
tree0.insert("raw0", "value0")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
tree3.insert("raw3", "value3")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
tree5.insert("raw5", "value5")
.map_err(|e| Error::StoreError(sled::Error::Unsupported(format!("Insert failed: {}", e))))?;
// Test accessing out of bounds
let invalid_result = ctx.raw_tree(10);
assert!(invalid_result.is_err());
Ok(())
})?;
Ok(())
}
#[test]
fn test_store_map_access() -> Result<()> {
let (range_store, namespace_store, _) = create_test_stores()?;
let transaction = Transaction::new()
.with_store("my_ranges", &range_store)
.with_store("my_namespaces", &namespace_store);
transaction.execute(|ctx| {
let store_map = ctx.store_map();
// Verify store map contains our stores
assert!(store_map.contains_key("my_ranges"));
assert!(store_map.contains_key("my_namespaces"));
// Verify ranges
let (start, end) = store_map.get("my_ranges").unwrap();
assert_eq!(*start, 0);
assert_eq!(*end, 3); // RangeStore has 3 trees
// Verify namespaces
let (start, end) = store_map.get("my_namespaces").unwrap();
assert_eq!(*start, 3);
assert_eq!(*end, 5); // NamespaceStore has 2 trees
Ok(())
})?;
Ok(())
}
}
\ No newline at end of file
diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs
index ea86e36..3122aed 100644
--- a/crates/store/src/lib.rs
+++ b/crates/store/src/lib.rs
@@ -1,19 +1,22 @@
mod error;
mod store;
pub use error::{Result, Error};
pub use store::Store;
pub use store::open;
pub mod namespace;
pub use namespace::NamespaceStore;
pub mod range;
pub use range::RangeStore;
+pub mod network;
+pub use network::NetworkStore;
+
pub mod combined;
pub use combined::{
Transaction, TransactionContext,
TransactionProvider,
CombinedTransaction, CombinedTransactionContext
};
diff --git a/crates/store/src/network.rs b/crates/store/src/network.rs
new file mode 100644
index 0000000..5041775
--- /dev/null
+++ b/crates/store/src/network.rs
@@ -0,0 +1,563 @@
+//! Network management module for storing and managing network configurations.
+//!
+//! This module provides functionality to create, read, update, and delete network
+//! configurations. Each network is defined by a name, network range (IPv4 or IPv6),
+//! a provider implementation, and an optional assigner implementation.
+//!
+//! # Example
+//!
+//! ```
+//! use store::network::*;
+//! # use tempfile::TempDir;
+//! # fn example() -> store::Result<()> {
+//! # let temp_dir = tempfile::tempdir().unwrap();
+//! # let db = sled::open(temp_dir.path())?;
+//! let store = NetworkStore::open(&db)?;
+//!
+//! // Create a network with IPv4 range
+//! let netrange = NetRange::from_cidr("192.168.1.0/24")?;
+//! let provider = BasicProvider::new()
+//! .with_config("type", "aws")
+//! .with_config("region", "us-west-2");
+//! let assigner = Some(BasicAssigner::new("dhcp"));
+//!
+//! store.create("production", netrange, provider, assigner)?;
+//!
+//! // Retrieve the network
+//! let network = store.get("production")?.unwrap();
+//! assert_eq!(network.name, "production");
+//!
+//! // List all networks
+//! let networks = store.list()?;
+//! assert!(networks.contains(&"production".to_string()));
+//! # Ok(())
+//! # }
+//! ```
+
+use crate::{Result, Error};
+use sled::Transactional;
+use std::collections::HashMap;
+use std::net::{Ipv4Addr, Ipv6Addr};
+
+/// Represents an IPv4 or IPv6 network range
+#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
+pub enum NetRange {
+ /// IPv4 network with address and prefix length
+ V4 { addr: Ipv4Addr, prefix: u8 },
+ /// IPv6 network with address and prefix length
+ V6 { addr: Ipv6Addr, prefix: u8 },
+}
+
+impl NetRange {
+ /// Create a new IPv4 network range
+ pub fn ipv4(addr: Ipv4Addr, prefix: u8) -> Result<Self> {
+ if prefix > 32 {
+ return Err(Error::StoreError(sled::Error::Unsupported(
+ format!("Invalid IPv4 prefix length: {}", prefix)
+ )));
+ }
+ Ok(NetRange::V4 { addr, prefix })
+ }
+
+ /// Create a new IPv6 network range
+ pub fn ipv6(addr: Ipv6Addr, prefix: u8) -> Result<Self> {
+ if prefix > 128 {
+ return Err(Error::StoreError(sled::Error::Unsupported(
+ format!("Invalid IPv6 prefix length: {}", prefix)
+ )));
+ }
+ Ok(NetRange::V6 { addr, prefix })
+ }
+
+ /// Parse a network range from CIDR notation
+ pub fn from_cidr(cidr: &str) -> Result<Self> {
+ let parts: Vec<&str> = cidr.split('/').collect();
+ if parts.len() != 2 {
+ return Err(Error::StoreError(sled::Error::Unsupported(
+ "Invalid CIDR format".to_string()
+ )));
+ }
+
+ let prefix: u8 = parts[1].parse().map_err(|_| {
+ Error::StoreError(sled::Error::Unsupported("Invalid prefix length".to_string()))
+ })?;
+
+ if let Ok(ipv4) = parts[0].parse::<Ipv4Addr>() {
+ Self::ipv4(ipv4, prefix)
+ } else if let Ok(ipv6) = parts[0].parse::<Ipv6Addr>() {
+ Self::ipv6(ipv6, prefix)
+ } else {
+ Err(Error::StoreError(sled::Error::Unsupported(
+ "Invalid IP address format".to_string()
+ )))
+ }
+ }
+
+ /// Convert to CIDR notation string
+ pub fn to_cidr(&self) -> String {
+ match self {
+ NetRange::V4 { addr, prefix } => format!("{}/{}", addr, prefix),
+ NetRange::V6 { addr, prefix } => format!("{}/{}", addr, prefix),
+ }
+ }
+}
+
+/// Trait for network providers
+pub trait NetworkProvider: std::fmt::Debug {
+ /// Get the provider type identifier
+ fn provider_type(&self) -> &'static str;
+ /// Serialize provider configuration to JSON
+ fn to_json(&self) -> Result<String>;
+ /// Create provider from JSON
+ fn from_json(json: &str) -> Result<Self> where Self: Sized;
+}
+
+/// Trait for network assigners
+pub trait NetworkAssigner: std::fmt::Debug {
+ /// Get the assigner type identifier
+ fn assigner_type(&self) -> &'static str;
+ /// Serialize assigner configuration to JSON
+ fn to_json(&self) -> Result<String>;
+ /// Create assigner from JSON
+ fn from_json(json: &str) -> Result<Self> where Self: Sized;
+}
+
+/// Basic network provider implementation
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct BasicProvider {
+ pub config: HashMap<String, String>,
+}
+
+impl BasicProvider {
+ pub fn new() -> Self {
+ Self {
+ config: HashMap::new(),
+ }
+ }
+
+ pub fn with_config(mut self, key: &str, value: &str) -> Self {
+ self.config.insert(key.to_string(), value.to_string());
+ self
+ }
+}
+
+impl NetworkProvider for BasicProvider {
+ fn provider_type(&self) -> &'static str {
+ "basic"
+ }
+
+ fn to_json(&self) -> Result<String> {
+ serde_json::to_string(self).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
+ })
+ }
+
+ fn from_json(json: &str) -> Result<Self> {
+ serde_json::from_str(json).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
+ })
+ }
+}
+
+/// Basic network assigner implementation
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct BasicAssigner {
+ pub strategy: String,
+ pub config: HashMap<String, String>,
+}
+
+impl BasicAssigner {
+ pub fn new(strategy: &str) -> Self {
+ Self {
+ strategy: strategy.to_string(),
+ config: HashMap::new(),
+ }
+ }
+
+ pub fn with_config(mut self, key: &str, value: &str) -> Self {
+ self.config.insert(key.to_string(), value.to_string());
+ self
+ }
+}
+
+impl NetworkAssigner for BasicAssigner {
+ fn assigner_type(&self) -> &'static str {
+ "basic"
+ }
+
+ fn to_json(&self) -> Result<String> {
+ serde_json::to_string(self).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
+ })
+ }
+
+ fn from_json(json: &str) -> Result<Self> {
+ serde_json::from_str(json).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
+ })
+ }
+}
+
+/// Network configuration
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct Network {
+ pub name: String,
+ pub netrange: NetRange,
+ pub provider_type: String,
+ pub provider_config: String,
+ pub assigner_type: Option<String>,
+ pub assigner_config: Option<String>,
+}
+
+/// Network store for managing network configurations
+#[derive(Debug, Clone)]
+pub struct NetworkStore {
+ pub(crate) namespaces: crate::namespace::NamespaceStore,
+ pub(crate) networks: sled::Tree,
+}
+
+impl NetworkStore {
+ /// Open a new network store
+ pub fn open(db: &sled::Db) -> Result<Self> {
+ Ok(NetworkStore {
+ namespaces: crate::namespace::NamespaceStore::open(db)?,
+ networks: db.open_tree("networks/1/data")?,
+ })
+ }
+
+ /// Create a new network with provider and optional assigner
+ pub fn create<P, A>(&self, name: &str, netrange: NetRange, provider: P, assigner: Option<A>) -> Result<()>
+ where
+ P: NetworkProvider,
+ A: NetworkAssigner,
+ {
+ // Ensure "networks" namespace exists
+ if !self.namespaces.namespace_exists("networks")? {
+ self.namespaces.define("networks")?;
+ }
+
+ let network = Network {
+ name: name.to_string(),
+ netrange,
+ provider_type: provider.provider_type().to_string(),
+ provider_config: provider.to_json()?,
+ assigner_type: assigner.as_ref().map(|a| a.assigner_type().to_string()),
+ assigner_config: assigner.as_ref().map(|a| a.to_json()).transpose()?,
+ };
+
+ let network_json = serde_json::to_string(&network).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
+ })?;
+
+ (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
+ |(names, spaces, networks)| {
+ // Reserve the network name in the "networks" namespace
+ if !self.namespaces.reserve_in_transaction(names, spaces, "networks", name, name)? {
+ return Err(sled::transaction::ConflictableTransactionError::Abort(()));
+ }
+
+ // Store the network configuration
+ networks.insert(name.as_bytes(), network_json.as_bytes())?;
+ Ok(())
+ }
+ ).map_err(|e| match e {
+ sled::transaction::TransactionError::Abort(()) => {
+ Error::NamespaceKeyReserved("networks".to_string(), name.to_string())
+ }
+ sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
+ })?;
+
+ Ok(())
+ }
+
+ /// Get a network by name
+ pub fn get(&self, name: &str) -> Result<Option<Network>> {
+ if let Some(data) = self.networks.get(name.as_bytes())? {
+ let network_json = String::from_utf8(data.to_vec())?;
+ let network: Network = serde_json::from_str(&network_json).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON deserialization error: {}", e)))
+ })?;
+ Ok(Some(network))
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Update an existing network
+ pub fn update<P, A>(&self, name: &str, netrange: NetRange, provider: P, assigner: Option<A>) -> Result<()>
+ where
+ P: NetworkProvider,
+ A: NetworkAssigner,
+ {
+ if !self.namespaces.key_exists("networks", name)? {
+ return Err(Error::StoreError(sled::Error::Unsupported(
+ format!("Network '{}' does not exist", name)
+ )));
+ }
+
+ let network = Network {
+ name: name.to_string(),
+ netrange,
+ provider_type: provider.provider_type().to_string(),
+ provider_config: provider.to_json()?,
+ assigner_type: assigner.as_ref().map(|a| a.assigner_type().to_string()),
+ assigner_config: assigner.as_ref().map(|a| a.to_json()).transpose()?,
+ };
+
+ let network_json = serde_json::to_string(&network).map_err(|e| {
+ Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e)))
+ })?;
+
+ self.networks.insert(name.as_bytes(), network_json.as_bytes())?;
+ Ok(())
+ }
+
+ /// Delete a network by name
+ pub fn delete(&self, name: &str) -> Result<bool> {
+ let result = (&self.namespaces.names, &self.namespaces.spaces, &self.networks).transaction(
+ |(names, spaces, networks)| {
+ // Remove from namespace
+ let removed = self.namespaces.remove_in_transaction(names, spaces, "networks", name)?;
+ if removed {
+ // Remove network data
+ networks.remove(name.as_bytes())?;
+ }
+ Ok(removed)
+ }
+ ).map_err(|e| match e {
+ sled::transaction::TransactionError::Abort(()) => {
+ Error::StoreError(sled::Error::Unsupported("Transaction aborted".to_string()))
+ }
+ sled::transaction::TransactionError::Storage(storage_err) => Error::StoreError(storage_err),
+ })?;
+
+ Ok(result)
+ }
+
+ /// List all network names
+ pub fn list(&self) -> Result<Vec<String>> {
+ self.namespaces.list_keys("networks")
+ }
+
+ /// Check if a network exists
+ pub fn exists(&self, name: &str) -> Result<bool> {
+ self.namespaces.key_exists("networks", name)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use tempfile::TempDir;
+
+ fn create_test_store() -> Result<(NetworkStore, TempDir)> {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let db = sled::open(temp_dir.path())?;
+ let store = NetworkStore::open(&db)?;
+ Ok((store, temp_dir))
+ }
+
+ #[test]
+ fn test_netrange_ipv4() -> Result<()> {
+ let netrange = NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?;
+ assert_eq!(netrange.to_cidr(), "192.168.1.0/24");
+ Ok(())
+ }
+
+ #[test]
+ fn test_netrange_ipv6() -> Result<()> {
+ let netrange = NetRange::ipv6("2001:db8::".parse().unwrap(), 64)?;
+ assert_eq!(netrange.to_cidr(), "2001:db8::/64");
+ Ok(())
+ }
+
+ #[test]
+ fn test_netrange_from_cidr() -> Result<()> {
+ let ipv4_range = NetRange::from_cidr("10.0.0.0/8")?;
+ assert_eq!(ipv4_range.to_cidr(), "10.0.0.0/8");
+
+ let ipv6_range = NetRange::from_cidr("fe80::/10")?;
+ assert_eq!(ipv6_range.to_cidr(), "fe80::/10");
+ Ok(())
+ }
+
+ #[test]
+ fn test_basic_provider() -> Result<()> {
+ let provider = BasicProvider::new()
+ .with_config("endpoint", "https://api.example.com")
+ .with_config("timeout", "30");
+
+ let json = provider.to_json()?;
+ let restored = BasicProvider::from_json(&json)?;
+
+ assert_eq!(provider.config, restored.config);
+ assert_eq!(provider.provider_type(), "basic");
+ Ok(())
+ }
+
+ #[test]
+ fn test_basic_assigner() -> Result<()> {
+ let assigner = BasicAssigner::new("round_robin")
+ .with_config("pool_size", "100");
+
+ let json = assigner.to_json()?;
+ let restored = BasicAssigner::from_json(&json)?;
+
+ assert_eq!(assigner.strategy, restored.strategy);
+ assert_eq!(assigner.config, restored.config);
+ assert_eq!(assigner.assigner_type(), "basic");
+ Ok(())
+ }
+
+ #[test]
+ fn test_create_and_get_network() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ let netrange = NetRange::ipv4("192.168.1.0".parse().unwrap(), 24)?;
+ let provider = BasicProvider::new().with_config("type", "test");
+ let assigner = Some(BasicAssigner::new("sequential"));
+
+ store.create("test_network", netrange.clone(), provider, assigner)?;
+
+ let network = store.get("test_network")?.unwrap();
+ assert_eq!(network.name, "test_network");
+ assert_eq!(network.netrange, netrange);
+ assert_eq!(network.provider_type, "basic");
+ assert!(network.assigner_type.is_some());
+ Ok(())
+ }
+
+ #[test]
+ fn test_create_network_without_assigner() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ let netrange = NetRange::ipv6("2001:db8::".parse().unwrap(), 64)?;
+ let provider = BasicProvider::new();
+ let assigner: Option<BasicAssigner> = None;
+
+ store.create("ipv6_network", netrange.clone(), provider, assigner)?;
+
+ let network = store.get("ipv6_network")?.unwrap();
+ assert_eq!(network.name, "ipv6_network");
+ assert_eq!(network.netrange, netrange);
+ assert!(network.assigner_type.is_none());
+ assert!(network.assigner_config.is_none());
+ Ok(())
+ }
+
+ #[test]
+ fn test_create_duplicate_network() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ let netrange = NetRange::ipv4("10.0.0.0".parse().unwrap(), 8)?;
+ let provider = BasicProvider::new();
+ let assigner: Option<BasicAssigner> = None;
+
+ // First creation should succeed
+ store.create("duplicate_test", netrange.clone(), provider.clone(), assigner.clone())?;
+
+ // Second creation should fail
+ let result = store.create("duplicate_test", netrange, provider, assigner);
+ assert!(result.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_update_network() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Create initial network
+ let netrange = NetRange::ipv4("172.16.0.0".parse().unwrap(), 12)?;
+ let provider = BasicProvider::new().with_config("version", "1");
+ let assigner: Option<BasicAssigner> = None;
+
+ store.create("update_test", netrange, provider, assigner)?;
+
+ // Update the network
+ let new_netrange = NetRange::ipv4("172.16.0.0".parse().unwrap(), 16)?;
+ let new_provider = BasicProvider::new().with_config("version", "2");
+ let new_assigner = Some(BasicAssigner::new("random"));
+
+ store.update("update_test", new_netrange.clone(), new_provider, new_assigner)?;
+
+ // Verify the update
+ let network = store.get("update_test")?.unwrap();
+ assert_eq!(network.netrange, new_netrange);
+ assert!(network.assigner_type.is_some());
+ Ok(())
+ }
+
+ #[test]
+ fn test_delete_network() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ let netrange = NetRange::ipv4("203.0.113.0".parse().unwrap(), 24)?;
+ let provider = BasicProvider::new();
+ let assigner: Option<BasicAssigner> = None;
+
+ store.create("delete_test", netrange, provider, assigner)?;
+ assert!(store.exists("delete_test")?);
+
+ let deleted = store.delete("delete_test")?;
+ assert!(deleted);
+ assert!(!store.exists("delete_test")?);
+
+ // Try to delete non-existent network
+ let deleted_again = store.delete("delete_test")?;
+ assert!(!deleted_again);
+ Ok(())
+ }
+
+ #[test]
+ fn test_list_networks() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Create multiple networks
+ let networks = vec![
+ ("net1", "10.1.0.0/16"),
+ ("net2", "10.2.0.0/16"),
+ ("net3", "2001:db8:1::/48"),
+ ];
+
+ for (name, cidr) in &networks {
+ let netrange = NetRange::from_cidr(cidr)?;
+ let provider = BasicProvider::new();
+ let assigner: Option<BasicAssigner> = None;
+ store.create(name, netrange, provider, assigner)?;
+ }
+
+ let mut network_names = store.list()?;
+ network_names.sort();
+
+ let mut expected: Vec<String> = networks.iter().map(|(name, _)| name.to_string()).collect();
+ expected.sort();
+
+ assert_eq!(network_names, expected);
+ Ok(())
+ }
+
+ #[test]
+ fn test_update_nonexistent_network() -> Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ let netrange = NetRange::ipv4("198.51.100.0".parse().unwrap(), 24)?;
+ let provider = BasicProvider::new();
+ let assigner: Option<BasicAssigner> = None;
+
+ let result = store.update("nonexistent", netrange, provider, assigner);
+ assert!(result.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_invalid_cidr() {
+ let result = NetRange::from_cidr("invalid");
+ assert!(result.is_err());
+
+ let result = NetRange::from_cidr("192.168.1.0");
+ assert!(result.is_err());
+
+ let result = NetRange::from_cidr("192.168.1.0/33");
+ assert!(result.is_err());
+ }
+}
\ No newline at end of file
diff --git a/crates/store/src/store.rs b/crates/store/src/store.rs
index acef081..0fe9637 100644
--- a/crates/store/src/store.rs
+++ b/crates/store/src/store.rs
@@ -1,52 +1,58 @@
use crate::Result;
#[derive(Debug, Clone)]
pub struct Db {
prefix: String,
db: sled::Db
}
#[derive(Debug, Clone)]
pub struct Store {
db: Db,
namespaces: crate::namespace::NamespaceStore,
ranges: crate::range::RangeStore,
+ networks: crate::network::NetworkStore,
}
impl Db {
pub fn open(path: String, prefix: String) -> Result<Self> {
let db = sled::open(path)?;
Ok(Db { prefix, db })
}
pub fn open_tree(&self, name: &str) -> Result<sled::Tree> {
Ok(self.db.open_tree(self.tree_path(name))?)
}
pub fn tree_path(&self, name: &str) -> String {
format!("t1/{}/{}", self.prefix, name)
}
}
pub fn open() -> Result<Store> {
let db = Db::open("libcollar_store".to_string(), "bonefire".to_string())?;
Ok(Store {
namespaces: crate::namespace::NamespaceStore::open(&db.db)?,
ranges: crate::range::RangeStore::open(&db.db)?,
+ networks: crate::network::NetworkStore::open(&db.db)?,
db: db,
})
}
impl Store {
fn tree_path(&self, name: &str) -> String {
self.db.tree_path(name)
}
pub fn namespaces(&self) -> &crate::namespace::NamespaceStore {
&self.namespaces
}
pub fn ranges(&self) -> &crate::range::RangeStore {
&self.ranges
}
+
+ pub fn networks(&self) -> &crate::network::NetworkStore {
+ &self.networks
+ }
}
diff --git a/crates/store/tests/network_integration.rs b/crates/store/tests/network_integration.rs
new file mode 100644
index 0000000..d700e16
--- /dev/null
+++ b/crates/store/tests/network_integration.rs
@@ -0,0 +1,137 @@
+use store::network::*;
+use tempfile::TempDir;
+
+fn create_test_store() -> store::Result<(NetworkStore, TempDir)> {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let db = sled::open(temp_dir.path())?;
+ let store = NetworkStore::open(&db)?;
+ Ok((store, temp_dir))
+}
+
+#[test]
+fn test_network_store_full_lifecycle() -> store::Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Test creating a network
+ let netrange = NetRange::from_cidr("10.0.0.0/8")?;
+ let provider = BasicProvider::new()
+ .with_config("type", "aws")
+ .with_config("region", "us-west-2");
+ let assigner = Some(BasicAssigner::new("dhcp")
+ .with_config("lease_time", "3600"));
+
+ store.create("production", netrange.clone(), provider, assigner)?;
+
+ // Verify the network was created
+ let network = store.get("production")?.unwrap();
+ assert_eq!(network.name, "production");
+ assert_eq!(network.netrange, netrange);
+ assert_eq!(network.provider_type, "basic");
+ assert!(network.assigner_type.is_some());
+
+ // Test listing networks
+ let networks = store.list()?;
+ assert!(networks.contains(&"production".to_string()));
+ assert!(store.exists("production")?);
+
+ // Test updating the network
+ let new_netrange = NetRange::from_cidr("10.0.0.0/16")?;
+ let new_provider = BasicProvider::new()
+ .with_config("type", "gcp")
+ .with_config("region", "us-central1");
+
+ store.update("production", new_netrange.clone(), new_provider, None::<BasicAssigner>)?;
+
+ // Verify the update
+ let updated_network = store.get("production")?.unwrap();
+ assert_eq!(updated_network.netrange, new_netrange);
+ assert!(updated_network.assigner_type.is_none());
+
+ // Test deletion
+ let deleted = store.delete("production")?;
+ assert!(deleted);
+
+ let deleted_network = store.get("production")?;
+ assert!(deleted_network.is_none());
+ assert!(!store.exists("production")?);
+
+ Ok(())
+}
+
+#[test]
+fn test_multiple_networks_management() -> store::Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Create multiple networks with different configurations
+ let networks_config = vec![
+ ("frontend", "192.168.1.0/24", "web", true),
+ ("backend", "192.168.2.0/24", "api", false),
+ ("database", "192.168.3.0/24", "db", true),
+ ("ipv6_net", "2001:db8::/48", "ipv6", false),
+ ];
+
+ for (name, cidr, net_type, has_assigner) in &networks_config {
+ let netrange = NetRange::from_cidr(cidr)?;
+ let provider = BasicProvider::new().with_config("type", net_type);
+ let assigner = if *has_assigner {
+ Some(BasicAssigner::new("sequential"))
+ } else {
+ None
+ };
+ store.create(name, netrange, provider, assigner)?;
+ }
+
+ // Verify all networks were created
+ let network_list = store.list()?;
+ assert_eq!(network_list.len(), 4);
+
+ for (name, _, _, has_assigner) in &networks_config {
+ assert!(network_list.contains(&name.to_string()));
+ assert!(store.exists(name)?);
+
+ let network = store.get(name)?.unwrap();
+ assert_eq!(network.assigner_type.is_some(), *has_assigner);
+ }
+
+ // Test that we can't create duplicate networks
+ let result = store.create("frontend", NetRange::from_cidr("10.0.0.0/8")?, BasicProvider::new(), None::<BasicAssigner>);
+ assert!(result.is_err());
+
+ Ok(())
+}
+
+#[test]
+fn test_complex_provider_and_assigner_configs() -> store::Result<()> {
+ let (store, _temp_dir) = create_test_store()?;
+
+ // Create a network with complex provider configuration
+ let provider = BasicProvider::new()
+ .with_config("endpoint", "https://api.example.com/v1")
+ .with_config("auth_token", "secret123")
+ .with_config("timeout", "30")
+ .with_config("retry_count", "3");
+
+ let assigner = Some(BasicAssigner::new("weighted")
+ .with_config("weights", "web:3,api:2,db:1")
+ .with_config("backup_strategy", "round_robin")
+ .with_config("health_check", "tcp:80"));
+
+ let netrange = NetRange::from_cidr("172.16.0.0/12")?;
+ store.create("complex_net", netrange.clone(), provider.clone(), assigner)?;
+
+ // Retrieve and verify the configuration was preserved
+ let network = store.get("complex_net")?.unwrap();
+ assert_eq!(network.netrange, netrange);
+
+ // Verify provider configuration is preserved by deserializing
+ let restored_provider = BasicProvider::from_json(&network.provider_config)?;
+ assert_eq!(restored_provider.config.get("endpoint").unwrap(), "https://api.example.com/v1");
+ assert_eq!(restored_provider.config.get("timeout").unwrap(), "30");
+
+ // Verify assigner configuration is preserved
+ let restored_assigner = BasicAssigner::from_json(network.assigner_config.as_ref().unwrap())?;
+ assert_eq!(restored_assigner.strategy, "weighted");
+ assert_eq!(restored_assigner.config.get("weights").unwrap(), "web:3,api:2,db:1");
+
+ Ok(())
+}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jun 8, 9:10 AM (19 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47586
Default Alt Text
(85 KB)

Event Timeline