Skip to content

Commit

Permalink
Add operation_id to errors
Browse files Browse the repository at this point in the history
This patch changes the code generator so that errors support the
`operation_id` attribute. This attribute and the existing ones will now
be used to generate more complete error messages. For example, the
following JSON error response:

```
{
  "kind": "Error",
  "id": "401",
  "href": "/api/clusters_mgmt/v1/errors/401",
  "code": "CLUSTERS-MGMT-401",
  "reason": "My reason",
  "operation_id": "456"
}
```

Will result in the following error string (in one single line):

```
identifier is '401', code is 'CLUSTERS-MGMT-401' and
operation identifier is '456': My reason
```

Related: openshift-online/ocm-sdk-go#150
Signed-off-by: Juan Hernandez <jhernand@redhat.com>
  • Loading branch information
jhernand committed Feb 26, 2020
1 parent a92c173 commit 0f3dd26
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 45 deletions.
141 changes: 98 additions & 43 deletions pkg/generators/golang/errors_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ func (g *ErrorsGenerator) generateCommonErrors() error {
}

// Generate the code:
g.buffer.Import("fmt", "")
g.buffer.Import("io", "")
g.buffer.Import("strings", "")
g.buffer.Import("github.com/golang/glog", "")
g.buffer.Import("github.com/openshift-online/ocm-api-metamodel/pkg/runtime", "")
g.buffer.Import(g.packages.HelpersImport(), "")
Expand All @@ -185,57 +187,67 @@ func (g *ErrorsGenerator) generateCommonErrors() error {
// ErrorBuilder is a builder for the error type.
type ErrorBuilder struct{
id *string
href *string
code *string
reason *string
id *string
href *string
code *string
reason *string
operationID *string
}
// Error represents errors.
type Error struct {
id *string
href *string
code *string
reason *string
id *string
href *string
code *string
reason *string
operationID *string
}
// NewError returns a new ErrorBuilder
// NewError creates a new builder that can then be used to create error objects.
func NewError() *ErrorBuilder {
return new(ErrorBuilder)
return &ErrorBuilder{}
}
// ID sets the id field for the ErrorBuilder
func (e *ErrorBuilder) ID(id string) *ErrorBuilder {
e.id = &id
return e
// ID sets the identifier of the error.
func (b *ErrorBuilder) ID(value string) *ErrorBuilder {
b.id = &value
return b
}
// HREF sets the href field for the ErrorBuilder
func (e *ErrorBuilder) HREF(href string) *ErrorBuilder {
e.href = &href
return e
// HREF sets the link of the error.
func (b *ErrorBuilder) HREF(value string) *ErrorBuilder {
b.href = &value
return b
}
// Code sets the cpde field for the ErrorBuilder
func (e *ErrorBuilder) Code(code string) *ErrorBuilder {
e.code = &code
return e
// Code sets the code of the error.
func (b *ErrorBuilder) Code(value string) *ErrorBuilder {
b.code = &value
return b
}
// Reason sets the reason field for the ErrorBuilder
func (e *ErrorBuilder) Reason(reason string) *ErrorBuilder {
e.reason = &reason
return e
// Reason sets the reason of the error.
func (b *ErrorBuilder) Reason(value string) *ErrorBuilder {
b.reason = &value
return b
}
// Build builds a new error type or returns an error.
func (e *ErrorBuilder) Build() (*Error, error) {
err := new(Error)
err.reason = e.reason
err.code = e.code
err.id = e.id
err.href = e.href
return err, nil
// OperationID sets the identifier of the operation that caused the error.
func (b *ErrorBuilder) OperationID(value string) *ErrorBuilder {
b.operationID = &value
return b
}
// Build uses the information stored in the builder to create a new error object.
func (b *ErrorBuilder) Build() (result *Error, err error) {
result = &Error{
id: b.id,
href: b.href,
code: b.code,
reason: b.reason,
operationID: b.operationID,
}
return
}
// Kind returns the name of the type of the error.
Expand Down Expand Up @@ -318,22 +330,57 @@ func (g *ErrorsGenerator) generateCommonErrors() error {
return
}
// OperationID returns the identifier of the operation that caused the error.
func (e *Error) OperationID() string {
if e != nil && e.operationID != nil {
return *e.operationID
}
return ""
}
// GetOperationID returns the identifier of the operation that caused the error and
// a flag indicating if that identifier does have a value.
func (e *Error) GetOperationID() (value string, ok bool) {
ok = e != nil && e.operationID != nil
if ok {
value = *e.operationID
}
return
}
// Error is the implementation of the error interface.
func (e *Error) Error() string {
if e.reason != nil {
return *e.reason
chunks := make([]string, 0, 3)
if e.id != nil && *e.id != "" {
chunks = append(chunks, fmt.Sprintf("identifier is '%s'", *e.id))
}
if e.code != nil {
return *e.code
if e.code != nil && *e.code != "" {
chunks = append(chunks, fmt.Sprintf("code is '%s'", *e.code))
}
if e.id != nil {
return *e.id
if e.operationID != nil && *e.operationID != "" {
chunks = append(chunks, fmt.Sprintf("operation identifier is '%s'", *e.operationID))
}
var result string
size := len(chunks)
if size == 1 {
result = chunks[0]
} else if size > 1 {
result = strings.Join(chunks[0:size-1], ", ") + " and " + chunks[size-1]
}
return "unknown error"
if e.reason != nil && *e.reason != "" {
if result != "" {
result = result + ": "
}
result = result + *e.reason
}
if result == "" {
result = "unknown error"
}
return result
}
// UnmarshalError reads an error from the given which can be an slice of bytes, a
// string, a reader or a JSON decoder.
// UnmarshalError reads an error from the given source which can be an slice of
// bytes, a string, a reader or a JSON decoder.
func UnmarshalError(source interface{}) (object *Error, err error) {
iterator, err := helpers.NewIterator(source)
if err != nil {
Expand Down Expand Up @@ -364,6 +411,9 @@ func (g *ErrorsGenerator) generateCommonErrors() error {
case "reason":
value := iterator.ReadString()
object.reason = &value
case "operation_id":
value := iterator.ReadString()
object.operationID = &value
default:
iterator.ReadAny()
}
Expand Down Expand Up @@ -403,6 +453,11 @@ func (g *ErrorsGenerator) generateCommonErrors() error {
stream.WriteObjectField("reason")
stream.WriteString(*e.reason)
}
if e.operationID != nil {
stream.WriteMore()
stream.WriteObjectField("operation_id")
stream.WriteString(*e.operationID)
}
stream.WriteObjectEnd()
}
Expand Down
17 changes: 17 additions & 0 deletions tests/go/builders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

amv1 "github.com/openshift-online/ocm-api-metamodel/tests/go/generated/accountsmgmt/v1"
cmv1 "github.com/openshift-online/ocm-api-metamodel/tests/go/generated/clustersmgmt/v1"
"github.com/openshift-online/ocm-api-metamodel/tests/go/generated/errors"
)

var _ = Describe("Builder", func() {
Expand Down Expand Up @@ -202,6 +203,22 @@ var _ = Describe("Builder", func() {
Expect(second.Username()).To(Equal("youruser"))
Expect(second.Email()).To(Equal("yourmail"))
})

It("Can build an error", func() {
object, err := errors.NewError().
ID("401").
HREF("/api/clusters_mgmt/v1/errors/401").
Code("CLUSTERS-MGMT-401").
Reason("My reason").
OperationID("456").
Build()
Expect(err).ToNot(HaveOccurred())
Expect(object.ID()).To(Equal("401"))
Expect(object.HREF()).To(Equal("/api/clusters_mgmt/v1/errors/401"))
Expect(object.Code()).To(Equal("CLUSTERS-MGMT-401"))
Expect(object.Reason()).To(Equal("My reason"))
Expect(object.OperationID()).To(Equal("456"))
})
})

Describe("Copy", func() {
Expand Down
102 changes: 102 additions & 0 deletions tests/go/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright (c) 2020 Red Hat, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This file contains tests for errors.

package tests

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"

"github.com/openshift-online/ocm-api-metamodel/tests/go/generated/errors"
)

var _ = FDescribe("Errors", func() {
DescribeTable(
"Conversion to string",
func(input, expected string) {
object, err := errors.UnmarshalError(input)
Expect(err).ToNot(HaveOccurred())
Expect(object).ToNot(BeNil())
actual := object.Error()
Expect(actual).To(Equal(expected))
},
Entry(
"Empty",
`{}`,
"unknown error",
),
Entry(
"Only identifier",
`{
"id": "401"
}`,
"identifier is '401'",
),
Entry(
"Only code",
`{
"code": "CLUSTERS-MGMT-401"
}`,
"code is 'CLUSTERS-MGMT-401'",
),
Entry(
"Only operation identifier",
`{
"operation_id": "456"
}`,
"operation identifier is '456'",
),
Entry(
"Only reason",
`{
"reason": "My reason"
}`,
`My reason`,
),
Entry(
"Identifier and code",
`{
"id": "401",
"code": "CLUSTERS-MGMT-401"
}`,
"identifier is '401' and code is 'CLUSTERS-MGMT-401'",
),
Entry(
"Identifier, code and operation identifier",
`{
"id": "401",
"code": "CLUSTERS-MGMT-401",
"operation_id": "456"
}`,
"identifier is '401', code is 'CLUSTERS-MGMT-401' and operation "+
"identifier is '456'",
),
Entry(
"Identifier, code, operation identifier and reason",
`{
"id": "401",
"code": "CLUSTERS-MGMT-401",
"reason": "My reason",
"operation_id": "456"
}`,
"identifier is '401', code is 'CLUSTERS-MGMT-401' and operation "+
"identifier is '456': My reason",
),
)
})
4 changes: 3 additions & 1 deletion tests/go/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ var _ = Describe("Marshal", func() {
HREF("/api/clusters_mgmt/v1/errors/401").
Code("CLUSTERS-MGMT-401").
Reason("My reason").
OperationID("456").
Build()
Expect(err).ToNot(HaveOccurred())
buffer := new(bytes.Buffer)
Expand All @@ -187,7 +188,8 @@ var _ = Describe("Marshal", func() {
"id": "401",
"href": "/api/clusters_mgmt/v1/errors/401",
"code": "CLUSTERS-MGMT-401",
"reason": "My reason"
"reason": "My reason",
"operation_id": "456"
}`))
})

Expand Down
4 changes: 3 additions & 1 deletion tests/go/unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@ var _ = Describe("Unmarshal", func() {
"id": "401",
"href": "/api/clusters_mgmt/v1/errors/401",
"code": "CLUSTERS-MGMT-401",
"reason": "My reason"
"reason": "My reason",
"operation_id": "456"
}`)
Expect(err).ToNot(HaveOccurred())
Expect(object).ToNot(BeNil())
Expand All @@ -478,6 +479,7 @@ var _ = Describe("Unmarshal", func() {
Expect(object.HREF()).To(Equal("/api/clusters_mgmt/v1/errors/401"))
Expect(object.Code()).To(Equal("CLUSTERS-MGMT-401"))
Expect(object.Reason()).To(Equal("My reason"))
Expect(object.OperationID()).To(Equal("456"))
})

It("Can read mixed known and unknown attributes", func() {
Expand Down

0 comments on commit 0f3dd26

Please sign in to comment.