weasel_diesel 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/LICENSE +23 -0
- data/README.md +231 -0
- data/Rakefile +12 -0
- data/lib/documentation.rb +151 -0
- data/lib/framework_ext/sinatra.rb +30 -0
- data/lib/framework_ext/sinatra_controller.rb +80 -0
- data/lib/inflection.rb +460 -0
- data/lib/json_response_verification.rb +109 -0
- data/lib/params.rb +374 -0
- data/lib/params_verification.rb +268 -0
- data/lib/response.rb +457 -0
- data/lib/weasel_diesel.rb +415 -0
- data/lib/weasel_diesel/version.rb +3 -0
- data/lib/ws_list.rb +58 -0
- data/spec/hello_world_controller.rb +5 -0
- data/spec/hello_world_service.rb +20 -0
- data/spec/json_response_description_spec.rb +124 -0
- data/spec/json_response_verification_spec.rb +225 -0
- data/spec/params_verification_spec.rb +102 -0
- data/spec/preferences_service.rb +10 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/test_services.rb +102 -0
- data/spec/wsdsl_sinatra_ext_spec.rb +26 -0
- data/spec/wsdsl_spec.rb +314 -0
- data/weasel_diesel.gemspec +30 -0
- metadata +127 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
wsdsl (1.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
rack (1.4.1)
|
11
|
+
rack-protection (1.2.0)
|
12
|
+
rack
|
13
|
+
rack-test (0.6.1)
|
14
|
+
rack (>= 1.0)
|
15
|
+
rspec (2.9.0)
|
16
|
+
rspec-core (~> 2.9.0)
|
17
|
+
rspec-expectations (~> 2.9.0)
|
18
|
+
rspec-mocks (~> 2.9.0)
|
19
|
+
rspec-core (2.9.0)
|
20
|
+
rspec-expectations (2.9.1)
|
21
|
+
diff-lcs (~> 1.1.3)
|
22
|
+
rspec-mocks (2.9.0)
|
23
|
+
sinatra (1.3.2)
|
24
|
+
rack (~> 1.3, >= 1.3.6)
|
25
|
+
rack-protection (~> 1.2)
|
26
|
+
tilt (~> 1.3, >= 1.3.3)
|
27
|
+
tilt (1.3.3)
|
28
|
+
yard (0.7.5)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
rack-test
|
35
|
+
rspec
|
36
|
+
sinatra
|
37
|
+
wsdsl!
|
38
|
+
yard
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012 Matt Aimonetti
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
--
|
23
|
+
|
data/README.md
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
# Web Service DSL
|
2
|
+
|
3
|
+
Weasel Diesel is a simple DSL allowing developers to simply describe and
|
4
|
+
document their web APIS.
|
5
|
+
The DSL is already setup on top of Sinatra in this [example
|
6
|
+
application](https://github.com/mattetti/sinatra-web-api-example) that
|
7
|
+
you can simply fork and use as a base for your application.
|
8
|
+
|
9
|
+
DSL examples:
|
10
|
+
|
11
|
+
``` ruby
|
12
|
+
describe_service "hello_world" do |service|
|
13
|
+
service.formats :json
|
14
|
+
service.http_verb :get
|
15
|
+
service.disable_auth # on by default
|
16
|
+
|
17
|
+
# INPUT
|
18
|
+
service.param.string :name, :default => 'World'
|
19
|
+
|
20
|
+
# OUTPUT
|
21
|
+
service.response do |response|
|
22
|
+
response.object do |obj|
|
23
|
+
obj.string :message, :doc => "The greeting message sent back. Defaults to 'World'"
|
24
|
+
obj.datetime :at, :doc => "The timestamp of when the message was dispatched"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# DOCUMENTATION
|
29
|
+
service.documentation do |doc|
|
30
|
+
doc.overall "This service provides a simple hello world implementation example."
|
31
|
+
doc.param :name, "The name of the person to greet."
|
32
|
+
doc.example "<code>curl -I 'http://localhost:9292/hello_world?name=Matt'</code>"
|
33
|
+
end
|
34
|
+
|
35
|
+
# ACTION/IMPLEMENTATION (specific to the sinatra app example, can
|
36
|
+
# instead be set to call a controller action)
|
37
|
+
service.implementation do
|
38
|
+
{:message => "Hello #{params[:name]}", :at => Time.now}.to_json
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
describe_service "hello_world" do |service|
|
46
|
+
service.formats :xml
|
47
|
+
service.http_verb :get
|
48
|
+
service.disable_auth # on by default
|
49
|
+
|
50
|
+
service.param.string :name, :default => 'World'
|
51
|
+
|
52
|
+
service.response do |response|
|
53
|
+
response.element(:name => "greeting") do |e|
|
54
|
+
e.attribute "message" => :string, :doc => "The greeting message sent back."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
service.documentation do |doc|
|
59
|
+
doc.overall "This service provides a simple hello world implementation example."
|
60
|
+
doc.params :name, "The name of the person to greet."
|
61
|
+
doc.example "<code>http://example.com/hello_world.xml?name=Matt</code>"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Or a more complex example:
|
68
|
+
|
69
|
+
``` ruby
|
70
|
+
SpecOptions = ['RSpec', 'Bacon'] # usually pulled from a model
|
71
|
+
|
72
|
+
describe_service "wsdsl/test.xml" do |service|
|
73
|
+
service.formats :xml, :json
|
74
|
+
service.http_verb :get
|
75
|
+
|
76
|
+
service.params do |p|
|
77
|
+
p.string :framework, :in => SpecOptions, :null => false, :required => true
|
78
|
+
|
79
|
+
p.datetime :timestamp, :default => Time.now
|
80
|
+
p.string :alpha, :in => ['a', 'b', 'c']
|
81
|
+
p.string :version, :null => false
|
82
|
+
p.integer :num, :minvalue => 42
|
83
|
+
end
|
84
|
+
|
85
|
+
# service.param :delta, :optional => true, :type => 'float'
|
86
|
+
# All params are optional by default.
|
87
|
+
# service.param :epsilon, :type => 'string'
|
88
|
+
|
89
|
+
service.params.namespace :user do |user|
|
90
|
+
user.integer :id, :required => :true
|
91
|
+
end
|
92
|
+
|
93
|
+
# the response contains a list of player creation ratings each object in the list
|
94
|
+
|
95
|
+
service.response do |response|
|
96
|
+
response.element(:name => "player_creation_ratings") do |e|
|
97
|
+
e.attribute :id => :integer, :doc => "id doc"
|
98
|
+
e.attribute :is_accepted => :boolean, :doc => "is accepted doc"
|
99
|
+
e.attribute :name => :string, :doc => "name doc"
|
100
|
+
|
101
|
+
e.array :name => 'player_creation_rating', :type => 'PlayerCreationRating' do |a|
|
102
|
+
a.attribute :comments => :string, :doc => "comments doc"
|
103
|
+
a.attribute :player_id => :integer, :doc => "player_id doc"
|
104
|
+
a.attribute :rating => :integer, :doc => "rating doc"
|
105
|
+
a.attribute :username => :string, :doc => "username doc"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
service.documentation do |doc|
|
111
|
+
# doc.overall <markdown description text>
|
112
|
+
doc.overall <<-DOC
|
113
|
+
This is a test service used to test the framework.
|
114
|
+
DOC
|
115
|
+
|
116
|
+
# doc.params <name>, <definition>
|
117
|
+
doc.params :framework, "The test framework used, could be one of the two following: #{SpecOptions.join(", ")}."
|
118
|
+
doc.params :version, "The version of the framework to use."
|
119
|
+
|
120
|
+
# doc.example <markdown text>
|
121
|
+
doc.example <<-DOC
|
122
|
+
The most common way to use this service looks like that:
|
123
|
+
http://example.com/wsdsl/test.xml?framework=rspec&version=2.0.0
|
124
|
+
DOC
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
## JSON APIs
|
130
|
+
|
131
|
+
This library was designed with XML responses in mind and JSON support
|
132
|
+
was added later on which explains why some response methods are aliases.
|
133
|
+
Consider the following JSON response:
|
134
|
+
|
135
|
+
``` json
|
136
|
+
{ people: [
|
137
|
+
{
|
138
|
+
id : 1,
|
139
|
+
online : false,
|
140
|
+
created_at : 123123123123,
|
141
|
+
team : {
|
142
|
+
id : 1231,
|
143
|
+
score : 123.32
|
144
|
+
}
|
145
|
+
},
|
146
|
+
{
|
147
|
+
id : 2,
|
148
|
+
online : true,
|
149
|
+
created_at : 123123123123,
|
150
|
+
team: {
|
151
|
+
id : 1233,
|
152
|
+
score : 1.32
|
153
|
+
}
|
154
|
+
},
|
155
|
+
] }
|
156
|
+
|
157
|
+
It would be described as follows:
|
158
|
+
|
159
|
+
``` ruby
|
160
|
+
describe_service "json_list" do |service|
|
161
|
+
service.formats :json
|
162
|
+
service.response do |response|
|
163
|
+
response.array :people do |node|
|
164
|
+
node.integer :id
|
165
|
+
node.boolean :online
|
166
|
+
node.datetime :created_at
|
167
|
+
node.object :team do |team|
|
168
|
+
team.integer :id
|
169
|
+
team.float :score, :null => true
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Nodes/elements can also use some meta attributes. Currently the
|
177
|
+
following meta attributes are available:
|
178
|
+
|
179
|
+
* key (refers to an attribute name that is key to this object)
|
180
|
+
* type (refers to the type of object described, valuable when using JSON
|
181
|
+
cross OO based apps.
|
182
|
+
|
183
|
+
JSON response validation can be done using an optional module.
|
184
|
+
Look at the spec/json_response_verification_spec.rb file for a complete
|
185
|
+
example. The goal of this module is to help automate API testing by
|
186
|
+
validating the data structure of the returned object.
|
187
|
+
|
188
|
+
## Test suite
|
189
|
+
|
190
|
+
This library comes with a test suite requiring Ruby 1.9.2
|
191
|
+
The following gems need to be available:
|
192
|
+
Rspec, Rack, Sinatra
|
193
|
+
|
194
|
+
## RUBY 1.8 warning
|
195
|
+
|
196
|
+
This library was written for Ruby 1.9 and 1.8 support was added later on
|
197
|
+
via the backports libary and some tweaks. However, because unlike in
|
198
|
+
ruby 1.9, the hash insert order isn't kept in 1.8 the following syntax
|
199
|
+
isn't supported and the alternative version needs to be used:
|
200
|
+
|
201
|
+
``` ruby
|
202
|
+
service.response do |response|
|
203
|
+
response.element(:name => "player_creation_ratings") do |e|
|
204
|
+
e.attribute :id => :integer, :doc => "id doc"
|
205
|
+
e.attribute :is_accepted => :boolean, :doc => "is accepted doc"
|
206
|
+
e.attribute :name => :string, :doc => "name doc"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
Instead the following version should be used:
|
212
|
+
|
213
|
+
``` ruby
|
214
|
+
service.response do |response|
|
215
|
+
response.element(:name => "player_creation_ratings") do |e|
|
216
|
+
e.integer :id, :doc => "id doc"
|
217
|
+
e.boolean :is_accepted, :doc => "is accepted doc"
|
218
|
+
e.string :name, :doc => "name doc"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
Both code snippets do the exact same thing but the first version is 1.9
|
224
|
+
only.
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
## Copyright
|
229
|
+
|
230
|
+
Copyright (c) 2012 Matt Aimonetti. See LICENSE for
|
231
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
6
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
task :default => :spec
|
10
|
+
|
11
|
+
require 'yard'
|
12
|
+
YARD::Rake::YardocTask.new
|
@@ -0,0 +1,151 @@
|
|
1
|
+
class WeaselDiesel
|
2
|
+
# Service documentation class
|
3
|
+
#
|
4
|
+
# @api public
|
5
|
+
class Documentation
|
6
|
+
|
7
|
+
# @api public
|
8
|
+
attr_reader :desc
|
9
|
+
|
10
|
+
# @api public
|
11
|
+
attr_reader :params_doc
|
12
|
+
|
13
|
+
# @api public
|
14
|
+
attr_reader :namespaced_params
|
15
|
+
|
16
|
+
# @api public
|
17
|
+
attr_reader :examples
|
18
|
+
|
19
|
+
# @api public
|
20
|
+
attr_reader :elements
|
21
|
+
|
22
|
+
# This class contains the documentation information regarding an element.
|
23
|
+
# Currently, elements are only used in the response info.
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
class ElementDoc
|
27
|
+
|
28
|
+
# @api public
|
29
|
+
attr_reader :name, :attributes
|
30
|
+
|
31
|
+
# @param [String] The element's name
|
32
|
+
# @api public
|
33
|
+
def initialize(name)
|
34
|
+
# raise ArgumentError, "An Element doc needs to be initialize by passing a hash with a ':name' keyed entry." unless opts.is_a?(Hash) && opts.has_key?(:name)
|
35
|
+
@name = name
|
36
|
+
@attributes = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [String] name The name of the attribute described
|
40
|
+
# @param [String] desc The description of the attribute
|
41
|
+
# @api public
|
42
|
+
def attribute(name, desc)
|
43
|
+
@attributes[name] = desc
|
44
|
+
end
|
45
|
+
|
46
|
+
end # of ElementDoc
|
47
|
+
|
48
|
+
# Namespaced param documentation
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
class NamespacedParam
|
52
|
+
|
53
|
+
# @return [String, Symbol] The name of the namespaced, usually a symbol
|
54
|
+
# @api public
|
55
|
+
attr_reader :name
|
56
|
+
|
57
|
+
# @return [Hash] The list of params within the namespace
|
58
|
+
# @api public
|
59
|
+
attr_reader :params
|
60
|
+
|
61
|
+
# @api public
|
62
|
+
def initialize(name)
|
63
|
+
@name = name
|
64
|
+
@params = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sets the description/documentation of a specific namespaced param
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
# @api public
|
71
|
+
def param(name, desc)
|
72
|
+
@params[name] = desc
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# Initialize a Documentation object wrapping all the documentation aspect of the service.
|
78
|
+
# The response documentation is a Documentation instance living inside the service documentation object.
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def initialize
|
82
|
+
@params_doc = {}
|
83
|
+
@examples = []
|
84
|
+
@elements = []
|
85
|
+
@namespaced_params = []
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets or returns the overall description
|
89
|
+
#
|
90
|
+
# @param [String] desc Service overall description
|
91
|
+
# @api public
|
92
|
+
# @return [String] The overall service description
|
93
|
+
def overall(desc)
|
94
|
+
if desc.nil?
|
95
|
+
@desc
|
96
|
+
else
|
97
|
+
@desc = desc
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Sets the description/documentation of a specific param
|
102
|
+
#
|
103
|
+
# @return [String]
|
104
|
+
# @api public
|
105
|
+
def params(name, desc)
|
106
|
+
@params_doc[name] = desc
|
107
|
+
end
|
108
|
+
alias_method :param, :params
|
109
|
+
|
110
|
+
# Define a new namespaced param and yield it to the passed block
|
111
|
+
# if available.
|
112
|
+
#
|
113
|
+
# @return [Array] the namespaced params
|
114
|
+
# @api public
|
115
|
+
def namespace(ns_name)
|
116
|
+
new_ns_param = NamespacedParam.new(ns_name)
|
117
|
+
if block_given?
|
118
|
+
yield(new_ns_param)
|
119
|
+
end
|
120
|
+
@namespaced_params << new_ns_param
|
121
|
+
end
|
122
|
+
|
123
|
+
def response
|
124
|
+
@response ||= Documentation.new
|
125
|
+
end
|
126
|
+
|
127
|
+
# Service usage example
|
128
|
+
#
|
129
|
+
# @param [String] desc Usage example.
|
130
|
+
# @return [Array<String>] All the examples.
|
131
|
+
# @api public
|
132
|
+
def example(desc)
|
133
|
+
@examples << desc
|
134
|
+
end
|
135
|
+
|
136
|
+
# Add a new element to the doc
|
137
|
+
# currently only used for response doc
|
138
|
+
#
|
139
|
+
# @param [Hash] opts element's documentation options
|
140
|
+
# @yield [ElementDoc] The new element doc.
|
141
|
+
# @return [Array<ElementDoc>]
|
142
|
+
# @api public
|
143
|
+
def element(opts={})
|
144
|
+
element = ElementDoc.new(opts)
|
145
|
+
yield(element)
|
146
|
+
@elements << element
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end # of Documentation
|
151
|
+
end
|