diff --git a/Cargo.lock b/Cargo.lock index ef08e4d..cdfbcfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -76,36 +76,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -206,9 +206,9 @@ checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed" [[package]] name = "bstr" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata", @@ -229,15 +229,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.2.2" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] @@ -248,12 +248,6 @@ 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 = "chrono" version = "0.4.38" @@ -313,15 +307,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -350,6 +344,7 @@ dependencies = [ "rattler_lock", "regex", "reqwest", + "rstest", "serde", "serde_json", "serde_yaml", @@ -369,16 +364,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -387,9 +372,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -487,17 +472,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -551,19 +525,19 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "file_url" @@ -573,7 +547,7 @@ checksum = "c2789b7b3e160530d89d1e126aff9811c3421bb77ebb9b62ffa3abbeba69f12d" dependencies = [ "itertools", "percent-encoding", - "thiserror 1.0.69", + "thiserror", "typed-path", "url", ] @@ -587,15 +561,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - [[package]] name = "fnv" version = "1.0.7" @@ -628,9 +593,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -643,9 +608,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -653,15 +618,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -670,31 +635,49 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -750,9 +733,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -760,7 +743,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -795,9 +778,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -805,6 +788,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -859,9 +848,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.5.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -914,9 +903,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -954,124 +943,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1080,23 +951,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -1112,12 +972,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.0", "serde", ] @@ -1144,17 +1004,16 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ - "once_cell", "wasm-bindgen", ] @@ -1189,9 +1048,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libredox" @@ -1209,12 +1068,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - [[package]] name = "lock_api" version = "0.4.12" @@ -1270,10 +1123,11 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", @@ -1291,7 +1145,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -1344,9 +1198,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags", "cfg-if", @@ -1376,9 +1230,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -1426,10 +1280,11 @@ dependencies = [ [[package]] name = "pep440_rs" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0922a442c78611fa8c5ed6065d2d898a820cf12fa90604217fdb2d01675efec7" +checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c" dependencies = [ + "once_cell", "serde", "unicode-width", "unscanny", @@ -1443,7 +1298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2feee999fa547bacab06a4881bacc74688858b92fa8ef1e206c748b0a76048" dependencies = [ "boxcar", - "indexmap 2.7.0", + "indexmap 2.6.0", "itertools", "once_cell", "pep440_rs", @@ -1451,7 +1306,7 @@ dependencies = [ "rustc-hash", "serde", "smallvec", - "thiserror 1.0.69", + "thiserror", "unicode-width", "url", "urlencoding", @@ -1510,9 +1365,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1549,7 +1404,7 @@ checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", - "float-cmp 0.9.0", + "float-cmp", "normalize-line-endings", "predicates-core", "regex", @@ -1571,6 +1426,15 @@ dependencies = [ "termtree", ] +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -1591,15 +1455,15 @@ dependencies = [ "phf", "serde", "smartstring", - "thiserror 1.0.69", + "thiserror", "unicase", ] [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", @@ -1608,38 +1472,34 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.3", + "thiserror", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "getrandom", "rand", "ring", "rustc-hash", "rustls", - "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror", "tinyvec", "tracing", - "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ - "cfg_aliases", "libc", "once_cell", "socket2", @@ -1698,7 +1558,7 @@ dependencies = [ "fxhash", "glob", "hex", - "indexmap 2.7.0", + "indexmap 2.6.0", "itertools", "lazy-regex", "nom", @@ -1716,7 +1576,7 @@ dependencies = [ "simd-json", "smallvec", "strum", - "thiserror 1.0.69", + "thiserror", "tracing", "typed-path", "url", @@ -1747,7 +1607,7 @@ dependencies = [ "chrono", "file_url", "fxhash", - "indexmap 2.7.0", + "indexmap 2.6.0", "itertools", "pep440_rs", "pep508_rs", @@ -1758,7 +1618,7 @@ dependencies = [ "serde_repr", "serde_with", "serde_yaml", - "thiserror 1.0.69", + "thiserror", "typed-path", "url", ] @@ -1799,7 +1659,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -1836,9 +1696,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1851,6 +1711,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "reqwest" version = "0.12.9" @@ -1913,6 +1779,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rstest" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1921,15 +1817,24 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", @@ -1940,9 +1845,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", @@ -1954,14 +1859,15 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", + "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework 3.0.1", + "security-framework", ] [[package]] @@ -1975,12 +1881,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" -dependencies = [ - "web-time", -] +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -1995,9 +1898,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" @@ -2007,18 +1910,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scc" -version = "2.2.5" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b202022bb57c049555430e11fc22fea12909276a80a4c3d368da36ac1d88ed" +checksum = "836f1e0f4963ef5288b539b643b35e043e76a32d0f4e47e67febf69576527f50" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ "windows-sys 0.59.0", ] @@ -2031,9 +1934,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "3.0.4" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" +checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc" [[package]] name = "security-framework" @@ -2042,34 +1945,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation 0.9.4", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] -name = "security-framework" -version = "3.0.1" +name = "security-framework-sys" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ - "bitflags", - "core-foundation 0.10.0", "core-foundation-sys", "libc", - "security-framework-sys", ] [[package]] -name = "security-framework-sys" -version = "2.12.1" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" @@ -2166,7 +2062,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -2192,7 +2088,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -2308,9 +2204,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2331,12 +2227,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -2390,29 +2280,18 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tempfile" -version = "3.14.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -2433,16 +2312,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl", ] [[package]] @@ -2456,22 +2326,11 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror-impl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "time" -version = "0.3.37" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -2490,24 +2349,14 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" version = "1.8.0" @@ -2613,7 +2462,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -2628,9 +2477,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2639,9 +2488,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", @@ -2650,9 +2499,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -2683,15 +2532,33 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.8.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-width" @@ -2719,9 +2586,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2735,18 +2602,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -2755,11 +2610,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "value-trait" -version = "0.10.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9170e001f458781e92711d2ad666110f153e4e50bfd5cbd02db6547625714187" +checksum = "bcaa56177466248ba59d693a048c0959ddb67f1151b963f904306312548cf392" dependencies = [ - "float-cmp 0.10.0", + "float-cmp", "halfbrown", "itoa", "ryu", @@ -2812,9 +2667,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", @@ -2823,9 +2678,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -2838,22 +2693,21 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2861,9 +2715,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -2874,25 +2728,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2900,9 +2744,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -3103,42 +2947,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.7.35" @@ -3160,51 +2968,8 @@ dependencies = [ "syn", ] -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 86995f1..4e09707 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,4 @@ log = "0.4.22" assert_cmd = "2.0.14" predicates = "3.1.0" serial_test = "3.2.0" +rstest = "0.23.0" diff --git a/src/check.rs b/src/check.rs new file mode 100644 index 0000000..cf775fd --- /dev/null +++ b/src/check.rs @@ -0,0 +1,79 @@ +use crate::{fetch_license_infos, license_info::LicenseInfo, CheckOutput, CondaDenyCheckConfig}; +use anyhow::{Context, Result}; +use colored::Colorize; +use log::debug; +use std::io::Write; + +fn check_license_infos(config: &CondaDenyCheckConfig) -> Result { + let license_infos = fetch_license_infos(config.lockfile_or_prefix.clone()) + .with_context(|| "Fetching license information failed.")?; + + if config.osi { + debug!("Checking licenses for OSI compliance"); + Ok(license_infos.osi_check()) + } else { + debug!("Checking licenses against specified whitelist"); + license_infos.check(config) + } +} + +pub fn check(check_config: CondaDenyCheckConfig, mut out: W) -> Result<()> { + let (safe_dependencies, unsafe_dependencies) = check_license_infos(&check_config)?; + + writeln!( + out, + "{}", + format_check_output( + safe_dependencies, + unsafe_dependencies.clone(), + ) + )?; + + if !unsafe_dependencies.is_empty() { + Err(anyhow::anyhow!("Unsafe licenses found")) + } else { + Ok(()) + } +} + +pub fn format_check_output( + safe_dependencies: Vec, + unsafe_dependencies: Vec, +) -> String { + let mut output = String::new(); + + if !unsafe_dependencies.is_empty() { + output.push_str( + format!( + "\n❌ {}:\n\n", + "The following dependencies are unsafe".red() + ) + .as_str(), + ); + for license_info in &unsafe_dependencies { + output.push_str(&license_info.pretty_print()) + } + } + + if unsafe_dependencies.is_empty() { + output.push_str(&format!( + "\n{}", + "✅ No unsafe licenses found! ✅".to_string().green() + )); + } else { + output.push_str(&format!( + "\n{}", + "❌ Unsafe licenses found! ❌".to_string().red() + )); + } + + output.push_str(&format!( + "\nThere were {} safe licenses and {} unsafe licenses.\n", + safe_dependencies.len().to_string().green(), + unsafe_dependencies.len().to_string().red() + )); + + output.push('\n'); + + output +} diff --git a/src/cli.rs b/src/cli.rs index 693e77a..f832149 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,10 @@ +use std::path::PathBuf; + use clap::ArgAction; use clap_verbosity_flag::{ErrorLevel, Verbosity}; use clap::Parser; +use rattler_conda_types::Platform; #[derive(Parser, Debug)] #[command(name = "conda-deny", about = "Check and list licenses of pixi and conda environments", version = env!("CARGO_PKG_VERSION"))] @@ -10,94 +13,130 @@ pub struct Cli { pub verbose: Verbosity, #[command(subcommand)] - pub command: Commands, + pub command: CondaDenyCliConfig, + /// Path to the conda-deny config file #[arg(short, long, global = true)] - pub config: Option, - - #[arg(long, global = true)] - pub prefix: Option>, - - #[arg(short, long)] - pub lockfile: Option>, - - #[arg(short, long)] - pub platform: Option>, - - #[arg(short, long)] - pub environment: Option>, + pub config: Option, } #[derive(clap::Subcommand, Debug)] -pub enum Commands { +pub enum CondaDenyCliConfig { Check { - #[arg(short, long, action = ArgAction::SetTrue)] - include_safe: bool, + /// Path to the pixi lockfile(s) + #[arg(short, long)] + lockfile: Option>, + /// Path to the conda prefix(es) + #[arg(long, global = true)] + prefix: Option>, + + /// Platform(s) to check + #[arg(short, long)] + platform: Option>, + + /// Pixi environment(s) to check + #[arg(short, long)] + environment: Option>, + + /// Check against OSI licenses instead of custom license whitelists. #[arg(short, long, action = ArgAction::SetTrue)] - osi: bool, + osi: Option, /// Ignore when encountering pypi packages instead of failing. #[arg(long, action = ArgAction::SetTrue)] - ignore_pypi: bool, + ignore_pypi: Option, + }, + List { + /// Path to the pixi lockfile(s) + #[arg(short, long)] + lockfile: Option>, + + /// Path to the conda prefix(es) + #[arg(long, global = true)] + prefix: Option>, + + /// Platform(s) to list + #[arg(short, long)] + platform: Option>, + + /// Pixi environment(s) to list + #[arg(short, long)] + environment: Option>, + + /// Ignore when encountering pypi packages instead of failing. + #[arg(long)] + ignore_pypi: Option, }, - List {}, } -#[cfg(test)] -mod tests { - use super::*; +impl CondaDenyCliConfig { + pub fn lockfile(&self) -> Option> { + match self { + CondaDenyCliConfig::Check { lockfile, .. } => lockfile.clone(), + CondaDenyCliConfig::List { lockfile, .. } => lockfile.clone(), + } + } - #[test] - fn test_cli_check_include_safe() { - let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--include-safe"]).unwrap(); - match cli.command { - Commands::Check { include_safe, .. } => { - assert!(include_safe); - } - _ => panic!("Expected check subcommand with --include-safe"), + pub fn prefix(&self) -> Option> { + match self { + CondaDenyCliConfig::Check { prefix, .. } => prefix.clone(), + CondaDenyCliConfig::List { prefix, .. } => prefix.clone(), } } + pub fn platform(&self) -> Option> { + match self { + CondaDenyCliConfig::Check { platform, .. } => platform.clone(), + CondaDenyCliConfig::List { platform, .. } => platform.clone(), + } + } + + pub fn environment(&self) -> Option> { + match self { + CondaDenyCliConfig::Check { environment, .. } => environment.clone(), + CondaDenyCliConfig::List { environment, .. } => environment.clone(), + } + } + + pub fn ignore_pypi(&self) -> Option { + match self { + CondaDenyCliConfig::Check { ignore_pypi, .. } => *ignore_pypi, + CondaDenyCliConfig::List { ignore_pypi, .. } => *ignore_pypi, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] fn test_cli_with_config() { let cli = Cli::try_parse_from(vec!["conda-deny", "list", "--config", "custom.toml"]).unwrap(); - assert_eq!(cli.config.as_deref(), Some("custom.toml")); + assert_eq!(cli.config, Some("custom.toml".into())); } #[test] fn test_cli_with_config_new_order() { let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--config", "custom.toml"]).unwrap(); - assert_eq!(cli.config.as_deref(), Some("custom.toml")); + assert_eq!(cli.config, Some("custom.toml".into())); match cli.command { - Commands::Check { include_safe, .. } => { - assert!(!include_safe); - } + CondaDenyCliConfig::Check { .. } => {} _ => panic!("Expected check subcommand with --config"), } } #[test] fn test_cli_with_check_arguments() { - let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--include-safe"]).unwrap(); - match cli.command { - Commands::Check { include_safe, .. } => { - assert!(include_safe); - } - _ => panic!("Expected check subcommand with --include-safe"), - } - } - - #[test] - fn test_cli_with_check_osi() { let cli = Cli::try_parse_from(vec!["conda-deny", "check", "--osi"]).unwrap(); match cli.command { - Commands::Check { osi, .. } => { - assert!(osi); + CondaDenyCliConfig::Check { osi, .. } => { + assert_eq!(osi, Some(true)); } - _ => panic!("Expected check subcommand with --osi"), + _ => panic!("Expected check subcommand with --include-safe"), } } } diff --git a/src/conda_deny_config.rs b/src/conda_deny_config.rs index 637c977..9ab2f5b 100644 --- a/src/conda_deny_config.rs +++ b/src/conda_deny_config.rs @@ -1,20 +1,22 @@ use anyhow::{Context, Result}; use log::debug; +use rattler_conda_types::Platform; use serde::Deserialize; +use std::path::PathBuf; use std::vec; use std::{fs::File, io::Read}; +use crate::license_whitelist::IgnorePackage; + #[derive(Debug, Deserialize)] -pub struct CondaDenyConfig { - tool: Tool, - #[serde(skip)] - pub path: String, +pub struct CondaDenyTomlConfig { + pub tool: Tool, } #[derive(Debug, Deserialize)] pub struct Tool { #[serde(rename = "conda-deny")] - conda_deny: CondaDeny, + pub conda_deny: CondaDeny, } #[derive(Debug, Deserialize, Clone)] @@ -27,8 +29,8 @@ pub enum LicenseWhitelist { #[derive(Debug, Deserialize, Clone)] #[serde(untagged)] pub enum PlatformSpec { - Single(String), - Multiple(Vec), + Single(Platform), + Multiple(Vec), } #[derive(Debug, Deserialize, Clone)] @@ -41,8 +43,8 @@ pub enum EnviromentSpec { #[derive(Debug, Deserialize, Clone)] #[serde(untagged)] pub enum LockfileSpec { - Single(String), - Multiple(Vec), + Single(PathBuf), + Multiple(Vec), } #[derive(Debug, Deserialize)] @@ -61,23 +63,27 @@ pub struct CondaDeny { environment_spec: Option, #[serde(rename = "lockfile")] lockfile_spec: Option, + #[serde(rename = "osi")] + osi: Option, + #[serde(rename = "ignore-pypi")] + ignore_pypi: Option, + #[serde(rename = "safe-licenses")] + pub safe_licenses: Option>, + #[serde(rename = "ignore-packages")] + pub ignore_packages: Option>, } -impl CondaDenyConfig { - pub fn from_path(filepath: &str) -> Result { - let mut file = - File::open(filepath).with_context(|| format!("Failed to open file: {}", filepath))?; +impl CondaDenyTomlConfig { + pub fn from_path(filepath: PathBuf) -> Result { + let mut file = File::open(filepath.clone())?; let mut contents = String::new(); - file.read_to_string(&mut contents) - .with_context(|| format!("Failed to read file contents: {}", filepath))?; - - let mut config: CondaDenyConfig = toml::from_str(&contents) - .with_context(|| format!("Failed to parse TOML from the file: {}", filepath))?; + file.read_to_string(&mut contents)?; - config.path = filepath.to_string(); + let config: CondaDenyTomlConfig = toml::from_str(&contents) + .with_context(|| format!("Failed to parse TOML from the file: {:?}", filepath))?; - debug!("Loaded config from file: {}", filepath); + debug!("Loaded config from file: {:?}", filepath.clone()); Ok(config) } @@ -86,16 +92,17 @@ impl CondaDenyConfig { if self.tool.conda_deny.license_whitelist.is_none() { Vec::::new() } else { - match &self.tool.conda_deny.license_whitelist.as_ref().unwrap() { - LicenseWhitelist::Single(path) => vec![path.clone()], - LicenseWhitelist::Multiple(path) => path.clone(), + match &self.tool.conda_deny.license_whitelist { + None => vec![], + Some(LicenseWhitelist::Single(path)) => vec![path.clone()], + Some(LicenseWhitelist::Multiple(path)) => path.clone(), } } } - pub fn get_platform_spec(&self) -> Option> { + pub fn get_platform_spec(&self) -> Option> { match &self.tool.conda_deny.platform_spec { - Some(PlatformSpec::Single(name)) => Some(vec![name.clone()]), + Some(PlatformSpec::Single(name)) => Some(vec![*name]), Some(PlatformSpec::Multiple(names)) => Some(names.clone()), None => None, } @@ -109,7 +116,7 @@ impl CondaDenyConfig { } } - pub fn get_lockfile_spec(&self) -> Vec { + pub fn get_lockfile_spec(&self) -> Vec { match &self.tool.conda_deny.lockfile_spec { Some(LockfileSpec::Single(name)) => vec![name.clone()], Some(LockfileSpec::Multiple(names)) => names.clone(), @@ -117,17 +124,28 @@ impl CondaDenyConfig { } } + pub fn get_osi(&self) -> Option { + self.tool.conda_deny.osi + } + + pub fn get_ignore_pypi(&self) -> Option { + self.tool.conda_deny.ignore_pypi + } + pub fn empty() -> Self { - CondaDenyConfig { + CondaDenyTomlConfig { tool: Tool { conda_deny: CondaDeny { license_whitelist: None, platform_spec: None, environment_spec: None, lockfile_spec: None, + osi: None, + ignore_pypi: None, + safe_licenses: None, + ignore_packages: None, }, }, - path: "".to_string(), } } } @@ -136,14 +154,19 @@ impl CondaDenyConfig { mod tests { use std::vec; + use rstest::{fixture, rstest}; + use super::*; - const TEST_FILES: &str = "tests/test_pyproject_toml_files/"; + #[fixture] + fn test_files() -> PathBuf { + PathBuf::from("tests/test_pyproject_toml_files/") + } - #[test] - fn test_valid_config_multiple_urls() { - let test_file_path = format!("{}valid_config_multiple_urls.toml", TEST_FILES); - let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config"); + #[rstest] + fn test_valid_config_multiple_urls(test_files: PathBuf) { + let test_file_path = test_files.join("valid_config_multiple_urls.toml"); + let config = CondaDenyTomlConfig::from_path(test_file_path).unwrap(); let license_config_paths = config.get_license_whitelists(); assert_eq!( @@ -155,10 +178,10 @@ mod tests { ); } - #[test] - fn test_missing_optional_fields() { - let test_file_path = format!("{}missing_optional_fields.toml", TEST_FILES); - let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config"); + #[rstest] + fn test_missing_optional_fields(test_files: PathBuf) { + let test_file_path = test_files.join("missing_optional_fields.toml"); + let config = CondaDenyTomlConfig::from_path(test_file_path).unwrap(); let license_config_paths = config.get_license_whitelists(); assert_eq!( @@ -167,26 +190,26 @@ mod tests { ); } - #[test] - fn test_invalid_toml() { - let test_file_path = format!("{}invalid.toml", TEST_FILES); - let result = CondaDenyConfig::from_path(&test_file_path); + #[rstest] + fn test_invalid_toml(test_files: PathBuf) { + let test_file_path = test_files.join("invalid.toml"); + let result = CondaDenyTomlConfig::from_path(test_file_path); assert!(result.is_err()); } - #[test] - fn test_get_license_config_paths() { - let test_file_path = format!("{}/valid_config_single_url.toml", TEST_FILES); - let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config"); + #[rstest] + fn test_get_license_config_paths(test_files: PathBuf) { + let test_file_path = test_files.join("valid_config_single_url.toml"); + let config = CondaDenyTomlConfig::from_path(test_file_path).unwrap(); assert_eq!( config.get_license_whitelists(), vec!["https://example.org/conda-deny/base_config.toml".to_string()] ); - let test_file_path = format!("{}/valid_config_multiple_urls.toml", TEST_FILES); - let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config"); + let test_file_path = test_files.join("valid_config_multiple_urls.toml"); + let config = CondaDenyTomlConfig::from_path(test_file_path).unwrap(); assert_eq!( config.get_license_whitelists(), diff --git a/src/conda_meta_entry.rs b/src/conda_meta_entry.rs index fdb7911..8f47fb4 100644 --- a/src/conda_meta_entry.rs +++ b/src/conda_meta_entry.rs @@ -4,7 +4,7 @@ use serde_json::Value; use std::fs; use std::fs::File; use std::io::Read; -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::license_info::LicenseState; @@ -68,7 +68,7 @@ pub struct CondaMetaEntries { } impl CondaMetaEntries { - pub fn from_dir(conda_meta_path: &str) -> Result { + pub fn from_dir(conda_meta_path: &PathBuf) -> Result { let mut conda_meta_entries: Vec = Vec::new(); for entry in fs::read_dir(conda_meta_path)? { @@ -99,12 +99,16 @@ impl CondaMetaEntries { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; #[test] pub fn test_non_json_in_conda_meta() { - let conda_meta_entries = - CondaMetaEntries::from_dir("tests/test_conda_metas/non-json-in-conda-meta").unwrap(); + let conda_meta_entries = CondaMetaEntries::from_dir( + &PathBuf::from_str("tests/test_conda_metas/non-json-in-conda-meta").unwrap(), + ) + .unwrap(); assert_eq!(conda_meta_entries.entries.len(), 1); assert_eq!(conda_meta_entries.entries[0].name, "xz"); assert_eq!(conda_meta_entries.entries[0].version, "5.2.6"); @@ -112,9 +116,10 @@ mod tests { #[test] pub fn test_non_existent_conda_meta() { - assert!( - CondaMetaEntries::from_dir("tests/test_conda_metas/non-existent-conda-meta").is_err() - ); + assert!(CondaMetaEntries::from_dir( + &PathBuf::from_str("tests/test_conda_metas/non-existent-conda-meta").unwrap() + ) + .is_err()); } #[test] @@ -131,7 +136,7 @@ mod tests { #[test] pub fn test_from_dir_nonexistent_dir() { - let entries = CondaMetaEntries::from_dir("non-existent-dir"); + let entries = CondaMetaEntries::from_dir(&PathBuf::from_str("non-existent-dir").unwrap()); assert!(entries.is_err()); assert!(entries .err() diff --git a/src/lib.rs b/src/lib.rs index f3ae9a5..ce06082 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,131 +1,245 @@ +pub mod check; pub mod cli; pub mod conda_deny_config; mod conda_meta_entry; mod conda_meta_package; -mod expression_utils; +pub mod expression_utils; mod license_info; pub mod license_whitelist; -mod list; +pub mod list; mod pixi_lock; -mod read_remote; -use colored::Colorize; +use std::{env, path::PathBuf}; + +use cli::CondaDenyCliConfig; +use conda_deny_config::CondaDenyTomlConfig; use license_info::LicenseInfo; -use license_whitelist::build_license_whitelist; +use license_whitelist::{get_license_information_from_toml_config, IgnorePackage}; use anyhow::{Context, Result}; use log::debug; +use rattler_conda_types::Platform; +use spdx::Expression; -use crate::conda_deny_config::CondaDenyConfig; use crate::license_info::LicenseInfos; -// todo: refactor this -pub type CliInput<'a> = ( - &'a CondaDenyConfig, - &'a Vec, - &'a Vec, - &'a Vec, - &'a Vec, - bool, - bool, -); -pub type CheckOutput = (Vec, Vec); +#[derive(Debug)] +pub enum CondaDenyConfig { + Check(CondaDenyCheckConfig), + List(CondaDenyListConfig), +} -pub fn fetch_license_infos(cli_input: CliInput) -> Result { - let (conda_deny_config, cli_lockfiles, cli_platforms, cli_environments, conda_prefixes, _, cli_ignore_pypi) = - cli_input; - - if conda_prefixes.is_empty() { - LicenseInfos::get_license_infos_from_config( - conda_deny_config, - cli_lockfiles, - cli_platforms, - cli_environments, - cli_ignore_pypi, - ) - .with_context(|| "Getting license information from config file failed.") - } else { - LicenseInfos::from_conda_prefixes(conda_prefixes) - .with_context(|| "Getting license information from conda prefixes failed.") - } +/// Configuration for the check command +#[derive(Debug)] +pub struct CondaDenyCheckConfig { + pub lockfile_or_prefix: LockfileOrPrefix, + pub osi: bool, + pub safe_licenses: Vec, + pub ignore_packages: Vec, } -pub fn list(cli_input: CliInput) -> Result<()> { - let license_infos = - fetch_license_infos(cli_input).with_context(|| "Fetching license information failed.")?; - license_infos.list(); - Ok(()) +/// Shared configuration between check and list commands +#[derive(Debug)] +pub struct CondaDenyListConfig { + pub lockfile_or_prefix: LockfileOrPrefix, } -pub fn check_license_infos(cli_input: CliInput) -> Result { - let (conda_deny_config, _, _, _, _, osi, _) = cli_input; +#[derive(Debug, Clone)] +pub struct LockfileSpec { + lockfiles: Vec, + platforms: Option>, + environments: Option>, + ignore_pypi: bool, +} - let license_infos = - fetch_license_infos(cli_input).with_context(|| "Fetching license information failed.")?; +#[derive(Debug, Clone)] +pub enum LockfileOrPrefix { + Lockfile(LockfileSpec), + Prefix(Vec), +} - if osi { - debug!("Checking licenses for OSI compliance"); - Ok(license_infos.osi_check()) - } else { - let license_whitelist = build_license_whitelist(conda_deny_config) - .with_context(|| "Building the license whitelist failed.")?; - debug!("Checking licenses against specified whitelist"); - Ok(license_infos.check(&license_whitelist)) +pub type CheckOutput = (Vec, Vec); + +pub fn fetch_license_infos(lockfile_or_prefix: LockfileOrPrefix) -> Result { + match lockfile_or_prefix { + LockfileOrPrefix::Lockfile(lockfile_spec) => { + LicenseInfos::from_pixi_lockfiles(lockfile_spec) + .with_context(|| "Getting license information from config file failed.") + } + LockfileOrPrefix::Prefix(prefixes) => LicenseInfos::from_conda_prefixes(&prefixes) + .with_context(|| "Getting license information from conda prefixes failed."), } } -pub fn format_check_output( - safe_dependencies: Vec, - unsafe_dependencies: Vec, - include_safe_dependencies: bool, -) -> String { - let mut output = String::new(); - - if include_safe_dependencies && !safe_dependencies.is_empty() { - output.push_str( - format!( - "\n✅ {}:\n\n", - "The following dependencies are safe".green() - ) - .as_str(), - ); - for license_info in &safe_dependencies { - output.push_str(&license_info.pretty_print(true)) +const IGNORE_PYPI_DEFAULT: bool = false; + +fn get_lockfile_or_prefix( + lockfile: Vec, + prefix: Vec, + platforms: Option>, + environments: Option>, + ignore_pypi: Option, +) -> Result { + if lockfile.is_empty() && prefix.is_empty() { + // test if pixi.lock exists next to config file, otherwise error + let default_lockfile_path = env::current_dir()?.join("pixi.lock"); + if !default_lockfile_path.is_file() { + Err(anyhow::anyhow!("No lockfiles or conda prefixes provided")) + } else { + Ok(LockfileOrPrefix::Lockfile(LockfileSpec { + lockfiles: vec![default_lockfile_path], + platforms, + environments, + ignore_pypi: ignore_pypi.unwrap_or(IGNORE_PYPI_DEFAULT), + })) + } + } else if !lockfile.is_empty() && !prefix.is_empty() { + Err(anyhow::anyhow!( + "Both lockfiles and conda prefixes provided. Please only provide either or." + )) + } else if !lockfile.is_empty() { + Ok(LockfileOrPrefix::Lockfile(LockfileSpec { + lockfiles: lockfile.iter().map(|s| s.into()).collect(), + platforms, + environments, + ignore_pypi: ignore_pypi.unwrap_or(IGNORE_PYPI_DEFAULT), + })) + } else { + assert!(!prefix.is_empty()); + + if platforms.is_some() { + Err(anyhow::anyhow!( + "Cannot specify platforms and conda prefixes at the same time" + )) + } else if environments.is_some() { + Err(anyhow::anyhow!( + "Cannot specify environments and conda prefixes at the same time" + )) + } else if ignore_pypi.is_some() { + Err(anyhow::anyhow!( + "Cannot specify ignore-pypi and conda prefixes at the same time" + )) + } else { + Ok(LockfileOrPrefix::Prefix( + prefix.iter().map(|s| s.into()).collect(), + )) } } +} - if !unsafe_dependencies.is_empty() { - output.push_str( - format!( - "\n❌ {}:\n\n", - "The following dependencies are unsafe".red() - ) - .as_str(), - ); - for license_info in &unsafe_dependencies { - output.push_str(&license_info.pretty_print(true)) +pub fn get_config_options( + config: Option, + cli_config: CondaDenyCliConfig, +) -> Result { + // if config provided, use config + // else, try to load pixi.toml, then pyproject.toml and if nothing helps, use empty config + let toml_config = if let Some(config_path) = config { + CondaDenyTomlConfig::from_path(config_path.clone()) + .with_context(|| format!("Failed to parse config file {:?}", config_path))? + } else { + match CondaDenyTomlConfig::from_path("pixi.toml".into()) + .with_context(|| "Failed to parse config file pixi.toml") + { + Ok(config) => { + debug!("Successfully loaded config from pixi.toml"); + config + } + Err(e) => { + debug!( + "Error parsing config file: pixi.toml: {}. Attempting to use pyproject.toml instead...", + e + ); + match CondaDenyTomlConfig::from_path("pyproject.toml".into()) + .context(e) + .with_context(|| "Failed to parse config file pyproject.toml") + { + Ok(config) => config, + Err(e) => { + debug!( + "Error parsing config file: pyproject.toml: {}. Using empty config instead...", + e + ); + CondaDenyTomlConfig::empty() + } + } + } } - } + }; - if unsafe_dependencies.is_empty() { - output.push_str(&format!( - "\n{}", - "✅ No unsafe licenses found! ✅".to_string().green() - )); + debug!("Parsed TOML config: {:?}", toml_config); + + // cli overrides toml configuration + let lockfile = cli_config + .lockfile() + .unwrap_or(toml_config.get_lockfile_spec()); + let prefix = cli_config.prefix().unwrap_or_default(); + + let platforms = if cli_config.platform().is_some() { + cli_config.platform() } else { - output.push_str(&format!( - "\n{}", - "❌ Unsafe licenses found! ❌".to_string().red() + toml_config.get_platform_spec() + }; + if platforms.is_some() && !prefix.is_empty() { + return Err(anyhow::anyhow!( + "Cannot specify platforms and conda prefixes at the same time" )); } - output.push_str(&format!( - "\nThere were {} safe licenses and {} unsafe licenses.\n", - safe_dependencies.len().to_string().green(), - unsafe_dependencies.len().to_string().red() - )); + let environments = if cli_config.environment().is_some() { + cli_config.environment() + } else { + toml_config.get_environment_spec() + }; + if environments.is_some() && !prefix.is_empty() { + return Err(anyhow::anyhow!( + "Cannot specify environments and conda prefixes at the same time" + )); + } - output.push('\n'); + let ignore_pypi = if cli_config.ignore_pypi().is_some() { + cli_config.ignore_pypi() + } else { + toml_config.get_ignore_pypi() + }; + + let lockfile_or_prefix = + get_lockfile_or_prefix(lockfile, prefix, platforms, environments, ignore_pypi)?; + + let config = match cli_config { + CondaDenyCliConfig::Check { + osi, .. + } => { + // defaults to false + let osi = if osi.is_some() { + osi + } else { + toml_config.get_osi() + } + .unwrap_or(false); + + let (safe_licenses, ignore_packages) = + get_license_information_from_toml_config(&toml_config)?; + if osi && !safe_licenses.is_empty() { + return Err(anyhow::anyhow!( + "Cannot use OSI mode and safe-licenses at the same time" + )); + } + + if !osi && safe_licenses.is_empty() { + return Err(anyhow::anyhow!("No license whitelist provided")); + } + + CondaDenyConfig::Check(CondaDenyCheckConfig { + lockfile_or_prefix, + osi, + safe_licenses, + ignore_packages, + }) + } + CondaDenyCliConfig::List { .. } => { + CondaDenyConfig::List(CondaDenyListConfig { lockfile_or_prefix }) + } + }; - output + Ok(config) } diff --git a/src/license_info.rs b/src/license_info.rs index 483507e..639ce59 100644 --- a/src/license_info.rs +++ b/src/license_info.rs @@ -1,29 +1,25 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; +use colored::Colorize; use rattler_conda_types::PackageRecord; use spdx::Expression; -use colored::*; + use crate::{ - conda_deny_config::CondaDenyConfig, conda_meta_entry::{CondaMetaEntries, CondaMetaEntry}, expression_utils::{check_expression_safety, extract_license_texts, parse_expression}, - license_whitelist::ParsedLicenseWhitelist, - list, + license_whitelist::is_package_ignored, pixi_lock::get_conda_packages_for_pixi_lock, - CheckOutput, + CheckOutput, CondaDenyCheckConfig, LockfileSpec, }; #[derive(Debug, Clone)] pub struct LicenseInfo { pub package_name: String, pub version: String, - #[allow(dead_code)] - pub timestamp: Option, pub license: LicenseState, - #[allow(dead_code)] pub platform: Option, pub build: String, } @@ -33,7 +29,6 @@ impl LicenseInfo { LicenseInfo { package_name: entry.name.clone(), version: entry.version.clone(), - timestamp: Some(entry.timestamp), license: entry.license.clone(), platform: Some(entry.platform.clone()), build: entry.build.clone(), @@ -53,14 +48,13 @@ impl LicenseInfo { LicenseInfo { package_name: package_record.name.as_source().to_string(), version: package_record.version.version().to_string(), - timestamp: None, license: license_for_package, platform: Some(package_record.subdir), build: package_record.build, } } - pub fn pretty_print(&self, colored: bool) -> String { + pub fn pretty_print(&self) -> String { let license_str = match &self.license { LicenseState::Valid(license) => license.to_string(), LicenseState::Invalid(license) => license.to_string(), @@ -70,31 +64,19 @@ impl LicenseInfo { LicenseState::Valid(_) => "", LicenseState::Invalid(_) => "(Non-SPDX)", }; - if colored { - format!( - "{} {}-{} ({}): {} {}\n", - &self.package_name.blue(), - &self.version.cyan(), - &self.build.bright_cyan().italic(), - &self - .platform - .as_ref() - .unwrap_or(&"Unknown".to_string()) - .bright_purple(), - license_str.yellow(), - recognized.bright_black(), - ) - } else { - format!( - "{} {}-{} ({}): {} {}\n", - &self.package_name, - &self.version, - &self.build.italic(), - &self.platform.as_ref().unwrap_or(&"Unknown".to_string()), - license_str, - recognized, - ) - } + format!( + "{} {}-{} ({}): {} {}\n", + &self.package_name.blue(), + &self.version.cyan(), + &self.build.bright_cyan().italic(), + &self + .platform + .as_ref() + .unwrap_or(&"Unknown".to_string()) + .bright_purple(), + license_str.yellow(), + recognized.bright_black(), + ) } } @@ -138,42 +120,28 @@ impl LicenseInfos { self.license_infos.dedup(); } - pub fn from_pixi_lockfiles( - lockfiles: Vec, - platforms: Vec, - environment_specs: Vec, - ignore_pypi: bool, - ) -> Result { + pub fn from_pixi_lockfiles(lockfile_spec: LockfileSpec) -> Result { let mut license_infos = Vec::new(); - let mut package_records = Vec::new(); - if lockfiles.is_empty() { + assert!(!lockfile_spec.lockfiles.is_empty()); + for lockfile in lockfile_spec.lockfiles { + let path: &Path = Path::new(&lockfile); let package_records_for_lockfile = get_conda_packages_for_pixi_lock( - None, - environment_specs.clone(), - platforms.clone(), - ignore_pypi, + path, + &lockfile_spec.environments, + &lockfile_spec.platforms, + lockfile_spec.ignore_pypi, ) - .with_context(|| "Failed to get package records for pixi.lock")?; - - package_records.extend(package_records_for_lockfile); - } else { - for lockfile in lockfiles { - let path = Path::new(&lockfile); - let package_records_for_lockfile = get_conda_packages_for_pixi_lock( - Some(path), - environment_specs.clone(), - platforms.clone(), - ignore_pypi, + .with_context(|| { + format!( + "Failed to get package records from lockfile: {:?}", + &lockfile.to_str() ) - .with_context(|| { - format!("Failed to get package records from lockfile: {}", &lockfile) - })?; - - package_records.extend(package_records_for_lockfile); - } + })?; + package_records.extend(package_records_for_lockfile); } + for package_record in package_records { let license_info = LicenseInfo::from_package_record(package_record); license_infos.push(license_info); @@ -185,15 +153,15 @@ impl LicenseInfos { Ok(LicenseInfos { license_infos }) } - pub fn from_conda_prefixes(conda_prefixes: &Vec) -> Result { + pub fn from_conda_prefixes(prefixes: &[PathBuf]) -> Result { let mut license_infos = Vec::new(); - - for conda_prefix in conda_prefixes { - let conda_meta_path = format!("{}/conda-meta", conda_prefix); + assert!(!prefixes.is_empty()); + for conda_prefix in prefixes { + let conda_meta_path = conda_prefix.join("conda-meta"); let conda_meta_entries = CondaMetaEntries::from_dir(&conda_meta_path).with_context(|| { format!( - "Failed to parse conda meta entries from conda-meta: {}", + "Failed to parse conda meta entries from conda-meta: {:?}", conda_meta_path ) })?; @@ -218,35 +186,19 @@ impl LicenseInfos { LicenseInfos { license_infos } } - pub fn get_license_infos_from_config( - config: &CondaDenyConfig, - cli_lockfiles: &[String], - cli_platforms: &[String], - cli_environments: &[String], - cli_ignore_pypi: bool, - ) -> Result { - let mut platforms = config.get_platform_spec().map_or(vec![], |p| p); - let mut lockfiles = config.get_lockfile_spec(); - let mut environment_specs = config.get_environment_spec().map_or(vec![], |e| e); - - platforms.extend(cli_platforms.to_owned()); - lockfiles.extend(cli_lockfiles.to_owned()); - environment_specs.extend(cli_environments.to_owned()); - - LicenseInfos::from_pixi_lockfiles(lockfiles, platforms, environment_specs, cli_ignore_pypi) - } - - pub fn check(&self, license_whitelist: &ParsedLicenseWhitelist) -> CheckOutput { + pub fn check(&self, config: &CondaDenyCheckConfig) -> Result { let mut safe_dependencies = Vec::new(); let mut unsafe_dependencies = Vec::new(); for license_info in &self.license_infos { match &license_info.license { LicenseState::Valid(license) => { - if check_expression_safety(license, &license_whitelist.safe_licenses) - || license_whitelist - .is_package_ignored(&license_info.package_name, &license_info.version) - .unwrap() + if check_expression_safety(license, &config.safe_licenses) + || is_package_ignored( + &config.ignore_packages, + &license_info.package_name, + &license_info.version, + )? { safe_dependencies.push(license_info.clone()); } else { @@ -254,10 +206,11 @@ impl LicenseInfos { } } LicenseState::Invalid(_) => { - if license_whitelist - .is_package_ignored(&license_info.package_name, &license_info.version) - .unwrap() - { + if is_package_ignored( + &config.ignore_packages, + &license_info.package_name, + &license_info.version, + )? { safe_dependencies.push(license_info.clone()); } else { unsafe_dependencies.push(license_info.clone()); @@ -266,7 +219,7 @@ impl LicenseInfos { } } - (safe_dependencies, unsafe_dependencies) + Ok((safe_dependencies, unsafe_dependencies)) } pub fn osi_check(&self) -> CheckOutput { @@ -299,11 +252,6 @@ impl LicenseInfos { (safe_dependencies, unsafe_dependencies) } - - pub fn list(&self) { - let output = list::list_license_infos(self, true); - println!("{}", output); - } } #[derive(Debug, Clone, PartialEq)] @@ -317,31 +265,8 @@ pub enum LicenseState { mod tests { use super::*; - use crate::conda_meta_entry::CondaMetaEntry; + use crate::{conda_meta_entry::CondaMetaEntry, LockfileOrPrefix}; use spdx::Expression; - - #[test] - fn test_license_info_creation() { - let license_info = LicenseInfo { - package_name: "test".to_string(), - version: "1.0".to_string(), - timestamp: Some(1234567890), - license: LicenseState::Invalid("Invalid-MIT".to_string()), - platform: Some("linux-64".to_string()), - build: "py_0".to_string(), - }; - - assert_eq!(license_info.package_name, "test"); - assert_eq!(license_info.version, "1.0"); - assert_eq!(license_info.timestamp, Some(1234567890)); - assert_eq!( - license_info.license, - LicenseState::Invalid("Invalid-MIT".to_string()) - ); - assert_eq!(license_info.platform, Some("linux-64".to_string())); - assert_eq!(license_info.build, "py_0".to_string()); - } - #[test] fn test_license_info_from_conda_meta_entry() { let entry = CondaMetaEntry { @@ -358,7 +283,6 @@ mod tests { assert_eq!(license_info.package_name, "test"); assert_eq!(license_info.version, "1.0"); - assert_eq!(license_info.timestamp, Some(1234567890)); assert_eq!( license_info.license, super::LicenseState::Invalid("Invalid-MIT".to_string()) @@ -373,7 +297,6 @@ mod tests { let unsafe_license_info = LicenseInfo { package_name: "test".to_string(), version: "0.1.0".to_string(), - timestamp: Some(1234567890), license: LicenseState::Invalid("Invalid-MIT".to_string()), platform: Some("linux-64".to_string()), build: "py_0".to_string(), @@ -381,7 +304,6 @@ mod tests { let safe_license_info = LicenseInfo { package_name: "test".to_string(), version: "0.1.0".to_string(), - timestamp: Some(1234567890), license: LicenseState::Valid(Expression::parse("MIT").unwrap()), platform: Some("linux-64".to_string()), build: "py_0".to_string(), @@ -395,18 +317,26 @@ mod tests { license_infos: vec![safe_license_info.clone(), safe_license_info.clone()], }; - let license_whitelist = ParsedLicenseWhitelist { - safe_licenses: vec![Expression::parse("MIT").unwrap()], - ignore_packages: vec![], + let safe_licenses = vec![Expression::parse("MIT").unwrap()]; + let ignore_packages = vec![]; + + let config = CondaDenyCheckConfig { + lockfile_or_prefix: LockfileOrPrefix::Lockfile(LockfileSpec { + lockfiles: vec!["pixi.lock".into()], + platforms: None, + environments: None, + ignore_pypi: false, + }), + osi: false, + safe_licenses, + ignore_packages, }; - let (_safe_dependencies, _unsafe_dependencies) = - unsafe_license_infos.check(&license_whitelist); - assert!(!_unsafe_dependencies.is_empty()); + let (_, unsafe_dependencies) = unsafe_license_infos.check(&config).unwrap(); + assert!(!unsafe_dependencies.is_empty()); - let (_safe_dependencies, _unsafe_dependencies) = - safe_license_infos.check(&license_whitelist); - assert!(_unsafe_dependencies.is_empty()); + let (_, unsafe_dependencies) = safe_license_infos.check(&config).unwrap(); + assert!(unsafe_dependencies.is_empty()); } #[test] @@ -414,7 +344,6 @@ mod tests { let license_info1 = LicenseInfo { package_name: "test".to_string(), version: "0.1.0".to_string(), - timestamp: Some(1234567890), license: LicenseState::Invalid("Invalid-MIT".to_string()), platform: Some("linux-64".to_string()), build: "py_0".to_string(), @@ -422,7 +351,6 @@ mod tests { let license_info2 = LicenseInfo { package_name: "test2".to_string(), version: "0.1.0".to_string(), - timestamp: Some(1234567890), license: LicenseState::Invalid("Invalid-MIT".to_string()), platform: Some("linux-64".to_string()), build: "py_0".to_string(), @@ -443,7 +371,6 @@ mod tests { let license_info1 = LicenseInfo { package_name: "test".to_string(), version: "0.1.0".to_string(), - timestamp: Some(1234567890), license: LicenseState::Invalid("Invalid-MIT".to_string()), platform: Some("linux-64".to_string()), build: "py_0".to_string(), @@ -451,7 +378,6 @@ mod tests { let license_info2 = LicenseInfo { package_name: "test".to_string(), version: "0.1.0".to_string(), - timestamp: Some(1234567890), license: LicenseState::Invalid("Invalid-MIT".to_string()), platform: Some("linux-64".to_string()), build: "py_0".to_string(), @@ -465,15 +391,4 @@ mod tests { assert_eq!(license_infos.license_infos.len(), 1); } - - #[test] - fn test_license_infos_from_config() { - let test_file_path = format!( - "{}test_config_for_license_infos.toml", - "tests/test_pyproject_toml_files/" - ); - let config = CondaDenyConfig::from_path(&test_file_path).expect("Failed to read config"); - let license_infos = LicenseInfos::get_license_infos_from_config(&config, &[], &[], &[], false); - assert_eq!(license_infos.unwrap().license_infos.len(), 396); - } } diff --git a/src/license_whitelist.rs b/src/license_whitelist.rs index def92bd..77161ba 100644 --- a/src/license_whitelist.rs +++ b/src/license_whitelist.rs @@ -8,7 +8,7 @@ use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, AUTHORIZATION}; use serde::Deserialize; use spdx::Expression; -use crate::{conda_deny_config::CondaDenyConfig, expression_utils::parse_expression}; +use crate::{conda_deny_config::CondaDenyTomlConfig, expression_utils::parse_expression}; #[derive(Debug, Deserialize)] pub struct LicenseWhitelistConfig { @@ -21,13 +21,7 @@ struct RemoteWhitelistTool { conda_deny: LicenseWhitelist, } -#[derive(Debug)] -pub struct ParsedLicenseWhitelist { - pub safe_licenses: Vec, - pub ignore_packages: Vec, -} - -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct IgnorePackage { package: String, version: Option, @@ -41,90 +35,80 @@ struct LicenseWhitelist { ignore_packages: Option>, } -impl ParsedLicenseWhitelist { - pub fn from_toml_file(toml_file: &str) -> Result { - let config_content = fs::read_to_string(toml_file) - .with_context(|| format!("Failed to read TOML file: {}", toml_file))?; - - let config: LicenseWhitelistConfig = toml::from_str(&config_content) - .with_context(|| format!("Failed to parse TOML content from file: {}", toml_file))?; - - let mut expressions = Vec::new(); - - if let Some(safe_licenses) = config.tool.conda_deny.safe_licenses { - for license in safe_licenses { - let expr = parse_expression(&license) - .with_context(|| format!("Failed to parse license expression: {}", license))?; - expressions.push(expr); - } - } - - let ignore_packages = config.tool.conda_deny.ignore_packages.unwrap_or_default(); - - Ok(ParsedLicenseWhitelist { - safe_licenses: expressions, - ignore_packages, - }) - } - - pub fn is_package_ignored(&self, package_name: &str, package_version: &str) -> Result { - let parsed_package_version = Version::from_str(package_version).with_context(|| { - format!( - "Error parsing package version: {} for package: {}", - package_version, package_name - ) - })?; - - for ignore_package in &self.ignore_packages { - if ignore_package.package == package_name { - match &ignore_package.version { - Some(version_req_str) => { - let version_req = - VersionSpec::from_str(version_req_str, ParseStrictness::Strict) - .with_context(|| { - format!( - "Error parsing version requirement: {} for package: {}", - version_req_str, package_name - ) - })?; - - if version_req.matches(&parsed_package_version) { - return Ok(true); - } - } - None => { +pub fn is_package_ignored( + ignore_packages: &Vec, + package_name: &str, + package_version: &str, +) -> Result { + let parsed_package_version = Version::from_str(package_version).with_context(|| { + format!( + "Error parsing package version: {} for package: {}", + package_version, package_name + ) + })?; + + for ignore_package in ignore_packages { + if ignore_package.package == package_name { + match &ignore_package.version { + Some(version_req_str) => { + let version_req = + VersionSpec::from_str(version_req_str, ParseStrictness::Strict) + .with_context(|| { + format!( + "Error parsing version requirement: {} for package: {}", + version_req_str, package_name + ) + })?; + + if version_req.matches(&parsed_package_version) { return Ok(true); } } + None => { + return Ok(true); + } } } - - // If no matches were found, the package is not ignored - Ok(false) } - pub fn empty() -> ParsedLicenseWhitelist { - ParsedLicenseWhitelist { - safe_licenses: Vec::new(), - ignore_packages: Vec::new(), + + // If no matches were found, the package is not ignored + Ok(false) +} + +pub fn license_config_from_toml_str( + toml_file: &str, +) -> Result<(Vec, Vec)> { + let config_content = fs::read_to_string(toml_file) + .with_context(|| format!("Failed to read TOML file: {}", toml_file))?; + + let config: LicenseWhitelistConfig = toml::from_str(&config_content) + .with_context(|| format!("Failed to parse TOML content from file: {}", toml_file))?; + + let mut expressions = Vec::new(); + + if let Some(safe_licenses) = config.tool.conda_deny.safe_licenses { + for license in safe_licenses { + let expr = parse_expression(&license) + .with_context(|| format!("Failed to parse license expression: {}", license))?; + expressions.push(expr); } } - pub fn extend(&mut self, other: ParsedLicenseWhitelist) { - self.safe_licenses.extend(other.safe_licenses); - self.ignore_packages.extend(other.ignore_packages); - } + let ignore_packages = config.tool.conda_deny.ignore_packages.unwrap_or_default(); + + Ok((expressions, ignore_packages)) } #[async_trait] pub trait ReadRemoteConfig { - async fn read(&self, url: &str) -> Result; + async fn read(&self, url: &str) -> Result; } pub struct RealRemoteConfigReader; #[async_trait] impl ReadRemoteConfig for RealRemoteConfigReader { - async fn read(&self, url: &str) -> Result { + async fn read(&self, url: &str) -> Result { let client = reqwest::Client::new(); // Add GitHub specific headers if GITHUB_TOKEN exists @@ -141,87 +125,66 @@ impl ReadRemoteConfig for RealRemoteConfigReader { ); } - let remote_config = client + let result = client .get(url) .headers(headers) .send() .await? + .error_for_status()? .text() .await?; - - let value: LicenseWhitelistConfig = toml::from_str(&remote_config).with_context(|| { - format!( - "Failed to parse license whitelist to TOML for whitelist URL: {}", - url - ) - })?; - - Ok(value) + Ok(result) } } pub fn fetch_safe_licenses( remote_config: &str, reader: &dyn ReadRemoteConfig, -) -> Result { +) -> Result<(Vec, Vec)> { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() - .build() - .unwrap(); + .build()?; let url = remote_config; let read_config_task = reader.read(url); + let config_str = runtime.block_on(read_config_task).map_err(|e| { + anyhow::anyhow!( + "Failed to read remote license whitelist.\nPlease check the URL. If you need a GITHUB_TOKEN, please set it in your environment.\nError: {}", + e + ) + })?; - match runtime.block_on(read_config_task) { - Ok(config) => { - let mut expressions = Vec::new(); - if config.tool.conda_deny.safe_licenses.is_some() { - for license in config.tool.conda_deny.safe_licenses.unwrap() { - let expr = parse_expression(&license).with_context(|| { - format!("Failed to parse license expression: {}", license) - })?; - expressions.push(expr); - } - } - let mut ignore_packages = Vec::new(); - if config.tool.conda_deny.ignore_packages.is_some() { - ignore_packages = config.tool.conda_deny.ignore_packages.unwrap(); - } - - Ok(ParsedLicenseWhitelist { - safe_licenses: expressions, - ignore_packages, - }) - } - Err(_) => Err(anyhow::anyhow!("Failed to read remote license whitelist.\nPlease check the URL. If you need a GITHUB_TOKEN, please set it in your environment.")), + let config: LicenseWhitelistConfig = toml::from_str(&config_str).with_context(|| { + format!( + "Failed to parse license whitelist to TOML for whitelist URL: {}", + url + ) + })?; + let mut expressions = Vec::new(); + for license in config.tool.conda_deny.safe_licenses.unwrap_or_default() { + let expr = parse_expression(&license) + .with_context(|| format!("Failed to parse license expression: {}", license))?; + expressions.push(expr); } + let ignore_packages = config.tool.conda_deny.ignore_packages.unwrap_or_default(); + Ok((expressions, ignore_packages)) } pub fn build_license_whitelist( - conda_deny_config: &CondaDenyConfig, -) -> Result { - debug!( - "Building license whitelist from config: {}", - conda_deny_config.path - ); - let mut final_license_whitelist = ParsedLicenseWhitelist::empty(); - - // Getting the license whitelist from the config file - let license_whitelist = ParsedLicenseWhitelist::from_toml_file(&conda_deny_config.path) - .with_context(|| { - format!( - "Failed to read the TOML file at path: {}", - conda_deny_config.path - ) - })?; - - final_license_whitelist.extend(license_whitelist); - - for license_whitelist_path in conda_deny_config.get_license_whitelists() { + license_whitelist: &[String], +) -> Result<(Vec, Vec)> { + let mut all_safe_licenses = Vec::new(); + let mut all_ignore_packages = Vec::new(); + + for license_whitelist_path in license_whitelist.iter() { + // todo: use Url (or Path) if license_whitelist_path.starts_with("http") { let reader = RealRemoteConfigReader; - match fetch_safe_licenses(&license_whitelist_path, &reader) { - Ok(safe_licenses_for_url) => final_license_whitelist.extend(safe_licenses_for_url), + match fetch_safe_licenses(license_whitelist_path, &reader) { + Ok((safe_licenses, ignore_packages)) => { + all_safe_licenses.extend(safe_licenses); + all_ignore_packages.extend(ignore_packages); + } Err(e) => { return Err(e).with_context(|| { format!( @@ -232,8 +195,11 @@ pub fn build_license_whitelist( } } } else { - match ParsedLicenseWhitelist::from_toml_file(&license_whitelist_path) { - Ok(license_whitelist) => final_license_whitelist.extend(license_whitelist), + match license_config_from_toml_str(license_whitelist_path) { + Ok((safe_licenses, ignore_packages)) => { + all_safe_licenses.extend(safe_licenses); + all_ignore_packages.extend(ignore_packages); + } Err(e) => { return Err(e).with_context(|| { format!( @@ -246,89 +212,95 @@ pub fn build_license_whitelist( } } - if final_license_whitelist.safe_licenses.is_empty() { - anyhow::bail!("Your license whitelist is empty.\nIf you want to use the OSI license whitelist, use the --osi flag."); - } else { - debug!("License whitelist built successfully."); - } - Ok(final_license_whitelist) + debug!("License whitelist built successfully."); + Ok((all_safe_licenses, all_ignore_packages)) +} + +pub fn get_license_information_from_toml_config( + toml_config: &CondaDenyTomlConfig, +) -> Result<(Vec, Vec)> { + let safe_licenses_from_toml = toml_config + .tool + .conda_deny + .safe_licenses + .clone() + .unwrap_or_default(); + let ignore_packages_from_toml = toml_config + .tool + .conda_deny + .ignore_packages + .clone() + .unwrap_or_default(); + + let license_whitelist_urls = toml_config.get_license_whitelists().clone(); + let (safe_licenses, ignore_packages) = build_license_whitelist(&license_whitelist_urls)?; + let safe_licenses = safe_licenses_from_toml + .iter() + .map(|license_str| parse_expression(license_str)) + .collect::>>()? + .into_iter() + .chain(safe_licenses) + .collect::>(); + let ignore_packages = ignore_packages_from_toml + .iter() + .cloned() + .chain(ignore_packages) + .collect::>(); + Ok((safe_licenses, ignore_packages)) } #[cfg(test)] mod tests { + use crate::conda_deny_config::CondaDenyTomlConfig; + use super::*; - use async_trait::async_trait; use std::error::Error; - struct MockReader; - - #[async_trait] - impl ReadRemoteConfig for MockReader { - async fn read(&self, _url: &str) -> Result { - Ok(LicenseWhitelistConfig { - tool: RemoteWhitelistTool { - conda_deny: LicenseWhitelist { - safe_licenses: Some(vec!["MIT".to_string(), "Apache-2.0".to_string()]), - ignore_packages: Some(vec![]), - }, - }, - }) - } - } - #[test] fn test_fetch_safe_licenses_success() { - let reader = MockReader; - // TODO: Change this to "https://raw.githubusercontent.com/QuantCo/conda-deny/main/tests/test_remote_base_configs/conda-deny-license_whitelist.toml" - let result = fetch_safe_licenses("https://raw.githubusercontent.com/PaulKMueller/conda-deny-test/main/conda-deny-license_whitelist.toml", &reader) + let reader = RealRemoteConfigReader; + let (safe_licenses, ignore_packages) = fetch_safe_licenses("https://raw.githubusercontent.com/quantco/conda-deny/main/tests/test_remote_base_configs/conda-deny-license_whitelist.toml", &reader) .unwrap(); // Assert the result - assert_eq!(result.safe_licenses.len(), 2); - assert!(result.safe_licenses.iter().any(|e| e.to_string() == "MIT")); - assert!(result - .safe_licenses - .iter() - .any(|e| e.to_string() == "Apache-2.0")); + assert_eq!(safe_licenses.len(), 4); + assert!(safe_licenses.iter().any(|e| e.to_string() == "MIT")); + assert!(safe_licenses.iter().any(|e| e.to_string() == "Apache-2.0")); + assert_eq!(ignore_packages.len(), 1); } #[test] fn test_valid_remote_base_config() { - let parsed_config = super::ParsedLicenseWhitelist::from_toml_file( - "tests/test_remote_base_configs/valid_config.toml", - ) - .unwrap(); - assert_eq!(parsed_config.safe_licenses.len(), 2); - assert_eq!(parsed_config.ignore_packages.len(), 1); + let (safe_licenses, ignored_packages) = + license_config_from_toml_str("tests/test_remote_base_configs/valid_config.toml") + .unwrap(); + assert_eq!(safe_licenses.len(), 2); + assert_eq!(ignored_packages.len(), 1); } #[test] fn test_invalid_remote_base_config() { - let parsed_config = super::ParsedLicenseWhitelist::from_toml_file( - "tests/test_remote_base_configs/invalid_config.toml", - ); - assert!(parsed_config.is_err()); + let result = + license_config_from_toml_str("tests/test_remote_base_configs/invalid_config.toml"); + assert!(result.is_err()); } #[test] fn test_different_versions_in_remote_base_config() { - let parsed_config = super::ParsedLicenseWhitelist::from_toml_file( + let (safe_licenses, ignored_packages) = license_config_from_toml_str( "tests/test_remote_base_configs/version_test_config.toml", ) .unwrap(); - assert_eq!(parsed_config.safe_licenses.len(), 2); - assert_eq!(parsed_config.ignore_packages.len(), 3); + assert_eq!(safe_licenses.len(), 2); + assert_eq!(ignored_packages.len(), 3); - assert!(parsed_config - .ignore_packages + assert!(ignored_packages .iter() .any(|x| x.package == "package1" && x.version == Some("=4.2.1".to_string()))); - assert!(parsed_config - .ignore_packages + assert!(ignored_packages .iter() .any(|x| x.package == "package2" && x.version == Some("<=4.2.1".to_string()))); - assert!(parsed_config - .ignore_packages + assert!(ignored_packages .iter() .any(|x| x.package == "package3" && x.version == Some(">4.2.1".to_string()))); } @@ -347,38 +319,13 @@ mod tests { #[test] fn test_is_package_ignored() { - let parsed_config = super::ParsedLicenseWhitelist::from_toml_file( + let (_, ignored_packages) = license_config_from_toml_str( "tests/test_remote_base_configs/version_test_config.toml", ) .unwrap(); - assert!(parsed_config - .is_package_ignored("package1", "4.2.1") - .unwrap()); - assert!(!parsed_config - .is_package_ignored("package1", "4.3.0") - .unwrap()); - assert!(!parsed_config - .is_package_ignored("package1", "4.3.2") - .unwrap()); - } - - #[test] - fn test_empty_parsed_license_whitelist() { - let empty = super::ParsedLicenseWhitelist::empty(); - assert_eq!(empty.safe_licenses.len(), 0); - assert_eq!(empty.ignore_packages.len(), 0); - } - - #[test] - fn test_extend_parsed_license_whitelist() { - let mut empty = super::ParsedLicenseWhitelist::empty(); - let parsed_config = super::ParsedLicenseWhitelist::from_toml_file( - "tests/test_remote_base_configs/version_test_config.toml", - ) - .unwrap(); - empty.extend(parsed_config); - assert_eq!(empty.safe_licenses.len(), 2); - assert_eq!(empty.ignore_packages.len(), 3); + assert!(is_package_ignored(&ignored_packages, "package1", "4.2.1").unwrap()); + assert!(!is_package_ignored(&ignored_packages, "package1", "4.3.0").unwrap()); + assert!(!is_package_ignored(&ignored_packages, "package1", "4.3.2").unwrap()); } // Mock the read_remote_config function @@ -397,12 +344,15 @@ mod tests { #[test] fn test_get_safe_licenses_local() { - let conda_deny_config = - CondaDenyConfig::from_path("tests/test_remote_base_configs/valid_config.toml").unwrap(); - let safe_licenses_whitelist = super::build_license_whitelist(&conda_deny_config).unwrap(); - assert_eq!(safe_licenses_whitelist.safe_licenses.len(), 5); + let toml_config = CondaDenyTomlConfig::from_path( + "tests/test_remote_base_configs/valid_config.toml".into(), + ) + .unwrap(); + let (safe_licenses, ignored_packages) = + get_license_information_from_toml_config(&toml_config).unwrap(); + assert_eq!(safe_licenses.len(), 5); assert_eq!( - safe_licenses_whitelist.safe_licenses, + safe_licenses, vec![ parse_expression("MIT").unwrap(), parse_expression("PSF-2.0").unwrap(), @@ -411,7 +361,7 @@ mod tests { parse_expression("WTFPL").unwrap() ] ); - assert_eq!(safe_licenses_whitelist.ignore_packages.len(), 2); - assert_eq!(safe_licenses_whitelist.ignore_packages[0].version, None); + assert_eq!(ignored_packages.len(), 2); + assert_eq!(ignored_packages[0].version, None); } } diff --git a/src/list.rs b/src/list.rs index 870e225..88803ce 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,10 +1,15 @@ -use crate::license_info::LicenseInfos; +use std::io::Write; -pub fn list_license_infos(license_infos: &LicenseInfos, colored: bool) -> String { - let mut output = String::new(); +use crate::{fetch_license_infos, CondaDenyListConfig}; +use anyhow::{Context, Result}; +pub fn list(config: CondaDenyListConfig, mut out: W) -> Result<()> { + let license_infos = fetch_license_infos(config.lockfile_or_prefix.clone()) + .with_context(|| "Fetching license information failed.")?; + let mut output = String::new(); for license_info in &license_infos.license_infos { - output.push_str(&license_info.pretty_print(colored)); + output.push_str(&license_info.pretty_print()); } - output + out.write_all(output.as_bytes())?; + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 757b073..36ea043 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ -use anyhow::{Context, Result}; +use std::io; + +use anyhow::Result; use clap::Parser; -use colored::Colorize; -use conda_deny::cli::{Cli, Commands}; -use conda_deny::conda_deny_config::CondaDenyConfig; -use conda_deny::{check_license_infos, format_check_output, list}; -use log::debug; +use conda_deny::check::check; +use conda_deny::cli::Cli; +use conda_deny::get_config_options; +use conda_deny::list::list; +use conda_deny::CondaDenyConfig; +use log::{debug, info}; fn main() -> Result<()> { let cli = Cli::parse(); @@ -13,99 +16,16 @@ fn main() -> Result<()> { .filter_level(cli.verbose.log_level_filter()) .init(); - let osi = match cli.command { - Commands::Check { osi, .. } => osi, - _ => false, - }; - - // todo: add an end-to-end test for this - let ignore_pypi = match cli.command { - Commands::Check { ignore_pypi, .. } => ignore_pypi, - _ => false, - }; - - let mut config = CondaDenyConfig::empty(); - - if !osi { - config = if let Some(config_path) = cli.config { - CondaDenyConfig::from_path(config_path.as_str()) - .with_context(|| format!("Failed to parse config file {}", config_path))? - } else { - match CondaDenyConfig::from_path("pixi.toml") - .with_context(|| "Failed to parse config file pixi.toml") - { - Ok(config) => { - debug!("Successfully loaded config from pixi.toml"); - config - } - Err(e) => { - debug!( - "Error parsing config file: pixi.toml: {}. Attempting to use pyproject.toml instead...", - e - ); - CondaDenyConfig::from_path("pyproject.toml") - .context(e) - .with_context(|| "Failed to parse config file pyproject.toml")? - } - } - }; - } else { - debug!("Skipping config file parsing for OSI compliance check. Your {} section will be ignored.", "[tool.conda-deny]".yellow()); - } - - let conda_prefixes = cli.prefix.unwrap_or_default(); - let cli_lockfiles = cli.lockfile.unwrap_or_default(); - let cli_platforms = cli.platform.unwrap_or_default(); - let cli_environments = cli.environment.unwrap_or_default(); - - let cli_input = ( - &config, - &cli_lockfiles, - &cli_platforms, - &cli_environments, - &conda_prefixes, - osi, - ignore_pypi, - ); - - debug!("CLI input for platforms: {:?}", cli_platforms); - debug!("CLI input for environments: {:?}", cli_environments); - debug!("CLI input for conda prefixes: {:?}", conda_prefixes); - let mut locks_to_check = cli_lockfiles.clone(); - locks_to_check.push("pixi.lock".to_string()); - debug!("CLI input for pixi lockfiles: {:?}", locks_to_check); - debug!("CLI input for OSI compliance: {}", osi); - - match cli.command { - Commands::Check { - include_safe, - osi: _, - ignore_pypi: _ - } => { - debug!("Check command called."); - - if include_safe { - debug!("Including safe dependencies in output"); - } + debug!("Parsed CLI config: {:?}", cli); - let check_output = check_license_infos(cli_input)?; + let config = get_config_options(cli.config, cli.command)?; - let (safe_dependencies, unsafe_dependencies) = check_output; + info!("Parsed config: {:?}", config); - print!( - "{}", - format_check_output(safe_dependencies, unsafe_dependencies.clone(), include_safe,) - ); + let stdout = io::stdout(); - if !unsafe_dependencies.is_empty() { - std::process::exit(1); - }; - Ok(()) - } - Commands::List {} => { - debug!("List command called"); - list(cli_input)?; - Ok(()) - } + match config { + CondaDenyConfig::Check(check_config) => check(check_config, stdout), + CondaDenyConfig::List(list_config) => list(list_config, stdout), } } diff --git a/src/pixi_lock.rs b/src/pixi_lock.rs index 629d6e5..bd395e4 100644 --- a/src/pixi_lock.rs +++ b/src/pixi_lock.rs @@ -6,38 +6,39 @@ use log::warn; use rattler_conda_types::{PackageRecord, Platform}; use rattler_lock::{LockFile, LockedPackageRef}; -fn _get_environment_names(pixi_lock_path: &Path) -> Vec { - let lock = LockFile::from_path(pixi_lock_path).unwrap(); - let environment_names = lock +fn _get_environment_names(lock_file: &LockFile) -> Vec { + lock_file .environments() .map(|env| env.0.to_string()) - .collect::>(); - environment_names + .collect::>() } pub fn get_conda_packages_for_pixi_lock( - pixi_lock_path: Option<&Path>, - mut environment_spec: Vec, - platform_spec: Vec, + pixi_lock_path: &Path, + environment_spec: &Option>, + platform_spec: &Option>, ignore_pypi: bool, ) -> Result> { - let pixi_lock_path = pixi_lock_path.unwrap_or(Path::new("pixi.lock")); - - let lock = LockFile::from_path(pixi_lock_path) + let lock_file = LockFile::from_path(pixi_lock_path) .with_context(|| format!("Failed to read pixi.lock file: {:?}", pixi_lock_path))?; - - if environment_spec.is_empty() { - environment_spec = _get_environment_names(pixi_lock_path); - } - + let environment_spec = environment_spec + .clone() + .unwrap_or_else(|| _get_environment_names(&lock_file)); let mut package_records = Vec::new(); - // todo: refactor this - if platform_spec.is_empty() { - for environment_name in environment_spec { - if let Some(environment) = lock.environment(&environment_name) { + for environment_name in environment_spec { + match lock_file.environment(&environment_name) { + Some(environment) => { for platform in environment.platforms() { - if let Some(packages) = environment.packages(platform) { + if platform_spec + .as_ref() + .map(|all| all.contains(&platform)) + .unwrap_or(true) + { + let packages = match environment.packages(platform) { + Some(pkgs) => pkgs, + None => continue, + }; for package in packages { match package { LockedPackageRef::Conda(conda_package) => { @@ -46,7 +47,10 @@ pub fn get_conda_packages_for_pixi_lock( } LockedPackageRef::Pypi(package_data, _) => { if !ignore_pypi { - return Err(anyhow::anyhow!("Pypi packages are not supported: {}", package_data.name)); + return Err(anyhow::anyhow!( + "Pypi packages are not supported: {}", + package_data.name + )); } else { warn!("Ignoring pypi package: {}", package_data.name); } @@ -56,31 +60,11 @@ pub fn get_conda_packages_for_pixi_lock( } } } - } - } else { - for platform_name in &platform_spec { - if let Ok(platform) = platform_name.parse::() { - for environment_name in environment_spec.clone() { - if let Some(environment) = lock.environment(&environment_name) { - if let Some(packages) = environment.packages(platform) { - for package in packages { - match package { - LockedPackageRef::Conda(conda_package) => { - let package_record = conda_package.record(); - package_records.push(package_record.to_owned()); - } - LockedPackageRef::Pypi(package_data, _) => { - if !ignore_pypi { - return Err(anyhow::anyhow!("Pypi packages are not supported: {}", package_data.name)); - } else { - warn!("Ignoring pypi package: {}", package_data.name); - } - } - } - } - } - } - } + None => { + return Err(anyhow::anyhow!( + "Environment not found in lock file: {}", + environment_name + )); } } } @@ -96,25 +80,26 @@ mod tests { #[test] fn test_pixi_lock_read_out() { - let environment_names = - _get_environment_names(Path::new("tests/test_pixi_lock_files/valid1_pixi.lock")); + let lock_file = + LockFile::from_path(Path::new("tests/test_pixi_lock_files/valid1_pixi.lock")).unwrap(); + let environment_names = _get_environment_names(&lock_file); assert_eq!(environment_names, vec!["default", "demo", "lint"]); } #[test] fn test_get_packages_for_pixi_lock() { let path = Path::new("tests/test_pixi_lock_files/valid1_pixi.lock"); - let package_records = get_conda_packages_for_pixi_lock(Some(path), vec![], vec![], false); + let package_records = get_conda_packages_for_pixi_lock(path, &None, &None, false); assert_eq!(package_records.unwrap().len(), 758); let package_records = - get_conda_packages_for_pixi_lock(Some(path), vec!["lint".to_string()], vec![], false); + get_conda_packages_for_pixi_lock(path, &Some(vec!["lint".to_string()]), &None, false); assert_eq!(package_records.unwrap().len(), 219); let package_records = get_conda_packages_for_pixi_lock( - Some(path), - vec!["lint".to_string()], - vec!["linux-64".to_string()], + path, + &Some(vec!["lint".to_string()]), + &Some(vec![Platform::Linux64]), false, ); assert_eq!(package_records.unwrap().len(), 48); diff --git a/src/read_remote.rs b/src/read_remote.rs deleted file mode 100644 index 86a7a29..0000000 --- a/src/read_remote.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::license_whitelist::LicenseWhitelistConfig; -use anyhow::{Context, Result}; -use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, AUTHORIZATION}; -use std::env; - -use core::str; - -pub async fn _read_remote_config(url: &str) -> Result { - let client = reqwest::Client::new(); - - let mut headers = HeaderMap::new(); - headers.insert( - ACCEPT, - HeaderValue::from_static("application/vnd.github.v3.raw"), - ); - - if let Ok(token) = env::var("GITHUB_TOKEN") { - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", token)) - .with_context(|| "Failed to create authorization header")?, - ); - } - - let response = client - .get(url) - .headers(headers) - .send() - .await - .with_context(|| "Failed to send request to the remote server")?; - - if !response.status().is_success() { - return Err(anyhow::anyhow!( - "Failed to download config from {}. Status: {}.\nPlease make sure you have set the GITHUB_TOKEN environment variable.", - url, - response.status() - )); - } - - let remote_config = response - .text() - .await - .with_context(|| "Failed to read response text")?; - - let value: LicenseWhitelistConfig = toml::from_str(&remote_config) - .with_context(|| "Failed to parse TOML from the downloaded config")?; - - Ok(value) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_read_remote_config() { - let url = "https://raw.githubusercontent.com/PaulKMueller/conda-deny-test/main/conda-deny-license_whitelist.toml"; - let whitelist = _read_remote_config(url).await; - - assert!(whitelist.is_ok()); - } - - #[tokio::test] - async fn test_read_remote_non_existent_config() { - let url = "https://raw.githubusercontent.com/PaulKMueller/this-config-does-not-exist.toml"; - let whitelist = _read_remote_config(url).await; - - assert!(whitelist.is_err()); - } -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e01cb42..da26563 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,140 +1,192 @@ -#[cfg(test)] -mod tests { - use assert_cmd::prelude::*; - use core::str; - use std::path::Path; - use std::process::Command; - - #[test] - fn test_default_use_case_check() { - let test_dir = Path::new("tests/test_end_to_end/test_default_use_case"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check").current_dir(test_dir); - command.assert().failure(); - } - - #[test] - fn test_default_use_case_list() { - let test_dir = Path::new("tests/test_end_to_end/test_default_use_case"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("list").current_dir(test_dir); - command.assert().success(); - } - - #[test] - fn test_default_use_case_pyproject_check() { - let test_dir = Path::new("tests/test_end_to_end/test_default_use_case_pyproject"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check").current_dir(test_dir); - command.assert().failure(); +use assert_cmd::prelude::*; +use conda_deny::cli::CondaDenyCliConfig; +use conda_deny::{ + check::check, get_config_options, list::list, CondaDenyCheckConfig, CondaDenyConfig, + CondaDenyListConfig, +}; +use rattler_conda_types::Platform; +use rstest::{fixture, rstest}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +#[fixture] +fn list_config( + #[default(None)] config: Option, + #[default(None)] lockfile: Option>, + #[default(None)] prefix: Option>, + #[default(None)] platform: Option>, + #[default(None)] environment: Option>, + #[default(None)] ignore_pypi: Option, +) -> CondaDenyListConfig { + let cli = CondaDenyCliConfig::List { + lockfile, + prefix, + platform, + environment, + ignore_pypi, + }; + + let config = get_config_options(config, cli).unwrap(); + + match config { + CondaDenyConfig::List(list_config) => list_config, + _ => panic!(), } +} - #[test] - fn test_default_use_case_pyproject_list() { - let test_dir = Path::new("tests/test_end_to_end/test_default_use_case_pyproject"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("list").current_dir(test_dir); - command.assert().success(); - } - - #[test] - fn test_remote_whitelist_check() { - let test_dir = Path::new("tests/test_end_to_end/test_remote_whitelist"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check").current_dir(test_dir); - command.assert().failure(); +#[fixture] +fn check_config( + #[default(None)] config: Option, + #[default(None)] lockfile: Option>, + #[default(None)] prefix: Option>, + #[default(None)] platform: Option>, + #[default(None)] environment: Option>, + #[default(None)] osi: Option, + #[default(None)] ignore_pypi: Option, +) -> CondaDenyCheckConfig { + let cli = CondaDenyCliConfig::Check { + lockfile, + prefix, + platform, + environment, + osi, + ignore_pypi, + }; + + let config = get_config_options(config, cli); + let config = config.unwrap(); + + match config { + CondaDenyConfig::Check(check_config) => check_config, + _ => panic!(), } +} - #[test] - fn test_remote_whitelist_list() { - let test_dir = Path::new("tests/test_end_to_end/test_remote_whitelist"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("list").current_dir(test_dir); - command.assert().success(); - } +#[fixture] +fn change_directory(#[default(PathBuf::from("examples/simple-python"))] path: PathBuf) { + std::env::set_current_dir(path).unwrap(); +} - #[test] - fn test_multiple_whitelists_check() { - let test_dir = Path::new("tests/test_end_to_end/test_multiple_whitelists"); +#[fixture] +fn out() -> Vec { + Vec::new() +} - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check").current_dir(test_dir); +#[rstest] +#[case("check", "test_default_use_case")] +#[case("list", "test_default_use_case")] +#[case("check", "test_default_use_case_pyproject")] +#[case("list", "test_default_use_case_pyproject")] +fn test_default_use_case(#[case] subcommand: &str, #[case] test_name: &str) { + let path_string = format!("tests/test_end_to_end/{}", test_name); + let test_dir = Path::new(path_string.as_str()); + + let mut command = Command::cargo_bin("conda-deny").unwrap(); + command.arg(subcommand).current_dir(test_dir); + if subcommand == "check" { command.assert().failure(); - } - - #[test] - fn test_multiple_whitelists_list() { - let test_dir = Path::new("tests/test_end_to_end/test_multiple_whitelists"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("list").current_dir(test_dir); + } else { command.assert().success(); } +} - #[test] - fn test_config_with_platform_and_env() { - let test_dir = Path::new("tests/test_end_to_end/test_platform_env_spec"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check").current_dir(test_dir); - command.assert().failure(); - } - - #[test] - fn test_osi_check() { - let test_dir = Path::new("tests/test_end_to_end/test_osi_check"); - - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check --osi").current_dir(test_dir); - command.assert().failure(); - } - - #[test] - fn test_prefix_list() { - // When --prefix is specified, only the license information for the conda-meta directory in the specified prefix should be listed - // License information from pixi.lock should not be listed - - let test_dir = Path::new("tests/test_end_to_end/test_prefix_list"); - - let output = Command::cargo_bin("conda-deny") - .unwrap() - .arg("list") - .arg("--prefix") - .arg("../../../tests/test_conda_prefixes/test-env") - .current_dir(test_dir) - .output() - .expect("Failed to execute command"); - - assert!( - output.status.success(), - "Command did not execute successfully" - ); - - let stdout = str::from_utf8(&output.stdout).expect("Failed to convert output to string"); +#[rstest] +fn test_remote_whitelist_check( + #[with(Some(PathBuf::from("tests/test_end_to_end/test_remote_whitelist/pixi.toml")), Some(vec!["tests/test_end_to_end/test_remote_whitelist/pixi.lock".into()]))] + check_config: CondaDenyCheckConfig, + mut out: Vec, +) { + let result = check(check_config, &mut out); + assert!(result.is_err()); +} - let line_count = stdout.lines().count(); +#[rstest] +fn test_multiple_whitelists_check( + #[with( + Some(PathBuf::from("tests/test_end_to_end/test_multiple_whitelists/pixi.toml")), + Some(vec!["tests/test_end_to_end/test_multiple_whitelists/pixi.lock".into()]) + )] + check_config: CondaDenyCheckConfig, + mut out: Vec, +) { + let result = check(check_config, &mut out); + assert!(result.is_err()); +} - let expected_line_count = 50; - assert_eq!( - line_count, expected_line_count, - "Unexpected number of output lines" - ); +#[rstest] +fn test_config_with_platform_and_env( + #[with( + Some(PathBuf::from("tests/test_end_to_end/test_platform_env_spec/pixi.toml")), + Some(vec!["tests/test_end_to_end/test_platform_env_spec/pixi.lock".into()]) + )] + check_config: CondaDenyCheckConfig, + mut out: Vec, +) { + let result = check(check_config, &mut out); + assert!(result.is_err()); +} - println!("Output has {} lines", line_count); - } +#[rstest] +fn test_osi_check( + #[with( + None, + Some(vec!["tests/test_end_to_end/test_osi_check/pixi.toml".into()]), + None, + None, + None, + Some(true) + )] + check_config: CondaDenyCheckConfig, + mut out: Vec, +) { + let result = check(check_config, &mut out); + + assert!(result.is_err()); +} - #[test] - fn test_exception_check() { - let test_dir = Path::new("tests/test_end_to_end/test_exception_use_case"); +#[rstest] +fn test_prefix_list( + #[with( + Some(PathBuf::from("tests/test_end_to_end/test_prefix_list/pixi.toml")), None, Some(vec!["tests/test_conda_prefixes/test-env".into()]) +)] + list_config: CondaDenyListConfig, + mut out: Vec, +) { + // When --prefix is specified, only the license information for the conda-meta directory in the specified prefix should be listed + // License information from pixi.lock should not be listed + let result = list(list_config, &mut out); + assert!(result.is_ok(), "{:?}", result.unwrap_err()); + let line_count = String::from_utf8(out).unwrap().split("\n").count(); + let expected_line_count = 50; + assert_eq!( + line_count, expected_line_count, + "Unexpected number of output lines" + ); + + println!("Output has {} lines", line_count); +} - let mut command = Command::cargo_bin("conda-deny").unwrap(); - command.arg("check").current_dir(test_dir); - command.assert().failure(); - } +#[test] +fn test_exception_check() { + let cli = CondaDenyCliConfig::Check { + lockfile: None, + prefix: None, + platform: None, + environment: None, + osi: None, + ignore_pypi: None, + }; + + let config = get_config_options( + Some("tests/test_end_to_end/test_exception_use_case/pixi.toml".into()), + cli, + ); + + assert!(config.is_err()); + let err_string = config.unwrap_err().to_string(); + assert!( + err_string.contains("No lockfiles or conda prefixes provided"), + "{}", + err_string + ); } diff --git a/tests/test_end_to_end/test_multiple_whitelists/pixi.toml b/tests/test_end_to_end/test_multiple_whitelists/pixi.toml index df13c7d..c0e6382 100644 --- a/tests/test_end_to_end/test_multiple_whitelists/pixi.toml +++ b/tests/test_end_to_end/test_multiple_whitelists/pixi.toml @@ -1,2 +1,2 @@ [tool.conda-deny] -license-whitelist = ["license_whitelist.toml", "https://raw.githubusercontent.com/PaulKMueller/conda-deny-test/main/conda-deny-license_whitelist.toml"] +license-whitelist = ["tests/test_end_to_end/test_multiple_whitelists/license_whitelist.toml", "https://raw.githubusercontent.com/PaulKMueller/conda-deny-test/main/conda-deny-license_whitelist.toml"] diff --git a/tests/test_end_to_end/test_platform_env_spec/pixi.toml b/tests/test_end_to_end/test_platform_env_spec/pixi.toml index aa2b361..c629dea 100644 --- a/tests/test_end_to_end/test_platform_env_spec/pixi.toml +++ b/tests/test_end_to_end/test_platform_env_spec/pixi.toml @@ -1,4 +1,4 @@ [tool.conda-deny] -license-whitelist = "license_whitelist.toml" +license-whitelist = "tests/test_end_to_end/test_platform_env_spec/license_whitelist.toml" platform = "linux-64" environment = "lint" \ No newline at end of file diff --git a/tests/test_end_to_end/test_prefix_list/pixi.toml b/tests/test_end_to_end/test_prefix_list/pixi.toml index aa2b361..aa1ed00 100644 --- a/tests/test_end_to_end/test_prefix_list/pixi.toml +++ b/tests/test_end_to_end/test_prefix_list/pixi.toml @@ -1,4 +1,2 @@ [tool.conda-deny] license-whitelist = "license_whitelist.toml" -platform = "linux-64" -environment = "lint" \ No newline at end of file diff --git a/tests/test_end_to_end/test_remote_whitelist/pixi.toml b/tests/test_end_to_end/test_remote_whitelist/pixi.toml index 0c370dc..11469ef 100644 --- a/tests/test_end_to_end/test_remote_whitelist/pixi.toml +++ b/tests/test_end_to_end/test_remote_whitelist/pixi.toml @@ -1,2 +1,2 @@ [tool.conda-deny] -license-whitelist = "https://raw.githubusercontent.com/PaulKMueller/conda-deny-test/main/conda-deny-license_whitelist.toml" \ No newline at end of file +license-whitelist = "https://raw.githubusercontent.com/PaulKMueller/conda-deny-test/main/conda-deny-license_whitelist.toml"