xenon-routing 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
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
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install:
5
+ - gem install bundler
6
+ cache: bundler
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,3 @@
1
+ require_relative 'hello_world'
2
+
3
+ run HelloWorld.new
@@ -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,5 @@
1
+ module Xenon
2
+ class Error < StandardError; end
3
+ class ParseError < Error; end
4
+ class ProtocolError < Error; end
5
+ 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