xenon-routing 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +18 -0
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +20 -0
- data/Guardfile +16 -0
- data/LICENSE +22 -0
- data/README.md +116 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/examples/hello_world/config.ru +3 -0
- data/examples/hello_world/hello_world.rb +27 -0
- data/xenon-http/lib/xenon/auth.rb +63 -0
- data/xenon-http/lib/xenon/errors.rb +5 -0
- data/xenon-http/lib/xenon/etag.rb +48 -0
- data/xenon-http/lib/xenon/headers.rb +112 -0
- data/xenon-http/lib/xenon/headers/accept.rb +34 -0
- data/xenon-http/lib/xenon/headers/accept_charset.rb +59 -0
- data/xenon-http/lib/xenon/headers/accept_encoding.rb +63 -0
- data/xenon-http/lib/xenon/headers/accept_language.rb +59 -0
- data/xenon-http/lib/xenon/headers/authorization.rb +50 -0
- data/xenon-http/lib/xenon/headers/cache_control.rb +56 -0
- data/xenon-http/lib/xenon/headers/content_type.rb +23 -0
- data/xenon-http/lib/xenon/headers/if_match.rb +53 -0
- data/xenon-http/lib/xenon/headers/if_modified_since.rb +22 -0
- data/xenon-http/lib/xenon/headers/if_none_match.rb +53 -0
- data/xenon-http/lib/xenon/headers/if_range.rb +45 -0
- data/xenon-http/lib/xenon/headers/if_unmodified_since.rb +22 -0
- data/xenon-http/lib/xenon/headers/user_agent.rb +65 -0
- data/xenon-http/lib/xenon/headers/www_authenticate.rb +71 -0
- data/xenon-http/lib/xenon/http.rb +7 -0
- data/xenon-http/lib/xenon/http_version.rb +3 -0
- data/xenon-http/lib/xenon/media_type.rb +162 -0
- data/xenon-http/lib/xenon/parsers/basic_rules.rb +86 -0
- data/xenon-http/lib/xenon/parsers/header_rules.rb +60 -0
- data/xenon-http/lib/xenon/parsers/media_type.rb +53 -0
- data/xenon-http/lib/xenon/quoted_string.rb +20 -0
- data/xenon-http/spec/spec_helper.rb +94 -0
- data/xenon-http/spec/xenon/etag_spec.rb +19 -0
- data/xenon-http/spec/xenon/headers/accept_charset_spec.rb +31 -0
- data/xenon-http/spec/xenon/headers/accept_encoding_spec.rb +40 -0
- data/xenon-http/spec/xenon/headers/accept_language_spec.rb +33 -0
- data/xenon-http/spec/xenon/headers/accept_spec.rb +54 -0
- data/xenon-http/spec/xenon/headers/authorization_spec.rb +47 -0
- data/xenon-http/spec/xenon/headers/cache_control_spec.rb +64 -0
- data/xenon-http/spec/xenon/headers/if_match_spec.rb +73 -0
- data/xenon-http/spec/xenon/headers/if_modified_since_spec.rb +19 -0
- data/xenon-http/spec/xenon/headers/if_none_match_spec.rb +79 -0
- data/xenon-http/spec/xenon/headers/if_range_spec.rb +45 -0
- data/xenon-http/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
- data/xenon-http/spec/xenon/headers/user_agent_spec.rb +67 -0
- data/xenon-http/spec/xenon/headers/www_authenticate_spec.rb +43 -0
- data/xenon-http/spec/xenon/media_type_spec.rb +267 -0
- data/xenon-http/xenon-http.gemspec +25 -0
- data/xenon-routing/lib/xenon/api.rb +118 -0
- data/xenon-routing/lib/xenon/marshallers.rb +48 -0
- data/xenon-routing/lib/xenon/request.rb +40 -0
- data/xenon-routing/lib/xenon/response.rb +29 -0
- data/xenon-routing/lib/xenon/routing.rb +6 -0
- data/xenon-routing/lib/xenon/routing/context.rb +35 -0
- data/xenon-routing/lib/xenon/routing/directives.rb +14 -0
- data/xenon-routing/lib/xenon/routing/header_directives.rb +32 -0
- data/xenon-routing/lib/xenon/routing/method_directives.rb +26 -0
- data/xenon-routing/lib/xenon/routing/param_directives.rb +22 -0
- data/xenon-routing/lib/xenon/routing/path_directives.rb +37 -0
- data/xenon-routing/lib/xenon/routing/route_directives.rb +51 -0
- data/xenon-routing/lib/xenon/routing/security_directives.rb +34 -0
- data/xenon-routing/lib/xenon/routing_version.rb +3 -0
- data/xenon-routing/spec/spec_helper.rb +94 -0
- data/xenon-routing/xenon-routing.gemspec +25 -0
- data/xenon.gemspec +26 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 385808aff014a3ce0b190e485996e21566ba4a96
|
4
|
+
data.tar.gz: 8230baa459671431db3a9728b1df184606b2a8d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b902c768fb0dc9dfec74cef1426a1fb7eef41c5be1be85717d60d16918282a2c1fbe6ee4d8f1763f01fcb1efd6df76c8b36f295183ee10e3a0554bd1cf461508
|
7
|
+
data.tar.gz: fdbeec5753727366d84cf827456c09f68ee3a6d55a4cd2375fc23418ca79cf5fc5423c7952d7962a20aa12480b66b01e2234713eead71768981790e6d40d59d3
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# For more details, see here:
|
2
|
+
# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform
|
3
|
+
|
4
|
+
# For a list of all available engines, see here:
|
5
|
+
# http://docs.codeclimate.com/article/296-engines-available-engines
|
6
|
+
|
7
|
+
engines:
|
8
|
+
rubocop:
|
9
|
+
enabled: true
|
10
|
+
|
11
|
+
ratings:
|
12
|
+
paths:
|
13
|
+
- lib/**/*
|
14
|
+
|
15
|
+
exclude_paths:
|
16
|
+
- examples/**/*
|
17
|
+
- spec/**/*
|
18
|
+
- vendor/**/*
|
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.ruby-version
|
4
|
+
.ruby-gemset
|
5
|
+
Gemfile.lock
|
6
|
+
/.config
|
7
|
+
/coverage/
|
8
|
+
/InstalledFiles
|
9
|
+
/pkg/
|
10
|
+
/spec/reports/
|
11
|
+
/test/tmp/
|
12
|
+
/test/version_tmp/
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
## Documentation cache and generated files:
|
16
|
+
/.yardoc/
|
17
|
+
/_yardoc/
|
18
|
+
/doc/
|
19
|
+
/rdoc/
|
20
|
+
|
21
|
+
## Environment normalisation:
|
22
|
+
/.bundle/
|
23
|
+
/lib/bundler/man/
|
24
|
+
.ruby-gemset
|
25
|
+
.ruby-version
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
Dir[File.join('*', '*.gemspec')].each do |gemspec|
|
6
|
+
lib = gemspec.scan(/xenon-(.*)\.gemspec/).flatten.first
|
7
|
+
gemspec(:name => "xenon-#{lib}", development_group: lib)
|
8
|
+
end
|
9
|
+
|
10
|
+
group :development, :test do
|
11
|
+
gem 'rake', require: false
|
12
|
+
gem 'rspec', require: false
|
13
|
+
gem 'guard', require: false
|
14
|
+
gem 'guard-rspec', require: false
|
15
|
+
gem 'yard', require: false
|
16
|
+
end
|
17
|
+
|
18
|
+
group :test do
|
19
|
+
gem "codeclimate-test-reporter", require: false
|
20
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
clearing :on
|
2
|
+
|
3
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
4
|
+
require "guard/rspec/dsl"
|
5
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
6
|
+
|
7
|
+
# RSpec files
|
8
|
+
rspec = dsl.rspec
|
9
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_files)
|
12
|
+
|
13
|
+
# Ruby files
|
14
|
+
ruby = dsl.ruby
|
15
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
16
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Greg Beech
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# Xenon
|
2
|
+
|
3
|
+
[![Gem Version][fury-badge]][fury] [![Build Status][travis-badge]][travis] [![Code Climate][cc-badge]][cc] [![Test Coverage][ccc-badge]][ccc] [![YARD Docs][docs-badge]][docs]
|
4
|
+
|
5
|
+
An HTTP framework for building RESTful APIs. As you can probably tell from the very low version number, this is in very early stages of development so I wouldn't use it anywhere close to production yet. But have a play around and let me know what you think, I'm very open to feedback.
|
6
|
+
|
7
|
+
The **xenon** gem is a top-level gem that simply pulls in the other gems for convenience.
|
8
|
+
|
9
|
+
## xenon-http
|
10
|
+
|
11
|
+
A set of model objects for the HTTP protocol which can parse and format the strings you typically get into proper objects you can work with. At the moment this covers key things like media types and the most common headers, but it will expand to cover the whole protocol. You can use the model by itself without any other parts of the library.
|
12
|
+
|
13
|
+
This is how things tend to look:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
accept = Xenon::Headers::Accept.parse('application/json, application/*; q=0.5')
|
17
|
+
accept.media_ranges.first.media_type.json? #=> true
|
18
|
+
accept.media_ranges.last.q #=> 0.5
|
19
|
+
# etc.
|
20
|
+
```
|
21
|
+
|
22
|
+
Yeah, it's not exciting and it's not glamorous, but if you need to parse parts of the HTTP protocol that other frameworks just don't reach, Xenon is here for you.
|
23
|
+
|
24
|
+
## xenon-routing
|
25
|
+
|
26
|
+
A tree-based routing approach using "directives" which gives you great flexibility in building APIs and without the need to write extensions, helpers, etc. because everything is a directive and you extend it by simply writing more directives!
|
27
|
+
|
28
|
+
A really simple example with a custom authentication directive is shown below. You can run this from the [examples](examples/hello_world) directory!
|
29
|
+
|
30
|
+
~~~ruby
|
31
|
+
class HelloWorld < Xenon::API
|
32
|
+
path '/' do
|
33
|
+
get do
|
34
|
+
hello_auth do |user|
|
35
|
+
params :greeting do |greeting|
|
36
|
+
complete :ok, { greeting => user.username }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def hello_auth
|
45
|
+
@authenticator ||= Xenon::BasicAuth.new realm: 'hello world' do |credentials|
|
46
|
+
OpenStruct.new(username: credentials.username) # should actually auth here!
|
47
|
+
end
|
48
|
+
authenticate @authenticator do |user|
|
49
|
+
authorize user.username == 'greg' do
|
50
|
+
yield user
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
~~~
|
56
|
+
|
57
|
+
Of course, it does all the things you'd expect like support content negotiation properly and return the correct status codes when paths or methods aren't found. For example, if you try to `POST` to the above code you'll see the error:
|
58
|
+
|
59
|
+
~~~json
|
60
|
+
{
|
61
|
+
"status": 405,
|
62
|
+
"developer_message": "Supported methods: GET"
|
63
|
+
}
|
64
|
+
~~~
|
65
|
+
|
66
|
+
Or if you send it an `Accept` header that doesn't allow JSON (the only supported format by default) you'll see:
|
67
|
+
|
68
|
+
~~~ruby
|
69
|
+
{
|
70
|
+
"status": 406,
|
71
|
+
"developer_message": "Supported media types: application/json"
|
72
|
+
}
|
73
|
+
~~~
|
74
|
+
|
75
|
+
## Spray
|
76
|
+
|
77
|
+
Xenon is inspired by [Spray][spray], an awesome Scala framework for building RESTful APIs, and which I sorely miss while working in Ruby. However although it's inspired by it, there are some key differences.
|
78
|
+
|
79
|
+
Firsly Xenon is synchronous rather than asynchronous, as that is a much more common approach to writing code in Ruby, and fits better with commonly used frameworks such as Rack and ActiveRecord. It's also much easier to write and reason about synchronous code, and you can still scale it pretty well using the process model.
|
80
|
+
|
81
|
+
Secondly the directives are just methods which are composed using Ruby's usual `yield` mechanism rather than being monads composed with flat map as in Spray. This is primarily to make the framework feel natural for Ruby users where the general desire is for simplicity and "it just works". This does limit composability of directives, but for most real-world situations this doesn't seem to be a problem so I think it's the right trade-off.
|
82
|
+
|
83
|
+
## Installation
|
84
|
+
|
85
|
+
Add this line to your application's Gemfile:
|
86
|
+
|
87
|
+
gem 'xenon'
|
88
|
+
|
89
|
+
And then execute:
|
90
|
+
|
91
|
+
$ bundle
|
92
|
+
|
93
|
+
Or install it yourself as:
|
94
|
+
|
95
|
+
$ gem install xenon
|
96
|
+
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
1. Fork it ( https://github.com/gregbeech/xenon/fork )
|
100
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
101
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
102
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
103
|
+
5. Create a new Pull Request
|
104
|
+
|
105
|
+
|
106
|
+
[fury]: http://badge.fury.io/rb/xenon "Xenon at Rubygems"
|
107
|
+
[fury-badge]: https://badge.fury.io/rb/xenon.svg "Gem Version"
|
108
|
+
[travis]: https://travis-ci.org/gregbeech/xenon "Xenon at Travis CI"
|
109
|
+
[travis-badge]: https://travis-ci.org/gregbeech/xenon.svg "Build Status"
|
110
|
+
[cc]: https://codeclimate.com/github/gregbeech/xenon "Xenon Quality at Code Climate"
|
111
|
+
[cc-badge]: https://codeclimate.com/github/gregbeech/xenon/badges/gpa.svg "Code Quality"
|
112
|
+
[ccc]: https://codeclimate.com/github/gregbeech/xenon/coverage "Xenon Coverage at Code Climate"
|
113
|
+
[ccc-badge]: https://codeclimate.com/github/gregbeech/xenon/badges/coverage.svg "Code Coverage"
|
114
|
+
[docs]: http://www.rubydoc.info/github/gregbeech/xenon "YARD Docs"
|
115
|
+
[docs-badge]: http://img.shields.io/badge/yard-docs-blue.svg "YARD Docs"
|
116
|
+
[spray]: http://spray.io/ "spray"
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
task :test => :spec
|
6
|
+
|
7
|
+
desc 'Do not use; use the spec task'
|
8
|
+
RSpec::Core::RakeTask.new(:rspec)
|
9
|
+
|
10
|
+
desc 'Runs specifications for all gems'
|
11
|
+
task :spec => [:'spec:http', :'spec:routing']
|
12
|
+
namespace :spec do
|
13
|
+
%i[http routing].each do |lib|
|
14
|
+
desc "Runs specifications for the xenon-#{lib} gem"
|
15
|
+
task lib do
|
16
|
+
Dir.chdir("xenon-#{lib}") do
|
17
|
+
Rake::Task['rspec'].reenable
|
18
|
+
Rake::Task['rspec'].invoke
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Build gems into the pkg directory'
|
25
|
+
task :build do
|
26
|
+
FileUtils.rm_rf('pkg')
|
27
|
+
Dir[File.join('*', '*.gemspec')].each do |gemspec|
|
28
|
+
system "gem build #{gemspec}"
|
29
|
+
end
|
30
|
+
system "gem build xenon.gemspec"
|
31
|
+
FileUtils.mkdir_p('pkg')
|
32
|
+
FileUtils.mv(Dir['*.gem'], 'pkg')
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Tags version, pushes to remote, and pushes gems'
|
36
|
+
task :release => :build do
|
37
|
+
sh "git tag v#{File.read(File.join(__dir__, 'VERSION'))}"
|
38
|
+
sh "git push origin master"
|
39
|
+
sh "ls pkg/*.gem | xargs -n 1 gem push"
|
40
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.4
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'xenon/api'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class HelloWorld < Xenon::API
|
5
|
+
path '/' do
|
6
|
+
get do
|
7
|
+
hello_auth do |user|
|
8
|
+
params :greeting do |greeting|
|
9
|
+
complete :ok, { greeting => user.username }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def hello_auth
|
18
|
+
@authenticator ||= Xenon::BasicAuth.new realm: 'hello world' do |credentials|
|
19
|
+
OpenStruct.new(username: credentials.username) # should actually auth here!
|
20
|
+
end
|
21
|
+
authenticate @authenticator do |user|
|
22
|
+
authorize user.username == 'greg' do
|
23
|
+
yield user
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'xenon/quoted_string'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
class BasicCredentials
|
5
|
+
attr_reader :username, :password
|
6
|
+
|
7
|
+
def initialize(username, password)
|
8
|
+
@username = username
|
9
|
+
@password = password
|
10
|
+
end
|
11
|
+
|
12
|
+
def token
|
13
|
+
Base64.strict_encode64("#{@username}:#{@password}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.decode(s)
|
17
|
+
str = Base64.strict_decode64(s)
|
18
|
+
username, password = str.split(':', 2)
|
19
|
+
BasicCredentials.new(username, password)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"Basic #{token}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class GenericCredentials
|
28
|
+
using QuotedString
|
29
|
+
|
30
|
+
attr_reader :scheme, :token, :params
|
31
|
+
|
32
|
+
def initialize(scheme, token: nil, params: {})
|
33
|
+
@scheme = scheme
|
34
|
+
@token = token
|
35
|
+
@params = params
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
s = @scheme.dup
|
40
|
+
s << ' ' << @token if @token
|
41
|
+
s << ' ' << @params.map { |n, v| "#{n}=#{v.quote}" }.join(', ')
|
42
|
+
s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class BasicAuth
|
47
|
+
attr_reader :auth_params
|
48
|
+
|
49
|
+
def initialize(auth_params = {}, &store)
|
50
|
+
@auth_params = auth_params
|
51
|
+
@store = store
|
52
|
+
end
|
53
|
+
|
54
|
+
def scheme
|
55
|
+
'Basic'
|
56
|
+
end
|
57
|
+
|
58
|
+
def call(request)
|
59
|
+
header = request.header('Authorization') rescue nil
|
60
|
+
@store.call(header.credentials) if header && header.credentials.is_a?(BasicCredentials)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'xenon/parsers/header_rules'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
class ETag
|
5
|
+
attr_reader :opaque_tag
|
6
|
+
|
7
|
+
def initialize(opaque_tag, weak: false)
|
8
|
+
@opaque_tag = opaque_tag
|
9
|
+
@weak = weak
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse(s)
|
13
|
+
tree = Parsers::ETag.new.etag.parse(s)
|
14
|
+
Parsers::ETagHeaderTransform.new.apply(tree)
|
15
|
+
end
|
16
|
+
|
17
|
+
def weak?
|
18
|
+
@weak
|
19
|
+
end
|
20
|
+
|
21
|
+
def strong?
|
22
|
+
!weak?
|
23
|
+
end
|
24
|
+
|
25
|
+
def strong_eq?(other)
|
26
|
+
strong? && other.strong? && @opaque_tag == other.opaque_tag
|
27
|
+
end
|
28
|
+
|
29
|
+
def weak_eq?(other)
|
30
|
+
@opaque_tag == other.opaque_tag
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
strong? == other.strong? && @opaque_tag == other.opaque_tag
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
s = weak? ? "W/" : ""
|
39
|
+
s << '"' << @opaque_tag << '"'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Parsers
|
44
|
+
class ETag < Parslet::Parser
|
45
|
+
include ETagHeaderRules
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|