weasel_diesel 1.0.0
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.
- 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
|