xenon-routing 0.0.4

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +18 -0
  3. data/.gitignore +25 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +20 -0
  7. data/Guardfile +16 -0
  8. data/LICENSE +22 -0
  9. data/README.md +116 -0
  10. data/Rakefile +40 -0
  11. data/VERSION +1 -0
  12. data/examples/hello_world/config.ru +3 -0
  13. data/examples/hello_world/hello_world.rb +27 -0
  14. data/xenon-http/lib/xenon/auth.rb +63 -0
  15. data/xenon-http/lib/xenon/errors.rb +5 -0
  16. data/xenon-http/lib/xenon/etag.rb +48 -0
  17. data/xenon-http/lib/xenon/headers.rb +112 -0
  18. data/xenon-http/lib/xenon/headers/accept.rb +34 -0
  19. data/xenon-http/lib/xenon/headers/accept_charset.rb +59 -0
  20. data/xenon-http/lib/xenon/headers/accept_encoding.rb +63 -0
  21. data/xenon-http/lib/xenon/headers/accept_language.rb +59 -0
  22. data/xenon-http/lib/xenon/headers/authorization.rb +50 -0
  23. data/xenon-http/lib/xenon/headers/cache_control.rb +56 -0
  24. data/xenon-http/lib/xenon/headers/content_type.rb +23 -0
  25. data/xenon-http/lib/xenon/headers/if_match.rb +53 -0
  26. data/xenon-http/lib/xenon/headers/if_modified_since.rb +22 -0
  27. data/xenon-http/lib/xenon/headers/if_none_match.rb +53 -0
  28. data/xenon-http/lib/xenon/headers/if_range.rb +45 -0
  29. data/xenon-http/lib/xenon/headers/if_unmodified_since.rb +22 -0
  30. data/xenon-http/lib/xenon/headers/user_agent.rb +65 -0
  31. data/xenon-http/lib/xenon/headers/www_authenticate.rb +71 -0
  32. data/xenon-http/lib/xenon/http.rb +7 -0
  33. data/xenon-http/lib/xenon/http_version.rb +3 -0
  34. data/xenon-http/lib/xenon/media_type.rb +162 -0
  35. data/xenon-http/lib/xenon/parsers/basic_rules.rb +86 -0
  36. data/xenon-http/lib/xenon/parsers/header_rules.rb +60 -0
  37. data/xenon-http/lib/xenon/parsers/media_type.rb +53 -0
  38. data/xenon-http/lib/xenon/quoted_string.rb +20 -0
  39. data/xenon-http/spec/spec_helper.rb +94 -0
  40. data/xenon-http/spec/xenon/etag_spec.rb +19 -0
  41. data/xenon-http/spec/xenon/headers/accept_charset_spec.rb +31 -0
  42. data/xenon-http/spec/xenon/headers/accept_encoding_spec.rb +40 -0
  43. data/xenon-http/spec/xenon/headers/accept_language_spec.rb +33 -0
  44. data/xenon-http/spec/xenon/headers/accept_spec.rb +54 -0
  45. data/xenon-http/spec/xenon/headers/authorization_spec.rb +47 -0
  46. data/xenon-http/spec/xenon/headers/cache_control_spec.rb +64 -0
  47. data/xenon-http/spec/xenon/headers/if_match_spec.rb +73 -0
  48. data/xenon-http/spec/xenon/headers/if_modified_since_spec.rb +19 -0
  49. data/xenon-http/spec/xenon/headers/if_none_match_spec.rb +79 -0
  50. data/xenon-http/spec/xenon/headers/if_range_spec.rb +45 -0
  51. data/xenon-http/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
  52. data/xenon-http/spec/xenon/headers/user_agent_spec.rb +67 -0
  53. data/xenon-http/spec/xenon/headers/www_authenticate_spec.rb +43 -0
  54. data/xenon-http/spec/xenon/media_type_spec.rb +267 -0
  55. data/xenon-http/xenon-http.gemspec +25 -0
  56. data/xenon-routing/lib/xenon/api.rb +118 -0
  57. data/xenon-routing/lib/xenon/marshallers.rb +48 -0
  58. data/xenon-routing/lib/xenon/request.rb +40 -0
  59. data/xenon-routing/lib/xenon/response.rb +29 -0
  60. data/xenon-routing/lib/xenon/routing.rb +6 -0
  61. data/xenon-routing/lib/xenon/routing/context.rb +35 -0
  62. data/xenon-routing/lib/xenon/routing/directives.rb +14 -0
  63. data/xenon-routing/lib/xenon/routing/header_directives.rb +32 -0
  64. data/xenon-routing/lib/xenon/routing/method_directives.rb +26 -0
  65. data/xenon-routing/lib/xenon/routing/param_directives.rb +22 -0
  66. data/xenon-routing/lib/xenon/routing/path_directives.rb +37 -0
  67. data/xenon-routing/lib/xenon/routing/route_directives.rb +51 -0
  68. data/xenon-routing/lib/xenon/routing/security_directives.rb +34 -0
  69. data/xenon-routing/lib/xenon/routing_version.rb +3 -0
  70. data/xenon-routing/spec/spec_helper.rb +94 -0
  71. data/xenon-routing/xenon-routing.gemspec +25 -0
  72. data/xenon.gemspec +26 -0
  73. metadata +145 -0
@@ -0,0 +1,112 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module Xenon
4
+ class Headers
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @hash = {}
9
+ end
10
+
11
+ def initialize_dup(other)
12
+ super
13
+ @hash = @hash.dup
14
+ end
15
+
16
+ def freeze
17
+ @hash.freeze
18
+ super
19
+ end
20
+
21
+ def each(&block)
22
+ @hash.values.each(&block)
23
+ end
24
+
25
+ def set!(header)
26
+ @hash[header.name] = header
27
+ self
28
+ end
29
+
30
+ def add!(header)
31
+ existing = @hash[header.name]
32
+ if existing
33
+ if existing.respond_to?(:merge)
34
+ set!(existing.merge(header))
35
+ else
36
+ raise "Unmergeable header '#{header.name}' already exists"
37
+ end
38
+ else
39
+ set!(header)
40
+ end
41
+ self
42
+ end
43
+
44
+ %i(set add).each do |name|
45
+ define_method name do |header|
46
+ dup.send("#{name}!", header)
47
+ end
48
+ end
49
+
50
+ alias_method :<<, :add!
51
+
52
+ class << self
53
+ def register(klass)
54
+ (@registered ||= {})[klass.const_get(:NAME)] = klass
55
+ end
56
+
57
+ def header_class(name)
58
+ (@registered || {})[name]
59
+ end
60
+
61
+ def Header(name)
62
+ klass = Class.new do
63
+ def name
64
+ self.class.const_get(:NAME)
65
+ end
66
+
67
+ def self.inherited(base)
68
+ Headers.register(base)
69
+ end
70
+ end
71
+ Headers.const_set("#{name.tr('-', '_').classify}Header", klass)
72
+ klass.const_set(:NAME, name)
73
+ klass
74
+ end
75
+
76
+ def ListHeader(name)
77
+ klass = Header(name)
78
+ klass.class_eval do
79
+ attr_reader :values
80
+
81
+ def initialize(values)
82
+ @values = values
83
+ end
84
+
85
+ def merge(other)
86
+ self.class.new(*(@values + other.values))
87
+ end
88
+
89
+ def to_s
90
+ @values.map(&:to_s).join(', ')
91
+ end
92
+ end
93
+ klass
94
+ end
95
+ end
96
+
97
+ class Raw
98
+ attr_reader :name, :value
99
+
100
+ def initialize(name, value)
101
+ @name = name
102
+ @value = value
103
+ end
104
+
105
+ def to_s
106
+ @value
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ Dir[File.join(__dir__, 'headers', '*.rb')].each { |f| require f }
@@ -0,0 +1,34 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+ require 'xenon/parsers/media_type'
4
+
5
+ module Xenon
6
+ class Headers
7
+ # http://tools.ietf.org/html/rfc7231#section-5.3.2
8
+ class Accept < ListHeader 'Accept'
9
+ def initialize(*media_ranges)
10
+ super(media_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
11
+ end
12
+
13
+ alias_method :media_ranges, :values
14
+
15
+ def self.parse(s)
16
+ tree = Parsers::AcceptHeader.new.parse(s)
17
+ Parsers::AcceptHeaderTransform.new.apply(tree)
18
+ end
19
+ end
20
+ end
21
+
22
+ module Parsers
23
+ class AcceptHeader < Parslet::Parser
24
+ include HeaderRules, MediaTypeRules
25
+ rule(:accept) { (media_range >> (list_sep >> media_range).repeat).as(:accept) }
26
+ root(:accept)
27
+ end
28
+
29
+ class AcceptHeaderTransform < MediaTypeTransform
30
+ rule(accept: sequence(:mr)) { Headers::Accept.new(*mr) }
31
+ rule(accept: simple(:mr)) { Headers::Accept.new(mr) }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+
4
+ module Xenon
5
+ class CharsetRange
6
+ attr_reader :charset, :q
7
+
8
+ DEFAULT_Q = 1.0
9
+
10
+ def initialize(charset, q = DEFAULT_Q)
11
+ @charset = charset
12
+ @q = Float(q) || DEFAULT_Q
13
+ end
14
+
15
+ def <=>(other)
16
+ @q <=> other.q
17
+ end
18
+
19
+ def to_s
20
+ s = @charset.dup
21
+ s << "; q=#{@q}" if @q != DEFAULT_Q
22
+ s
23
+ end
24
+ end
25
+
26
+ class Headers
27
+ # http://tools.ietf.org/html/rfc7231#section-5.3.3
28
+ class AcceptCharset < ListHeader 'Accept-Charset'
29
+ def initialize(*charset_ranges)
30
+ super(charset_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
31
+ end
32
+
33
+ alias_method :charset_ranges, :values
34
+
35
+ def self.parse(s)
36
+ tree = Parsers::AcceptCharsetHeader.new.parse(s)
37
+ Parsers::AcceptCharsetHeaderTransform.new.apply(tree)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Parsers
43
+ class AcceptCharsetHeader < Parslet::Parser
44
+ include HeaderRules
45
+ rule(:charset) { token.as(:charset) >> sp? }
46
+ rule(:wildcard) { str('*').as(:charset) >> sp? }
47
+ rule(:charset_range) { (charset | wildcard) >> weight.maybe }
48
+ rule(:accept_charset) { (charset_range >> (list_sep >> charset_range).repeat).as(:accept_charset) }
49
+ root(:accept_charset)
50
+ end
51
+
52
+ class AcceptCharsetHeaderTransform < HeaderTransform
53
+ rule(charset: simple(:c), q: simple(:q)) { CharsetRange.new(c, q) }
54
+ rule(charset: simple(:c)) { CharsetRange.new(c) }
55
+ rule(accept_charset: sequence(:cr)) { Headers::AcceptCharset.new(*cr) }
56
+ rule(accept_charset: simple(:cr)) { Headers::AcceptCharset.new(cr) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+
4
+ module Xenon
5
+ class ContentCodingRange
6
+ attr_reader :coding, :q
7
+
8
+ DEFAULT_Q = 1.0
9
+
10
+ def initialize(coding, q = DEFAULT_Q)
11
+ @coding = coding
12
+ @q = Float(q) || DEFAULT_Q
13
+ end
14
+
15
+ def <=>(other)
16
+ @q <=> other.q
17
+ end
18
+
19
+ def to_s
20
+ s = @coding.dup
21
+ s << "; q=#{@q}" if @q != DEFAULT_Q
22
+ s
23
+ end
24
+ end
25
+
26
+ class Headers
27
+ # http://tools.ietf.org/html/rfc7231#section-5.3.4
28
+ class AcceptEncoding < ListHeader 'Accept-Encoding'
29
+ def initialize(*coding_ranges)
30
+ super(coding_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
31
+ end
32
+
33
+ alias_method :coding_ranges, :values
34
+
35
+ def self.parse(s)
36
+ tree = Parsers::AcceptEncodingHeader.new.parse(s)
37
+ Parsers::AcceptEncodingHeaderTransform.new.apply(tree)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Parsers
43
+ class AcceptEncodingHeader < Parslet::Parser
44
+ include HeaderRules
45
+ %w(identity compress x-compress deflate gzip x-gzip).each do |c|
46
+ rule(c.tr('-', '_').to_sym) { str(c).as(:coding) >> sp? }
47
+ end
48
+ rule(:coding) { compress | x_compress | deflate | gzip | x_gzip }
49
+ rule(:wildcard) { str('*').as(:coding) >> sp? }
50
+ rule(:coding_range) { (coding | identity | wildcard) >> weight.maybe }
51
+ rule(:accept_encoding) { (coding_range >> (list_sep >> coding_range).repeat).maybe.as(:accept_encoding) }
52
+ root(:accept_encoding)
53
+ end
54
+
55
+ class AcceptEncodingHeaderTransform < HeaderTransform
56
+ rule(coding: simple(:c), q: simple(:q)) { ContentCodingRange.new(c, q) }
57
+ rule(coding: simple(:c)) { ContentCodingRange.new(c) }
58
+ rule(accept_encoding: sequence(:er)) { Headers::AcceptEncoding.new(*er) }
59
+ rule(accept_encoding: simple(:cc)) { Headers::AcceptEncoding.new(cc) }
60
+ rule(accept_encoding: nil) { Headers::AcceptEncoding.new }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+
4
+ module Xenon
5
+ class LanguageRange
6
+ attr_reader :language, :q
7
+
8
+ DEFAULT_Q = 1.0
9
+
10
+ def initialize(language, q = DEFAULT_Q)
11
+ @language = language
12
+ @q = Float(q) || DEFAULT_Q
13
+ end
14
+
15
+ def <=>(other)
16
+ @q <=> other.q
17
+ end
18
+
19
+ def to_s
20
+ s = @language.dup
21
+ s << "; q=#{@q}" if @q != DEFAULT_Q
22
+ s
23
+ end
24
+ end
25
+
26
+ class Headers
27
+ # http://tools.ietf.org/html/rfc7231#section-5.3.5
28
+ class AcceptLanguage < ListHeader 'Accept-Language'
29
+ def initialize(*language_ranges)
30
+ super(language_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
31
+ end
32
+
33
+ alias_method :language_ranges, :values
34
+
35
+ def self.parse(s)
36
+ tree = Parsers::AcceptLanguageHeader.new.parse(s)
37
+ Parsers::AcceptLanguageHeaderTransform.new.apply(tree)
38
+ end
39
+ end
40
+ end
41
+
42
+ module Parsers
43
+ class AcceptLanguageHeader < Parslet::Parser
44
+ include HeaderRules
45
+ rule(:language) { (alpha.repeat(1, 8) >> (str('-') >> alphanum.repeat(1, 8)).maybe).as(:language) >> sp? }
46
+ rule(:wildcard) { str('*').as(:language) >> sp? }
47
+ rule(:language_range) { (language | wildcard) >> weight.maybe }
48
+ rule(:accept_language) { (language_range >> (list_sep >> language_range).repeat).as(:accept_language) }
49
+ root(:accept_language)
50
+ end
51
+
52
+ class AcceptLanguageHeaderTransform < HeaderTransform
53
+ rule(language: simple(:e), q: simple(:q)) { LanguageRange.new(e, q) }
54
+ rule(language: simple(:e)) { LanguageRange.new(e) }
55
+ rule(accept_language: sequence(:lr)) { Headers::AcceptLanguage.new(*lr) }
56
+ rule(accept_language: simple(:lr)) { Headers::AcceptLanguage.new(lr) }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ require 'base64'
2
+ require 'xenon/auth'
3
+ require 'xenon/headers'
4
+ require 'xenon/parsers/header_rules'
5
+
6
+ module Xenon
7
+ class Headers
8
+ # http://tools.ietf.org/html/rfc7235#section-4.2
9
+ class Authorization < Header 'Authorization'
10
+ attr_reader :credentials
11
+
12
+ def initialize(credentials)
13
+ @credentials = credentials
14
+ end
15
+
16
+ def self.parse(s)
17
+ tree = Parsers::AuthorizationHeader.new.parse(s)
18
+ Parsers::AuthorizationHeaderTransform.new.apply(tree)
19
+ rescue Parslet::ParseFailed
20
+ raise Xenon::ParseError.new("Invalid Authorization header (#{s}).")
21
+ end
22
+
23
+ def to_s
24
+ @credentials.to_s
25
+ end
26
+ end
27
+ end
28
+
29
+ module Parsers
30
+ class AuthorizationHeader < Parslet::Parser
31
+ include AuthHeaderRules
32
+ rule(:credentials) { auth_scheme >> sp >> (token68 | auth_params) }
33
+ rule(:authorization) { credentials.as(:authorization) }
34
+ root(:authorization)
35
+ end
36
+
37
+ class AuthorizationHeaderTransform < HeaderTransform
38
+ rule(auth_param: { name: simple(:n), value: simple(:v) }) { [n, v] }
39
+ rule(auth_params: subtree(:x)) { { foo: x } }
40
+ rule(auth_scheme: simple(:s), token: simple(:t)) {
41
+ case s
42
+ when 'Basic' then BasicCredentials.decode(t)
43
+ else GenericCredentials.new(s, token: t)
44
+ end
45
+ }
46
+ rule(auth_scheme: simple(:s), auth_params: subtree(:p)) { GenericCredentials.new(s, params: Hash[p]) }
47
+ rule(authorization: simple(:c)) { Headers::Authorization.new(c) }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/parsers/header_rules'
3
+ require 'xenon/quoted_string'
4
+
5
+ module Xenon
6
+ class CacheDirective
7
+ using QuotedString
8
+
9
+ attr_reader :name, :value
10
+
11
+ def initialize(name, value = nil)
12
+ @name = name
13
+ @value = value
14
+ end
15
+
16
+ def to_s
17
+ s = @name.dup
18
+ s << '=' << @value.quote if @value
19
+ s
20
+ end
21
+ end
22
+
23
+ class Headers
24
+ # http://tools.ietf.org/html/rfc7234#section-5.2
25
+ class CacheControl < ListHeader 'Cache-Control'
26
+ def initialize(*directives)
27
+ super(directives)
28
+ end
29
+
30
+ alias_method :directives, :values
31
+
32
+ def self.parse(s)
33
+ tree = Parsers::CacheControlHeader.new.parse(s)
34
+ Parsers::CacheControlHeaderTransform.new.apply(tree)
35
+ end
36
+ end
37
+ end
38
+
39
+ module Parsers
40
+ class CacheControlHeader < Parslet::Parser
41
+ include HeaderRules
42
+ rule(:name) { token.as(:name) }
43
+ rule(:value) { str('=') >> (token | quoted_string).as(:value) }
44
+ rule(:directive) { (name >> value.maybe).as(:directive) >> sp? }
45
+ rule(:cache_control) { (directive >> (list_sep >> directive).repeat).as(:cache_control) }
46
+ root(:cache_control)
47
+ end
48
+
49
+ class CacheControlHeaderTransform < HeaderTransform
50
+ rule(directive: { name: simple(:n), value: simple(:v) }) { CacheDirective.new(n, v) }
51
+ rule(directive: { name: simple(:n) }) { CacheDirective.new(n) }
52
+ rule(cache_control: sequence(:d)) { Headers::CacheControl.new(*d) }
53
+ rule(cache_control: simple(:d)) { Headers::CacheControl.new(d) }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ require 'xenon/headers'
2
+ require 'xenon/media_type'
3
+
4
+ module Xenon
5
+ class Headers
6
+ # http://tools.ietf.org/html/rfc7231#section-3.1.1.5
7
+ class ContentType < Header 'Content-Type'
8
+ attr_reader :content_type
9
+
10
+ def initialize(content_type)
11
+ @content_type = content_type
12
+ end
13
+
14
+ def self.parse(s)
15
+ new(Xenon::ContentType.parse(s))
16
+ end
17
+
18
+ def to_s
19
+ @content_type.to_s
20
+ end
21
+ end
22
+ end
23
+ end