xenon 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +18 -0
- data/.gitignore +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +10 -0
- data/Guardfile +0 -32
- data/README.md +59 -5
- data/examples/hello_world/config.ru +3 -0
- data/examples/hello_world/hello_world.rb +17 -0
- data/lib/xenon.rb +62 -49
- data/lib/xenon/auth.rb +63 -0
- data/lib/xenon/errors.rb +1 -0
- data/lib/xenon/etag.rb +48 -0
- data/lib/xenon/headers.rb +2 -4
- data/lib/xenon/headers/accept.rb +3 -2
- data/lib/xenon/headers/accept_charset.rb +5 -5
- data/lib/xenon/headers/accept_encoding.rb +5 -5
- data/lib/xenon/headers/accept_language.rb +5 -5
- data/lib/xenon/headers/authorization.rb +7 -53
- data/lib/xenon/headers/cache_control.rb +3 -3
- data/lib/xenon/headers/content_type.rb +1 -1
- data/lib/xenon/headers/if_match.rb +53 -0
- data/lib/xenon/headers/if_modified_since.rb +22 -0
- data/lib/xenon/headers/if_none_match.rb +53 -0
- data/lib/xenon/headers/if_range.rb +45 -0
- data/lib/xenon/headers/if_unmodified_since.rb +22 -0
- data/lib/xenon/headers/user_agent.rb +65 -0
- data/lib/xenon/headers/www_authenticate.rb +70 -0
- data/lib/xenon/media_type.rb +24 -2
- data/lib/xenon/parsers/basic_rules.rb +38 -7
- data/lib/xenon/parsers/header_rules.rb +49 -3
- data/lib/xenon/parsers/media_type.rb +4 -3
- data/lib/xenon/quoted_string.rb +11 -1
- data/lib/xenon/routing/directives.rb +14 -0
- data/lib/xenon/routing/header_directives.rb +32 -0
- data/lib/xenon/routing/method_directives.rb +26 -0
- data/lib/xenon/routing/param_directives.rb +22 -0
- data/lib/xenon/routing/path_directives.rb +37 -0
- data/lib/xenon/routing/route_directives.rb +51 -0
- data/lib/xenon/routing/security_directives.rb +20 -0
- data/lib/xenon/version.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/xenon/etag_spec.rb +19 -0
- data/spec/xenon/headers/if_match_spec.rb +73 -0
- data/spec/xenon/headers/if_modified_since_spec.rb +19 -0
- data/spec/xenon/headers/if_none_match_spec.rb +79 -0
- data/spec/xenon/headers/if_range_spec.rb +45 -0
- data/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
- data/spec/xenon/headers/user_agent_spec.rb +67 -0
- data/spec/xenon/headers/www_authenticate_spec.rb +43 -0
- data/xenon.gemspec +4 -3
- metadata +60 -10
- data/lib/xenon/routing.rb +0 -133
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
class Headers
|
5
|
+
# http://tools.ietf.org/html/rfc7232#section-3.4
|
6
|
+
class IfUnmodifiedSince < Header 'If-Unmodified-Since'
|
7
|
+
attr_reader :date
|
8
|
+
|
9
|
+
def initialize(date)
|
10
|
+
@date = date
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parse(s)
|
14
|
+
new(Time.httpdate(s))
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@date.httpdate
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class Product
|
6
|
+
attr_reader :name, :version, :comment
|
7
|
+
|
8
|
+
def initialize(name, version = nil, comment = nil)
|
9
|
+
@name = name
|
10
|
+
@version = version
|
11
|
+
@comment = comment
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
s = ''
|
16
|
+
s << @name if @name
|
17
|
+
s << '/' << @version if @version
|
18
|
+
if @comment
|
19
|
+
s << ' ' unless s.empty?
|
20
|
+
s << '(' << @comment << ')'
|
21
|
+
end
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.5.3
|
28
|
+
class UserAgent < Header 'User-Agent'
|
29
|
+
attr_reader :products
|
30
|
+
|
31
|
+
def initialize(*products)
|
32
|
+
@products = products
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::UserAgentHeader.new.parse(s)
|
37
|
+
Parsers::UserAgentHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
@products.map(&:to_s).join(' ')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Parsers
|
47
|
+
class UserAgentHeader < Parslet::Parser
|
48
|
+
include HeaderRules
|
49
|
+
rule(:product) { (token.as(:name) >> (str('/') >> token.as(:version)).maybe >> (rws >> comment.as(:comment)).maybe).as(:product) }
|
50
|
+
rule(:product_comment) { comment.as(:product_comment) }
|
51
|
+
rule(:user_agent) { (product >> (rws >> (product | product_comment)).repeat).as(:user_agent) }
|
52
|
+
root(:user_agent)
|
53
|
+
end
|
54
|
+
|
55
|
+
class UserAgentHeaderTransform < HeaderTransform
|
56
|
+
rule(product: { name: simple(:p), version: simple(:v), comment: simple(:c) }) { Product.new(p, v, c) }
|
57
|
+
rule(product: { name: simple(:p), version: simple(:v) }) { Product.new(p, v) }
|
58
|
+
rule(product: { name: simple(:p), comment: simple(:c) }) { Product.new(p, nil, c) }
|
59
|
+
rule(product: { name: simple(:p) }) { Product.new(p) }
|
60
|
+
rule(product_comment: simple(:c)) { Product.new(nil, nil, c) }
|
61
|
+
rule(user_agent: sequence(:p)) { Headers::UserAgent.new(*p) }
|
62
|
+
rule(user_agent: simple(:p)) { Headers::UserAgent.new(p) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'xenon/headers'
|
3
|
+
require 'xenon/parsers/header_rules'
|
4
|
+
require 'xenon/quoted_string'
|
5
|
+
|
6
|
+
module Xenon
|
7
|
+
class Headers
|
8
|
+
class Challenge
|
9
|
+
extend Forwardable
|
10
|
+
using QuotedString
|
11
|
+
|
12
|
+
attr_reader :auth_scheme
|
13
|
+
def_delegators :@params, :key?, :include?, :[]
|
14
|
+
|
15
|
+
def initialize(auth_scheme, params = {})
|
16
|
+
@auth_scheme = auth_scheme
|
17
|
+
@params = params.with_indifferent_access
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(name, *args, &block)
|
21
|
+
name = name.to_sym
|
22
|
+
@params.key?(name) ? @params[name] : super
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to_missing?(name, include_all)
|
26
|
+
@params.key?(name.to_sym) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
param_string = @params.map { |k, v| "#{k}=#{v.quote}"}.join(', ')
|
31
|
+
"#{@auth_scheme} #{param_string}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# https://tools.ietf.org/html/rfc7235#section-4.1
|
36
|
+
class WWWAuthenticate < ListHeader 'WWW-Authenticate'
|
37
|
+
def initialize(*challenges)
|
38
|
+
super(challenges)
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :challenges, :values
|
42
|
+
|
43
|
+
def self.parse(s)
|
44
|
+
tree = Parsers::WWWAuthenticateHeader.new.parse(s)
|
45
|
+
Parsers::WWWAuthenticateHeaderTransform.new.apply(tree)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
challenges.map(&:to_s).join(', ')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Parsers
|
55
|
+
class WWWAuthenticateHeader < Parslet::Parser
|
56
|
+
include AuthHeaderRules
|
57
|
+
rule(:challenge) { (auth_scheme >> sp >> (auth_params | token68)).as(:challenge) }
|
58
|
+
rule(:www_authenticate) { (challenge >> (comma >> challenge).repeat).as(:www_authenticate) }
|
59
|
+
root(:www_authenticate)
|
60
|
+
end
|
61
|
+
|
62
|
+
class WWWAuthenticateHeaderTransform < HeaderTransform
|
63
|
+
rule(auth_param: { name: simple(:n), value: simple(:v) }) { Tuple.new(n, v) }
|
64
|
+
rule(challenge: { auth_scheme: simple(:s), auth_params: simple(:p) }) { Headers::Challenge.new(s, Hash[*p.to_a]) }
|
65
|
+
rule(challenge: { auth_scheme: simple(:s), auth_params: sequence(:p) }) { Headers::Challenge.new(s, Hash[p.map(&:to_a)]) }
|
66
|
+
rule(www_authenticate: simple(:c)) { Headers::WWWAuthenticate.new(c) }
|
67
|
+
rule(www_authenticate: sequence(:c)) { Headers::WWWAuthenticate.new(*c) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/xenon/media_type.rb
CHANGED
@@ -3,20 +3,33 @@ require 'xenon/parsers/media_type'
|
|
3
3
|
|
4
4
|
module Xenon
|
5
5
|
|
6
|
+
# A media type.
|
7
|
+
#
|
8
|
+
# @see ContentType
|
9
|
+
# @see MediaRange
|
6
10
|
class MediaType
|
7
11
|
attr_reader :type, :subtype, :params
|
8
12
|
|
13
|
+
# Initializes a new instance of MediaType.
|
14
|
+
#
|
15
|
+
# @param type [String] The main type, e.g. 'application'.
|
16
|
+
# @param subtype [String] The subtype, e.g. 'json'.
|
17
|
+
# @param params [Hash] Any params for the media type; don't use 'q' or 'charset'.
|
9
18
|
def initialize(type, subtype, params = {})
|
10
19
|
@type = type
|
11
20
|
@subtype = subtype
|
12
21
|
@params = params
|
13
22
|
end
|
14
23
|
|
24
|
+
# Parses a media type.
|
25
|
+
#
|
26
|
+
# @param s [String] The media type string.
|
27
|
+
# @return [MediaType] The media type.
|
15
28
|
def self.parse(s)
|
16
29
|
tree = Parsers::MediaType.new.parse(s)
|
17
30
|
Parsers::MediaTypeTransform.new.apply(tree)
|
18
31
|
rescue Parslet::ParseFailed
|
19
|
-
raise Xenon::ParseError.new("Invalid media type (#{s})")
|
32
|
+
raise Xenon::ParseError.new("Invalid media type (#{s}).")
|
20
33
|
end
|
21
34
|
|
22
35
|
%w(application audio image message multipart text video).each do |type|
|
@@ -43,10 +56,18 @@ module Xenon
|
|
43
56
|
end
|
44
57
|
end
|
45
58
|
|
59
|
+
# Creates a {MediaRange} using this media type with a quality factor.
|
60
|
+
#
|
61
|
+
# @param q [Numeric] A value between 1.0 (most desirable) and 0.0 (not acceptable).
|
62
|
+
# @return [MediaRange] The media range.
|
46
63
|
def with_q(q)
|
47
64
|
MediaRange.new(self, q)
|
48
65
|
end
|
49
66
|
|
67
|
+
# Creates a {ContentType} using this media type with a charset.
|
68
|
+
#
|
69
|
+
# @param charset [String] The desired charset, e.g. 'utf-8'.
|
70
|
+
# @return [ContentType] The content type.
|
50
71
|
def with_charset(charset)
|
51
72
|
ContentType.new(self, charset)
|
52
73
|
end
|
@@ -59,6 +80,7 @@ module Xenon
|
|
59
80
|
XML = MediaType.new('application', 'xml')
|
60
81
|
end
|
61
82
|
|
83
|
+
# A content type.
|
62
84
|
class ContentType
|
63
85
|
attr_reader :media_type, :charset
|
64
86
|
|
@@ -109,7 +131,7 @@ module Xenon
|
|
109
131
|
dp = params.size <=> other.params.size
|
110
132
|
return dp if dp != 0
|
111
133
|
@q <=> other.q
|
112
|
-
end
|
134
|
+
end
|
113
135
|
|
114
136
|
def =~(media_type)
|
115
137
|
(type == '*' || type == media_type.type) &&
|
@@ -3,6 +3,19 @@ require 'parslet'
|
|
3
3
|
module Xenon
|
4
4
|
module Parsers
|
5
5
|
|
6
|
+
# Parslet doesn't match sequence of sequences (i.e. [['foo', 'bar']]) as a sequence(:v) in transform
|
7
|
+
# rules so this is a little wrapper class that allows smuggling an array through the matcher rules,
|
8
|
+
# for example above would be [Tuple.new('foo', 'bar')], when no 'proper' class is required.
|
9
|
+
class Tuple
|
10
|
+
def initialize(*values)
|
11
|
+
@values = values
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_a
|
15
|
+
@values
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
6
19
|
module BasicRules
|
7
20
|
include Parslet
|
8
21
|
|
@@ -35,21 +48,39 @@ module Xenon
|
|
35
48
|
rule(:tchar) { alpha | digit | match(/[!#\$%&'\*\+\-\.\^_`\|~]/) }
|
36
49
|
rule(:token) { tchar.repeat(1) }
|
37
50
|
|
38
|
-
|
39
|
-
rule(:
|
40
|
-
rule(:
|
41
|
-
rule(:
|
51
|
+
# http://tools.ietf.org/html/rfc7231#section-7.1.1.1
|
52
|
+
rule(:day_name) { str('Mon') | str('Tue') | str('Wed') | str('Thu') | str('Fri') | str('Sat') | str('Sun') }
|
53
|
+
rule(:day) { digit.repeat(2) }
|
54
|
+
rule(:month) { (str('Jan') | str('Feb') | str('Mar') | str('Apr') | str('May') | str('Jun') | str('Jul') | str('Aug') | str('Sep') | str('Oct') | str('Nov') | str('Dec')) }
|
55
|
+
rule(:year) { digit.repeat(4) }
|
56
|
+
rule(:date1) { day >> sp >> month >> sp >> year }
|
57
|
+
rule(:gmt) { str('GMT') }
|
58
|
+
rule(:hour) { digit.repeat(2) }
|
59
|
+
rule(:minute) { digit.repeat(2) }
|
60
|
+
rule(:second) { digit.repeat(2) }
|
61
|
+
rule(:time_of_day) { hour >> str(':') >> minute >> str(':') >> second }
|
62
|
+
rule(:imf_fixdate) { day_name >> str(',') >> sp >> date1 >> sp >> time_of_day >> sp >> gmt }
|
63
|
+
rule(:day_name_l) { str('Monday') | str('Tuesday') | str('Wednesday') | str('Thursday') | str('Friday') | str('Saturday') | str('Sunday') }
|
64
|
+
rule(:year2) { digit.repeat(2) }
|
65
|
+
rule(:date2) { day >> str('-') >> month >> str('-') >> year2 }
|
66
|
+
rule(:rfc850_date) { day_name_l >> str(',') >> sp >> date2 >> sp >> time_of_day >> sp >> gmt }
|
67
|
+
rule(:day1) { sp >> digit }
|
68
|
+
rule(:date3) { month >> sp >> (day | day1) }
|
69
|
+
rule(:asctime_date) { day_name >> sp >> date3 >> sp >> time_of_day >> sp >> year }
|
70
|
+
rule(:obs_date) { rfc850_date | asctime_date }
|
71
|
+
rule(:http_date) { (imf_fixdate | obs_date).as(:http_date) }
|
42
72
|
|
43
|
-
# extras -- TODO: move these into header rules?
|
73
|
+
# extras -- TODO: move these into header rules?
|
44
74
|
rule(:comma) { str(',') >> sp? }
|
45
75
|
rule(:semicolon) { str(';') >> sp? }
|
46
76
|
end
|
47
77
|
|
48
78
|
class BasicTransform < Parslet::Transform
|
49
79
|
rule(simple(:v)) { v.respond_to?(:str) ? v.str : v }
|
50
|
-
|
80
|
+
|
51
81
|
rule(quoted_string: simple(:qs)) { qs[1..-2].gsub(/\\(.)/, '\1') }
|
82
|
+
rule(http_date: simple(:str)) { Time.httpdate(str) }
|
52
83
|
end
|
53
|
-
|
84
|
+
|
54
85
|
end
|
55
86
|
end
|
@@ -1,13 +1,59 @@
|
|
1
|
+
require 'xenon/quoted_string'
|
1
2
|
require 'xenon/parsers/basic_rules'
|
2
3
|
|
3
4
|
module Xenon
|
4
5
|
module Parsers
|
5
6
|
|
6
|
-
|
7
|
-
module WeightRules
|
7
|
+
module HeaderRules
|
8
8
|
include Parslet, BasicRules
|
9
|
+
|
10
|
+
# http://tools.ietf.org/html/rfc7230#section-3.2.6
|
11
|
+
rule(:list_sep) { str(',') >> sp? }
|
12
|
+
rule(:param_sep) { str(';') >> sp? }
|
13
|
+
rule(:obs_text) { match(/[\u0080-\u00ff]/)}
|
14
|
+
rule(:qdtext) { htab | sp | match(/[\u0021\u0023-\u005b\u005d-\u007e]/) | obs_text }
|
15
|
+
rule(:quoted_pair) { str('\\') >> (htab | sp | vchar | obs_text) }
|
16
|
+
rule(:quoted_string) { (dquote >> (qdtext | quoted_pair).repeat >> dquote).as(:quoted_string) }
|
17
|
+
rule(:ctext) { htab | sp | match(/[\u0021-\u0027\u002a-\u005b\u005d-\u007e]/) | obs_text }
|
18
|
+
rule(:comment) { (str('(') >> (ctext | quoted_pair | comment).repeat >> str(')')).as(:comment) }
|
19
|
+
|
20
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.1
|
9
21
|
rule(:weight_value) { (digit >> (str('.') >> digit.repeat(0, 3)).maybe).as(:q) }
|
10
|
-
rule(:weight) {
|
22
|
+
rule(:weight) { param_sep >> str('q') >> sp? >> str('=') >> sp? >> weight_value >> sp? }
|
23
|
+
end
|
24
|
+
|
25
|
+
module AuthHeaderRules
|
26
|
+
include Parslet, HeaderRules
|
27
|
+
|
28
|
+
rule(:token68) { ((alpha | digit | match(/[\-\._~\+\/]/)) >> str('=').repeat).repeat(1).as(:token) }
|
29
|
+
rule(:auth_scheme) { token.as(:auth_scheme) }
|
30
|
+
rule(:name) { token.as(:name) }
|
31
|
+
rule(:value) { token.as(:value) }
|
32
|
+
rule(:auth_param) { (name >> bws >> str('=') >> bws >> (token | quoted_string).as(:value)).as(:auth_param) }
|
33
|
+
rule(:auth_params) { (auth_param.maybe >> (ows >> comma >> ows >> auth_param).repeat).as(:auth_params) }
|
34
|
+
end
|
35
|
+
|
36
|
+
module ETagHeaderRules
|
37
|
+
include Parslet, HeaderRules
|
38
|
+
|
39
|
+
# http://tools.ietf.org/html/rfc7232#section-2.3
|
40
|
+
rule(:wildcard) { str('*').as(:wildcard) }
|
41
|
+
rule(:weak) { str('W/').as(:weak) }
|
42
|
+
rule(:etagc) { str('!') | match(/[\u0023-\u007e#-~]/) | obs_text }
|
43
|
+
rule(:opaque_tag) { dquote >> etagc.repeat.as(:opaque_tag) >> dquote }
|
44
|
+
rule(:etag) { (weak.maybe >> opaque_tag).as(:etag) }
|
45
|
+
end
|
46
|
+
|
47
|
+
class HeaderTransform < BasicTransform
|
48
|
+
using QuotedString
|
49
|
+
|
50
|
+
rule(quoted_string: simple(:qs)) { qs.unquote }
|
51
|
+
rule(comment: simple(:c)) { c.uncomment }
|
52
|
+
end
|
53
|
+
|
54
|
+
class ETagHeaderTransform < HeaderTransform
|
55
|
+
rule(etag: { opaque_tag: simple(:t), weak: simple(:w) }) { Xenon::ETag.new(t, weak: true) }
|
56
|
+
rule(etag: { opaque_tag: simple(:t) }) { Xenon::ETag.new(t) }
|
11
57
|
end
|
12
58
|
|
13
59
|
end
|
@@ -16,10 +16,11 @@ module Xenon
|
|
16
16
|
rule(:slash) { str('/') }
|
17
17
|
rule(:subtype) { restricted_name.as(:subtype) >> sp? }
|
18
18
|
|
19
|
+
rule(:param_sep) { str(';') >> sp? }
|
19
20
|
rule(:param_name) { restricted_name.as(:param_name) >> sp? }
|
20
21
|
rule(:equals) { str('=') >> sp? }
|
21
22
|
rule(:param_value) { token.as(:param_value) >> sp? } # not quite correct but probably correct enough
|
22
|
-
rule(:param) {
|
23
|
+
rule(:param) { param_sep >> param_name >> (equals >> param_value).maybe >> sp? }
|
23
24
|
rule(:params) { param.repeat.as(:params) }
|
24
25
|
|
25
26
|
rule(:media_type) { (type >> slash >> subtype >> params).as(:media_type) >> sp? }
|
@@ -44,8 +45,8 @@ module Xenon
|
|
44
45
|
rule(param_name: simple(:n), param_value: simple(:v)) { [n.str, v.str] }
|
45
46
|
rule(param_name: simple(:n)) { [n.str, nil] }
|
46
47
|
rule(type: simple(:t), subtype: simple(:s), params: subtree(:p)) { { type: t.str, subtype: s.str, params: Hash[p] } }
|
47
|
-
rule(media_type: subtree(:mt)) {
|
48
|
-
rule(media_range: subtree(:mr)) {
|
48
|
+
rule(media_type: subtree(:mt)) { Xenon::MediaType.new(mt[:type], mt[:subtype], mt[:params])}
|
49
|
+
rule(media_range: subtree(:mr)) { Xenon::MediaRange.new(mr[:type], mr[:subtype], mr[:params])}
|
49
50
|
end
|
50
51
|
|
51
52
|
end
|
data/lib/xenon/quoted_string.rb
CHANGED
@@ -2,9 +2,19 @@ module Xenon
|
|
2
2
|
module QuotedString
|
3
3
|
refine String do
|
4
4
|
def quote
|
5
|
-
qs =
|
5
|
+
qs = gsub(/([\\"])/, '\\\\\1')
|
6
6
|
self == qs ? self : %{"#{qs}"}
|
7
7
|
end
|
8
|
+
|
9
|
+
def unquote
|
10
|
+
qs = start_with?('"') && end_with?('"') ? self[1..-2] : self
|
11
|
+
qs.gsub(/\\(.)/, '\1')
|
12
|
+
end
|
13
|
+
|
14
|
+
def uncomment
|
15
|
+
qs = start_with?('(') && end_with?(')') ? self[1..-2] : self
|
16
|
+
qs.gsub(/\\(.)/, '\1')
|
17
|
+
end
|
8
18
|
end
|
9
19
|
end
|
10
20
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Dir[File.join(__dir__, '*_directives.rb')].each { |f| require f }
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module Directives
|
6
|
+
include RouteDirectives
|
7
|
+
include HeaderDirectives
|
8
|
+
include MethodDirectives
|
9
|
+
include ParamDirectives
|
10
|
+
include PathDirectives
|
11
|
+
include SecurityDirectives
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'xenon/routing/route_directives'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
module Routing
|
5
|
+
module HeaderDirectives
|
6
|
+
include RouteDirectives
|
7
|
+
|
8
|
+
def optional_header(name)
|
9
|
+
extract_request do |request|
|
10
|
+
yield request.header(name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def header(name)
|
15
|
+
optional_header(name) do |value|
|
16
|
+
if value
|
17
|
+
yield value
|
18
|
+
else
|
19
|
+
reject Rejection.new(:header, { required: name })
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_with_header(header)
|
25
|
+
map_response -> r { r.copy(headers: r.headers.add(header)) } do
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|