hyperdrive 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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