sourceafBedSheet::Route.fan


** Matches HTTP Requests to response objects.
** 
** 'Route' is a mixin so you may provide your own implementations. The rest of this documentation 
** relates to the default implementation which uses regular expressions to match against the 
** Request URL and HTTP Method.
** 
** Regex Routes
** ************
** Matches the HTTP Request URL and HTTP Method to a response object using regular expressions.
** 
** Note that all URL matching is case-insensitive. If you really need case-sensitive matching (???)
** use the 'RegexRoute' explicitly, passing 'false' as the 'caseInsensitive' argument. Example:
** 
**   RegexRoute(`/index`, MyPage#hello, "GET", false)
** 
** 
** 
** Response Objects
** ================
** A 'Route' may return *any* response object, be it `Text`, `HttpStatus`, 'File', or any other.
** It simply returns whatever is passed into the ctor. 
** 
** Example, this matches the URL '/greet' and returns the string 'Hello Mum!'
** 
**   Route(`/greet`, Text.fromPlain("Hello Mum!")) 
** 
** And this redirects any request for '/home' to '/greet'
** 
**   Route(`/home`, Redirect.movedTemporarily(`/greet`)) 
** 
** You can use glob expressions in your URL, so:
** 
**   Route(`/greet.*`, ...) 
** 
** will match the URLs '/greet.html', '/greet.php' but not '/greet'. 
** 
** 
** 
** Response Methods
** ================
** Routes may also return `MethodCall` instances that call a Fantom method. 
** To use, pass in the method as the response object. 
** On a successful match, the 'Route' will convert the method into a 'MethodCall' object.
** 
**   Route(`/greet`, MyPage#hello)
** 
** Method matching can also map URL path segments to method parameters and is a 2 stage process:
** 
** Stage 1 - URL Matching
** ----------------------
** First a special *glob* syntax is used to capture string sections from the request URL.
** In stage 2 these strings are used as potential method arguments.
** 
** In brief, the special glob syntax is:
**  - '?' optionally matches the last character, 
**  - '/*' captures a path segment,
**  - '/**' captures all remaining path segments,
**  - '/***' captures the remaining URL.
** 
** Full examples follow:
** 
**   URL              glob          captures
**   --------------------------------------------
**   /user/       --> /user/*    => ""
**   /user/42     --> /user/*    => "42"
**   /user/42/    --> /user/*    => no match
**   /user/42/dee --> /user/*    => no match
**                               
**   /user/       --> /user/*/*  => no match
**   /user/42     --> /user/*/*  => no match
**   /user/42/    --> /user/*/*  => "42", ""
**   /user/42/dee --> /user/*/*  => "42", "dee"
**                               
**   /user/       --> /user/**   => ""
**   /user/42     --> /user/**   => "42"
**   /user/42/    --> /user/**   => "42", ""
**   /user/42/dee --> /user/**   => "42", "dee"
**                               
**   /user/       --> /user/***  => ""
**   /user/42     --> /user/***  => "42"
**   /user/42/    --> /user/***  => "42/"
**   /user/42/dee --> /user/***  => "42/dee"
** 
** Note that in stage 2 empty strings may be converted to 'nulls'. 
** 
** The intention of the '?' character is to optionally match a trailing slash. Example:
** 
**   URL              glob          captures
**   --------------------------------------------
**   /index       --> /index/?   => match
**   /index/      --> /index/?   => match
**                vs      
**   /index       --> /index/    => no match
**   /index/      --> /index     => no match
**  
** Should a match be found, then the captured strings are further processed in stage 2.
** 
** A 'no match' signifies just that.
** 
** 
** 
** Stage 2 - Method Parameter Matching
** -----------------------------------
** An attempt is now made to match the captured strings to method parameters, taking into account nullable types 
** and default values.
** 
** First, the number of captured strings have to match the number of method parameters, taking into 
** account any optional / default values on the method.
** 
** Next the captured strings are converted to method arguments using the [ValueEncoder]`ValueEncoder` 
** service. If no value encoder is found then the following default behaviour is used:
**  - Non-empty strings are converted using a [TypeCoercer]`afBeanUtils::TypeCoercer`
**  - Empty strings are considered 'null' but
**    - if the method parameter is not nullable, then [BeanFactory.defaultValue()]`afBeanUtils::BeanFactory.defaultValue` is used.
** 
** The above process may sound complicated but in practice it just works and does what you expect.
** 
** Here are a couple of examples:
** 
**   strings          method signature          args
**   ----------------------------------------------------------
**              -->  (Obj a, Obj b)         =>  no match
**
**   ""         -->  (Str? a)               =>  null
**   ""         -->  (Str a)                =>  ""
**   "wotever"  -->  (Str a)                =>  "wotever"
** 
**   ""         -->  (Int? a)               =>  null
**   ""         -->  (Int a)                =>  0
**   "68"       -->  (Int a)                =>  68
**   "wotever"  -->  (Int a)                =>  no match
** 
**   ""         -->  (Str? a, Int b := 68)  =>  null, (default)
**   ""         -->  (Str a, Int b := 68)   =>  "", (default)
** 
** Assuming you you have an entity object, such as 'User', with an ID field; you can contribute a 
** 'ValueEncoder' that inflates (or otherwise reads from a database) 'User' objects from a string 
** version of the ID. Then your methods can declare 'User' as a parameter and BedSheet will 
** convert the captured strings to User objects for you! 
** 
** 
** 
** Method Invocation
** -----------------
** Handler methods may be non-static. 
** They they belong to an IoC service then the service is obtained from the IoC registry.
** Otherwise the containing class is [autobuilt]`afIoc::Registry.autobuild`. 
** If the class is 'const', the instance is cached for future use.
** 
const mixin Route {
    
    @NoDoc @Deprecated { msg="Deprecated with no replacement. As Route is now a mixin, implementations matches may not be based on a regex." }
    virtual Regex routeRegex() { Str.defVal.toRegex }
    
    @NoDoc @Deprecated { msg="Deprecated with no replacement. As Route is now a mixin, implementations matches may not be based on HTTP methods." }
    virtual Str httpMethod() { Str.defVal }

    @NoDoc @Deprecated { msg="Deprecated with no replacement. As Route is now a mixin, implementations may generate dynamic responses." }
    virtual Obj response() { Str.defVal }

    ** Creates a Route that matches on the given URL glob pattern. 
    ** 'urlGlob' must start with a slash "/". Example: 
    ** 
    **   Route(`/index/**`)
    ** 
    ** Note that matching is made against URI patterns in [Fantom standard form]`sys::Uri`. 
    ** That means certain delimiter characters in the path section will be escaped with a 
    ** backslash. Notably the ':/?#[]@\' characters. Glob expressions have to take account 
    ** of this.   
    ** 
    ** 'httpMethod' may specify multiple HTTP methods, separated by spaces and / or commas.  
    ** Each may also be a glob pattern. Example, all the following are valid:
    **  - 'GET' 
    **  - 'GET HEAD'
    **  - 'GET, HEAD'
    **  - 'GET, H*'
    ** 
    ** Use the simple string '*' to match all HTTP methods.
    static new makeFromGlob(Uri urlGlob, Obj response, Str httpMethod := "GET") {
        RegexRoute(urlGlob, response, httpMethod)
    }

    ** For hardcore users; make a Route from a regex. Capture groups are used to match arguments.
    ** Example:
    ** 
    **   Route(Regex<|(?i)^\/index\/(.*?)$|>, #foo, "GET", true) ==> Route(`/index/**`)
    ** 
    ** Set 'matchAllSegs' to 'true' to have the last capture group mimic the glob '**' operator, 
    ** splitting on "/" to match all remaining segments.  
    ** 
    ** Note that matching is made against URI patterns in [Fantom standard form]`sys::Uri`. 
    ** That means certain delimiter characters in the path section will be escaped with a 
    ** backslash. Notably the ':/?#[]@\' characters. Regular expressions have to take account 
    ** of this.
    **    
    ** 'httpMethod' may specify multiple HTTP methods, separated by spaces and / or commas.  
    ** Each may also be a glob pattern. Example, all the following are valid:
    **  - 'GET' 
    **  - 'GET HEAD'
    **  - 'GET, HEAD'
    **  - 'GET, H*'
    ** 
    ** Use the simple string '*' to match all HTTP methods.
    static new makeFromRegex(Regex uriRegex, Obj response, Str httpMethod := "GET", Bool matchAllSegs := false) {
        RegexRoute(uriRegex, response, httpMethod, matchAllSegs)
    }

    ** Returns a response object should the given uri (and http method) match this route. Returns 'null' if not.
    abstract Obj? match(HttpRequest httpRequest)
    
    ** Returns a hint at what this route matches on. Used for debugging and in 404 / 500 error pages. 
    abstract Str matchHint()

    ** Returns a hint at what response this route returns. Used for debugging and in 404 / 500 error pages. 
    abstract Str responseHint()
}