From 3e608cf2494d7e6a6dd42d4b73501f8c968e0540 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Thu, 23 May 2024 16:08:27 +1000 Subject: [PATCH] feat: Bump minor version; extract gRPC plugin code to seperate module --- drivers/rust/driver/Cargo.lock | 624 ++++++++++++----- drivers/rust/driver/Cargo.toml | 10 +- drivers/rust/driver/README.md | 1 + drivers/rust/driver/src/content.rs | 291 ++------ drivers/rust/driver/src/grpc_plugin.rs | 790 ++++++++++++++++++++++ drivers/rust/driver/src/lib.rs | 8 +- drivers/rust/driver/src/lua_plugin.rs | 0 drivers/rust/driver/src/mock_server.rs | 4 +- drivers/rust/driver/src/plugin_manager.rs | 568 ++++++---------- drivers/rust/driver/src/plugin_models.rs | 394 ++++------- drivers/rust/driver_ffi/Cargo.toml | 2 +- drivers/rust/driver_pact_tests/Cargo.toml | 2 +- 12 files changed, 1649 insertions(+), 1045 deletions(-) create mode 100644 drivers/rust/driver/src/grpc_plugin.rs create mode 100644 drivers/rust/driver/src/lua_plugin.rs diff --git a/drivers/rust/driver/Cargo.lock b/drivers/rust/driver/Cargo.lock index a7a2ff54..74c0ab33 100644 --- a/drivers/rust/driver/Cargo.lock +++ b/drivers/rust/driver/Cargo.lock @@ -54,47 +54,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -102,9 +103,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ariadne" @@ -118,9 +128,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" +checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498" dependencies = [ "flate2", "futures-core", @@ -164,9 +174,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -236,15 +246,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "beef" @@ -273,6 +277,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -317,9 +331,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ "jobserver", "libc", @@ -379,9 +393,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "console" @@ -398,9 +412,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation" @@ -427,11 +441,26 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -457,9 +486,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -471,6 +500,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + [[package]] name = "deranged" version = "0.3.11" @@ -480,6 +515,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -491,11 +537,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "encode_unicode" @@ -543,9 +600,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -562,9 +619,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "filetime" @@ -574,7 +631,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] @@ -586,9 +643,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -707,9 +764,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -758,9 +815,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashers" @@ -933,7 +990,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.28", - "rustls 0.21.11", + "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] @@ -1037,7 +1094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1070,9 +1127,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -1083,6 +1140,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -1101,6 +1164,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1162,26 +1234,32 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.21" @@ -1220,12 +1298,50 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "lua-src" +version = "546.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da0daa7eee611a4c30c8f5ee31af55266e26e573971ba9336d2993e2da129b2" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.8+5790d25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441f18d9ad792e871fc2f7f2cb8902c386f6f56fdbddef3b835b61475e375346" +dependencies = [ + "cc", + "which", +] + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchit" version = "0.7.3" @@ -1258,9 +1374,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -1276,6 +1392,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mlua" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e340c022072f3208a4105458286f4985ba5355bfe243c3073afe45cbe9ecf491" +dependencies = [ + "bstr", + "mlua-sys", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5552e7e4e22ada0463dfdeee6caf6dc057a189fdc83136408a8f950a5e5c5540" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + [[package]] name = "multimap" version = "0.10.0" @@ -1301,6 +1443,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1309,9 +1461,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1386,9 +1538,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pact-plugin-driver" -version = "0.6.2" +version = "0.7.0" dependencies = [ "anyhow", "async-trait", @@ -1401,11 +1559,12 @@ dependencies = [ "futures-util", "home", "indicatif", - "itertools 0.12.1", + "itertools 0.13.0", "lazy_static", "log", "maplit", "md5", + "mlua", "os_info", "pact_models", "prost", @@ -1471,9 +1630,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1481,47 +1640,34 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "parse-zoneinfo" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" dependencies = [ "regex", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "pbkdf2" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", - "password-hash", - "sha2", ] [[package]] @@ -1538,9 +1684,9 @@ checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57" [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap 2.2.6", @@ -1642,9 +1788,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", "syn", @@ -1652,18 +1798,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -1671,9 +1817,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", @@ -1692,9 +1838,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", @@ -1705,9 +1851,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] @@ -1827,6 +1973,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex" version = "1.10.4" @@ -1835,10 +1990,19 @@ checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata", + "regex-automata 0.4.6", "regex-syntax 0.8.3", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + [[package]] name = "regex-automata" version = "0.4.6" @@ -1894,7 +2058,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.11", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", "serde", @@ -1919,7 +2083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ "async-compression", - "base64 0.22.0", + "base64 0.22.1", "bytes", "futures-core", "futures-util", @@ -1973,9 +2137,15 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" @@ -1992,9 +2162,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -2011,7 +2181,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.3", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] @@ -2056,15 +2226,15 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" @@ -2078,9 +2248,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -2089,15 +2259,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" @@ -2126,11 +2296,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -2139,9 +2309,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2149,24 +2319,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -2175,9 +2345,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2186,9 +2356,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -2227,6 +2397,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2236,6 +2415,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.11" @@ -2259,9 +2444,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2291,9 +2476,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -2308,9 +2493,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sysinfo" -version = "0.30.11" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87341a165d73787554941cd5ef55ad728011566fe714e987d1b976c15dbc3a83" +checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" dependencies = [ "cfg-if", "core-foundation-sys", @@ -2377,25 +2562,56 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b319995299c65d522680decf80f2c108d85b861d81dfe340a10d16cee29d9e6" +checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "env_logger", "test-log-macros", + "tracing-subscriber", ] [[package]] name = "test-log-macros" -version = "0.2.15" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8f546451eaa38373f549093fe9fd05e7d2bade739e2ddf834b9968621d60107" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -2476,7 +2692,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.11", + "rustls 0.21.12", "tokio", ] @@ -2504,23 +2720,22 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", @@ -2530,18 +2745,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap 2.2.6", "serde", @@ -2655,6 +2870,34 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -2696,9 +2939,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "untrusted" @@ -2848,6 +3091,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3030,9 +3285,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] @@ -3057,6 +3312,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "xattr" version = "1.3.1" @@ -3079,43 +3340,78 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zip" -version = "0.6.6" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "1b7a5a9285bd4ee13bdeb3f8a4917eb46557e53f270c783849db8bef37b0ad00" dependencies = [ "aes", - "byteorder", + "arbitrary", "bzip2", "constant_time_eq", "crc32fast", "crossbeam-utils", + "deflate64", + "displaydoc", "flate2", "hmac", + "indexmap 2.2.6", + "lzma-rs", "pbkdf2", + "rand 0.8.5", "sha1", + "thiserror", "time", + "zeroize", + "zopfli", "zstd", ] +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + [[package]] name = "zstd" -version = "0.11.2+zstd.1.5.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ - "libc", "zstd-sys", ] diff --git a/drivers/rust/driver/Cargo.toml b/drivers/rust/driver/Cargo.toml index 5ed2f09a..6cd96685 100644 --- a/drivers/rust/driver/Cargo.toml +++ b/drivers/rust/driver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pact-plugin-driver" -version = "0.6.2" +version = "0.7.0" description = "Pact support library that provides an interface for interacting with Pact plugins" edition = "2021" documentation = "https://docs.rs/pact-plugin-driver" @@ -15,9 +15,10 @@ exclude = [ ] [features] -default = ["datetime", "xml"] +default = ["datetime", "xml", "lua"] datetime = ["pact_models/datetime"] # Support for date/time matchers and expressions xml = ["pact_models/xml"] # support for matching XML documents +lua = ["dep:mlua"] # support for plugins written in Lua [dependencies] anyhow = "1.0.82" @@ -29,11 +30,12 @@ flate2 = "1.0.28" futures-util = "0.3.30" home = "0.5.9" indicatif = "0.17.8" -itertools = "0.12.1" +itertools = "0.13.0" lazy_static = "1.4.0" log = "0.4.21" maplit = "1.0.2" md5 = "0.7.0" +mlua = { version = "0.9.8", features = ["lua54", "vendored"], optional = true } os_info = "3.7.0" pact_models = { version = "~1.2.0", default-features = false } prost = "0.12.4" @@ -52,7 +54,7 @@ tonic = "0.11.0" tracing = { version = "0.1.40", features = [ "log" ] } # This needs to be the same version across all the pact libs (i.e. pact ffi) tracing-core = "0.1.32" # This needs to be the same version across all the pact libs (i.e. pact ffi) uuid = { version = "1.8.0", features = ["v4"] } -zip = "0.6.6" +zip = "1.3.1" [dev-dependencies] env_logger = "0.11.3" diff --git a/drivers/rust/driver/README.md b/drivers/rust/driver/README.md index 0b92a771..866338c4 100644 --- a/drivers/rust/driver/README.md +++ b/drivers/rust/driver/README.md @@ -17,6 +17,7 @@ All features are enabled by default `chronos` crate as a dependency. * `xml`: Enables support for parsing XML documents. This feature will add the `sxd-document` crate as a dependency. +* `lua`: Enables plugins written in Lua (Lua 5.4). ## Building the Rust driver diff --git a/drivers/rust/driver/src/content.rs b/drivers/rust/driver/src/content.rs index e8c4a626..7f137a3f 100644 --- a/drivers/rust/driver/src/content.rs +++ b/drivers/rust/driver/src/content.rs @@ -1,32 +1,26 @@ //! Support for matching and generating content based on content types use std::collections::HashMap; -use std::str::from_utf8; use anyhow::anyhow; -use bytes::Bytes; use maplit::hashmap; use pact_models::bodies::OptionalBody; -use pact_models::content_types::ContentTypeHint; -use pact_models::matchingrules::{Category, MatchingRule, MatchingRuleCategory, RuleList}; -use pact_models::path_exp::DocPath; -use pact_models::prelude::{ContentType, Generator, GeneratorCategory, Generators, RuleLogic}; +use pact_models::matchingrules::MatchingRuleCategory; +use pact_models::prelude::{ContentType, Generator, Generators}; use pact_models::plugins::PluginData; use serde_json::Value; use tracing::{debug, error}; use crate::catalogue_manager::{CatalogueEntry, CatalogueEntryProviderType}; use crate::plugin_manager::lookup_plugin; -use crate::plugin_models::{PactPluginManifest, PactPluginRpc, PluginInteractionConfig}; -use crate::proto::{ - Body, - CompareContentsRequest, - ConfigureInteractionRequest, - GenerateContentRequest, - PluginConfiguration as ProtoPluginConfiguration +use crate::plugin_models::{ + PactPluginManifest, + PluginInteractionConfig, + CompareContentRequest, + CompareContentResult, + GenerateContentRequest }; -use crate::proto::body; -use crate::proto::interaction_response::MarkupType; -use crate::utils::{proto_struct_to_json, proto_struct_to_map, to_proto_struct}; +use crate::proto::{PluginConfiguration as ProtoPluginConfiguration}; +use crate::utils::proto_struct_to_map; /// Matcher for contents based on content type #[derive(Clone, Debug)] @@ -174,118 +168,16 @@ impl ContentMatcher { /// Get the plugin to configure the interaction contents for the interaction part based on the /// provided definition - pub async fn configure_interation( + pub async fn configure_interaction( &self, content_type: &ContentType, definition: HashMap ) -> anyhow::Result<(Vec, Option)> { debug!("Sending ConfigureContents request to plugin {:?}", self.catalogue_entry); - let request = ConfigureInteractionRequest { - content_type: content_type.to_string(), - contents_config: Some(to_proto_struct(&definition)), - }; - let plugin_manifest = self.catalogue_entry.plugin.as_ref() .expect("Plugin type is required"); match lookup_plugin(&plugin_manifest.as_dependency()) { - Some(plugin) => match plugin.configure_interaction(request).await { - Ok(response) => { - debug!("Got response: {:?}", response); - if response.error.is_empty() { - let mut results = vec![]; - - for response in &response.interaction { - let body = match &response.contents { - Some(body) => { - let returned_content_type = ContentType::parse(body.content_type.as_str()).ok(); - let contents = body.content.as_ref().cloned().unwrap_or_default(); - OptionalBody::Present(Bytes::from(contents), returned_content_type, - Some(match body.content_type_hint() { - body::ContentTypeHint::Text => ContentTypeHint::TEXT, - body::ContentTypeHint::Binary => ContentTypeHint::BINARY, - body::ContentTypeHint::Default => ContentTypeHint::DEFAULT, - })) - }, - None => OptionalBody::Missing - }; - - let rules = Self::setup_matching_rules(&response.rules)?; - - let generators = if !response.generators.is_empty() || !response.metadata_generators.is_empty() { - let mut categories = hashmap!{}; - - if !response.generators.is_empty() { - let mut generators = hashmap!{}; - for (k, gen) in &response.generators { - generators.insert(DocPath::new(k)?, - Generator::create(gen.r#type.as_str(), - &gen.values.as_ref().map(|attr| proto_struct_to_json(attr)).unwrap_or_default())?); - } - categories.insert(GeneratorCategory::BODY, generators); - } - - if !response.metadata_generators.is_empty() { - let mut generators = hashmap!{}; - for (k, gen) in &response.metadata_generators { - generators.insert(DocPath::new(k)?, - Generator::create(gen.r#type.as_str(), - &gen.values.as_ref().map(|attr| proto_struct_to_json(attr)).unwrap_or_default())?); - } - categories.insert(GeneratorCategory::METADATA, generators); - } - - Some(Generators { categories }) - } else { - None - }; - - let metadata = response.message_metadata.as_ref().map(|md| proto_struct_to_map(md)); - let metadata_rules = Self::setup_matching_rules(&response.metadata_rules)?; - - let plugin_config = if let Some(plugin_configuration) = &response.plugin_configuration { - PluginConfiguration { - interaction_configuration: plugin_configuration.interaction_configuration.as_ref() - .map(|val| proto_struct_to_map(val)).unwrap_or_default(), - pact_configuration: plugin_configuration.pact_configuration.as_ref() - .map(|val| proto_struct_to_map(val)).unwrap_or_default() - } - } else { - PluginConfiguration::default() - }; - - debug!("body={}", body); - debug!("rules={:?}", rules); - debug!("generators={:?}", generators); - debug!("metadata={:?}", metadata); - debug!("metadata_rules={:?}", metadata_rules); - debug!("pluginConfig={:?}", plugin_config); - - results.push(InteractionContents { - part_name: response.part_name.clone(), - body, - rules, - generators, - metadata, - metadata_rules, - plugin_config, - interaction_markup: response.interaction_markup.clone(), - interaction_markup_type: match response.interaction_markup_type() { - MarkupType::Html => "HTML".to_string(), - _ => "COMMON_MARK".to_string(), - } - }) - } - - Ok((results, response.plugin_configuration.map(|config| PluginConfiguration::from(config)))) - } else { - Err(anyhow!("Request to configure interaction failed: {}", response.error)) - } - } - Err(err) => { - error!("Call to plugin failed - {}", err); - Err(anyhow!("Call to plugin failed - {}", err)) - } - }, + Some(plugin) => plugin.configure_interaction(content_type, &definition).await, None => { error!("Plugin for {:?} was not found in the plugin register", self.catalogue_entry); Err(anyhow!("Plugin for {:?} was not found in the plugin register", self.catalogue_entry)) @@ -293,29 +185,6 @@ impl ContentMatcher { } } - fn setup_matching_rules(rules_map: &HashMap) -> anyhow::Result> { - if !rules_map.is_empty() { - let mut rules = hashmap!{}; - for (k, rule_list) in rules_map { - let mut vec = vec![]; - for rule in &rule_list.rule { - let mr = MatchingRule::create(rule.r#type.as_str(), &rule.values.as_ref().map(|rule| { - proto_struct_to_json(rule) - }).unwrap_or_default())?; - vec.push(mr); - } - rules.insert(DocPath::new(k)?, RuleList { - rules: vec, - rule_logic: RuleLogic::And, - cascaded: false - }); - } - Ok(Some(MatchingRuleCategory { name: Category::BODY, rules })) - } else { - Ok(None) - } - } - /// Get the plugin to match the contents against the expected contents returning all the mismatches. /// Note that it is an error to call this with a non-plugin (core) content matcher. /// @@ -329,87 +198,52 @@ impl ContentMatcher { allow_unexpected_keys: bool, plugin_config: Option ) -> Result<(), HashMap>> { - let request = CompareContentsRequest { - expected: Some(Body { - content_type: expected.content_type().unwrap_or_default().to_string(), - content: expected.value().map(|b| b.to_vec()), - content_type_hint: body::ContentTypeHint::Default as i32 - }), - actual: Some(Body { - content_type: actual.content_type().unwrap_or_default().to_string(), - content: actual.value().map(|b| b.to_vec()), - content_type_hint: body::ContentTypeHint::Default as i32 - }), + let request = CompareContentRequest { + expected_contents: expected.clone(), + actual_contents: actual.clone(), allow_unexpected_keys, - rules: context.rules.iter().map(|(k, r)| { - (k.to_string(), crate::proto::MatchingRules { - rule: r.rules.iter().map(|rule|{ - crate::proto::MatchingRule { - r#type: rule.name(), - values: Some(to_proto_struct(&rule.values().iter().map(|(k, v)| (k.to_string(), v.clone())).collect())), - } - }).collect() - }) - }).collect(), - plugin_configuration: plugin_config.map(|config| ProtoPluginConfiguration { - interaction_configuration: Some(to_proto_struct(&config.interaction_configuration)), - pact_configuration: Some(to_proto_struct(&config.pact_configuration)) - }) + matching_rules: context.rules.clone(), + plugin_configuration: plugin_config.clone(), + .. CompareContentRequest::default() }; let plugin_manifest = self.catalogue_entry.plugin.as_ref() .expect("Plugin type is required"); match lookup_plugin(&plugin_manifest.as_dependency()) { - Some(plugin) => match plugin.compare_contents(request).await { - Ok(response) => if let Some(mismatch) = response.type_mismatch { - Err(hashmap!{ - String::default() => vec![ - ContentMismatch { - expected: mismatch.expected.clone(), - actual: mismatch.actual.clone(), - mismatch: format!("Expected content type '{}' but got '{}'", mismatch.expected, mismatch.actual), - path: "".to_string(), - diff: None, - mismatch_type: None - } - ] - }) - } else if !response.error.is_empty() { - Err(hashmap! { + Some(plugin) => match plugin.match_contents(request).await { + Ok(response) => match response { + CompareContentResult::Error(err) => Err(hashmap! { + String::default() => vec![ + ContentMismatch { + expected: Default::default(), + actual: Default::default(), + mismatch: err.clone(), + path: "".to_string(), + diff: None, + mismatch_type: None + } + ] + }), + CompareContentResult::TypeMismatch(expected, actual) => Err(hashmap!{ String::default() => vec![ ContentMismatch { - expected: Default::default(), - actual: Default::default(), - mismatch: response.error.clone(), + expected: expected.clone(), + actual: actual.clone(), + mismatch: format!("Expected content type '{}' but got '{}'", expected, actual), path: "".to_string(), diff: None, mismatch_type: None } ] - }) - } else if !response.results.is_empty() { - Err(response.results.iter().map(|(k, v)| { - (k.clone(), v.mismatches.iter().map(|mismatch| { - ContentMismatch { - expected: mismatch.expected.as_ref() - .map(|e| from_utf8(&e).unwrap_or_default().to_string()) - .unwrap_or_default(), - actual: mismatch.actual.as_ref() - .map(|a| from_utf8(&a).unwrap_or_default().to_string()) - .unwrap_or_default(), - mismatch: mismatch.mismatch.clone(), - path: mismatch.path.clone(), - diff: if mismatch.diff.is_empty() { - None - } else { - Some(mismatch.diff.clone()) - }, - mismatch_type: Some(mismatch.mismatch_type.clone()) - } - }).collect()) - }).collect()) - } else { - Ok(()) + }), + CompareContentResult::Mismatches(mismatches) => { + if mismatches.is_empty() { + Ok(()) + } else { + Err(mismatches.clone()) + } + } + CompareContentResult::OK => Ok(()) } Err(err) => { error!("Call to plugin failed - {}", err); @@ -496,24 +330,12 @@ impl ContentGenerator { let interaction_data = interaction_data.get(&pact_plugin_manifest.name); let request = GenerateContentRequest { - contents: Some(crate::proto::Body { - content_type: content_type.to_string(), - content: Some(body.value().unwrap_or_default().to_vec()), - content_type_hint: body::ContentTypeHint::Default as i32 - }), - generators: generators.iter().map(|(k, v)| { - (k.clone(), crate::proto::Generator { - r#type: v.name(), - values: Some(to_proto_struct(&v.values().iter() - .map(|(k, v)| (k.to_string(), v.clone())).collect())), - }) - }).collect(), - plugin_configuration: Some(ProtoPluginConfiguration { - pact_configuration: plugin_data.as_ref().map(to_proto_struct), - interaction_configuration: interaction_data.map(to_proto_struct), - .. ProtoPluginConfiguration::default() - }), - test_context: Some(to_proto_struct(&context.iter().map(|(k, v)| (k.to_string(), v.clone())).collect())), + content_type: content_type.clone(), + content: body.clone(), + generators: generators.clone(), + plugin_data: plugin_data.clone(), + interaction_data: interaction_data.cloned(), + test_context: context.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), .. GenerateContentRequest::default() }; @@ -522,16 +344,7 @@ impl ContentGenerator { match lookup_plugin(&plugin_manifest.as_dependency()) { Some(plugin) => { debug!("Sending generateContent request to plugin {:?}", plugin_manifest); - match plugin.generate_content(request).await?.contents { - Some(contents) => { - Ok(OptionalBody::Present( - Bytes::from(contents.content.unwrap_or_default()), - ContentType::parse(contents.content_type.as_str()).ok(), - None - )) - } - None => Ok(OptionalBody::Empty) - } + plugin.generate_contents(request).await }, None => { error!("Plugin for {:?} was not found in the plugin register", self.catalogue_entry); diff --git a/drivers/rust/driver/src/grpc_plugin.rs b/drivers/rust/driver/src/grpc_plugin.rs new file mode 100644 index 00000000..2d5e5d9c --- /dev/null +++ b/drivers/rust/driver/src/grpc_plugin.rs @@ -0,0 +1,790 @@ +//! Support for plugins running via the Plugin gRPC interface + +use std::collections::HashMap; +use std::path::PathBuf; +use std::process::Stdio; +use std::str::from_utf8; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use anyhow::anyhow; +use async_trait::async_trait; +use bytes::Bytes; +use itertools::Either; +use log::max_level; +use maplit::hashmap; +use os_info::Type; +use pact_models::bodies::OptionalBody; +use pact_models::content_types::{ContentType, ContentTypeHint}; +use pact_models::generators::{Generator, GeneratorCategory, Generators}; +use pact_models::matchingrules::{Category, MatchingRule, MatchingRuleCategory, RuleList, RuleLogic}; +use pact_models::pact::Pact; +use pact_models::PactSpecification; +use pact_models::path_exp::DocPath; +use pact_models::prelude::v4::V4Pact; +use pact_models::v4::interaction::V4Interaction; +use serde_json::Value; +use sysinfo::{Pid, Signal, System}; +use tokio::process::Command; +use tonic::{Request, Status}; +use tonic::codegen::InterceptedService; +use tonic::metadata::Ascii; +use tonic::service::Interceptor; +use tonic::transport::Channel; +use tracing::{debug, trace, warn}; + +use crate::catalogue_manager::{CatalogueEntry, register_plugin_entries}; +use crate::child_process::ChildPluginProcess; +use crate::content::{ContentMismatch, InteractionContents}; +use crate::mock_server::{MockServerConfig, MockServerDetails}; +use crate::plugin_models::{CompareContentResult, PactPlugin, PactPluginManifest}; +use crate::proto::{body, Body, Catalogue, CompareContentsRequest, CompareContentsResponse, ConfigureInteractionRequest, ConfigureInteractionResponse, GenerateContentRequest, GenerateContentResponse, InitPluginRequest, InitPluginResponse, InteractionData, metadata_value, MetadataValue, MockServerRequest, MockServerResults, PluginConfiguration, ShutdownMockServerRequest, ShutdownMockServerResponse, start_mock_server_response, StartMockServerRequest, StartMockServerResponse, verification_preparation_response, VerificationPreparationRequest, VerificationPreparationResponse, verify_interaction_response, VerifyInteractionRequest, VerifyInteractionResponse}; +use crate::proto::interaction_response::MarkupType; +use crate::proto::pact_plugin_client::PactPluginClient; +use crate::utils::{optional_string, proto_struct_to_json, proto_struct_to_map, proto_value_to_json, to_proto_struct, to_proto_value}; +use crate::verification::{InteractionVerificationData, InteractionVerificationResult}; + +/// Trait with remote-calling methods for a running gRPC-based plugin +#[async_trait] +pub trait PactPluginRpc { + /// Send an init request to the plugin process + async fn init_plugin(&mut self, request: InitPluginRequest) -> anyhow::Result; + + /// Send a compare contents request to the plugin process + async fn compare_contents(&self, request: CompareContentsRequest) -> anyhow::Result; + + /// Send a configure contents request to the plugin process + async fn configure_interaction(&self, request: ConfigureInteractionRequest) -> anyhow::Result; + + /// Send a generate content request to the plugin + async fn generate_content(&self, request: GenerateContentRequest) -> anyhow::Result; + + /// Start a mock server + async fn start_mock_server(&self, request: StartMockServerRequest) -> anyhow::Result; + + /// Shutdown a running mock server + async fn shutdown_mock_server(&self, request: ShutdownMockServerRequest) -> anyhow::Result; + + /// Get the matching results from a running mock server + async fn get_mock_server_results(&self, request: MockServerRequest) -> anyhow::Result; + + /// Prepare an interaction for verification. This should return any data required to construct any request + /// so that it can be amended before the verification is run. + async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result; + + /// Execute the verification for the interaction. + async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result; + + /// Updates the catalogue. This will be sent when the core catalogue has been updated (probably by a plugin loading). + async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>; +} + +/// Running plugin details of a gRPC-based plugin +#[derive(Debug, Clone)] +pub struct GrpcPactPlugin { + /// Manifest for this plugin + pub manifest: PactPluginManifest, + + /// Running child process + pub child: Arc, + + /// Count of access to the plugin. If this is ever zero, the plugin process will be shutdown + access_count: Arc +} + +#[async_trait] +impl PactPluginRpc for GrpcPactPlugin { + /// Send an init request to the plugin process + async fn init_plugin(&mut self, request: InitPluginRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.init_plugin(Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + /// Send a compare contents request to the plugin process + async fn compare_contents(&self, request: CompareContentsRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.compare_contents(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + /// Send a configure contents request to the plugin process + async fn configure_interaction(&self, request: ConfigureInteractionRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.configure_interaction(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + /// Send a generate content request to the plugin + async fn generate_content(&self, request: GenerateContentRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.generate_content(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + async fn start_mock_server(&self, request: StartMockServerRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.start_mock_server(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + async fn shutdown_mock_server(&self, request: ShutdownMockServerRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.shutdown_mock_server(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + async fn get_mock_server_results(&self, request: MockServerRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.get_mock_server_results(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.prepare_interaction_for_verification(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result { + let mut client = self.get_plugin_client().await?; + let response = client.verify_interaction(tonic::Request::new(request)).await?; + Ok(response.get_ref().clone()) + } + + async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()> { + let mut client = self.get_plugin_client().await?; + client.update_catalogue(tonic::Request::new(request)).await?; + Ok(()) + } +} + +impl GrpcPactPlugin { + /// Create a new Plugin + pub fn new(manifest: &PactPluginManifest, child: ChildPluginProcess) -> Self { + GrpcPactPlugin { + manifest: manifest.clone(), + child: Arc::new(child), + access_count: Arc::new(AtomicUsize::new(1)) + } + } + + /// Port the plugin is running on + pub fn port(&self) -> u16 { + self.child.port() + } + + async fn connect_channel(&self) -> anyhow::Result { + let port = self.child.port(); + match Channel::from_shared(format!("http://[::1]:{}", port))?.connect().await { + Ok(channel) => Ok(channel), + Err(err) => { + debug!("IP6 connection failed, will try IP4 address - {err}"); + Channel::from_shared(format!("http://127.0.0.1:{}", port))?.connect().await + .map_err(|err| anyhow!(err)) + } + } + } + + async fn get_plugin_client(&self) -> anyhow::Result>> { + let channel = self.connect_channel().await?; + let interceptor = PactPluginInterceptor::new(self.child.plugin_info.server_key.as_str())?; + Ok(PactPluginClient::with_interceptor(channel, interceptor)) + } + + fn setup_matching_rules(rules_map: &HashMap) -> anyhow::Result> { + if !rules_map.is_empty() { + let mut rules = hashmap!{}; + for (k, rule_list) in rules_map { + let mut vec = vec![]; + for rule in &rule_list.rule { + let mr = MatchingRule::create(rule.r#type.as_str(), &rule.values.as_ref().map(|rule| { + proto_struct_to_json(rule) + }).unwrap_or_default())?; + vec.push(mr); + } + rules.insert(DocPath::new(k)?, RuleList { + rules: vec, + rule_logic: RuleLogic::And, + cascaded: false + }); + } + Ok(Some(MatchingRuleCategory { name: Category::BODY, rules })) + } else { + Ok(None) + } + } +} + +#[async_trait] +impl PactPlugin for GrpcPactPlugin { + fn manifest(&self) -> PactPluginManifest { + self.manifest.clone() + } + + fn kill(&self) { + self.child.kill(); + } + + fn update_access(&mut self) { + let count = self.access_count.fetch_add(1, Ordering::SeqCst); + trace!("update_access: Plugin {}/{} access is now {}", self.manifest.name, + self.manifest.version, count + 1); + } + + fn drop_access(&mut self) -> usize { + let check = self.access_count.fetch_update(Ordering::SeqCst, + Ordering::SeqCst, |count| { + if count > 0 { + Some(count - 1) + } else { + None + } + }); + let count = if let Ok(v) = check { + if v > 0 { v - 1 } else { v } + } else { + 0 + }; + trace!("drop_access: Plugin {}/{} access is now {}", self.manifest.name, self.manifest.version, + count); + count + } + + fn boxed(&self) -> Box { + Box::new(self.clone()) + } + + fn arced(&self) -> Arc { + Arc::new(self.clone()) + } + + async fn publish_updated_catalogue(&self, catalogue: &[CatalogueEntry]) -> anyhow::Result<()> { + let request = Catalogue { + catalogue: catalogue.iter() + .map(|entry| crate::proto::CatalogueEntry { + r#type: entry.entry_type.to_proto_type() as i32, + key: entry.key.clone(), + values: entry.values.clone() + }).collect() + }; + self.update_catalogue(request).await + } + + async fn generate_contents( + &self, + request: crate::plugin_models::GenerateContentRequest + ) -> anyhow::Result { + let request = crate::proto::GenerateContentRequest { + contents: Some(crate::proto::Body { + content_type: request.content_type.to_string(), + content: Some(request.content.value().unwrap_or_default().to_vec()), + content_type_hint: body::ContentTypeHint::Default as i32 + }), + generators: request.generators.iter().map(|(k, v)| { + (k.clone(), crate::proto::Generator { + r#type: v.name(), + values: Some(to_proto_struct(&v.values().iter() + .map(|(k, v)| (k.to_string(), v.clone())).collect())), + }) + }).collect(), + plugin_configuration: Some(crate::proto::PluginConfiguration { + pact_configuration: request.plugin_data.as_ref().map(to_proto_struct), + interaction_configuration: request.interaction_data.as_ref().map(to_proto_struct), + .. crate::proto::PluginConfiguration::default() + }), + test_context: Some(to_proto_struct(&request.test_context.iter().map(|(k, v)| (k.to_string(), v.clone())).collect())), + .. crate::proto::GenerateContentRequest::default() + }; + self.generate_content(request).await.map(|response| { + match response.contents { + Some(contents) => { + OptionalBody::Present( + Bytes::from(contents.content.unwrap_or_default()), + ContentType::parse(contents.content_type.as_str()).ok(), + None + ) + } + None => OptionalBody::Empty + } + }) + } + + async fn match_contents( + &self, + request: crate::plugin_models::CompareContentRequest + ) -> anyhow::Result { + let request = crate::proto::CompareContentsRequest { + expected: Some(Body { + content_type: request.expected_contents.content_type().unwrap_or_default().to_string(), + content: request.expected_contents.value().map(|b| b.to_vec()), + content_type_hint: body::ContentTypeHint::Default as i32 + }), + actual: Some(Body { + content_type: request.actual_contents.content_type().unwrap_or_default().to_string(), + content: request.actual_contents.value().map(|b| b.to_vec()), + content_type_hint: body::ContentTypeHint::Default as i32 + }), + allow_unexpected_keys: request.allow_unexpected_keys, + rules: request.matching_rules.iter().map(|(k, r)| { + (k.to_string(), crate::proto::MatchingRules { + rule: r.rules.iter().map(|rule|{ + crate::proto::MatchingRule { + r#type: rule.name(), + values: Some(to_proto_struct(&rule.values().iter().map(|(k, v)| (k.to_string(), v.clone())).collect())), + } + }).collect() + }) + }).collect(), + plugin_configuration: request.plugin_configuration.map(|config| PluginConfiguration { + interaction_configuration: Some(to_proto_struct(&config.interaction_configuration)), + pact_configuration: Some(to_proto_struct(&config.pact_configuration)) + }) + }; + self.compare_contents(request).await.map(|result| { + if let Some(mismatch) = result.type_mismatch { + CompareContentResult::TypeMismatch(mismatch.expected, mismatch.actual) + } else if !result.error.is_empty() { + CompareContentResult::Error(result.error.clone()) + } else if !result.results.is_empty() { + CompareContentResult::Mismatches( + result.results.iter().map(|(k, v)| { + (k.clone(), v.mismatches.iter().map(|mismatch| { + ContentMismatch { + expected: mismatch.expected.as_ref() + .map(|e| from_utf8(&e).unwrap_or_default().to_string()) + .unwrap_or_default(), + actual: mismatch.actual.as_ref() + .map(|a| from_utf8(&a).unwrap_or_default().to_string()) + .unwrap_or_default(), + mismatch: mismatch.mismatch.clone(), + path: mismatch.path.clone(), + diff: if mismatch.diff.is_empty() { + None + } else { + Some(mismatch.diff.clone()) + }, + mismatch_type: Some(mismatch.mismatch_type.clone()) + } + }).collect()) + }).collect() + ) + } else { + CompareContentResult::OK + } + }) + } + + async fn configure_interaction( + &self, + content_type: &ContentType, + definition: &HashMap + ) -> anyhow::Result<(Vec, Option)> { + let request = ConfigureInteractionRequest { + content_type: content_type.to_string(), + contents_config: Some(to_proto_struct(&definition)), + }; + match PactPluginRpc::configure_interaction(self, request).await { + Ok(response) => { + debug!("Got response: {:?}", response); + if response.error.is_empty() { + let mut results = vec![]; + + for response in &response.interaction { + let body = match &response.contents { + Some(body) => { + let returned_content_type = ContentType::parse(body.content_type.as_str()).ok(); + let contents = body.content.as_ref().cloned().unwrap_or_default(); + OptionalBody::Present(Bytes::from(contents), returned_content_type, + Some(match body.content_type_hint() { + body::ContentTypeHint::Text => ContentTypeHint::TEXT, + body::ContentTypeHint::Binary => ContentTypeHint::BINARY, + body::ContentTypeHint::Default => ContentTypeHint::DEFAULT, + })) + }, + None => OptionalBody::Missing + }; + + let rules = Self::setup_matching_rules(&response.rules)?; + + let generators = if !response.generators.is_empty() || !response.metadata_generators.is_empty() { + let mut categories = hashmap!{}; + + if !response.generators.is_empty() { + let mut generators = hashmap!{}; + for (k, gen) in &response.generators { + generators.insert(DocPath::new(k)?, + Generator::create(gen.r#type.as_str(), + &gen.values.as_ref().map(|attr| proto_struct_to_json(attr)).unwrap_or_default())?); + } + categories.insert(GeneratorCategory::BODY, generators); + } + + if !response.metadata_generators.is_empty() { + let mut generators = hashmap!{}; + for (k, gen) in &response.metadata_generators { + generators.insert(DocPath::new(k)?, + Generator::create(gen.r#type.as_str(), + &gen.values.as_ref().map(|attr| proto_struct_to_json(attr)).unwrap_or_default())?); + } + categories.insert(GeneratorCategory::METADATA, generators); + } + + Some(Generators { categories }) + } else { + None + }; + + let metadata = response.message_metadata.as_ref().map(|md| proto_struct_to_map(md)); + let metadata_rules = Self::setup_matching_rules(&response.metadata_rules)?; + + let plugin_config = if let Some(plugin_configuration) = &response.plugin_configuration { + crate::content::PluginConfiguration { + interaction_configuration: plugin_configuration.interaction_configuration.as_ref() + .map(|val| proto_struct_to_map(val)).unwrap_or_default(), + pact_configuration: plugin_configuration.pact_configuration.as_ref() + .map(|val| proto_struct_to_map(val)).unwrap_or_default() + } + } else { + crate::content::PluginConfiguration::default() + }; + + debug!("body={}", body); + debug!("rules={:?}", rules); + debug!("generators={:?}", generators); + debug!("metadata={:?}", metadata); + debug!("metadata_rules={:?}", metadata_rules); + debug!("pluginConfig={:?}", plugin_config); + + results.push(InteractionContents { + part_name: response.part_name.clone(), + body, + rules, + generators, + metadata, + metadata_rules, + plugin_config, + interaction_markup: response.interaction_markup.clone(), + interaction_markup_type: match response.interaction_markup_type() { + MarkupType::Html => "HTML".to_string(), + _ => "COMMON_MARK".to_string(), + } + }) + } + + Ok((results, response.plugin_configuration.map(|config| crate::content::PluginConfiguration::from(config)))) + } else { + Err(anyhow!("Request to configure interaction failed: {}", response.error)) + } + } + Err(err) => Err(err) + } + } + + async fn verify_interaction( + &self, + pact: &V4Pact, + interaction: &(dyn V4Interaction + Send + Sync), + verification_data: &InteractionVerificationData, + config: &HashMap + ) -> anyhow::Result { + let request = VerifyInteractionRequest { + pact: pact.to_json(PactSpecification::V4)?.to_string(), + interaction_key: interaction.unique_key(), + config: Some(to_proto_struct(config)), + interaction_data: Some(InteractionData { + body: Some((&verification_data.request_data).into()), + metadata: verification_data.metadata.iter().map(|(k, v)| { + (k.clone(), MetadataValue { value: Some(match v { + Either::Left(value) => metadata_value::Value::NonBinaryValue(to_proto_value(value)), + Either::Right(b) => metadata_value::Value::BinaryValue(b.to_vec()) + }) }) + }).collect() + }) + }; + + let response = PactPluginRpc::verify_interaction(self, request).await?; + let validation_response = response.response + .ok_or_else(|| anyhow!("Did not get a valid response from the verification call"))?; + match &validation_response { + verify_interaction_response::Response::Error(err) => Err(anyhow!("Failed to verify the request: {}", err)), + verify_interaction_response::Response::Result(data) => Ok(data.into()) + } + } + + async fn prepare_interaction_for_verification( + &self, + pact: &V4Pact, + interaction: &(dyn V4Interaction + Send + Sync), + context: &HashMap + ) -> anyhow::Result { + let request = VerificationPreparationRequest { + pact: pact.to_json(PactSpecification::V4)?.to_string(), + interaction_key: interaction.unique_key(), + config: Some(to_proto_struct(context)) + }; + + let response = PactPluginRpc::prepare_interaction_for_verification(self, request).await?; + let validation_response = response.response + .ok_or_else(|| anyhow!("Did not get a valid response from the prepare interaction for verification call"))?; + match &validation_response { + verification_preparation_response::Response::Error(err) => Err(anyhow!("Failed to prepare the request: {}", err)), + verification_preparation_response::Response::InteractionData(data) => { + let content_type = data.body.as_ref().and_then(|body| ContentType::parse(body.content_type.as_str()).ok()); + Ok(InteractionVerificationData { + request_data: data.body.as_ref() + .and_then(|body| body.content.as_ref()) + .map(|body| OptionalBody::Present(Bytes::from(body.clone()), content_type, None)).unwrap_or_default(), + metadata: data.metadata.iter().map(|(k, v)| { + let value = match &v.value { + Some(v) => match &v { + metadata_value::Value::NonBinaryValue(v) => Either::Left(proto_value_to_json(v)), + metadata_value::Value::BinaryValue(b) => Either::Right(Bytes::from(b.clone())) + } + None => Either::Left(Value::Null) + }; + (k.clone(), value) + }).collect() + }) + } + } + } + + async fn start_mock_server( + &self, + config: &MockServerConfig, + pact: Box, + test_context: HashMap + ) -> anyhow::Result { + let request = StartMockServerRequest { + host_interface: config.host_interface.clone().unwrap_or_default(), + port: config.port, + tls: config.tls, + pact: pact.to_json(PactSpecification::V4)?.to_string(), + test_context: Some(to_proto_struct(&test_context)) + }; + let response = PactPluginRpc::start_mock_server(self, request).await?; + let mock_server_response = response.response + .ok_or_else(|| anyhow!("Did not get a valid response from the start mock server call"))?; + match mock_server_response { + start_mock_server_response::Response::Error(err) => Err(anyhow!("Mock server failed to start: {}", err)), + start_mock_server_response::Response::Details(details) => Ok(MockServerDetails { + key: details.key.clone(), + base_url: details.address.clone(), + port: details.port, + plugin: self.boxed() + }) + } + } + + async fn get_mock_server_results( + &self, + mock_server_key: &str + ) -> anyhow::Result> { + let request = MockServerRequest { + server_key: mock_server_key.to_string() + }; + let response = PactPluginRpc::get_mock_server_results(self, request).await?; + if response.ok { + Ok(vec![]) + } else { + Ok(response.results.iter().map(|result| { + crate::mock_server::MockServerResults { + path: result.path.clone(), + error: result.error.clone(), + mismatches: result.mismatches.iter().map(|mismatch| { + ContentMismatch { + expected: mismatch.expected.as_ref() + .map(|e| from_utf8(e.as_slice()).unwrap_or_default().to_string()) + .unwrap_or_default(), + actual: mismatch.actual.as_ref() + .map(|a| from_utf8(a.as_slice()).unwrap_or_default().to_string()) + .unwrap_or_default(), + mismatch: mismatch.mismatch.clone(), + path: mismatch.path.clone(), + diff: optional_string(&mismatch.diff), + mismatch_type: optional_string(&mismatch.mismatch_type) + } + }).collect() + } + }).collect()) + } + } + + async fn shutdown_mock_server( + &self, + mock_server_key: &str + ) -> anyhow::Result> { + let request = ShutdownMockServerRequest { + server_key: mock_server_key.to_string() + }; + let response = PactPluginRpc::shutdown_mock_server(self, request).await?; + if response.ok { + Ok(vec![]) + } else { + Ok(response.results.iter().map(|result| { + crate::mock_server::MockServerResults { + path: result.path.clone(), + error: result.error.clone(), + mismatches: result.mismatches.iter().map(|mismatch| { + ContentMismatch { + expected: mismatch.expected.as_ref() + .map(|e| from_utf8(&e).unwrap_or_default().to_string()) + .unwrap_or_default(), + actual: mismatch.actual.as_ref() + .map(|a| from_utf8(&a).unwrap_or_default().to_string()) + .unwrap_or_default(), + mismatch: mismatch.mismatch.clone(), + path: mismatch.path.clone(), + diff: optional_string(&mismatch.diff), + mismatch_type: optional_string(&mismatch.mismatch_type) + } + }).collect() + } + }).collect()) + } + } +} + +/// Interceptor to inject the server key as an authorisation header +#[derive(Clone, Debug)] +struct PactPluginInterceptor { + /// Server key to inject + server_key: tonic::metadata::MetadataValue +} + +impl PactPluginInterceptor { + fn new(server_key: &str) -> anyhow::Result { + let token = tonic::metadata::MetadataValue::try_from(server_key)?; + Ok(PactPluginInterceptor { + server_key: token + }) + } +} + +impl Interceptor for PactPluginInterceptor { + fn call(&mut self, mut request: Request<()>) -> Result, Status> { + request.metadata_mut().insert("authorization", self.server_key.clone()); + Ok(request) + } +} + +/// Internal function: public for testing +pub async fn init_handshake(manifest: &PactPluginManifest, plugin: &mut (dyn PactPluginRpc + Send + Sync)) -> anyhow::Result<()> { + let request = InitPluginRequest { + implementation: "plugin-driver-rust".to_string(), + version: option_env!("CARGO_PKG_VERSION").unwrap_or("0").to_string() + }; + let response = plugin.init_plugin(request).await?; + debug!("Got init response {:?} from plugin {}", response, manifest.name); + register_plugin_entries(manifest, &response.catalogue); + Ok(()) +} + +#[cfg(not(windows))] +pub(crate) async fn start_plugin_process(manifest: &PactPluginManifest) -> anyhow::Result { + debug!("Starting plugin with manifest {:?}", manifest); + + let os_info = os_info::get(); + debug!("Detected OS: {}", os_info); + let mut path = if let Some(entry_point) = manifest.entry_points.get(&os_info.to_string()) { + PathBuf::from(entry_point) + } else if os_info.os_type() == Type::Windows && manifest.entry_points.contains_key("windows") { + PathBuf::from(manifest.entry_points.get("windows").unwrap()) + } else { + PathBuf::from(&manifest.entry_point) + }; + + if !path.is_absolute() || !path.exists() { + path = PathBuf::from(manifest.plugin_dir.clone()).join(path); + } + debug!("Starting plugin using {:?}", &path); + + let log_level = max_level(); + let mut child_command = Command::new(path.clone()); + let mut child_command = child_command + .env("LOG_LEVEL", log_level.to_string()) + .env("RUST_LOG", log_level.to_string()) + .current_dir(manifest.plugin_dir.clone()); + + if let Some(args) = &manifest.args { + child_command = child_command.args(args); + } + + let child = child_command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|err| anyhow!("Was not able to start plugin process for '{}' - {}", + path.to_string_lossy(), err))?; + let child_pid = child.id().unwrap_or_default(); + debug!("Plugin {} started with PID {}", manifest.name, child_pid); + + match ChildPluginProcess::new(child, manifest).await { + Ok(child) => Ok(GrpcPactPlugin::new(manifest, child)), + Err(err) => { + let mut s = System::new(); + s.refresh_processes(); + if let Some(process) = s.process(Pid::from_u32(child_pid)) { + process.kill_with(Signal::Term); + } else { + warn!("Child process with PID {} was not found", child_pid); + } + Err(err) + } + } +} + +#[cfg(windows)] +async fn start_plugin_process(manifest: &PactPluginManifest) -> anyhow::Result { + debug!("Starting plugin with manifest {:?}", manifest); + + let mut path = if let Some(entry) = manifest.entry_points.get("windows") { + PathBuf::from(entry) + } else { + PathBuf::from(&manifest.entry_point) + }; + if !path.is_absolute() || !path.exists() { + path = PathBuf::from(manifest.plugin_dir.clone()).join(path); + } + debug!("Starting plugin using {:?}", &path); + + let log_level = max_level(); + let mut child_command = Command::new(path.clone()); + let mut child_command = child_command + .env("LOG_LEVEL", log_level.to_string()) + .env("RUST_LOG", log_level.to_string()) + .current_dir(manifest.plugin_dir.clone()); + + if let Some(args) = &manifest.args { + child_command = child_command.args(args); + } + + let child = child_command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|err| anyhow!("Was not able to start plugin process for '{}' - {}", + path.to_string_lossy(), err))?; + let child_pid = child.id(); + debug!("Plugin {} started with PID {}", manifest.name, child_pid); + + match ChildPluginProcess::new(child, manifest).await { + Ok(child) => Ok(GrpcPactPlugin::new(manifest, child)), + Err(err) => { + let mut s = System::new(); + s.refresh_processes(); + if let Some(process) = s.process(Pid::from_u32(child_pid)) { + process.kill_with(Signal::Term); + } else { + warn!("Child process with PID {} was not found", child_pid); + } + Err(err) + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + +} diff --git a/drivers/rust/driver/src/lib.rs b/drivers/rust/driver/src/lib.rs index 70879891..0fc48bdc 100644 --- a/drivers/rust/driver/src/lib.rs +++ b/drivers/rust/driver/src/lib.rs @@ -2,10 +2,8 @@ pub mod plugin_models; pub mod plugin_manager; -#[cfg(not(windows))] -mod child_process; -#[cfg(windows)] -mod child_process_windows; +#[cfg(not(windows))] mod child_process; +#[cfg(windows)] mod child_process_windows; pub mod proto; pub mod catalogue_manager; pub mod content; @@ -15,3 +13,5 @@ pub mod mock_server; pub mod verification; pub mod repository; pub mod download; +mod grpc_plugin; +#[cfg(feature = "lua")] mod lua_plugin; diff --git a/drivers/rust/driver/src/lua_plugin.rs b/drivers/rust/driver/src/lua_plugin.rs new file mode 100644 index 00000000..e69de29b diff --git a/drivers/rust/driver/src/mock_server.rs b/drivers/rust/driver/src/mock_server.rs index 8c478f17..8c98f281 100644 --- a/drivers/rust/driver/src/mock_server.rs +++ b/drivers/rust/driver/src/mock_server.rs @@ -19,7 +19,7 @@ pub struct MockServerConfig { } /// Details of the running mock server -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct MockServerDetails { /// Unique key for the mock server pub key: String, @@ -28,7 +28,7 @@ pub struct MockServerDetails { /// Port the mock server is running on pub port: u32, /// Plugin the mock server belongs to - pub plugin: PactPlugin + pub plugin: Box } /// Results from the mock server diff --git a/drivers/rust/driver/src/plugin_manager.rs b/drivers/rust/driver/src/plugin_manager.rs index 36c431f1..4ff27a59 100644 --- a/drivers/rust/driver/src/plugin_manager.rs +++ b/drivers/rust/driver/src/plugin_manager.rs @@ -5,66 +5,53 @@ use std::fs; use std::fs::File; use std::io::{BufReader, Write}; use std::path::PathBuf; -use std::process::Stdio; -#[cfg(windows)] use std::process::Command; -use std::str::from_utf8; use std::str::FromStr; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use std::thread; use anyhow::{anyhow, bail, Context}; -use bytes::Bytes; -use itertools::Either; use lazy_static::lazy_static; -use log::max_level; use maplit::hashmap; -#[cfg(not(windows))] use os_info::Type; -use pact_models::bodies::OptionalBody; use pact_models::json_utils::json_to_string; -use pact_models::PactSpecification; -use pact_models::prelude::{ContentType, Pact}; +use pact_models::prelude::Pact; use pact_models::prelude::v4::V4Pact; use pact_models::v4::interaction::V4Interaction; use reqwest::Client; use semver::Version; use serde_json::Value; -use sysinfo::{Pid, Signal, System}; -#[cfg(not(windows))] use tokio::process::Command; use tracing::{debug, info, trace, warn}; -use crate::catalogue_manager::{all_entries, CatalogueEntry, register_plugin_entries, remove_plugin_entries}; -#[cfg(not(windows))] use crate::child_process::ChildPluginProcess; -#[cfg(windows)] use crate::child_process_windows::ChildPluginProcess; -use crate::content::ContentMismatch; +use crate::catalogue_manager::{all_entries, CatalogueEntry, remove_plugin_entries}; use crate::download::{download_json_from_github, download_plugin_executable, fetch_json_from_url}; +use crate::grpc_plugin::{init_handshake, start_plugin_process}; use crate::metrics::send_metrics; use crate::mock_server::{MockServerConfig, MockServerDetails, MockServerResults}; -use crate::plugin_models::{PactPlugin, PactPluginManifest, PactPluginRpc, PluginDependency}; -use crate::proto::*; +use crate::plugin_models::{PactPlugin, PactPluginManifest, PluginDependency}; use crate::repository::{fetch_repository_index, USER_AGENT}; -use crate::utils::{optional_string, proto_value_to_json, to_proto_struct, to_proto_value, versions_compatible}; +use crate::utils::versions_compatible; use crate::verification::{InteractionVerificationData, InteractionVerificationResult}; lazy_static! { static ref PLUGIN_MANIFEST_REGISTER: Mutex> = Mutex::new(HashMap::new()); - static ref PLUGIN_REGISTER: Mutex> = Mutex::new(HashMap::new()); + pub static ref PLUGIN_REGISTER: Mutex>> = Mutex::new(HashMap::new()); } /// Load the plugin defined by the dependency information. Will first look in the global /// plugin registry. -pub async fn load_plugin(plugin: &PluginDependency) -> anyhow::Result { +pub async fn load_plugin<'a>(plugin: &PluginDependency) -> anyhow::Result> { let thread_id = thread::current().id(); debug!("Loading plugin {:?}", plugin); trace!("Rust plugin driver version {}", option_env!("CARGO_PKG_VERSION").unwrap_or_default()); trace!("load_plugin {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); let mut inner = PLUGIN_REGISTER.lock().unwrap(); trace!("load_plugin {:?}: Got PLUGIN_REGISTER lock", thread_id); - let result = match lookup_plugin_inner(plugin, &mut inner) { - Some(plugin) => { - debug!("Found running plugin {:?}", plugin); - plugin.update_access(); - Ok(plugin.clone()) - }, + let update_access = |plugin: &mut (dyn PactPlugin + Send + Sync)| { + debug!("Found running plugin {:?}", plugin); + plugin.update_access(); + plugin.arced() + }; + let result = match with_plugin_mut(plugin, &mut inner, &update_access) { + Some(plugin) => Ok(plugin), None => { debug!("Did not find plugin, will attempt to start it"); let manifest = match load_plugin_manifest(plugin) { @@ -92,29 +79,48 @@ pub async fn load_plugin(plugin: &PluginDependency) -> anyhow::Result( +fn lookup_plugin_inner( + plugin: &PluginDependency, + plugin_register: &HashMap> +) -> Option> { + if let Some(version) = &plugin.version { + plugin_register.get(format!("{}/{}", plugin.name, version).as_str()) + .map(|plugin| plugin.clone()) + } else { + plugin_register.iter() + .filter(|(_, value)| value.manifest().name == plugin.name) + .max_by(|(_, v1), (_, v2)| v1.manifest().version.cmp(&v2.manifest().version)) + .map(|(_, plugin)| plugin.clone()) + } +} + +fn with_plugin_mut( plugin: &PluginDependency, - plugin_register: &'a mut HashMap -) -> Option<&'a mut PactPlugin> { + plugin_register: &mut HashMap>, + f: &dyn Fn(&mut (dyn PactPlugin + Send + Sync)) -> R +) -> Option { if let Some(version) = &plugin.version { plugin_register.get_mut(format!("{}/{}", plugin.name, version).as_str()) + .map(|plugin| Arc::get_mut(plugin).map(|inner| f(inner))) + .flatten() } else { plugin_register.iter_mut() - .filter(|(_, value)| value.manifest.name == plugin.name) - .max_by(|(_, v1), (_, v2)| v1.manifest.version.cmp(&v2.manifest.version)) - .map(|(_, plugin)| plugin) + .filter(|(_, value)| value.manifest().name == plugin.name) + .max_by(|(_, v1), (_, v2)| v1.manifest().version.cmp(&v2.manifest().version)) + .map(|(_, plugin)| Arc::get_mut(plugin).map(|inner| f(inner))) + .flatten() } } /// Look up the plugin in the global plugin register -pub fn lookup_plugin(plugin: &PluginDependency) -> Option { +pub fn lookup_plugin<'a>(plugin: &PluginDependency) -> Option> { let thread_id = thread::current().id(); trace!("lookup_plugin {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); let mut inner = PLUGIN_REGISTER.lock().unwrap(); trace!("lookup_plugin {:?}: Got PLUGIN_REGISTER lock", thread_id); let entry = lookup_plugin_inner(plugin, &mut inner); trace!("lookup_plugin {:?}: Releasing PLUGIN_REGISTER lock", thread_id); - entry.cloned() + entry } /// Return the plugin manifest for the given plugin. Will first look in the global plugin manifest @@ -207,10 +213,10 @@ pub fn lookup_plugin_manifest(plugin: &PluginDependency) -> Option( manifest: &PactPluginManifest, - plugin_register: &mut HashMap -) -> anyhow::Result { + plugin_register: &'a mut HashMap> +) -> anyhow::Result> { match manifest.executable_type.as_str() { "exec" => { let mut plugin = start_plugin_process(manifest).await?; @@ -220,129 +226,23 @@ async fn initialise_plugin( plugin.kill(); anyhow!("Failed to send init request to the plugin - {}", err) })?; + publish_updated_catalogue(); + let arc = Arc::new(plugin); let key = format!("{}/{}", manifest.name, manifest.version); - plugin_register.insert(key, plugin.clone()); + plugin_register.insert(key, arc.clone()); - Ok(plugin) + Ok(arc) } - _ => Err(anyhow!("Plugin executable type of {} is not supported", manifest.executable_type)) - } -} - -/// Internal function: public for testing -pub async fn init_handshake(manifest: &PactPluginManifest, plugin: &mut (dyn PactPluginRpc + Send + Sync)) -> anyhow::Result<()> { - let request = InitPluginRequest { - implementation: "plugin-driver-rust".to_string(), - version: option_env!("CARGO_PKG_VERSION").unwrap_or("0").to_string() - }; - let response = plugin.init_plugin(request).await?; - debug!("Got init response {:?} from plugin {}", response, manifest.name); - register_plugin_entries(manifest, &response.catalogue); - tokio::task::spawn(publish_updated_catalogue()); - Ok(()) -} - -#[cfg(not(windows))] -async fn start_plugin_process(manifest: &PactPluginManifest) -> anyhow::Result { - debug!("Starting plugin with manifest {:?}", manifest); - - let os_info = os_info::get(); - debug!("Detected OS: {}", os_info); - let mut path = if let Some(entry_point) = manifest.entry_points.get(&os_info.to_string()) { - PathBuf::from(entry_point) - } else if os_info.os_type() == Type::Windows && manifest.entry_points.contains_key("windows") { - PathBuf::from(manifest.entry_points.get("windows").unwrap()) - } else { - PathBuf::from(&manifest.entry_point) - }; - - if !path.is_absolute() || !path.exists() { - path = PathBuf::from(manifest.plugin_dir.clone()).join(path); - } - debug!("Starting plugin using {:?}", &path); - - let log_level = max_level(); - let mut child_command = Command::new(path.clone()); - let mut child_command = child_command - .env("LOG_LEVEL", log_level.to_string()) - .env("RUST_LOG", log_level.to_string()) - .current_dir(manifest.plugin_dir.clone()); - - if let Some(args) = &manifest.args { - child_command = child_command.args(args); - } - - let child = child_command - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|err| anyhow!("Was not able to start plugin process for '{}' - {}", - path.to_string_lossy(), err))?; - let child_pid = child.id().unwrap_or_default(); - debug!("Plugin {} started with PID {}", manifest.name, child_pid); - - match ChildPluginProcess::new(child, manifest).await { - Ok(child) => Ok(PactPlugin::new(manifest, child)), - Err(err) => { - let mut s = System::new(); - s.refresh_processes(); - if let Some(process) = s.process(Pid::from_u32(child_pid)) { - process.kill_with(Signal::Term); - } else { - warn!("Child process with PID {} was not found", child_pid); + "lua" => { + #[cfg(feature = "lua")] { + todo!() } - Err(err) - } - } -} - -#[cfg(windows)] -async fn start_plugin_process(manifest: &PactPluginManifest) -> anyhow::Result { - debug!("Starting plugin with manifest {:?}", manifest); - - let mut path = if let Some(entry) = manifest.entry_points.get("windows") { - PathBuf::from(entry) - } else { - PathBuf::from(&manifest.entry_point) - }; - if !path.is_absolute() || !path.exists() { - path = PathBuf::from(manifest.plugin_dir.clone()).join(path); - } - debug!("Starting plugin using {:?}", &path); - - let log_level = max_level(); - let mut child_command = Command::new(path.clone()); - let mut child_command = child_command - .env("LOG_LEVEL", log_level.to_string()) - .env("RUST_LOG", log_level.to_string()) - .current_dir(manifest.plugin_dir.clone()); - - if let Some(args) = &manifest.args { - child_command = child_command.args(args); - } - - let child = child_command - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|err| anyhow!("Was not able to start plugin process for '{}' - {}", - path.to_string_lossy(), err))?; - let child_pid = child.id(); - debug!("Plugin {} started with PID {}", manifest.name, child_pid); - - match ChildPluginProcess::new(child, manifest).await { - Ok(child) => Ok(PactPlugin::new(manifest, child)), - Err(err) => { - let mut s = System::new(); - s.refresh_processes(); - if let Some(process) = s.process(Pid::from_u32(child_pid)) { - process.kill_with(Signal::Term); - } else { - warn!("Child process with PID {} was not found", child_pid); + #[cfg(not(feature = "lua"))] { + Err(anyhow!("Lua plugins are not supported (Lua feature flag is not enabled)")) } - Err(err) } + _ => Err(anyhow!("Plugin executable type of {} is not supported", manifest.executable_type)) } } @@ -351,64 +251,56 @@ pub fn shutdown_plugins() { let thread_id = thread::current().id(); debug!("Shutting down all plugins"); trace!("shutdown_plugins {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); - let mut guard = PLUGIN_REGISTER.lock().unwrap(); + let mut guard = crate::plugin_manager::PLUGIN_REGISTER.lock().unwrap(); trace!("shutdown_plugins {:?}: Got PLUGIN_REGISTER lock", thread_id); for plugin in guard.values() { debug!("Shutting down plugin {:?}", plugin); plugin.kill(); - remove_plugin_entries(&plugin.manifest.name); + remove_plugin_entries(&plugin.manifest().name); } guard.clear(); trace!("shutdown_plugins {:?}: Releasing PLUGIN_REGISTER lock", thread_id); } /// Shutdown the given plugin -pub fn shutdown_plugin(plugin: &mut PactPlugin) { - debug!("Shutting down plugin {}:{}", plugin.manifest.name, plugin.manifest.version); +pub fn shutdown_plugin(plugin: &(dyn PactPlugin + Send + Sync)) { + debug!("Shutting down plugin {}:{}", plugin.manifest().name, plugin.manifest().version); plugin.kill(); - remove_plugin_entries(&plugin.manifest.name); + remove_plugin_entries(&plugin.manifest().name); } /// Publish the current catalogue to all plugins -pub async fn publish_updated_catalogue() { +pub fn publish_updated_catalogue() { let thread_id = thread::current().id(); + let catalogue = all_entries(); - let request = Catalogue { - catalogue: all_entries().iter() - .map(|entry| crate::proto::CatalogueEntry { - r#type: entry.entry_type.to_proto_type() as i32, - key: entry.key.clone(), - values: entry.values.clone() - }).collect() - }; + trace!("publish_updated_catalogue {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); + let inner = PLUGIN_REGISTER.lock().unwrap(); + trace!("publish_updated_catalogue {:?}: Got PLUGIN_REGISTER lock", thread_id); + for plugin in inner.values() { + tokio::task::spawn(publish_catalogue_to_plugin(catalogue.clone(), plugin.clone())); + } - let plugins = { - trace!("publish_updated_catalogue {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); - let inner = PLUGIN_REGISTER.lock().unwrap(); - trace!("publish_updated_catalogue {:?}: Got PLUGIN_REGISTER lock", thread_id); - let plugins = inner.values().cloned().collect::>(); - trace!("publish_updated_catalogue {:?}: Releasing PLUGIN_REGISTER lock", thread_id); - plugins - }; + trace!("publish_updated_catalogue {:?}: Releasing PLUGIN_REGISTER lock", thread_id); +} - for plugin in plugins { - if let Err(err) = plugin.update_catalogue(request.clone()).await { - warn!("Failed to send updated catalogue to plugin '{}' - {}", plugin.manifest.name, err); - } +async fn publish_catalogue_to_plugin(catalogue: Vec, plugin: Arc) { + if let Err(err) = plugin.publish_updated_catalogue(catalogue.as_slice()).await { + warn!("Failed to send updated catalogue to plugin '{}' - {}", plugin.manifest().name, err); } } /// Increment access to the plugin. #[tracing::instrument] -pub fn increment_plugin_access(plugin: &PluginDependency) { +pub fn increment_plugin_access(plugin_dep: &PluginDependency) { let thread_id = thread::current().id(); trace!("increment_plugin_access {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); let mut inner = PLUGIN_REGISTER.lock().unwrap(); trace!("increment_plugin_access {:?}: Got PLUGIN_REGISTER lock", thread_id); - if let Some(plugin) = lookup_plugin_inner(plugin, &mut inner) { - plugin.update_access(); + if with_plugin_mut(plugin_dep, &mut inner, &|plugin| plugin.update_access()).is_none() { + warn!("Plugin {} was not found", plugin_dep); } trace!("increment_plugin_access {:?}: Releasing PLUGIN_REGISTER lock", thread_id); @@ -416,19 +308,27 @@ pub fn increment_plugin_access(plugin: &PluginDependency) { /// Decrement access to the plugin. If the current access count is zero, shut down the plugin #[tracing::instrument] -pub fn drop_plugin_access(plugin: &PluginDependency) { +pub fn drop_plugin_access(plugin_dep: &PluginDependency) { let thread_id = thread::current().id(); trace!("drop_plugin_access {:?}: Waiting on PLUGIN_REGISTER lock", thread_id); let mut inner = PLUGIN_REGISTER.lock().unwrap(); trace!("drop_plugin_access {:?}: Got PLUGIN_REGISTER lock", thread_id); - if let Some(plugin) = lookup_plugin_inner(plugin, &mut inner) { - let key = format!("{}/{}", plugin.manifest.name, plugin.manifest.version); + match with_plugin_mut(plugin_dep, &mut inner, &|plugin| { if plugin.drop_access() == 0 { shutdown_plugin(plugin); - inner.remove(key.as_str()); + Some(format!("{}/{}", plugin.manifest().name, plugin.manifest().version)) + } else { + None + } + }) { + Some(dropped) => { + if let Some(key) = dropped { + inner.remove(key.as_str()); + } } + None => warn!("Plugin {} was not found", plugin_dep) } trace!("drop_plugin_access {:?}: Releasing PLUGIN_REGISTER lock", thread_id); @@ -458,109 +358,39 @@ pub async fn start_mock_server_v2( debug!(plugin_name = manifest.name.as_str(), plugin_version = manifest.version.as_str(), "Sending startMockServer request to plugin"); - let request = StartMockServerRequest { - host_interface: config.host_interface.unwrap_or_default(), - port: config.port, - tls: config.tls, - pact: pact.to_json(PactSpecification::V4)?.to_string(), - test_context: Some(to_proto_struct(&test_context)) - }; - let response = plugin.start_mock_server(request).await?; + let response = plugin.as_ref().start_mock_server( + &config, + pact, + test_context + ).await; debug!("Got response ${response:?}"); - - let mock_server_response = response.response - .ok_or_else(|| anyhow!("Did not get a valid response from the start mock server call"))?; - match mock_server_response { - start_mock_server_response::Response::Error(err) => Err(anyhow!("Mock server failed to start: {}", err)), - start_mock_server_response::Response::Details(details) => Ok(MockServerDetails { - key: details.key.clone(), - base_url: details.address.clone(), - port: details.port, - plugin - }) - } + response } /// Shutdowns a running mock server. Will return any errors from the mock server. pub async fn shutdown_mock_server(mock_server: &MockServerDetails) -> anyhow::Result> { - let request = ShutdownMockServerRequest { - server_key: mock_server.key.to_string() - }; - debug!( - plugin_name = mock_server.plugin.manifest.name.as_str(), - plugin_version = mock_server.plugin.manifest.version.as_str(), + plugin_name = mock_server.plugin.manifest().name.as_str(), + plugin_version = mock_server.plugin.manifest().version.as_str(), server_key = mock_server.key.as_str(), "Sending shutdownMockServer request to plugin" ); - let response = mock_server.plugin.shutdown_mock_server(request).await?; + let response = mock_server.plugin.shutdown_mock_server(mock_server.key.as_str()).await; debug!("Got response: {response:?}"); - - if response.ok { - Ok(vec![]) - } else { - Ok(response.results.iter().map(|result| { - MockServerResults { - path: result.path.clone(), - error: result.error.clone(), - mismatches: result.mismatches.iter().map(|mismatch| { - ContentMismatch { - expected: mismatch.expected.as_ref() - .map(|e| from_utf8(&e).unwrap_or_default().to_string()) - .unwrap_or_default(), - actual: mismatch.actual.as_ref() - .map(|a| from_utf8(&a).unwrap_or_default().to_string()) - .unwrap_or_default(), - mismatch: mismatch.mismatch.clone(), - path: mismatch.path.clone(), - diff: optional_string(&mismatch.diff), - mismatch_type: optional_string(&mismatch.mismatch_type) - } - }).collect() - } - }).collect()) - } + response } /// Gets the results from a running mock server. pub async fn get_mock_server_results(mock_server: &MockServerDetails) -> anyhow::Result> { - let request = MockServerRequest { - server_key: mock_server.key.to_string() - }; - debug!( - plugin_name = mock_server.plugin.manifest.name.as_str(), - plugin_version = mock_server.plugin.manifest.version.as_str(), + plugin_name = mock_server.plugin.manifest().name.as_str(), + plugin_version = mock_server.plugin.manifest().version.as_str(), server_key = mock_server.key.as_str(), "Sending getMockServerResults request to plugin" ); - let response = mock_server.plugin.get_mock_server_results(request).await?; + let response = mock_server.plugin.get_mock_server_results(mock_server.key.as_str()).await; debug!("Got response: {response:?}"); - - if response.ok { - Ok(vec![]) - } else { - Ok(response.results.iter().map(|result| { - MockServerResults { - path: result.path.clone(), - error: result.error.clone(), - mismatches: result.mismatches.iter().map(|mismatch| { - ContentMismatch { - expected: mismatch.expected.as_ref() - .map(|e| from_utf8(&e).unwrap_or_default().to_string()) - .unwrap_or_default(), - actual: mismatch.actual.as_ref() - .map(|a| from_utf8(&a).unwrap_or_default().to_string()) - .unwrap_or_default(), - mismatch: mismatch.mismatch.clone(), - path: mismatch.path.clone(), - diff: optional_string(&mismatch.diff), - mismatch_type: optional_string(&mismatch.mismatch_type) - } - }).collect() - } - }).collect()) - } + response } /// Sets up a transport request to be made. This is the first phase when verifying, and it allows the @@ -576,11 +406,11 @@ pub async fn prepare_validation_for_interaction( let plugin = lookup_plugin(&manifest.as_dependency()) .ok_or_else(|| anyhow!("Did not find a running plugin for manifest {:?}", manifest))?; - prepare_validation_for_interaction_inner(&plugin, manifest, pact, interaction, context).await + prepare_verification_for_interaction_inner(plugin.as_ref(), manifest, pact, interaction, context).await } -pub(crate) async fn prepare_validation_for_interaction_inner( - plugin: &(dyn PactPluginRpc + Send + Sync), +pub(crate) async fn prepare_verification_for_interaction_inner( + plugin: &(dyn PactPlugin + Send + Sync), manifest: &PactPluginManifest, pact: &V4Pact, interaction: &(dyn V4Interaction + Send + Sync), @@ -588,40 +418,13 @@ pub(crate) async fn prepare_validation_for_interaction_inner( ) -> anyhow::Result { let mut pact = pact.clone(); pact.interactions = pact.interactions.iter().map(|i| i.with_unique_key()).collect(); - let request = VerificationPreparationRequest { - pact: pact.to_json(PactSpecification::V4)?.to_string(), - interaction_key: interaction.unique_key(), - config: Some(to_proto_struct(context)) - }; debug!(plugin_name = manifest.name.as_str(), plugin_version = manifest.version.as_str(), - "Sending prepareValidationForInteraction request to plugin"); - let response = plugin.prepare_interaction_for_verification(request).await?; + "Sending prepare verification for interaction request to plugin"); + let response = plugin.prepare_interaction_for_verification(&pact, interaction, context).await; debug!("Got response: {response:?}"); - let validation_response = response.response - .ok_or_else(|| anyhow!("Did not get a valid response from the prepare interaction for verification call"))?; - match &validation_response { - verification_preparation_response::Response::Error(err) => Err(anyhow!("Failed to prepare the request: {}", err)), - verification_preparation_response::Response::InteractionData(data) => { - let content_type = data.body.as_ref().and_then(|body| ContentType::parse(body.content_type.as_str()).ok()); - Ok(InteractionVerificationData { - request_data: data.body.as_ref() - .and_then(|body| body.content.as_ref()) - .map(|body| OptionalBody::Present(Bytes::from(body.clone()), content_type, None)).unwrap_or_default(), - metadata: data.metadata.iter().map(|(k, v)| { - let value = match &v.value { - Some(v) => match &v { - metadata_value::Value::NonBinaryValue(v) => Either::Left(proto_value_to_json(v)), - metadata_value::Value::BinaryValue(b) => Either::Right(Bytes::from(b.clone())) - } - None => Either::Left(Value::Null) - }; - (k.clone(), value) - }).collect() - }) - } - } + response } /// Executes the verification of the interaction that was configured with the prepare_validation_for_interaction call @@ -638,7 +441,7 @@ pub async fn verify_interaction( .ok_or_else(|| anyhow!("Did not find a running plugin for manifest {:?}", manifest))?; verify_interaction_inner( - &plugin, + plugin.as_ref(), &manifest, verification_data, config, @@ -648,7 +451,7 @@ pub async fn verify_interaction( } pub(crate) async fn verify_interaction_inner( - plugin: &(dyn PactPluginRpc + Send + Sync), + plugin: &(dyn PactPlugin + Send + Sync), manifest: &PactPluginManifest, verification_data: &InteractionVerificationData, config: &HashMap, @@ -657,32 +460,12 @@ pub(crate) async fn verify_interaction_inner( ) -> anyhow::Result { let mut pact = pact.clone(); pact.interactions = pact.interactions.iter().map(|i| i.with_unique_key()).collect(); - let request = VerifyInteractionRequest { - pact: pact.to_json(PactSpecification::V4)?.to_string(), - interaction_key: interaction.unique_key(), - config: Some(to_proto_struct(config)), - interaction_data: Some(InteractionData { - body: Some((&verification_data.request_data).into()), - metadata: verification_data.metadata.iter().map(|(k, v)| { - (k.clone(), MetadataValue { value: Some(match v { - Either::Left(value) => metadata_value::Value::NonBinaryValue(to_proto_value(value)), - Either::Right(b) => metadata_value::Value::BinaryValue(b.to_vec()) - }) }) - }).collect() - }) - }; debug!(plugin_name = manifest.name.as_str(), plugin_version = manifest.version.as_str(), "Sending verifyInteraction request to plugin"); - let response = plugin.verify_interaction(request).await?; + let response = plugin.verify_interaction(&pact, interaction, verification_data, config).await; debug!("Got response: {response:?}"); - - let validation_response = response.response - .ok_or_else(|| anyhow!("Did not get a valid response from the verification call"))?; - match &validation_response { - verify_interaction_response::Response::Error(err) => Err(anyhow!("Failed to verify the request: {}", err)), - verify_interaction_response::Response::Result(data) => Ok(data.into()) - } + response } /// Tries to download and install the plugin from the given URL, returning the manifest for the @@ -747,23 +530,29 @@ fn create_plugin_dir(manifest: &PactPluginManifest) -> anyhow::Result { #[cfg(test)] mod tests { + use std::collections::HashMap; use std::fs::{self, File}; + use std::sync::{Arc, RwLock}; + use async_trait::async_trait; + use expectest::prelude::*; + use lazy_static::lazy_static; use maplit::hashmap; - use pact_models::v4::sync_message::SynchronousMessage; + use pact_models::bodies::OptionalBody; + use pact_models::content_types::ContentType; + use pact_models::pact::Pact; use pact_models::prelude::v4::V4Pact; use pact_models::v4::interaction::V4Interaction; - - use expectest::prelude::*; + use pact_models::v4::sync_message::SynchronousMessage; + use serde_json::Value; use tempdir::TempDir; + use crate::content::InteractionContents; - use crate::plugin_models::PluginDependency; - use crate::plugin_manager::prepare_validation_for_interaction_inner; + use crate::mock_server::MockServerConfig; + use crate::plugin_manager::prepare_verification_for_interaction_inner; use crate::plugin_manager::verify_interaction_inner; - use crate::plugin_models::tests::MockPlugin; - use crate::plugin_models::tests::PREPARE_INTERACTION_FOR_VERIFICATION_ARG; - use crate::plugin_models::tests::VERIFY_INTERACTION_ARG; - use crate::verification::InteractionVerificationData; + use crate::plugin_models::{CompareContentRequest, CompareContentResult, PactPlugin, PluginDependency}; + use crate::verification::{InteractionVerificationData, InteractionVerificationResult}; use super::{ load_manifest_from_dir, @@ -834,6 +623,89 @@ mod tests { expect!(result.version).to(be_equal_to("0.1.20")); } + lazy_static!{ + pub(crate) static ref PREPARE_INTERACTION_FOR_VERIFICATION_ARG: RwLock> = RwLock::new(None); + pub(crate) static ref VERIFY_INTERACTION_ARG: RwLock> = RwLock::new(None); + } + + #[derive(Default, Debug, Clone)] + pub(crate) struct MockPlugin {} + + #[async_trait] + impl PactPlugin for MockPlugin { + fn manifest(&self) -> PactPluginManifest { + unimplemented!() + } + + fn kill(&self) { + unimplemented!() + } + + fn update_access(&mut self) { + unimplemented!() + } + + fn drop_access(&mut self) -> usize { + unimplemented!() + } + + fn boxed(&self) -> Box { + Box::new(self.clone()) + } + + fn arced(&self) -> Arc { + Arc::new(self.clone()) + } + + async fn publish_updated_catalogue(&self, _catalogue: &[crate::catalogue_manager::CatalogueEntry]) -> anyhow::Result<()> { + unimplemented!() + } + + async fn generate_contents(&self, _request: crate::plugin_models::GenerateContentRequest) -> anyhow::Result { + unimplemented!() + } + + async fn match_contents(&self, _request: CompareContentRequest) -> anyhow::Result { + unimplemented!() + } + + async fn configure_interaction(&self, _content_type: &ContentType, _definition: &HashMap) -> anyhow::Result<(Vec, Option)> { + unimplemented!() + } + + async fn verify_interaction(&self, pact: &V4Pact, _interaction: &(dyn V4Interaction + Send + Sync), _verification_data: &InteractionVerificationData, _config: &HashMap) -> anyhow::Result { + let mut w = VERIFY_INTERACTION_ARG.write().unwrap(); + let _ = w.insert(pact.clone()); + Ok(InteractionVerificationResult { + ok: true, + details: vec![], + output: vec![] + }) + } + + async fn prepare_interaction_for_verification(&self, pact: &V4Pact, _interaction: &(dyn V4Interaction + Send + Sync), _context: &HashMap) -> anyhow::Result { + let mut w = PREPARE_INTERACTION_FOR_VERIFICATION_ARG.write().unwrap(); + let _ = w.insert(pact.clone()); + Ok(InteractionVerificationData { + request_data: Default::default(), + metadata: Default::default(), + }) + } + + async fn start_mock_server(&self, _config: &MockServerConfig, _pact: Box, _test_context: HashMap) -> anyhow::Result { + unimplemented!() + } + + + async fn get_mock_server_results(&self, _mock_server_key: &str) -> anyhow::Result> { + unimplemented!() + } + + async fn shutdown_mock_server(&self, _mock_server_key: &str) -> anyhow::Result> { + unimplemented!() + } + } + #[test_log::test(tokio::test)] async fn prepare_validation_for_interaction_passes_in_pact_with_interaction_keys_set() { let manifest = PactPluginManifest { @@ -854,7 +726,7 @@ mod tests { }; let context = hashmap!{}; - let result = prepare_validation_for_interaction_inner( + let result = prepare_verification_for_interaction_inner( &mock_plugin, &manifest, &pact, @@ -863,12 +735,9 @@ mod tests { ).await; expect!(result).to(be_ok()); - let request = { - let r = PREPARE_INTERACTION_FOR_VERIFICATION_ARG.read().unwrap(); - r.clone() - }.unwrap(); - let pact_in = V4Pact::pact_from_json(&serde_json::from_str(request.pact.as_str()).unwrap(), "").unwrap(); - expect!(pact_in.interactions[0].key().unwrap()).to(be_equal_to(request.interaction_key)); + let r = PREPARE_INTERACTION_FOR_VERIFICATION_ARG.read().unwrap(); + let pact_in = r.as_ref().unwrap(); + expect!(pact_in.interactions[0].key()).to(be_some()); } #[test_log::test(tokio::test)] @@ -902,11 +771,8 @@ mod tests { ).await; expect!(result).to(be_ok()); - let request = { - let r = VERIFY_INTERACTION_ARG.read().unwrap(); - r.clone() - }.unwrap(); - let pact_in = V4Pact::pact_from_json(&serde_json::from_str(request.pact.as_str()).unwrap(), "").unwrap(); - expect!(pact_in.interactions[0].key().unwrap()).to(be_equal_to(request.interaction_key)); + let r = VERIFY_INTERACTION_ARG.read().unwrap(); + let pact_in = r.as_ref().unwrap(); + expect!(pact_in.interactions[0].key()).to(be_some()); } } diff --git a/drivers/rust/driver/src/plugin_models.rs b/drivers/rust/driver/src/plugin_models.rs index a87bf801..a8663a0a 100644 --- a/drivers/rust/driver/src/plugin_models.rs +++ b/drivers/rust/driver/src/plugin_models.rs @@ -1,27 +1,25 @@ //! Models for representing plugins use std::collections::HashMap; -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; -use anyhow::anyhow; use async_trait::async_trait; +use pact_models::bodies::OptionalBody; +use pact_models::content_types::ContentType; +use pact_models::generators::Generator; +use pact_models::matchingrules::RuleList; +use pact_models::pact::Pact; +use pact_models::path_exp::DocPath; +use pact_models::prelude::v4::V4Pact; +use pact_models::v4::interaction::V4Interaction; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tonic::{Request, Status}; -use tonic::codegen::InterceptedService; -use tonic::metadata::{Ascii, MetadataValue}; -use tonic::service::Interceptor; -use tonic::transport::Channel; -use tracing::{debug, trace}; - -#[cfg(not(windows))] -use crate::child_process::ChildPluginProcess; -#[cfg(windows)] -use crate::child_process_windows::ChildPluginProcess; -use crate::proto::*; -use crate::proto::pact_plugin_client::PactPluginClient; + +use crate::catalogue_manager::CatalogueEntry; +use crate::content::{ContentMismatch, InteractionContents, PluginConfiguration}; +use crate::mock_server::{MockServerConfig, MockServerDetails, MockServerResults}; +use crate::verification::{InteractionVerificationData, InteractionVerificationResult}; /// Type of plugin dependencies #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] @@ -134,208 +132,121 @@ impl Default for PactPluginManifest { } } -/// Trait with remote-calling methods for a running plugin -#[async_trait] -pub trait PactPluginRpc { - /// Send an init request to the plugin process - async fn init_plugin(&mut self, request: InitPluginRequest) -> anyhow::Result; - - /// Send a compare contents request to the plugin process - async fn compare_contents(&self, request: CompareContentsRequest) -> anyhow::Result; - - /// Send a configure contents request to the plugin process - async fn configure_interaction(&self, request: ConfigureInteractionRequest) -> anyhow::Result; - - /// Send a generate content request to the plugin - async fn generate_content(&self, request: GenerateContentRequest) -> anyhow::Result; - - /// Start a mock server - async fn start_mock_server(&self, request: StartMockServerRequest) -> anyhow::Result; - - /// Shutdown a running mock server - async fn shutdown_mock_server(&self, request: ShutdownMockServerRequest) -> anyhow::Result; - - /// Get the matching results from a running mock server - async fn get_mock_server_results(&self, request: MockServerRequest) -> anyhow::Result; - - /// Prepare an interaction for verification. This should return any data required to construct any request - /// so that it can be amended before the verification is run. - async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result; - - /// Execute the verification for the interaction. - async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result; - - /// Updates the catalogue. This will be sent when the core catalogue has been updated (probably by a plugin loading). - async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>; +/// Request to compare the contents by a plugin +#[derive(Clone, Debug, Default)] +pub struct CompareContentRequest { + /// The expected contents from the Pact interaction + pub expected_contents: OptionalBody, + /// The actual contents that was received + pub actual_contents: OptionalBody, + /// Where there are keys or attributes in the data, indicates whether unexpected values are allowed + pub allow_unexpected_keys: bool, + /// Matching rules that apply + pub matching_rules: HashMap, + /// Plugin configuration form the Pact + pub plugin_configuration: Option } -/// Running plugin details -#[derive(Debug, Clone)] -pub struct PactPlugin { - /// Manifest for this plugin - pub manifest: PactPluginManifest, - - /// Running child process - pub child: Arc, - - /// Count of access to the plugin. If this is ever zero, the plugin process will be shutdown - access_count: Arc +/// Result of comparing the contents by a plugin +#[derive(Clone, Debug)] +pub enum CompareContentResult { + /// An error occurred trying to compare the contents + Error(String), + /// The content type was incorrect + TypeMismatch(String, String), + /// There were mismatched results + Mismatches(HashMap>), + /// All OK + OK } -#[async_trait] -impl PactPluginRpc for PactPlugin { - /// Send an init request to the plugin process - async fn init_plugin(&mut self, request: InitPluginRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.init_plugin(Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - /// Send a compare contents request to the plugin process - async fn compare_contents(&self, request: CompareContentsRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.compare_contents(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - /// Send a configure contents request to the plugin process - async fn configure_interaction(&self, request: ConfigureInteractionRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.configure_interaction(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - /// Send a generate content request to the plugin - async fn generate_content(&self, request: GenerateContentRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.generate_content(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - async fn start_mock_server(&self, request: StartMockServerRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.start_mock_server(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - async fn shutdown_mock_server(&self, request: ShutdownMockServerRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.shutdown_mock_server(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - async fn get_mock_server_results(&self, request: MockServerRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.get_mock_server_results(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.prepare_interaction_for_verification(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result { - let mut client = self.get_plugin_client().await?; - let response = client.verify_interaction(tonic::Request::new(request)).await?; - Ok(response.get_ref().clone()) - } - - async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()> { - let mut client = self.get_plugin_client().await?; - client.update_catalogue(tonic::Request::new(request)).await?; - Ok(()) - } +/// Request to generate the contents by a plugin +#[derive(Clone, Debug, Default)] +pub struct GenerateContentRequest { + /// The content type to generate + pub content_type: ContentType, + /// Example contents to replace + pub content: OptionalBody, + /// Generators that apply to the contents + pub generators: HashMap, + /// Global plugin data stored in the Pact file + pub plugin_data: Option>, + /// Plugin data stored on the interaction + pub interaction_data: Option>, + /// Test context in effect + pub test_context: HashMap } -impl PactPlugin { - /// Create a new Plugin - pub fn new(manifest: &PactPluginManifest, child: ChildPluginProcess) -> Self { - PactPlugin { - manifest: manifest.clone(), - child: Arc::new(child), - access_count: Arc::new(AtomicUsize::new(1)) - } - } - - /// Port the plugin is running on - pub fn port(&self) -> u16 { - self.child.port() - } +/// Pact Plugin trait +#[async_trait] +pub trait PactPlugin: Debug { + /// Manifest for this plugin + fn manifest(&self) -> PactPluginManifest; - /// Kill the running plugin process - pub fn kill(&self) { - self.child.kill(); - } + /// Shutdown this plugin. For plugins running as a separate process, this will kill the plugin process. + fn kill(&self); - /// Update the access of the plugin - pub fn update_access(&mut self) { - let count = self.access_count.fetch_add(1, Ordering::SeqCst); - trace!("update_access: Plugin {}/{} access is now {}", self.manifest.name, - self.manifest.version, count + 1); - } + /// Update the access count of the plugin. This is used for plugins running as a separate process, + /// so that the child process can be cleaned up when no longer used. + fn update_access(&mut self); /// Decrement and return the access count for the plugin - pub fn drop_access(&mut self) -> usize { - let check = self.access_count.fetch_update(Ordering::SeqCst, - Ordering::SeqCst, |count| { - if count > 0 { - Some(count - 1) - } else { - None - } - }); - let count = if let Ok(v) = check { - if v > 0 { v - 1 } else { v } - } else { - 0 - }; - trace!("drop_access: Plugin {}/{} access is now {}", self.manifest.name, self.manifest.version, - count); - count - } + fn drop_access(&mut self) -> usize; - async fn connect_channel(&self) -> anyhow::Result { - let port = self.child.port(); - match Channel::from_shared(format!("http://[::1]:{}", port))?.connect().await { - Ok(channel) => Ok(channel), - Err(err) => { - debug!("IP6 connection failed, will try IP4 address - {err}"); - Channel::from_shared(format!("http://127.0.0.1:{}", port))?.connect().await - .map_err(|err| anyhow!(err)) - } - } - } + /// Clone the plugin and return it wrapped in a Box + fn boxed(&self) -> Box; - async fn get_plugin_client(&self) -> anyhow::Result>> { - let channel = self.connect_channel().await?; - let interceptor = PactPluginInterceptor::new(self.child.plugin_info.server_key.as_str())?; - Ok(PactPluginClient::with_interceptor(channel, interceptor)) - } -} + /// Clone the plugin and return it wrapped in an Arc + fn arced(&self) -> Arc; -/// Interceptor to inject the server key as an authorisation header -#[derive(Clone, Debug)] -struct PactPluginInterceptor { - /// Server key to inject - server_key: MetadataValue -} + /// Publish the current catalogue to the plugin + async fn publish_updated_catalogue(&self, catalogue: &[CatalogueEntry]) -> anyhow::Result<()>; -impl PactPluginInterceptor { - fn new(server_key: &str) -> anyhow::Result { - let token = MetadataValue::try_from(server_key)?; - Ok(PactPluginInterceptor { - server_key: token - }) - } -} - -impl Interceptor for PactPluginInterceptor { - fn call(&mut self, mut request: Request<()>) -> Result, Status> { - request.metadata_mut().insert("authorization", self.server_key.clone()); - Ok(request) - } + /// Send a generate content request to the plugin + async fn generate_contents(&self, request: GenerateContentRequest) -> anyhow::Result; + + /// Send a match content request to the plugin + async fn match_contents(&self, request: CompareContentRequest) -> anyhow::Result; + + /// Get the plugin to configure the interaction contents for the interaction part based on the + /// provided definition. Returns the interaction data used to create the Pact interaction. + async fn configure_interaction( + &self, + content_type: &ContentType, + definition: &HashMap + ) -> anyhow::Result<(Vec, Option)>; + + /// Execute the verification for the interaction returning the verification results. + async fn verify_interaction( + &self, + pact: &V4Pact, + interaction: &(dyn V4Interaction + Send + Sync), + verification_data: &InteractionVerificationData, + config: &HashMap + ) -> anyhow::Result; + + /// Sets up a transport request to be made. This is the first phase when verifying, and it allows the + /// users to add additional values to any requests that are made. + async fn prepare_interaction_for_verification( + &self, + pact: &V4Pact, + interaction: &(dyn V4Interaction + Send + Sync), + context: &HashMap + ) -> anyhow::Result; + + /// Starts a mock server given the Pact, mock server config and test context + async fn start_mock_server( + &self, + config: &MockServerConfig, + pact: Box, + test_context: HashMap + ) -> anyhow::Result; + + /// Gets the results from a running mock server. + async fn get_mock_server_results(&self, mock_server_key: &str) -> anyhow::Result>; + + /// Shutdowns a running mock server. Will return any errors from the mock server. + async fn shutdown_mock_server(&self, mock_server_key: &str) -> anyhow::Result>; } /// Plugin configuration to add to the matching context for an interaction @@ -349,80 +260,5 @@ pub struct PluginInteractionConfig { #[cfg(test)] pub(crate) mod tests { - use async_trait::async_trait; - use lazy_static::lazy_static; - use std::sync::RwLock; - - use crate::plugin_models::PactPluginRpc; - use crate::proto::*; - use crate::proto::verification_preparation_response::Response; - - lazy_static!{ - pub(crate) static ref PREPARE_INTERACTION_FOR_VERIFICATION_ARG: RwLock> = RwLock::new(None); - pub(crate) static ref VERIFY_INTERACTION_ARG: RwLock> = RwLock::new(None); - } - - #[derive(Default)] - pub(crate) struct MockPlugin {} - #[async_trait] - impl PactPluginRpc for MockPlugin { - async fn init_plugin(&mut self, _request: InitPluginRequest) -> anyhow::Result { - unimplemented!() - } - - async fn compare_contents(&self, _request: CompareContentsRequest) -> anyhow::Result { - unimplemented!() - } - - async fn configure_interaction(&self, _request: ConfigureInteractionRequest) -> anyhow::Result { - unimplemented!() - } - - async fn generate_content(&self, _request: GenerateContentRequest) -> anyhow::Result { - unimplemented!() - } - - async fn start_mock_server(&self, _request: StartMockServerRequest) -> anyhow::Result { - unimplemented!() - } - - async fn shutdown_mock_server(&self, _request: ShutdownMockServerRequest) -> anyhow::Result { - unimplemented!() - } - - async fn get_mock_server_results(&self, _request: MockServerRequest) -> anyhow::Result { - unimplemented!() - } - - async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result { - let mut w = PREPARE_INTERACTION_FOR_VERIFICATION_ARG.write().unwrap(); - let _ = w.insert(request); - let data = InteractionData { - body: None, - metadata: Default::default() - }; - Ok(VerificationPreparationResponse { - response: Some(Response::InteractionData(data)) - }) - } - - async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result { - let mut w = VERIFY_INTERACTION_ARG.write().unwrap(); - let _ = w.insert(request); - let result = VerificationResult { - success: false, - response_data: None, - mismatches: vec![], - output: vec![] - }; - Ok(VerifyInteractionResponse { - response: Some(verify_interaction_response::Response::Result(result)) - }) - } - - async fn update_catalogue(&self, _request: Catalogue) -> anyhow::Result<()> { - unimplemented!() - } - } } diff --git a/drivers/rust/driver_ffi/Cargo.toml b/drivers/rust/driver_ffi/Cargo.toml index df920c6c..1a63455a 100644 --- a/drivers/rust/driver_ffi/Cargo.toml +++ b/drivers/rust/driver_ffi/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" edition = "2018" [dev-dependencies] -pact-plugin-driver = { version = "~0.6", path = "../driver" } +pact-plugin-driver = { version = "~0.7", path = "../driver" } pact_ffi = "~0.4.14" env_logger = "0.11.3" expectest = "0.12.0" diff --git a/drivers/rust/driver_pact_tests/Cargo.toml b/drivers/rust/driver_pact_tests/Cargo.toml index 9b820cac..23a0ba6d 100644 --- a/drivers/rust/driver_pact_tests/Cargo.toml +++ b/drivers/rust/driver_pact_tests/Cargo.toml @@ -11,7 +11,7 @@ prost = "0.12.3" prost-types = "0.12.3" [dev-dependencies] -pact-plugin-driver = { version = "~0.6", path = "../driver" } +pact-plugin-driver = { version = "~0.7", path = "../driver" } expectest = "0.12.0" env_logger = "0.11.3" pact_consumer = "~1.2.0"