raml_ruby 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +71 -0
  7. data/Rakefile +1 -0
  8. data/fixtures/include_1.raml +7 -0
  9. data/fixtures/schemas/canonicalSchemas.raml +3 -0
  10. data/fixtures/schemas/filesystem/file.json +1 -0
  11. data/fixtures/schemas/filesystem/files.json +1 -0
  12. data/fixtures/schemas/filesystem/fileupdate.json +1 -0
  13. data/fixtures/schemas/filesystem/relative/test.json +1 -0
  14. data/lib/raml.rb +104 -0
  15. data/lib/raml/exceptions.rb +27 -0
  16. data/lib/raml/mixin/bodies.rb +32 -0
  17. data/lib/raml/mixin/documentable.rb +32 -0
  18. data/lib/raml/mixin/global.rb +20 -0
  19. data/lib/raml/mixin/headers.rb +22 -0
  20. data/lib/raml/mixin/merge.rb +24 -0
  21. data/lib/raml/mixin/parent.rb +54 -0
  22. data/lib/raml/mixin/validation.rb +49 -0
  23. data/lib/raml/node.rb +219 -0
  24. data/lib/raml/node/abstract_method.rb +61 -0
  25. data/lib/raml/node/abstract_resource.rb +165 -0
  26. data/lib/raml/node/abstract_resource_circular.rb +5 -0
  27. data/lib/raml/node/body.rb +94 -0
  28. data/lib/raml/node/documentation.rb +28 -0
  29. data/lib/raml/node/header.rb +4 -0
  30. data/lib/raml/node/method.rb +106 -0
  31. data/lib/raml/node/parameter/abstract_parameter.rb +251 -0
  32. data/lib/raml/node/parameter/base_uri_parameter.rb +6 -0
  33. data/lib/raml/node/parameter/form_parameter.rb +6 -0
  34. data/lib/raml/node/parameter/query_parameter.rb +6 -0
  35. data/lib/raml/node/parameter/uri_parameter.rb +7 -0
  36. data/lib/raml/node/parametized_reference.rb +15 -0
  37. data/lib/raml/node/reference.rb +4 -0
  38. data/lib/raml/node/resource.rb +26 -0
  39. data/lib/raml/node/resource_type.rb +20 -0
  40. data/lib/raml/node/resource_type_reference.rb +5 -0
  41. data/lib/raml/node/response.rb +32 -0
  42. data/lib/raml/node/root.rb +246 -0
  43. data/lib/raml/node/schema.rb +41 -0
  44. data/lib/raml/node/schema_reference.rb +5 -0
  45. data/lib/raml/node/template.rb +55 -0
  46. data/lib/raml/node/trait.rb +18 -0
  47. data/lib/raml/node/trait_reference.rb +5 -0
  48. data/lib/raml/parser.rb +57 -0
  49. data/lib/raml/parser/include.rb +25 -0
  50. data/lib/raml/patch/hash.rb +6 -0
  51. data/lib/raml/patch/module.rb +12 -0
  52. data/lib/raml/version.rb +3 -0
  53. data/raml_ruby.gemspec +35 -0
  54. data/raml_spec_reqs.md +276 -0
  55. data/templates/abstract_parameter.slim +68 -0
  56. data/templates/body.slim +15 -0
  57. data/templates/collapse.slim +10 -0
  58. data/templates/documentation.slim +2 -0
  59. data/templates/method.slim +38 -0
  60. data/templates/resource.slim +33 -0
  61. data/templates/response.slim +13 -0
  62. data/templates/root.slim +39 -0
  63. data/templates/style.sass +119 -0
  64. data/test/apis/box-api.raml +4224 -0
  65. data/test/apis/instagram-api.raml +3378 -0
  66. data/test/apis/stripe-api.raml +12227 -0
  67. data/test/apis/twilio-rest-api.raml +6618 -0
  68. data/test/apis/twitter-rest-api.raml +34284 -0
  69. data/test/raml/body_spec.rb +268 -0
  70. data/test/raml/documentation_spec.rb +49 -0
  71. data/test/raml/header_spec.rb +17 -0
  72. data/test/raml/include_spec.rb +40 -0
  73. data/test/raml/method_spec.rb +701 -0
  74. data/test/raml/parameter/abstract_parameter_spec.rb +564 -0
  75. data/test/raml/parameter/form_parameter_spec.rb +17 -0
  76. data/test/raml/parameter/query_parameter_spec.rb +33 -0
  77. data/test/raml/parameter/uri_parameter_spec.rb +44 -0
  78. data/test/raml/parser_spec.rb +53 -0
  79. data/test/raml/raml_spec.rb +32 -0
  80. data/test/raml/resource_spec.rb +440 -0
  81. data/test/raml/resource_type_spec.rb +51 -0
  82. data/test/raml/response_spec.rb +251 -0
  83. data/test/raml/root_spec.rb +655 -0
  84. data/test/raml/schema_spec.rb +110 -0
  85. data/test/raml/spec_helper.rb +11 -0
  86. data/test/raml/template_spec.rb +98 -0
  87. data/test/raml/trait_spec.rb +31 -0
  88. metadata +337 -0
@@ -0,0 +1,268 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Raml::Body do
4
+ let (:media_type) { 'text/xml' }
5
+ let (:body_data ) {
6
+ YAML.load(%q(
7
+ schema: |
8
+ {
9
+ "$schema": "http://json-schema.org/draft-03/schema#",
10
+ "properties": {
11
+ "input": {
12
+ "required": false,
13
+ "type": "string"
14
+ }
15
+ },
16
+ "required": false,
17
+ "type": "object"
18
+ }
19
+ ))
20
+ }
21
+ let(:form_body_data) {
22
+ YAML.load(%q(
23
+ formParameters:
24
+ param:
25
+ type: string
26
+ ))
27
+ }
28
+ let(:root) { Raml::Root.new 'title' => 'x', 'baseUri' => 'http://foo.com', 'schemas' => [{'Job' => 'xxx'}] }
29
+
30
+ subject { Raml::Body.new media_type, body_data, root }
31
+
32
+ describe '#initialize' do
33
+ context 'when the media type is valid' do
34
+ it "inits body with media_type" do
35
+ expect( subject.media_type ).to eq media_type
36
+ end
37
+ end
38
+ context 'when the media type is invalid' do
39
+ let(:media_type) { 'foo' }
40
+ it { expect { subject }.to raise_error Raml::InvalidMediaType }
41
+ end
42
+
43
+ context 'when the body is not a web form' do
44
+ context 'when the schema property is valid schema' do
45
+ it "inits body with schema" do
46
+ expect( subject.schema ).to be_an Raml::Schema
47
+ end
48
+ end
49
+ context 'when the schema property is valid schema reference' do
50
+ let (:body_data ) { { 'schema' => 'Job' } }
51
+ it "inits body with schema" do
52
+ expect( subject.schema ).to be_an Raml::SchemaReference
53
+ end
54
+ end
55
+ context 'when the schema property is not a string' do
56
+ let (:body_data ) { { 'schema' => 1 } }
57
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /schema/ }
58
+ end
59
+ context 'when the schema property is an empty string' do
60
+ let (:body_data ) { { 'schema' => '' } }
61
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /schema/ }
62
+ end
63
+ end
64
+ context 'when body is a web form' do
65
+ let(:body_data) { form_body_data }
66
+ [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].each do |mtype|
67
+ context "when media type is #{mtype}" do
68
+ let(:media_type) { mtype }
69
+ context 'when a formParameters property is not provided' do
70
+ before { body_data.delete 'formParameters' }
71
+ it { expect { subject }.to raise_error Raml::RequiredPropertyMissing, /formParameters/ }
72
+ end
73
+ context 'when a formParameters property is provided' do
74
+ context 'when a formParameters property is valid' do
75
+ it { expect { subject }.to_not raise_error }
76
+ it 'stores all as Raml::Parameter::FormParameter instances' do
77
+ expect( subject.form_parameters.values ).to all( be_a Raml::Parameter::FormParameter )
78
+ subject.form_parameters.keys.should contain_exactly('param')
79
+ end
80
+ end
81
+ context 'when the formParameters property is not a map' do
82
+ before { body_data['formParameters'] = 1 }
83
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /formParameters/ }
84
+ end
85
+ context 'when the formParameters property is not a map with non-string keys' do
86
+ before { body_data['formParameters'] = { 1 => {}} }
87
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /formParameters/ }
88
+ end
89
+ context 'when the formParameters property is not a map with non-string keys' do
90
+ before { body_data['formParameters'] = { '1' => 'x'} }
91
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /formParameters/ }
92
+ end
93
+ end
94
+
95
+ context 'when a schema property is not provided' do
96
+ it { expect { subject }.to_not raise_error }
97
+ end
98
+ context 'when a schema property is provided' do
99
+ let(:body_data) {
100
+ YAML.load %q(
101
+ schema: |
102
+ {
103
+ "$schema": "http://json-schema.org/draft-03/schema#",
104
+ "properties": {
105
+ "input": {
106
+ "required": false,
107
+ "type": "string"
108
+ }
109
+ },
110
+ "required": false,
111
+ "type": "object"
112
+ }
113
+ formParameters:
114
+ param:
115
+ type: string
116
+ )
117
+ }
118
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /schema/ }
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#form_parameters' do
126
+ context 'when body is not a web form' do
127
+ it 'returns no form parameters' do
128
+ subject.form_parameters { should be_empty }
129
+ end
130
+ end
131
+ context 'when body is a web form' do
132
+ let(:body_data) { form_body_data }
133
+ it 'returns form parameters' do
134
+ subject.form_parameters { should_not be_empty }
135
+ expect( subject.form_parameters.values ).to all( be_a Raml::Parameter::FormParameter )
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#web_form?' do
141
+ context 'when body isnt a web form' do
142
+ it { should_not be_web_form }
143
+ end
144
+ context 'when body is a web form' do
145
+ let(:body_data) { form_body_data }
146
+ [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].each do |mtype|
147
+ context "when media type is #{mtype}" do
148
+ let(:media_type) { mtype }
149
+ it { should be_web_form }
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ describe '#merge' do
156
+ context 'when body and mixin have different media types' do
157
+ let(:body ) { Raml::Body.new 'foo/bar', {}, root }
158
+ let(:mixin) { Raml::Body.new 'foo/boo', {}, root }
159
+ it { expect { body.merge mixin }.to raise_error Raml::MergeError }
160
+ end
161
+ context 'when body and mixin have the same media type' do
162
+ let(:body ) { Raml::Body.new 'foo/bar', body_data , root }
163
+ let(:mixin) { Raml::Body.new 'foo/bar', mixin_data, root }
164
+
165
+ context 'when the body to merge in has a property set' do
166
+ context 'when the body to merge into does not have the property set' do
167
+ let(:body_data) { {} }
168
+ context 'example property' do
169
+ let(:mixin_data) { {'example' => 'body example'} }
170
+ it 'merges in the property' do
171
+ body.merge(mixin).example.should eq mixin.example
172
+ end
173
+ end
174
+ context 'schema property' do
175
+ let(:mixin_data) { {'schema' => 'mixin schema'} }
176
+ it 'merges in the property' do
177
+ body.merge(mixin).schema.should be_a Raml::Schema
178
+ body.schema.should eq mixin.schema
179
+ end
180
+ end
181
+ context 'formParameters properties' do
182
+ let(:mixin_data) { {
183
+ 'formParameters' => {
184
+ 'param1' => {'description' => 'foo'},
185
+ 'param2' => {'description' => 'bar'}
186
+ }
187
+ } }
188
+ it 'adds the form parameters to the body' do
189
+ body.merge(mixin).form_parameters.keys.should contain_exactly('param1', 'param2')
190
+ end
191
+ end
192
+ end
193
+ context 'when the body to merge into has the property set' do
194
+ context 'example property' do
195
+ let(:body_data ) { {'example' => 'body example'} }
196
+ let(:mixin_data) { {'example' => 'mixin example'} }
197
+ it 'overrides the property' do
198
+ body.merge(mixin).example.should eq 'mixin example'
199
+ end
200
+ end
201
+ context 'schema property' do
202
+ let(:body_data ) { {'schema' => 'body schema' } }
203
+ let(:mixin_data) { {'schema' => 'mixin schema'} }
204
+ it 'overrides the property' do
205
+ body.merge(mixin).schema.value.should eq 'mixin schema'
206
+ end
207
+ end
208
+ context 'formParameters properties' do
209
+ let(:body_data) { {
210
+ 'formParameters' => {
211
+ 'param1' => {'description' => 'foo'},
212
+ 'param2' => {'description' => 'bar'}
213
+ }
214
+ } }
215
+ context 'when the merged in body form parameters are different from the form parametes of the body merged into' do
216
+ let(:mixin_data) { {
217
+ 'formParameters' => {
218
+ 'param3' => {'description' => 'foo2'},
219
+ 'param4' => {'description' => 'bar2'}
220
+ }
221
+ } }
222
+ it 'adds the form parameters to the body' do
223
+ body.merge(mixin).form_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
224
+ end
225
+ end
226
+ context 'when the merged in body form parameters overlap with the form parametes of the body merged into' do
227
+ let(:mixin_data) { {
228
+ 'formParameters' => {
229
+ 'param2' => {'description' => 'bar3', 'displayName' => 'Param 3'},
230
+ 'param3' => {'description' => 'foo2'},
231
+ 'param4' => {'description' => 'bar2'}
232
+ }
233
+ } }
234
+ it 'merges the matching orm parameters and adds the non-matching orm parameters to the body' do
235
+ body.merge(mixin).form_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
236
+ body.form_parameters['param2'].display_name.should eq mixin.form_parameters['param2'].display_name
237
+ body.form_parameters['param2'].description.should eq mixin.form_parameters['param2'].description
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ describe '#document' do
247
+ context 'when the body is not a form body' do
248
+ it 'returns a String' do
249
+ subject.document.should be_a String
250
+ end
251
+ it 'should render the template' do
252
+ mock(Slim::Template).new(/templates\/body.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
253
+ subject.document
254
+ end
255
+ end
256
+ context 'when the body is a form body' do
257
+ let(:media_type) { 'application/x-www-form-urlencoded' }
258
+ let(:body_data) { form_body_data }
259
+ it 'returns a String' do
260
+ subject.document.should be_a String
261
+ end
262
+ it 'should render the template' do
263
+ mock(Slim::Template).new(/templates\/body.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
264
+ subject.document
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: UTF-8
2
+ require_relative 'spec_helper'
3
+
4
+ describe Raml::Documentation do
5
+ let(:root_data) { {'title' => 'x', 'baseUri' => 'http://foo.com'} }
6
+ let(:root) { Raml::Root.new root_data }
7
+
8
+ let(:title) { 'Request Formats' }
9
+ let(:content) { <<-EOS
10
+ Credentials
11
+ -----------
12
+
13
+ All requests to Twilio's REST API require you to authenticate using [HTTP basic auth](http://en.wikipedia.org/wiki/Basic_access_authentication) to convey your identity. The username is your AccountSid (a 34 character string, starting with the letters AC). The password is your AuthToken. Your AccountSid and AuthToken are on the [Account Dashboard](http://www.twilio.com/user/account/) page.
14
+
15
+ Most HTTP clients (including web-browsers) present a dialog or prompt for you to provide a username and password for HTTP basic auth. Most clients will also allow you to provide credentials in the URL itself. For example:
16
+
17
+ ```
18
+ https://{AccountSid}:{AuthToken}@api.twilio.com/2010-04-01/Accounts
19
+ ```
20
+
21
+ Retrieving Resources with the HTTP GET Method
22
+ ---------------------------------------------
23
+
24
+ You can retrieve a representation of a resource by GETting its url. The easiest way to do this is to copy and paste a URL into your web browser's address bar.
25
+
26
+ ### Possible GET Response Status Codes
27
+
28
+ * 200 OK: The request was successful and the response body contains the representation requested.
29
+ * 302 FOUND: A common redirect response; you can GET the representation at the URI in the Location response header.
30
+ * 304 NOT MODIFIED: Your client's cached version of the representation is still up to date.
31
+ * 401 UNAUTHORIZED: The supplied credentials, if any, are not sufficient to access the resource.
32
+ * 404 NOT FOUND: You know this one.
33
+ * 429 TOO MANY REQUESTS: Your application is sending too many simultaneous requests.
34
+ * 500 SERVER ERROR: We couldn't return the representation due to an internal server error.
35
+ * 503 SERVICE UNAVAILABLE: We are temporarily unable to return the representation. Please wait for a bit and try again.
36
+ EOS
37
+ }
38
+ subject { Raml::Documentation.new(title, {'content' => content }, root) }
39
+
40
+ describe '#document' do
41
+ it 'returns a String' do
42
+ subject.document.should be_a String
43
+ end
44
+ it 'should render the template' do
45
+ mock(Slim::Template).new(/templates\/documentation.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
46
+ subject.document
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Raml::Header do
4
+ let(:root_data) { {'title' => 'x', 'baseUri' => 'http://foo.com'} }
5
+ let(:root) { Raml::Root.new root_data }
6
+ let(:name) { 'meta' }
7
+ let (:data) {
8
+ YAML.load(%q(
9
+ displayName: Job Metadata
10
+ description: Field names prefixed with x-Zencoder-job-metadata- contain user-specified metadata.
11
+ ))
12
+ }
13
+
14
+ it "should instanciate Header" do
15
+ Raml::Header.new(name, data, root)
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Raml::Parser::Include do
5
+ let(:include) { Raml::Parser::Include.new }
6
+ describe '#init_with' do
7
+ it 'fetches the path from the given Psych::Coder instance' do
8
+ include.init_with OpenStruct.new scalar: 'path_to_file'
9
+ include.path.should eq 'path_to_file'
10
+ end
11
+ end
12
+
13
+ describe '#content' do
14
+ before { include.init_with OpenStruct.new scalar: path }
15
+ context 'when the path in the include directive is absolute' do
16
+ let(:path) { '/absolute_file_path'}
17
+ it 'opens and reads the file' do
18
+ mock(File).open(path).stub!.read { 'content' }
19
+ include.content('cwd').should eq 'content'
20
+ end
21
+ end
22
+ context 'when the path in the include directive is relative' do
23
+ let(:path) { 'relative_file_path'}
24
+ it 'opens and reads the file' do
25
+ mock(File).open("cwd/#{path}").stub!.read { 'content' }
26
+ include.content('cwd').should eq 'content'
27
+ end
28
+ end
29
+ [ 'yaml', 'yml', 'raml' ].each do |ext|
30
+ context "when include directive points to a file ending in .#{ext}" do
31
+ let(:path) { "/absolute_file_path.#{ext}" }
32
+ let(:content) { { 'a' => [1,2,3], 'b' => 4 } }
33
+ it 'parses the file as YAML' do
34
+ stub(File).open(path).stub!.read { content.to_yaml }
35
+ include.content('cwd').should eq content
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,701 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Raml::Method do
4
+ let(:name) { 'get' }
5
+ let(:data) {
6
+ YAML.load(%q(
7
+ description: Get a list of users
8
+ queryParameters:
9
+ page:
10
+ description: Specify the page that you want to retrieve
11
+ type: integer
12
+ required: true
13
+ example: 1
14
+ per_page:
15
+ description: Specify the amount of items that will be retrieved per page
16
+ type: integer
17
+ minimum: 10
18
+ maximum: 200
19
+ default: 30
20
+ example: 50
21
+ protocols: [ HTTP, HTTPS ]
22
+ responses:
23
+ 200:
24
+ description: |
25
+ The list of popular media.
26
+ ))
27
+ }
28
+ let(:root_data) { {'title' => 'x', 'baseUri' => 'http://foo.com'} }
29
+ let(:root) { Raml::Root.new root_data }
30
+
31
+ subject { Raml::Method.new(name, data, root) }
32
+
33
+ describe '#new' do
34
+ it "should instanciate Method" do
35
+ subject
36
+ end
37
+
38
+ context 'when the method is a method defined in RFC2616 or RFC5789' do
39
+ %w(options get head post put delete trace connect patch).each do |method|
40
+ context "when the method is #{method}" do
41
+ let(:name) { method }
42
+ it { expect { subject }.to_not raise_error }
43
+ end
44
+ end
45
+ end
46
+ context 'when the method is an unsupported method' do
47
+ %w(propfind proppatch mkcol copy move lock unlock).each do |method|
48
+ context "when the method is #{method}" do
49
+ let(:name) { method }
50
+ it { expect { subject }.to raise_error Raml::InvalidMethod }
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'when description property is not given' do
56
+ let(:data) { {} }
57
+ it { expect { subject }.to_not raise_error }
58
+ end
59
+ context 'when description property is given' do
60
+ context 'when the description property is not a string' do
61
+ let(:data) { { 'description' => 1 } }
62
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /description/ }
63
+ end
64
+ context 'when the description property is a string' do
65
+ let(:data) { { 'description' => 'My Description' } }
66
+ it { expect { subject }.to_not raise_error }
67
+ it 'should store the value' do
68
+ subject.description.should eq data['description']
69
+ end
70
+ it 'uses the description in the documentation' do
71
+ subject.document.should include data['description']
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'when the headers parameter is given' do
77
+ context 'when the headers property is well formed' do
78
+ let (:data) {
79
+ YAML.load(%q(
80
+ headers:
81
+ Zencoder-Api-Key:
82
+ displayName: ZEncoder API Key
83
+ x-Zencoder-job-metadata-{*}:
84
+ displayName: Job Metadata
85
+ ))
86
+ }
87
+ it { expect { subject }.to_not raise_error }
88
+ it 'stores all as Raml::Header instances' do
89
+ expect( subject.headers.values ).to all( be_a Raml::Header )
90
+ expect( subject.headers.keys ).to contain_exactly('Zencoder-Api-Key','x-Zencoder-job-metadata-{*}')
91
+ end
92
+ end
93
+ context 'when the headers property is not a map' do
94
+ let(:data) { { 'headers' => 1 } }
95
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /headers/ }
96
+ end
97
+ context 'when the headers property is not a map with non-string keys' do
98
+ let(:data) { { 'headers' => { 1 => {}} } }
99
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /headers/ }
100
+ end
101
+ context 'when the headers property is not a map with non-string keys' do
102
+ let(:data) { { 'headers' => { '1' => 'x'} } }
103
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /headers/ }
104
+ end
105
+ end
106
+
107
+ context 'when the protocols property is missing' do
108
+ let(:data) { { } }
109
+ it { expect{ subject }.to_not raise_error }
110
+ end
111
+ context 'when the protocols property is given' do
112
+ context 'when the protocol property is not an array' do
113
+ let(:data) { { 'protocols' => 'HTTP' } }
114
+ it { expect{ subject }.to raise_error Raml::InvalidProperty, /protocols/ }
115
+ end
116
+ context 'when the protocol property is an array but not all elements are strings' do
117
+ let(:data) { { 'protocols' => ['HTTP', 1] } }
118
+ it { expect{ subject }.to raise_error Raml::InvalidProperty, /protocols/ }
119
+ end
120
+ context 'when the protocol property is an array of strings with invalid values' do
121
+ let(:data) { { 'protocols' => ['HTTP', 'foo'] } }
122
+ it { expect{ subject }.to raise_error Raml::InvalidProperty, /protocols/ }
123
+ end
124
+ [
125
+ [ 'HTTP' ],
126
+ [ 'HTTPS' ],
127
+ [ 'HTTP', 'HTTPS' ]
128
+ ].each do |protocols|
129
+ context "when the protocol property is #{protocols}" do
130
+ let(:data) { { 'protocols' => protocols } }
131
+ it { expect{ subject }.to_not raise_error }
132
+ it 'stores the values' do
133
+ subject.protocols.should eq protocols
134
+ end
135
+ end
136
+ end
137
+ context 'when the protocol property is an array of valid values in lowercase' do
138
+ let(:data) { { 'protocols' => ['http', 'https'] } }
139
+ it 'uppercases them' do
140
+ subject.protocols.should eq [ 'HTTP', 'HTTPS' ]
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'when a queryParameters property is given' do
146
+ context 'when the queryParameters property is well formed' do
147
+ let(:data) {
148
+ YAML.load(
149
+ %q(
150
+ description: Get a list of users
151
+ queryParameters:
152
+ page:
153
+ description: Specify the page that you want to retrieve
154
+ type: integer
155
+ required: true
156
+ example: 1
157
+ per_page:
158
+ description: Specify the amount of items that will be retrieved per page
159
+ type: integer
160
+ minimum: 10
161
+ maximum: 200
162
+ default: 30
163
+ example: 50
164
+ )
165
+ )
166
+ }
167
+
168
+ it { expect { subject }.to_not raise_error }
169
+ it 'stores all as Raml::Parameter::UriParameter instances' do
170
+ expect( subject.query_parameters.values ).to all( be_a Raml::Parameter::QueryParameter )
171
+ subject.query_parameters.keys.should contain_exactly('page', 'per_page')
172
+ end
173
+ end
174
+ context 'when the queryParameters property is not a map' do
175
+ before { data['queryParameters'] = 1 }
176
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /queryParameters/ }
177
+ end
178
+ context 'when the queryParameters property is not a map with non-string keys' do
179
+ before { data['queryParameters'] = { 1 => {}} }
180
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /queryParameters/ }
181
+ end
182
+ context 'when the queryParameters property is not a map with non-string keys' do
183
+ before { data['queryParameters'] = { '1' => 'x'} }
184
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /queryParameters/ }
185
+ end
186
+ end
187
+
188
+ context 'when a body property is given' do
189
+ context 'when the body property is well formed' do
190
+ let(:data) {
191
+ YAML.load(
192
+ %q(
193
+ description: Create a Job
194
+ body:
195
+ text/xml:
196
+ schema: job_xml_schema
197
+ application/json:
198
+ schema: json_xml_schema
199
+ )
200
+ )
201
+ }
202
+
203
+ it { expect { subject }.to_not raise_error }
204
+ it 'stores all as Raml::Body instances' do
205
+ expect( subject.bodies.values ).to all( be_a Raml::Body )
206
+ subject.bodies.keys.should contain_exactly('text/xml', 'application/json')
207
+ end
208
+ end
209
+ context 'when the body property is not a map' do
210
+ before { data['body'] = 1 }
211
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /body/ }
212
+ end
213
+ context 'when the body property is a map with non-string keys' do
214
+ before { data['body'] = { 1 => {}} }
215
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /body/ }
216
+ end
217
+ context 'when the body property is a map with non-map values' do
218
+ before { data['body'] = { 'text/xml' => 1 } }
219
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /body/ }
220
+ end
221
+ end
222
+
223
+ context 'when a responses property is given' do
224
+ context 'when the responses property is well formed' do
225
+ let(:data) {
226
+ YAML.load(
227
+ %q(
228
+ responses:
229
+ 200:
230
+ body:
231
+ application/json:
232
+ example: !include examples/instagram-v1-media-popular-example.json
233
+ 503:
234
+ description: The service is currently unavailable.
235
+ )
236
+ )
237
+ }
238
+
239
+ it { expect { subject }.to_not raise_error }
240
+ it 'stores all as Raml::Response instances' do
241
+ expect( subject.responses.values ).to all( be_a Raml::Response )
242
+ subject.responses.keys.should contain_exactly(200, 503)
243
+ end
244
+ end
245
+ context 'when the responses property is not a map' do
246
+ before { data['responses'] = 1 }
247
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /responses/ }
248
+ end
249
+ context 'when the responses property is not a map with string integer keys' do
250
+ before { data['responses'] = { '200' => {}} }
251
+ it { expect { subject }.not_to raise_error }
252
+ end
253
+ context 'when the responses property is not a map with non-string keys' do
254
+ before { data['responses'] = { 200 => 'x'} }
255
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /responses/ }
256
+ end
257
+ end
258
+
259
+ context 'when an is property is given' do
260
+ let(:root) {
261
+ Raml::Root.new 'title' => 'x', 'baseUri' => 'http://foo.com', 'traits' => [
262
+ { 'secured' => {} },
263
+ { 'paged' => {} },
264
+ { 'rateLimited' => {} }
265
+ ]
266
+ }
267
+ context 'when the property is valid' do
268
+ context 'when the property is an array of trait references' do
269
+ let(:data) { { 'is' => [ 'secured', 'paged' ] } }
270
+ it { expect { subject }.to_not raise_error }
271
+ it 'should store the trait references' do
272
+ subject.traits.should all( be_a Raml::TraitReference )
273
+ subject.traits.map(&:name).should contain_exactly('secured', 'paged')
274
+ end
275
+ end
276
+ context 'when the property is an array of trait references with parameters' do
277
+ let(:data) { {
278
+ 'is' => [
279
+ {'secured' => {'tokenName' => 'access_token'}},
280
+ {'paged' => {'maxPages' => 10 }}
281
+ ]
282
+ } }
283
+ it { expect { subject }.to_not raise_error }
284
+ it 'should store the trait references' do
285
+ subject.traits.should all( be_a Raml::TraitReference )
286
+ subject.traits.map(&:name).should contain_exactly('secured', 'paged')
287
+ end
288
+ end
289
+ context 'when the property is an array of trait definitions' do
290
+ let(:data) { {
291
+ 'is' => [
292
+ {'queryParameters' => {'tokenName' => {'description'=>'foo'}}},
293
+ {'queryParameters' => {'numPages' => {'description'=>'bar'}}}
294
+ ]
295
+ } }
296
+ it { expect { subject }.to_not raise_error }
297
+ it 'should store the traits' do
298
+ subject.traits.should all( be_a Raml::Trait )
299
+ subject.traits[0].value.should eq ({'queryParameters' => {'tokenName' => {'description'=>'foo'}}})
300
+ subject.traits[1].value.should eq ({'queryParameters' => {'numPages' => {'description'=>'bar'}}})
301
+ end
302
+ end
303
+ context 'when the property is an array of mixed trait refrences, trait refrences with parameters, and trait definitions' do
304
+ let(:data) { {
305
+ 'is' => [
306
+ {'secured' => {'tokenName' => 'access_token'}},
307
+ {'queryParameters' => {'numPages' => {'description'=>'bar'}}},
308
+ 'rateLimited'
309
+ ]
310
+ } }
311
+ it { expect { subject }.to_not raise_error }
312
+ it 'should store the traits' do
313
+ subject.traits.select {|t| t.is_a? Raml::TraitReference }.map(&:name).should contain_exactly('secured', 'rateLimited')
314
+ subject.traits.select {|t| t.is_a? Raml::Trait}[0].value.should eq ({'queryParameters' => {'numPages' => {'description'=>'bar'}}})
315
+ end
316
+ end
317
+ end
318
+ context 'when the property is invalid' do
319
+ context 'when the property is not an array' do
320
+ let(:data) { { 'is' => 1 } }
321
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /is/ }
322
+ end
323
+ context 'when the property is an array with elements other than a string or map' do
324
+ let(:data) { { 'is' => [1] } }
325
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /is/ }
326
+ end
327
+ context 'when the property is an array an element that appears to be a trait name with parameters, but the params are not a map' do
328
+ let(:data) { { 'is' => [ { 'secured' => 1 } ] } }
329
+ it { expect { subject }.to raise_error Raml::InvalidProperty, /is/ }
330
+ end
331
+ end
332
+ end
333
+
334
+ context 'when the syntax tree contains optional properties' do
335
+ let(:data) {
336
+ YAML.load(%q(
337
+ description: Get a list of users
338
+ queryParameters:
339
+ page?:
340
+ description: Specify the page that you want to retrieve
341
+ type: integer
342
+ required: true
343
+ example: 1
344
+ protocols: [ HTTP, HTTPS ]
345
+ responses:
346
+ 200:
347
+ description: |
348
+ The list of popular media.
349
+ ))
350
+ }
351
+ it { expect {subject }.to raise_error Raml::InvalidProperty, /Optional properties/ }
352
+ end
353
+ end
354
+
355
+ describe '#merge' do
356
+ let(:method) { Raml::Method.new 'get' , method_data, root }
357
+ let(:trait ) { Raml::Trait.new( 'trait_name', trait_data , root).instantiate({}) }
358
+ context 'when the trait has a property set' do
359
+ context 'when the method does not have that property set' do
360
+ let(:method_data) { {} }
361
+ context 'description property' do
362
+ let(:trait_data) { {description: 'trait description'} }
363
+ it 'sets the property in the method' do
364
+ method.merge(trait).description.should eq trait.description
365
+ end
366
+ end
367
+ context 'protocols property' do
368
+ let(:trait_data) { {protocols: ['HTTPS']} }
369
+ it 'sets the property in the method' do
370
+ method.merge(trait).protocols.should eq trait.protocols
371
+ end
372
+ end
373
+ context 'headers properties' do
374
+ let(:trait_data) { {
375
+ 'headers' => {
376
+ 'header1' => {'description' => 'foo'},
377
+ 'header2' => {'description' => 'bar'}
378
+ }
379
+ } }
380
+ it 'adds the headers to the method' do
381
+ method.merge(trait).headers.keys.should contain_exactly('header1', 'header2')
382
+ end
383
+ end
384
+ context 'queryParameters properties' do
385
+ let(:trait_data) { {
386
+ 'queryParameters' => {
387
+ 'param1' => {'description' => 'foo'},
388
+ 'param2' => {'description' => 'bar'}
389
+ }
390
+ } }
391
+ it 'adds the headers to the method' do
392
+ method.merge(trait).query_parameters.keys.should contain_exactly('param1', 'param2')
393
+ end
394
+ end
395
+ context 'body property' do
396
+ let(:trait_data) { {
397
+ 'body' => {
398
+ 'text/mime1' => {'schema' => 'foo'},
399
+ 'text/mime2' => {'schema' => 'bar'}
400
+ }
401
+ } }
402
+ it 'adds the body media types to the method' do
403
+ method.merge(trait).bodies.keys.should contain_exactly('text/mime1', 'text/mime2')
404
+ end
405
+ end
406
+ context 'responses property' do
407
+ let(:trait_data) { {
408
+ 'responses' => {
409
+ 200 => {'description' => 'foo'},
410
+ 404 => {'description' => 'bar'}
411
+ }
412
+ } }
413
+ it 'adds the responses to the method' do
414
+ method.merge(trait).responses.keys.should contain_exactly(200, 404)
415
+ end
416
+ end
417
+ context 'usage property' do
418
+ let(:trait_data) { { 'usage' => 'trait usage' } }
419
+ it 'does not add the usage property to the method' do
420
+ method.merge(trait)
421
+ expect { method.usage }.to raise_error NoMethodError
422
+ end
423
+ end
424
+ end
425
+ context 'when the method has that property set' do
426
+ context 'description property' do
427
+ let(:method_data) { {description: 'method description'} }
428
+ let(:trait_data ) { {description: 'trait description' } }
429
+ it 'overwrites the method property' do
430
+ method.merge(trait).description.should eq 'trait description'
431
+ end
432
+ end
433
+ context 'protocols property' do
434
+ let(:method_data) { {protocols: ['HTTP' ]} }
435
+ let(:trait_data ) { {protocols: ['HTTPS']} }
436
+ it 'overwrites the method property' do
437
+ method.merge(trait).protocols.should eq ['HTTPS']
438
+ end
439
+ end
440
+ context 'headers properties' do
441
+ let(:method_data) { {
442
+ 'headers' => {
443
+ 'header1' => {'description' => 'foo'},
444
+ 'header2' => {'description' => 'bar'}
445
+ }
446
+ } }
447
+ context 'when the trait headers are different from the method headers' do
448
+ let(:trait_data) { {
449
+ 'headers' => {
450
+ 'header3' => {'description' => 'foo2'},
451
+ 'header4' => {'description' => 'bar2'}
452
+ }
453
+ } }
454
+ it 'adds the headers to the method' do
455
+ method.merge(trait).headers.keys.should contain_exactly('header1', 'header2', 'header3', 'header4')
456
+ end
457
+ end
458
+ context 'when the trait headers overlap the the method headers' do
459
+ let(:trait_data) { {
460
+ 'headers' => {
461
+ 'header2' => {'displayName' => 'Header 3'},
462
+ 'header3' => {'description' => 'foo2'},
463
+ 'header4' => {'description' => 'bar2'}
464
+ }
465
+ } }
466
+ it 'merges the matching headers and adds the non-matching headers to the method' do
467
+ method.merge(trait).headers.keys.should contain_exactly('header1', 'header2', 'header3', 'header4')
468
+ method.headers['header2'].display_name.should eq trait.headers['header2'].display_name
469
+ end
470
+ end
471
+ end
472
+ context 'queryParameters properties' do
473
+ let(:method_data) { {
474
+ 'queryParameters' => {
475
+ 'param1' => {'description' => 'foo'},
476
+ 'param2' => {'description' => 'bar'}
477
+ }
478
+ } }
479
+ context 'when the trait query parameters are different from the method headers' do
480
+ let(:trait_data) { {
481
+ 'queryParameters' => {
482
+ 'param3' => {'description' => 'foo2'},
483
+ 'param4' => {'description' => 'bar2'}
484
+ }
485
+ } }
486
+ it 'adds the query parameters to the method' do
487
+ method.merge(trait).query_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
488
+ end
489
+ end
490
+ context 'when the trait query parameters overlap the the method query parameters' do
491
+ let(:trait_data) { {
492
+ 'queryParameters' => {
493
+ 'param2' => {'displayName' => 'Param 3'},
494
+ 'param3' => {'description' => 'foo2'},
495
+ 'param4' => {'description' => 'bar2'}
496
+ }
497
+ } }
498
+ it 'merges the matching headers and adds the non-matching headers to the method' do
499
+ method.merge(trait).query_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
500
+ method.query_parameters['param2'].display_name.should eq trait.query_parameters['param2'].display_name
501
+ end
502
+ end
503
+ end
504
+ context 'body property' do
505
+ let(:method_data) { {
506
+ 'body' => {
507
+ 'text/mime1' => {'schema' => 'foo'},
508
+ 'text/mime2' => {'schema' => 'bar'}
509
+ }
510
+ } }
511
+ context 'when the trait query parameters are different from the method headers' do
512
+ let(:trait_data) { {
513
+ 'body' => {
514
+ 'text/mime3' => {'schema' => 'foo2'},
515
+ 'text/mime4' => {'schema' => 'bar2'}
516
+ }
517
+ } }
518
+ it 'adds the body media types to the method' do
519
+ method.merge(trait).bodies.keys.should contain_exactly('text/mime1', 'text/mime2', 'text/mime3', 'text/mime4')
520
+ end
521
+ end
522
+ context 'when the trait query parameters overlap the the method query parameters' do
523
+ let(:trait_data) { {
524
+ 'body' => {
525
+ 'text/mime2' => {'example' => 'Example 2'},
526
+ 'text/mime3' => {'schema' => 'foo2'},
527
+ 'text/mime4' => {'schema' => 'bar2'}
528
+ }
529
+ } }
530
+ it 'merges the matching media types and adds the non-matching media types to the method' do
531
+ method.merge(trait).bodies.keys.should contain_exactly('text/mime1', 'text/mime2', 'text/mime3', 'text/mime4')
532
+ method.bodies['text/mime2'].example.should eq trait.bodies['text/mime2'].example
533
+ end
534
+ end
535
+ end
536
+ context 'responses property' do
537
+ let(:method_data) { {
538
+ 'responses' => {
539
+ 200 => {'description' => 'foo', 'body' => { 'text/mime1' => {'schema' => 'schema1'} } },
540
+ 404 => {'description' => 'bar'}
541
+ }
542
+ } }
543
+ context 'when the trait response status codes are different from the method responses status codes' do
544
+ let(:trait_data) { {
545
+ 'responses' => {
546
+ 201 => {'description' => 'foo2'},
547
+ 403 => {'description' => 'bar2'}
548
+ }
549
+ } }
550
+ it 'adds the body media types to the method' do
551
+ method.merge(trait).responses.keys.should contain_exactly(200, 404, 201, 403)
552
+ end
553
+ end
554
+ context 'when the trait query parameters overlap the the method query parameters' do
555
+ let(:trait_data) { {
556
+ 'responses' => {
557
+ 200 => {'description' => 'foo', 'body' => { 'text/mime2' => {'schema' => 'schema2'} } },
558
+ 201 => {'description' => 'foo2'},
559
+ 403 => {'description' => 'bar2'}
560
+ }
561
+ } }
562
+ it 'merges the matching media types and adds the non-matching media types to the method' do
563
+ method.merge(trait).responses.keys.should contain_exactly(200, 404, 201, 403)
564
+ method.responses[200].bodies.keys.should contain_exactly('text/mime1', 'text/mime2')
565
+ end
566
+ end
567
+ end
568
+ end
569
+ end
570
+ end
571
+
572
+ describe '#apply_traits' do
573
+ let(:root_data) { {
574
+ 'title' => 'x',
575
+ 'baseUri' => 'http://foo.com',
576
+ '/users' => {
577
+ '/comments' => {
578
+ 'is' => resource_trait_data,
579
+ 'get' => method_data
580
+ }
581
+ }
582
+ } }
583
+ let(:method) { root.resources['/users'].resources['/comments'].methods['get'] }
584
+ before { method.apply_traits }
585
+ describe 'order application' do
586
+ context 'when given no resource traits' do
587
+ let(:resource_trait_data) { [] }
588
+ context 'when method has a trait' do
589
+ let(:method_data) { { 'is' => [ { 'description' => 'trait description' } ] } }
590
+ it 'applies the method trait' do
591
+ method.description.should eq 'trait description'
592
+ end
593
+ end
594
+ context 'when the method has multiple traits' do
595
+ let(:method_data) { {
596
+ 'is' => [
597
+ {
598
+ 'description' => 'trait description',
599
+ 'headers' => { 'header1' => { 'description' => 'header1' } }
600
+ },
601
+ {
602
+ 'description' => 'trait description 2',
603
+ 'headers' => { 'header2' => { 'description' => 'header2' } }
604
+ }
605
+ ]
606
+ } }
607
+ it 'applies them in order of precedence, right to left' do
608
+ method.description.should eq 'trait description 2'
609
+ method.headers.keys.should contain_exactly('header1', 'header2')
610
+ end
611
+ end
612
+ end
613
+ context 'when given resource traits' do
614
+ let(:resource_trait_data) { [ { 'description' => 'resource trait description' } ] }
615
+ context 'when the method has no traits' do
616
+ let(:method_data) { {} }
617
+ it 'applies the resource trait' do
618
+ method.description.should eq 'resource trait description'
619
+ end
620
+ end
621
+ context 'when the method has traits' do
622
+ let(:resource_trait_data) {
623
+ [
624
+ {
625
+ 'description' => 'trait4 description',
626
+ 'headers' => {
627
+ 'header3' => { 'description' => 'trait4' },
628
+ 'header4' => { 'description' => 'trait4' }
629
+ }
630
+ },
631
+ {
632
+ 'description' => 'trait3 description',
633
+ 'headers' => {
634
+ 'header2' => { 'description' => 'trait3' },
635
+ 'header3' => { 'description' => 'trait3' }
636
+ }
637
+ }
638
+ ]
639
+ }
640
+ let(:method_data) { {
641
+ 'description' => 'method description',
642
+ 'is' => [
643
+ {
644
+ 'description' => 'trait2 description',
645
+ 'headers' => {
646
+ 'header1' => { 'description' => 'trait2' },
647
+ 'header2' => { 'description' => 'trait2' }
648
+ }
649
+ },
650
+ {
651
+ 'description' => 'trait1 description',
652
+ 'headers' => {
653
+ 'header1' => { 'description' => 'trait1' }
654
+ }
655
+ }
656
+ ]
657
+ } }
658
+ it 'applies method traits first in reverse order, then resource traits in reverse order' do
659
+ method.description.should eq 'method description'
660
+ method.headers.keys.should contain_exactly('header1','header2', 'header3', 'header4')
661
+ method.headers['header1'].description.should eq 'trait1'
662
+ method.headers['header2'].description.should eq 'trait2'
663
+ method.headers['header3'].description.should eq 'trait3'
664
+ method.headers['header4'].description.should eq 'trait4'
665
+ end
666
+ end
667
+ end
668
+ end
669
+ describe 'reserved parameters' do
670
+ let(:resource_trait_data) { [] }
671
+ context 'resourcePath' do
672
+ let(:method_data) { { 'is' => [ { 'description' => 'trait <<resourcePath>>' } ] } }
673
+ it 'instances traits with the reserved parameter' do
674
+ method.description.should eq 'trait /users/comments'
675
+ end
676
+ end
677
+ context 'resourcePathName' do
678
+ let(:method_data) { { 'is' => [ { 'description' => 'trait <<resourcePathName>>' } ] } }
679
+ it 'instances traits with the reserved parameter' do
680
+ method.description.should eq 'trait comments'
681
+ end
682
+ end
683
+ context 'methodName' do
684
+ let(:method_data) { { 'is' => [ { 'description' => 'trait <<methodName>>' } ] } }
685
+ it 'instances traits with the reserved parameter' do
686
+ method.description.should eq 'trait get'
687
+ end
688
+ end
689
+ end
690
+ end
691
+
692
+ describe '#document' do
693
+ it 'returns a String' do
694
+ subject.document.should be_a String
695
+ end
696
+ it 'should render the template' do
697
+ mock(Slim::Template).new(/templates\/method.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
698
+ subject.document
699
+ end
700
+ end
701
+ end