Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F73669
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
85 KB
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jun 8, 9:10 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
47586
Default Alt Text
(85 KB)
Attached To
rCOLLAR collar
Event Timeline
Log In to Comment