Skip to content

Commit

Permalink
improve error handling (#39)
Browse files Browse the repository at this point in the history
* simplify error handling

* remove obsolete dependencies

* fix tests

* update endpoint logic

* first endpoint code
  • Loading branch information
Noroth authored Aug 9, 2023
1 parent 8ebfe0e commit 9ff4282
Show file tree
Hide file tree
Showing 22 changed files with 366 additions and 413 deletions.
8 changes: 8 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ output:
linters:
enable:
- depguard
- importas
- gocritic
- gofumpt
- goimports
Expand All @@ -20,6 +21,7 @@ linters:
- revive
- unconvert
- unused
- nilerr

issues:
max-same-issues: 0
Expand All @@ -32,6 +34,12 @@ issues:
- errcheck

linters-settings:
importas:
alias:
- pkg: github.com/eurofurence/reg-room-service/internal/errors
alias: apierrors
no-unaliased: true
no-extra-aliases: false
depguard:
rules:
main:
Expand Down
22 changes: 20 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
GO ?= go
# On Windows OS is set. This makefile requires Linux or MacOS
ifeq ($(OS),)
SHELL := /bin/bash
MAKE ?= make

checktool = $(shell command -v $1 2>/dev/null)
tool = $(if $(call checktool, $(firstword $1)), $1, @echo "$(firstword $1) was not found on the system. Please install it")

GO ?= $(call checktool, go)
GOTEST ?= $(GO) test
GOTEST_ARGS ?= -timeout 2m -count 1 -cover

GO_TOOLS= $(GO) run -modfile ./tools/go.mod

.PHONY: test
test:
@$(GO) clean -testcache
$(GOTEST) $(GOTEST_ARGS) ./... -v

.PHONY: lint
lint:
@$(GO_TOOLS) github.com/golangci/golangci-lint/cmd/golangci-lint run -c .golangci.yml

.PHONY: lint-fix
lint-fix:
@$(GO_TOOLS) github.com/golangci/golangci-lint/cmd/golangci-lint run -c .golangci.yml --fix
@$(GO_TOOLS) github.com/golangci/golangci-lint/cmd/golangci-lint run -c .golangci.yml --fix
endif
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ This service uses go modules to provide dependency management, see `go.mod`.
If you place this repository OUTSIDE of your gopath, `go build cmd/main.go` and
`go test ./...` will download all required dependencies by default.

Go 1.14 or later is required.
Go 1.20 or later is required.
2 changes: 1 addition & 1 deletion api/openapi-spec/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
openapi: 3.0.3
openapi: 3.1.0
info:
title: Room Service
description: |-
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -26,5 +27,4 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
115 changes: 115 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package config

import (
"errors"
"io"

"gopkg.in/yaml.v3"
)

var appConfig *Application

type (
DatabaseType string
LogStyle string
)

const (
Mysql DatabaseType = "mysql"
Inmemory DatabaseType = "inmemory"

Plain LogStyle = "plain"
ECS LogStyle = "ecs" // default
)

type (
// Application is the root configuration type
// that holds all other subconfiguration types
Application struct {
Service ServiceConfig `yaml:"service"`
Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"`
Security SecurityConfig `yaml:"security"`
Logging LoggingConfig `yaml:"logging"`
}

// ServiceConfig contains configuration values
// for service related tasks. E.g. URL to payment provider adapter
ServiceConfig struct{}

// ServerConfig contains all values for
// http releated configuration
ServerConfig struct {
BaseAddress string `yaml:"address"`
Port int `yaml:"port"`
ReadTimeout int `yaml:"read_timeout_seconds"`
WriteTimeout int `yaml:"write_timeout_seconds"`
IdleTimeout int `yaml:"idle_timeout_seconds"`
}

// DatabaseConfig configures which db to use (mysql, inmemory)
// and how to connect to it (needed for mysql only)
DatabaseConfig struct {
Use DatabaseType `yaml:"use"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Database string `yaml:"database"`
Parameters []string `yaml:"parameters"`
}

// SecurityConfig configures everything related to security
SecurityConfig struct {
Fixed FixedTokenConfig `yaml:"fixed_token"`
Oidc OpenIdConnectConfig `yaml:"oidc"`
Cors CorsConfig `yaml:"cors"`
RequireLogin bool `yaml:"require_login_for_reg"`
}

FixedTokenConfig struct {
Api string `yaml:"api"` // shared-secret for server-to-server backend authentication
}

OpenIdConnectConfig struct {
IdTokenCookieName string `yaml:"id_token_cookie_name"` // optional, but must both be set, then tokens are read from cookies
AccessTokenCookieName string `yaml:"access_token_cookie_name"` // optional, but must both be set, then tokens are read from cookies
TokenPublicKeysPEM []string `yaml:"token_public_keys_PEM"` // a list of public RSA keys in PEM format, see https://github.com/Jumpy-Squirrel/jwks2pem for obtaining PEM from openid keyset endpoint
AdminGroup string `yaml:"admin_group"` // the group claim that supplies admin rights
AuthService string `yaml:"auth_service"` // base url, usually http://localhost:nnnn, will skip userinfo checks if unset
Audience string `yaml:"audience"`
Issuer string `yaml:"issuer"`
}

CorsConfig struct {
DisableCors bool `yaml:"disable"`
AllowOrigin string `yaml:"allow_origin"`
}

// LoggingConfig configures logging
LoggingConfig struct {
Style LogStyle `yaml:"style"`
Severity string `yaml:"severity"`
}
)

// UnmarshalFromYamlConfiguration decodes yaml data from an `io.Reader` interface.
func UnmarshalFromYamlConfiguration(file io.Reader) (*Application, error) {
d := yaml.NewDecoder(file)
d.KnownFields(true) // strict

var conf Application

if err := d.Decode(&conf); err != nil {
return nil, err
}

appConfig = &conf
return &conf, nil
}

func GetApplicationConfig() (*Application, error) {
if appConfig == nil {
return nil, errors.New("config was not yet loaded")
}

return appConfig, nil
}
9 changes: 9 additions & 0 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package controller

// Controller is the service interface, which defines
// the functions in the service layer of this application
//
// A type implementing this interface provides functionality
// to interact between the web layer and the data layer
type Controller interface {
}
42 changes: 21 additions & 21 deletions internal/apierrors/apierror.go → internal/errors/errors.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package apierrors
package errors

import (
"errors"
Expand All @@ -8,16 +8,16 @@ import (

var _ error = (*StatusError)(nil)

type KnownReason int
type KnownReason string

const (
KnownReasonBadRequest KnownReason = iota
KnownReasonUnauthorized
KnownReasonForbidden
KnownReasonNotFound
KnownReasonConflict
KnownReasonInternalServerError
KnownReasonUnknown
KnownReasonBadRequest KnownReason = "BadRequest"
KnownReasonUnauthorized KnownReason = "Unauthorized"
KnownReasonForbidden KnownReason = "Forbidden"
KnownReasonNotFound KnownReason = "NotFound"
KnownReasonConflict KnownReason = "Conflict"
KnownReasonInternalServerError KnownReason = "InternalServerError"
KnownReasonUnknown KnownReason = "Unknown"
)

type Status struct {
Expand All @@ -44,72 +44,72 @@ func (se *StatusError) Status() Status {
}

// NewBadRequest creates a new StatusError with error code 400
func NewBadRequest(details string) *StatusError {
func NewBadRequest(message, details string) APIStatus {
return &StatusError{
ErrStatus: Status{
Reason: KnownReasonBadRequest,
Code: http.StatusBadRequest,
Message: "Status Error (Bad Request)",
Message: message,
Details: details,
},
}
}

// NewUnauthorized creates a new StatusError with error code 401
func NewUnauthorized(details string) *StatusError {
func NewUnauthorized(message, details string) APIStatus {
return &StatusError{
ErrStatus: Status{
Reason: KnownReasonUnauthorized,
Code: http.StatusUnauthorized,
Message: "Status Error (Unauthorized)",
Message: message,
Details: details,
},
}
}

// NewForbidden creates a new StatusError with error code 403
func NewForbidden(details string) *StatusError {
func NewForbidden(message, details string) APIStatus {
return &StatusError{
ErrStatus: Status{
Reason: KnownReasonForbidden,
Code: http.StatusForbidden,
Message: "Status Error (Forbidden)",
Message: message,
Details: details,
},
}
}

// NewNotFound creates a new StatusError with error code 404
func NewNotFound(details string) *StatusError {
func NewNotFound(message, details string) APIStatus {
return &StatusError{
ErrStatus: Status{
Reason: KnownReasonNotFound,
Code: http.StatusNotFound,
Message: "Status Error (Not Found)",
Message: message,
Details: details,
},
}
}

// NewConflict creates a new StatusError with error code 409
func NewConflict(details string) *StatusError {
func NewConflict(message, details string) APIStatus {
return &StatusError{
ErrStatus: Status{
Reason: KnownReasonConflict,
Code: http.StatusConflict,
Message: "Status Error (Conflict)",
Message: message,
Details: details,
},
}
}

// NewInternalServerError creates a new StatusError with error code 500
func NewInternalServerError(details string) *StatusError {
func NewInternalServerError(message, details string) APIStatus {
return &StatusError{
ErrStatus: Status{
Reason: KnownReasonInternalServerError,
Code: http.StatusInternalServerError,
Message: "Status Error (Internal Server Error)",
Message: message,
Details: details,
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package apierrors
package errors

// TODO Write tests for apierror package
Loading

0 comments on commit 9ff4282

Please sign in to comment.