diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db11091..312ec86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,10 +26,10 @@ jobs: make build-in-docker - name: Code coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} - file: ./build/coverage.txt + files: ./build/coverage.txt yml: ./codecov.yaml - name: SonarCloud Scan diff --git a/CHANGELOG.md b/CHANGELOG.md index 8deab0a..9763e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Improvements: * Ability to remap struct member names to via `xmlrpc` tags (#47) +* Ability to skip unknown fields by `SkipUnknownFields(bool)` `Option` (#48) Library is now built against Go 1.19 diff --git a/codec.go b/codec.go index 21dc6eb..4c64846 100644 --- a/codec.go +++ b/codec.go @@ -62,6 +62,16 @@ func NewCodec(endpoint *url.URL, httpClient *http.Client) *Codec { } } +// SetEncoder allows setting a new Encoder on the codec +func (c *Codec) SetEncoder(encoder Encoder) { + c.encoder = encoder +} + +// SetDecoder allows setting a new Decoder on the codec +func (c *Codec) SetDecoder(decoder Decoder) { + c.decoder = decoder +} + func (c *Codec) WriteRequest(req *rpc.Request, args interface{}) error { bodyBuffer := new(bytes.Buffer) err := c.encoder.Encode(bodyBuffer, req.ServiceMethod, args) diff --git a/codecov.yaml b/codecov.yaml index f0f6358..523a4e8 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -1,4 +1,12 @@ coverage: + status: + project: + default: + target: 80% + threshold: 5% + patch: + default: + target: 70% precision: 2 round: down range: "70...100" diff --git a/decode.go b/decode.go index 0a65b73..37c8242 100644 --- a/decode.go +++ b/decode.go @@ -22,7 +22,9 @@ type Decoder interface { } // StdDecoder is the default implementation of the Decoder interface. -type StdDecoder struct{} +type StdDecoder struct { + skipUnknownFields bool +} func (d *StdDecoder) DecodeRaw(body []byte, v interface{}) error { response, err := NewResponse(body) @@ -139,6 +141,9 @@ func (d *StdDecoder) decodeValue(value *ResponseValue, field reflect.Value) erro f := findFieldByNameOrTag(field, fName) if !f.IsValid() { + if d.skipUnknownFields { + continue + } return fmt.Errorf("cannot find field '%s' on struct", fName) } diff --git a/decode_test.go b/decode_test.go index e021619..8fea95f 100644 --- a/decode_test.go +++ b/decode_test.go @@ -13,11 +13,12 @@ import ( func TestStdDecoder_DecodeRaw(t *testing.T) { tests := []struct { - name string - testFile string - v interface{} - expect interface{} - err error + name string + testFile string + skipUnknown bool + v interface{} + expect interface{} + err error }{ { name: "simple response", @@ -107,6 +108,39 @@ func TestStdDecoder_DecodeRaw(t *testing.T) { }, }, }, + { + name: "struct response - skip unknown", + testFile: "response_struct.xml", + skipUnknown: true, + v: &struct { + Struct struct { + Foo string + Baz int + WoBleBobble bool + WoBleBobble2 int + } + }{}, + expect: &struct { + Struct struct { + Foo string + Baz int + WoBleBobble bool + WoBleBobble2 int + } + }{ + Struct: struct { + Foo string + Baz int + WoBleBobble bool + WoBleBobble2 int + }{ + Foo: "bar", + Baz: 2, + WoBleBobble: true, + WoBleBobble2: 34, + }, + }, + }, { name: "struct response - bad param", testFile: "response_struct.xml", @@ -121,6 +155,7 @@ func TestStdDecoder_DecodeRaw(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dec := &StdDecoder{} + dec.skipUnknownFields = tt.skipUnknown err := dec.DecodeRaw(loadTestFile(t, tt.testFile), tt.v) if tt.err == nil { diff --git a/options.go b/options.go index c762359..e0d1f4f 100644 --- a/options.go +++ b/options.go @@ -26,3 +26,13 @@ func UserAgent(userAgent string) Option { client.codec.userAgent = userAgent } } + +// SkipUnknownFields option allows setting decoder setting to skip unknown fields. +// This is only effective if using standard client, which in turn uses StdDecoder. +func SkipUnknownFields(skip bool) Option { + return func(client *Client) { + if v, ok := client.codec.decoder.(*StdDecoder); ok { + v.skipUnknownFields = skip + } + } +} diff --git a/options_test.go b/options_test.go index c81cba9..288e779 100644 --- a/options_test.go +++ b/options_test.go @@ -199,3 +199,58 @@ func TestClient_Option_HttpClient(t *testing.T) { }) } } + +func TestClient_Option_SkipUnknownFields(t *testing.T) { + tests := []struct { + name string + opts []Option + expect bool + }{ + { + name: "default setting", + expect: false, + }, + { + name: "new setting - false", + opts: []Option{ + SkipUnknownFields(false), + }, + expect: false, + }, + { + name: "new setting - false", + opts: []Option{ + SkipUnknownFields(true), + }, + expect: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + serverCalled := false + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + serverCalled = true + _, _ = fmt.Fprintln(w, string(loadTestFile(t, "response_bugzilla_version.xml"))) + })) + defer ts.Close() + + c, err := NewClient(ts.URL, tt.opts...) + require.NoError(t, err) + + v := &struct { + Bugzilla struct { + } + }{} + + err = c.Call("test.Method", nil, v) + if tt.expect { + require.NoError(t, err) + } else { + require.Error(t, err) + } + + require.True(t, serverCalled, "server must be called") + }) + } +}