Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add problem library #2

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# PlayNet Libs

[![Go Report Card](https://goreportcard.com/badge/github.com/playnet-public/libs)](https://goreportcard.com/report/github.com/playnet-public/libs)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Build Status](https://travis-ci.org/playnet-public/libs.svg?branch=master)](https://travis-ci.org/playnet-public/libs)
Expand All @@ -9,7 +10,8 @@ The repository containing various shared libs for the entire playnet project.
## Libs

### Logging
Our logging setup using go.uber.org/zap.

Our logging setup using `go.uber.org/zap`.
Sentry and Jaeger are being added for production environments.

```go
Expand All @@ -27,6 +29,46 @@ If you provide a zap.Error tag, the related stacktrace will also be attached.

Additionally there is a tracer(opentracing/jaeger) available in the logger which should be closed before exiting main.

### Problems

`problems` is a small library which implements the RFC7807 error response format standard for e.g. HTTP API's.

The `problems` lib provides a struct called `Problem` and an interface called `ProblemInfo`.
`Problem` implements the `error` interface, so you can simply return the problem as an error in your application.

```go
func returnError() error {
return problems.New()
}
```

You can marshal the `Problem` struct to JSON:

```go
func makeJSON(problem *Problem) ([]byte, error) {
return json.Marshal(&problem)
}
```

If you want to define a HTTP problem, you can do it like this:

```go
var problemNotFound = problems.New().SetTitle("NotFound").SetDetail("The requested url was not found.").SetStatus(404).SetType("https://example.com/problem/description")
```

For custom fields for more detailed problem information, you can implement `Problem` in your custom struct:

```go
type customInfo struct {
AdditionalField1 bool `json:"additional_field_1"`
}

type customProblem struct {
*problems.Problem
pixlcrashr marked this conversation as resolved.
Show resolved Hide resolved
*customInfo
}
```

## Contributions

Pull Requests and Issue Reports are welcome.
Expand Down
147 changes: 147 additions & 0 deletions problems/problems.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package errors
pixlcrashr marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"

"github.com/pkg/errors"
)

//DefaultType implements the default type content described in RFC 7807
const DefaultType = "about:blank"

//Problem implements the RFC 7807 "problem"/error standard
//Additional error field can be defined in custom struct implementing the Problem struct
type Problem struct {
pixlcrashr marked this conversation as resolved.
Show resolved Hide resolved
Title string `json:"title"`
Detail string `json:"detail,omitempty"`
Type string `json:"type,omitempty"`
Instance string `json:"instance,omitempty"`
Status int `json:"status,omitempty"`
cause error
}

//Wrap is an alias to github.com/pkg/errors Wrap function. If Problem pointer passed as error, it sets the error and a new message of Problem.
func Wrap(err error, msg string) error {
if err == nil {
return nil
}

problem, ok := err.(*Problem)
if !ok {
return errors.Wrap(err, msg)
}

problem.cause = errors.Wrap(problem.cause, msg)
return problem
}

//WithStack is an alias to github.com/pkg/errors WithStack function. If Problem pointer passed as error, it sets the error of Problem.
func WithStack(err error) error {
if err == nil {
return nil
}

problem, ok := err.(*Problem)
if !ok {
return errors.WithStack(err)
}

problem.cause = errors.WithStack(problem.cause)
return problem
}

//WithMessage is an alias to github.com/pkg/errors WithMessage function. If Problem pointer passed as error, it sets the error, stacktrace and message of Problem.
func WithMessage(err error, msg string) error {
if err == nil {
return nil
}

problem, ok := err.(*Problem)
if !ok {
return errors.WithMessage(err, msg)
}

problem.cause = errors.WithMessage(problem.cause, msg)
return problem
}

//New creates a new Problem with all object details described in RFC 7807
func New(title, detail string, status int) error {
return newWithError(
errors.New(detail),
title,
detail,
status,
)
}

pixlcrashr marked this conversation as resolved.
Show resolved Hide resolved
func newWithError(err error, title, detail string, status int) *Problem {
return &Problem{
Type: DefaultType,
Title: title,
Detail: err.Error(),
Status: status,
cause: err,
}
}

//Format displays the contained error in a specific format
func (p Problem) Format(s fmt.State, verb rune) {
if p.cause == nil {
fmt.Fprint(s, p.Detail)
return
}

formatter, ok := p.cause.(fmt.Formatter)
if !ok {
fmt.Fprint(s, p.Detail)
return
}

formatter.Format(s, verb)
}

//Cause returns the outermost error of Problem
func (p Problem) Cause() error {
return p.cause
}

//Error returns the error of the problem
func (p Problem) Error() string {
return p.cause.Error()
}

//SetTitle sets the title field of the problem, specified in RFC 7807
func (p Problem) SetTitle(title string) *Problem {
pixlcrashr marked this conversation as resolved.
Show resolved Hide resolved
p.Title = title
return &p
}

//SetDetail sets the detail field of the problem, specified in RFC 7807
func (p Problem) SetDetail(detail string) *Problem {
p.Detail = detail
return &p
}

//SetType sets the detail field of the problem, specified in RFC 7807
func (p Problem) SetType(t string) *Problem {
if t == "" {
p.Type = DefaultType
} else {
p.Type = t
}

return &p
}

//SetInstance sets the instance field of the problem, specified in RFC 7807
func (p Problem) SetInstance(instance string) *Problem {
p.Instance = instance
return &p
}

//SetStatus sets the status field of the problem, specified in RFC 7807
func (p Problem) SetStatus(status int) *Problem {
p.Status = status
return &p
}