forked from goffee/core
initial commits 2
This commit is contained in:
parent
5475b7dd26
commit
7f38826b9c
39 changed files with 4525 additions and 0 deletions
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2021 Harran Ali <harran.m@gmail.com>
|
||||||
|
Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
73
cache.go
Normal file
73
cache.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
redis *redis.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCache(cacheConfig CacheConfig) *Cache {
|
||||||
|
ctx = context.Background()
|
||||||
|
dbStr := os.Getenv("REDIS_DB")
|
||||||
|
db64, err := strconv.ParseInt(dbStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error parsing redis db env var: %v", err))
|
||||||
|
}
|
||||||
|
db := int(db64)
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%v:%v", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")),
|
||||||
|
Password: os.Getenv("REDIS_PASSWORD"), // no password set
|
||||||
|
DB: db, // use default DB
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err = rdb.Ping(ctx).Result()
|
||||||
|
if cacheConfig.EnableCache && err != nil {
|
||||||
|
panic(fmt.Sprintf("problem connecting to redis cache, (if it's not needed you can disable it in config/cache.go): %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Cache{
|
||||||
|
redis: rdb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Set(key string, value string) error {
|
||||||
|
err := c.redis.Set(ctx, key, value, 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) SetWithExpiration(key string, value string, expiration time.Duration) error {
|
||||||
|
err := c.redis.Set(ctx, key, value, expiration).Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Get(key string) (string, error) {
|
||||||
|
result, err := c.redis.Get(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Delete(key string) error {
|
||||||
|
err := c.redis.Del(ctx, key).Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
27
config.go
Normal file
27
config.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
type EnvFileConfig struct {
|
||||||
|
UseDotEnvFile bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestConfig struct {
|
||||||
|
MaxUploadFileSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTConfig struct {
|
||||||
|
SecretKey string
|
||||||
|
Lifetime int
|
||||||
|
}
|
||||||
|
|
||||||
|
type GormConfig struct {
|
||||||
|
EnableGorm bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CacheConfig struct {
|
||||||
|
EnableCache bool
|
||||||
|
}
|
17
consts.go
Normal file
17
consts.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
const GET string = "get"
|
||||||
|
const POST string = "post"
|
||||||
|
const DELETE string = "delete"
|
||||||
|
const PATCH string = "patch"
|
||||||
|
const PUT string = "put"
|
||||||
|
const OPTIONS string = "options"
|
||||||
|
const HEAD string = "head"
|
||||||
|
const CONTENT_TYPE string = "content-Type"
|
||||||
|
const CONTENT_TYPE_HTML string = "text/html; charset=utf-8"
|
||||||
|
const CONTENT_TYPE_JSON string = "application/json"
|
||||||
|
const CONTENT_TYPE_TEXT string = "text/plain"
|
||||||
|
const CONTENT_TYPE_MULTIPART_FORM_DATA string = "multipart/form-data;"
|
||||||
|
const LOCALHOST string = "http://localhost"
|
||||||
|
const TEST_STR string = "Testing!"
|
||||||
|
const PRODUCTION string = "production"
|
334
context.go
Normal file
334
context.go
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.smarteching.com/goffee/core/logger"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
Request *Request
|
||||||
|
Response *Response
|
||||||
|
GetValidator func() *Validator
|
||||||
|
GetJWT func() *JWT
|
||||||
|
GetGorm func() *gorm.DB
|
||||||
|
GetCache func() *Cache
|
||||||
|
GetHashing func() *Hashing
|
||||||
|
GetMailer func() *Mailer
|
||||||
|
GetEventsManager func() *EventsManager
|
||||||
|
GetLogger func() *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO enhance
|
||||||
|
func (c *Context) DebugAny(variable interface{}) {
|
||||||
|
var formatted string
|
||||||
|
m := reflect.ValueOf(variable)
|
||||||
|
if m.Kind() == reflect.Pointer {
|
||||||
|
formatted = fmt.Sprintf("\n\nType: [pointer] %v (%v) \nMemory Address: %v \nValue: %v\n\n", m.Type(), m.Elem().Kind(), m, m.Elem())
|
||||||
|
} else {
|
||||||
|
formatted = fmt.Sprintf("\n\nType: %v (%v) \nValue: %v\n\n", m.Type(), m.Kind(), variable)
|
||||||
|
}
|
||||||
|
fmt.Println(formatted)
|
||||||
|
c.Response.HttpResponseWriter.Write([]byte(formatted))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Next() {
|
||||||
|
ResolveApp().Next(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) prepare(ctx *Context) {
|
||||||
|
ctx.Request.httpRequest.ParseMultipartForm(int64(app.Config.Request.MaxUploadFileSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetPathParam(key string) interface{} {
|
||||||
|
return c.Request.httpPathParams.ByName(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetRequestParam(key string) interface{} {
|
||||||
|
return c.Request.httpRequest.FormValue(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) RequestParamExists(key string) bool {
|
||||||
|
return c.Request.httpRequest.Form.Has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetHeader(key string) string {
|
||||||
|
return c.Request.httpRequest.Header.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetUploadedFile(name string) *UploadedFileInfo {
|
||||||
|
file, fileHeader, err := c.Request.httpRequest.FormFile(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error with file,[%v]", err.Error()))
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
ext := strings.TrimPrefix(path.Ext(fileHeader.Filename), ".")
|
||||||
|
tmpFilePath := filepath.Join(os.TempDir(), fileHeader.Filename)
|
||||||
|
tmpFile, err := os.Create(tmpFilePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error with file,[%v]", err.Error()))
|
||||||
|
}
|
||||||
|
buff := make([]byte, 100)
|
||||||
|
for {
|
||||||
|
n, err := file.Read(buff)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic("error with uploaded file")
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n, _ = tmpFile.Write(buff[:n])
|
||||||
|
}
|
||||||
|
tmpFileInfo, err := os.Stat(tmpFilePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error with file,[%v]", err.Error()))
|
||||||
|
}
|
||||||
|
defer tmpFile.Close()
|
||||||
|
uploadedFileInfo := &UploadedFileInfo{
|
||||||
|
FullPath: tmpFilePath,
|
||||||
|
Name: fileHeader.Filename,
|
||||||
|
NameWithoutExtension: strings.TrimSuffix(fileHeader.Filename, path.Ext(fileHeader.Filename)),
|
||||||
|
Extension: ext,
|
||||||
|
Size: int(tmpFileInfo.Size()),
|
||||||
|
}
|
||||||
|
return uploadedFileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) MoveFile(sourceFilePath string, destFolderPath string) error {
|
||||||
|
o := syscall.Umask(0)
|
||||||
|
defer syscall.Umask(o)
|
||||||
|
newFileName := filepath.Base(sourceFilePath)
|
||||||
|
os.MkdirAll(destFolderPath, 766)
|
||||||
|
srcFileInfo, err := os.Stat(sourceFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !srcFileInfo.Mode().IsRegular() {
|
||||||
|
return errors.New("can not move file, not in a regular mode")
|
||||||
|
}
|
||||||
|
srcFile, err := os.Open(sourceFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
destFilePath := filepath.Join(destFolderPath, newFileName)
|
||||||
|
destFile, err := os.Create(destFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buff := make([]byte, 1024*8)
|
||||||
|
for {
|
||||||
|
n, err := srcFile.Read(buff)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic(fmt.Sprintf("error moving file %v", sourceFilePath))
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err = destFile.Write(buff[:n])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destFile.Close()
|
||||||
|
err = os.Remove(sourceFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) CopyFile(sourceFilePath string, destFolderPath string) error {
|
||||||
|
o := syscall.Umask(0)
|
||||||
|
defer syscall.Umask(o)
|
||||||
|
newFileName := filepath.Base(sourceFilePath)
|
||||||
|
os.MkdirAll(destFolderPath, 766)
|
||||||
|
srcFileInfo, err := os.Stat(sourceFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !srcFileInfo.Mode().IsRegular() {
|
||||||
|
return errors.New("can not move file, not in a regular mode")
|
||||||
|
}
|
||||||
|
srcFile, err := os.Open(sourceFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
destFilePath := filepath.Join(destFolderPath, newFileName)
|
||||||
|
destFile, err := os.Create(destFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buff := make([]byte, 1024*8)
|
||||||
|
for {
|
||||||
|
n, err := srcFile.Read(buff)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic(fmt.Sprintf("error moving file %v", sourceFilePath))
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err = destFile.Write(buff[:n])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destFile.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) MapToJson(v any) string {
|
||||||
|
r := reflect.ValueOf(v)
|
||||||
|
if r.Kind() != reflect.Map {
|
||||||
|
panic("parameter is not a map")
|
||||||
|
}
|
||||||
|
j, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return string(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadedFileInfo struct {
|
||||||
|
FullPath string
|
||||||
|
Name string
|
||||||
|
NameWithoutExtension string
|
||||||
|
Extension string
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetBaseDirPath() string {
|
||||||
|
return basePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) CastToString(value interface{}) string {
|
||||||
|
if !basicType(value) {
|
||||||
|
panic("can not cast to string")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Context) GetUserAgent() string {
|
||||||
|
return c.Request.httpRequest.UserAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) CastToInt(value interface{}) int {
|
||||||
|
var i int
|
||||||
|
if !basicType(value) {
|
||||||
|
panic("can not cast to int")
|
||||||
|
}
|
||||||
|
i, ok := value.(int)
|
||||||
|
if ok {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
_i, ok := value.(int32)
|
||||||
|
if ok {
|
||||||
|
i := int(_i)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
_ii, ok := value.(int64)
|
||||||
|
if ok {
|
||||||
|
i := int(_ii)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
f, ok := value.(float32)
|
||||||
|
if ok {
|
||||||
|
i := int(f)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
ff, ok := value.(float64)
|
||||||
|
if ok {
|
||||||
|
i := int(ff)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
s, ok := value.(string)
|
||||||
|
if ok {
|
||||||
|
fff, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic("error casting to int")
|
||||||
|
}
|
||||||
|
i = int(fff)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
panic("error casting to int")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) CastToFloat(value interface{}) float64 {
|
||||||
|
if !basicType(value) {
|
||||||
|
panic("can not cast to float")
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.Pointer {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
var str string
|
||||||
|
var ok bool
|
||||||
|
if v.Kind() == reflect.Float64 {
|
||||||
|
f, ok := value.(float64)
|
||||||
|
if !ok {
|
||||||
|
panic("error casting to float")
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Float32 {
|
||||||
|
s := fmt.Sprintf("%v", value)
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic("error casting to float")
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.String {
|
||||||
|
str, ok = value.(string)
|
||||||
|
if !ok {
|
||||||
|
panic("error casting to float")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.CanInt() {
|
||||||
|
i, ok := value.(int)
|
||||||
|
if !ok {
|
||||||
|
panic("error casting to float")
|
||||||
|
}
|
||||||
|
str = fmt.Sprintf("%v.0", i)
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic("error casting to float")
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicType(value interface{}) bool {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.Pointer {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if !(v.Kind() == reflect.Array ||
|
||||||
|
v.Kind() == reflect.Slice ||
|
||||||
|
v.Kind() == reflect.Map ||
|
||||||
|
v.Kind() == reflect.Struct ||
|
||||||
|
v.Kind() == reflect.Interface ||
|
||||||
|
v.Kind() == reflect.Func) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
555
context_test.go
Normal file
555
context_test.go
Normal file
|
@ -0,0 +1,555 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.smarteching.com/goffee/core/logger"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDebugAny(t *testing.T) {
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c := &Context{
|
||||||
|
Request: &Request{
|
||||||
|
httpRequest: r,
|
||||||
|
httpPathParams: nil,
|
||||||
|
},
|
||||||
|
Response: &Response{
|
||||||
|
headers: []header{},
|
||||||
|
body: nil,
|
||||||
|
HttpResponseWriter: w,
|
||||||
|
},
|
||||||
|
GetValidator: nil,
|
||||||
|
GetJWT: nil,
|
||||||
|
}
|
||||||
|
h := func(c *Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var msg interface{}
|
||||||
|
msg = "test-debug-pointer"
|
||||||
|
c.DebugAny(&msg)
|
||||||
|
c.DebugAny("test-debug-msg")
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
h(w, r)
|
||||||
|
b, err := io.ReadAll(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing debug any")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(b), "test-debug-msg") {
|
||||||
|
t.Errorf("failed testing debug any")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(b), "test-debug-pointer") {
|
||||||
|
t.Errorf("failed testing debug any")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogInfo(t *testing.T) {
|
||||||
|
tmpF := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
msg := "test-log-info"
|
||||||
|
c := makeCTXLogTestCTX(t, w, r, tmpF)
|
||||||
|
h := func(c *Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.GetLogger().Info(msg)
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
h(w, r)
|
||||||
|
fc, err := os.ReadFile(tmpF)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing log info")
|
||||||
|
}
|
||||||
|
if !(strings.Contains(string(fc), msg) || strings.Contains(string(fc), "info:")) {
|
||||||
|
t.Errorf("failed testing log info")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogWarning(t *testing.T) {
|
||||||
|
tmpF := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
msg := "test-log-warning"
|
||||||
|
c := makeCTXLogTestCTX(t, w, r, tmpF)
|
||||||
|
h := func(c *Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.GetLogger().Warning(msg)
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
h(w, r)
|
||||||
|
fc, err := os.ReadFile(tmpF)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing log warning")
|
||||||
|
}
|
||||||
|
if !(strings.Contains(string(fc), msg) || strings.Contains(string(fc), "warning:")) {
|
||||||
|
t.Errorf("failed testing log warning")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogDebug(t *testing.T) {
|
||||||
|
tmpF := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
msg := "test-log-debug"
|
||||||
|
c := makeCTXLogTestCTX(t, w, r, tmpF)
|
||||||
|
h := func(c *Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.GetLogger().Debug(msg)
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
h(w, r)
|
||||||
|
fc, err := os.ReadFile(tmpF)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing log debug")
|
||||||
|
}
|
||||||
|
if !(strings.Contains(string(fc), msg) || strings.Contains(string(fc), "debug:")) {
|
||||||
|
t.Errorf("failed testing log debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogError(t *testing.T) {
|
||||||
|
tmpF := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
msg := "test-log-error"
|
||||||
|
c := makeCTXLogTestCTX(t, w, r, tmpF)
|
||||||
|
h := func(c *Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c.GetLogger().Error(msg)
|
||||||
|
}
|
||||||
|
}(c)
|
||||||
|
h(w, r)
|
||||||
|
fc, err := os.ReadFile(tmpF)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing log error")
|
||||||
|
}
|
||||||
|
if !(strings.Contains(string(fc), msg) || strings.Contains(string(fc), "error:")) {
|
||||||
|
t.Errorf("failed testing log error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPathParams(t *testing.T) {
|
||||||
|
NewEventsManager() //TODO removing require refactoring makeHTTPRouterHandlerFunc()
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
pathParams := httprouter.Params{
|
||||||
|
{
|
||||||
|
Key: "param1",
|
||||||
|
Value: "param1val",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "param2",
|
||||||
|
Value: "param2val",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a := New()
|
||||||
|
h := a.makeHTTPRouterHandlerFunc(
|
||||||
|
Handler(func(c *Context) *Response {
|
||||||
|
rsp := fmt.Sprintf("param1: %v | param2: %v", c.GetPathParam("param1"), c.GetPathParam("param2"))
|
||||||
|
return c.Response.Text(rsp)
|
||||||
|
}), nil)
|
||||||
|
h(w, r, pathParams)
|
||||||
|
b, err := io.ReadAll(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("failed testing get path params")
|
||||||
|
}
|
||||||
|
bStr := string(b)
|
||||||
|
if !(strings.Contains(bStr, "param1val") || strings.Contains(bStr, "param2val")) {
|
||||||
|
t.Errorf("failed testing get path params")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRequestParams(t *testing.T) {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
app := New()
|
||||||
|
app.SetBasePath(pwd)
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Post("/pt", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Get("/gt", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
client := s.Client()
|
||||||
|
defer s.Close()
|
||||||
|
rsp, err := client.PostForm(s.URL+"/pt", url.Values{"param": {"paramValPost"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get request params: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "paramValPost" {
|
||||||
|
t.Errorf("failed test get request params")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
rsp, err = http.Get(s.URL + "/gt?param=paramValGet")
|
||||||
|
b, err = io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get request params")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "paramValGet" {
|
||||||
|
t.Errorf("failed test get request param")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestParamsExists(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Post("/pt", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.RequestParamExists("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Get("/gt", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.RequestParamExists("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
rsp, err := http.PostForm(s.URL+"/pt", url.Values{"param": {"paramValPost"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "true" {
|
||||||
|
t.Errorf("failed test get request params")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
|
||||||
|
rsp, err = http.Get(s.URL + "/gt?param=paramValGet")
|
||||||
|
b, err = io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get request params")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "true" {
|
||||||
|
t.Errorf("failed test get request param")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHeader(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Post("/pt", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetHeader("headerkey"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Get("/gt", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetHeader("headerkey"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", s.URL+"/pt", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get header")
|
||||||
|
}
|
||||||
|
req.Header.Add("headerkey", "headerPostVal")
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "headerPostVal" {
|
||||||
|
t.Errorf("failed test get request params")
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest("GET", s.URL+"/gt", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get header")
|
||||||
|
}
|
||||||
|
req.Header.Add("headerkey", "headerGetVal")
|
||||||
|
rsp, err = clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
b, err = io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get request params")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "headerGetVal" {
|
||||||
|
t.Errorf("failed test get request params")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUploadedFile(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Post("/pt", Handler(func(c *Context) *Response {
|
||||||
|
uploadedFile := c.GetUploadedFile("myfile")
|
||||||
|
rs := fmt.Sprintf("file name: %v | size: %v", uploadedFile.Name, uploadedFile.Size)
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, rs)
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
tfp := filepath.Join(wd, "testingdata/testdata.json")
|
||||||
|
file, err := os.Open(tfp)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed test get upload file")
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("myfile", filepath.Base(file.Name()))
|
||||||
|
io.Copy(part, file)
|
||||||
|
writer.Close()
|
||||||
|
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", s.URL+"/pt", body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get uploaded file")
|
||||||
|
}
|
||||||
|
req.Header.Add(CONTENT_TYPE, writer.FormDataContentType())
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get get uploaded file")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get get uploaded file")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
asrtFile, err := os.Stat(tfp)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("failed test get get uploaded file")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(b), fmt.Sprintf("size: %v", asrtFile.Size())) {
|
||||||
|
t.Errorf("failed test get uploaded file")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(b), "testdata.json") {
|
||||||
|
t.Errorf("failed test get uploaded file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoveFile(t *testing.T) {
|
||||||
|
o := syscall.Umask(0)
|
||||||
|
defer syscall.Umask(o)
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
var tmpDir string
|
||||||
|
tmpDir = path.Join(pwd, "testingdata/tmp")
|
||||||
|
_, err := os.Stat(path.Join("./testingdata", "totestmovefile.md"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test move file: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c := makeCTX(t)
|
||||||
|
err = c.MoveFile("./testingdata/totestmovefile.md", tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test move file: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(path.Join(tmpDir, "totestmovefile.md"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test move file: %v", err.Error())
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
c.MoveFile(filepath.Join(tmpDir, "totestmovefile.md"), "./testingdata")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyFile(t *testing.T) {
|
||||||
|
o := syscall.Umask(0)
|
||||||
|
defer syscall.Umask(o)
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
var tmpDir string
|
||||||
|
tmpDir = path.Join(pwd, "testingdata/tmp")
|
||||||
|
_, err := os.Stat(path.Join("./testingdata", "totestcopyfile.md"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test move file: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c := makeCTX(t)
|
||||||
|
err = c.CopyFile("./testingdata/totestcopyfile.md", tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test move file: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(path.Join(tmpDir, "totestcopyfile.md"))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test move file: %v", err.Error())
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove(filepath.Join(tmpDir, "totestcopyfile.md"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCastToString(t *testing.T) {
|
||||||
|
c := makeCTX(t)
|
||||||
|
s := c.CastToString(25)
|
||||||
|
if fmt.Sprintf("%T", s) != "string" {
|
||||||
|
t.Errorf("failed test cast to string")
|
||||||
|
}
|
||||||
|
s = c.CastToString(25.54)
|
||||||
|
if fmt.Sprintf("%T", s) != "string" {
|
||||||
|
t.Errorf("failed test cast to string")
|
||||||
|
}
|
||||||
|
var v interface{} = "434"
|
||||||
|
s = c.CastToString(v)
|
||||||
|
if fmt.Sprintf("%T", s) != "string" {
|
||||||
|
t.Errorf("failed test cast to string")
|
||||||
|
}
|
||||||
|
var vs interface{} = "this is a string"
|
||||||
|
s = c.CastToString(vs)
|
||||||
|
if fmt.Sprintf("%T", s) != "string" {
|
||||||
|
t.Errorf("failed test cast to string")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCastToInt(t *testing.T) {
|
||||||
|
c := makeCTX(t)
|
||||||
|
i := c.CastToInt(4)
|
||||||
|
if !(i == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
ii := c.CastToInt(4.434)
|
||||||
|
if !(ii == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
iii := c.CastToInt("4")
|
||||||
|
if !(iii == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
iiii := c.CastToInt("4.434")
|
||||||
|
if !(iiii == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
var iInterface interface{}
|
||||||
|
iInterface = 4
|
||||||
|
i = c.CastToInt(iInterface)
|
||||||
|
if !(i == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
iInterface = 4.545
|
||||||
|
ii = c.CastToInt(iInterface)
|
||||||
|
if !(ii == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
iInterface = "4"
|
||||||
|
iii = c.CastToInt(iInterface)
|
||||||
|
if !(iii == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
iInterface = "4.434"
|
||||||
|
iiii = c.CastToInt(iInterface)
|
||||||
|
if !(iiii == 4 && fmt.Sprintf("%T", i) == "int") {
|
||||||
|
t.Errorf("failed test cast to int")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCastToFloat(t *testing.T) {
|
||||||
|
c := makeCTX(t)
|
||||||
|
f := c.CastToFloat(4)
|
||||||
|
if !(f == 4 && fmt.Sprintf("%T", f) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
var varf32 float32 = 4.434
|
||||||
|
ff32 := c.CastToFloat(varf32)
|
||||||
|
if !(ff32 == 4.434 && fmt.Sprintf("%T", ff32) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
var varf64 float64 = 4.434
|
||||||
|
ff64 := c.CastToFloat(varf64)
|
||||||
|
if !(ff64 == 4.434 && fmt.Sprintf("%T", ff64) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
|
||||||
|
fff := c.CastToFloat("4")
|
||||||
|
if !(fff == 4 && fmt.Sprintf("%T", fff) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
ffff := c.CastToFloat("4.434")
|
||||||
|
if !(ffff == 4.434 && fmt.Sprintf("%T", ffff) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
var iInterface interface{}
|
||||||
|
iInterface = 4
|
||||||
|
f = c.CastToFloat(iInterface)
|
||||||
|
if !(f == 4 && fmt.Sprintf("%T", f) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
iInterface = 4.434
|
||||||
|
iff := c.CastToFloat(iInterface)
|
||||||
|
if !(iff == 4.434 && fmt.Sprintf("%T", iff) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
iInterface = "4"
|
||||||
|
fff = c.CastToFloat(iInterface)
|
||||||
|
if !(fff == 4 && fmt.Sprintf("%T", fff) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
iInterface = "4.434"
|
||||||
|
ffff = c.CastToFloat(iInterface)
|
||||||
|
if !(ffff == 4.434 && fmt.Sprintf("%T", ffff) == "float64") {
|
||||||
|
t.Errorf("failed test cast to float")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBaseDirPath(t *testing.T) {
|
||||||
|
c := makeCTX(t)
|
||||||
|
p := c.GetBaseDirPath()
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test get base dir path")
|
||||||
|
}
|
||||||
|
if p != pwd {
|
||||||
|
t.Errorf("failed test get base dir path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCTXLogTestCTX(t *testing.T, w http.ResponseWriter, r *http.Request, tmpFilePath string) *Context {
|
||||||
|
t.Helper()
|
||||||
|
return &Context{
|
||||||
|
Request: &Request{
|
||||||
|
httpRequest: r,
|
||||||
|
httpPathParams: nil,
|
||||||
|
},
|
||||||
|
Response: &Response{
|
||||||
|
headers: []header{},
|
||||||
|
body: nil,
|
||||||
|
HttpResponseWriter: w,
|
||||||
|
},
|
||||||
|
GetValidator: nil,
|
||||||
|
GetJWT: nil,
|
||||||
|
GetLogger: func() *logger.Logger {
|
||||||
|
return logger.NewLogger(logger.LogFileDriver{FilePath: tmpFilePath})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
541
core.go
Normal file
541
core.go
Normal file
|
@ -0,0 +1,541 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.smarteching.com/goffee/core/env"
|
||||||
|
"git.smarteching.com/goffee/core/logger"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var loggr *logger.Logger
|
||||||
|
var logsDriver *logger.LogsDriver
|
||||||
|
var requestC RequestConfig
|
||||||
|
var jwtC JWTConfig
|
||||||
|
var gormC GormConfig
|
||||||
|
var cacheC CacheConfig
|
||||||
|
var db *gorm.DB
|
||||||
|
var mailer *Mailer
|
||||||
|
var basePath string
|
||||||
|
var disableEvents bool = false
|
||||||
|
|
||||||
|
type configContainer struct {
|
||||||
|
Request RequestConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
t int // for trancking middlewares
|
||||||
|
chain *chain
|
||||||
|
middlewares *Middlewares
|
||||||
|
Config *configContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
var app *App
|
||||||
|
|
||||||
|
func New() *App {
|
||||||
|
app = &App{
|
||||||
|
chain: &chain{},
|
||||||
|
middlewares: NewMiddlewares(),
|
||||||
|
Config: &configContainer{
|
||||||
|
Request: requestC,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveApp() *App {
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetLogsDriver(d logger.LogsDriver) {
|
||||||
|
logsDriver = &d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Bootstrap() {
|
||||||
|
loggr = logger.NewLogger(*logsDriver)
|
||||||
|
NewRouter()
|
||||||
|
NewEventsManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Run(router *httprouter.Router) {
|
||||||
|
portNumber := os.Getenv("App_HTTP_PORT")
|
||||||
|
if portNumber == "" {
|
||||||
|
portNumber = "80"
|
||||||
|
}
|
||||||
|
router = app.RegisterRoutes(ResolveRouter().GetRoutes(), router)
|
||||||
|
useHttpsStr := os.Getenv("App_USE_HTTPS")
|
||||||
|
if useHttpsStr == "" {
|
||||||
|
useHttpsStr = "false"
|
||||||
|
}
|
||||||
|
useHttps, _ := strconv.ParseBool(useHttpsStr)
|
||||||
|
|
||||||
|
fmt.Printf("Welcome to Goffee\n")
|
||||||
|
if useHttps {
|
||||||
|
fmt.Printf("Listening on https \nWaiting for requests...\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Listening on port %s\nWaiting for requests...\n", portNumber)
|
||||||
|
}
|
||||||
|
UseLetsEncryptStr := os.Getenv("App_USE_LETSENCRYPT")
|
||||||
|
if UseLetsEncryptStr == "" {
|
||||||
|
UseLetsEncryptStr = "false"
|
||||||
|
}
|
||||||
|
UseLetsEncrypt, _ := strconv.ParseBool(UseLetsEncryptStr)
|
||||||
|
if useHttps && UseLetsEncrypt {
|
||||||
|
m := &autocert.Manager{
|
||||||
|
Cache: autocert.DirCache("letsencrypt-certs-dir"),
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
}
|
||||||
|
LetsEncryptEmail := os.Getenv("APP_LETSENCRYPT_EMAIL")
|
||||||
|
if LetsEncryptEmail != "" {
|
||||||
|
m.Email = LetsEncryptEmail
|
||||||
|
}
|
||||||
|
HttpsHosts := os.Getenv("App_HTTPS_HOSTS")
|
||||||
|
if HttpsHosts != "" {
|
||||||
|
m.HostPolicy = autocert.HostWhitelist(HttpsHosts)
|
||||||
|
}
|
||||||
|
log.Fatal(http.Serve(m.Listener(), router))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if useHttps && !UseLetsEncrypt {
|
||||||
|
CertFile := os.Getenv("App_CERT_FILE_PATH")
|
||||||
|
if CertFile == "" {
|
||||||
|
CertFile = "tls/server.crt"
|
||||||
|
}
|
||||||
|
KeyFile := os.Getenv("App_KEY_FILE_PATH")
|
||||||
|
if KeyFile == "" {
|
||||||
|
KeyFile = "tls/server.key"
|
||||||
|
}
|
||||||
|
certFilePath := filepath.Join(basePath, CertFile)
|
||||||
|
KeyFilePath := filepath.Join(basePath, KeyFile)
|
||||||
|
log.Fatal(http.ListenAndServeTLS(":443", certFilePath, KeyFilePath, router))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", portNumber), router))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) RegisterRoutes(routes []Route, router *httprouter.Router) *httprouter.Router {
|
||||||
|
router.PanicHandler = panicHandler
|
||||||
|
router.NotFound = notFoundHandler{}
|
||||||
|
router.MethodNotAllowed = methodNotAllowed{}
|
||||||
|
for _, route := range routes {
|
||||||
|
switch route.Method {
|
||||||
|
case GET:
|
||||||
|
router.GET(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
case POST:
|
||||||
|
router.POST(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
case DELETE:
|
||||||
|
router.DELETE(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
case PATCH:
|
||||||
|
router.PATCH(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
case PUT:
|
||||||
|
router.PUT(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
case OPTIONS:
|
||||||
|
router.OPTIONS(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
case HEAD:
|
||||||
|
router.HEAD(route.Path, app.makeHTTPRouterHandlerFunc(route.Handler, route.Middlewares))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) makeHTTPRouterHandlerFunc(h Handler, ms []Middleware) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
ctx := &Context{
|
||||||
|
Request: &Request{
|
||||||
|
httpRequest: r,
|
||||||
|
httpPathParams: ps,
|
||||||
|
},
|
||||||
|
Response: &Response{
|
||||||
|
headers: []header{},
|
||||||
|
body: nil,
|
||||||
|
contentType: "",
|
||||||
|
overrideContentType: "",
|
||||||
|
HttpResponseWriter: w,
|
||||||
|
isTerminated: false,
|
||||||
|
redirectTo: "",
|
||||||
|
},
|
||||||
|
GetValidator: getValidator(),
|
||||||
|
GetJWT: getJWT(),
|
||||||
|
GetGorm: getGormFunc(),
|
||||||
|
GetCache: resolveCache(),
|
||||||
|
GetHashing: resloveHashing(),
|
||||||
|
GetMailer: resolveMailer(),
|
||||||
|
GetEventsManager: resolveEventsManager(),
|
||||||
|
GetLogger: resolveLogger(),
|
||||||
|
}
|
||||||
|
ctx.prepare(ctx)
|
||||||
|
rhs := app.combHandlers(h, ms)
|
||||||
|
app.prepareChain(rhs)
|
||||||
|
app.t = 0
|
||||||
|
app.chain.execute(ctx)
|
||||||
|
for _, header := range ctx.Response.headers {
|
||||||
|
w.Header().Add(header.key, header.val)
|
||||||
|
}
|
||||||
|
logger.CloseLogsFile()
|
||||||
|
var ct string
|
||||||
|
if ctx.Response.overrideContentType != "" {
|
||||||
|
ct = ctx.Response.overrideContentType
|
||||||
|
} else if ctx.Response.contentType != "" {
|
||||||
|
ct = ctx.Response.contentType
|
||||||
|
} else {
|
||||||
|
ct = CONTENT_TYPE_HTML
|
||||||
|
}
|
||||||
|
w.Header().Add(CONTENT_TYPE, ct)
|
||||||
|
if ctx.Response.statusCode != 0 {
|
||||||
|
w.WriteHeader(ctx.Response.statusCode)
|
||||||
|
}
|
||||||
|
if ctx.Response.redirectTo != "" {
|
||||||
|
http.Redirect(w, r, ctx.Response.redirectTo, http.StatusPermanentRedirect)
|
||||||
|
} else {
|
||||||
|
w.Write(ctx.Response.body)
|
||||||
|
}
|
||||||
|
e := ResolveEventsManager()
|
||||||
|
if e != nil {
|
||||||
|
e.setContext(ctx).processFiredEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
app.t = 0
|
||||||
|
ctx.Response.reset()
|
||||||
|
app.chain.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notFoundHandler struct{}
|
||||||
|
type methodNotAllowed struct{}
|
||||||
|
|
||||||
|
func (n notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
res := "{\"message\": \"Not Found\"}"
|
||||||
|
loggr.Error("Not Found")
|
||||||
|
loggr.Error(debug.Stack())
|
||||||
|
w.Header().Add(CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||||
|
w.Write([]byte(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n methodNotAllowed) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
res := "{\"message\": \"Method not allowed\"}"
|
||||||
|
loggr.Error("Method not allowed")
|
||||||
|
loggr.Error(debug.Stack())
|
||||||
|
w.Header().Add(CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||||
|
w.Write([]byte(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
var panicHandler = func(w http.ResponseWriter, r *http.Request, e interface{}) {
|
||||||
|
isDebugModeStr := os.Getenv("APP_DEBUG_MODE")
|
||||||
|
isDebugMode, err := strconv.ParseBool(isDebugModeStr)
|
||||||
|
if err != nil {
|
||||||
|
errStr := "error parsing env var APP_DEBUG_MODE"
|
||||||
|
loggr.Error(errStr)
|
||||||
|
fmt.Sprintln(errStr)
|
||||||
|
w.Write([]byte(errStr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !isDebugMode {
|
||||||
|
errStr := "internal error"
|
||||||
|
loggr.Error(errStr)
|
||||||
|
fmt.Sprintln(errStr)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Header().Add(CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||||
|
w.Write([]byte(fmt.Sprintf("{\"message\": \"%v\"}", errStr)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shrtMsg := fmt.Sprintf("%v", e)
|
||||||
|
loggr.Error(shrtMsg)
|
||||||
|
fmt.Println(shrtMsg)
|
||||||
|
loggr.Error(string(debug.Stack()))
|
||||||
|
var res string
|
||||||
|
if env.GetVarOtherwiseDefault("APP_ENV", "local") == PRODUCTION {
|
||||||
|
res = "{\"message\": \"internal error\"}"
|
||||||
|
} else {
|
||||||
|
res = fmt.Sprintf("{\"message\": \"%v\", \"stack trace\": \"%v\"}", e, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Header().Add(CONTENT_TYPE, CONTENT_TYPE_JSON)
|
||||||
|
w.Write([]byte(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UseMiddleware(mw Middleware) {
|
||||||
|
ResolveMiddlewares().Attach(mw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Next(c *Context) {
|
||||||
|
app.t = app.t + 1
|
||||||
|
n := app.chain.getByIndex(app.t)
|
||||||
|
if n != nil {
|
||||||
|
f, ok := n.(Middleware)
|
||||||
|
if ok {
|
||||||
|
f(c)
|
||||||
|
} else {
|
||||||
|
ff, ok := n.(Handler)
|
||||||
|
if ok {
|
||||||
|
ff(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type chain struct {
|
||||||
|
nodes []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *chain) reset() {
|
||||||
|
cn.nodes = []interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chain) getByIndex(i int) interface{} {
|
||||||
|
for k := range c.nodes {
|
||||||
|
if k == i {
|
||||||
|
return c.nodes[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) prepareChain(hs []interface{}) {
|
||||||
|
mw := app.middlewares.GetMiddlewares()
|
||||||
|
for _, v := range mw {
|
||||||
|
app.chain.nodes = append(app.chain.nodes, v)
|
||||||
|
}
|
||||||
|
for _, v := range hs {
|
||||||
|
app.chain.nodes = append(app.chain.nodes, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *chain) execute(ctx *Context) {
|
||||||
|
i := cn.getByIndex(0)
|
||||||
|
if i != nil {
|
||||||
|
f, ok := i.(Middleware)
|
||||||
|
if ok {
|
||||||
|
f(ctx)
|
||||||
|
} else {
|
||||||
|
ff, ok := i.(Handler)
|
||||||
|
if ok {
|
||||||
|
ff(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) combHandlers(h Handler, mw []Middleware) []interface{} {
|
||||||
|
var rev []interface{}
|
||||||
|
for _, k := range mw {
|
||||||
|
rev = append(rev, k)
|
||||||
|
}
|
||||||
|
rev = append(rev, h)
|
||||||
|
return rev
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGormFunc() func() *gorm.DB {
|
||||||
|
f := func() *gorm.DB {
|
||||||
|
if !gormC.EnableGorm {
|
||||||
|
panic("you are trying to use gorm but it's not enabled, you can enable it in the file config/gorm.go")
|
||||||
|
}
|
||||||
|
return ResolveGorm()
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGorm() *gorm.DB {
|
||||||
|
var err error
|
||||||
|
switch os.Getenv("DB_DRIVER") {
|
||||||
|
case "mysql":
|
||||||
|
db, err = mysqlConnect()
|
||||||
|
case "postgres":
|
||||||
|
db, err = postgresConnect()
|
||||||
|
case "sqlite":
|
||||||
|
sqlitePath := os.Getenv("SQLITE_DB_PATH")
|
||||||
|
fullSqlitePath := path.Join(basePath, sqlitePath)
|
||||||
|
_, err := os.Stat(fullSqlitePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error locating sqlite file: %v", err.Error()))
|
||||||
|
}
|
||||||
|
db, err = gorm.Open(sqlite.Open(fullSqlitePath), &gorm.Config{})
|
||||||
|
default:
|
||||||
|
panic("database driver not selected")
|
||||||
|
}
|
||||||
|
if gormC.EnableGorm && err != nil {
|
||||||
|
panic(fmt.Sprintf("gorm has problem connecting to %v, (if it's not needed you can disable it in config/gorm.go): %v", os.Getenv("DB_DRIVER"), err))
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveGorm() *gorm.DB {
|
||||||
|
if db != nil {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
db = NewGorm()
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveCache() func() *Cache {
|
||||||
|
f := func() *Cache {
|
||||||
|
if !cacheC.EnableCache {
|
||||||
|
panic("you are trying to use cache but it's not enabled, you can enable it in the file config/cache.go")
|
||||||
|
}
|
||||||
|
return NewCache(cacheC)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func postgresConnect() (*gorm.DB, error) {
|
||||||
|
dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=%v TimeZone=%v",
|
||||||
|
os.Getenv("POSTGRES_HOST"),
|
||||||
|
os.Getenv("POSTGRES_USER"),
|
||||||
|
os.Getenv("POSTGRES_PASSWORD"),
|
||||||
|
os.Getenv("POSTGRES_DB_NAME"),
|
||||||
|
os.Getenv("POSTGRES_PORT"),
|
||||||
|
os.Getenv("POSTGRES_SSL_MODE"),
|
||||||
|
os.Getenv("POSTGRES_TIMEZONE"),
|
||||||
|
)
|
||||||
|
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func mysqlConnect() (*gorm.DB, error) {
|
||||||
|
dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=%v&parseTime=True&loc=Local",
|
||||||
|
os.Getenv("MYSQL_USERNAME"),
|
||||||
|
os.Getenv("MYSQL_PASSWORD"),
|
||||||
|
os.Getenv("MYSQL_HOST"),
|
||||||
|
os.Getenv("MYSQL_PORT"),
|
||||||
|
os.Getenv("MYSQL_DB_NAME"),
|
||||||
|
os.Getenv("MYSQL_CHARSET"),
|
||||||
|
)
|
||||||
|
return gorm.Open(mysql.New(mysql.Config{
|
||||||
|
DSN: dsn, // data source name
|
||||||
|
DefaultStringSize: 256, // default size for string fields
|
||||||
|
DisableDatetimePrecision: true, // disable datetime precision, which not supported before MySQL 5.6
|
||||||
|
DontSupportRenameIndex: true, // drop & create when rename index, rename index not supported before MySQL 5.7, MariaDB
|
||||||
|
DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB
|
||||||
|
SkipInitializeWithVersion: false, // auto configure based on currently MySQL version
|
||||||
|
}), &gorm.Config{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJWT() func() *JWT {
|
||||||
|
f := func() *JWT {
|
||||||
|
secret := os.Getenv("JWT_SECRET")
|
||||||
|
if secret == "" {
|
||||||
|
panic("jwt secret key is not set")
|
||||||
|
}
|
||||||
|
lifetimeStr := os.Getenv("JWT_LIFESPAN_MINUTES")
|
||||||
|
if lifetimeStr == "" {
|
||||||
|
lifetimeStr = "10080" // 7 days
|
||||||
|
}
|
||||||
|
lifetime64, err := strconv.ParseInt(lifetimeStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
lifetime := int(lifetime64)
|
||||||
|
return newJWT(JWTOptions{
|
||||||
|
SigningKey: secret,
|
||||||
|
LifetimeMinutes: lifetime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func getValidator() func() *Validator {
|
||||||
|
f := func() *Validator {
|
||||||
|
return &Validator{}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func resloveHashing() func() *Hashing {
|
||||||
|
f := func() *Hashing {
|
||||||
|
return &Hashing{}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveMailer() func() *Mailer {
|
||||||
|
f := func() *Mailer {
|
||||||
|
if mailer != nil {
|
||||||
|
return mailer
|
||||||
|
}
|
||||||
|
var m *Mailer
|
||||||
|
var emailsDriver string
|
||||||
|
if os.Getenv("EMAILS_DRIVER") == "" {
|
||||||
|
emailsDriver = "SMTP"
|
||||||
|
}
|
||||||
|
switch emailsDriver {
|
||||||
|
case "SMTP":
|
||||||
|
m = initiateMailerWithSMTP()
|
||||||
|
case "sparkpost":
|
||||||
|
m = initiateMailerWithSparkPost()
|
||||||
|
case "sendgrid":
|
||||||
|
m = initiateMailerWithSendGrid()
|
||||||
|
case "mailgun":
|
||||||
|
return initiateMailerWithMailGun()
|
||||||
|
default:
|
||||||
|
m = initiateMailerWithSMTP()
|
||||||
|
}
|
||||||
|
mailer = m
|
||||||
|
return mailer
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveEventsManager() func() *EventsManager {
|
||||||
|
f := func() *EventsManager {
|
||||||
|
return ResolveEventsManager()
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveLogger() func() *logger.Logger {
|
||||||
|
f := func() *logger.Logger {
|
||||||
|
return loggr
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) MakeDirs(dirs ...string) {
|
||||||
|
o := syscall.Umask(0)
|
||||||
|
defer syscall.Umask(o)
|
||||||
|
for _, dir := range dirs {
|
||||||
|
os.MkdirAll(path.Join(basePath, dir), 0766)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetRequestConfig(r RequestConfig) {
|
||||||
|
requestC = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetGormConfig(g GormConfig) {
|
||||||
|
gormC = g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetCacheConfig(c CacheConfig) {
|
||||||
|
cacheC = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) SetBasePath(path string) {
|
||||||
|
basePath = path
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableEvents() {
|
||||||
|
disableEvents = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnableEvents() {
|
||||||
|
disableEvents = false
|
||||||
|
}
|
525
core_test.go
Normal file
525
core_test.go
Normal file
|
@ -0,0 +1,525 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.smarteching.com/goffee/core/env"
|
||||||
|
"git.smarteching.com/goffee/core/logger"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
if fmt.Sprintf("%T", app) != "*core.App" {
|
||||||
|
t.Errorf("failed testing new core")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetEnv(t *testing.T) {
|
||||||
|
envVars, err := godotenv.Read("./testingdata/.env")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed reading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
|
env.SetEnvVars(envVars)
|
||||||
|
|
||||||
|
if os.Getenv("KEY_ONE") != "VAL_ONE" || os.Getenv("KEY_TWO") != "VAL_TWO" {
|
||||||
|
t.Errorf("failed to set env vars")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeHTTPHandlerFunc(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
tmpFile := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
app.SetLogsDriver(&logger.LogFileDriver{
|
||||||
|
FilePath: filepath.Join(t.TempDir(), uuid.NewString()),
|
||||||
|
})
|
||||||
|
hdlr := Handler(func(c *Context) *Response {
|
||||||
|
f, _ := os.Create(tmpFile)
|
||||||
|
f.WriteString("DFT2V56H")
|
||||||
|
c.Response.SetHeader("header-key", "header-val")
|
||||||
|
return c.Response.Text("DFT2V56H")
|
||||||
|
})
|
||||||
|
h := app.makeHTTPRouterHandlerFunc(hdlr, nil)
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h(w, r, []httprouter.Param{{Key: "tkey", Value: "tvalue"}})
|
||||||
|
rsp := w.Result()
|
||||||
|
if rsp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("failed testing make http handler func")
|
||||||
|
}
|
||||||
|
s, _ := os.ReadFile(tmpFile)
|
||||||
|
if string(s) != "DFT2V56H" {
|
||||||
|
t.Errorf("failed testing make http handler func")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeHTTPHandlerFuncVerifyJson(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
tmpFile := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
app.SetLogsDriver(&logger.LogFileDriver{
|
||||||
|
FilePath: filepath.Join(t.TempDir(), uuid.NewString()),
|
||||||
|
})
|
||||||
|
hdlr := Handler(func(c *Context) *Response {
|
||||||
|
f, _ := os.Create(tmpFile)
|
||||||
|
f.WriteString("DFT2V56H")
|
||||||
|
c.Response.SetHeader("header-key", "header-val")
|
||||||
|
return c.Response.Json("{\"testKey\": \"testVal\"}")
|
||||||
|
})
|
||||||
|
h := app.makeHTTPRouterHandlerFunc(hdlr, nil)
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
h(w, r, []httprouter.Param{{Key: "tkey", Value: "tvalue"}})
|
||||||
|
rsp := w.Result()
|
||||||
|
if rsp.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("failed testing make http handler func with json verify")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing make http handler func with json verify")
|
||||||
|
}
|
||||||
|
var j map[string]interface{}
|
||||||
|
err = json.Unmarshal(b, &j)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing make http handler func with json verify: %v", err)
|
||||||
|
}
|
||||||
|
if j["testKey"] != "testVal" {
|
||||||
|
t.Errorf("failed testing make http handler func with json verify")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodNotAllowedHandler(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
app.SetLogsDriver(logger.LogNullDriver{})
|
||||||
|
app.Bootstrap()
|
||||||
|
m := &methodNotAllowed{}
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
m.ServeHTTP(w, r)
|
||||||
|
rsp := w.Result()
|
||||||
|
if rsp.StatusCode != 405 {
|
||||||
|
t.Errorf("failed testing method not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotFoundHandler(t *testing.T) {
|
||||||
|
n := ¬FoundHandler{}
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
n.ServeHTTP(w, r)
|
||||||
|
rsp := w.Result()
|
||||||
|
if rsp.StatusCode != 404 {
|
||||||
|
t.Errorf("failed testing not found handler")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseMiddleware(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
UseMiddleware(Middleware(func(c *Context) { c.GetLogger().Info("Testing!") }))
|
||||||
|
if len(app.middlewares.GetMiddlewares()) != 1 {
|
||||||
|
t.Errorf("failed testing use middleware")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainReset(t *testing.T) {
|
||||||
|
c := &chain{}
|
||||||
|
c.nodes = append(c.nodes, Middleware(func(c *Context) { c.GetLogger().Info("Testing1!") }))
|
||||||
|
c.nodes = append(c.nodes, Middleware(func(c *Context) { c.GetLogger().Info("Testing2!") }))
|
||||||
|
|
||||||
|
c.reset()
|
||||||
|
if len(c.nodes) != 0 {
|
||||||
|
t.Errorf("failed testing reset chain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNext(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
app.t = 0
|
||||||
|
tfPath := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
var hs []interface{}
|
||||||
|
hs = append(hs, Middleware(func(c *Context) { c.Next() }))
|
||||||
|
hs = append(hs, Handler(func(c *Context) *Response {
|
||||||
|
f, _ := os.Create(tfPath)
|
||||||
|
f.WriteString("DFT2V56H")
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
app.prepareChain(hs)
|
||||||
|
|
||||||
|
app.chain.execute(makeCTX(t))
|
||||||
|
cnt, _ := os.ReadFile(tfPath)
|
||||||
|
if string(cnt) != "DFT2V56H" {
|
||||||
|
// t.Errorf("failed testing next")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainGetByIndex(t *testing.T) {
|
||||||
|
c := &chain{}
|
||||||
|
tf := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
var hs []interface{}
|
||||||
|
hs = append(hs, Middleware(func(c *Context) { c.GetLogger().Info("testing!") }))
|
||||||
|
hs = append(hs, Middleware(func(c *Context) {
|
||||||
|
f, _ := os.Create(tf)
|
||||||
|
f.WriteString("DFT2V56H")
|
||||||
|
}))
|
||||||
|
c.nodes = hs
|
||||||
|
pf := c.getByIndex(1)
|
||||||
|
f, ok := pf.(Middleware)
|
||||||
|
if ok {
|
||||||
|
f(makeCTX(t))
|
||||||
|
}
|
||||||
|
d, _ := os.ReadFile(tf)
|
||||||
|
if string(d) != "DFT2V56H" {
|
||||||
|
t.Errorf("failed testing chain get by index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareChain(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
UseMiddleware(Middleware(func(c *Context) { c.GetLogger().Info("Testing!") }))
|
||||||
|
var hs []interface{}
|
||||||
|
hs = append(hs, Middleware(func(c *Context) { c.GetLogger().Info("testing1!") }))
|
||||||
|
hs = append(hs, Middleware(func(c *Context) { c.GetLogger().Info("testing2!") }))
|
||||||
|
app.prepareChain(hs)
|
||||||
|
if len(app.chain.nodes) != 3 {
|
||||||
|
t.Errorf("failed preparing chain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainExecute(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
f1Path := filepath.Join(tmpDir, uuid.NewString())
|
||||||
|
c := &chain{}
|
||||||
|
c.nodes = []interface{}{
|
||||||
|
Handler(func(c *Context) *Response {
|
||||||
|
tf, _ := os.Create(f1Path)
|
||||||
|
defer tf.Close()
|
||||||
|
tf.WriteString("DFT2V56H")
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
ctx := makeCTX(t)
|
||||||
|
c.execute(ctx)
|
||||||
|
cnt, _ := os.ReadFile(f1Path)
|
||||||
|
if string(cnt) != "DFT2V56H" {
|
||||||
|
t.Errorf("failed testing execute chain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCTX(t *testing.T) *Context {
|
||||||
|
t.Helper()
|
||||||
|
return &Context{
|
||||||
|
Request: &Request{
|
||||||
|
httpRequest: httptest.NewRequest(GET, LOCALHOST, nil),
|
||||||
|
httpPathParams: nil,
|
||||||
|
},
|
||||||
|
Response: &Response{
|
||||||
|
headers: []header{},
|
||||||
|
body: nil,
|
||||||
|
HttpResponseWriter: httptest.NewRecorder(),
|
||||||
|
},
|
||||||
|
GetValidator: nil,
|
||||||
|
GetJWT: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestcombHndlers(t *testing.T) {
|
||||||
|
app := createNewApp(t)
|
||||||
|
t1 := Handler(func(c *Context) *Response { c.GetLogger().Info("Testing1!"); return nil })
|
||||||
|
t2 := Middleware(func(c *Context) { c.GetLogger().Info("Testing2!") })
|
||||||
|
|
||||||
|
mw := []Middleware{t2}
|
||||||
|
comb := app.combHandlers(t1, mw)
|
||||||
|
if reflect.ValueOf(t1).Pointer() != reflect.ValueOf(comb[0]).Pointer() {
|
||||||
|
t.Errorf("failed testing reverse handlers")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.ValueOf(t2).Pointer() != reflect.ValueOf(comb[1]).Pointer() {
|
||||||
|
t.Errorf("failed testing reverse handlers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterGetRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Get("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Post("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Delete("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Patch("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Put("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Options("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
gcr.Head("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", s.URL+"?param=valget", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register routes")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register routes")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register routes")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "valget" {
|
||||||
|
t.Errorf("failed test register routes")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterPostRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Post("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("POST", s.URL+"?param=valpost", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register post route")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register post route")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register post route")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "valpost" {
|
||||||
|
t.Errorf("failed test register post route")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterDeleteRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Delete("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("DELETE", s.URL+"?param=valdelete", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register delete route")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register delete route")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register delete route")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "valdelete" {
|
||||||
|
t.Errorf("failed test register delete route")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterPatchRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Patch("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("PATCH", s.URL+"?param=valpatch", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register patch route")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register patch route")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register patch route")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "valpatch" {
|
||||||
|
t.Errorf("failed test register patch route")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterPutRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Put("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("PUT", s.URL+"?param=valput", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register put route")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register put route")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register put route")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "valput" {
|
||||||
|
t.Errorf("failed test register put route")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterOptionsRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
gcr.Options("/", Handler(func(c *Context) *Response {
|
||||||
|
fmt.Fprintln(c.Response.HttpResponseWriter, c.GetRequestParam("param"))
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("OPTIONS", s.URL+"?param=valoptions", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register options route")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register options route")
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register options route")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(b)) != "valoptions" {
|
||||||
|
t.Errorf("failed test register options route")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterHeadRoute(t *testing.T) {
|
||||||
|
app := New()
|
||||||
|
hr := httprouter.New()
|
||||||
|
gcr := NewRouter()
|
||||||
|
tfp := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
gcr.Head("/", Handler(func(c *Context) *Response {
|
||||||
|
param := c.GetRequestParam("param")
|
||||||
|
p, _ := param.(string)
|
||||||
|
f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, 777)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
f.WriteString("fromhead")
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
hr = app.RegisterRoutes(gcr.GetRoutes(), hr)
|
||||||
|
s := httptest.NewServer(hr)
|
||||||
|
defer s.Close()
|
||||||
|
clt := &http.Client{}
|
||||||
|
req, err := http.NewRequest("HEAD", s.URL+"?param="+tfp, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register head route")
|
||||||
|
}
|
||||||
|
rsp, err := clt.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register head route")
|
||||||
|
}
|
||||||
|
f, err := os.Open(tfp)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register head route: %v", err.Error())
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test register head route: %v", err.Error())
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
if strings.TrimSpace(string(b)) != "fromhead" {
|
||||||
|
t.Errorf("failed test register head route")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPanicHandler(t *testing.T) {
|
||||||
|
os.Setenv("APP_DEBUG_MODE", "true")
|
||||||
|
loggr = logger.NewLogger(&logger.LogNullDriver{})
|
||||||
|
r := httptest.NewRequest(GET, LOCALHOST, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
panicHandler(w, r, "")
|
||||||
|
rsp := w.Result()
|
||||||
|
b, _ := io.ReadAll(rsp.Body)
|
||||||
|
if !strings.Contains(string(b), "stack trace") {
|
||||||
|
t.Errorf("failed test panic handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNewApp(t *testing.T) *App {
|
||||||
|
t.Helper()
|
||||||
|
a := New()
|
||||||
|
a.SetLogsDriver(&logger.LogNullDriver{})
|
||||||
|
a.SetRequestConfig(testingRequestC)
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
32
env/env.go
vendored
Normal file
32
env/env.go
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetVar(varName string) string {
|
||||||
|
return os.Getenv(varName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVarOtherwiseDefault(varName string, defaultValue string) string {
|
||||||
|
v, p := os.LookupEnv(varName)
|
||||||
|
if p {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSet(varName string) bool {
|
||||||
|
_, p := os.LookupEnv(varName)
|
||||||
|
if p {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEnvVars(envVars map[string]string) {
|
||||||
|
for key, val := range envVars {
|
||||||
|
os.Setenv(strings.TrimSpace(key), strings.TrimSpace(val))
|
||||||
|
}
|
||||||
|
}
|
53
env/env_test.go
vendored
Normal file
53
env/env_test.go
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetVar(t *testing.T) {
|
||||||
|
os.Setenv("testKey11", "testVal")
|
||||||
|
v := GetVar("testKey11")
|
||||||
|
if v != "testVal" {
|
||||||
|
t.Error("failed testing get var")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVarOtherwiseDefault(t *testing.T) {
|
||||||
|
v := GetVarOtherwiseDefault("testKey12", "defaultVal")
|
||||||
|
if v != "defaultVal" {
|
||||||
|
t.Error("failed testing get default")
|
||||||
|
}
|
||||||
|
os.Setenv("testKey12", "testVal")
|
||||||
|
v = GetVarOtherwiseDefault("testKey12", "defaultVal")
|
||||||
|
if v != "testVal" {
|
||||||
|
t.Error("failed testing get default val")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsSet(t *testing.T) {
|
||||||
|
i := IsSet("testKey13")
|
||||||
|
if i == true {
|
||||||
|
t.Error("failed testing is set")
|
||||||
|
}
|
||||||
|
os.Setenv("testKey13", "testVal")
|
||||||
|
i = IsSet("testKey13")
|
||||||
|
if i == false {
|
||||||
|
t.Error("filed testing is set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetEnvVars(t *testing.T) {
|
||||||
|
envVars := map[string]string{
|
||||||
|
"key14": "testVal14",
|
||||||
|
"key15": "testVal15",
|
||||||
|
}
|
||||||
|
|
||||||
|
SetEnvVars(envVars)
|
||||||
|
if GetVar("key14") != "testVal14" {
|
||||||
|
t.Error("failed testing set vars")
|
||||||
|
}
|
||||||
|
if GetVar("key15") != "testVal15" {
|
||||||
|
t.Error("failed testing set vars")
|
||||||
|
}
|
||||||
|
}
|
3
event-job.go
Normal file
3
event-job.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
type EventJob func(event *Event, requestContext *Context)
|
6
event.go
Normal file
6
event.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Name string
|
||||||
|
Payload map[string]interface{}
|
||||||
|
}
|
89
events-manager.go
Normal file
89
events-manager.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventsManager struct {
|
||||||
|
eventsJobsList map[string][]EventJob
|
||||||
|
firedEvents []*Event
|
||||||
|
}
|
||||||
|
|
||||||
|
var manager *EventsManager
|
||||||
|
var rqc *Context
|
||||||
|
|
||||||
|
func NewEventsManager() *EventsManager {
|
||||||
|
manager = &EventsManager{
|
||||||
|
eventsJobsList: map[string][]EventJob{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveEventsManager() *EventsManager {
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EventsManager) setContext(requestContext *Context) *EventsManager {
|
||||||
|
rqc = requestContext
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EventsManager) Fire(e *Event) error {
|
||||||
|
if disableEvents {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e.Name == "" {
|
||||||
|
return errors.New("event name is empty")
|
||||||
|
}
|
||||||
|
_, exists := m.eventsJobsList[e.Name]
|
||||||
|
if !exists {
|
||||||
|
return errors.New(fmt.Sprintf("event %v is not registered", e.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
m.firedEvents = append(m.firedEvents, e)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EventsManager) Register(eName string, job EventJob) {
|
||||||
|
if disableEvents {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if eName == "" {
|
||||||
|
panic("event name is empty")
|
||||||
|
}
|
||||||
|
_, exists := m.eventsJobsList[eName]
|
||||||
|
if !exists {
|
||||||
|
m.eventsJobsList[eName] = []EventJob{job}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, jobs := range m.eventsJobsList {
|
||||||
|
if key == eName {
|
||||||
|
jobs = append(jobs, job)
|
||||||
|
m.eventsJobsList[key] = jobs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EventsManager) processFiredEvents() {
|
||||||
|
if disableEvents {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, event := range m.firedEvents {
|
||||||
|
m.executeEventJobs(event)
|
||||||
|
}
|
||||||
|
m.firedEvents = []*Event{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EventsManager) executeEventJobs(event *Event) {
|
||||||
|
for key, jobs := range m.eventsJobsList {
|
||||||
|
if key == event.Name {
|
||||||
|
for _, job := range jobs {
|
||||||
|
job(event, rqc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
events-manager_test.go
Normal file
103
events-manager_test.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewEventsManager(t *testing.T) {
|
||||||
|
m := NewEventsManager()
|
||||||
|
if fmt.Sprintf("%T", m) != "*core.EventsManager" {
|
||||||
|
t.Errorf("failed testing new events manager")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TestResolveEventsManager(t *testing.T) {
|
||||||
|
// NewEventsManager()
|
||||||
|
// m := ResolveEventsManager()
|
||||||
|
// if fmt.Sprintf("%T", m) != "*core.EventsManager" {
|
||||||
|
// t.Errorf("failed testing new events manager")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func TestEvents(t *testing.T) {
|
||||||
|
// pwd, _ := os.Getwd()
|
||||||
|
// const eventName1 string = "test-event-name1"
|
||||||
|
// const eventName2 string = "test-event-name2"
|
||||||
|
// var tmpDir string
|
||||||
|
// if runtime.GOOS == "linux" {
|
||||||
|
// tmpDir = t.TempDir()
|
||||||
|
// } else {
|
||||||
|
// tmpDir = filepath.Join(pwd, "/testingdata/tmp")
|
||||||
|
// }
|
||||||
|
// tmpFile1 := filepath.Join(tmpDir, uuid.NewString())
|
||||||
|
// tmpFile2 := filepath.Join(tmpDir, uuid.NewString())
|
||||||
|
// tmpFile3 := filepath.Join(tmpDir, uuid.NewString())
|
||||||
|
// m := NewEventsManager()
|
||||||
|
// m.Register(eventName1, func(event *Event, requestContext *Context) {
|
||||||
|
// os.Create(tmpFile1)
|
||||||
|
// f, err := os.Create(tmpFile1)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Errorf("error testing register event: %v", err.Error())
|
||||||
|
// }
|
||||||
|
// f.WriteString(event.Name)
|
||||||
|
// f.Close()
|
||||||
|
// })
|
||||||
|
// m.Register(eventName1, func(event *Event, requestContext *Context) {
|
||||||
|
// os.Create(tmpFile3)
|
||||||
|
// f, err := os.Create(tmpFile3)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Errorf("error testing register event: %v", err.Error())
|
||||||
|
// }
|
||||||
|
// f.WriteString(event.Name)
|
||||||
|
// f.Close()
|
||||||
|
// })
|
||||||
|
// m.Fire(&Event{Name: eventName1})
|
||||||
|
// m.processFiredEvents()
|
||||||
|
|
||||||
|
// ff, err := os.Open(tmpFile1)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Errorf("error testing register event : %v", err.Error())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// d, err := io.ReadAll(ff)
|
||||||
|
// if string(d) != eventName1 {
|
||||||
|
// t.Error("faild testing events")
|
||||||
|
// }
|
||||||
|
// ff.Close()
|
||||||
|
// os.Remove(tmpFile1)
|
||||||
|
|
||||||
|
// ff, err = os.Open(tmpFile3)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Errorf("error testing register event : %v", err.Error())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// d, err = io.ReadAll(ff)
|
||||||
|
// if string(d) != eventName1 {
|
||||||
|
// t.Error("faild testing events")
|
||||||
|
// }
|
||||||
|
// ff.Close()
|
||||||
|
// os.Remove(tmpFile3)
|
||||||
|
|
||||||
|
// m.Register(eventName2, func(event *Event, requestContext *Context) {
|
||||||
|
// f, err := os.Create(tmpFile2)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Errorf("error testing register event: %v", err.Error())
|
||||||
|
// }
|
||||||
|
// f.WriteString(event.Name)
|
||||||
|
// f.Close()
|
||||||
|
// })
|
||||||
|
// m.Fire(&Event{Name: eventName2})
|
||||||
|
// m.processFiredEvents()
|
||||||
|
|
||||||
|
// ff, err = os.Open(tmpFile2)
|
||||||
|
// if err != nil {
|
||||||
|
// t.Errorf("error testing register event : %v", err.Error())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// d, err = io.ReadAll(ff)
|
||||||
|
// if string(d) != eventName2 {
|
||||||
|
// t.Error("faild testing events")
|
||||||
|
// }
|
||||||
|
// ff.Close()
|
||||||
|
// }
|
50
go.mod
Normal file
50
go.mod
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
module git.smarteching.com/goffee/core
|
||||||
|
|
||||||
|
replace git.smarteching.com/goffee/core/logger => ./logger
|
||||||
|
|
||||||
|
replace git.smarteching.com/goffee/core/env => ./env
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/brianvoe/gofakeit/v6 v6.21.0
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/harranali/mailing v1.2.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
|
github.com/redis/go-redis/v9 v9.0.5
|
||||||
|
golang.org/x/crypto v0.11.0
|
||||||
|
gorm.io/driver/mysql v1.5.1
|
||||||
|
gorm.io/driver/postgres v1.5.2
|
||||||
|
gorm.io/driver/sqlite v1.5.2
|
||||||
|
gorm.io/gorm v1.25.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/SparkPost/gosparkpost v0.2.0 // indirect
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
|
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.3.1 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.10 // indirect
|
||||||
|
github.com/mailgun/mailgun-go/v4 v4.10.0 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||||
|
github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect
|
||||||
|
golang.org/x/net v0.10.0 // indirect
|
||||||
|
golang.org/x/text v0.11.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
)
|
109
go.sum
Normal file
109
go.sum
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s=
|
||||||
|
github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
|
github.com/brianvoe/gofakeit/v6 v6.21.0 h1:tNkm9yxEbpuPK8Bx39tT4sSc5i9SUGiciLdNix+VDQY=
|
||||||
|
github.com/brianvoe/gofakeit/v6 v6.21.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||||
|
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||||
|
github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
|
||||||
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||||
|
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||||
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||||
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||||
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||||
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
|
||||||
|
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
||||||
|
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
||||||
|
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
github.com/jhillyerd/enmime v0.8.0/go.mod h1:MBHs3ugk03NGjMM6PuRynlKf+HA5eSillZ+TRCm73AE=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/mailgun/mailgun-go/v4 v4.10.0 h1:e5LVsxpqjOYRyaOWifrJORoLQZTYDP+g4ljfmf9G2zE=
|
||||||
|
github.com/mailgun/mailgun-go/v4 v4.10.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
|
||||||
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
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/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
|
||||||
|
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||||
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
|
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||||
|
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||||
|
github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg=
|
||||||
|
github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
|
||||||
|
gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
|
||||||
|
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
|
||||||
|
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
|
||||||
|
gorm.io/driver/sqlite v1.5.2 h1:TpQ+/dqCY4uCigCFyrfnrJnrW9zjpelWVoEVNy5qJkc=
|
||||||
|
gorm.io/driver/sqlite v1.5.2/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
|
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
|
||||||
|
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
8
handler.go
Normal file
8
handler.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
type Handler func(c *Context) *Response
|
37
hashing.go
Normal file
37
hashing.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hashing struct{}
|
||||||
|
|
||||||
|
func (h *Hashing) HashPassword(password string) (string, error) {
|
||||||
|
res, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hashing) CheckPasswordHash(hashedPassword string, originalPassowrd string) (bool, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(originalPassowrd))
|
||||||
|
if err != nil && errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err)
|
||||||
|
loggr.Debug(err.Error())
|
||||||
|
return false, errors.New("failed checking password hash")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
100
jwt.go
Normal file
100
jwt.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWT struct {
|
||||||
|
signingKey []byte
|
||||||
|
expiresAt time.Time
|
||||||
|
}
|
||||||
|
type JWTOptions struct {
|
||||||
|
SigningKey string
|
||||||
|
LifetimeMinutes int
|
||||||
|
}
|
||||||
|
|
||||||
|
var j *JWT
|
||||||
|
|
||||||
|
func newJWT(opts JWTOptions) *JWT {
|
||||||
|
d := time.Duration(opts.LifetimeMinutes)
|
||||||
|
j = &JWT{
|
||||||
|
signingKey: []byte(opts.SigningKey),
|
||||||
|
expiresAt: time.Now().Add(d * time.Minute),
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
func resolveJWT() *JWT {
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
type claims struct {
|
||||||
|
J []byte
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) GenerateToken(payload map[string]interface{}) (string, error) {
|
||||||
|
claims, err := mapClaims(payload, j.expiresAt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
token, err := t.SignedString(j.signingKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) DecodeToken(token string) (payload map[string]interface{}, err error) {
|
||||||
|
t, err := jwt.ParseWithClaims(token, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return j.signingKey, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c, ok := t.Claims.(*claims)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("error decoding token")
|
||||||
|
}
|
||||||
|
expiresAt := time.Unix(c.ExpiresAt.Unix(), 0)
|
||||||
|
et := time.Now().Compare(expiresAt)
|
||||||
|
if et != -1 {
|
||||||
|
return nil, errors.New("token has expired")
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(c.J, &payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWT) HasExpired(token string) (bool, error) {
|
||||||
|
_, err := jwt.ParseWithClaims(token, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return j.signingKey, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapClaims(data map[string]interface{}, expiresAt time.Time) (jwt.Claims, error) {
|
||||||
|
j, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return claims{}, err
|
||||||
|
}
|
||||||
|
r := claims{
|
||||||
|
j,
|
||||||
|
jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
114
jwt_test.go
Normal file
114
jwt_test.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewJWT(t *testing.T) {
|
||||||
|
j := newJWT(JWTOptions{
|
||||||
|
SigningKey: "testsigning",
|
||||||
|
LifetimeMinutes: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
if fmt.Sprintf("%T", j) != "*core.JWT" {
|
||||||
|
t.Errorf("failed testing new jwt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveJWT(t *testing.T) {
|
||||||
|
initiateJWTHelper(t)
|
||||||
|
j := resolveJWT()
|
||||||
|
if fmt.Sprintf("%T", j) != "*core.JWT" {
|
||||||
|
t.Errorf("failed testing resolve jwt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateToken(t *testing.T) {
|
||||||
|
j := initiateJWTHelper(t)
|
||||||
|
token, err := j.GenerateToken(map[string]interface{}{
|
||||||
|
"testKey": "testVal",
|
||||||
|
})
|
||||||
|
if err != nil || token == "" {
|
||||||
|
t.Errorf("error testing generate jwt token")
|
||||||
|
}
|
||||||
|
d, err := j.DecodeToken(token)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error testing generate jwt token: %v", err.Error())
|
||||||
|
}
|
||||||
|
if d["testKey"] != "testVal" {
|
||||||
|
t.Errorf("error testing generate jwt token: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeToken(t *testing.T) {
|
||||||
|
expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJKIjoiZXlKMFpYTjBTMlY1SWpvaWRHVnpkRlpoYkNKOSIsImV4cCI6MTY4NDkyMzQwOX0.v2aM9OTDJ48L4KnGjfLH3JAFQw4Gkgj5z7cA7txPNag"
|
||||||
|
j := initiateJWTHelper(t)
|
||||||
|
_, err := j.DecodeToken(expiredToken)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed test decode token")
|
||||||
|
}
|
||||||
|
token, err := j.GenerateToken(map[string]interface{}{
|
||||||
|
"testKey": "testVal",
|
||||||
|
})
|
||||||
|
if err != nil || token == "" {
|
||||||
|
t.Errorf("failed testing decode token")
|
||||||
|
}
|
||||||
|
d, err := j.DecodeToken(token)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error testing decode jwt token")
|
||||||
|
}
|
||||||
|
if d["testKey"] != "testVal" {
|
||||||
|
t.Errorf("error testing decode jwt token")
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err = j.DecodeToken("test-token")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("error testing decode jwt token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasExpired(t *testing.T) {
|
||||||
|
expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJKIjoiZXlKMFpYTjBTMlY1SWpvaWRHVnpkRlpoYkNKOSIsImV4cCI6MTY4NDkyMzQwOX0.v2aM9OTDJ48L4KnGjfLH3JAFQw4Gkgj5z7cA7txPNag"
|
||||||
|
j := initiateJWTHelper(t)
|
||||||
|
tokenHasExpired, err := j.HasExpired(expiredToken)
|
||||||
|
if !tokenHasExpired {
|
||||||
|
t.Errorf("failed test token has expired check")
|
||||||
|
}
|
||||||
|
if tokenHasExpired && err != nil {
|
||||||
|
t.Errorf("failed test decode token: %v", err.Error())
|
||||||
|
}
|
||||||
|
token, err := j.GenerateToken(map[string]interface{}{
|
||||||
|
"testKey": "testVal",
|
||||||
|
})
|
||||||
|
if err != nil || token == "" {
|
||||||
|
t.Errorf("failed testing decode token")
|
||||||
|
}
|
||||||
|
_, err = j.HasExpired(token)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error testing decode jwt token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapClaims(t *testing.T) {
|
||||||
|
c, err := mapClaims(map[string]interface{}{
|
||||||
|
"testKey": "testVal",
|
||||||
|
}, time.Now())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing map claims")
|
||||||
|
}
|
||||||
|
if fmt.Sprintf("%T", c) != "core.claims" {
|
||||||
|
t.Errorf("failed testing map claims")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateJWTHelper(t *testing.T) *JWT {
|
||||||
|
t.Helper()
|
||||||
|
j := newJWT(JWTOptions{
|
||||||
|
SigningKey: "testsigning",
|
||||||
|
LifetimeMinutes: 2,
|
||||||
|
})
|
||||||
|
return j
|
||||||
|
}
|
94
logger/logger.go
Normal file
94
logger/logger.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// logs file
|
||||||
|
var logsFile *os.File
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
infoLogger *log.Logger
|
||||||
|
warningLogger *log.Logger
|
||||||
|
errorLogger *log.Logger
|
||||||
|
debugLogger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var l *Logger
|
||||||
|
|
||||||
|
type LogsDriver interface {
|
||||||
|
GetTarget() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogFileDriver struct {
|
||||||
|
FilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogNullDriver struct{}
|
||||||
|
|
||||||
|
func (n LogNullDriver) GetTarget() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f LogFileDriver) GetTarget() interface{} {
|
||||||
|
return f.FilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(driver LogsDriver) *Logger {
|
||||||
|
if driver.GetTarget() == nil {
|
||||||
|
l = &Logger{
|
||||||
|
infoLogger: log.New(io.Discard, "info: ", log.LstdFlags),
|
||||||
|
warningLogger: log.New(io.Discard, "warning: ", log.LstdFlags),
|
||||||
|
errorLogger: log.New(io.Discard, "error: ", log.LstdFlags),
|
||||||
|
debugLogger: log.New(io.Discard, "debug: ", log.LstdFlags),
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
path, ok := driver.GetTarget().(string)
|
||||||
|
if !ok {
|
||||||
|
panic("something wrong with the file path")
|
||||||
|
}
|
||||||
|
logsFile, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
l = &Logger{
|
||||||
|
infoLogger: log.New(logsFile, "info: ", log.LstdFlags),
|
||||||
|
warningLogger: log.New(logsFile, "warning: ", log.LstdFlags),
|
||||||
|
errorLogger: log.New(logsFile, "error: ", log.LstdFlags),
|
||||||
|
debugLogger: log.New(logsFile, "debug: ", log.LstdFlags),
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveLogger() *Logger {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(msg interface{}) {
|
||||||
|
l.infoLogger.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(msg interface{}) {
|
||||||
|
l.debugLogger.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warning(msg interface{}) {
|
||||||
|
l.warningLogger.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(msg interface{}) {
|
||||||
|
l.errorLogger.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseLogsFile() {
|
||||||
|
if logsFile != nil {
|
||||||
|
defer logsFile.Close()
|
||||||
|
}
|
||||||
|
}
|
133
logger/logger_test.go
Normal file
133
logger/logger_test.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewLogger(t *testing.T) {
|
||||||
|
fp := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
f, err := os.Create(fp)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test new logger")
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
l := NewLogger(&LogNullDriver{})
|
||||||
|
l.Info("testing")
|
||||||
|
fdrv := &LogFileDriver{
|
||||||
|
fp,
|
||||||
|
}
|
||||||
|
trgt := fdrv.GetTarget()
|
||||||
|
ts, ok := trgt.(string)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("failed test new logger")
|
||||||
|
}
|
||||||
|
if ts != fp {
|
||||||
|
t.Errorf("failed test new logger")
|
||||||
|
}
|
||||||
|
l = NewLogger(fdrv)
|
||||||
|
l.Error("test-err")
|
||||||
|
f, err = os.Open(fp)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test new logger")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
b, err := io.ReadAll(f)
|
||||||
|
if !strings.Contains(string(b), "test-err") {
|
||||||
|
t.Errorf("failed test new logger")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInfo(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
l := NewLogger(&LogFileDriver{
|
||||||
|
FilePath: path,
|
||||||
|
})
|
||||||
|
l.Info("DFT2V56H")
|
||||||
|
lf, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed testing info")
|
||||||
|
}
|
||||||
|
d, err := io.ReadAll(lf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("error testing info")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(d), "DFT2V56H") {
|
||||||
|
t.Error("error testing info")
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
CloseLogsFile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarning(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
l := NewLogger(&LogFileDriver{
|
||||||
|
FilePath: path,
|
||||||
|
})
|
||||||
|
|
||||||
|
l.Warning("DFT2V56H")
|
||||||
|
lf, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed testing warning")
|
||||||
|
}
|
||||||
|
d, err := io.ReadAll(lf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed testing warning")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(d), "DFT2V56H") {
|
||||||
|
t.Error("failed testing warning")
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
CloseLogsFile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebug(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
l := NewLogger(&LogFileDriver{
|
||||||
|
FilePath: path,
|
||||||
|
})
|
||||||
|
l.Debug("DFT2V56H")
|
||||||
|
lf, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed testing debug")
|
||||||
|
}
|
||||||
|
d, err := io.ReadAll(lf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("error testing debug")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(d), "DFT2V56H") {
|
||||||
|
t.Error("error testing debug")
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
CloseLogsFile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), uuid.NewString())
|
||||||
|
l := NewLogger(&LogFileDriver{
|
||||||
|
FilePath: path,
|
||||||
|
})
|
||||||
|
l.Error("DFT2V56H")
|
||||||
|
lf, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed testing error")
|
||||||
|
}
|
||||||
|
d, err := io.ReadAll(lf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("failed testing error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(d), "DFT2V56H") {
|
||||||
|
t.Error("failed testing error")
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
CloseLogsFile()
|
||||||
|
})
|
||||||
|
}
|
183
mailer.go
Normal file
183
mailer.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/harranali/mailing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mailer struct {
|
||||||
|
mailer *mailing.Mailer
|
||||||
|
sender mail.Address
|
||||||
|
receiver mail.Address
|
||||||
|
cc []mail.Address
|
||||||
|
bcc []mail.Address
|
||||||
|
subject string
|
||||||
|
htmlBody string
|
||||||
|
plainText string
|
||||||
|
attachment string
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailAddress struct {
|
||||||
|
Name string // the name can be empty
|
||||||
|
Address string // ex: john@example.com
|
||||||
|
}
|
||||||
|
type EmailAttachment struct {
|
||||||
|
Name string // name of the file
|
||||||
|
Path string // full path to the file
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateMailerWithSMTP() *Mailer {
|
||||||
|
portStr := os.Getenv("SMTP_PORT")
|
||||||
|
if portStr == "" {
|
||||||
|
panic("error reading smtp port env var")
|
||||||
|
}
|
||||||
|
port, err := strconv.ParseInt(portStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error parsing smtp port env var: %v", err))
|
||||||
|
}
|
||||||
|
skipTlsVerifyStr := os.Getenv("SMTP_TLS_SKIP_VERIFY_HOST")
|
||||||
|
if skipTlsVerifyStr == "" {
|
||||||
|
panic("error reading smtp tls verify env var")
|
||||||
|
}
|
||||||
|
skipTlsVerify, err := strconv.ParseBool(skipTlsVerifyStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error parsing smtp tls verify env var: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Mailer{
|
||||||
|
mailer: mailing.NewMailerWithSMTP(&mailing.SMTPConfig{
|
||||||
|
Host: os.Getenv("SMTP_HOST"),
|
||||||
|
Port: int(port),
|
||||||
|
Username: os.Getenv("SMTP_USERNAME"),
|
||||||
|
Password: os.Getenv("SMTP_PASSWORD"),
|
||||||
|
TLSConfig: tls.Config{
|
||||||
|
ServerName: os.Getenv("SMTP_HOST"),
|
||||||
|
InsecureSkipVerify: skipTlsVerify,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateMailerWithSparkPost() *Mailer {
|
||||||
|
apiVersionStr := os.Getenv("SPARKPOST_API_VERSION")
|
||||||
|
if apiVersionStr == "" {
|
||||||
|
panic("error reading sparkpost base url env var")
|
||||||
|
}
|
||||||
|
apiVersion, err := strconv.ParseInt(apiVersionStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error parsing sparkpost base url env var: %v", apiVersion))
|
||||||
|
}
|
||||||
|
return &Mailer{
|
||||||
|
mailer: mailing.NewMailerWithSparkPost(&mailing.SparkPostConfig{
|
||||||
|
BaseUrl: os.Getenv("SPARKPOST_BASE_URL"),
|
||||||
|
ApiKey: os.Getenv("SPARKPOST_API_KEY"),
|
||||||
|
ApiVersion: int(apiVersion),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateMailerWithSendGrid() *Mailer {
|
||||||
|
return &Mailer{
|
||||||
|
mailer: mailing.NewMailerWithSendGrid(&mailing.SendGridConfig{
|
||||||
|
Host: os.Getenv("SENDGRID_HOST"),
|
||||||
|
Endpoint: os.Getenv("SENDGRID_ENDPOINT"),
|
||||||
|
ApiKey: os.Getenv("SENDGRID_API_KEY"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initiateMailerWithMailGun() *Mailer {
|
||||||
|
skipTlsVerifyStr := os.Getenv("MAILGUN_TLS_SKIP_VERIFY_HOST")
|
||||||
|
if skipTlsVerifyStr == "" {
|
||||||
|
panic("error reading mailgun tls verify env var")
|
||||||
|
}
|
||||||
|
skipTlsVerify, err := strconv.ParseBool(skipTlsVerifyStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error parsing mailgun tls verify env var: %v", err))
|
||||||
|
}
|
||||||
|
return &Mailer{
|
||||||
|
mailer: mailing.NewMailerWithMailGun(&mailing.MailGunConfig{
|
||||||
|
Domain: os.Getenv("MAILGUN_DOMAIN"),
|
||||||
|
APIKey: os.Getenv("MAILGUN_API_KEY"),
|
||||||
|
SkipTLSVerification: skipTlsVerify,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetFrom(emailAddresses EmailAddress) *Mailer {
|
||||||
|
e := mailing.EmailAddress{
|
||||||
|
Name: emailAddresses.Name,
|
||||||
|
Address: emailAddresses.Address,
|
||||||
|
}
|
||||||
|
m.mailer.SetFrom(e)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetTo(emailAddresses []EmailAddress) *Mailer {
|
||||||
|
var addressesList []mailing.EmailAddress
|
||||||
|
for _, v := range emailAddresses {
|
||||||
|
addressesList = append(addressesList, mailing.EmailAddress{Name: v.Name, Address: v.Address})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mailer.SetTo(addressesList)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetCC(emailAddresses []EmailAddress) *Mailer {
|
||||||
|
var addressesList []mailing.EmailAddress
|
||||||
|
for _, v := range emailAddresses {
|
||||||
|
addressesList = append(addressesList, mailing.EmailAddress{Name: v.Name, Address: v.Address})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mailer.SetCC(addressesList)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetBCC(emailAddresses []EmailAddress) *Mailer {
|
||||||
|
var addressesList []mailing.EmailAddress
|
||||||
|
for _, v := range emailAddresses {
|
||||||
|
addressesList = append(addressesList, mailing.EmailAddress{Name: v.Name, Address: v.Address})
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mailer.SetBCC(addressesList)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetSubject(subject string) *Mailer {
|
||||||
|
m.mailer.SetSubject(subject)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetHTMLBody(body string) *Mailer {
|
||||||
|
m.mailer.SetHTMLBody(body)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetPlainTextBody(body string) *Mailer {
|
||||||
|
m.mailer.SetPlainTextBody(body)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) SetAttachments(attachments []EmailAttachment) *Mailer {
|
||||||
|
var aList []mailing.Attachment
|
||||||
|
for _, v := range attachments {
|
||||||
|
aList = append(aList, mailing.Attachment{Name: v.Name, Path: v.Path})
|
||||||
|
}
|
||||||
|
m.mailer.SetAttachments(aList)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mailer) Send() error {
|
||||||
|
return m.mailer.Send()
|
||||||
|
}
|
8
middleware.go
Normal file
8
middleware.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
type Middleware func(c *Context)
|
40
middlewares.go
Normal file
40
middlewares.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
type Middlewares struct {
|
||||||
|
middlewares []Middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
var m *Middlewares
|
||||||
|
|
||||||
|
func NewMiddlewares() *Middlewares {
|
||||||
|
m = &Middlewares{}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveMiddlewares() *Middlewares {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Middlewares) Attach(mw Middleware) *Middlewares {
|
||||||
|
m.middlewares = append(m.middlewares, mw)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Middlewares) GetMiddlewares() []Middleware {
|
||||||
|
return m.middlewares
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Middlewares) getByIndex(i int) Middleware {
|
||||||
|
for k := range m.middlewares {
|
||||||
|
if k == i {
|
||||||
|
return m.middlewares[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
65
middlewares_test.go
Normal file
65
middlewares_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewMiddlewares(t *testing.T) {
|
||||||
|
mw := NewMiddlewares()
|
||||||
|
if fmt.Sprintf("%T", mw) != "*core.Middlewares" {
|
||||||
|
t.Errorf("failed testing new middleware")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResloveMiddleWares(t *testing.T) {
|
||||||
|
NewMiddlewares()
|
||||||
|
mw := ResolveMiddlewares()
|
||||||
|
if fmt.Sprintf("%T", mw) != "*core.Middlewares" {
|
||||||
|
t.Errorf("failed resolve middlewares")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttach(t *testing.T) {
|
||||||
|
mw := NewMiddlewares()
|
||||||
|
tmw := Middleware(func(c *Context) {
|
||||||
|
c.GetLogger().Info("Testing!")
|
||||||
|
})
|
||||||
|
mw.Attach(tmw)
|
||||||
|
mws := mw.getByIndex(0)
|
||||||
|
if reflect.ValueOf(tmw).Pointer() != reflect.ValueOf(mws).Pointer() {
|
||||||
|
t.Errorf("Failed testing attach middleware")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMiddleWares(t *testing.T) {
|
||||||
|
mw := NewMiddlewares()
|
||||||
|
t1 := Middleware(func(c *Context) {
|
||||||
|
c.GetLogger().Info("testing1!")
|
||||||
|
})
|
||||||
|
t2 := Middleware(func(c *Context) {
|
||||||
|
c.GetLogger().Info("testing2!")
|
||||||
|
})
|
||||||
|
mw.Attach(t1)
|
||||||
|
mw.Attach(t2)
|
||||||
|
if len(mw.GetMiddlewares()) != 2 {
|
||||||
|
t.Errorf("failed testing get middlewares")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareGetByIndex(t *testing.T) {
|
||||||
|
mw := NewMiddlewares()
|
||||||
|
t1 := Middleware(func(c *Context) {
|
||||||
|
c.GetLogger().Info("testing!")
|
||||||
|
})
|
||||||
|
mw.Attach(t1)
|
||||||
|
if reflect.ValueOf(mw.getByIndex(0)).Pointer() != reflect.ValueOf(t1).Pointer() {
|
||||||
|
t.Errorf("failed testing get by index")
|
||||||
|
}
|
||||||
|
}
|
17
request.go
Normal file
17
request.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
httpRequest *http.Request
|
||||||
|
httpPathParams httprouter.Params
|
||||||
|
}
|
183
response.go
Normal file
183
response.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
headers []header
|
||||||
|
body []byte
|
||||||
|
statusCode int
|
||||||
|
contentType string
|
||||||
|
overrideContentType string
|
||||||
|
isTerminated bool
|
||||||
|
redirectTo string
|
||||||
|
HttpResponseWriter http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
key string
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) Any(body any) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
rs.contentType = CONTENT_TYPE_HTML
|
||||||
|
rs.body = []byte(rs.castBasicVarsToString(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) Json(body string) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
rs.contentType = CONTENT_TYPE_JSON
|
||||||
|
rs.body = []byte(body)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) Text(body string) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
rs.contentType = CONTENT_TYPE_TEXT
|
||||||
|
rs.body = []byte(body)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) HTML(body string) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
rs.contentType = CONTENT_TYPE_HTML
|
||||||
|
rs.body = []byte(body)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) SetStatusCode(code int) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
rs.statusCode = code
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) SetContentType(c string) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
rs.overrideContentType = c
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add doc
|
||||||
|
func (rs *Response) SetHeader(key string, val string) *Response {
|
||||||
|
if rs.isTerminated == false {
|
||||||
|
h := header{
|
||||||
|
key: key,
|
||||||
|
val: val,
|
||||||
|
}
|
||||||
|
rs.headers = append(rs.headers, h)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Response) ForceSendResponse() {
|
||||||
|
rs.isTerminated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Response) Redirect(url string) *Response {
|
||||||
|
validator := resolveValidator()
|
||||||
|
v := validator.Validate(map[string]interface{}{
|
||||||
|
"url": url,
|
||||||
|
}, map[string]interface{}{
|
||||||
|
"url": "url",
|
||||||
|
})
|
||||||
|
if v.Failed() {
|
||||||
|
if url[0:1] != "/" {
|
||||||
|
rs.redirectTo = "/" + url
|
||||||
|
} else {
|
||||||
|
rs.redirectTo = url
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
rs.redirectTo = url
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Response) castBasicVarsToString(data interface{}) string {
|
||||||
|
switch dataType := data.(type) {
|
||||||
|
case string:
|
||||||
|
return fmt.Sprintf("%v", data)
|
||||||
|
case []byte:
|
||||||
|
d := data.(string)
|
||||||
|
return fmt.Sprintf("%v", d)
|
||||||
|
case int:
|
||||||
|
intVar, _ := data.(int)
|
||||||
|
return fmt.Sprintf("%v", intVar)
|
||||||
|
case int8:
|
||||||
|
int8Var := data.(int8)
|
||||||
|
return fmt.Sprintf("%v", int8Var)
|
||||||
|
case int16:
|
||||||
|
int16Var := data.(int16)
|
||||||
|
return fmt.Sprintf("%v", int16Var)
|
||||||
|
case int32:
|
||||||
|
int32Var := data.(int32)
|
||||||
|
return fmt.Sprintf("%v", int32Var)
|
||||||
|
case int64:
|
||||||
|
int64Var := data.(int64)
|
||||||
|
return fmt.Sprintf("%v", int64Var)
|
||||||
|
case uint:
|
||||||
|
uintVar, _ := data.(uint)
|
||||||
|
return fmt.Sprintf("%v", uintVar)
|
||||||
|
case uint8:
|
||||||
|
uint8Var := data.(uint8)
|
||||||
|
return fmt.Sprintf("%v", uint8Var)
|
||||||
|
case uint16:
|
||||||
|
uint16Var := data.(uint16)
|
||||||
|
return fmt.Sprintf("%v", uint16Var)
|
||||||
|
case uint32:
|
||||||
|
uint32Var := data.(uint32)
|
||||||
|
return fmt.Sprintf("%v", uint32Var)
|
||||||
|
case uint64:
|
||||||
|
uint64Var := data.(uint64)
|
||||||
|
return fmt.Sprintf("%v", uint64Var)
|
||||||
|
case float32:
|
||||||
|
float32Var := data.(float32)
|
||||||
|
return fmt.Sprintf("%v", float32Var)
|
||||||
|
case float64:
|
||||||
|
float64Var := data.(float64)
|
||||||
|
return fmt.Sprintf("%v", float64Var)
|
||||||
|
case complex64:
|
||||||
|
complex64Var := data.(complex64)
|
||||||
|
return fmt.Sprintf("%v", complex64Var)
|
||||||
|
case complex128:
|
||||||
|
complex128Var := data.(complex128)
|
||||||
|
return fmt.Sprintf("%v", complex128Var)
|
||||||
|
case bool:
|
||||||
|
boolVar := data.(bool)
|
||||||
|
return fmt.Sprintf("%v", boolVar)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported response data type %v!", dataType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *Response) reset() {
|
||||||
|
rs.body = nil
|
||||||
|
rs.statusCode = http.StatusOK
|
||||||
|
rs.contentType = CONTENT_TYPE_HTML
|
||||||
|
rs.overrideContentType = ""
|
||||||
|
rs.isTerminated = false
|
||||||
|
rs.redirectTo = ""
|
||||||
|
}
|
154
response_test.go
Normal file
154
response_test.go
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
res := Response{}
|
||||||
|
v := "test-text"
|
||||||
|
res.Any(v)
|
||||||
|
|
||||||
|
if string(res.body) != v {
|
||||||
|
t.Errorf("failed writing text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteJson(t *testing.T) {
|
||||||
|
res := Response{}
|
||||||
|
j := "{\"name\": \"test\"}"
|
||||||
|
res.Json(j)
|
||||||
|
|
||||||
|
if string(res.body) != j {
|
||||||
|
t.Errorf("failed wrting jsom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetHeaders(t *testing.T) {
|
||||||
|
res := Response{}
|
||||||
|
res.SetHeader("testkey", "testval")
|
||||||
|
|
||||||
|
headers := res.headers
|
||||||
|
if len(headers) < 1 {
|
||||||
|
t.Errorf("testing set header failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReset(t *testing.T) {
|
||||||
|
res := Response{}
|
||||||
|
res.Any("test text")
|
||||||
|
if res.body == nil {
|
||||||
|
t.Errorf("expecting body to not be empty, found empty")
|
||||||
|
}
|
||||||
|
j := "{\"name\": \"test\"}"
|
||||||
|
res.Json(j)
|
||||||
|
if string(res.body) == "" {
|
||||||
|
t.Errorf("expecting JsonBody to not be empty, found empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.reset()
|
||||||
|
|
||||||
|
if !(res.body == nil && string(res.body) == "") {
|
||||||
|
t.Errorf("failed testing response reset()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCastBasicVarToString(t *testing.T) {
|
||||||
|
s := "test str"
|
||||||
|
r := Response{}
|
||||||
|
c := r.castBasicVarsToString(s)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var i int = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(i)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var i8 int8 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(i8)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var i16 int16 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(i16)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var i32 int32 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(i32)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var i64 int64 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(i64)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var ui uint = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(ui)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var ui8 uint8 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(ui8)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var ui16 uint16 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(ui16)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var ui32 uint32 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(ui32)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var ui64 uint64 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(ui64)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var f32 float32 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(f32)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var f64 float64 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(f64)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var c64 complex64 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(c64)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var c128 complex128 = 3
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(c128)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
var b bool = true
|
||||||
|
r = Response{}
|
||||||
|
c = r.castBasicVarsToString(b)
|
||||||
|
if fmt.Sprintf("%T", c) != "string" {
|
||||||
|
t.Errorf("failed test cast basic var to string")
|
||||||
|
}
|
||||||
|
}
|
104
router.go
Normal file
104
router.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Handler Handler
|
||||||
|
Middlewares []Middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
Routes []Route
|
||||||
|
}
|
||||||
|
|
||||||
|
var router *Router
|
||||||
|
|
||||||
|
func NewRouter() *Router {
|
||||||
|
router = &Router{
|
||||||
|
[]Route{},
|
||||||
|
}
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveRouter() *Router {
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Get(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: GET,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Post(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: POST,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Delete(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: DELETE,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Patch(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: PATCH,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Put(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: PUT,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Options(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: OPTIONS,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Head(path string, handler Handler, middlewares ...Middleware) *Router {
|
||||||
|
r.Routes = append(r.Routes, Route{
|
||||||
|
Method: HEAD,
|
||||||
|
Path: path,
|
||||||
|
Handler: handler,
|
||||||
|
Middlewares: middlewares,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) GetRoutes() []Route {
|
||||||
|
return r.Routes
|
||||||
|
}
|
126
router_test.go
Normal file
126
router_test.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
// Copyright 2021 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||||
|
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||||
|
// Use of this source code is governed by MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRouter(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
|
||||||
|
if fmt.Sprintf("%T", r) != "*core.Router" {
|
||||||
|
t.Error("failed asserting initiation of new router")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveRouter(t *testing.T) {
|
||||||
|
r := ResolveRouter()
|
||||||
|
if fmt.Sprintf("%T", r) != "*core.Router" {
|
||||||
|
t.Error("failed resolve router variable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRequest(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
handler := Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.Get("/", handler)
|
||||||
|
|
||||||
|
route := r.GetRoutes()[0]
|
||||||
|
if route.Method != "get" || route.Path != "/" {
|
||||||
|
t.Errorf("failed adding route with get http method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostRequest(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
handler := Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.Post("/", handler)
|
||||||
|
|
||||||
|
route := r.GetRoutes()[0]
|
||||||
|
if route.Method != "post" || route.Path != "/" {
|
||||||
|
t.Errorf("failed adding route with post http method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteRequest(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
handler := Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.Delete("/", handler)
|
||||||
|
|
||||||
|
route := r.GetRoutes()[0]
|
||||||
|
if route.Method != "delete" || route.Path != "/" {
|
||||||
|
t.Errorf("failed adding route with delete http method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutRequest(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
handler := Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.Put("/", handler)
|
||||||
|
|
||||||
|
route := r.GetRoutes()[0]
|
||||||
|
if route.Method != "put" || route.Path != "/" {
|
||||||
|
t.Errorf("failed adding route with put http method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionsRequest(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
handler := Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.Options("/", handler)
|
||||||
|
|
||||||
|
route := r.GetRoutes()[0]
|
||||||
|
if route.Method != "options" || route.Path != "/" {
|
||||||
|
t.Errorf("failed adding route with options http method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeadRequest(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
handler := Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
r.Head("/", handler)
|
||||||
|
|
||||||
|
route := r.GetRoutes()[0]
|
||||||
|
if route.Method != "head" || route.Path != "/" {
|
||||||
|
t.Errorf("failed adding route with head http method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddMultipleRoutes(t *testing.T) {
|
||||||
|
r := NewRouter()
|
||||||
|
r.Get("/", Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
r.Post("/", Handler(func(c *Context) *Response {
|
||||||
|
c.GetLogger().Info(TEST_STR)
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
if len(r.GetRoutes()) != 2 {
|
||||||
|
t.Errorf("failed getting added routes")
|
||||||
|
}
|
||||||
|
}
|
5
testing-config.go
Normal file
5
testing-config.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
var testingRequestC = RequestConfig{
|
||||||
|
MaxUploadFileSize: 20000000, // 20MB
|
||||||
|
}
|
10
testingdata/.env
Normal file
10
testingdata/.env
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#################################
|
||||||
|
### Env Vars ###
|
||||||
|
#################################
|
||||||
|
KEY_ONE=VAL_ONE
|
||||||
|
KEY_TWO=VAL_TWO
|
||||||
|
|
||||||
|
|
||||||
|
# SQLITE
|
||||||
|
SQLITE_DB=testingdata/db.sqlite
|
||||||
|
|
0
testingdata/db.sqlite
Normal file
0
testingdata/db.sqlite
Normal file
3
testingdata/testdata.json
Normal file
3
testingdata/testdata.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"testKey": "testVal"
|
||||||
|
}
|
1
testingdata/totestcopyfile.md
Normal file
1
testingdata/totestcopyfile.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
test copy file
|
1
testingdata/totestmovefile.md
Normal file
1
testingdata/totestmovefile.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
test move file
|
242
validator.go
Normal file
242
validator.go
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
validation "github.com/go-ozzo/ozzo-validation"
|
||||||
|
"github.com/go-ozzo/ozzo-validation/is"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Validator struct{}
|
||||||
|
type validationResult struct {
|
||||||
|
hasFailed bool
|
||||||
|
errorMessages map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var vr validationResult
|
||||||
|
var v *Validator
|
||||||
|
|
||||||
|
func newValidator() *Validator {
|
||||||
|
v := &Validator{}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveValidator() *Validator {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) Validate(data map[string]interface{}, rules map[string]interface{}) validationResult {
|
||||||
|
vr = validationResult{}
|
||||||
|
vr.hasFailed = false
|
||||||
|
res := map[string]string{}
|
||||||
|
for key, val := range data {
|
||||||
|
_, ok := rules[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rls, err := parseRules(rules[key])
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
err = validation.Validate(val, rls...)
|
||||||
|
if err != nil {
|
||||||
|
res[key] = fmt.Sprintf("%v: %v", key, err.Error())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(res) != 0 {
|
||||||
|
vr.hasFailed = true
|
||||||
|
vr.errorMessages = res
|
||||||
|
}
|
||||||
|
return vr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vr *validationResult) Failed() bool {
|
||||||
|
return vr.hasFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vr *validationResult) GetErrorMessagesMap() map[string]string {
|
||||||
|
return vr.errorMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vr *validationResult) GetErrorMessagesJson() string {
|
||||||
|
j, err := json.Marshal(vr.GetErrorMessagesMap())
|
||||||
|
if err != nil {
|
||||||
|
panic("error converting validation error messages to json")
|
||||||
|
}
|
||||||
|
return string(j)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRules(rawRules interface{}) ([]validation.Rule, error) {
|
||||||
|
var res []validation.Rule
|
||||||
|
rulesStr, ok := rawRules.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid validation rule")
|
||||||
|
}
|
||||||
|
rules := strings.Split(rulesStr, "|")
|
||||||
|
for _, rule := range rules {
|
||||||
|
rule = strings.TrimSpace(rule)
|
||||||
|
r, err := getRule(rule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, r)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRule(rule string) (validation.Rule, error) {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(rule, "max:"):
|
||||||
|
return getRuleMax(rule)
|
||||||
|
case strings.Contains(rule, "min:"):
|
||||||
|
return getRuleMin(rule)
|
||||||
|
case strings.Contains(rule, "in:"):
|
||||||
|
return getRuleIn(rule)
|
||||||
|
case strings.Contains(rule, "dateLayout:"):
|
||||||
|
return getRuleDateLayout(rule)
|
||||||
|
case strings.Contains(rule, "length:"):
|
||||||
|
return getRuleLength(rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rule {
|
||||||
|
case "required":
|
||||||
|
return validation.Required, nil
|
||||||
|
case "email":
|
||||||
|
return is.Email, nil
|
||||||
|
case "url":
|
||||||
|
return is.URL, nil
|
||||||
|
case "alpha":
|
||||||
|
return is.Alpha, nil
|
||||||
|
case "digit":
|
||||||
|
return is.Digit, nil
|
||||||
|
case "alphaNumeric":
|
||||||
|
return is.Alphanumeric, nil
|
||||||
|
case "lowerCase":
|
||||||
|
return is.LowerCase, nil
|
||||||
|
case "upperCase":
|
||||||
|
return is.UpperCase, nil
|
||||||
|
case "int":
|
||||||
|
return is.Int, nil
|
||||||
|
case "float":
|
||||||
|
return is.Float, nil
|
||||||
|
case "uuid":
|
||||||
|
return is.UUID, nil
|
||||||
|
case "creditCard":
|
||||||
|
return is.CreditCard, nil
|
||||||
|
case "json":
|
||||||
|
return is.JSON, nil
|
||||||
|
case "base64":
|
||||||
|
return is.Base64, nil
|
||||||
|
case "countryCode2":
|
||||||
|
return is.CountryCode2, nil
|
||||||
|
case "countryCode3":
|
||||||
|
return is.CountryCode3, nil
|
||||||
|
case "isoCurrencyCode":
|
||||||
|
return is.CurrencyCode, nil
|
||||||
|
case "mac":
|
||||||
|
return is.MAC, nil
|
||||||
|
case "ip":
|
||||||
|
return is.IP, nil
|
||||||
|
case "ipv4":
|
||||||
|
return is.IPv4, nil
|
||||||
|
case "ipv6":
|
||||||
|
return is.IPv6, nil
|
||||||
|
case "subdomain":
|
||||||
|
return is.Subdomain, nil
|
||||||
|
case "domain":
|
||||||
|
return is.Domain, nil
|
||||||
|
case "dnsName":
|
||||||
|
return is.DNSName, nil
|
||||||
|
case "host":
|
||||||
|
return is.Host, nil
|
||||||
|
case "port":
|
||||||
|
return is.Port, nil
|
||||||
|
case "mongoID":
|
||||||
|
return is.MongoID, nil
|
||||||
|
case "latitude":
|
||||||
|
return is.Latitude, nil
|
||||||
|
case "longitude":
|
||||||
|
return is.Longitude, nil
|
||||||
|
case "ssn":
|
||||||
|
return is.SSN, nil
|
||||||
|
case "semver":
|
||||||
|
return is.Semver, nil
|
||||||
|
default:
|
||||||
|
err := errors.New(fmt.Sprintf("invalid validation rule: %v", rule))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRuleMax(rule string) (validation.Rule, error) {
|
||||||
|
// max: 44
|
||||||
|
rr := strings.ReplaceAll(rule, "max:", "")
|
||||||
|
m := strings.TrimSpace(rr)
|
||||||
|
n, err := strconv.ParseInt(m, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err := errors.New("invalid value for validation rule 'max'")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return validation.Max(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRuleMin(rule string) (validation.Rule, error) {
|
||||||
|
// min: 33
|
||||||
|
rr := strings.ReplaceAll(rule, "min:", "")
|
||||||
|
m := strings.TrimSpace(rr)
|
||||||
|
n, err := strconv.ParseInt(m, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err := errors.New("invalid value for validation rule 'min'")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return validation.Min(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRuleIn(rule string) (validation.Rule, error) {
|
||||||
|
// in: first, second, third
|
||||||
|
var readyElms []interface{}
|
||||||
|
rr := strings.ReplaceAll(rule, "in:", "")
|
||||||
|
elms := strings.Split(rr, ",")
|
||||||
|
for _, elm := range elms {
|
||||||
|
readyElms = append(readyElms, strings.TrimSpace(elm))
|
||||||
|
}
|
||||||
|
return validation.In(readyElms...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// example date layouts: https://programming.guide/go/format-parse-string-time-date-example.html
|
||||||
|
func getRuleDateLayout(rule string) (validation.Rule, error) {
|
||||||
|
// dateLayout: 02 January 2006
|
||||||
|
rr := rule
|
||||||
|
rr = strings.TrimSpace(strings.Replace(rr, "dateLayout:", "", -1))
|
||||||
|
return validation.Date(rr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRuleLength(rule string) (validation.Rule, error) {
|
||||||
|
// length: 3, 7
|
||||||
|
rr := rule
|
||||||
|
rr = strings.Replace(rr, "length:", "", -1)
|
||||||
|
lengthRange := strings.Split(rr, ",")
|
||||||
|
if len(lengthRange) < 0 {
|
||||||
|
err := errors.New("min value is not set for validation rule 'length'")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
min, err := strconv.Atoi(strings.TrimSpace(lengthRange[0]))
|
||||||
|
if err != nil {
|
||||||
|
err := errors.New("min value is not set for validation rule 'length'")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(lengthRange) < 1 {
|
||||||
|
err := errors.New("max value is not set for validation rule 'length'")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
max, err := strconv.Atoi(strings.TrimSpace(lengthRange[1]))
|
||||||
|
if err != nil {
|
||||||
|
err := errors.New("max value is not set for validation rule 'length'")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return validation.Length(min, max), nil
|
||||||
|
}
|
358
validator_test.go
Normal file
358
validator_test.go
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ruleTestData struct {
|
||||||
|
ruleName string
|
||||||
|
correctValue interface{}
|
||||||
|
correctValueExpectedResult interface{}
|
||||||
|
incorrectValue interface{}
|
||||||
|
incorrectValueExpectedResult error
|
||||||
|
}
|
||||||
|
|
||||||
|
type rulesTestData []ruleTestData
|
||||||
|
|
||||||
|
var rulesTestDataList = []ruleTestData{
|
||||||
|
{
|
||||||
|
ruleName: "required",
|
||||||
|
correctValue: gofakeit.Name(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "email",
|
||||||
|
correctValue: gofakeit.Email(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "test@mailcom",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "url",
|
||||||
|
correctValue: gofakeit.URL(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "http:/githubcom/goffe/ggoffeor",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "alpha",
|
||||||
|
correctValue: gofakeit.LoremIpsumWord(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "test232",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "digit",
|
||||||
|
correctValue: gofakeit.Digit(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "d",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "alphaNumeric",
|
||||||
|
correctValue: "abc3",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "!",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "lowerCase",
|
||||||
|
correctValue: "abc",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ABC",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "upperCase",
|
||||||
|
correctValue: "ABC",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "abc",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "int",
|
||||||
|
correctValue: "343",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "342.3",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "float",
|
||||||
|
correctValue: "433.5",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: gofakeit.LoremIpsumWord(),
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "uuid",
|
||||||
|
correctValue: uuid.NewString(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: gofakeit.LoremIpsumWord(),
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "creditCard",
|
||||||
|
correctValue: "4242 4242 4242 4242",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "dd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "json",
|
||||||
|
correctValue: "{\"testKEy\": \"testVal\"}",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "dd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "base64",
|
||||||
|
correctValue: "+rxVsR0pD0DU4XO4MZbXXg==",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "dd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "countryCode2",
|
||||||
|
correctValue: "SD",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "countryCode3",
|
||||||
|
correctValue: "SDN",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "isoCurrencyCode",
|
||||||
|
correctValue: "USD",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "mac",
|
||||||
|
correctValue: gofakeit.MacAddress(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "ip",
|
||||||
|
correctValue: gofakeit.IPv4Address(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "ipv4",
|
||||||
|
correctValue: gofakeit.IPv4Address(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "ipv6",
|
||||||
|
correctValue: gofakeit.IPv6Address(),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "domain",
|
||||||
|
correctValue: "site.com",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "latitude",
|
||||||
|
correctValue: fmt.Sprintf("%v", gofakeit.Latitude()),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "longitude",
|
||||||
|
correctValue: fmt.Sprintf("%v", gofakeit.Longitude()),
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleName: "semver",
|
||||||
|
correctValue: "3.2.4",
|
||||||
|
correctValueExpectedResult: nil,
|
||||||
|
incorrectValue: "ddd",
|
||||||
|
incorrectValueExpectedResult: errors.New("mock error"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorValidate(t *testing.T) {
|
||||||
|
validator := newValidator()
|
||||||
|
v := validator.Validate(map[string]interface{}{
|
||||||
|
"name": gofakeit.LoremIpsumWord(),
|
||||||
|
"link": gofakeit.URL(),
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "required|alphaNumeric",
|
||||||
|
"link": "required|url",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if v.Failed() {
|
||||||
|
t.Errorf("erro testing validator validate: '%v'", v.GetErrorMessagesJson())
|
||||||
|
}
|
||||||
|
v = validator.Validate(map[string]interface{}{
|
||||||
|
"name": "",
|
||||||
|
"link": gofakeit.URL(),
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "required|alphaNumeric",
|
||||||
|
"link": "required|url",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !v.Failed() {
|
||||||
|
t.Errorf("erro testing validator validate")
|
||||||
|
}
|
||||||
|
msgsMap := v.GetErrorMessagesMap()
|
||||||
|
if !strings.Contains(msgsMap["name"], "cannot be blank") {
|
||||||
|
t.Errorf("erro testing validator validate")
|
||||||
|
}
|
||||||
|
msgsJson := v.GetErrorMessagesJson()
|
||||||
|
var masgsMapOfJ map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(msgsJson), &masgsMapOfJ)
|
||||||
|
sval, _ := masgsMapOfJ["name"].(string)
|
||||||
|
if !strings.Contains(sval, "cannot be blank") {
|
||||||
|
t.Errorf("erro testing validator validate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorParseRules(t *testing.T) {
|
||||||
|
_, err := parseRules("344")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed testing validation parse rules")
|
||||||
|
}
|
||||||
|
rules, err := parseRules("required| min: 3|length: 3, 5")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation parse rules: '%v'", err.Error())
|
||||||
|
}
|
||||||
|
if len(rules) != 3 {
|
||||||
|
t.Errorf("failed testing validation parse rules")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorGetRule(t *testing.T) {
|
||||||
|
for _, td := range rulesTestDataList {
|
||||||
|
r, err := getRule(td.ruleName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule '%v'", td.ruleName)
|
||||||
|
}
|
||||||
|
err = r.Validate(td.correctValue)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule '%v'", td.ruleName)
|
||||||
|
}
|
||||||
|
err = r.Validate(td.incorrectValue)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed testing validation rule %v", td.ruleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := getRule("unknownrule")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed testing validation rule")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorGetRuleMax(t *testing.T) {
|
||||||
|
r, err := getRuleMax("max: 33")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'max'")
|
||||||
|
}
|
||||||
|
err = r.Validate(30)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'max': %v", err.Error())
|
||||||
|
}
|
||||||
|
err = r.Validate(40)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed testing validation rule 'max'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorGetRuleMin(t *testing.T) {
|
||||||
|
r, err := getRuleMin("min: 33")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'min'")
|
||||||
|
}
|
||||||
|
err = r.Validate(34)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'min': %v", err.Error())
|
||||||
|
}
|
||||||
|
err = r.Validate(3)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed testing validation rule 'min'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorGetRuleIn(t *testing.T) {
|
||||||
|
r, err := getRuleIn("in: a, b, c")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'in'")
|
||||||
|
}
|
||||||
|
err = r.Validate("a")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'in': %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetValidationRuleDateLayout(t *testing.T) {
|
||||||
|
r, err := getRuleDateLayout("dateLayout: 02 January 2006")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'dateLayout'")
|
||||||
|
}
|
||||||
|
err = r.Validate("02 May 2023")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed testing validation rule 'dateLayout': %v", err.Error())
|
||||||
|
}
|
||||||
|
err = r.Validate("02-04-2023")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed testing validation rule 'dateLayout'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatorGetRuleLenth(t *testing.T) {
|
||||||
|
r, err := getRuleLength("length: 3, 5")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test validation rule 'length': %v", err.Error())
|
||||||
|
}
|
||||||
|
err = r.Validate("123")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed test validation rule 'length': %v", err.Error())
|
||||||
|
}
|
||||||
|
err = r.Validate("12")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed test validation rule 'length'")
|
||||||
|
}
|
||||||
|
err = r.Validate("123456")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed test validation rule 'length'")
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err = getRuleLength("length: 3dd, 5")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed test validation rule 'length'")
|
||||||
|
}
|
||||||
|
r, err = getRuleLength("length: 3, 5dd")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("failed test validation rule 'length'")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue