123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- package macro
- import (
- "errors"
- "fmt"
- "net"
- "net/mail"
- "strconv"
- "strings"
- "time"
- "github.com/kataras/iris/v12/macro/interpreter/ast"
- "github.com/google/uuid"
- )
- var (
- // String type
- // Allows anything (single path segment, as everything except the `Path`).
- // Its functions can be used by the rest of the macros and param types whenever not available function by name is used.
- // Because of its "master" boolean value to true (third parameter).
- String = NewMacro("string", "", true, false, nil).
- RegisterFunc("regexp", MustRegexp).
- // checks if param value starts with the 'prefix' arg
- RegisterFunc("prefix", func(prefix string) func(string) bool {
- return func(paramValue string) bool {
- return strings.HasPrefix(paramValue, prefix)
- }
- }).
- // checks if param value ends with the 'suffix' arg
- RegisterFunc("suffix", func(suffix string) func(string) bool {
- return func(paramValue string) bool {
- return strings.HasSuffix(paramValue, suffix)
- }
- }).
- // checks if param value contains the 's' arg
- RegisterFunc("contains", func(s string) func(string) bool {
- return func(paramValue string) bool {
- return strings.Contains(paramValue, s)
- }
- }).
- // checks if param value's length is at least 'min'
- RegisterFunc("min", func(min int) func(string) bool {
- return func(paramValue string) bool {
- return len(paramValue) >= min
- }
- }).
- // checks if param value's length is not bigger than 'max'
- RegisterFunc("max", func(max int) func(string) bool {
- return func(paramValue string) bool {
- return max >= len(paramValue)
- }
- }).
- // checks if param value's matches the given input
- RegisterFunc("eq", func(s string) func(string) bool {
- return func(paramValue string) bool {
- return paramValue == s
- }
- }).
- // checks if param value's matches at least one of the inputs
- RegisterFunc("eqor", func(texts []string) func(string) bool {
- if len(texts) == 1 {
- text := texts[0]
- return func(paramValue string) bool {
- return paramValue == text
- }
- }
- return func(paramValue string) bool {
- for _, s := range texts {
- if paramValue == s {
- return true
- }
- }
- return false
- }
- })
- // Int or number type
- // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch.
- // If x64: -9223372036854775808 to 9223372036854775807.
- // If x32: -2147483648 to 2147483647 and etc..
- Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.Atoi(paramValue)
- if err != nil {
- return err, false
- }
- return v, true
- }).
- // checks if the param value's int representation is
- // bigger or equal than 'min'
- RegisterFunc("min", func(min int) func(int) bool {
- return func(paramValue int) bool {
- return paramValue >= min
- }
- }).
- // checks if the param value's int representation is
- // smaller or equal than 'max'.
- RegisterFunc("max", func(max int) func(int) bool {
- return func(paramValue int) bool {
- return paramValue <= max
- }
- }).
- // checks if the param value's int representation is
- // between min and max, including 'min' and 'max'.
- RegisterFunc("range", func(min, max int) func(int) bool {
- return func(paramValue int) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Int8 type
- // -128 to 127.
- Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseInt(paramValue, 10, 8)
- if err != nil {
- return err, false
- }
- return int8(v), true
- }).
- RegisterFunc("min", func(min int8) func(int8) bool {
- return func(paramValue int8) bool {
- return paramValue >= min
- }
- }).
- RegisterFunc("max", func(max int8) func(int8) bool {
- return func(paramValue int8) bool {
- return paramValue <= max
- }
- }).
- RegisterFunc("range", func(min, max int8) func(int8) bool {
- return func(paramValue int8) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Int16 type
- // -32768 to 32767.
- Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseInt(paramValue, 10, 16)
- if err != nil {
- return err, false
- }
- return int16(v), true
- }).
- RegisterFunc("min", func(min int16) func(int16) bool {
- return func(paramValue int16) bool {
- return paramValue >= min
- }
- }).
- RegisterFunc("max", func(max int16) func(int16) bool {
- return func(paramValue int16) bool {
- return paramValue <= max
- }
- }).
- RegisterFunc("range", func(min, max int16) func(int16) bool {
- return func(paramValue int16) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Int32 type
- // -2147483648 to 2147483647.
- Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseInt(paramValue, 10, 32)
- if err != nil {
- return err, false
- }
- return int32(v), true
- }).
- RegisterFunc("min", func(min int32) func(int32) bool {
- return func(paramValue int32) bool {
- return paramValue >= min
- }
- }).
- RegisterFunc("max", func(max int32) func(int32) bool {
- return func(paramValue int32) bool {
- return paramValue <= max
- }
- }).
- RegisterFunc("range", func(min, max int32) func(int32) bool {
- return func(paramValue int32) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Int64 as int64 type
- // -9223372036854775808 to 9223372036854775807.
- Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseInt(paramValue, 10, 64)
- if err != nil { // if err == strconv.ErrRange...
- return err, false
- }
- return v, true
- }).
- // checks if the param value's int64 representation is
- // bigger or equal than 'min'.
- RegisterFunc("min", func(min int64) func(int64) bool {
- return func(paramValue int64) bool {
- return paramValue >= min
- }
- }).
- // checks if the param value's int64 representation is
- // smaller or equal than 'max'.
- RegisterFunc("max", func(max int64) func(int64) bool {
- return func(paramValue int64) bool {
- return paramValue <= max
- }
- }).
- // checks if the param value's int64 representation is
- // between min and max, including 'min' and 'max'.
- RegisterFunc("range", func(min, max int64) func(int64) bool {
- return func(paramValue int64) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Uint as uint type
- // actual value can be min-max uint64 or min-max uint32 depends on the arch.
- // If x64: 0 to 18446744073709551615.
- // If x32: 0 to 4294967295 and etc.
- Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64...
- if err != nil {
- return err, false
- }
- return uint(v), true
- }).
- // checks if the param value's int representation is
- // bigger or equal than 'min'
- RegisterFunc("min", func(min uint) func(uint) bool {
- return func(paramValue uint) bool {
- return paramValue >= min
- }
- }).
- // checks if the param value's int representation is
- // smaller or equal than 'max'.
- RegisterFunc("max", func(max uint) func(uint) bool {
- return func(paramValue uint) bool {
- return paramValue <= max
- }
- }).
- // checks if the param value's int representation is
- // between min and max, including 'min' and 'max'.
- RegisterFunc("range", func(min, max uint) func(uint) bool {
- return func(paramValue uint) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Uint8 as uint8 type
- // 0 to 255.
- Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseUint(paramValue, 10, 8)
- if err != nil {
- return err, false
- }
- return uint8(v), true
- }).
- // checks if the param value's uint8 representation is
- // bigger or equal than 'min'.
- RegisterFunc("min", func(min uint8) func(uint8) bool {
- return func(paramValue uint8) bool {
- return paramValue >= min
- }
- }).
- // checks if the param value's uint8 representation is
- // smaller or equal than 'max'.
- RegisterFunc("max", func(max uint8) func(uint8) bool {
- return func(paramValue uint8) bool {
- return paramValue <= max
- }
- }).
- // checks if the param value's uint8 representation is
- // between min and max, including 'min' and 'max'.
- RegisterFunc("range", func(min, max uint8) func(uint8) bool {
- return func(paramValue uint8) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Uint16 as uint16 type
- // 0 to 65535.
- Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseUint(paramValue, 10, 16)
- if err != nil {
- return err, false
- }
- return uint16(v), true
- }).
- RegisterFunc("min", func(min uint16) func(uint16) bool {
- return func(paramValue uint16) bool {
- return paramValue >= min
- }
- }).
- RegisterFunc("max", func(max uint16) func(uint16) bool {
- return func(paramValue uint16) bool {
- return paramValue <= max
- }
- }).
- RegisterFunc("range", func(min, max uint16) func(uint16) bool {
- return func(paramValue uint16) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Uint32 as uint32 type
- // 0 to 4294967295.
- Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseUint(paramValue, 10, 32)
- if err != nil {
- return err, false
- }
- return uint32(v), true
- }).
- RegisterFunc("min", func(min uint32) func(uint32) bool {
- return func(paramValue uint32) bool {
- return paramValue >= min
- }
- }).
- RegisterFunc("max", func(max uint32) func(uint32) bool {
- return func(paramValue uint32) bool {
- return paramValue <= max
- }
- }).
- RegisterFunc("range", func(min, max uint32) func(uint32) bool {
- return func(paramValue uint32) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Uint64 as uint64 type
- // 0 to 18446744073709551615.
- Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) {
- v, err := strconv.ParseUint(paramValue, 10, 64)
- if err != nil {
- return err, false
- }
- return v, true
- }).
- // checks if the param value's uint64 representation is
- // bigger or equal than 'min'.
- RegisterFunc("min", func(min uint64) func(uint64) bool {
- return func(paramValue uint64) bool {
- return paramValue >= min
- }
- }).
- // checks if the param value's uint64 representation is
- // smaller or equal than 'max'.
- RegisterFunc("max", func(max uint64) func(uint64) bool {
- return func(paramValue uint64) bool {
- return paramValue <= max
- }
- }).
- // checks if the param value's uint64 representation is
- // between min and max, including 'min' and 'max'.
- RegisterFunc("range", func(min, max uint64) func(uint64) bool {
- return func(paramValue uint64) bool {
- return !(paramValue < min || paramValue > max)
- }
- })
- // Bool or boolean as bool type
- // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True"
- // or "0" or "f" or "F" or "FALSE" or "false" or "False".
- Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) {
- // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$
- // in this case.
- v, err := strconv.ParseBool(paramValue)
- if err != nil {
- return err, false
- }
- return v, true
- })
- // ErrParamNotAlphabetical is fired when the parameter value is not an alphabetical text.
- ErrParamNotAlphabetical = errors.New("parameter is not alphabetical")
- alphabeticalEval = MustRegexp("^[a-zA-Z ]+$")
- // Alphabetical letter type
- // letters only (upper or lowercase)
- Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) {
- if !alphabeticalEval(paramValue) {
- return fmt.Errorf("%s: %w", paramValue, ErrParamNotAlphabetical), false
- }
- return paramValue, true
- })
- // ErrParamNotFile is fired when the parameter value is not a form of a file.
- ErrParamNotFile = errors.New("parameter is not a file")
- fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$")
- // File type
- // letters (upper or lowercase)
- // numbers (0-9)
- // underscore (_)
- // dash (-)
- // point (.)
- // no spaces! or other character
- File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) {
- if !fileEval(paramValue) {
- return fmt.Errorf("%s: %w", paramValue, ErrParamNotFile), false
- }
- return paramValue, true
- })
- // Path type
- // anything, should be the last part
- //
- // It allows everything, we have String and Path as different
- // types because I want to give the opportunity to the user
- // to organise the macro functions based on wildcard or single dynamic named path parameter.
- // Should be living in the latest path segment of a route path.
- Path = NewMacro("path", "", false, true, nil)
- // UUID string type for validating a uuidv4 (and v1) path parameter.
- // Read more at: https://tools.ietf.org/html/rfc4122.
- UUID = NewMacro("uuid", "uuidv4", false, false, func(paramValue string) (interface{}, bool) {
- _, err := uuid.Parse(paramValue) // this is x10+ times faster than regexp.
- if err != nil {
- return err, false
- }
- return paramValue, true
- })
- // Email string type for validating an e-mail path parameter. It returns the address as string, instead of an *mail.Address.
- // Read more at go std mail.ParseAddress method. See the ':email' path parameter for a more strictly version of validation.
- Mail = NewMacro("mail", "", false, false, func(paramValue string) (interface{}, bool) {
- _, err := mail.ParseAddress(paramValue)
- if err != nil {
- return fmt.Errorf("%s: %w", paramValue, err), false
- }
- return paramValue, true
- })
- // Email string type for validating an e-mail path parameter. It returns the address as string, instead of an *mail.Address.
- // It is a combined validation using mail.ParseAddress and net.LookupMX so only valid domains can be passed.
- // It's a more strictly version of the ':mail' path parameter.
- Email = NewMacro("email", "", false, false, func(paramValue string) (interface{}, bool) {
- _, err := mail.ParseAddress(paramValue)
- if err != nil {
- return fmt.Errorf("%s: %w", paramValue, err), false
- }
- domainPart := strings.Split(paramValue, "@")[1]
- mx, err := net.LookupMX(domainPart)
- if err != nil {
- return fmt.Errorf("%s: %w", paramValue, err), false
- }
- if len(mx) == 0 {
- return fmt.Errorf("%s: mx is empty", paramValue), false
- }
- return paramValue, true
- })
- simpleDateLayout = "2006/01/02"
- // Date type.
- Date = NewMacro("date", "", false, true, func(paramValue string) (interface{}, bool) {
- tt, err := time.Parse(simpleDateLayout, paramValue)
- if err != nil {
- return fmt.Errorf("%s: %w", paramValue, err), false
- }
- return tt, true
- })
- // ErrParamNotWeekday is fired when the parameter value is not a form of a time.Weekday.
- ErrParamNotWeekday = errors.New("parameter is not a valid weekday")
- longDayNames = map[string]time.Weekday{
- "Sunday": time.Sunday,
- "Monday": time.Monday,
- "Tuesday": time.Tuesday,
- "Wednesday": time.Wednesday,
- "Thursday": time.Thursday,
- "Friday": time.Friday,
- "Saturday": time.Saturday,
- // lowercase.
- "sunday": time.Sunday,
- "monday": time.Monday,
- "tuesday": time.Tuesday,
- "wednesday": time.Wednesday,
- "thursday": time.Thursday,
- "friday": time.Friday,
- "saturday": time.Saturday,
- }
- // Weekday type, returns a type of time.Weekday.
- // Valid values:
- // 0 to 7 (leading zeros don't matter) or "Sunday" to "Monday" or "sunday" to "monday".
- Weekday = NewMacro("weekday", "", false, false, func(paramValue string) (interface{}, bool) {
- d, ok := longDayNames[paramValue]
- if !ok {
- // try parse from integer.
- n, err := strconv.Atoi(paramValue)
- if err != nil {
- return fmt.Errorf("%s: %w", paramValue, err), false
- }
- if n < 0 || n > 6 {
- return fmt.Errorf("%s: %w", paramValue, ErrParamNotWeekday), false
- }
- return time.Weekday(n), true
- }
- return d, true
- })
- // Defaults contains the defaults macro and parameters types for the router.
- //
- // Read https://github.com/kataras/iris/tree/main/_examples/routing/macros for more details.
- Defaults = &Macros{
- String,
- Int,
- Int8,
- Int16,
- Int32,
- Int64,
- Uint,
- Uint8,
- Uint16,
- Uint32,
- Uint64,
- Bool,
- Alphabetical,
- File,
- Path,
- UUID,
- Mail,
- Email,
- Date,
- Weekday,
- }
- )
- // Macros is just a type of a slice of *Macro
- // which is responsible to register and search for macros based on the indent(parameter type).
- type Macros []*Macro
- // Register registers a custom Macro.
- // The "indent" should not be empty and should be unique, it is the parameter type's name, i.e "string".
- // The "alias" is optionally and it should be unique, it is the alias of the parameter type.
- // "isMaster" and "isTrailing" is for default parameter type and wildcard respectfully.
- // The "evaluator" is the function that is converted to an Iris handler which is executed every time
- // before the main chain of a route's handlers that contains this macro of the specific parameter type.
- //
- // Read https://github.com/kataras/iris/tree/main/_examples/routing/macros for more details.
- func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro {
- macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator)
- if ms.register(macro) {
- return macro
- }
- return nil
- }
- func (ms *Macros) register(macro *Macro) bool {
- if macro.Indent() == "" {
- return false
- }
- cp := *ms
- for _, m := range cp {
- // can't add more than one with the same ast characteristics.
- if macro.Indent() == m.Indent() {
- return false
- }
- if alias := macro.Alias(); alias != "" {
- if alias == m.Alias() || alias == m.Indent() {
- return false
- }
- }
- if macro.Master() && m.Master() {
- return false
- }
- }
- cp = append(cp, macro)
- *ms = cp
- return true
- }
- // Unregister removes a macro and its parameter type from the list.
- func (ms *Macros) Unregister(indent string) bool {
- cp := *ms
- for i, m := range cp {
- if m.Indent() == indent {
- copy(cp[i:], cp[i+1:])
- cp[len(cp)-1] = nil
- cp = cp[:len(cp)-1]
- *ms = cp
- return true
- }
- }
- return false
- }
- // Lookup returns the responsible macro for a parameter type, it can return nil.
- func (ms *Macros) Lookup(pt ast.ParamType) *Macro {
- if m := ms.Get(pt.Indent()); m != nil {
- return m
- }
- if alias, has := ast.HasAlias(pt); has {
- if m := ms.Get(alias); m != nil {
- return m
- }
- }
- return nil
- }
- // Get returns the responsible macro for a parameter type, it can return nil.
- func (ms *Macros) Get(indentOrAlias string) *Macro {
- if indentOrAlias == "" {
- return nil
- }
- for _, m := range *ms {
- if m.Indent() == indentOrAlias {
- return m
- }
- if m.Alias() == indentOrAlias {
- return m
- }
- }
- return nil
- }
- // GetMaster returns the default macro and its parameter type,
- // by default it will return the `String` macro which is responsible for the "string" parameter type.
- func (ms *Macros) GetMaster() *Macro {
- for _, m := range *ms {
- if m.Master() {
- return m
- }
- }
- return nil
- }
- // GetTrailings returns the macros that have support for wildcards parameter types.
- // By default it will return the `Path` macro which is responsible for the "path" parameter type.
- func (ms *Macros) GetTrailings() (macros []*Macro) {
- for _, m := range *ms {
- if m.Trailing() {
- macros = append(macros, m)
- }
- }
- return
- }
- // SetErrorHandler registers a common type path parameter error handler.
- // The "fnHandler" MUST be a type of handler.ParamErrorHandler:
- // func(ctx iris.Context, paramIndex int, err error). It calls
- // the Macro.HandleError method for each of the "ms" entries.
- func (ms *Macros) SetErrorHandler(fnHandler interface{}) {
- for _, m := range *ms {
- if m == nil {
- continue
- }
- m.HandleError(fnHandler)
- }
- }
|