Skip to content

Commit 252e623

Browse files
Merge pull request #10 from matthewshirley/feat/errors
Improve Shell Output When An Error Occurs
2 parents 4b86fad + 09f9fc9 commit 252e623

File tree

9 files changed

+163
-20
lines changed

9 files changed

+163
-20
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
env:
3333
PACT_DART_LIB_DOWNLOAD_PATH: .
3434
- name: Format coverage
35-
run: dart run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --packages=.packages --report-on=lib
35+
run: dart run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --report-on=lib
3636
- uses: codecov/codecov-action@v2
3737
with:
3838
files: ./coverage.lcov

lib/src/bindings/bindings.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ class PactFFIBindings {
4949

5050
late int Function(int mock_server_port) pactffi_mock_server_matched;
5151

52+
/// External interface to get all the mismatches from a mock server. The port number of the
53+
/// mock server is passed in, and a pointer to a C string with the mismatches in JSON
54+
/// format is returned.
55+
///
56+
/// https://docs.rs/pact_ffi/0.3.3/pact_ffi/mock_server/fn.pactffi_mock_server_mismatches.html
57+
/// https://docs.rs/pact_ffi/0.3.3/src/pact_ffi/mock_server/mod.rs.html#391-414
58+
///
5259
late Pointer<Utf8> Function(int mock_server_port)
5360
pactffi_mock_server_mismatches;
5461

@@ -60,6 +67,13 @@ class PactFFIBindings {
6067

6168
late int Function(int mock_server_port) pactffi_cleanup_mock_server;
6269

70+
/// Get a description of a mismatch.
71+
///
72+
/// https://docs.rs/pact_ffi/0.3.3/pact_ffi/fn.pactffi_mismatch_description.html
73+
/// https://docs.rs/pact_ffi/0.3.3/src/pact_ffi/lib.rs.html#265-274
74+
late Pointer<Utf8> Function(Pointer<Utf8> mismatches)
75+
pactffi_mismatch_description;
76+
6377
PactFFIBindings() {
6478
pactffi = openLibrary();
6579

@@ -147,6 +161,11 @@ class PactFFIBindings {
147161
.lookup<NativeFunction<pactffi_cleanup_mock_server_native>>(
148162
'pactffi_cleanup_mock_server')
149163
.asFunction();
164+
165+
pactffi_mismatch_description = pactffi
166+
.lookup<NativeFunction<pactffi_mismatch_description_native>>(
167+
'pactffi_mismatch_description')
168+
.asFunction();
150169
}
151170
}
152171

lib/src/bindings/signatures.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ typedef pactffi_mock_server_logs_native = Pointer<Utf8> Function(
7474
typedef pactffi_mock_server_matched_native = Int8 Function(
7575
Int32 mock_server_port);
7676

77+
/// External interface to get all the mismatches from a mock server. The port number of the
78+
/// mock server is passed in, and a pointer to a C string with the mismatches in JSON
79+
/// format is returned.
80+
///
81+
/// https://docs.rs/pact_ffi/0.3.3/pact_ffi/mock_server/fn.pactffi_mock_server_mismatches.html
82+
/// https://docs.rs/pact_ffi/0.3.3/src/pact_ffi/mock_server/mod.rs.html#391-414
7783
typedef pactffi_mock_server_mismatches_native = Pointer<Utf8> Function(
7884
Int32 mock_server_port);
7985

@@ -147,3 +153,10 @@ typedef pactffi_write_message_pact_file_native = Int32 Function(
147153

148154
typedef pactffi_write_pact_file_native = Int32 Function(
149155
Int32 mock_server_port, Pointer<Utf8> directory, Int8 overwrite);
156+
157+
/// Get a description of a mismatch.
158+
///
159+
/// https://docs.rs/pact_ffi/0.3.3/pact_ffi/fn.pactffi_mismatch_description.html
160+
/// https://docs.rs/pact_ffi/0.3.3/src/pact_ffi/lib.rs.html#265-274
161+
typedef pactffi_mismatch_description_native = Pointer<Utf8> Function(
162+
Pointer<Utf8> mismatch);

lib/src/errors.dart

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:convert';
2+
13
class EmptyParameterError extends Error {
24
String parameter = '';
35

@@ -31,14 +33,103 @@ class PactMatcherError extends Error {
3133
String toString() => 'Unable to create PactMatcher. $reason';
3234
}
3335

34-
class PactMismatchError extends Error {
35-
final String mismatches;
36+
enum MismatchErrorType {
37+
MissingRequest,
38+
RequestNotFound,
39+
RequestMismatch,
40+
MockServerParsingFail,
41+
Unknown;
42+
43+
static MismatchErrorType fromType(String type) {
44+
switch (type) {
45+
case 'missing-request':
46+
return MissingRequest;
47+
48+
case 'request-not-found':
49+
return RequestNotFound;
50+
51+
case 'request-mismatch':
52+
return RequestMismatch;
53+
54+
case 'mock-server-parsing-fail':
55+
return MockServerParsingFail;
56+
57+
default:
58+
return Unknown;
59+
}
60+
}
61+
}
62+
63+
extension MismatchErrorTypeReason on MismatchErrorType {
64+
static var errorReasons = {
65+
MismatchErrorType.MissingRequest: 'Request was missing',
66+
MismatchErrorType.RequestNotFound: 'Request was unexpected',
67+
MismatchErrorType.RequestMismatch: 'Request was not matched',
68+
MismatchErrorType.MockServerParsingFail:
69+
'Mock Server was uanble to parse the failure.',
70+
MismatchErrorType.Unknown: 'Something went wrong.'
71+
};
72+
73+
String get reason => errorReasons[this] ?? 'Something went wrong';
74+
}
75+
76+
class PactMatchFailure extends Error {
77+
late List errors;
3678

37-
PactMismatchError(this.mismatches);
79+
PactMatchFailure(String mismatches) {
80+
errors = jsonDecode(mismatches);
81+
}
3882

3983
@override
40-
String toString() =>
41-
'Pact was unable to verify all interactions. Pact returned: $mismatches';
84+
String toString() {
85+
var output = 'Pact was unable to validate one or more interaction(s):\n\n';
86+
var errorReason = '';
87+
88+
var index = 1;
89+
errors.forEach((error) {
90+
var errorType = MismatchErrorType.fromType(error['type']);
91+
errorReason = errorType.reason;
92+
var expected = '';
93+
var actual = '';
94+
95+
switch (errorType) {
96+
case MismatchErrorType.MissingRequest:
97+
expected += '${error['method']} ${error['path']}';
98+
break;
99+
100+
case MismatchErrorType.RequestNotFound:
101+
expected += '';
102+
actual += '${error['method']} ${error['path']}';
103+
break;
104+
105+
case MismatchErrorType.RequestMismatch:
106+
var mismatches = error['mismatches'];
107+
errorReason += " (${error['method']} ${error['path']})";
108+
109+
mismatches.forEach((mismatch) {
110+
expected += '- ${mismatch['mismatch']}\n\t\t';
111+
});
112+
break;
113+
114+
default:
115+
break;
116+
}
117+
118+
output += 'Interaction Mismatch #$index\n';
119+
output += '\tReason:\n\t\t$errorReason\n';
120+
if (expected.isNotEmpty) {
121+
output += '\n\tExpected:\n\t\t$expected\n';
122+
}
123+
124+
if (actual.isNotEmpty) {
125+
output += '\n\tActual:\n\t\t$actual\n\n';
126+
}
127+
128+
index++;
129+
});
130+
131+
return output;
132+
}
42133
}
43134

44135
class PactCreateMockServerError extends Error {

lib/src/interaction.dart

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import 'dart:convert';
2+
import 'dart:ffi';
23
import 'package:ffi/ffi.dart';
34

45
import 'package:pact_dart/src/bindings/types.dart';
56
import 'package:pact_dart/src/bindings/bindings.dart';
67
import 'package:pact_dart/src/errors.dart';
8+
import 'package:pact_dart/src/utils/content_type.dart';
79

810
class Interaction {
911
late InteractionHandle interaction;
@@ -62,17 +64,25 @@ class Interaction {
6264
});
6365
}
6466

65-
void _withBody(InteractionPart part, String contentType, Map body) {
66-
final cContentType = contentType.toNativeUtf8();
67+
void _withBody<T>(InteractionPart part, T body, String? contentType) {
68+
Pointer<Utf8> cContentType;
69+
70+
if (contentType != null) {
71+
cContentType = contentType.toNativeUtf8();
72+
} else {
73+
cContentType = getContentType(body).toNativeUtf8();
74+
}
75+
6776
final cBody = jsonEncode(body).toNativeUtf8();
6877

6978
bindings.pactffi_with_body(interaction, part.value, cContentType, cBody);
7079
}
7180

72-
Interaction withRequest(String method, String path,
81+
Interaction withRequest<T>(String method, String path,
7382
{Map<String, String>? headers,
7483
Map<String, String>? query,
75-
dynamic body}) {
84+
T? body,
85+
String? contentType}) {
7686
if (method.isEmpty || path.isEmpty) {
7787
throw EmptyParametersError(['method', 'path']);
7888
}
@@ -95,24 +105,26 @@ class Interaction {
95105
}
96106

97107
if (body != null) {
98-
_withBody(InteractionPart.Request, 'application/json',
99-
body); // TODO: Assumes all requests are JSON
108+
_withBody(InteractionPart.Request, body, contentType);
100109
}
101110

102111
return this;
103112
}
104113

105-
Interaction willRespondWith(int status,
106-
{Map<String, String>? headers, Map? body}) {
114+
Interaction willRespondWith<T>(
115+
int status, {
116+
Map<String, String>? headers,
117+
T? body,
118+
String? contentType,
119+
}) {
107120
bindings.pactffi_response_status(interaction, status);
108121

109122
if (headers != null) {
110123
_withHeaders(InteractionPart.Response, headers);
111124
}
112125

113126
if (body != null) {
114-
_withBody(InteractionPart.Response, 'application/json',
115-
body); // TODO: Assumes all requests are JSON
127+
_withBody(InteractionPart.Response, body, contentType);
116128
}
117129

118130
return this;

lib/src/pact_mock_service.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@ class PactMockService {
7676
}
7777
}
7878

79+
void onPactMismatches() {}
80+
7981
/// Verifies the interactions were matched and writes the JSON contract
8082
void writePactFile({String directory = 'contracts', bool overwrite = false}) {
8183
final hasMatchedInteractions = this.hasMatchedInteractions();
8284

8385
if (!hasMatchedInteractions) {
8486
final mismatches =
8587
bindings.pactffi_mock_server_mismatches(port).toDartString();
86-
87-
throw PactMismatchError(mismatches);
88+
throw PactMatchFailure(mismatches);
8889
}
8990

9091
final result = bindings.pactffi_write_pact_file(

lib/src/utils/content_type.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
String getContentType(content) {
2+
if (content is Map) {
3+
return 'application/json';
4+
}
5+
6+
return 'text/plain';
7+
}

pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,4 +352,4 @@ packages:
352352
source: hosted
353353
version: "3.1.0"
354354
sdks:
355-
dart: ">=2.12.0 <3.0.0"
355+
dart: ">=2.17.0 <3.0.0"

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version: 0.5.0
44
homepage: https://github.com/matthewshirley/pact_dart
55

66
environment:
7-
sdk: ">=2.12.0 <3.0.0"
7+
sdk: ">=2.17.0 <3.0.0"
88

99
dependencies:
1010
ffi: ^1.1.2

0 commit comments

Comments
 (0)