Skip to content

Commit

Permalink
feat: update content_type_matching logic for binary payloads
Browse files Browse the repository at this point in the history
  • Loading branch information
YOU54F committed May 28, 2024
1 parent 260a91d commit 968be07
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-ffi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
fail-fast: false
matrix:
operating-system: [ ubuntu-latest, windows-latest, macos-12 ]
operating-system: [ ubuntu-latest, windows-latest, macos-12, macos-14 ]
rust: [ stable ]
env:
pact_do_not_track: true
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ jobs:
build:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ ubuntu-latest, windows-latest, macos-12 ]
operating-system: [ ubuntu-latest, windows-latest, macos-12, macos-14 ]
rust: [ stable ]
env:
pact_do_not_track: true
Expand Down
33 changes: 29 additions & 4 deletions compatibility-suite/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ Feature: V3 era Matching Rules
| application/octet-stream | file: sample.pdf |
When the request is compared to the expected one
Then the comparison should NOT be OK
And the mismatches will contain a mismatch with error "$" -> "Expected binary contents to have content type 'image/jpeg' but detected contents was 'application/pdf'"
And the mismatches will contain a mismatch with error "$" -> "Expected binary contents to have content type 'image/jpeg' but inferred contents are 'application/pdf', magic contents are 'application/pdf'"

Scenario: Supports a Values matcher (positive case, ignores missing and additional keys)
Given an expected request configured with the following:
Expand Down
69 changes: 51 additions & 18 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/pact_consumer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ itertools = "0.12.1"
lazy_static = "1.4.0"
maplit = "1.0.2"
pact_matching = { version = "~1.2.2", path = "../pact_matching", default-features = false }
pact_mock_server = { version = "~1.2.6", default-features = false }
pact_mock_server = { version = "=1.2.8", default-features = false, git = "https://github.com/you54f/pact-core-mock-server.git", branch = "main" }
pact_models = { version = "~1.2.0", default-features = false }
pact-plugin-driver = { version = "~0.6.1", optional = true, default-features = false }
regex = "1.10.4"
Expand Down
3 changes: 2 additions & 1 deletion rust/pact_ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ maplit = "1.0.2"
multipart = { version = "0.18.0", default-features = false, features = ["client", "mock"] }
onig = { version = "6.4.0", default-features = false }
pact_matching = { version = "~1.2.2", path = "../pact_matching" }
pact_mock_server = { version = "~1.2.6" }
pact_mock_server = { version = "=1.2.8", git = "https://github.com/you54f/pact-core-mock-server.git", branch = "main" }
# pact_mock_server = { version = "=1.2.8", path = "../../../pact-core-mock-server/pact_mock_server" }
pact_models = { version = "~1.2.0" }
pact-plugin-driver = { version = "~0.6.1" }
pact_verifier = { version = "~1.2.1", path = "../pact_verifier" }
Expand Down
9 changes: 7 additions & 2 deletions rust/pact_ffi/src/matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ mod tests {
];

#[test_log::test]
#[cfg(not(windows))]
fn pactffi_matches_binary_value_test() {
let rule = MatchingRule::ContentType("image/gif".to_string());
let rule_ptr = &rule as *const MatchingRule;
Expand All @@ -363,7 +362,13 @@ mod tests {
let rule_ptr = &rule as *const MatchingRule;
let err_result = pactffi_matches_binary_value(rule_ptr, value, 35, value, 35, 0);
let string = unsafe { CString::from_raw(err_result as *mut c_char) };
expect!(string.to_string_lossy()).to(be_equal_to("Expected binary contents to have content type 'image/png' but detected contents was 'image/gif'"));
// required if shared-mime-info not installed, not installed on windows easily
#[cfg(not(windows))]
let magic_content_type = "image/gif";
#[cfg(windows)]
let magic_content_type = "application/octet-stream";

expect!(string.to_string_lossy()).to(be_equal_to(format!("Expected binary contents to have content type 'image/png' but inferred contents are 'image/gif', magic contents are '{}'", magic_content_type)));
}

#[test_log::test]
Expand Down
7 changes: 4 additions & 3 deletions rust/pact_ffi/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,6 @@ fn fixture_path(path: &str) -> PathBuf {
.to_owned()
}

#[cfg(not(windows))]
#[rstest(
specification, expected_value,
case::specification_unknown(PactSpecification::Unknown, false),
Expand Down Expand Up @@ -687,14 +686,16 @@ fn pactffi_with_binary_file_feature_test(specification: PactSpecification, expec

let client = Client::default();
let result = client.post(format!("http://127.0.0.1:{}/upload", port).as_str())
.header("Content-Type", "image/gif")
.body(buffer)
.header("Content-Type", "image/gif")
.body(buffer)
.send();

let mismatches = unsafe {
CStr::from_ptr(pactffi_mock_server_mismatches(port)).to_string_lossy().into_owned()
};

println!("{}",mismatches);

match result {
Ok(res) => {
let status = res.status();
Expand Down
5 changes: 4 additions & 1 deletion rust/pact_matching/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ sxd-document = { version = "0.3.2", optional = true }
tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
tracing-core = "0.1.32"
tree_magic_mini = "3.1.4"
tree_magic_mini = {version = "3.1.4", git = "https://github.com/YOU54F/tree_magic.git", branch = "mini" }
# tree_magic_mini = {version = "3.1.4", path = "../../../tree_magic" }
infer = "0.15.0"
uuid = { version = "1.8.0", features = ["v4"] }
# indexmap = { version = "1.9.3", features = ["std"] } # for errors x-compiling from macos without cross

[dev-dependencies]
quickcheck = "1"
Expand Down
27 changes: 27 additions & 0 deletions rust/pact_matching/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,33 @@ Example:
matching(contentType, 'application/json', '{}')
```

###### Content Type - Detection Mechanisms for Binary Content

`pact_matching` currently performs the following for matching Binary content-types

1. Determines `expected` `Content-Type` header requested by user in test
2. Read content buffer with `infer` library, and guess `Content-Type` based on magic bytes
3. If unsuccessful
1. Read content buffer with `tree_magic_mini` library, and guess `Content-Type` based on shared-mime-info DB
1. MagicDB is not shipped with pact_matching, due to GPL restrictions, users can add manually
1. Linux Alpine - `apk add shared-mime-info`
2. MacOS `brew install shared-mime-info`
1. `arm64` MacOS requires `tree_magic_mini` [fork](https://github.com/you54f/tree_magic)
3. Linux - Debian`apt-get install -y shared-mime-info`
4. If either result returns `text/plain`, then manually read bytes using `detect_content_type_from_bytes` function in `pact_models`
5. If all of `2`, `3`, or `4` fails, then throw error, otherwise return `Ok`

Rust libraries used:

- https://github.com/mbrubeck/tree_magic
- fork https://github.com/you54f/tree_magic
- https://github.com/bojand/infer
- Explore:- https://github.com/ebassi/xdg-mime-rs

TODO:

Provide user full opt-out of content-type matching?

##### Matching an example type by reference

Type matching can also be specified by a reference to an example. References are defined by a dollar (`$`) followed by
Expand Down
Loading

0 comments on commit 968be07

Please sign in to comment.