mirror of
https://github.com/vale981/myway
synced 2025-03-04 09:21:40 -05:00
Initial commit.
This commit is contained in:
commit
a15c72ebd5
11 changed files with 657 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
*.fasl
|
||||
*.dx32fsl
|
||||
*.dx64fsl
|
||||
*.lx32fsl
|
||||
*.lx64fsl
|
||||
*.x86f
|
||||
*~
|
||||
.#*
|
78
README.markdown
Normal file
78
README.markdown
Normal file
|
@ -0,0 +1,78 @@
|
|||
# My Way
|
||||
|
||||
My Way is a Sinatra-compatible URL routing library. This was originally written as Clack.Util.Route, a part of [Clack](http://clacklisp.org/).
|
||||
|
||||
## Usage
|
||||
|
||||
```common-lisp
|
||||
(use-package :myway)
|
||||
|
||||
(defvar *mapper* (make-mapper))
|
||||
|
||||
(connect *mapper* "/" "Welcome to My Way.")
|
||||
|
||||
(connect *mapper* "/hello/?:name?"
|
||||
(lambda (params)
|
||||
(format nil "Hello, ~A" (or (getf params :name)
|
||||
"Guest"))))
|
||||
|
||||
(dispatch *mapper* "/")
|
||||
;=> "Welcome to My Way."
|
||||
; T
|
||||
|
||||
(dispatch *mapper* "/hello")
|
||||
;=> "Hello, Guest"
|
||||
; T
|
||||
|
||||
(dispatch *mapper* "/hello/Eitaro")
|
||||
;=> "Hello, Eitaro"
|
||||
; T
|
||||
|
||||
(dispatch *mapper* "/hello/Eitaro" :method :POST)
|
||||
;=> NIL
|
||||
; NIL
|
||||
```
|
||||
|
||||
### next-route
|
||||
|
||||
```common-lisp
|
||||
(connect *mapper* "/guess/:who"
|
||||
(lambda (params)
|
||||
(if (string= (getf params :who) "Eitaro")
|
||||
"You got me!"
|
||||
(next-route))))
|
||||
|
||||
(connect *mapper* "/guess/*"
|
||||
(lambda (params)
|
||||
(declare (ignore params))
|
||||
"You missed!"))
|
||||
```
|
||||
|
||||
### to-app
|
||||
|
||||
`to-app` makes a Clack app from `mapper`.
|
||||
|
||||
```common-lisp
|
||||
(to-app *mapper*)
|
||||
;=> #<CLOSURE (LAMBDA (MYWAY::ENV) :IN TO-APP) {100E24F13B}>
|
||||
|
||||
(clack:clackup (to-app *mapper*))
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```common-lisp
|
||||
(ql:quickload :myway)
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
* Eitaro Fukamachi (e.arrows@gmail.com)
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright (c) 2014 Eitaro Fukamachi (e.arrows@gmail.com)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the LLGPL License.
|
24
myway-test.asd
Normal file
24
myway-test.asd
Normal file
|
@ -0,0 +1,24 @@
|
|||
#|
|
||||
This file is a part of myway project.
|
||||
Copyright (c) 2014 Eitaro Fukamachi (e.arrows@gmail.com)
|
||||
|#
|
||||
|
||||
(in-package :cl-user)
|
||||
(defpackage myway-test-asd
|
||||
(:use :cl :asdf))
|
||||
(in-package :myway-test-asd)
|
||||
|
||||
(defsystem myway-test
|
||||
:author "Eitaro Fukamachi"
|
||||
:license "LLGPL"
|
||||
:depends-on (:myway
|
||||
:prove)
|
||||
:components ((:module "t"
|
||||
:components
|
||||
((:test-file "rule")
|
||||
(:test-file "myway"))))
|
||||
|
||||
:defsystem-depends-on (:prove-asdf)
|
||||
:perform (test-op :after (op c)
|
||||
(funcall (intern #.(string :run-test-system) :prove-asdf) c)
|
||||
(asdf:clear-system c)))
|
47
myway.asd
Normal file
47
myway.asd
Normal file
|
@ -0,0 +1,47 @@
|
|||
#|
|
||||
This file is a part of myway project.
|
||||
Copyright (c) 2014 Eitaro Fukamachi (e.arrows@gmail.com)
|
||||
|#
|
||||
|
||||
#|
|
||||
Sinatra-compatible routing library.
|
||||
|
||||
Author: Eitaro Fukamachi (e.arrows@gmail.com)
|
||||
|#
|
||||
|
||||
(in-package :cl-user)
|
||||
(defpackage myway-asd
|
||||
(:use :cl :asdf))
|
||||
(in-package :myway-asd)
|
||||
|
||||
(defsystem myway
|
||||
:version "0.1.0"
|
||||
:author "Eitaro Fukamachi"
|
||||
:license "LLGPL"
|
||||
:depends-on (:cl-ppcre
|
||||
:trivial-types
|
||||
:do-urlencode
|
||||
:map-set
|
||||
:alexandria
|
||||
:cl-utilities)
|
||||
:components ((:module "src"
|
||||
:components
|
||||
((:file "myway" :depends-on ("route" "mapper" "util"))
|
||||
(:file "rule")
|
||||
(:file "route" :depends-on ("rule"))
|
||||
(:file "mapper" :depends-on ("route" "util"))
|
||||
(:file "util"))))
|
||||
:description "Sinatra-compatible routing library."
|
||||
:long-description
|
||||
#.(with-open-file (stream (merge-pathnames
|
||||
#p"README.markdown"
|
||||
(or *load-pathname* *compile-file-pathname*))
|
||||
:if-does-not-exist nil
|
||||
:direction :input)
|
||||
(when stream
|
||||
(let ((seq (make-array (file-length stream)
|
||||
:element-type 'character
|
||||
:fill-pointer t)))
|
||||
(setf (fill-pointer seq) (read-sequence seq stream))
|
||||
seq)))
|
||||
:in-order-to ((test-op (test-op myway-test))))
|
61
src/mapper.lisp
Normal file
61
src/mapper.lisp
Normal file
|
@ -0,0 +1,61 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway.mapper
|
||||
(:use :cl)
|
||||
(:import-from :myway.route
|
||||
:route-rule
|
||||
:route-handler
|
||||
:equal-route
|
||||
:match-route)
|
||||
(:import-from :myway.rule
|
||||
:rule-methods
|
||||
:rule-url)
|
||||
(:import-from :myway.util
|
||||
:make-collector)
|
||||
(:import-from :map-set
|
||||
:map-set-index)
|
||||
(:export :mapper
|
||||
:make-mapper
|
||||
:mapper-routes
|
||||
:member-route
|
||||
:add-route
|
||||
:next-route
|
||||
:dispatch))
|
||||
(in-package :myway.mapper)
|
||||
|
||||
(defstruct mapper
|
||||
(%routes (make-collector)))
|
||||
|
||||
(defun mapper-routes (mapper)
|
||||
(funcall (mapper-%routes mapper)))
|
||||
|
||||
(defun member-route (mapper route)
|
||||
(member route
|
||||
(mapper-routes mapper)
|
||||
:test #'equal-route))
|
||||
|
||||
(defun add-route (mapper route)
|
||||
(let ((routes (member-route mapper route)))
|
||||
(if routes
|
||||
(progn
|
||||
(warn "Redefining a route for ~S ~A."
|
||||
(rule-url (route-rule route))
|
||||
(map-set-index (rule-methods (route-rule route))))
|
||||
(rplaca routes route))
|
||||
(funcall (mapper-%routes mapper) route))))
|
||||
|
||||
(defparameter *next-route-function* nil)
|
||||
(defun next-route ()
|
||||
(funcall *next-route-function*))
|
||||
|
||||
(defun dispatch (mapper url-string &key (method :GET) (allow-head T))
|
||||
(check-type mapper mapper)
|
||||
(labels ((dispatch-with-rules (routes)
|
||||
(loop for (route . routes) on routes
|
||||
do (multiple-value-bind (matchp params)
|
||||
(match-route route method url-string :allow-head allow-head)
|
||||
(when matchp
|
||||
(let ((*next-route-function* (lambda () (dispatch-with-rules routes))))
|
||||
(return
|
||||
(values (funcall (route-handler route) params) T)))))
|
||||
finally (return (values nil nil)))))
|
||||
(dispatch-with-rules (mapper-routes mapper))))
|
58
src/myway.lisp
Normal file
58
src/myway.lisp
Normal file
|
@ -0,0 +1,58 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway
|
||||
(:use :cl)
|
||||
(:import-from :myway.mapper
|
||||
:mapper
|
||||
:make-mapper
|
||||
:member-route
|
||||
:add-route
|
||||
:next-route
|
||||
:dispatch)
|
||||
(:import-from :myway.route
|
||||
:route
|
||||
:make-route)
|
||||
(:import-from :myway.util
|
||||
:function-name)
|
||||
(:export :make-mapper
|
||||
:connect
|
||||
:next-route
|
||||
:dispatch
|
||||
|
||||
:*env*
|
||||
:to-app
|
||||
|
||||
:route
|
||||
:mapper
|
||||
:add-route
|
||||
:find-route
|
||||
:make-route))
|
||||
(in-package :myway)
|
||||
|
||||
(defun connect (mapper url fn &key (method '(:GET)) regexp (name (when (functionp fn)
|
||||
(function-name fn))))
|
||||
(add-route mapper
|
||||
(make-route url
|
||||
:method method
|
||||
:regexp regexp
|
||||
:name name
|
||||
:handler (typecase fn
|
||||
(function fn)
|
||||
(T (lambda (params)
|
||||
(declare (ignore params))
|
||||
fn))))))
|
||||
|
||||
(defun find-route (mapper url &key (method '(:GET)) regexp name)
|
||||
(car
|
||||
(member-route mapper
|
||||
(make-route url
|
||||
:method method
|
||||
:regexp regexp
|
||||
:name name))))
|
||||
|
||||
(defparameter *env* nil)
|
||||
|
||||
(defun to-app (mapper)
|
||||
(lambda (env)
|
||||
(let ((*env* env))
|
||||
(destructuring-bind (&key method path-info &allow-other-keys) env
|
||||
(dispatch mapper path-info :method method)))))
|
33
src/route.lisp
Normal file
33
src/route.lisp
Normal file
|
@ -0,0 +1,33 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway.route
|
||||
(:use :cl)
|
||||
(:import-from :myway.rule
|
||||
:make-rule
|
||||
:match-rule
|
||||
:equal-rule)
|
||||
(:export :route
|
||||
:route-name
|
||||
:route-rule
|
||||
:route-handler
|
||||
:make-route
|
||||
:match-route
|
||||
:equal-route))
|
||||
(in-package :myway.route)
|
||||
|
||||
(defstruct (route (:constructor %make-route))
|
||||
name
|
||||
rule
|
||||
handler)
|
||||
|
||||
(defun make-route (url &key (method '(:GET)) regexp name handler)
|
||||
(%make-route :name name
|
||||
:rule (make-rule url :method method :regexp regexp)
|
||||
:handler handler))
|
||||
|
||||
(defun equal-route (route1 route2)
|
||||
(and (eq (route-name route1) (route-name route2))
|
||||
(equal-rule (route-rule route1) (route-rule route2))))
|
||||
|
||||
(defun match-route (route method url-string &key allow-head)
|
||||
(check-type route route)
|
||||
(match-rule (route-rule route) method url-string :allow-head allow-head))
|
120
src/rule.lisp
Normal file
120
src/rule.lisp
Normal file
|
@ -0,0 +1,120 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway.rule
|
||||
(:use :cl)
|
||||
(:import-from :trivial-types
|
||||
:proper-list)
|
||||
(:import-from :do-urlencode
|
||||
:urlencode)
|
||||
(:import-from :map-set
|
||||
:map-set
|
||||
:make-map-set
|
||||
:ms-insert
|
||||
:ms-member-p
|
||||
:map-set-index)
|
||||
(:import-from :alexandria
|
||||
:ensure-list)
|
||||
(:export :rule
|
||||
:regex-rule
|
||||
:make-rule
|
||||
:match-rule
|
||||
:equal-rule))
|
||||
(in-package :myway.rule)
|
||||
|
||||
(defun list-to-map-set (elements)
|
||||
(let ((ms (make-map-set)))
|
||||
(dolist (el elements ms)
|
||||
(ms-insert ms el))))
|
||||
|
||||
(defvar *default-rule-methods*
|
||||
(list-to-map-set '(:GET)))
|
||||
|
||||
(defstruct (rule (:constructor %make-rule))
|
||||
(methods *default-rule-methods* :type map-set)
|
||||
url
|
||||
regex
|
||||
format-string
|
||||
param-keys)
|
||||
|
||||
(defstruct (regex-rule (:include rule)
|
||||
(:constructor %make-regex-rule)))
|
||||
|
||||
(defun make-rule (url &key (method :GET) regexp)
|
||||
(let ((rule (if regexp
|
||||
(%make-regex-rule :methods (list-to-map-set (ensure-list method)) :url url)
|
||||
(%make-rule :methods (list-to-map-set (ensure-list method)) :url url))))
|
||||
(if regexp
|
||||
(setf (rule-regex rule) (rule-url rule)
|
||||
(rule-format-string rule) (ppcre:regex-replace-all "\\(.+?\\)" (rule-regex rule) "~A"))
|
||||
(compile-rule rule))
|
||||
rule))
|
||||
|
||||
(defun compile-rule (rule)
|
||||
(check-type rule rule)
|
||||
(loop with list = (ppcre:split "(?::([\\w-]+)|(\\*))" (rule-url rule)
|
||||
:with-registers-p t :omit-unmatched-p t)
|
||||
for (prefix name) on list by #'cddr
|
||||
collect (ppcre:regex-replace-all
|
||||
"[^\\?\\/\\w-]" prefix
|
||||
#'escape-special-char
|
||||
:simple-calls t) into re
|
||||
collect prefix into cs
|
||||
if (string= name "*")
|
||||
collect :splat into names
|
||||
and collect "(.*?)" into re
|
||||
and collect "~A" into cs
|
||||
else if name
|
||||
collect (read-from-string (concatenate 'string ":" name)) into names
|
||||
and collect "([^/?]+)" into re
|
||||
and collect "~A" into cs
|
||||
finally
|
||||
(setf (rule-regex rule) (format nil "^~{~A~}$" re)
|
||||
(rule-format-string rule) (ppcre:regex-replace-all "~A\\?" (format nil "~{~A~}" cs)
|
||||
"~:[~;~:*~A~]")
|
||||
(rule-param-keys rule) names)))
|
||||
|
||||
(defun escape-special-char (char)
|
||||
(let ((enc (urlencode (string char))))
|
||||
(cond
|
||||
((string= char " ") (format nil "(?:~A|~A)" enc (escape-special-char #\+)))
|
||||
((string= enc char) (ppcre:quote-meta-chars enc))
|
||||
(t enc))))
|
||||
|
||||
(defun match-method-p (rule method &key allow-head)
|
||||
(check-type rule rule)
|
||||
(check-type method keyword)
|
||||
(flet ((method-equal (rule-method)
|
||||
(or (eq :ANY rule-method)
|
||||
(eq method rule-method)
|
||||
(and (eq method :HEAD)
|
||||
allow-head
|
||||
(string= :GET rule-method)))))
|
||||
(some #'method-equal (map-set-index (rule-methods rule)))))
|
||||
|
||||
(defun match-rule (rule method url-string &key allow-head)
|
||||
(check-type rule rule)
|
||||
(check-type method keyword)
|
||||
(check-type url-string string)
|
||||
(when (match-method-p rule method :allow-head allow-head)
|
||||
(multiple-value-bind (matchp values)
|
||||
(ppcre:scan-to-strings (rule-regex rule) url-string)
|
||||
(when matchp
|
||||
(values matchp
|
||||
(if (regex-rule-p rule)
|
||||
`(:captures ,(coerce values 'list))
|
||||
(loop for key in (rule-param-keys rule)
|
||||
for val across values
|
||||
if (eq key :splat)
|
||||
collect val into splat
|
||||
else if val
|
||||
append (list key val) into result
|
||||
finally
|
||||
(return (if splat
|
||||
`(:splat ,splat ,@result)
|
||||
result)))))))))
|
||||
|
||||
(defun equal-rule (rule1 rule2)
|
||||
(and (let ((rule2-methods (rule-methods rule2)))
|
||||
(every (lambda (rule)
|
||||
(ms-member-p rule2-methods rule))
|
||||
(map-set-index (rule-methods rule1))))
|
||||
(string= (rule-url rule1) (rule-url rule2))))
|
35
src/util.lisp
Normal file
35
src/util.lisp
Normal file
|
@ -0,0 +1,35 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway.util
|
||||
(:use :cl)
|
||||
(:import-from :cl-utilities
|
||||
:with-collectors)
|
||||
(:export :make-collector
|
||||
:function-name))
|
||||
(in-package :myway.util)
|
||||
|
||||
(defun make-collector ()
|
||||
(let ((none '#:none))
|
||||
(declare (dynamic-extent none))
|
||||
(with-collectors (buffer)
|
||||
(return-from make-collector
|
||||
(lambda (&optional (data none))
|
||||
(unless (eq data none)
|
||||
(buffer data))
|
||||
buffer)))))
|
||||
|
||||
(defun function-name (fn)
|
||||
(when (symbolp fn)
|
||||
(return-from function-name fn))
|
||||
#+ccl (ccl:function-name fn)
|
||||
#-ccl
|
||||
(multiple-value-bind (lambda closurep name) (function-lambda-expression fn)
|
||||
(declare (ignore closurep))
|
||||
(cond
|
||||
(lambda nil)
|
||||
((and (listp name)
|
||||
(or (eq (car name) 'labels)
|
||||
(eq (car name) 'flet)))
|
||||
(cadr name))
|
||||
((and (listp name)
|
||||
(eq (car name) 'lambda)) nil)
|
||||
(T name))))
|
62
t/myway.lisp
Normal file
62
t/myway.lisp
Normal file
|
@ -0,0 +1,62 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway-test
|
||||
(:use :cl
|
||||
:myway
|
||||
:prove))
|
||||
(in-package :myway-test)
|
||||
|
||||
(plan nil)
|
||||
|
||||
(defparameter *mapper* (make-mapper))
|
||||
|
||||
(is (find-route *mapper* "/") nil)
|
||||
(is-values (dispatch *mapper* "/" :method :GET) '(nil nil))
|
||||
|
||||
(connect *mapper* "/"
|
||||
(lambda (params)
|
||||
(declare (ignore params))
|
||||
"Hello, World!"))
|
||||
|
||||
(ok (find-route *mapper* "/"))
|
||||
(is (find-route *mapper* "/" :method :POST) nil)
|
||||
(is (dispatch *mapper* "/" :method :GET) "Hello, World!")
|
||||
|
||||
(connect *mapper* "/post"
|
||||
(lambda (params)
|
||||
(declare (ignore params))
|
||||
"posted")
|
||||
:method :POST)
|
||||
|
||||
(is (find-route *mapper* "/post") nil)
|
||||
(ok (find-route *mapper* "/post" :method :POST))
|
||||
(is-values (dispatch *mapper* "/post" :method :GET) '(nil nil))
|
||||
(is (dispatch *mapper* "/post" :method :POST) "posted")
|
||||
|
||||
(connect *mapper* "/new"
|
||||
(lambda (params)
|
||||
(declare (ignore params))
|
||||
"new"))
|
||||
|
||||
(is (dispatch *mapper* "/new" :method :GET) "new")
|
||||
|
||||
(is (funcall (to-app *mapper*)
|
||||
'(:method :GET :path-info "/"))
|
||||
"Hello, World!")
|
||||
|
||||
(connect *mapper* "/id/:n"
|
||||
(lambda (params)
|
||||
(if (oddp (parse-integer (getf params :n)))
|
||||
"odd"
|
||||
(next-route)))
|
||||
:name 'odd-id)
|
||||
|
||||
(connect *mapper* "/id/*"
|
||||
(lambda (params)
|
||||
(declare (ignore params))
|
||||
"even")
|
||||
:name 'even-id)
|
||||
|
||||
(is (dispatch *mapper* "/id/1") "odd")
|
||||
(is (dispatch *mapper* "/id/2") "even")
|
||||
|
||||
(finalize)
|
131
t/rule.lisp
Normal file
131
t/rule.lisp
Normal file
|
@ -0,0 +1,131 @@
|
|||
(in-package :cl-user)
|
||||
(defpackage myway-test.rule
|
||||
(:use :cl
|
||||
:myway.rule
|
||||
:prove))
|
||||
(in-package :myway-test.rule)
|
||||
|
||||
(plan nil)
|
||||
|
||||
(subtest "method"
|
||||
(ok (match-rule (make-rule "/hello")
|
||||
:GET "/hello")
|
||||
"GET")
|
||||
|
||||
(ok (match-rule (make-rule "/hello" :method :GET)
|
||||
:GET "/hello")
|
||||
"GET")
|
||||
|
||||
(ok (match-rule (make-rule "/hello" :method :GET)
|
||||
:HEAD "/hello" :allow-head t)
|
||||
"HEAD is allowed for GET rule")
|
||||
|
||||
(ok (match-rule (make-rule "/new" :method :POST)
|
||||
:POST "/new"))
|
||||
|
||||
(is (match-rule (make-rule "/new" :method :POST)
|
||||
:GET "/new")
|
||||
nil
|
||||
"GET fails for POST rule")
|
||||
|
||||
(ok (match-rule (make-rule "/new" :method '(:GET :POST))
|
||||
:GET "/new")
|
||||
"GET or POST")
|
||||
|
||||
(ok (match-rule (make-rule "/new" :method '(:GET :POST))
|
||||
:POST "/new")
|
||||
"GET or POST")
|
||||
|
||||
(ok (match-rule (make-rule "/new" :method '(:ANY))
|
||||
:POST "/new")
|
||||
"ANY"))
|
||||
|
||||
(subtest "with named parameters"
|
||||
(is-values (match-rule (make-rule "/hello/:name" :method :GET)
|
||||
:GET "/hello/fukamachi")
|
||||
'("/hello/fukamachi" (:name "fukamachi"))
|
||||
"match")
|
||||
(is-values (match-rule (make-rule "/hello/:name" :method :GET)
|
||||
:GET "/hello/fukamachi/eitaro")
|
||||
'(nil)
|
||||
"containing a slash")
|
||||
(is-values (match-rule (make-rule "/hello/:name" :method :GET)
|
||||
:GET "/bye/fukamachi")
|
||||
'(nil)
|
||||
"not match")
|
||||
(is-values (match-rule (make-rule "/blog/:post-id" :method :GET)
|
||||
:GET "/blog/10")
|
||||
'("/blog/10" (:post-id "10")))
|
||||
(is-values (match-rule (make-rule "/tag/:tag" :method :GET)
|
||||
:GET "/tag/#lisp")
|
||||
'("/tag/#lisp" (:tag "#lisp")))
|
||||
(is-values (match-rule (make-rule "/hello/?:name?" :method :GET)
|
||||
:GET "/hello/Eitaro")
|
||||
'("/hello/Eitaro" (:name "Eitaro")))
|
||||
(is-values (match-rule (make-rule "/hello/?:name?" :method :GET)
|
||||
:GET "/hello/")
|
||||
'("/hello/" nil))
|
||||
(is-values (match-rule (make-rule "/hello/?:name?" :method :GET)
|
||||
:GET "/hello")
|
||||
'("/hello" nil))
|
||||
(is-values (match-rule (make-rule "/say/:hello/to/:name" :method :GET)
|
||||
:GET "/say/hello/to/fukamachi")
|
||||
'("/say/hello/to/fukamachi" (:hello "hello" :name "fukamachi"))
|
||||
"multiple named parameters"))
|
||||
|
||||
(subtest "splat"
|
||||
(is-values (match-rule (make-rule "/say/*/to/*" :method :GET)
|
||||
:GET "/say/hello/to/world")
|
||||
'("/say/hello/to/world" (:splat ("hello" "world")))))
|
||||
|
||||
(subtest "regex rules"
|
||||
(is-values (match-rule (make-rule "/hello/([\\w]+)" :method :GET :regexp t)
|
||||
:GET "/hello/world")
|
||||
'("/hello/world" (:captures ("world")))))
|
||||
|
||||
(subtest "optional parameters"
|
||||
(is-values (match-rule (make-rule "/?:foo?/?:bar?" :method :GET)
|
||||
:GET "/hello/world")
|
||||
'("/hello/world" (:foo "hello" :bar "world")))
|
||||
(is-values (match-rule (make-rule "/?:foo?/?:bar?" :method :GET)
|
||||
:GET "/hello")
|
||||
'("/hello" (:foo "hello")))
|
||||
(is-values (match-rule (make-rule "/?:foo?/?:bar?" :method :GET)
|
||||
:GET "/")
|
||||
'("/" nil))
|
||||
(is-values (match-rule (make-rule "/hello/?:name?" :method :GET)
|
||||
:GET "/hello")
|
||||
'("/hello" nil)))
|
||||
|
||||
(subtest "splat and normal cases"
|
||||
(is-values (match-rule (make-rule "/:foo/*" :method :GET)
|
||||
:GET "/foo/bar/baz")
|
||||
'("/foo/bar/baz" (:splat ("bar/baz") :foo "foo"))))
|
||||
|
||||
(subtest "escape"
|
||||
(is-values (match-rule (make-rule "/te+st/" :method :GET)
|
||||
:GET "/te%2Bst/")
|
||||
'("/te%2Bst/" nil)
|
||||
"escape +")
|
||||
(is-values (match-rule (make-rule "/te st/" :method :GET)
|
||||
:GET "/te%2Bst/")
|
||||
'("/te%2Bst/" nil)
|
||||
"escape space")
|
||||
(is-values (match-rule (make-rule "/test$/" :method :GET)
|
||||
:GET "/test%24/")
|
||||
'("/test%24/" nil)
|
||||
"escape $")
|
||||
(is-values (match-rule (make-rule "/te.st/" :method :GET)
|
||||
:GET "/te.st/")
|
||||
'("/te.st/" nil)
|
||||
"escape .")
|
||||
(is-values (match-rule (make-rule "/te.st/" :method :GET)
|
||||
:GET "/te0st/")
|
||||
'(nil)
|
||||
"escape .")
|
||||
(is-values (match-rule (make-rule "/test(bar)/" :method :GET)
|
||||
:GET "/test%28bar%29/")
|
||||
'("/test%28bar%29/" nil)
|
||||
"escape ()"))
|
||||
|
||||
(finalize)
|
Loading…
Add table
Reference in a new issue