hyperdrive 0.0.3 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1773ebd648b23d917073080a22cdfbdc7f1db732
4
- data.tar.gz: b01991fbf7b86592e493a6001a9c3855e6d9648f
3
+ metadata.gz: 04f0142457c2c2727dc798316907630f2d53c828
4
+ data.tar.gz: b1742f5f51b354d1ada859f1b04603e1703396b8
5
5
  SHA512:
6
- metadata.gz: 12fa4db2f3a29dde64ed725c164c8ba7368163f2d178518e7a67e999b5d24e889919668d99a16e8f3f89aeaadb5e467717c7990283560a514eb3c5be948ec0b2
7
- data.tar.gz: 492dd95b6214f6a1a16cb9df13d1484879351984c0ca46c89a0fdb2d9e574212ccf506741d54ab00770fd74c4d7e451030cbcf53f6bfd2739bf832c63de7135e
6
+ metadata.gz: 165eb035a20d31e3c33c29e82dc71cd856661aa91d58f0b542e5e90bfe700f185dd1262ea6714e203cbc5db1f047b0d5f80e929ac8da491bbca6fb7fa74dd7eb
7
+ data.tar.gz: f2b83b37947c9b2bd3ed27c6c7db5b5deb15729714b324c68a6c28dc0520ddb13b28f5ac3f40ae9d1a98e117705b15a1aefa344b72c1fec360d856a5c279bc2d
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore CHANGED
@@ -16,3 +16,6 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .DS_Store
19
+ config.ru
20
+ # must be last
21
+ !.gitkeep
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ gemspec
3
3
 
4
4
  group :development do
5
5
  gem 'gem-release', require: false
6
+ gem 'pry', require: false
6
7
  end
7
8
 
8
9
  group :test, :rake do
@@ -17,5 +18,5 @@ group :test do
17
18
  gem 'minitest', require: false
18
19
  gem 'minitest-spec-context', require: false
19
20
  gem 'minitest-reporters', require: false
20
- #gem 'rack-test', require: false
21
+ gem 'rack-test', require: false
21
22
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Hyperdrive
2
2
 
3
- Ruby DSL for defining self-documenting, HATEOS™ complaint, Hypermedia endpoints.
3
+ Ruby DSL for defining self-documenting, HATEOAS™ complaint, Hypermedia endpoints.
4
4
 
5
5
  ## Installation
6
6
 
@@ -21,6 +21,7 @@ Or install it yourself as:
21
21
  Proposed Syntax (WIP):
22
22
 
23
23
  ```ruby
24
+ # api.rb
24
25
  hyperdrive do
25
26
  resource(:thing) do
26
27
  name 'Thing Resource'
@@ -40,13 +41,85 @@ hyperdrive do
40
41
  # applied). Unlike allowed params, filters are not required by default.
41
42
  filter :start_date, 'Format: YYYY-MM-DD'
42
43
  filter :end_date, 'Format: YYYY-MM-DD'
43
- filter :parent_id, 'Parent ID of Thing", required: true
44
+ filter :parent_id, 'Parent ID of Thing', required: true
45
+
46
+
47
+ # Handle API Requests
48
+ #
49
+ # - Simply return a string to be used as the body of the response. How you generate
50
+ # that string is completely up to you.
51
+ # - Status Codes and Headers will be handled automatically.
52
+ # - Any unhandled requests will return a 405 "Method Not Supported" error
53
+
54
+ request(:get) do
55
+ # retrieve 1-N objects...
56
+ '{ ... }'
57
+ end
58
+
59
+ # Options and Head requests will be handled automatically and
60
+ # will use info from the GET request block you define, as needed.
61
+
62
+ # POST Requests should return the full object that was created during the request.
63
+ request(:post) do
64
+ # create the object...
65
+ '{ ... }'
66
+ end
67
+
68
+ # PUT Requests should return the full object that was updated during the request.
69
+ request(:put) do
70
+ # 'upsert' the object...
71
+ '{ ... }'
72
+ end
73
+
74
+ # PATCH requests should return the full object that was updated during the request.
75
+ request(:patch) do
76
+ # update the object...
77
+ '{ ... }'
78
+ end
79
+
80
+ # DELETE requests should return a simple response indicating success.
81
+ request(:delete) do
82
+ # delete the object...
83
+ '{"deleted":true}'
84
+ end
44
85
  end
45
86
  end
87
+
88
+ # config.ru
89
+ run Hyperdrive::Server
46
90
  ```
47
91
 
92
+ ## CLI
93
+
94
+ ### Generating Documentation
95
+
96
+ `$ hyperdrive docs <option> <parameter>`
97
+
98
+ __`--input` Option__
99
+
100
+ Use the `--input` option and specify a file or directory as a parameter to generate documentation for your API resources.
101
+
102
+ - `$ hyperdrive docs --input api.rb`
103
+
104
+ or
105
+
106
+ - `$ hyperdrive docs --input api`
107
+
108
+ `-in` can be used as an alias for `--input`
109
+
110
+ __`--output` Option__
111
+
112
+ You can also provide a `--output` option and specify a destination for your documentation to be created.
113
+
114
+ - `$ hyperdrive docs --input api.rb --output docs/docs.md`
115
+
116
+ `-out` can be used as an alias for `--output`
117
+
118
+ If the `--output` option is not provided the generated documentation will be written to `docs/doc.md` by default.
119
+
48
120
  ## Project Status
49
121
 
122
+ - Version: [![Gem Version](https://badge.fury.io/rb/hyperdrive.png)](http://badge.fury.io/rb/hyperdrive)
50
123
  - Build: [![Build Status](https://secure.travis-ci.org/styleseek/hyperdrive.png?branch=master)](https://travis-ci.org/styleseek/hyperdrive)
51
124
  - Code Quality: [![Code Climate](https://codeclimate.com/github/styleseek/hyperdrive.png)](https://codeclimate.com/github/styleseek/hyperdrive)
52
125
  - Dependencies: [![Dependency Status](https://gemnasium.com/styleseek/hyperdrive.png)](https://gemnasium.com/styleseek/hyperdrive)
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['RACK_ENV'] ||= 'development'
3
+ # load path
4
+ lib_path = File.expand_path('../../lib', __FILE__)
5
+ ($:.unshift lib_path) unless ($:.include? lib_path)
6
+
7
+ # require farm
8
+ require 'bundler'
9
+ Bundler.require(:default, ENV['RACK_ENV'])
10
+ require 'pry'
11
+ require 'hyperdrive'
12
+
13
+ # fire up the ftl drive
14
+ Pry.start
data/bin/hyperdrive ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['RACK_ENV'] ||= 'development'
3
+ # load path
4
+ lib_path = File.expand_path('../../lib', __FILE__)
5
+ ($:.unshift lib_path) unless ($:.include? lib_path)
6
+
7
+ require 'thor'
8
+ require 'hyperdrive'
9
+
10
+ module Hyperdrive
11
+ class CLI < Thor
12
+ include Thor::Actions
13
+
14
+ desc "docs", "Generate docs in markdown based on your resources."
15
+ method_option :input, aliases: "\--in", desc: "Specify the directory or file to generate docs for."
16
+ method_option :output, aliases: "\--out", desc: "Specify the destination of the docs.", default: "docs/api.md"
17
+ def docs
18
+ input = File.expand_path(options[:input])
19
+
20
+ if File.file?(input)
21
+ require input
22
+ elsif File.directory?(input)
23
+ Dir.glob("#{input}/*.rb").each do |file|
24
+ require file
25
+ end
26
+ else
27
+ raise ArgumentError, "Input is neither a file nor a directory"
28
+ end
29
+
30
+ if hyperdrive.resources.empty?
31
+ say "This API doesn't have any resources to document", :red
32
+ return
33
+ end
34
+
35
+ data = Hyperdrive::Docs.new(hyperdrive.resources).output
36
+ create_file(options[:output], data)
37
+ say "Done!"
38
+ end
39
+ end
40
+ end
41
+
42
+ Hyperdrive::CLI.start
data/hyperdrive.gemspec CHANGED
@@ -9,15 +9,16 @@ Gem::Specification.new do |gem|
9
9
  gem.authors = ['StyleSeek Engineering']
10
10
  gem.email = ['engineering@styleseek.com']
11
11
  gem.summary = %q{Hypermedia State Machine}
12
- gem.description = %q{Ruby DSL for defining self-documenting, HATEOS™ complaint, Hypermedia API endpoints.}
12
+ gem.description = %q{Ruby DSL for defining self-documenting, HATEOAS™ complaint, Hypermedia API endpoints.}
13
13
  gem.homepage = "https://github.com/styleseek/hyperdrive"
14
14
  gem.license = "MIT"
15
15
 
16
16
  gem.files = `git ls-files -z`.split("\x0")
17
- gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ gem.executables = ['hyperdrive']
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
-
21
- #gem.add_development_dependency "bundler", "~> 1.5"
22
- #gem.add_development_dependency "rake"
20
+
21
+ gem.add_dependency 'rack'
22
+ gem.add_dependency 'linguistics'
23
+ gem.add_dependency 'thor'
23
24
  end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ class Docs
5
+ attr_reader :resources
6
+
7
+ def initialize(resources)
8
+ @resources = resources
9
+ end
10
+
11
+ def output
12
+ out = ""
13
+ resources.each_value do |resource|
14
+ out += header(resource.name)
15
+ out += paragraph(resource.desc)
16
+ out += header("Endpoint URL", 2)
17
+ out += paragraph(bullet(code(resource.endpoint), 1))
18
+ out += header("Params", 2)
19
+ out += list(resource.allowed_params)
20
+ out += header("Filters", 2)
21
+ out += list(resource.filters)
22
+ end
23
+ out
24
+ end
25
+
26
+ def header(string, level = 1)
27
+ raise ArgumentError, "Header level must be between 1 and 6." unless (1..6).cover?(level)
28
+ header = "#" * level
29
+ "\n\n#{header} #{string}\n\n"
30
+ end
31
+
32
+ def paragraph(string)
33
+ "#{string}\n\n"
34
+ end
35
+
36
+ def bold(string)
37
+ "__#{string}__"
38
+ end
39
+
40
+ def italics(string)
41
+ "_#{string}_"
42
+ end
43
+
44
+ def code(string)
45
+ "`#{string}`"
46
+ end
47
+
48
+ def bullet(string, nest = 1)
49
+ raise ArgumentError, "Nest level must be between 1 and 3." unless (1..3).cover?(nest)
50
+ nest = " " * nest
51
+ "#{nest}- #{string}\n"
52
+ end
53
+
54
+ def list(items)
55
+ list = ""
56
+ items.each do |key, value|
57
+ list += bullet(bold(key), 1)
58
+
59
+ value.each do |subkey, subvalue|
60
+ list += bullet(italics(subkey), 2)
61
+
62
+ if subvalue.kind_of? Array
63
+ list += bullet(code_options(subvalue), 3)
64
+ else
65
+ list += bullet(subvalue, 3)
66
+ end
67
+ end
68
+ end
69
+ list
70
+ end
71
+
72
+ def code_options(options)
73
+ options.map { |option| code(option) }.join(", ")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ require 'singleton'
3
+
4
+ module Hyperdrive
5
+ module DSL
6
+ class Main
7
+ include Singleton
8
+ attr_reader :resources
9
+
10
+ def initialize(&block)
11
+ @resources = {}
12
+ end
13
+
14
+ def resource(name, &block)
15
+ @resources[name] = Resource.new(name, &block).resource
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ module DSL
5
+ class Resource
6
+ attr_reader :resource
7
+ def initialize(key, &block)
8
+ @resource = ::Hyperdrive::Resource.new(key)
9
+ instance_eval(&block) if block_given?
10
+ end
11
+
12
+ def name(name)
13
+ resource.name = name
14
+ end
15
+
16
+ def desc(description)
17
+ resource.desc = description
18
+ end
19
+
20
+ def param(*args)
21
+ resource.register_param(*args)
22
+ end
23
+
24
+ def filter(*args)
25
+ resource.register_filter(*args)
26
+ end
27
+
28
+ def request(method, &block)
29
+ unless definable_request_methods.include? method
30
+ raise Errors::DSL::UnknownArgument.new(method, 'request')
31
+ end
32
+ resource.define_request_handler(method, block)
33
+ end
34
+
35
+ private
36
+
37
+ def definable_request_methods
38
+ [:get, :post, :put, :patch, :delete].freeze
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hyperdrive/dsl/main'
4
+ require 'hyperdrive/dsl/resource'
5
+
6
+ def hyperdrive(&block)
7
+ Hyperdrive::DSL::Main.instance.instance_eval(&block) if block_given?
8
+ Hyperdrive::DSL::Main.instance
9
+ end
@@ -0,0 +1,13 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ class BadRequest < HTTPError
4
+ def message
5
+ "The request cannot be fulfilled due to bad syntax."
6
+ end
7
+
8
+ def http_status_code
9
+ 400
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ module DSL
4
+ class UnknownArgument < RuntimeError
5
+ def initialize(argument, method_name)
6
+ @argument = case argument
7
+ when Symbol
8
+ ":#{argument}"
9
+ else
10
+ argument.to_s
11
+ end
12
+ @method_name = method_name
13
+ end
14
+
15
+ def message
16
+ "#{@argument} is not supported by `#{@method_name}'."
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ module Errors
5
+ class HTTPError < RuntimeError
6
+ def http_status_code
7
+ 500
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ class InternalServerError < HTTPError
4
+ def message
5
+ "The server encountered an unexpected condition which prevented it from fulfilling the request."
6
+ end
7
+
8
+ def http_status_code
9
+ 500
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ class MethodNotAllowed < HTTPError
4
+ def initialize(request_method)
5
+ @request_method = request_method
6
+ end
7
+
8
+ def message
9
+ "A request was made using a request method (#{@request_method}) not supported by this resource."
10
+ end
11
+
12
+ def http_status_code
13
+ 405
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ class NotFound < HTTPError
4
+ def message
5
+ "The requested resource could not be found."
6
+ end
7
+
8
+ def http_status_code
9
+ 404
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ class NotImplemented < HTTPError
4
+ def initialize(request_method)
5
+ @request_method = request_method
6
+ end
7
+
8
+ def message
9
+ "The server either does not recognise the request method (#{@request_method}), or it lacks the ability to fulfill the request."
10
+ end
11
+
12
+ def http_status_code
13
+ 501
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Hyperdrive
2
+ module Errors
3
+ class Unauthorized < HTTPError
4
+ def message
5
+ "The request requires user authentication."
6
+ end
7
+
8
+ def http_status_code
9
+ 401
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hyperdrive/errors/http_error'
4
+ require 'hyperdrive/errors/bad_request'
5
+ require 'hyperdrive/errors/internal_server_error'
6
+ require 'hyperdrive/errors/method_not_allowed'
7
+ require 'hyperdrive/errors/not_found'
8
+ require 'hyperdrive/errors/not_implemented'
9
+ require 'hyperdrive/errors/unauthorized'
10
+ require 'hyperdrive/errors/dsl/unknown_argument'
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Hyperdrive
4
4
  class Resource
5
- attr_reader :resource, :allowed_params, :filters
5
+ attr_reader :endpoint, :allowed_params, :filters, :request_handlers
6
6
  attr_accessor :name, :desc
7
7
 
8
- def initialize(resource)
9
- @resource = resource
8
+ def initialize(key)
9
+ @key = key
10
+ @endpoint = "/#{@key.to_s.en.plural}"
10
11
  @allowed_params = default_allowed_params
11
12
  @filters = default_filters
13
+ @request_handlers = default_request_handlers
12
14
  end
13
15
 
14
16
  def register_param(key, description, options = {})
@@ -21,6 +23,26 @@ module Hyperdrive
21
23
  @filters[key] = { desc: description }.merge(options)
22
24
  end
23
25
 
26
+ def define_request_handler(request_method, block)
27
+ @request_handlers[request_method] = block
28
+ if request_method == :get
29
+ @request_handlers[:head] = block
30
+ end
31
+ end
32
+
33
+ def request_handler(http_request_method)
34
+ request_method = Hyperdrive::Values.request_methods_string_map[http_request_method]
35
+ request_handlers[request_method]
36
+ end
37
+
38
+ def request_method_allowed?(http_request_method)
39
+ allowed_methods.include?(http_request_method)
40
+ end
41
+
42
+ def allowed_methods
43
+ Hyperdrive::Values.request_methods_symbol_map.values_at(*request_handlers.keys)
44
+ end
45
+
24
46
  private
25
47
 
26
48
  def default_allowed_params
@@ -42,5 +64,9 @@ module Hyperdrive
42
64
  def default_filter_options
43
65
  { required: false }.freeze
44
66
  end
67
+
68
+ def default_request_handlers
69
+ { options: proc { '' } }
70
+ end
45
71
  end
46
72
  end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ class Response
5
+ attr_reader :resource, :env, :http_request_method, :headers
6
+
7
+ def initialize(env, resource)
8
+ @resource = resource
9
+ @env = env
10
+ @http_request_method = env['REQUEST_METHOD']
11
+
12
+ unless request_method_supported?
13
+ raise Errors::NotImplemented.new(http_request_method)
14
+ end
15
+
16
+ unless resource.request_method_allowed?(http_request_method)
17
+ raise Errors::MethodNotAllowed.new(http_request_method)
18
+ end
19
+
20
+ @headers = default_headers
21
+ end
22
+
23
+ def response
24
+ @headers.merge({ 'Content-Type' => 'text/plain' })
25
+ status = (http_request_method == 'POST') ? 201 : 200
26
+ ::Rack::Response.new(resource.request_handler(http_request_method).call(env), status, headers).finish
27
+ end
28
+
29
+ private
30
+
31
+ def default_headers
32
+ {
33
+ 'Allow' => resource.allowed_methods
34
+ }
35
+ end
36
+
37
+ def request_method_supported?
38
+ Hyperdrive::Values.request_methods_string_map.key?(http_request_method)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ class Server
5
+ def self.call(env)
6
+ server.call(env)
7
+ end
8
+
9
+ private
10
+
11
+ def self.server
12
+ Rack::Builder.new do
13
+ hyperdrive.resources.each do |key, resource|
14
+ map resource.endpoint do
15
+ use Rack::Runtime
16
+ use Rack::Lint
17
+ use Rack::Head
18
+ run ->(env) {
19
+ begin
20
+ Hyperdrive::Response.new(env, resource).response
21
+ rescue Hyperdrive::Errors::HTTPError => error
22
+ [error.http_status_code, { 'Allow' => resource.allowed_methods.join(',') }, [error.message]]
23
+ end
24
+ }
25
+ end
26
+ end
27
+ end.to_app
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module Hyperdrive
2
+ module Values
3
+ def self.request_methods
4
+ %w(GET HEAD OPTIONS POST PUT PATCH DELETE).freeze
5
+ end
6
+
7
+ def self.request_methods_symbol_map
8
+ {
9
+ get: 'GET',
10
+ head: 'HEAD',
11
+ options: 'OPTIONS',
12
+ post: 'POST',
13
+ put: 'PUT',
14
+ patch: 'PATCH',
15
+ delete: 'DELETE'
16
+ }.freeze
17
+ end
18
+
19
+ def self.request_methods_string_map
20
+ {
21
+ 'GET' => :get,
22
+ 'HEAD' => :head,
23
+ 'OPTIONS' => :options,
24
+ 'POST' => :post,
25
+ 'PUT' => :put,
26
+ 'PATCH' => :patch,
27
+ 'DELETE' => :delete
28
+ }.freeze
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,3 @@
1
1
  module Hyperdrive
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/hyperdrive.rb CHANGED
@@ -1,8 +1,16 @@
1
1
  # encoding: utf-8
2
2
 
3
+ # stdlib
4
+ require 'rack'
5
+ require 'linguistics'
6
+ Linguistics.use(:en)
7
+
8
+ # prepare for hyperspace!
9
+ require 'hyperdrive/docs'
10
+ require 'hyperdrive/dsl'
11
+ require 'hyperdrive/errors'
3
12
  require 'hyperdrive/resource'
13
+ require 'hyperdrive/response'
14
+ require 'hyperdrive/server'
15
+ require 'hyperdrive/values'
4
16
  require 'hyperdrive/version'
5
-
6
- module Hyperdrive
7
- # spinning up the FTL drive
8
- end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::Docs do
6
+ before do
7
+ sample_api
8
+ @docs = Hyperdrive::Docs.new(hyperdrive.resources)
9
+ end
10
+
11
+ it 'generates a header with size 1 as default' do
12
+ @docs.header('Thing Resource').must_equal "\n\n# Thing Resource\n\n"
13
+ end
14
+
15
+ it 'generates a header only between size 1 and 6' do
16
+ proc {@docs.header('Thing Resource', 0)}.must_raise ArgumentError
17
+ proc {@docs.header('Thing Resource', 8)}.must_raise ArgumentError
18
+ end
19
+
20
+ it 'generates a paragraph' do
21
+ @docs.paragraph('Description of Thing Resource').must_equal "Description of Thing Resource\n\n"
22
+ end
23
+
24
+ it 'generates bold text' do
25
+ @docs.bold('name').must_equal '__name__'
26
+ end
27
+
28
+ it 'generates code formatted text' do
29
+ @docs.code('/things').must_equal '`/things`'
30
+ end
31
+
32
+ it 'generates a bullet with nest level of 1 as default' do
33
+ @docs.bullet('test').must_equal " - test\n"
34
+ end
35
+
36
+ it 'generates a bullet with nest level between 1 and 3' do
37
+ proc {@docs.bullet('test', 4)}.must_raise ArgumentError
38
+ proc {@docs.bullet('test', 0)}.must_raise ArgumentError
39
+ end
40
+
41
+ it 'generates a nested bulleted list' do
42
+ @docs.bullet('test', 2).must_equal " - test\n"
43
+ end
44
+
45
+ it 'generates a nested bullet code span' do
46
+ @docs.bullet('`/things`', 2).must_equal " - `/things`\n"
47
+ end
48
+
49
+ it 'generates nested bulleted bold text' do
50
+ @docs.bullet('__id__', 3).must_equal " - __id__\n"
51
+ end
52
+
53
+ it 'outputs a string of the completed doc' do
54
+ @docs.output.must_be_kind_of String
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::DSL::Main do
6
+ it "creates a singleton instance of hyperdrive" do
7
+ hyperdrive do
8
+ end.must_be_instance_of ::Hyperdrive::DSL::Main
9
+ end
10
+
11
+ it "registers a resource" do
12
+ hyperdrive do
13
+ resource(:thing) {}
14
+ end.resources[:thing].must_be_instance_of ::Hyperdrive::Resource
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::DSL::Resource do
6
+
7
+ it "sets the name of the endpoint" do
8
+ hyperdrive do
9
+ resource(:thing) do
10
+ name "Thing Resource"
11
+ end
12
+ end.resources[:thing].name.must_equal 'Thing Resource'
13
+ end
14
+
15
+ it "describes the endpoint" do
16
+ hyperdrive do
17
+ resource(:thing) do
18
+ desc "Thing Description"
19
+ end
20
+ end.resources[:thing].desc.must_equal 'Thing Description'
21
+ end
22
+
23
+ it "registers an allowed param on a resource" do
24
+ hyperdrive do
25
+ resource(:thing) do
26
+ param :name, "Thing's Name"
27
+ end
28
+ end.resources[:thing].allowed_params[:name][:desc].must_equal "Thing's Name"
29
+ end
30
+
31
+ it "registers an allowed param on a resource" do
32
+ hyperdrive do
33
+ resource(:thing) do
34
+ filter :parent_id, "Thing's Parent ID"
35
+ end
36
+ end.resources[:thing].filters[:parent_id][:desc].must_equal "Thing's Parent ID"
37
+ end
38
+
39
+ it "defines how requests are handled" do
40
+ hyperdrive do
41
+ resource(:thing) do
42
+ request(:get) do
43
+ 'ok'
44
+ end
45
+ end
46
+ end.resources[:thing].request_handlers[:get].call.must_equal 'ok'
47
+ end
48
+
49
+ it "raises an exception if request method argument is unknown" do
50
+ proc do
51
+ hyperdrive do
52
+ resource(:thing) do
53
+ request(:verb)
54
+ end
55
+ end
56
+ end.must_raise Hyperdrive::Errors::DSL::UnknownArgument
57
+ end
58
+ end
@@ -5,10 +5,6 @@ describe Hyperdrive::Resource do
5
5
  @resource = Hyperdrive::Resource.new(:thing)
6
6
  end
7
7
 
8
- it "creates a new resource" do
9
- @resource.resource.must_equal :thing
10
- end
11
-
12
8
  it "has a name" do
13
9
  @resource.name = 'Thing'
14
10
  @resource.name.must_equal 'Thing'
@@ -19,6 +15,10 @@ describe Hyperdrive::Resource do
19
15
  @resource.desc.must_equal 'Description of Thing Resource'
20
16
  end
21
17
 
18
+ it "has an endpoint" do
19
+ @resource.endpoint.must_equal '/things'
20
+ end
21
+
22
22
  it "auto-registers the :id param" do
23
23
  @resource.allowed_params[:id][:desc].must_equal 'Resource Identifier'
24
24
  @resource.allowed_params[:id][:required].must_equal %w(PUT PATCH DELETE)
@@ -32,7 +32,7 @@ describe Hyperdrive::Resource do
32
32
 
33
33
  it "auto-registers the :id filter" do
34
34
  @resource.filters[:id][:desc].must_equal 'Resource Identifier'
35
- @resource.filters[:id][:required].must_equal false
35
+ @resource.filters[:id][:required].must_equal false
36
36
  end
37
37
 
38
38
  it "registers a filter" do
@@ -40,4 +40,28 @@ describe Hyperdrive::Resource do
40
40
  @resource.filters[:parent_id][:desc].must_equal 'Parent ID of Thing'
41
41
  @resource.filters[:parent_id][:required].must_equal true
42
42
  end
43
+
44
+ it "defines a request handler" do
45
+ @resource.define_request_handler(:get, Proc.new { return 'ok' })
46
+ @resource.request_handlers[:get].call.must_equal 'ok'
47
+ end
48
+
49
+ it "returns the specified request handler" do
50
+ @resource.define_request_handler(:get, Proc.new { return 'ok' })
51
+ @resource.request_handler('GET').call.must_equal 'ok'
52
+ end
53
+
54
+ it "returns true if the request can be handled" do
55
+ @resource.define_request_handler(:get, Proc.new { return 'ok' })
56
+ @resource.request_method_allowed?('GET').must_equal true
57
+ end
58
+
59
+ it "returns false if the request can not be handled" do
60
+ @resource.request_method_allowed?('GET').must_equal false
61
+ end
62
+
63
+ it "returns the request methods that can handled" do
64
+ @resource.define_request_handler(:get, Proc.new { return 'ok' })
65
+ @resource.allowed_methods.must_equal ['OPTIONS','GET','HEAD']
66
+ end
43
67
  end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::Response do
6
+ before do
7
+ @resource = Hyperdrive::Resource.new(:things)
8
+ end
9
+
10
+ it "throws an error when the request method is unspported by hyperdrive" do
11
+ proc { Hyperdrive::Response.new({ 'REQUEST_METHOD' => 'TRACE' }, @resource) }.must_raise Hyperdrive::Errors::NotImplemented
12
+ end
13
+
14
+ it "throws an error when the request method is not allowed" do
15
+ proc { Hyperdrive::Response.new({ 'REQUEST_METHOD' => 'GET' }, @resource) }.must_raise Hyperdrive::Errors::MethodNotAllowed
16
+ end
17
+
18
+ it "responds to requests" do
19
+ @resource.define_request_handler(:get, Proc.new { return 'ok' })
20
+ response = Hyperdrive::Response.new({ 'REQUEST_METHOD' => 'GET' }, @resource)
21
+ response.response.must_equal 'ok'
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ describe Hyperdrive::Server do
4
+ it "handles GET requests successfully"
5
+ it "handles HEAD requests successfully"
6
+ it "handles OPTIONS requests successfully"
7
+ it "handles POST requests successfully"
8
+ it "handles PUT requests successfully"
9
+ it "handles PATCH requests successfully"
10
+ it "handles DELETE requests successfully"
11
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,13 +4,15 @@
4
4
  ENV['RACK_ENV'] = 'test'
5
5
  lib_path = File.expand_path('../lib', __FILE__)
6
6
  ($:.unshift lib_path) unless ($:.include? lib_path)
7
+
8
+ # require dependencies
7
9
  require 'bundler'
8
10
  Bundler.setup(:default, ENV['RACK_ENV'])
9
11
 
10
- # test subject
12
+ # our humble test subject
11
13
  require 'hyperdrive'
12
14
 
13
- # BDD Stack
15
+ # Fire up the BDD Stack
14
16
  require 'minitest/autorun'
15
17
  require "minitest-spec-context"
16
18
  require 'minitest/reporters'
@@ -18,3 +20,20 @@ require 'minitest/reporters'
18
20
  # all systems go
19
21
  MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
20
22
  #include Rack::Test::Methods
23
+
24
+ def sample_api
25
+ hyperdrive do
26
+ resource(:thing) do
27
+ name 'Thing Resource'
28
+ desc 'Description of Thing Resource'
29
+
30
+ param :name, '50 Chars or less'
31
+ param :start_date, 'Format: YYYY-MM-DD', required: false
32
+ param :end_date, 'Format: YYYY-MM-DD', required: false
33
+
34
+ filter :start_date, 'Format: YYYY-MM-DD'
35
+ filter :end_date, 'Format: YYYY-MM-DD'
36
+ filter :parent_id, 'Parent ID of Thing', required: true
37
+ end
38
+ end
39
+ end
metadata CHANGED
@@ -1,34 +1,101 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyperdrive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - StyleSeek Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-13 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Ruby DSL for defining self-documenting, HATEOS™ complaint, Hypermedia
11
+ date: 2014-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: linguistics
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Ruby DSL for defining self-documenting, HATEOAS™ complaint, Hypermedia
14
56
  API endpoints.
15
57
  email:
16
58
  - engineering@styleseek.com
17
- executables: []
59
+ executables:
60
+ - hyperdrive
18
61
  extensions: []
19
62
  extra_rdoc_files: []
20
63
  files:
64
+ - ".coveralls.yml"
21
65
  - ".gitignore"
22
66
  - ".travis.yml"
23
67
  - Gemfile
24
68
  - LICENSE.txt
25
69
  - README.md
26
70
  - Rakefile
71
+ - bin/console
72
+ - bin/hyperdrive
27
73
  - hyperdrive.gemspec
28
74
  - lib/hyperdrive.rb
75
+ - lib/hyperdrive/docs.rb
76
+ - lib/hyperdrive/dsl.rb
77
+ - lib/hyperdrive/dsl/main.rb
78
+ - lib/hyperdrive/dsl/resource.rb
79
+ - lib/hyperdrive/errors.rb
80
+ - lib/hyperdrive/errors/bad_request.rb
81
+ - lib/hyperdrive/errors/dsl/unknown_argument.rb
82
+ - lib/hyperdrive/errors/http_error.rb
83
+ - lib/hyperdrive/errors/internal_server_error.rb
84
+ - lib/hyperdrive/errors/method_not_allowed.rb
85
+ - lib/hyperdrive/errors/not_found.rb
86
+ - lib/hyperdrive/errors/not_implemented.rb
87
+ - lib/hyperdrive/errors/unauthorized.rb
29
88
  - lib/hyperdrive/resource.rb
89
+ - lib/hyperdrive/response.rb
90
+ - lib/hyperdrive/server.rb
91
+ - lib/hyperdrive/values.rb
30
92
  - lib/hyperdrive/version.rb
93
+ - spec/hyperdrive/docs_spec.rb
94
+ - spec/hyperdrive/dsl/main_spec.rb
95
+ - spec/hyperdrive/dsl/resource_spec.rb
31
96
  - spec/hyperdrive/resource_spec.rb
97
+ - spec/hyperdrive/response_spec.rb
98
+ - spec/hyperdrive/server_spec.rb
32
99
  - spec/spec_helper.rb
33
100
  homepage: https://github.com/styleseek/hyperdrive
34
101
  licenses:
@@ -55,5 +122,10 @@ signing_key:
55
122
  specification_version: 4
56
123
  summary: Hypermedia State Machine
57
124
  test_files:
125
+ - spec/hyperdrive/docs_spec.rb
126
+ - spec/hyperdrive/dsl/main_spec.rb
127
+ - spec/hyperdrive/dsl/resource_spec.rb
58
128
  - spec/hyperdrive/resource_spec.rb
129
+ - spec/hyperdrive/response_spec.rb
130
+ - spec/hyperdrive/server_spec.rb
59
131
  - spec/spec_helper.rb