package iris import ( "fmt" "strconv" "strings" ) type ( // IRoute is the interface which the Route should implements // it useful to have it as an interface because this interface is passed to the plugins IRoute interface { GetMethod() string GetDomain() string GetPath() string GetName() string // Name sets the name of the route Name(string) IRoute GetMiddleware() Middleware HasCors() bool ParsePath(...interface{}) string ParseURI(...interface{}) string } // RouteNameFunc is returned to from route handle RouteNameFunc func(string) IRoute // Route contains basic and temporary info about the route in order to be stored to the tree Route struct { method string domain string fullpath string // the name of the route, the default name is just the registed path. name string middleware Middleware // station station *Iris // this is used to convert /mypath/:aparam/:something to -> /mypath/%v/%v and /mypath/* -> mypath/%v // we use %v to escape from the conversions between strings,booleans and integers. // used inside custom html template func 'url' formattedPath string // formattedParts is just the formattedPath count, used to see if we have one path parameter then the url's function arguments will be passed as one string to the %v formattedParts int } ) var _ IRoute = &Route{} // NewRoute creates, from a path string, and a slice of HandlerFunc func NewRoute(method string, registedPath string, middleware Middleware, station *Iris) *Route { domain := "" //dirdy but I'm not touching this again:P if registedPath[0] != SlashByte && strings.Contains(registedPath, ".") && (strings.IndexByte(registedPath, SlashByte) == -1 || strings.IndexByte(registedPath, SlashByte) > strings.IndexByte(registedPath, '.')) { //means that is a path with domain //we have to extract the domain //find the first '/' firstSlashIndex := strings.IndexByte(registedPath, SlashByte) //firt of all remove the first '/' if that exists and we have domain if firstSlashIndex == 0 { //e.g /admin.ideopod.com/hey //then just remove the first slash and re-execute the NewRoute and return it registedPath = registedPath[1:] return NewRoute(method, registedPath, middleware, station) } //if it's just the domain, then set it(registedPath) as the domain //and after set the registedPath to a slash '/' for the path part if firstSlashIndex == -1 { domain = registedPath registedPath = Slash } else { //we have a domain + path domain = registedPath[0:firstSlashIndex] registedPath = registedPath[len(domain):] } } r := &Route{method: method, domain: domain, fullpath: registedPath, middleware: middleware, name: registedPath, formattedPath: registedPath, station: station} r.formatPath() return r } func (r *Route) isWildcard() bool { return r.domain != r.station.server.Hostname() && r.domain == PrefixDynamicSubdomain } func (r *Route) formatPath() { // we don't care about performance here, no runtime func. n1Len := strings.Count(r.fullpath, ":") isMatchEverything := r.fullpath[len(r.fullpath)-1] == MatchEverythingByte if n1Len == 0 && !isMatchEverything { // its a static return } if n1Len == 0 && isMatchEverything { //if we have something like: /mypath/anything/* -> /mypatch/anything/%v r.formattedPath = r.fullpath[0:len(r.fullpath)-2] + "%v" r.formattedParts++ return } tempPath := r.fullpath splittedN1 := strings.Split(r.fullpath, "/") for _, v := range splittedN1 { if len(v) > 0 { if v[0] == ':' || v[0] == MatchEverythingByte { r.formattedParts++ tempPath = strings.Replace(tempPath, v, "%v", -1) // n1Len, but let it we don't care about performance here. } } } r.formattedPath = tempPath } // GetMethod returns the http method func (r Route) GetMethod() string { return r.method } // GetDomain returns the registed domain which this route is ( if none, is "" which is means "localhost"/127.0.0.1) func (r Route) GetDomain() string { return r.domain } // GetPath returns the full registed path func (r Route) GetPath() string { return r.fullpath } // GetName returns the name of the route func (r Route) GetName() string { return r.name } // Name sets the route's name func (r *Route) Name(newName string) IRoute { r.name = newName return r } // GetMiddleware returns the chain of the []HandlerFunc registed to this Route func (r Route) GetMiddleware() Middleware { return r.middleware } // HasCors check if middleware passsed to a route has cors func (r *Route) HasCors() bool { return RouteConflicts(r, "httpmethod") } // ParsePath used to check arguments with the route's named parameters and return the correct url // if parse failed returns empty string func (r *Route) ParsePath(args ...interface{}) string { argsLen := len(args) // we have named parameters but arguments not given if argsLen == 0 && r.formattedParts > 0 { return "" } // we have arguments but they are much more than the named parameters // 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter if argsLen > r.formattedParts { if r.fullpath[len(r.fullpath)-1] == MatchEverythingByte { // we have to convert each argument to a string in this case argsString := make([]string, argsLen, argsLen) for i, v := range args { if s, ok := v.(string); ok { argsString[i] = s } else if num, ok := v.(int); ok { argsString[i] = strconv.Itoa(num) } else if b, ok := v.(bool); ok { argsString[i] = strconv.FormatBool(b) } else if arr, ok := v.([]string); ok { if len(arr) > 0 { argsString[i] = arr[0] argsString = append(argsString, arr[1:]...) } } } parameter := strings.Join(argsString, Slash) result := fmt.Sprintf(r.formattedPath, parameter) return result } // 2 if !1 return false return "" } arguments := args[0:] // check for arrays for i, v := range arguments { if arr, ok := v.([]string); ok { if len(arr) > 0 { interfaceArr := make([]interface{}, len(arr)) for j, sv := range arr { interfaceArr[j] = sv } arguments[i] = interfaceArr[0] arguments = append(arguments, interfaceArr[1:]...) } } } return fmt.Sprintf(r.formattedPath, arguments...) } // ParseURI returns the subdomain+ host + ParsePath(...optional named parameters if route is dynamic) // returns an empty string if parse is failed func (r *Route) ParseURI(args ...interface{}) (uri string) { scheme := "http://" if r.station.server.IsSecure() { scheme = "https://" } host := r.station.server.Host() arguments := args[0:] // join arrays as arguments for i, v := range arguments { if arr, ok := v.([]string); ok { if len(arr) > 0 { interfaceArr := make([]interface{}, len(arr)) for j, sv := range arr { interfaceArr[j] = sv } arguments[i] = interfaceArr[0] arguments = append(arguments, interfaceArr[1:]...) } } } // if it's dynamic subdomain then the first argument is the subdomain part if r.isWildcard() { if len(arguments) == 0 { // it's a wildcard subdomain but not arguments return } if subdomain, ok := arguments[0].(string); ok { host = subdomain + "." + host } else { // it is not array because we join them before. if not pass a string then this is not a subdomain part, return empty uri return } arguments = arguments[1:] } if parsedPath := r.ParsePath(arguments...); parsedPath != "" { uri = scheme + host + parsedPath } return } // RouteConflicts checks for route's middleware conflicts func RouteConflicts(r *Route, with string) bool { for _, h := range r.middleware { if m, ok := h.(interface { Conflicts() string }); ok { if c := m.Conflicts(); c == with { return true } } } return false }