Skip to content

Commit

Permalink
Init project
Browse files Browse the repository at this point in the history
  • Loading branch information
haimkastner committed Feb 6, 2025
1 parent 168bcda commit ec64176
Show file tree
Hide file tree
Showing 9 changed files with 419 additions and 1 deletion.
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Created by: haimkastner
# Created at: 2025-02-06 21:23:13 UTC

name: CI

on:
push

jobs:
test:
name: Test
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
cache: true

- name: Install dependencies
run: |
go mod download
- name: Run tests
run: go test ./...
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"Gleece"
]
}
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# gleece-runtime
# Gleece Runtime

Core runtime package for the [Gleece](https://github.com/gopher-fleece/gleece) tool.

## Installation

```bash
go get github.com/gopher-fleece/gleece-runtime
```

This package is required dependency for projects using Gleece-generated routes for routing.

The package contains all runtime dependencies needed by the generated code and controllers.

To generate routes & specs, use the [Gleece](https://github.com/gopher-fleece/gleece) CLI directly.
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/gopher-fleece/gleece-runtime

go 1.23.6

require github.com/go-playground/validator/v10 v10.24.0

require (
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
28 changes: 28 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
155 changes: 155 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package runtime

type HttpStatusCode uint

// Taken from net/http
// Prefer to have this typed in-house as an 'enum'
const (
StatusContinue HttpStatusCode = 100 // RFC 9110, 15.2.1
StatusSwitchingProtocols HttpStatusCode = 101 // RFC 9110, 15.2.2
StatusProcessing HttpStatusCode = 102 // RFC 2518, 10.1
StatusEarlyHints HttpStatusCode = 103 // RFC 8297

StatusOK HttpStatusCode = 200 // RFC 9110, 15.3.1
StatusCreated HttpStatusCode = 201 // RFC 9110, 15.3.2
StatusAccepted HttpStatusCode = 202 // RFC 9110, 15.3.3
StatusNonAuthoritativeInfo HttpStatusCode = 203 // RFC 9110, 15.3.4
StatusNoContent HttpStatusCode = 204 // RFC 9110, 15.3.5
StatusResetContent HttpStatusCode = 205 // RFC 9110, 15.3.6
StatusPartialContent HttpStatusCode = 206 // RFC 9110, 15.3.7
StatusMultiStatus HttpStatusCode = 207 // RFC 4918, 11.1
StatusAlreadyReported HttpStatusCode = 208 // RFC 5842, 7.1
StatusIMUsed HttpStatusCode = 226 // RFC 3229, 10.4.1

StatusMultipleChoices HttpStatusCode = 300 // RFC 9110, 15.4.1
StatusMovedPermanently HttpStatusCode = 301 // RFC 9110, 15.4.2
StatusFound HttpStatusCode = 302 // RFC 9110, 15.4.3
StatusSeeOther HttpStatusCode = 303 // RFC 9110, 15.4.4
StatusNotModified HttpStatusCode = 304 // RFC 9110, 15.4.5
StatusUseProxy HttpStatusCode = 305 // RFC 9110, 15.4.6

StatusTemporaryRedirect HttpStatusCode = 307 // RFC 9110, 15.4.8
StatusPermanentRedirect HttpStatusCode = 308 // RFC 9110, 15.4.9

StatusBadRequest HttpStatusCode = 400 // RFC 9110, 15.5.1
StatusUnauthorized HttpStatusCode = 401 // RFC 9110, 15.5.2
StatusPaymentRequired HttpStatusCode = 402 // RFC 9110, 15.5.3
StatusForbidden HttpStatusCode = 403 // RFC 9110, 15.5.4
StatusNotFound HttpStatusCode = 404 // RFC 9110, 15.5.5
StatusMethodNotAllowed HttpStatusCode = 405 // RFC 9110, 15.5.6
StatusNotAcceptable HttpStatusCode = 406 // RFC 9110, 15.5.7
StatusProxyAuthRequired HttpStatusCode = 407 // RFC 9110, 15.5.8
StatusRequestTimeout HttpStatusCode = 408 // RFC 9110, 15.5.9
StatusConflict HttpStatusCode = 409 // RFC 9110, 15.5.10
StatusGone HttpStatusCode = 410 // RFC 9110, 15.5.11
StatusLengthRequired HttpStatusCode = 411 // RFC 9110, 15.5.12
StatusPreconditionFailed HttpStatusCode = 412 // RFC 9110, 15.5.13
StatusRequestEntityTooLarge HttpStatusCode = 413 // RFC 9110, 15.5.14
StatusRequestURITooLong HttpStatusCode = 414 // RFC 9110, 15.5.15
StatusUnsupportedMediaType HttpStatusCode = 415 // RFC 9110, 15.5.16
StatusRequestedRangeNotSatisfiable HttpStatusCode = 416 // RFC 9110, 15.5.17
StatusExpectationFailed HttpStatusCode = 417 // RFC 9110, 15.5.18
StatusTeapot HttpStatusCode = 418 // RFC 9110, 15.5.19 (Unused)
StatusMisdirectedRequest HttpStatusCode = 421 // RFC 9110, 15.5.20
StatusUnprocessableEntity HttpStatusCode = 422 // RFC 9110, 15.5.21
StatusLocked HttpStatusCode = 423 // RFC 4918, 11.3
StatusFailedDependency HttpStatusCode = 424 // RFC 4918, 11.4
StatusTooEarly HttpStatusCode = 425 // RFC 8470, 5.2.
StatusUpgradeRequired HttpStatusCode = 426 // RFC 9110, 15.5.22
StatusPreconditionRequired HttpStatusCode = 428 // RFC 6585, 3
StatusTooManyRequests HttpStatusCode = 429 // RFC 6585, 4
StatusRequestHeaderFieldsTooLarge HttpStatusCode = 431 // RFC 6585, 5
StatusUnavailableForLegalReasons HttpStatusCode = 451 // RFC 7725, 3

StatusInternalServerError HttpStatusCode = 500 // RFC 9110, 15.6.1
StatusNotImplemented HttpStatusCode = 501 // RFC 9110, 15.6.2
StatusBadGateway HttpStatusCode = 502 // RFC 9110, 15.6.3
StatusServiceUnavailable HttpStatusCode = 503 // RFC 9110, 15.6.4
StatusGatewayTimeout HttpStatusCode = 504 // RFC 9110, 15.6.5
StatusHTTPVersionNotSupported HttpStatusCode = 505 // RFC 9110, 15.6.6
StatusVariantAlsoNegotiates HttpStatusCode = 506 // RFC 2295, 8.1
StatusInsufficientStorage HttpStatusCode = 507 // RFC 4918, 11.5
StatusLoopDetected HttpStatusCode = 508 // RFC 5842, 7.2
StatusNotExtended HttpStatusCode = 510 // RFC 2774, 7
StatusNetworkAuthenticationRequired HttpStatusCode = 511 // RFC 6585, 6
)

type Rfc7807Error struct {
Type string `json:"type"`
Title string `json:"title"`
Detail string `json:"detail"`
Status int `json:"status"`
Instance string `json:"instance"`
Extensions map[string]string `json:"extensions"`
}

// GleeceController provides common functionality for controllers.
type GleeceController struct {
statusCode *HttpStatusCode
headers map[string]string
request any // Request is the HTTP request from the underlying routing engine (gin, echo etc.)
}

func (gc *GleeceController) InitController(request any) {
gc.request = request
gc.headers = make(map[string]string)
}

// SetStatus sets the status code for the GleeceController.
func (gc *GleeceController) SetStatus(statusCode HttpStatusCode) {
gc.statusCode = &statusCode
}

// GetStatus gets the status code for the GleeceController.
func (gc *GleeceController) GetStatus() *HttpStatusCode {
return gc.statusCode
}

// SetHeader sets a header for the GleeceController.
func (gc *GleeceController) SetHeader(name string, value string) {
gc.headers[name] = value
}

// GetHeaders get headers set (defined using the `SetHeader` API).
func (gc *GleeceController) GetHeaders() map[string]string {
return gc.headers
}

// GetContext returns the underlying request object (the type of the object is specific to the underlying routing engine).
func (gc *GleeceController) GetContext() any {
return gc.request
}

type Controller interface {
InitController(request any)

// SetStatus sets the status code for the GleeceController.
SetStatus(statusCode HttpStatusCode)

// GetStatus gets the status code for the GleeceController.
GetStatus() *HttpStatusCode

// SetHeader sets a header for the GleeceController.
SetHeader(name string, value string)

// GetHeaders get headers set (defined using the `SetHeader` API).
GetHeaders() map[string]string

// GetContext returns the underlying request object (the type of the object is specific to the underlying routing engine).
GetContext() any
}

type SecurityCheck struct {
SchemaName string `json:"name" validate:"required,starts_with_letter"`
Scopes []string `json:"scopes" validate:"not_nil_array"`
}

type CustomError struct {
Payload any
}

type SecurityError struct {
Message string
StatusCode HttpStatusCode
CustomError *CustomError
}
29 changes: 29 additions & 0 deletions validation.logic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package runtime

import (
"fmt"

validator "github.com/go-playground/validator/v10"
)

func ExtractValidationErrorMessage(err error, fieldName *string) string {
if err == nil {
return ""
}

validationErrors, ok := err.(validator.ValidationErrors)
if !ok {
return err.Error()
}

var errStr string
for _, validationErr := range validationErrors {
fName := validationErr.Field()
if fieldName != nil {
fName = *fieldName
}
errStr += fmt.Sprintf("Field '%s' failed validation with tag '%s'. ", fName, validationErr.Tag())
}

return errStr
}
76 changes: 76 additions & 0 deletions validation.logic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package runtime

import (
"errors"
"testing"

"github.com/go-playground/validator/v10"
)

type TestStruct struct {
Name string `validate:"required"`
Email string `validate:"required,email"`
Age int `validate:"required,gte=0,lte=130"`
}

func TestExtractValidationErrorMessage(t *testing.T) {
validate := validator.New()

tests := []struct {
name string
input error
fieldName *string
want string
}{
{
name: "nil error",
input: nil,
fieldName: nil,
want: "",
},
{
name: "non-validation error",
input: errors.New("regular error"),
fieldName: nil,
want: "regular error",
},
{
name: "single validation error - required field",
input: validate.Struct(TestStruct{}),
fieldName: nil,
want: "Field 'Name' failed validation with tag 'required'. Field 'Email' failed validation with tag 'required'. Field 'Age' failed validation with tag 'required'. ",
},
{
name: "invalid email validation error",
input: validate.Struct(TestStruct{Name: "John", Email: "invalid-email", Age: 25}),
fieldName: nil,
want: "Field 'Email' failed validation with tag 'email'. ",
},
{
name: "validation error with custom field name",
input: validate.Struct(TestStruct{Name: "John", Email: "invalid-email", Age: 25}),
fieldName: strPtr("CustomField"),
want: "Field 'CustomField' failed validation with tag 'email'. ",
},
{
name: "age out of range validation",
input: validate.Struct(TestStruct{Name: "John", Email: "[email protected]", Age: 150}),
fieldName: nil,
want: "Field 'Age' failed validation with tag 'lte'. ",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ExtractValidationErrorMessage(tt.input, tt.fieldName)
if got != tt.want {
t.Errorf("ExtractValidationErrorMessage() = %v, want %v", got, tt.want)
}
})
}
}

// strPtr returns a pointer to the given string
func strPtr(s string) *string {
return &s
}
Loading

0 comments on commit ec64176

Please sign in to comment.