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(e: sled::transaction::ConflictableTransactionError, 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, // 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, 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 { &self.store_map } } /// Generic transaction struct for atomic operations across multiple stores pub struct Transaction<'a> { stores: HashMap, 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(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(&self, operations: F) -> Result where F: Fn(&TransactionContext) -> Result, { // 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 { + 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 { + 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 { + 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::() { + Self::ipv4(ipv4, prefix) + } else if let Ok(ipv6) = parts[0].parse::() { + 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; + /// Create provider from JSON + fn from_json(json: &str) -> Result 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; + /// Create assigner from JSON + fn from_json(json: &str) -> Result where Self: Sized; +} + +/// Basic network provider implementation +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct BasicProvider { + pub config: HashMap, +} + +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 { + serde_json::to_string(self).map_err(|e| { + Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e))) + }) + } + + fn from_json(json: &str) -> Result { + 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, +} + +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 { + serde_json::to_string(self).map_err(|e| { + Error::StoreError(sled::Error::Unsupported(format!("JSON serialization error: {}", e))) + }) + } + + fn from_json(json: &str) -> Result { + 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, + pub assigner_config: Option, +} + +/// 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 { + 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(&self, name: &str, netrange: NetRange, provider: P, assigner: Option) -> 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> { + 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(&self, name: &str, netrange: NetRange, provider: P, assigner: Option) -> 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 { + 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> { + self.namespaces.list_keys("networks") + } + + /// Check if a network exists + pub fn exists(&self, name: &str) -> Result { + 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 = 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 = 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 = 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 = 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 = None; + store.create(name, netrange, provider, assigner)?; + } + + let mut network_names = store.list()?; + network_names.sort(); + + let mut expected: Vec = 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 = 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 { let db = sled::open(path)?; Ok(Db { prefix, db }) } pub fn open_tree(&self, name: &str) -> Result { 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 { 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::)?; + + // 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::); + 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