-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: impl Problem Details based on RFC 7807
- Loading branch information
0 parents
commit 7364c93
Showing
11 changed files
with
863 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
name: Lint and Test | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
go-version: [ '1.21.x' ] | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Setup Go ${{ matrix.go-version }} | ||
uses: actions/setup-go@v3 | ||
with: | ||
go-version: ${{ matrix.go-version }} | ||
|
||
- name: Display Go version | ||
run: go version | ||
|
||
- name: Prepare Tooling | ||
run: make tools | ||
|
||
- name: Run Linter | ||
run: make lint | ||
|
||
- name: Run Test | ||
run: make test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# If you prefer the allow list template instead of the deny list, see community template: | ||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore | ||
# | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
# Go workspace file | ||
go.work | ||
|
||
# Editor | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#!/usr/bin/make -f | ||
|
||
# Choosing the shell | ||
# - [docs](https://www.gnu.org/software/make/manual/html_node/Choosing-the-Shell.html) | ||
SHELL =/bin/bash | ||
|
||
|
||
# The default target is to prepare the development environment. | ||
all: tools lint | ||
|
||
.PHONY: lint | ||
lint: hack/go-lint.sh | ||
@chmod +x hack/go-lint.sh | ||
@echo "Running linter." | ||
@hack/go-lint.sh | ||
@echo "Linter done." | ||
|
||
.PHONY: test | ||
test: hack/go-unittest.sh | ||
@chmod +x hack/go-unittest.sh | ||
@echo "Running unit tests." | ||
@hack/go-unittest.sh | ||
@echo "Unit tests done." | ||
|
||
|
||
# Install all development tools, these tools are used by pre-commit hook. | ||
tools: hack/install-tools.sh | ||
@echo "Installing tools" | ||
@hack/install-tools.sh | ||
@echo "Tools installed" | ||
|
||
|
||
# Enable pre-commit hook. | ||
setup-pre-commit: | ||
@echo "Setting up pre-commit hook" | ||
@cp -f hack/pre-commit.sh .git/hooks/pre-commit | ||
@chmod +x .git/hooks/pre-commit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# Problem Details | ||
|
||
This package implements [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807) (Problem Details for HTTP APIs) for Go. | ||
It provides an idiomatic way to use RFC 7807 in Go and offers both JSON and XML writers. | ||
|
||
## Installation | ||
|
||
```bash | ||
go get github.com/josestg/problemdetail | ||
``` | ||
|
||
## Examples | ||
|
||
### Problem Details as an Error | ||
|
||
```go | ||
const ( | ||
TypOutOfCredit = "https://example.com/probs/out-of-credit" | ||
TypProductNotFound = "https://example.com/probs/product-not-found" | ||
) | ||
|
||
func service() error { | ||
// do something... | ||
|
||
// simulate 30% error rate for each type of error. | ||
n := rand.Intn(9) | ||
if n < 3 { | ||
return problemdetail.New(TypOutOfCredit, | ||
problemdetail.WithValidateLevel(problemdetail.LStandard), | ||
problemdetail.WithTitle("You do not have enough credit."), | ||
problemdetail.WithDetail("Your current balance is 30, but that costs 50."), | ||
) | ||
} | ||
|
||
if n < 6 { | ||
return problemdetail.New(TypProductNotFound, | ||
problemdetail.WithValidateLevel(problemdetail.LStandard), | ||
problemdetail.WithTitle("The product was not found."), | ||
problemdetail.WithDetail("The product you requested was not found in the system."), | ||
) | ||
} | ||
|
||
return errors.New("unknown error") | ||
} | ||
|
||
// handler is a sample handler for HTTP server. | ||
// you can make this as a centralized error handler middleware. | ||
func handler(w http.ResponseWriter, _ *http.Request) { | ||
err := service() | ||
if err != nil { | ||
// read the error as problemdetail.ProblemDetailer. | ||
var pd problemdetail.ProblemDetailer | ||
if !errors.As(err, &pd) { | ||
// untyped error for generic error handling. | ||
untyped := problemdetail.New( | ||
problemdetail.Untyped, | ||
problemdetail.WithValidateLevel(problemdetail.LStandard), | ||
) | ||
_ = problemdetail.WriteJSON(w, untyped, http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// typed error for specific error handling. | ||
switch pd.Kind() { | ||
case TypOutOfCredit: | ||
_ = problemdetail.WriteJSON(w, pd, http.StatusForbidden) | ||
// or problemdetail.WriteXML(w, pd, http.StatusForbidden) for XML | ||
case TypProductNotFound: | ||
_ = problemdetail.WriteJSON(w, pd, http.StatusNotFound) | ||
} | ||
return | ||
} | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
|
||
func main() { | ||
http.HandleFunc("/", handler) | ||
log.Fatal(http.ListenAndServe(":8080", nil)) | ||
} | ||
``` | ||
|
||
### Problem Details with Extensions | ||
|
||
```go | ||
const ( | ||
TypOutOfCredit = "https://example.com/probs/out-of-credit" | ||
TypProductNotFound = "https://example.com/probs/product-not-found" | ||
) | ||
|
||
// BalanceProblemDetail is a sample problem detail with extension by embedding ProblemDetail. | ||
type BalanceProblemDetail struct { | ||
*problemdetail.ProblemDetail | ||
Balance int64 `json:"balance" xml:"balance"` | ||
Accounts []string `json:"accounts" xml:"accounts"` | ||
} | ||
|
||
func service() error { | ||
// do something... | ||
|
||
// simulate 30% error rate for each type of error. | ||
n := rand.Intn(9) | ||
if n < 3 { | ||
pd := problemdetail.New(TypOutOfCredit, | ||
problemdetail.WithValidateLevel(problemdetail.LStandard), | ||
problemdetail.WithTitle("You do not have enough credit."), | ||
problemdetail.WithDetail("Your current balance is 30, but that costs 50."), | ||
) | ||
return &BalanceProblemDetail{ | ||
ProblemDetail: pd, | ||
Balance: 30, | ||
Accounts: []string{"/account/12345", "/account/67890"}, | ||
} | ||
} | ||
|
||
if n < 6 { | ||
return problemdetail.New(TypProductNotFound, | ||
problemdetail.WithValidateLevel(problemdetail.LStandard), | ||
problemdetail.WithTitle("The product was not found."), | ||
problemdetail.WithDetail("The product you requested was not found in the system."), | ||
) | ||
} | ||
|
||
return errors.New("unknown error") | ||
} | ||
|
||
// handler is a sample handler for HTTP server. | ||
// you can make this as a centralized error handler middleware. | ||
func handler(w http.ResponseWriter, _ *http.Request) { | ||
err := service() | ||
if err != nil { | ||
// read the error as problemdetail.ProblemDetailer. | ||
var pd problemdetail.ProblemDetailer | ||
if !errors.As(err, &pd) { | ||
// untyped error for generic error handling. | ||
untyped := problemdetail.New( | ||
problemdetail.Untyped, | ||
problemdetail.WithValidateLevel(problemdetail.LStandard), | ||
) | ||
_ = problemdetail.WriteJSON(w, untyped, http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// typed error for specific error handling. | ||
switch pd.Kind() { | ||
case TypOutOfCredit: | ||
_ = problemdetail.WriteJSON(w, pd, http.StatusForbidden) | ||
// or problemdetail.WriteXML(w, pd, http.StatusForbidden) for XML | ||
case TypProductNotFound: | ||
_ = problemdetail.WriteJSON(w, pd, http.StatusNotFound) | ||
} | ||
return | ||
} | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
|
||
func main() { | ||
http.HandleFunc("/", handler) | ||
log.Fatal(http.ListenAndServe(":8080", nil)) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/josestg/problemdetail | ||
|
||
go 1.21.3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#!/usr/bin/env bash | ||
|
||
# ensure the execution will stop if any command fails (returns non-zero value) | ||
set -e -o pipefail | ||
|
||
echo "execute go vet" | ||
go vet ./... | ||
|
||
if ! command -v staticcheck > /dev/null; then | ||
echo "staticcheck not installed or available in the PATH" >&2 | ||
exit 1 | ||
else | ||
echo "execute staticcheck" | ||
staticcheck ./... | ||
fi | ||
|
||
if ! command -v govulncheck > /dev/null; then | ||
echo "govulncheck not installed or available in the PATH" >&2 | ||
exit 1 | ||
else | ||
echo "execute govulncheck" | ||
govulncheck ./... | ||
fi | ||
|
||
if ! command -v gosec > /dev/null; then | ||
echo "gosec not installed or available in the PATH" >&2 | ||
exit 1 | ||
else | ||
echo "execute gosec, will take a while. please be patient!" | ||
gosec -exclude-generated -quiet ./... | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/usr/bin/env bash | ||
|
||
# ensure the execution will stop if any command fails (returns non-zero value) | ||
set -e -o pipefail | ||
|
||
echo "execute go test" | ||
go test -race -short -timeout 60s ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#!/usr/bin/env bash | ||
|
||
# ensure the execution will stop if any command fails (returns non-zero value) | ||
set -e -o pipefail | ||
|
||
# install staticcheck if not installed yet. | ||
if ! command -v staticcheck > /dev/null; then | ||
echo "Installing staticcheck" | ||
go install honnef.co/go/tools/cmd/staticcheck@latest | ||
else | ||
echo "staticcheck already installed" | ||
fi | ||
|
||
# install goimports if not installed yet. | ||
if ! command -v goimports > /dev/null; then | ||
echo "Installing goimports" | ||
go install golang.org/x/tools/cmd/goimports@latest | ||
else | ||
echo "goimports already installed" | ||
fi | ||
|
||
# install gosec if not installed yet. | ||
if ! command -v gosec > /dev/null; then | ||
echo "Installing gosec" | ||
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b "$(go env GOPATH)"/bin | ||
else | ||
echo "gosec already installed" | ||
fi | ||
|
||
# install govulncheck if not installed yet. | ||
if ! command -v govulncheck > /dev/null; then | ||
echo "Installing govulncheck" | ||
go install golang.org/x/vuln/cmd/govulncheck@latest | ||
else | ||
echo "govulncheck already installed" | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#!/usr/bin/env bash | ||
|
||
# ensure the execution will stop if any command fails (returns non-zero value) | ||
set -e -o pipefail | ||
|
||
echo "check if go.mod needs to be updated" | ||
# Check if go.mod needs to be updated. | ||
if go mod tidy -v 2>&1 | grep -q 'updates to go.mod needed'; then | ||
echo "please run go mod tidy and commit the changes" | ||
exit 1 | ||
fi | ||
|
||
## this will retrieve all of the .go files that have been | ||
## changed since the last commit | ||
go_staged_files=$(git diff --cached --diff-filter=ACM --name-only -- '*.go') | ||
|
||
|
||
if [[ $go_staged_files == "" ]]; then | ||
# when there are no staged go files, we can skip the rest of the checks. | ||
echo "no go files in staged changes. skipping gofmt, goimports, go vet and staticcheck" | ||
else | ||
if ! command -v gofmt &> /dev/null ; then | ||
echo "gofmt not installed or available in the PATH" >&2 | ||
exit 1 | ||
fi | ||
|
||
|
||
if ! command -v goimports &> /dev/null ; then | ||
echo "goimports not installed or available in the PATH" >&2 | ||
exit 1 | ||
fi | ||
|
||
for file in $go_staged_files; do | ||
printf "[gofmt, goimports] %s\n" "$file" | ||
goimports -l -w "$file" | ||
gofmt -l -w "$file" | ||
git add "$file" | ||
done | ||
fi | ||
|
||
make lint | ||
make test |
Oops, something went wrong.