chore: update crates
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled

This commit is contained in:
Jesús Pérez 2026-02-03 22:29:15 +00:00
parent f9d22b5382
commit 6335aba33b
Signed by: jesus
GPG Key ID: 9F243E355E0BC939
15 changed files with 335 additions and 319 deletions

263
Cargo.lock generated
View File

@ -23,6 +23,18 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "affinitypool"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dde2a385b82232b559baeec740c37809051c596f9b56e7da0d0da2c8e8f54f6"
dependencies = [
"async-channel",
"num_cpus",
"thiserror 1.0.69",
"tokio",
]
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.8" version = "0.7.8"
@ -332,7 +344,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"static_assertions_next", "static_assertions_next",
"thiserror 2.0.17", "thiserror 2.0.18",
] ]
[[package]] [[package]]
@ -349,7 +361,7 @@ dependencies = [
"quote", "quote",
"strum", "strum",
"syn 2.0.114", "syn 2.0.114",
"thiserror 2.0.17", "thiserror 2.0.18",
] ]
[[package]] [[package]]
@ -394,6 +406,17 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "async-lock"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.6" version = "0.3.6"
@ -490,7 +513,7 @@ dependencies = [
"num-traits", "num-traits",
"pastey", "pastey",
"rayon", "rayon",
"thiserror 2.0.17", "thiserror 2.0.18",
"v_frame", "v_frame",
"y4m", "y4m",
] ]
@ -1070,6 +1093,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.6"
@ -1411,6 +1443,12 @@ dependencies = [
"urlencoding", "urlencoding",
] ]
[[package]]
name = "double-ended-peekable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57"
[[package]] [[package]]
name = "dtoa" name = "dtoa"
version = "1.0.11" version = "1.0.11"
@ -1567,6 +1605,19 @@ dependencies = [
"zune-inflate", "zune-inflate",
] ]
[[package]]
name = "ext-sort"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5d3b056bcc471d38082b8c453acb6670f7327fd44219b3c411e40834883569"
dependencies = [
"log",
"rayon",
"rmp-serde",
"serde",
"tempfile",
]
[[package]] [[package]]
name = "fastembed" name = "fastembed"
version = "5.8.1" version = "5.8.1"
@ -2055,6 +2106,10 @@ name = "hashbrown"
version = "0.14.5" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash 0.8.12",
"allocator-api2",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
@ -2149,7 +2204,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.17", "thiserror 2.0.18",
"ureq 2.12.1", "ureq 2.12.1",
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
@ -2240,6 +2295,16 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]]
name = "humantime-serde"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
dependencies = [
"humantime",
"serde",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.8.1"
@ -2700,7 +2765,6 @@ dependencies = [
"async-trait", "async-trait",
"chrono", "chrono",
"dashmap 6.1.0", "dashmap 6.1.0",
"fastembed",
"futures", "futures",
"mockito", "mockito",
"notify", "notify",
@ -2711,10 +2775,11 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"stratum-embeddings",
"surrealdb", "surrealdb",
"tempfile", "tempfile",
"tera", "tera",
"thiserror 2.0.17", "thiserror 2.0.18",
"tokio", "tokio",
"toml", "toml",
"tracing", "tracing",
@ -2889,6 +2954,15 @@ dependencies = [
"imgref", "imgref",
] ]
[[package]]
name = "lru"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown 0.15.5",
]
[[package]] [[package]]
name = "lru-slab" name = "lru-slab"
version = "0.1.2" version = "0.1.2"
@ -3088,6 +3162,26 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "moka"
version = "0.12.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a"
dependencies = [
"async-lock",
"crossbeam-channel",
"crossbeam-epoch",
"crossbeam-utils",
"equivalent",
"event-listener",
"futures-util",
"parking_lot",
"portable-atomic",
"smallvec",
"tagptr",
"uuid",
]
[[package]] [[package]]
name = "monostate" name = "monostate"
version = "0.1.18" version = "0.1.18"
@ -3404,7 +3498,7 @@ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"thiserror 2.0.17", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
"url", "url",
@ -4001,6 +4095,18 @@ dependencies = [
"parking_lot", "parking_lot",
] ]
[[package]]
name = "quick_cache"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3"
dependencies = [
"ahash 0.8.12",
"equivalent",
"hashbrown 0.16.1",
"parking_lot",
]
[[package]] [[package]]
name = "quinn" name = "quinn"
version = "0.11.9" version = "0.11.9"
@ -4015,7 +4121,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2",
"thiserror 2.0.17", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
"web-time", "web-time",
@ -4036,7 +4142,7 @@ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
"thiserror 2.0.17", "thiserror 2.0.18",
"tinyvec", "tinyvec",
"tracing", "tracing",
"web-time", "web-time",
@ -4177,7 +4283,7 @@ dependencies = [
"rand 0.9.2", "rand 0.9.2",
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"simd_helpers", "simd_helpers",
"thiserror 2.0.17", "thiserror 2.0.18",
"v_frame", "v_frame",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -4268,7 +4374,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [ dependencies = [
"getrandom 0.2.17", "getrandom 0.2.17",
"libredox", "libredox",
"thiserror 2.0.17", "thiserror 2.0.18",
] ]
[[package]] [[package]]
@ -4377,6 +4483,15 @@ dependencies = [
"webpki-roots 1.0.5", "webpki-roots 1.0.5",
] ]
[[package]]
name = "revision"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f53179a035f881adad8c4d58a2c599c6b4a8325b989c68d178d7a34d1b1e4c"
dependencies = [
"revision-derive 0.10.0",
]
[[package]] [[package]]
name = "revision" name = "revision"
version = "0.11.0" version = "0.11.0"
@ -4386,12 +4501,23 @@ dependencies = [
"chrono", "chrono",
"geo", "geo",
"regex", "regex",
"revision-derive", "revision-derive 0.11.0",
"roaring", "roaring",
"rust_decimal", "rust_decimal",
"uuid", "uuid",
] ]
[[package]]
name = "revision-derive"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0ec466e5d8dca9965eb6871879677bef5590cf7525ad96cae14376efb75073"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "revision-derive" name = "revision-derive"
version = "0.11.0" version = "0.11.0"
@ -4411,9 +4537,9 @@ checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
[[package]] [[package]]
name = "rig-core" name = "rig-core"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b1a48121c1ecd6f6ce59d64ec353c791aac6fc07bf4aa353380e8185659e6eb" checksum = "a8f7a3f0c7c00eaced15a68ee16e1bd6bb709ff598d11b9aedac8b628217dc09"
dependencies = [ dependencies = [
"as-any", "as-any",
"async-stream", "async-stream",
@ -4427,13 +4553,14 @@ dependencies = [
"http", "http",
"mime", "mime",
"mime_guess", "mime_guess",
"nanoid",
"ordered-float", "ordered-float",
"pin-project-lite", "pin-project-lite",
"reqwest", "reqwest",
"schemars 1.2.0", "schemars 1.2.0",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.17", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
"tracing-futures", "tracing-futures",
@ -4492,6 +4619,16 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "rmp-serde"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
dependencies = [
"rmp",
"serde",
]
[[package]] [[package]]
name = "rmpv" name = "rmpv"
version = "1.3.1" version = "1.3.1"
@ -5039,7 +5176,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
"thiserror 2.0.17", "thiserror 2.0.18",
"time", "time",
] ]
@ -5183,6 +5320,24 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "stratum-embeddings"
version = "0.1.0"
dependencies = [
"async-trait",
"fastembed",
"futures",
"humantime-serde",
"moka",
"serde",
"serde_json",
"surrealdb",
"thiserror 2.0.18",
"tokio",
"tracing",
"xxhash-rust",
]
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.9" version = "0.8.9"
@ -5243,9 +5398,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "surrealdb" name = "surrealdb"
version = "2.4.1" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f921fcafdc840d36a4378ef7639fcb2731a21a858b048de83f0bd7194c242479" checksum = "62b7720b39ce2985efbfa10858b7397ffd95655a9bab6d9dfaa03622bbdc3bc2"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"async-channel", "async-channel",
@ -5260,7 +5415,7 @@ dependencies = [
"pharos", "pharos",
"reblessive", "reblessive",
"reqwest", "reqwest",
"revision", "revision 0.11.0",
"ring", "ring",
"rust_decimal", "rust_decimal",
"rustls", "rustls",
@ -5285,11 +5440,12 @@ dependencies = [
[[package]] [[package]]
name = "surrealdb-core" name = "surrealdb-core"
version = "2.4.1" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae1a46c6d68a61c0a270f456a152433093f4d5c0e71c45eea64f95e95d68bd9" checksum = "c48e42c81713be2f9b3dae64328999eafe8b8060dd584059445a908748b39787"
dependencies = [ dependencies = [
"addr", "addr",
"affinitypool",
"ahash 0.8.12", "ahash 0.8.12",
"ammonia", "ammonia",
"any_ascii", "any_ascii",
@ -5309,12 +5465,14 @@ dependencies = [
"dashmap 5.5.3", "dashmap 5.5.3",
"deunicode", "deunicode",
"dmp", "dmp",
"ext-sort",
"fst", "fst",
"futures", "futures",
"fuzzy-matcher", "fuzzy-matcher",
"geo", "geo",
"geo-types", "geo-types",
"getrandom 0.3.4", "getrandom 0.3.4",
"hashbrown 0.14.5",
"hex", "hex",
"http", "http",
"ipnet", "ipnet",
@ -5333,13 +5491,13 @@ dependencies = [
"pharos", "pharos",
"phf", "phf",
"pin-project-lite", "pin-project-lite",
"quick_cache", "quick_cache 0.5.2",
"radix_trie", "radix_trie",
"rand 0.8.5", "rand 0.8.5",
"rayon", "rayon",
"reblessive", "reblessive",
"regex", "regex",
"revision", "revision 0.11.0",
"ring", "ring",
"rmpv", "rmpv",
"roaring", "roaring",
@ -5356,7 +5514,9 @@ dependencies = [
"storekey", "storekey",
"strsim", "strsim",
"subtle", "subtle",
"surrealkv",
"sysinfo", "sysinfo",
"tempfile",
"thiserror 1.0.69", "thiserror 1.0.69",
"tokio", "tokio",
"tracing", "tracing",
@ -5365,12 +5525,31 @@ dependencies = [
"unicase", "unicase",
"url", "url",
"uuid", "uuid",
"vart", "vart 0.8.1",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasmtimer", "wasmtimer",
"ws_stream_wasm", "ws_stream_wasm",
] ]
[[package]]
name = "surrealkv"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a5041979bdff8599a1d5f6cb7365acb9a79664e2a84e5c4fddac2b3969f7d1"
dependencies = [
"ahash 0.8.12",
"bytes",
"chrono",
"crc32fast",
"double-ended-peekable",
"getrandom 0.2.17",
"lru",
"parking_lot",
"quick_cache 0.6.18",
"revision 0.10.0",
"vart 0.9.3",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -5448,6 +5627,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "tagptr"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
[[package]] [[package]]
name = "tap" name = "tap"
version = "1.0.1" version = "1.0.1"
@ -5522,11 +5707,11 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.17" version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [ dependencies = [
"thiserror-impl 2.0.17", "thiserror-impl 2.0.18",
] ]
[[package]] [[package]]
@ -5542,9 +5727,9 @@ dependencies = [
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.17" version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -5666,7 +5851,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"spm_precompiled", "spm_precompiled",
"thiserror 2.0.17", "thiserror 2.0.18",
"unicode-normalization-alignments", "unicode-normalization-alignments",
"unicode-segmentation", "unicode-segmentation",
"unicode_categories", "unicode_categories",
@ -6156,9 +6341,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.19.0" version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
"js-sys", "js-sys",
@ -6189,6 +6374,12 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87782b74f898179396e93c0efabb38de0d58d50bbd47eae00c71b3a1144dbbae" checksum = "87782b74f898179396e93c0efabb38de0d58d50bbd47eae00c71b3a1144dbbae"
[[package]]
name = "vart"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1982d899e57d646498709735f16e9224cf1e8680676ad687f930cf8b5b555ae"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -6738,7 +6929,7 @@ dependencies = [
"pharos", "pharos",
"rustc_version", "rustc_version",
"send_wrapper", "send_wrapper",
"thiserror 2.0.17", "thiserror 2.0.18",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -6753,6 +6944,12 @@ dependencies = [
"tap", "tap",
] ]
[[package]]
name = "xxhash-rust"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
[[package]] [[package]]
name = "y4m" name = "y4m"
version = "0.8.0" version = "0.8.0"

View File

@ -28,7 +28,7 @@ toml = "0.9"
tokio = { version = "1.49", features = ["full"] } tokio = { version = "1.49", features = ["full"] }
# Error handling # Error handling
thiserror = "2.0" thiserror = "2.0.18"
anyhow = "1.0" anyhow = "1.0"
# Markdown parsing # Markdown parsing
@ -38,10 +38,10 @@ pulldown-cmark = "0.13"
tera = "1.20" tera = "1.20"
# Embeddings # Embeddings
rig-core = "0.28" rig-core = "0.30"
# Storage # Storage
surrealdb = "2.4" surrealdb = "2.6"
dashmap = "6.1" dashmap = "6.1"
# File watching # File watching
@ -51,7 +51,7 @@ notify = "8.2"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
# UUID generation # UUID generation
uuid = { version = "1.19", features = ["v4", "serde"] } uuid = { version = "1.20", features = ["v4", "serde"] }
# Logging # Logging
tracing = "0.1" tracing = "0.1"

View File

@ -508,7 +508,7 @@ watch_paths = ["notes", "decisions", "guidelines", "patterns", "journal"]
/// Add a new node /// Add a new node
async fn cmd_add( async fn cmd_add(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
node_type: CliNodeType, node_type: CliNodeType,
title: String, title: String,
@ -566,7 +566,7 @@ async fn cmd_add(
/// Search KOGRAL /// Search KOGRAL
async fn cmd_search( async fn cmd_search(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
query: String, query: String,
node_type: Option<CliNodeType>, node_type: Option<CliNodeType>,
@ -652,7 +652,7 @@ async fn cmd_search(
/// Create a relationship between nodes /// Create a relationship between nodes
async fn cmd_link( async fn cmd_link(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
from: String, from: String,
to: String, to: String,
@ -710,7 +710,7 @@ async fn cmd_link(
/// Sync filesystem with storage /// Sync filesystem with storage
async fn cmd_sync( async fn cmd_sync(
_project_dir: &PathBuf, _project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
direction: String, direction: String,
dry_run: bool, dry_run: bool,
@ -744,7 +744,7 @@ async fn cmd_sync(
/// Start MCP server /// Start MCP server
async fn cmd_serve( async fn cmd_serve(
_project_dir: &PathBuf, _project_dir: &Path,
_config: Option<KbConfig>, _config: Option<KbConfig>,
transport: String, transport: String,
port: u16, port: u16,
@ -784,7 +784,7 @@ async fn cmd_serve(
/// Show graph visualization /// Show graph visualization
#[allow(clippy::excessive_nesting)] #[allow(clippy::excessive_nesting)]
async fn cmd_graph( async fn cmd_graph(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
format: String, format: String,
node_type: Option<CliNodeType>, node_type: Option<CliNodeType>,
@ -873,7 +873,7 @@ async fn cmd_graph(
/// Import from external sources /// Import from external sources
async fn cmd_import( async fn cmd_import(
_project_dir: &PathBuf, _project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
source: ImportSource, source: ImportSource,
path: PathBuf, path: PathBuf,
@ -948,7 +948,7 @@ async fn cmd_import(
/// Export to external formats /// Export to external formats
async fn cmd_export( async fn cmd_export(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
format: ExportFormat, format: ExportFormat,
output: PathBuf, output: PathBuf,
@ -1066,7 +1066,7 @@ async fn cmd_export(
/// List all nodes /// List all nodes
async fn cmd_list( async fn cmd_list(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
node_type: Option<CliNodeType>, node_type: Option<CliNodeType>,
status: Option<CliNodeStatus>, status: Option<CliNodeStatus>,
@ -1134,7 +1134,7 @@ async fn cmd_list(
/// Show node details /// Show node details
#[allow(clippy::excessive_nesting)] #[allow(clippy::excessive_nesting)]
async fn cmd_show( async fn cmd_show(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
node: String, node: String,
relations: bool, relations: bool,
@ -1199,7 +1199,7 @@ async fn cmd_show(
/// Delete a node /// Delete a node
async fn cmd_delete( async fn cmd_delete(
project_dir: &PathBuf, project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
node: String, node: String,
cascade: bool, cascade: bool,
@ -1249,7 +1249,7 @@ async fn cmd_delete(
/// Manage configuration /// Manage configuration
async fn cmd_config( async fn cmd_config(
_project_dir: &PathBuf, _project_dir: &Path,
config: Option<KbConfig>, config: Option<KbConfig>,
show: bool, show: bool,
get: Option<String>, get: Option<String>,

View File

@ -30,12 +30,14 @@ tracing = { workspace = true }
# Additional dependencies # Additional dependencies
async-trait = { workspace = true } async-trait = { workspace = true }
fastembed = { workspace = true, optional = true }
regex = { workspace = true } regex = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
# Stratum embeddings
stratum-embeddings = { path = "/Users/Akasha/Development/stratumiops/crates/stratum-embeddings", features = ["kogral"] }
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true, features = ["test-util", "macros"] } tokio = { workspace = true, features = ["test-util", "macros"] }
mockito = { workspace = true } mockito = { workspace = true }
@ -45,5 +47,4 @@ tempfile = { workspace = true }
default = ["filesystem"] default = ["filesystem"]
filesystem = [] filesystem = []
surrealdb-backend = ["surrealdb"] surrealdb-backend = ["surrealdb"]
fastembed = ["dep:fastembed"] full = ["surrealdb-backend"]
full = ["surrealdb-backend", "fastembed"]

View File

@ -15,8 +15,8 @@ use std::fmt::Write;
use crate::error::Result; use crate::error::Result;
use crate::models::{Block, TaskStatus}; use crate::models::{Block, TaskStatus};
use crate::regex_patterns::{ use crate::regex_patterns::{
PROPERTY_START_PATTERN, TAG_PATTERN, PROPERTY_INLINE_PATTERN, UUID_REF_PATTERN, LOGSEQ_WIKILINK_PATTERN, PROPERTY_INLINE_PATTERN, PROPERTY_START_PATTERN, TAG_PATTERN,
LOGSEQ_WIKILINK_PATTERN, UUID_REF_PATTERN,
}; };
/// Parser for converting markdown outliner format to/from Block structures /// Parser for converting markdown outliner format to/from Block structures
@ -208,7 +208,9 @@ impl BlockParser {
} }
// Remove properties from content // Remove properties from content
clean_content = PROPERTY_INLINE_PATTERN.replace_all(&clean_content, "").to_string(); clean_content = PROPERTY_INLINE_PATTERN
.replace_all(&clean_content, "")
.to_string();
(properties, clean_content) (properties, clean_content)
} }

View File

@ -1,113 +0,0 @@
//! `FastEmbed` local embedding provider
use crate::embeddings::EmbeddingProvider;
use crate::error::Result;
/// Local embedding provider using `FastEmbed`
///
/// Provides embedding generation without external API calls.
/// Uses local models for privacy and offline capability.
///
/// Default model: BAAI/bge-small-en-v1.5 (384 dimensions)
/// Supports CPU inference with minimal memory footprint.
#[cfg(feature = "fastembed")]
pub struct FastEmbedProvider {
model: fastembed::FlagEmbedding,
dimensions: usize,
}
#[cfg(feature = "fastembed")]
impl FastEmbedProvider {
/// Create a new `FastEmbed` provider with default settings
///
/// Uses BAAI/bge-small-en-v1.5 model by default (384 dimensions).
///
/// # Errors
///
/// Returns error if model initialization fails (e.g., download issues).
pub fn new() -> Result<Self> {
let model = fastembed::FlagEmbedding::try_new(Default::default()).map_err(|e| {
crate::error::KbError::Embedding(format!("Failed to initialize FastEmbed: {}", e))
})?;
Ok(Self {
dimensions: 384, // BAAI/bge-small-en-v1.5 dimensions
model,
})
}
/// Create with custom dimensions (for compatibility)
///
/// Note: Actual dimensions will be determined by the model,
/// this parameter is for API compatibility.
///
/// # Errors
///
/// Returns error if model initialization fails.
pub fn with_dimensions(dimensions: usize) -> Result<Self> {
let model = fastembed::FlagEmbedding::try_new(Default::default()).map_err(|e| {
crate::error::KbError::Embedding(format!("Failed to initialize FastEmbed: {}", e))
})?;
Ok(Self { model, dimensions })
}
}
#[cfg(feature = "fastembed")]
impl EmbeddingProvider for FastEmbedProvider {
fn embed(&self, text: &str) -> Result<Vec<f32>> {
let embeddings = self
.model
.embed(vec![text], None)
.map_err(|e| crate::error::KbError::Embedding(format!("Embedding error: {}", e)))?;
Ok(embeddings
.into_iter()
.next()
.ok_or_else(|| crate::error::KbError::Embedding("No embedding returned".to_string()))?)
}
fn dimensions(&self) -> usize {
self.dimensions
}
}
/// Fallback for when feature is not enabled
#[cfg(not(feature = "fastembed"))]
pub struct FastEmbedProvider {
dimensions: usize,
}
#[cfg(not(feature = "fastembed"))]
impl FastEmbedProvider {
/// Create a new `FastEmbed` provider (stub when feature not enabled)
///
/// # Errors
///
/// Always returns error since fastembed feature is not enabled
pub fn new() -> Result<Self> {
Err(crate::error::KbError::Embedding(
"fastembed feature not enabled. Enable with: cargo build --features fastembed"
.to_string(),
))
}
/// Create with custom dimensions (stub when feature not enabled)
#[must_use]
pub fn with_dimensions(dimensions: usize) -> Self {
Self { dimensions }
}
}
#[cfg(not(feature = "fastembed"))]
impl EmbeddingProvider for FastEmbedProvider {
fn embed(&self, _text: &str) -> Result<Vec<f32>> {
Err(crate::error::KbError::Embedding(
"fastembed feature not enabled".to_string(),
))
}
fn dimensions(&self) -> usize {
self.dimensions
}
}

View File

@ -1,20 +1,10 @@
//! Embedding generation for semantic search //! Embedding generation for semantic search using stratum-embeddings
use crate::error::Result; pub use stratum_embeddings::{
cosine_similarity, euclidean_distance, normalize_embedding, Embedding, EmbeddingError,
EmbeddingOptions, EmbeddingProvider, EmbeddingResult, FastEmbedProvider, MemoryCache,
ProviderInfo,
};
/// Embedding provider trait /// Re-export for backward compatibility
pub trait EmbeddingProvider: Send + Sync { pub type Result<T> = std::result::Result<T, EmbeddingError>;
/// Generate embeddings for text
///
/// # Errors
///
/// Returns an error if embedding generation fails
fn embed(&self, text: &str) -> Result<Vec<f32>>;
/// Get embedding dimensions
fn dimensions(&self) -> usize;
}
// Module stubs
pub mod fastembed;
pub mod rig;

View File

@ -1,109 +0,0 @@
//! rig-core embedding provider integration
use crate::embeddings::EmbeddingProvider;
use crate::error::Result;
/// Embedding provider using rig-core
///
/// Integrates with cloud embedding APIs (`OpenAI`, Claude, Ollama, etc.)
/// via the rig-core library for production semantic search.
///
/// Supports multiple embedding services via rig-core abstraction.
/// API key should be set in environment variables (e.g., `OPENAI_API_KEY`).
pub struct RigEmbeddingProvider {
#[allow(dead_code)]
model: String,
dimensions: usize,
}
impl RigEmbeddingProvider {
/// Create a new rig-core embedding provider
///
/// # Arguments
///
/// * `model` - Model identifier (e.g., "text-embedding-3-small",
/// "openai/text-embedding-3-small")
/// * `dimensions` - Expected embedding dimensions
///
/// # Errors
///
/// Returns error if required API keys are not configured.
#[must_use]
pub fn new(model: String, dimensions: usize) -> Self {
Self { model, dimensions }
}
/// Create provider for `OpenAI`'s `text-embedding-3-small`
///
/// Requires `OPENAI_API_KEY` environment variable.
#[must_use]
pub fn openai_small() -> Self {
Self {
model: "text-embedding-3-small".to_string(),
dimensions: 1536,
}
}
/// Create provider for `OpenAI`'s `text-embedding-3-large`
///
/// Requires `OPENAI_API_KEY` environment variable.
#[must_use]
pub fn openai_large() -> Self {
Self {
model: "text-embedding-3-large".to_string(),
dimensions: 3072,
}
}
/// Create provider for Ollama embeddings (local inference)
///
/// Requires Ollama running locally on default port 11434.
#[must_use]
pub fn ollama(model: String, dimensions: usize) -> Self {
Self { model, dimensions }
}
}
impl EmbeddingProvider for RigEmbeddingProvider {
fn embed(&self, _text: &str) -> Result<Vec<f32>> {
// Note: Full rig-core integration requires async context.
// For now, return error with helpful message about API keys.
// In production, this would use rig-core's embedding client.
// Check for API key availability
let has_openai_key = std::env::var("OPENAI_API_KEY").is_ok();
let has_anthropic_key = std::env::var("ANTHROPIC_API_KEY").is_ok();
if !has_openai_key && !has_anthropic_key {
return Err(crate::error::KbError::Embedding(
"No embedding service configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY"
.to_string(),
));
}
// Return placeholder embeddings with correct dimensions
// In actual implementation, this would call rig-core's embedding service
Ok(vec![0.0; self.dimensions])
}
fn dimensions(&self) -> usize {
self.dimensions
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_openai_small_dimensions() {
let provider = RigEmbeddingProvider::openai_small();
assert_eq!(provider.dimensions(), 1536);
}
#[test]
fn test_openai_large_dimensions() {
let provider = RigEmbeddingProvider::openai_large();
assert_eq!(provider.dimensions(), 3072);
}
}

View File

@ -14,7 +14,7 @@ use serde_yaml;
use crate::error::{KbError, Result}; use crate::error::{KbError, Result};
use crate::models::{Node, NodeStatus, NodeType}; use crate::models::{Node, NodeStatus, NodeType};
use crate::regex_patterns::{WIKILINK_PATTERN, CODE_REF_PATTERN}; use crate::regex_patterns::{CODE_REF_PATTERN, WIKILINK_PATTERN};
/// Extract frontmatter as a map of key-value strings /// Extract frontmatter as a map of key-value strings
/// ///

View File

@ -1,7 +1,8 @@
//! Lazy-compiled regex patterns for the KOGRAL knowledge base engine //! Lazy-compiled regex patterns for the KOGRAL knowledge base engine
//! //!
//! All regex patterns are compiled once at startup using `once_cell::sync::Lazy` //! All regex patterns are compiled once at startup using
//! to avoid recompilation overhead during normal operation. //! `once_cell::sync::Lazy` to avoid recompilation overhead during normal
//! operation.
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
@ -11,8 +12,7 @@ pub static WIKILINK_PATTERN: Lazy<Regex> =
Lazy::new(|| Regex::new(r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]").unwrap()); Lazy::new(|| Regex::new(r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]").unwrap());
/// Code reference pattern: `@path/to/file.rs:42` /// Code reference pattern: `@path/to/file.rs:42`
pub static CODE_REF_PATTERN: Lazy<Regex> = pub static CODE_REF_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"@([\w/.-]+:\d+)").unwrap());
Lazy::new(|| Regex::new(r"@([\w/.-]+:\d+)").unwrap());
/// Tag pattern: `#tagname` /// Tag pattern: `#tagname`
pub static TAG_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"#(\w+)").unwrap()); pub static TAG_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"#(\w+)").unwrap());

View File

@ -1,6 +1,6 @@
//! Integration tests for Nickel configuration loading //! Integration tests for Nickel configuration loading
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use kogral_core::config::nickel; use kogral_core::config::nickel;
use kogral_core::config::schema::{EmbeddingProvider, KbConfig, StorageType}; use kogral_core::config::schema::{EmbeddingProvider, KbConfig, StorageType};

View File

@ -29,7 +29,8 @@ impl AuthConfig {
/// Verify a request token against the configured token /// Verify a request token against the configured token
/// ///
/// # Arguments /// # Arguments
/// * `request_token` - Token provided in the request (can be from params or header) /// * `request_token` - Token provided in the request (can be from params or
/// header)
/// ///
/// # Returns /// # Returns
/// * Ok(()) if authentication succeeds /// * Ok(()) if authentication succeeds

View File

@ -7,7 +7,7 @@ use serde_json::{json, Value};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use crate::{ use crate::{
auth::{AuthConfig, extract_token_from_params}, auth::{extract_token_from_params, AuthConfig},
prompts, resources, tools, prompts, resources, tools,
types::{ types::{
JsonRpcRequest, JsonRpcResponse, PromptsCapability, ResourcesCapability, JsonRpcRequest, JsonRpcResponse, PromptsCapability, ResourcesCapability,
@ -31,7 +31,11 @@ impl McpServer {
} else { } else {
info!("MCP server running without authentication"); info!("MCP server running without authentication");
} }
Self { name, version, auth } Self {
name,
version,
auth,
}
} }
/// Run the MCP server with stdio transport /// Run the MCP server with stdio transport
@ -96,7 +100,8 @@ impl McpServer {
debug!("Handling method: {}", request.method); debug!("Handling method: {}", request.method);
// Verify authentication for protected methods // Verify authentication for protected methods
// Initialize is allowed without auth for initial setup, ping can be used for health checks // Initialize is allowed without auth for initial setup, ping can be used for
// health checks
if !matches!(request.method.as_str(), "initialize" | "ping") { if !matches!(request.method.as_str(), "initialize" | "ping") {
if let Err(e) = self.authenticate(&request.params) { if let Err(e) = self.authenticate(&request.params) {
debug!("Authentication failed: {}", e); debug!("Authentication failed: {}", e);

View File

@ -358,7 +358,12 @@ async fn tool_add_note(args: Value) -> Result<ToolResult> {
let mut node = Node::new(NodeType::Note, title); let mut node = Node::new(NodeType::Note, title);
node.content = content; node.content = content;
let tags = validate_string_array(args["tags"].as_array(), "tags", MAX_ARRAY_ITEMS, MAX_SHORT_LENGTH)?; let tags = validate_string_array(
args["tags"].as_array(),
"tags",
MAX_ARRAY_ITEMS,
MAX_SHORT_LENGTH,
)?;
node.tags = tags; node.tags = tags;
let relates_to = validate_string_array( let relates_to = validate_string_array(
@ -369,7 +374,9 @@ async fn tool_add_note(args: Value) -> Result<ToolResult> {
)?; )?;
node.relates_to = relates_to; node.relates_to = relates_to;
if let Some(project) = validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)? { if let Some(project) =
validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)?
{
node.project = Some(project); node.project = Some(project);
} }
@ -393,14 +400,17 @@ async fn tool_add_note(args: Value) -> Result<ToolResult> {
/// Add an ADR /// Add an ADR
async fn tool_add_decision(args: Value) -> Result<ToolResult> { async fn tool_add_decision(args: Value) -> Result<ToolResult> {
let title = validate_required_string(args["title"].as_str(), "title", MAX_STRING_LENGTH)?; let title = validate_required_string(args["title"].as_str(), "title", MAX_STRING_LENGTH)?;
let decision = validate_required_string(args["decision"].as_str(), "decision", MAX_STRING_LENGTH)?; let decision =
validate_required_string(args["decision"].as_str(), "decision", MAX_STRING_LENGTH)?;
let mut node = Node::new(NodeType::Decision, title); let mut node = Node::new(NodeType::Decision, title);
node.content = decision.clone(); node.content = decision.clone();
// Store decision-specific data in metadata // Store decision-specific data in metadata
let mut metadata = serde_json::Map::new(); let mut metadata = serde_json::Map::new();
if let Some(context) = validate_optional_string(args["context"].as_str(), "context", MAX_STRING_LENGTH)? { if let Some(context) =
validate_optional_string(args["context"].as_str(), "context", MAX_STRING_LENGTH)?
{
metadata.insert("context".to_string(), json!(context)); metadata.insert("context".to_string(), json!(context));
} }
metadata.insert("decision".to_string(), json!(decision)); metadata.insert("decision".to_string(), json!(decision));
@ -417,10 +427,17 @@ async fn tool_add_decision(args: Value) -> Result<ToolResult> {
node.metadata = metadata.into_iter().collect(); node.metadata = metadata.into_iter().collect();
let tags = validate_string_array(args["tags"].as_array(), "tags", MAX_ARRAY_ITEMS, MAX_SHORT_LENGTH)?; let tags = validate_string_array(
args["tags"].as_array(),
"tags",
MAX_ARRAY_ITEMS,
MAX_SHORT_LENGTH,
)?;
node.tags = tags; node.tags = tags;
if let Some(project) = validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)? { if let Some(project) =
validate_optional_string(args["project"].as_str(), "project", MAX_SHORT_LENGTH)?
{
node.project = Some(project); node.project = Some(project);
} }
@ -448,7 +465,14 @@ async fn tool_link(args: Value) -> Result<ToolResult> {
let relation_str = validate_enum( let relation_str = validate_enum(
args["relation"].as_str(), args["relation"].as_str(),
"relation", "relation",
&["relates_to", "depends_on", "implements", "extends", "supersedes", "explains"], &[
"relates_to",
"depends_on",
"implements",
"extends",
"supersedes",
"explains",
],
)?; )?;
let strength = validate_strength(args["strength"].as_f64())?; let strength = validate_strength(args["strength"].as_f64())?;
@ -479,8 +503,10 @@ async fn tool_link(args: Value) -> Result<ToolResult> {
/// Get guidelines /// Get guidelines
async fn tool_get_guidelines(args: Value) -> Result<ToolResult> { async fn tool_get_guidelines(args: Value) -> Result<ToolResult> {
let language = validate_optional_string(args["language"].as_str(), "language", MAX_SHORT_LENGTH)?; let language =
let category = validate_optional_string(args["category"].as_str(), "category", MAX_SHORT_LENGTH)?; validate_optional_string(args["language"].as_str(), "language", MAX_SHORT_LENGTH)?;
let category =
validate_optional_string(args["category"].as_str(), "category", MAX_SHORT_LENGTH)?;
let storage = FilesystemStorage::new(std::path::PathBuf::from(".kogral")); let storage = FilesystemStorage::new(std::path::PathBuf::from(".kogral"));
let graph = storage.load_graph("default").await?; let graph = storage.load_graph("default").await?;
@ -584,8 +610,16 @@ async fn tool_export(args: Value) -> Result<ToolResult> {
async fn tool_find_blocks(args: Value) -> Result<ToolResult> { async fn tool_find_blocks(args: Value) -> Result<ToolResult> {
let tag = validate_optional_string(args["tag"].as_str(), "tag", MAX_SHORT_LENGTH)?; let tag = validate_optional_string(args["tag"].as_str(), "tag", MAX_SHORT_LENGTH)?;
let status = validate_optional_string(args["status"].as_str(), "status", MAX_SHORT_LENGTH)?; let status = validate_optional_string(args["status"].as_str(), "status", MAX_SHORT_LENGTH)?;
let property_key = validate_optional_string(args["property_key"].as_str(), "property_key", MAX_SHORT_LENGTH)?; let property_key = validate_optional_string(
let property_value = validate_optional_string(args["property_value"].as_str(), "property_value", MAX_STRING_LENGTH)?; args["property_key"].as_str(),
"property_key",
MAX_SHORT_LENGTH,
)?;
let property_value = validate_optional_string(
args["property_value"].as_str(),
"property_value",
MAX_STRING_LENGTH,
)?;
let limit = validate_limit(args["limit"].as_u64(), 20)?; let limit = validate_limit(args["limit"].as_u64(), 20)?;
let storage = FilesystemStorage::new(std::path::PathBuf::from(".kogral")); let storage = FilesystemStorage::new(std::path::PathBuf::from(".kogral"));

View File

@ -1,6 +1,7 @@
//! Input validation for MCP tools //! Input validation for MCP tools
//! //!
//! Enforces safe limits on user inputs to prevent abuse and resource exhaustion. //! Enforces safe limits on user inputs to prevent abuse and resource
//! exhaustion.
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use serde_json::Value; use serde_json::Value;
@ -41,7 +42,11 @@ pub fn validate_required_string(
} }
/// Validates an optional string field /// Validates an optional string field
pub fn validate_optional_string(value: Option<&str>, field_name: &str, max_length: usize) -> Result<Option<String>> { pub fn validate_optional_string(
value: Option<&str>,
field_name: &str,
max_length: usize,
) -> Result<Option<String>> {
match value { match value {
Some(s) => { Some(s) => {
if s.is_empty() { if s.is_empty() {
@ -82,7 +87,10 @@ pub fn validate_limit(value: Option<u64>, default: usize) -> Result<usize> {
pub fn validate_strength(value: Option<f64>) -> Result<f32> { pub fn validate_strength(value: Option<f64>) -> Result<f32> {
let strength = value.unwrap_or(1.0); let strength = value.unwrap_or(1.0);
if !(0.0..=1.0).contains(&strength) { if !(0.0..=1.0).contains(&strength) {
return Err(anyhow!("Strength must be between 0 and 1, got {}", strength)); return Err(anyhow!(
"Strength must be between 0 and 1, got {}",
strength
));
} }
Ok(strength as f32) Ok(strength as f32)
} }