Skip to content

Commit

Permalink
feat: url redirection, add tag validation
Browse files Browse the repository at this point in the history
  • Loading branch information
call3hudson committed Dec 20, 2023
1 parent f74cb9b commit 084735f
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 4 deletions.
39 changes: 39 additions & 0 deletions http/handling/alias_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package handling

import (
"fmt"
"net/http"
"platform/http/actionresults"
"platform/services"
"reflect"
"regexp"
)

func (rc *RouterComponent) AddMethodAlias(srcUrl string, method interface{}, data ...interface{}) *RouterComponent {
var urlgen URLGenerator
services.GetService(&urlgen)
url, err := urlgen.GenerateUrl(method, data...)
if err == nil {
return rc.AddUrlAlias(srcUrl, url)
} else {
panic(err)
}
}

func (rc *RouterComponent) AddUrlAlias(srcUrl, targetUrl string) *RouterComponent {
aliasFunc := func(interface{}) actionresults.ActionResult {
return actionresults.NewRedirectAction(targetUrl)
}
alias := Route{
httpMethod: http.MethodGet,
handlerName: "Alias",
actionName: "Redirect",
expression: *regexp.MustCompile(fmt.Sprintf("^%v[/]?$", srcUrl)),
handlerMethod: reflect.Method{
Type: reflect.TypeOf(aliasFunc),
Func: reflect.ValueOf(aliasFunc),
},
}
rc.routes = append([]Route{alias}, rc.routes...)
return rc
}
13 changes: 12 additions & 1 deletion http/handling/request_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ import (
)

func NewRouter(handlers ...HandlerEntry) *RouterComponent {
return &RouterComponent{generateRoutes(handlers...)}
routes := generateRoutes(handlers...)

var urlGen URLGenerator
services.GetService(&urlGen)
if urlGen == nil {
services.AddSingleton(func() URLGenerator {
return &routeUrlGenerator{routes: routes}
})
} else {
urlGen.AddRoutes(routes)
}
return &RouterComponent{routes: routes}
}

type RouterComponent struct {
Expand Down
69 changes: 69 additions & 0 deletions http/handling/url_generation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package handling

import (
"errors"
"fmt"
"net/http"
"reflect"
"strings"
)

type URLGenerator interface {
GenerateUrl(method interface{}, data ...interface{}) (string, error)
GenerateURLByName(handlerName, methodName string, data ...interface{}) (string, error)
AddRoutes(routes []Route)
}

type routeUrlGenerator struct {
routes []Route
}

func (gen *routeUrlGenerator) AddRoutes(routes []Route) {
if gen.routes == nil {
gen.routes = routes
} else {
gen.routes = append(gen.routes, routes...)
}
}

func (gen *routeUrlGenerator) GenerateUrl(method interface{}, data ...interface{}) (string, error) {
methodVal := reflect.ValueOf(method)
if methodVal.Kind() == reflect.Func && methodVal.Type().In(0).Kind() == reflect.Struct {
for _, route := range gen.routes {
if route.handlerMethod.Func.Pointer() == methodVal.Pointer() {
return generateUrl(route, data...)
}
}
}
return "", errors.New("No matching route")
}

func (gen *routeUrlGenerator) GenerateURLByName(handlerName, methodName string, data ...interface{}) (string, error) {
for _, route := range gen.routes {
if strings.EqualFold(route.handlerName, handlerName) && strings.EqualFold(route.httpMethod+route.actionName, methodName) {
return generateUrl(route, data...)
}
}
return "", errors.New("No matching route")
}

func generateUrl(route Route, data ...interface{}) (url string, err error) {
url = "/" + route.prefix
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
if !strings.HasSuffix(url, "/") {
url += "/"
}
url += strings.ToLower(route.actionName)
if len(data) > 0 && !strings.EqualFold(route.httpMethod, http.MethodGet) {
err = errors.New("Only GET handler can have data values")
} else if strings.EqualFold(route.httpMethod, http.MethodGet) && len(data) != route.handlerMethod.Type.NumIn()-1 {
err = errors.New("Number of data values doesn't match method params")
} else {
for _, val := range data {
url = fmt.Sprintf("%v/%v", url, val)
}
}
return
}
15 changes: 15 additions & 0 deletions placeholder/name_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{ layout "layout.html" }}

<form method="POST" action="{{ . }}">
<div style="padding: 5px;">
<label>Name:</label>
<input name="name" />
</div>
<div style="padding: 5px;">
<label>Insert At Front:</label>
<input name="insertatstart" type="checkbox" value="true" />
</div>
<div style="padding: 5px;">
<button type="submit">Submit</button>
</div>
</form>
29 changes: 27 additions & 2 deletions placeholder/name_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package placeholder
import (
"fmt"
"platform/http/actionresults"
"platform/http/handling"
"platform/logging"
"platform/validation"
)

var names = []string{"Alice", "Bob", "Charlie", "Dora"}

type NameHandler struct {
logging.Logger
handling.URLGenerator
validation.Validator
}

func (n NameHandler) GetName(i int) actionresults.ActionResult {
Expand All @@ -29,20 +33,41 @@ func (n NameHandler) GetNames() actionresults.ActionResult {
}

type NewName struct {
Name string
Name string `validation:"required,min:3"`
InsertAtStart bool
}

func (n NameHandler) GetForm() actionresults.ActionResult {
postUrl, _ := n.URLGenerator.GenerateUrl(NameHandler.PostName)
return actionresults.NewTemplateAction("name_form.html", postUrl)
}

func (n NameHandler) PostName(new NewName) actionresults.ActionResult {
n.Logger.Debugf("PostName method invoked with argument %v", new)
if ok, errs := n.Validator.Validate(&new); !ok {
return actionresults.NewTemplateAction("validation_errors.html", errs)
}
if new.InsertAtStart {
names = append([]string{new.Name}, names...)
} else {
names = append(names, new.Name)
}
return actionresults.NewRedirectAction("/names")
return n.redirectOrError(NameHandler.GetNames)
}

func (n NameHandler) GetRedirect() actionresults.ActionResult {
return n.redirectOrError(NameHandler.GetNames)
}

func (n NameHandler) GetJsonData() actionresults.ActionResult {
return actionresults.NewJsonAction(names)
}

func (n NameHandler) redirectOrError(handler interface{}, data ...interface{}) actionresults.ActionResult {
url, err := n.GenerateUrl(handler)
if err == nil {
return actionresults.NewRedirectAction(url)
} else {
return actionresults.NewErrorAction(err)
}
}
2 changes: 1 addition & 1 deletion placeholder/startup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func createPipeline() pipeline.RequestPipeline {
handling.NewRouter(
handling.HandlerEntry{Prefix: "", Handler: NameHandler{}},
handling.HandlerEntry{Prefix: "", Handler: DayHandler{}},
),
).AddMethodAlias("/", NameHandler.GetNames),
)
}

Expand Down
9 changes: 9 additions & 0 deletions placeholder/validation_errors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{ layout "layout.html" }}

<h3>Validation Errors</h3>

<ul>
{{ range . }}
<li>{{.FieldName}}: {{ .Error }}</li>
{{ end }}
</ul>
7 changes: 7 additions & 0 deletions services/service_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"platform/config"
"platform/logging"
"platform/templates"
"platform/validation"
)

func RegisterDefaultServices() {
Expand All @@ -30,6 +31,12 @@ func RegisterDefaultServices() {
templates.LoadTemplates(c)
return &templates.LayoutTemplateProcessor{}
})

err = AddSingleton(
func() validation.Validator {
return validation.NewDefaultValidator(validation.DefaultValidators())
})

if err != nil {
panic(err)
}
Expand Down
54 changes: 54 additions & 0 deletions validation/tag_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package validation

import (
"reflect"
"strings"
)

func NewDefaultValidator(validators map[string]ValidatorFunc) Validator {
return &TagValidator{DefaultValidators()}
}

type TagValidator struct {
validators map[string]ValidatorFunc
}

func (tv *TagValidator) Validate(data interface{}) (ok bool, errs []ValidationError) {
errs = []ValidationError{}
dataVal := reflect.ValueOf(data)
if dataVal.Kind() == reflect.Ptr {
dataVal = dataVal.Elem()
}
if dataVal.Kind() != reflect.Struct {
panic("Only structs can be validated")
}
for i := 0; i < dataVal.NumField(); i++ {
fieldType := dataVal.Type().Field(i)
validationTag, found := fieldType.Tag.Lookup("validation")
if found {
for _, v := range strings.Split(validationTag, ",") {
var name, arg string = "", ""
if strings.Contains(v, ":") {
nameAndArgs := strings.SplitN(v, ":", 2)
name = nameAndArgs[0]
arg = nameAndArgs[1]
} else {
name = v
}
if validator, ok := tv.validators[name]; ok {
valid, err := validator(fieldType.Name, dataVal.Field(i).Interface(), arg)
if !valid {
errs = append(errs, ValidationError{
FieldName: fieldType.Name,
Error: err,
})
}
} else {
panic("Unknown validator: " + name)
}
}
}
}
ok = len(errs) == 0
return
}
19 changes: 19 additions & 0 deletions validation/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package validation

type Validator interface {
Validate(data interface{}) (ok bool, errs []ValidationError)
}

type ValidationError struct {
FieldName string
Error error
}

type ValidatorFunc func(fieldName string, value interface{}, arg string) (bool, error)

func DefaultValidators() map[string]ValidatorFunc {
return map[string]ValidatorFunc{
"required": required,
"min": min,
}
}
36 changes: 36 additions & 0 deletions validation/validator_functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package validation

import (
"errors"
"fmt"
"strconv"
)

func required(fieldName string, value interface{}, arg string) (valid bool, err error) {
if str, ok := value.(string); ok {
valid = str != ""
err = fmt.Errorf("A value is required")
} else {
err = errors.New("The required validator is for strings")
}
return
}

func min(fieldName string, value interface{}, arg string) (valid bool, err error) {
minVal, err := strconv.Atoi(arg)
if err != nil {
panic("Invalid arguments for validator: " + arg)
}
err = fmt.Errorf("The minimum value is %v", minVal)
if iVal, iValOk := value.(int); iValOk {
valid = iVal >= minVal
} else if fVal, fValOk := value.(float64); fValOk {
valid = fVal >= float64(minVal)
} else if strVal, strValOk := value.(string); strValOk {
err = fmt.Errorf("The minimum length is %v characters", minVal)
valid = len(strVal) >= minVal
} else {
err = errors.New("The min validator is for int, float64, and str values")
}
return
}

0 comments on commit 084735f

Please sign in to comment.