Skip to content

Commit

Permalink
Merge pull request #2 from hostwithquantum/handle-errors
Browse files Browse the repository at this point in the history
handle errors
  • Loading branch information
till committed Mar 13, 2023
2 parents a1665f6 + 47012f4 commit 343b8df
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 36 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ body := golexoffice.ContactBody{
"",
0,
golexoffice.ContactBodyRoles{
golexoffice.ContactBodyCustomer{},
golexoffice.ContactBodyVendor{},
&golexoffice.ContactBodyCustomer{},
&golexoffice.ContactBodyVendor{},
},
golexoffice.ContactBodyCompany{
&golexoffice.ContactBodyCompany{
"J&J Ideenschmiede GmbH",
"12345/12345",
"DE123456789",
Expand All @@ -79,14 +79,14 @@ body := golexoffice.ContactBody{
}},
},
golexoffice.ContactBodyAddresses{
[]golexoffice.ContactBodyBilling{{
[]&golexoffice.ContactBodyBilling{{
"Rechnungsadressenzusatz",
"Fährstraße 31",
"21502",
"Geesthacht",
"DE",
}},
[]golexoffice.ContactBodyShipping{{
[]&golexoffice.ContactBodyShipping{{
"Lieferadressenzusatz",
"Fährstraße 31",
"21502",
Expand Down Expand Up @@ -130,15 +130,15 @@ body := golexoffice.ContactBody{
"ID",
1,
golexoffice.ContactBodyRoles{
golexoffice.ContactBodyCustomer{},
golexoffice.ContactBodyVendor{},
&golexoffice.ContactBodyCustomer{},
&golexoffice.ContactBodyVendor{},
},
golexoffice.ContactBodyCompany{
"J&J Ideenschmiede GmbH",
"12345/12345",
"DE123456789",
true,
[]golexoffice.ContactBodyContactPersons{{
[]&golexoffice.ContactBodyContactPersons{{
"Herr",
"Jonas",
"Kwiedor",
Expand All @@ -147,14 +147,14 @@ body := golexoffice.ContactBody{
}},
},
golexoffice.ContactBodyAddresses{
[]golexoffice.ContactBodyBilling{{
[]&golexoffice.ContactBodyBilling{{
"Rechnungsadressenzusatz",
"Fährstraße 31",
"21502",
"Geesthacht",
"DE",
}},
[]golexoffice.ContactBodyShipping{{
[]&golexoffice.ContactBodyShipping{{
"Lieferadressenzusatz",
"Fährstraße 31",
"21502",
Expand Down
52 changes: 50 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
package golexoffice

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)

const (
Expand Down Expand Up @@ -68,7 +72,51 @@ func (c *Config) Send(path string, body io.Reader, method, contentType string) (
return nil, err
}

// Return data
return response, nil
if isSuccessful(response) {
// Return data
return response, nil
}

// TODO(till): revisit parsing when we add more API endpoints
if strings.Contains(url, "invoices") {
return nil, parseErrorResponse(response)
}

return nil, parseLegacyErrorResponse(response)
}

func isSuccessful(response *http.Response) bool {
return response.StatusCode < 400
}

func parseErrorResponse(response *http.Response) error {
var errorResp ErrorResponse
err := json.NewDecoder(response.Body).Decode(&errorResp)
if err != nil {
return fmt.Errorf("decoding error while unpacking response: %s", err)
}

var keep []error
for _, detail := range errorResp.Details {
keep = append(keep, fmt.Errorf(
"field: %s (%s): %s", detail.Field, detail.Violation, detail.Message,
))
}

return errors.Join(keep...)
}

func parseLegacyErrorResponse(response *http.Response) error {
var errorResp LegacyErrorResponse
err := json.NewDecoder(response.Body).Decode(&errorResp)
if err != nil {
return fmt.Errorf("decoding error while unpacking response: %s", err)
}

// potentially multiple issues returned from the LexOffice API
var keep []error
for _, issue := range errorResp.IssueList {
keep = append(keep, fmt.Errorf("key: %s (%s): %s", issue.Key, issue.Source, issue.Type))
}
return errors.Join(keep...)
}
42 changes: 21 additions & 21 deletions contacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ type ContactsReturnContent struct {
Id string `json:"id,omitempty"`
Version int `json:"version,omitempty"`
Roles ContactBodyRoles `json:"roles"`
Company ContactBodyCompany `json:"company,omitempty"`
Person ContactBodyPerson `json:"person,omitempty"`
Company *ContactBodyCompany `json:"company,omitempty"`
Person *ContactBodyPerson `json:"person,omitempty"`
Addresses ContactBodyAddresses `json:"addresses"`
EmailAddresses ContactBodyEmailAddresses `json:"emailAddresses"`
PhoneNumbers ContactBodyPhoneNumbers `json:"phoneNumbers"`
Expand Down Expand Up @@ -117,21 +117,21 @@ type ContactsReturnSort struct {

// ContactBody is to create a new contact
type ContactBody struct {
Id string `json:"id,omitempty"`
Version int `json:"version,omitempty"`
Roles ContactBodyRoles `json:"roles"`
Company ContactBodyCompany `json:"company,omitempty"`
Person ContactBodyPerson `json:"person,omitempty"`
Addresses ContactBodyAddresses `json:"addresses"`
EmailAddresses ContactBodyEmailAddresses `json:"emailAddresses"`
PhoneNumbers ContactBodyPhoneNumbers `json:"phoneNumbers"`
Note string `json:"note"`
Archived bool `json:"archived,omitempty"`
Id string `json:"id,omitempty"`
Version int `json:"version"`
Roles ContactBodyRoles `json:"roles"`
Company *ContactBodyCompany `json:"company,omitempty"`
Person *ContactBodyPerson `json:"person,omitempty"`
Addresses *ContactBodyAddresses `json:"addresses,omitempty"`
EmailAddresses *ContactBodyEmailAddresses `json:"emailAddresses,omitempty"`
PhoneNumbers *ContactBodyPhoneNumbers `json:"phoneNumbers,omitempty"`
Note string `json:"note"`
Archived bool `json:"archived,omitempty"`
}

type ContactBodyRoles struct {
Customer ContactBodyCustomer `json:"customer"`
Vendor ContactBodyVendor `json:"vendor"`
Customer *ContactBodyCustomer `json:"customer,omitempty"`
Vendor *ContactBodyVendor `json:"vendor,omitempty"`
}

type ContactBodyCustomer struct {
Expand All @@ -143,11 +143,11 @@ type ContactBodyVendor struct {
}

type ContactBodyCompany struct {
Name string `json:"name"`
TaxNumber string `json:"taxNumber"`
VatRegistrationId string `json:"vatRegistrationId"`
AllowTaxFreeInvoices bool `json:"allowTaxFreeInvoices"`
ContactPersons []ContactBodyContactPersons `json:"contactPersons"`
Name string `json:"name"`
TaxNumber string `json:"taxNumber,omitempty"`
VatRegistrationId string `json:"vatRegistrationId,omitempty"`
AllowTaxFreeInvoices bool `json:"allowTaxFreeInvoices"`
ContactPersons []*ContactBodyContactPersons `json:"contactPersons"`
}

type ContactBodyPerson struct {
Expand All @@ -165,8 +165,8 @@ type ContactBodyContactPersons struct {
}

type ContactBodyAddresses struct {
Billing []ContactBodyBilling `json:"billing"`
Shipping []ContactBodyShipping `json:"shipping"`
Billing []*ContactBodyBilling `json:"billing"`
Shipping []*ContactBodyShipping `json:"shipping"`
}

type ContactBodyBilling struct {
Expand Down
5 changes: 3 additions & 2 deletions contacts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ func TestAddContact(t *testing.T) {
config.SetBaseUrl(server.URL)

resp, err := config.AddContact(golexoffice.ContactBody{
Version: 0,
Roles: golexoffice.ContactBodyRoles{
Customer: golexoffice.ContactBodyCustomer{},
Customer: &golexoffice.ContactBodyCustomer{},
},
Person: golexoffice.ContactBodyPerson{
Person: &golexoffice.ContactBodyPerson{
FirstName: "Thomas",
LastName: "Mustermann",
},
Expand Down
52 changes: 52 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package golexoffice

// source: https://developers.lexoffice.io/docs/#error-codes-legacy-error-response
// files, profile, contacts
//
// {
// "requestId":"3fb21ee4-ad26-4e2f-82af-a1197af02d08",
// "IssueList":[
// {"i18nKey":"invalid_value","source":"company and person","type":"validation_failure"},
// {"i18nKey":"missing_entity","source":"company.name","type":"validation_failure"}
// ]
// }
type LegacyErrorResponse struct {
RequestId string `json:"requestId"`
IssueList []struct {
Key string `json:"i18nKey"`
Source string `json:"source"`
Type string `json:"type"`
} `json:"IssueList"`
}

// source: https://developers.lexoffice.io/docs/#error-codes-regular-error-response
// event-subscription, invoices
//
// {
// "timestamp": "2017-05-11T17:12:31.233+02:00",
// "status": 406,
// "error": "Not Acceptable",
// "path": "/v1/invoices",
// "traceId": "90d78d0777be",
// "message": "Validation failed for request. Please see details list for specific causes.",
// "details": [
// {
// "violation": "NOTNULL",
// "field": "lineItems[0].unitPrice.taxRatePercentage",
// "message": "darf nicht leer sein"
// }
// ]
// }
type ErrorResponse struct {
Timestamp string `json:"timestamp"` // FIXME: should be a date thing
Status int `json:"status"`
Error string `json:"error"`
Path string `json:"path"`
TraceID string `json:"traceId"`
Message string `json:"message"`
Details []struct {
Violation string `json:"violation"`
Field string `json:"field"`
Message string `json:"message"`
} `json:"details"`
}
77 changes: 77 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package golexoffice_test

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/hostwithquantum/golexoffice"
"github.com/stretchr/testify/assert"
)

// {"requestId":"3fb21ee4-ad26-4e2f-82af-a1197af02d08","IssueList":[{"i18nKey":"invalid_value","source":"company and person","type":"validation_failure"},{"i18nKey":"missing_entity","source":"company.name","type":"validation_failure"}]}

// {"requestId":"75d4dad6-6ccb-40fd-8c22-797f2d421d98","IssueList":[{"i18nKey":"missing_entity","source":"company.vatRegistrationId","type":"validation_failure"},{"i18nKey":"missing_entity","source":"company.taxNumber","type":"validation_failure"}]}

func TestErrorResponse(t *testing.T) {
server := errorMock()
defer server.Close()

lexOffice := golexoffice.NewConfig("token", nil)
lexOffice.SetBaseUrl(server.URL)

t.Run("errors=legacy", func(t *testing.T) {
_, err := lexOffice.AddContact(golexoffice.ContactBody{
Company: &golexoffice.ContactBodyCompany{
Name: "company",
VatRegistrationId: "",
TaxNumber: "",
},
})
assert.Error(t, err)
})

t.Run("errors=new", func(t *testing.T) {
_, err := lexOffice.AddInvoice(golexoffice.InvoiceBody{})
assert.Error(t, err)
})

}

func errorMock() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v1/contacts" {
w.WriteHeader(http.StatusBadRequest)
//nolint:errcheck
w.Write([]byte(`{
"requestId":"75d4dad6-6ccb-40fd-8c22-797f2d421d98",
"IssueList":[
{"i18nKey":"missing_entity","source":"company.vatRegistrationId","type":"validation_failure"},
{"i18nKey":"missing_entity","source":"company.taxNumber","type":"validation_failure"}
]
}`))
return
}
if r.URL.Path == "/v1/invoices" {
w.WriteHeader(http.StatusNotAcceptable)
//nolint:errcheck
w.Write([]byte(`{
"timestamp": "2017-05-11T17:12:31.233+02:00",
"status": 406,
"error": "Not Acceptable",
"path": "/v1/invoices",
"traceId": "90d78d0777be",
"message": "Validation failed for request. Please see details list for specific causes.",
"details": [
{
"violation": "NOTNULL",
"field": "lineItems[0].unitPrice.taxRatePercentage",
"message": "darf nicht leer sein"
}
]
}`))
return
}
w.WriteHeader(http.StatusNotFound)
}))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/hostwithquantum/golexoffice

go 1.19
go 1.20

require github.com/stretchr/testify v1.8.2

Expand Down

0 comments on commit 343b8df

Please sign in to comment.