• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pact-foundation / pact-go / 9931721822

15 Jul 2024 12:08AM UTC coverage: 29.383% (+0.03%) from 29.358%
9931721822

push

github

web-flow
Merge pull request #445 from lbcjbb/fix-crash

fix: don't use defer in a loop

7 of 15 new or added lines in 2 files covered. (46.67%)

3 existing lines in 1 file now uncovered.

1872 of 6371 relevant lines covered (29.38%)

12.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.94
/internal/native/mock_server.go
1
package native
2

3
/*
4
#include "pact.h"
5
*/
6
import "C"
7

8
import (
9
        "crypto/tls"
10
        "crypto/x509"
11
        "encoding/json"
12
        "fmt"
13
        "log"
14
        "os"
15
        "strings"
16
        "unsafe"
17
)
18

19
type interactionPart int
20

21
const (
22
        INTERACTION_PART_REQUEST interactionPart = iota
23
        INTERACTION_PART_RESPONSE
24
)
25

26
const (
27
        RESULT_OK interactionPart = iota
28
        RESULT_FAILED
29
)
30

31
type specificationVersion int
32

33
const (
34
        SPECIFICATION_VERSION_UNKNOWN specificationVersion = iota
35
        SPECIFICATION_VERSION_V1
36
        SPECIFICATION_VERSION_V1_1
37
        SPECIFICATION_VERSION_V2
38
        SPECIFICATION_VERSION_V3
39
        SPECIFICATION_VERSION_V4
40
)
41

42
type logLevel int
43

44
const (
45
        LOG_LEVEL_OFF logLevel = iota
46
        LOG_LEVEL_ERROR
47
        LOG_LEVEL_WARN
48
        LOG_LEVEL_INFO
49
        LOG_LEVEL_DEBUG
50
        LOG_LEVEL_TRACE
51
)
52

53
var logLevelStringToInt = map[string]logLevel{
54
        "OFF":   LOG_LEVEL_OFF,
55
        "ERROR": LOG_LEVEL_ERROR,
56
        "WARN":  LOG_LEVEL_WARN,
57
        "INFO":  LOG_LEVEL_INFO,
58
        "DEBUG": LOG_LEVEL_DEBUG,
59
        "TRACE": LOG_LEVEL_TRACE,
60
}
61

62
// Pact is a Go representation of the PactHandle struct
63
type Pact struct {
64
        handle C.PactHandle
65
}
66

67
// Interaction is a Go representation of the InteractionHandle struct
68
type Interaction struct {
69
        handle C.InteractionHandle
70
}
71

72
// Version returns the current semver FFI interface version
73
func Version() string {
18✔
74
        v := C.pactffi_version()
18✔
75

18✔
76
        return C.GoString(v)
18✔
77
}
18✔
78

79
var loggingInitialised string
80

81
// Init initialises the library
82
func Init(logLevel string) {
18✔
83
        log.Println("[DEBUG] initialising native interface")
18✔
84
        logLevel = strings.ToUpper(logLevel)
18✔
85

18✔
86
        if loggingInitialised != "" {
18✔
87
                log.Printf("log level ('%s') cannot be set to '%s' after initialisation\n", loggingInitialised, logLevel)
×
88
        } else {
18✔
89
                l, ok := logLevelStringToInt[logLevel]
18✔
90
                if !ok {
27✔
91
                        l = LOG_LEVEL_INFO
9✔
92
                }
9✔
93
                log.Printf("[DEBUG] initialised native log level to %s (%d)", logLevel, l)
18✔
94

18✔
95
                if os.Getenv("PACT_LOG_PATH") != "" {
18✔
96
                        log.Println("[DEBUG] initialised native log to log to file:", os.Getenv("PACT_LOG_PATH"))
×
97
                        err := logToFile(os.Getenv("PACT_LOG_PATH"), l)
×
98
                        if err != nil {
×
99
                                log.Println("[ERROR] failed to log to file:", err)
×
100
                        }
×
101
                } else {
18✔
102
                        log.Println("[DEBUG] initialised native log to log to stdout")
18✔
103
                        err := logToStdout(l)
18✔
104
                        if err != nil {
27✔
105
                                log.Println("[ERROR] failed to log to stdout:", err)
9✔
106
                        }
9✔
107
                }
108
        }
109
}
110

111
// MockServer is the public interface for managing the HTTP mock server
112
type MockServer struct {
113
        pact         *Pact
114
        messagePact  *MessagePact
115
        interactions []*Interaction
116
}
117

118
// NewHTTPPact creates a new HTTP mock server for a given consumer/provider
119
func NewHTTPPact(consumer string, provider string) *MockServer {
18✔
120
        cConsumer := C.CString(consumer)
18✔
121
        cProvider := C.CString(provider)
18✔
122
        defer free(cConsumer)
18✔
123
        defer free(cProvider)
18✔
124

18✔
125
        return &MockServer{pact: &Pact{handle: C.pactffi_new_pact(cConsumer, cProvider)}}
18✔
126
}
18✔
127

128
// Version returns the current semver FFI interface version
129
func (m *MockServer) Version() string {
×
130
        return Version()
×
131
}
×
132

133
func (m *MockServer) WithSpecificationVersion(version specificationVersion) {
9✔
134
        C.pactffi_with_specification(m.pact.handle, C.int(version))
9✔
135
}
9✔
136

137
// CreateMockServer creates a new Mock Server from a given Pact file.
138
// Returns the port number it started on or an error if failed
139
func (m *MockServer) CreateMockServer(pact string, address string, tls bool) (int, error) {
54✔
140
        log.Println("[DEBUG] mock server starting on address:", address)
54✔
141
        cPact := C.CString(pact)
54✔
142
        cAddress := C.CString(address)
54✔
143
        defer free(cPact)
54✔
144
        defer free(cAddress)
54✔
145
        tlsEnabled := false
54✔
146
        if tls {
54✔
147
                tlsEnabled = true
×
148
        }
×
149

150
        p := C.pactffi_create_mock_server(cPact, cAddress, C.bool(tlsEnabled))
54✔
151

54✔
152
        // | Error | Description |
54✔
153
        // |-------|-------------|
54✔
154
        // | -1 | A null pointer was received |
54✔
155
        // | -2 | The pact JSON could not be parsed |
54✔
156
        // | -3 | The mock server could not be started |
54✔
157
        // | -4 | The method panicked |
54✔
158
        // | -5 | The address is not valid |
54✔
159
        // | -6 | Could not create the TLS configuration with the self-signed certificate |
54✔
160
        port := int(p)
54✔
161
        switch port {
54✔
162
        case -1:
×
163
                return 0, ErrInvalidMockServerConfig
×
164
        case -2:
×
165
                return 0, ErrInvalidPact
×
166
        case -3:
×
167
                return 0, ErrMockServerUnableToStart
×
168
        case -4:
×
169
                return 0, ErrMockServerPanic
×
170
        case -5:
×
171
                return 0, ErrInvalidAddress
×
172
        case -6:
×
173
                return 0, ErrMockServerTLSConfiguration
×
174
        default:
54✔
175
                if port > 0 {
108✔
176
                        log.Println("[DEBUG] mock server running on port:", port)
54✔
177
                        return port, nil
54✔
178
                }
54✔
179
                return port, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", port)
×
180
        }
181
}
182

183
// Verify verifies that all interactions were successful. If not, returns a slice
184
// of Mismatch-es. Does not write the pact or cleanup server.
185
func (m *MockServer) Verify(port int, dir string) (bool, []MismatchedRequest) {
18✔
186
        mismatches := m.MockServerMismatchedRequests(port)
18✔
187
        log.Println("[DEBUG] mock server mismatches:", len(mismatches))
18✔
188

18✔
189
        return len(mismatches) == 0, mismatches
18✔
190
}
18✔
191

192
// MockServerMismatchedRequests returns a JSON object containing any mismatches from
193
// the last set of interactions.
194
func (m *MockServer) MockServerMismatchedRequests(port int) []MismatchedRequest {
54✔
195
        log.Println("[DEBUG] mock server determining mismatches:", port)
54✔
196
        var res []MismatchedRequest
54✔
197

54✔
198
        mismatches := C.pactffi_mock_server_mismatches(C.int(port))
54✔
199
        // This method can return a nil pointer, in which case, it
54✔
200
        // should be considered a failure (or at least, an issue)
54✔
201
        // converting it to a string might also do nasty things here!
54✔
202
        if mismatches == nil {
54✔
203
                log.Println("[WARN] received a null pointer from the native interface, returning empty list of mismatches")
×
204
                return []MismatchedRequest{}
×
205
        }
×
206

207
        err := json.Unmarshal([]byte(C.GoString(mismatches)), &res)
54✔
208
        if err != nil {
54✔
209
                log.Println("[ERROR] failed to unmarshal mismatches response, returning empty list of mismatches")
×
210
                return []MismatchedRequest{}
×
211
        }
×
212
        return res
54✔
213
}
214

215
// CleanupMockServer frees the memory from the previous mock server.
216
func (m *MockServer) CleanupMockServer(port int) bool {
63✔
217
        if len(m.interactions) == 0 {
108✔
218
                return true
45✔
219
        }
45✔
220
        log.Println("[DEBUG] mock server cleaning up port:", port)
18✔
221
        res := C.pactffi_cleanup_mock_server(C.int(port))
18✔
222

18✔
223
        return bool(res)
18✔
224
}
225

226
// WritePactFile writes the Pact to file.
227
// TODO: expose overwrite
228
func (m *MockServer) WritePactFile(port int, dir string) error {
27✔
229
        log.Println("[DEBUG] writing pact file for mock server on port:", port, ", dir:", dir)
27✔
230
        cDir := C.CString(dir)
27✔
231
        defer free(cDir)
27✔
232

27✔
233
        // overwritePact := 0
27✔
234
        // if overwrite {
27✔
235
        //         overwritePact = 1
27✔
236
        // }
27✔
237

27✔
238
        // res := int(C.pactffi_write_pact_file(C.int(port), cDir, C.int(overwritePact)))
27✔
239
        res := int(C.pactffi_write_pact_file(C.int(port), cDir, C.bool(false)))
27✔
240

27✔
241
        // | Error | Description |
27✔
242
        // |-------|-------------|
27✔
243
        // | 1 | A general panic was caught |
27✔
244
        // | 2 | The pact file was not able to be written |
27✔
245
        // | 3 | A mock server with the provided port was not found |
27✔
246
        switch res {
27✔
247
        case 0:
27✔
248
                return nil
27✔
249
        case 1:
×
250
                return ErrMockServerPanic
×
251
        case 2:
×
252
                return ErrUnableToWritePactFile
×
253
        case 3:
×
254
                return ErrMockServerNotfound
×
255
        default:
×
256
                return fmt.Errorf("an unknown error ocurred when writing to pact file")
×
257
        }
258
}
259

260
// GetTLSConfig returns a tls.Config compatible with the TLS
261
// mock server
262
func GetTLSConfig() *tls.Config {
9✔
263
        cert := C.pactffi_get_tls_ca_certificate()
9✔
264
        defer libRustFree(cert)
9✔
265

9✔
266
        goCert := C.GoString(cert)
9✔
267
        certPool := x509.NewCertPool()
9✔
268
        certPool.AppendCertsFromPEM([]byte(goCert))
9✔
269

9✔
270
        return &tls.Config{
9✔
271
                RootCAs: certPool,
9✔
272
        }
9✔
273
}
9✔
274

275
func free(str *C.char) {
1,539✔
276
        C.free(unsafe.Pointer(str))
1,539✔
277
}
1,539✔
278

279
func libRustFree(str *C.char) {
9✔
280
        C.pactffi_free_string(str)
9✔
281
}
9✔
282

283
// Start starts up the mock HTTP server on the given address:port and TLS config
284
// https://docs.rs/pact_mock_server_ffi/0.0.7/pact_mock_server_ffi/fn.create_mock_server_for_pact.html
285
func (m *MockServer) Start(address string, tls bool) (int, error) {
18✔
286
        if len(m.interactions) == 0 {
18✔
287
                return 0, ErrNoInteractions
×
288
        }
×
289

290
        log.Println("[DEBUG] mock server starting on address:", address)
18✔
291
        cAddress := C.CString(address)
18✔
292
        defer free(cAddress)
18✔
293
        tlsEnabled := false
18✔
294
        if tls {
18✔
295
                tlsEnabled = true
×
296
        }
×
297

298
        p := C.pactffi_create_mock_server_for_pact(m.pact.handle, cAddress, C.bool(tlsEnabled))
18✔
299

18✔
300
        // | Error | Description |
18✔
301
        // |-------|-------------|
18✔
302
        // | -1 | A null pointer was received |
18✔
303
        // | -2 | The pact JSON could not be parsed |
18✔
304
        // | -3 | The mock server could not be started |
18✔
305
        // | -4 | The method panicked |
18✔
306
        // | -5 | The address is not valid |
18✔
307
        // | -6 | Could not create the TLS configuration with the self-signed certificate |
18✔
308
        port := int(p)
18✔
309
        switch port {
18✔
310
        case -1:
×
311
                return 0, ErrInvalidMockServerConfig
×
312
        case -2:
×
313
                return 0, ErrInvalidPact
×
314
        case -3:
×
315
                return 0, ErrMockServerUnableToStart
×
316
        case -4:
×
317
                return 0, ErrMockServerPanic
×
318
        case -5:
×
319
                return 0, ErrInvalidAddress
×
320
        case -6:
×
321
                return 0, ErrMockServerTLSConfiguration
×
322
        default:
18✔
323
                if port > 0 {
36✔
324
                        log.Println("[DEBUG] mock server running on port:", port)
18✔
325
                        return port, nil
18✔
326
                }
18✔
327
                return port, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", port)
×
328
        }
329
}
330

331
// StartTransport starts up a mock server on the given address:port for the given transport
332
// https://docs.rs/pact_ffi/latest/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html
333
func (m *MockServer) StartTransport(transport string, address string, port int, config map[string][]interface{}) (int, error) {
×
334
        if len(m.interactions) == 0 {
×
335
                return 0, ErrNoInteractions
×
336
        }
×
337

338
        log.Println("[DEBUG] mock server starting on address:", address, port)
×
339
        cAddress := C.CString(address)
×
340
        defer free(cAddress)
×
341

×
342
        cTransport := C.CString(transport)
×
343
        defer free(cTransport)
×
344

×
345
        configJson := stringFromInterface(config)
×
346
        cConfig := C.CString(configJson)
×
347
        defer free(cConfig)
×
348

×
349
        p := C.pactffi_create_mock_server_for_transport(m.pact.handle, cAddress, C.ushort(port), cTransport, cConfig)
×
350

×
351
        // | Error | Description
×
352
        // |-------|-------------
×
353
        // | -1           | An invalid handle was received. Handles should be created with pactffi_new_pact
×
354
        // | -2           | transport_config is not valid JSON
×
355
        // | -3           | The mock server could not be started
×
356
        // | -4           | The method panicked
×
357
        // | -5           | The address is not valid
×
358
        msPort := int(p)
×
359
        switch msPort {
×
360
        case -1:
×
361
                return 0, ErrInvalidMockServerConfig
×
362
        case -2:
×
363
                return 0, ErrInvalidMockServerConfig
×
364
        case -3:
×
365
                return 0, ErrMockServerUnableToStart
×
366
        case -4:
×
367
                return 0, ErrMockServerPanic
×
368
        case -5:
×
369
                return 0, ErrInvalidAddress
×
370
        default:
×
371
                if msPort > 0 {
×
372
                        log.Println("[DEBUG] mock server running on port:", msPort)
×
373
                        return msPort, nil
×
374
                }
×
375
                return msPort, fmt.Errorf("an unknown error (code: %v) occurred when starting a mock server for the test", msPort)
×
376
        }
377
}
378

379
// Sets the additional metadata on the Pact file. Common uses are to add the client library details such as the name and version
380
func (m *MockServer) WithMetadata(namespace, k, v string) *MockServer {
×
381
        cNamespace := C.CString(namespace)
×
382
        defer free(cNamespace)
×
383
        cName := C.CString(k)
×
384
        defer free(cName)
×
385
        cValue := C.CString(v)
×
386
        defer free(cValue)
×
387

×
388
        C.pactffi_with_pact_metadata(m.pact.handle, cNamespace, cName, cValue)
×
389

×
390
        return m
×
391
}
×
392

393
// NewInteraction initialises a new interaction for the current contract
394
func (m *MockServer) UsingPlugin(pluginName string, pluginVersion string) error {
9✔
395
        cPluginName := C.CString(pluginName)
9✔
396
        defer free(cPluginName)
9✔
397
        cPluginVersion := C.CString(pluginVersion)
9✔
398
        defer free(cPluginVersion)
9✔
399

9✔
400
        r := C.pactffi_using_plugin(m.pact.handle, cPluginName, cPluginVersion)
9✔
401
        InstallSignalHandlers()
9✔
402

9✔
403
        // 1 - A general panic was caught.
9✔
404
        // 2 - Failed to load the plugin.
9✔
405
        // 3 - Pact Handle is not valid.
9✔
406
        res := int(r)
9✔
407
        switch res {
9✔
408
        case 1:
×
409
                return ErrPluginGenericPanic
×
410
        case 2:
×
411
                return ErrPluginFailed
×
412
        case 3:
×
413
                return ErrHandleNotFound
×
414
        default:
9✔
415
                if res != 0 {
9✔
416
                        return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res)
×
417
                }
×
418
        }
419

420
        return nil
9✔
421
}
422

423
// NewInteraction initialises a new interaction for the current contract
424
func (m *MockServer) CleanupPlugins() {
9✔
425
        C.pactffi_cleanup_plugins(m.pact.handle)
9✔
426
}
9✔
427

428
// NewInteraction initialises a new interaction for the current contract
429
func (m *MockServer) NewInteraction(description string) *Interaction {
18✔
430
        cDescription := C.CString(description)
18✔
431
        defer free(cDescription)
18✔
432

18✔
433
        i := &Interaction{
18✔
434
                handle: C.pactffi_new_interaction(m.pact.handle, cDescription),
18✔
435
        }
18✔
436
        m.interactions = append(m.interactions, i)
18✔
437

18✔
438
        return i
18✔
439
}
18✔
440

441
// NewInteraction initialises a new interaction for the current contract
442
func (i *Interaction) WithPluginInteractionContents(part interactionPart, contentType string, contents string) error {
9✔
443
        cContentType := C.CString(contentType)
9✔
444
        defer free(cContentType)
9✔
445
        cContents := C.CString(contents)
9✔
446
        defer free(cContents)
9✔
447

9✔
448
        r := C.pactffi_interaction_contents(i.handle, C.int(part), cContentType, cContents)
9✔
449

9✔
450
        // 1 - A general panic was caught.
9✔
451
        // 2 - The mock server has already been started.
9✔
452
        // 3 - The interaction handle is invalid.
9✔
453
        // 4 - The content type is not valid.
9✔
454
        // 5 - The contents JSON is not valid JSON.
9✔
455
        // 6 - The plugin returned an error.
9✔
456
        res := int(r)
9✔
457
        switch res {
9✔
458
        case 1:
×
459
                return ErrPluginGenericPanic
×
460
        case 2:
×
461
                return ErrPluginMockServerStarted
×
462
        case 3:
×
463
                return ErrPluginInteractionHandleInvalid
×
464
        case 4:
×
465
                return ErrPluginInvalidContentType
×
466
        case 5:
×
467
                return ErrPluginInvalidJson
×
468
        case 6:
×
469
                return ErrPluginSpecificError
×
470
        default:
9✔
471
                if res != 0 {
9✔
472
                        return fmt.Errorf("an unknown error (code: %v) occurred when adding a plugin for the test. Received error code:", res)
×
473
                }
×
474
        }
475

476
        return nil
9✔
477
}
478

479
func (i *Interaction) UponReceiving(description string) *Interaction {
18✔
480
        cDescription := C.CString(description)
18✔
481
        defer free(cDescription)
18✔
482

18✔
483
        C.pactffi_upon_receiving(i.handle, cDescription)
18✔
484

18✔
485
        return i
18✔
486
}
18✔
487

488
func (i *Interaction) Given(state string) *Interaction {
18✔
489
        cState := C.CString(state)
18✔
490
        defer free(cState)
18✔
491

18✔
492
        C.pactffi_given(i.handle, cState)
18✔
493

18✔
494
        return i
18✔
495
}
18✔
496

497
func (i *Interaction) GivenWithParameter(state string, params map[string]interface{}) *Interaction {
×
498
        cState := C.CString(state)
×
499
        defer free(cState)
×
500

×
501
        for k, v := range params {
×
502
                cKey := C.CString(k)
×
503
                param := stringFromInterface(v)
×
504
                cValue := C.CString(param)
×
505

×
506
                C.pactffi_given_with_param(i.handle, cState, cKey, cValue)
×
507

×
NEW
508
                free(cValue)
×
NEW
509
                free(cKey)
×
UNCOV
510
        }
×
511

512
        return i
×
513
}
514

515
func (i *Interaction) WithRequest(method string, pathOrMatcher interface{}) *Interaction {
18✔
516
        cMethod := C.CString(method)
18✔
517
        defer free(cMethod)
18✔
518

18✔
519
        path := stringFromInterface(pathOrMatcher)
18✔
520
        cPath := C.CString(path)
18✔
521
        defer free(cPath)
18✔
522

18✔
523
        C.pactffi_with_request(i.handle, cMethod, cPath)
18✔
524

18✔
525
        return i
18✔
526
}
18✔
527

528
func (i *Interaction) WithRequestHeaders(valueOrMatcher map[string][]interface{}) *Interaction {
×
529
        return i.withHeaders(INTERACTION_PART_REQUEST, valueOrMatcher)
×
530
}
×
531

532
func (i *Interaction) WithResponseHeaders(valueOrMatcher map[string][]interface{}) *Interaction {
×
533
        return i.withHeaders(INTERACTION_PART_RESPONSE, valueOrMatcher)
×
534
}
×
535

536
func (i *Interaction) withHeaders(part interactionPart, valueOrMatcher map[string][]interface{}) *Interaction {
×
537
        for k, v := range valueOrMatcher {
×
538
                cName := C.CString(k)
×
539

×
540
                for _, header := range v {
×
541
                        value := stringFromInterface(header)
×
542
                        cValue := C.CString(value)
×
543

×
544
                        C.pactffi_with_header_v2(i.handle, C.int(part), cName, C.ulong(0), cValue)
×
NEW
545

×
NEW
546
                        free(cValue)
×
UNCOV
547
                }
×
548

NEW
549
                free(cName)
×
550
        }
551

552
        return i
×
553
}
554

555
func (i *Interaction) WithQuery(valueOrMatcher map[string][]interface{}) *Interaction {
×
556
        for k, values := range valueOrMatcher {
×
557
                cName := C.CString(k)
×
558

×
559
                for idx, v := range values {
×
560
                        value := stringFromInterface(v)
×
561
                        cValue := C.CString(value)
×
562

×
563
                        C.pactffi_with_query_parameter_v2(i.handle, cName, C.ulong(idx), cValue)
×
NEW
564

×
NEW
565
                        free(cValue)
×
UNCOV
566
                }
×
567

NEW
568
                free(cName)
×
569
        }
570

571
        return i
×
572
}
573

574
func (i *Interaction) WithJSONRequestBody(body interface{}) *Interaction {
×
575
        return i.withJSONBody(body, INTERACTION_PART_REQUEST)
×
576
}
×
577

578
func (i *Interaction) WithJSONResponseBody(body interface{}) *Interaction {
9✔
579
        return i.withJSONBody(body, INTERACTION_PART_RESPONSE)
9✔
580
}
9✔
581

582
func (i *Interaction) withJSONBody(body interface{}, part interactionPart) *Interaction {
9✔
583
        cHeader := C.CString("application/json")
9✔
584
        defer free(cHeader)
9✔
585

9✔
586
        jsonBody := stringFromInterface(body)
9✔
587
        cBody := C.CString(jsonBody)
9✔
588
        defer free(cBody)
9✔
589

9✔
590
        C.pactffi_with_body(i.handle, C.int(part), cHeader, cBody)
9✔
591

9✔
592
        return i
9✔
593
}
9✔
594

595
func (i *Interaction) WithRequestBody(contentType string, body []byte) *Interaction {
×
596
        return i.withBody(contentType, body, 0)
×
597
}
×
598

599
func (i *Interaction) WithResponseBody(contentType string, body []byte) *Interaction {
×
600
        return i.withBody(contentType, body, 1)
×
601
}
×
602

603
func (i *Interaction) withBody(contentType string, body []byte, part interactionPart) *Interaction {
×
604
        cHeader := C.CString(contentType)
×
605
        defer free(cHeader)
×
606

×
607
        cBody := C.CString(string(body))
×
608
        defer free(cBody)
×
609

×
610
        C.pactffi_with_body(i.handle, C.int(part), cHeader, cBody)
×
611

×
612
        return i
×
613
}
×
614

615
func (i *Interaction) withBinaryBody(contentType string, body []byte, part interactionPart) *Interaction {
×
616
        cHeader := C.CString(contentType)
×
617
        defer free(cHeader)
×
618

×
619
        C.pactffi_with_binary_file(i.handle, C.int(part), cHeader, (*C.uchar)(unsafe.Pointer(&body[0])), C.ulong(len(body)))
×
620

×
621
        return i
×
622
}
×
623

624
func (i *Interaction) WithBinaryRequestBody(body []byte) *Interaction {
×
625
        return i.withBinaryBody("application/octet-stream", body, INTERACTION_PART_REQUEST)
×
626
}
×
627

628
func (i *Interaction) WithBinaryResponseBody(body []byte) *Interaction {
×
629
        return i.withBinaryBody("application/octet-stream", body, INTERACTION_PART_RESPONSE)
×
630
}
×
631

632
func (i *Interaction) WithRequestMultipartFile(contentType string, filename string, mimePartName string) *Interaction {
×
633
        return i.withMultipartFile(contentType, filename, mimePartName, INTERACTION_PART_REQUEST)
×
634
}
×
635

636
func (i *Interaction) WithResponseMultipartFile(contentType string, filename string, mimePartName string) *Interaction {
×
637
        return i.withMultipartFile(contentType, filename, mimePartName, INTERACTION_PART_RESPONSE)
×
638
}
×
639

640
func (i *Interaction) withMultipartFile(contentType string, filename string, mimePartName string, part interactionPart) *Interaction {
×
641
        cHeader := C.CString(contentType)
×
642
        defer free(cHeader)
×
643

×
644
        cPartName := C.CString(mimePartName)
×
645
        defer free(cPartName)
×
646

×
647
        cFilename := C.CString(filename)
×
648
        defer free(cFilename)
×
649

×
650
        C.pactffi_with_multipart_file(i.handle, C.int(part), cHeader, cFilename, cPartName)
×
651

×
652
        return i
×
653
}
×
654

655
// Set the expected HTTTP response status
656
func (i *Interaction) WithStatus(status int) *Interaction {
18✔
657
        C.pactffi_response_status(i.handle, C.ushort(status))
18✔
658

18✔
659
        return i
18✔
660
}
18✔
661

662
type stringLike interface {
663
        String() string
664
}
665

666
func stringFromInterface(obj interface{}) string {
126✔
667
        switch t := obj.(type) {
126✔
668
        case string:
81✔
669
                return t
81✔
670
        default:
45✔
671
                bytes, err := json.Marshal(obj)
45✔
672
                if err != nil {
45✔
673
                        panic(fmt.Sprintln("unable to marshal body to JSON:", err))
×
674
                }
675
                return quotedString(string(bytes))
45✔
676
        }
677
}
678

679
// This fixes a quirk where certain "matchers" (e.g. matchers.S/String) are
680
// really just strings. However, whene we JSON encode them they get wrapped in quotes
681
// and the rust core sees them as plain strings, requiring then the quotes to be matched
682
func quotedString(s string) string {
45✔
683
        if s[0] == '"' && s[len(s)-1] == '"' {
45✔
684
                return s[1 : len(s)-1]
×
685
        }
×
686
        return s
45✔
687
}
688

689
// Experimental logging options
690
// func LogMessage(pkg, level, message string) {
691
//         cPkg := C.CString(pkg)
692
//         defer free(cPkg)
693

694
//         cLevel := C.CString(level)
695
//         defer free(cLevel)
696

697
//         cMessage := C.CString(message)
698
//         defer free(cMessage)
699

700
//         res := C.pactffi_log_message(cPkg, cLevel, cMessage)
701
//         log.Println("[DEBUG] log_to_buffer res", res)
702
// }
703

704
// func logToBuffer(level logLevel) error {
705
//         res := C.pactffi_log_to_buffer(C.int(level))
706
//         log.Println("[DEBUG] log_to_buffer res", res)
707

708
//         return logResultToError(int(res))
709
// }
710

711
func logToStdout(level logLevel) error {
18✔
712
        res := C.pactffi_log_to_stdout(C.int(level))
18✔
713
        log.Println("[DEBUG] log_to_stdout res", res)
18✔
714

18✔
715
        return logResultToError(int(res))
18✔
716
}
18✔
717

718
func logToFile(file string, level logLevel) error {
×
719
        cFile := C.CString(file)
×
720
        defer free(cFile)
×
721

×
722
        res := C.pactffi_log_to_file(cFile, C.int(level))
×
723
        log.Println("[DEBUG] log_to_file res", res)
×
724

×
725
        return logResultToError(int(res))
×
726
}
×
727

728
// func getLogBuffer() string {
729
//         buf := C.pactffi_fetch_log_buffer()
730
//         defer free(buf)
731

732
//         return C.GoString(buf)
733
// }
734

735
func logResultToError(res int) error {
18✔
736
        switch res {
18✔
737
        case 0:
9✔
738
                return nil
9✔
739
        case -1:
9✔
740
                return ErrCantSetLogger
9✔
741
        case -2:
×
742
                return ErrNoLogger
×
743
        case -3:
×
744
                return ErrSpecifierNotUtf8
×
745
        case -4:
×
746
                return ErrUnknownSinkType
×
747
        case -5:
×
748
                return ErrMissingFilePath
×
749
        case -6:
×
750
                return ErrCantOpenSinkToFile
×
751
        case -7:
×
752
                return ErrCantConstructSink
×
753
        default:
×
754
                return fmt.Errorf("an unknown error ocurred when writing to pact file")
×
755
        }
756
}
757

758
// Errors
759
var (
760
        // ErrHandleNotFound indicates the underlying handle was not found, and a logic error in the framework
761
        ErrHandleNotFound = fmt.Errorf("unable to find the native interface handle (this indicates a defect in the framework)")
762

763
        // ErrMockServerPanic indicates a panic ocurred when invoking the remote Mock Server.
764
        ErrMockServerPanic = fmt.Errorf("a general panic occured when starting/invoking mock service (this indicates a defect in the framework)")
765

766
        // ErrUnableToWritePactFile indicates an error when writing the pact file to disk.
767
        ErrUnableToWritePactFile = fmt.Errorf("unable to write to file")
768

769
        // ErrMockServerNotfound indicates the Mock Server could not be found.
770
        ErrMockServerNotfound = fmt.Errorf("unable to find mock server with the given port")
771

772
        // ErrInvalidMockServerConfig indicates an issue configuring the mock server
773
        ErrInvalidMockServerConfig = fmt.Errorf("configuration for the mock server was invalid and an unknown error occurred (this is most likely a defect in the framework)")
774

775
        // ErrInvalidPact indicates the pact file provided to the mock server was not a valid pact file
776
        ErrInvalidPact = fmt.Errorf("pact given to mock server is invalid")
777

778
        // ErrMockServerUnableToStart means the mock server could not be started in the rust library
779
        ErrMockServerUnableToStart = fmt.Errorf("unable to start the mock server")
780

781
        // ErrInvalidAddress means the address provided to the mock server was invalid and could not be understood
782
        ErrInvalidAddress = fmt.Errorf("invalid address provided to the mock server")
783

784
        // ErrMockServerTLSConfiguration indicates a TLS mock server could not be started
785
        // and is likely a framework level problem
786
        ErrMockServerTLSConfiguration = fmt.Errorf("a tls mock server could not be started (this is likely a defect in the framework)")
787

788
        // ErrNoInteractions indicates no Interactions have been registered to a mock server, and cannot be started/stopped until at least one is added
789
        ErrNoInteractions = fmt.Errorf("no interactions have been registered for the mock server")
790

791
        // ErrPluginFailed indicates the plugin could not be started
792
        ErrPluginFailed = fmt.Errorf("the plugin could not be started")
793
)
794

795
// Log Errors
796
var (
797
        ErrCantSetLogger      = fmt.Errorf("can't set logger (applying the logger failed, perhaps because one is applied already).")
798
        ErrNoLogger           = fmt.Errorf("no logger has been initialized (call `logger_init` before any other log function).")
799
        ErrSpecifierNotUtf8   = fmt.Errorf("The sink specifier was not UTF-8 encoded.")
800
        ErrUnknownSinkType    = fmt.Errorf(`the sink type specified is not a known type (known types: "buffer", "stdout", "stderr", or "file /some/path").`)
801
        ErrMissingFilePath    = fmt.Errorf("no file path was specified in a file-type sink specification.")
802
        ErrCantOpenSinkToFile = fmt.Errorf("opening a sink to the specified file path failed (check permissions).")
803
        ErrCantConstructSink  = fmt.Errorf("can't construct the log sink")
804
)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc