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,5 @@
1
+ class Raml::AbstractResource < Raml::PropertiesNode
2
+ # @!attribute [r] type
3
+ # @return [Raml::ResourceType, Raml::ResourceTypeReference>] the resource type or resource type references, if any.
4
+ child_of :type, [ Raml::ResourceType, Raml::ResourceTypeReference ]
5
+ end
@@ -0,0 +1,94 @@
1
+ module Raml
2
+ class Body < PropertiesNode
3
+ inherit_class_attributes
4
+
5
+ include Global
6
+ include Merge
7
+ include Parent
8
+ include Validation
9
+
10
+ # @private
11
+ MEDIA_TYPE_RE = %r{[a-z\d][-\w.+!#$&^]{0,63}/[a-z\d][-\w.+!#$&^]{0,63}(;.*)?}oi
12
+
13
+ # @!attribute [rw] example
14
+ # @return [String, nil] an example of a valid body.
15
+
16
+ # @!attribute [r] form_parameters
17
+ # @return [Hash<String, Raml::Parameter::FormParameter>] the form parameters, keyed
18
+ # by the parameter name. Only valid for "application/x-www-form-urlencoded" and
19
+ # "multipart/form-data" media types.
20
+
21
+ # @!attribute [r] schema
22
+ # @return [Raml::Schema, nil] the body's schema. Only valid if the media type
23
+ # is not one of "application/x-www-form-urlencoded" or "multipart/form-data".
24
+
25
+ # @!attribute [r] media\_type
26
+ # @return [String] media type of the of body. An alias for #name.
27
+
28
+ scalar_property :example
29
+ non_scalar_property :form_parameters, :schema
30
+
31
+ alias_method :media_type, :name
32
+
33
+ self.doc_template = relative_path 'body.slim'
34
+
35
+ children_by :form_parameters, :name, Parameter::FormParameter
36
+
37
+ child_of :schema, [ Schema, SchemaReference ]
38
+
39
+ # Returns whether the body is a web form. Returns true for "application/x-www-form-urlencoded" and
40
+ # "multipart/form-data" media types.
41
+ # @return [Boolean] true if the body is a web form, false otherwise.
42
+ def web_form?
43
+ [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include? media_type
44
+ end
45
+
46
+ # @private
47
+ def merge(other)
48
+ raise MergeError, "Media types don't match." if media_type != other.media_type
49
+
50
+ super
51
+
52
+ merge_properties other, :form_parameters
53
+
54
+ if other.schema
55
+ @children.delete_if { |c| [ Schema, SchemaReference ].include? c.class } if schema
56
+ @children << other.schema
57
+ end
58
+
59
+ self
60
+ end
61
+
62
+ private
63
+
64
+ def validate_name
65
+ raise InvalidMediaType, 'body media type is invalid' unless media_type =~ Body::MEDIA_TYPE_RE
66
+ end
67
+
68
+ def parse_form_parameters(value)
69
+ validate_hash 'formParameters', value, String, Hash
70
+
71
+ value.map do |name, form_parameter_data|
72
+ Parameter::FormParameter.new name, form_parameter_data, self
73
+ end
74
+ end
75
+
76
+ def parse_schema(value)
77
+ validate_string :schema, value
78
+
79
+ if schema_declarations.include? value
80
+ SchemaReference.new value, self
81
+ else
82
+ Schema.new '_', value, self
83
+ end
84
+ end
85
+
86
+ def validate
87
+ if web_form?
88
+ raise InvalidProperty, 'schema property can\'t be defined for web forms.' if schema
89
+ raise RequiredPropertyMissing, 'formParameters property must be specified for web forms.' if
90
+ form_parameters.empty?
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,28 @@
1
+ require 'kramdown'
2
+
3
+ module Raml
4
+ class Documentation < PropertiesNode
5
+
6
+ # @!attribute [rw] content
7
+ # @return [String] the documentation content.
8
+
9
+ # @!attribute [rw] title
10
+ # @return [String] the documentation title. An alias for #name.
11
+
12
+ scalar_property :content
13
+ alias_method :title, :name
14
+
15
+ self.doc_template = relative_path 'documentation.slim'
16
+
17
+ private
18
+
19
+ def validate
20
+ raise InvalidProperty, 'document title cannot be empty.' if title.nil? or title.empty?
21
+ raise InvalidProperty, 'document content cannot be empty.' if content.nil? or content.empty?
22
+ end
23
+
24
+ def html_content
25
+ Kramdown::Document.new(content, input: :GFM).to_html
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ module Raml
2
+ class Header < Parameter::AbstractParameter
3
+ end
4
+ end
@@ -0,0 +1,106 @@
1
+ module Raml
2
+ class Method < AbstractMethod
3
+ inherit_class_attributes
4
+
5
+ NAMES = %w(options get head post put delete trace connect patch)
6
+
7
+ # @!attribute [r] traits
8
+ # @return [Array<Raml::Trait, Raml::TraitReference>] the traits and trait references.
9
+
10
+ non_scalar_property :is
11
+
12
+ children_of :traits, [ Trait, TraitReference ]
13
+
14
+ self.doc_template = relative_path 'method.slim'
15
+
16
+ # @private
17
+ def apply_traits
18
+ # We apply resource traits before method traits, and apply traits at each level in
19
+ # the other they are listed (first to last, left to righ). Later traits scalar
20
+ # properties overwrite earlier ones. We end by merging a copy of the method, so
21
+ # that scalar properties in the method hierarchy overwrite those in the traits.
22
+ # We must apply the traits against the method first, as they may contain optional
23
+ # properties that depend on the method hiearchy.
24
+ cloned_self = self.clone
25
+
26
+ (@parent.traits + traits).
27
+ map { |trait| instantiate_trait trait }.
28
+ each { |trait| merge trait }
29
+
30
+ merge cloned_self
31
+ end
32
+
33
+ # @private
34
+ def merge(other)
35
+ super
36
+
37
+ merge_properties other, :headers
38
+ merge_properties other, :query_parameters
39
+ merge_properties other, :bodies
40
+ merge_properties other, :responses
41
+
42
+ # We may be applying a resource type, which will result in the merging of a method that may have
43
+ # traits, instead of a trait that can't have no traits.
44
+ if other.is_a? Method
45
+ # merge traits. insert the non-matching ones in the front, so they have the least priority.
46
+ match, no_match = other.traits.partition do |other_trait|
47
+ if other_trait.is_a? Trait
48
+ false
49
+ else # TraitReference
50
+ self.traits.any? do |self_trait|
51
+ self_trait.is_a?(TraitReference) &&
52
+ self_trait.name == other_trait.name &&
53
+ self_trait.parameters == other_trait.parameters
54
+ end
55
+ end
56
+ end
57
+ @children.unshift(*no_match)
58
+ end
59
+
60
+ self
61
+ end
62
+
63
+ private
64
+
65
+ def validate_name
66
+ raise InvalidMethod, "#{@name} is an unsupported HTTP method" unless NAMES.include? @name
67
+ end
68
+
69
+ def validate_parent
70
+ raise InvalidParent, "Parent of method cannot be nil." if @parent.nil?
71
+ end
72
+
73
+ def parse_is(value)
74
+ validate_array :is, value, [String, Hash]
75
+
76
+ value.map do |trait|
77
+ if trait.is_a? Hash
78
+ if trait.keys.size == 1 and trait_declarations.include? trait.keys.first
79
+ raise InvalidProperty, 'is property with map of trait name but params are not a map' unless
80
+ trait.values[0].is_a? Hash
81
+ TraitReference.new( *trait.first, self )
82
+ else
83
+ Trait.new '_', trait, self
84
+ end
85
+ else
86
+ raise UnknownTraitReference, "#{trait} referenced in method but not found in traits declaration." unless
87
+ trait_declarations.include? trait
88
+ TraitReference.new trait, self
89
+ end
90
+ end
91
+ end
92
+
93
+ def instantiate_trait(trait)
94
+ reserved_params = {
95
+ 'resourcePath' => @parent.resource_path,
96
+ 'resourcePathName' => @parent.resource_path.split('/')[-1],
97
+ 'methodName' => self.name
98
+ }
99
+ if TraitReference === trait
100
+ trait_declarations[trait.name].instantiate trait.parameters.merge reserved_params
101
+ else
102
+ trait.instantiate reserved_params
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,251 @@
1
+ module Raml
2
+ module Parameter
3
+ class AbstractParameter < PropertiesNode
4
+ inherit_class_attributes
5
+
6
+ include Documentable
7
+ include Merge
8
+ include Parent
9
+
10
+ VALID_TYPES = %w(string number integer date boolean file)
11
+
12
+ # @!attribute [rw] type
13
+ # @return [String] the value type. One of: "string", "number", "integer", "date",
14
+ # "boolean", or "file".
15
+
16
+ # @!attribute [rw] enum
17
+ # @return [Array<String>,nil] the possible values. Only valid for parameters of type "string".
18
+
19
+ # @!attribute [rw] pattern
20
+ # @return [Regexp,nil] a regular expression the value must match. Only valid for
21
+ # parameters of type "string".
22
+
23
+ # @!attribute [rw] min_length
24
+ # @return [Integer,nil] the minimum value length. Only valid for parameters of type "string".
25
+
26
+ # @!attribute [rw] max_length
27
+ # @return [Integer,nil] the maximum value length. Only valid for parameters of type "string".
28
+
29
+ # @!attribute [rw] minimum
30
+ # @return [Numeric,nil] the minimum value length. Only valid for parameters of type "number" or "integer".
31
+
32
+ # @!attribute [rw] maximum
33
+ # @return [Numeric,nil] the maximum value length. Only valid for parameters of type "number" or "integer".
34
+
35
+ # @!attribute [rw] example
36
+ # @return [String,Numeric,Boolean,nil] an example of the value.
37
+
38
+ # @!attribute [rw] default
39
+ # @return [String,Numeric,Boolean,nil] the default value.
40
+
41
+ # @!attribute [rw] required
42
+ # @return [Boolean] whether the parameter is required.
43
+
44
+ # @!attribute [rw] repeat
45
+ # @return [Boolean] whether the parameter can be repeated.
46
+
47
+ # @!attribute [r] types
48
+ # @return [Hash<String, Raml::Parameter::AbstractParameter>] if the parameter supports multiple types,
49
+ # the type alternatives, keyed by the type.
50
+
51
+ scalar_property :type , :enum , :pattern , :min_length ,
52
+ :max_length , :minimum , :maximum , :example ,
53
+ :repeat , :required , :default
54
+
55
+ attr_reader_default :type , 'string'
56
+ attr_reader_default :repeat , false
57
+ attr_reader_default :required, false
58
+
59
+ children_by :types, :type, AbstractParameter
60
+
61
+ self.doc_template = relative_path 'abstract_parameter.slim'
62
+
63
+ # @param name [String] the parameter name.
64
+ # @param parameter_data [Hash, Array<Hash>] the parameter data. If the parameter supports multiple types,
65
+ # it should be an array of hashes, one hash each for each type.
66
+ # @param parent [Raml::Node] the parameter's parent node.
67
+ def initialize(name, parameter_data, parent)
68
+ if parameter_data.is_a? Array
69
+ @name = name
70
+ @children ||= []
71
+ parameter_data.each do |parameter|
72
+ @children << self.class.new(name, parameter, self)
73
+ end
74
+ elsif parameter_data.is_a? Hash
75
+ super
76
+ end
77
+ end
78
+
79
+ # @return [Boolean] true if the parameter supports multiple type alternatives, false otherwise.
80
+ def has_multiple_types?
81
+ not children.empty?
82
+ end
83
+
84
+ # @private
85
+ def merge(other)
86
+ raise MergeError, "#{self.class} names don't match." if name != other.name
87
+
88
+ case [ has_multiple_types?, other.has_multiple_types? ]
89
+ when [ true , true ]
90
+ match, no_match = other.types.values.partition { |param| types.include? param.type }
91
+
92
+ # Merge parameters with the same type.
93
+ match = Hash[ match.map { |param| [ param.type, param ] } ]
94
+ types.each { |type, param| param.merge match[type] if match[type] }
95
+
96
+ # Add parameters with no matching type.
97
+ @children.concat no_match
98
+
99
+ when [ true , false ]
100
+ if types[other.type]
101
+ types[other.type].merge other
102
+ else
103
+ @children << other
104
+ end
105
+
106
+ when [ false, true ]
107
+ if other.types[self.type]
108
+ self.merge other.types[self.type]
109
+ @children << self.clone
110
+ @children.concat other.types.values.reject { |type| self.type == type.type }
111
+ reset
112
+
113
+ else
114
+ @children << self.clone
115
+ @children.concat other.types.values
116
+ reset
117
+ end
118
+
119
+ when [ false, false ]
120
+ super
121
+ end
122
+
123
+ self
124
+ end
125
+
126
+ private
127
+
128
+ def validate_type
129
+ raise InvalidParameterType unless VALID_TYPES.include? type
130
+ end
131
+
132
+ def validate_enum
133
+ if enum
134
+ if type == 'string'
135
+ raise InvalidParameterAttribute, "enum attribute must be an array of strings: #{enum} (#{enum.class})" unless
136
+ enum.is_a?(Array) && enum.all? { |val| val.is_a? String }
137
+ else
138
+ raise InapplicableParameterAttribute, 'enum attribute is only applicable to string parameters.'
139
+ end
140
+ end
141
+ end
142
+
143
+ def validate_pattern
144
+ if pattern
145
+ if type == 'string'
146
+ raise InvalidParameterAttribute, 'pattern attribute must be a string' unless pattern.is_a? String
147
+ pattern.gsub!(/\\*\^/u) { |m| m.size.odd? ? "#{m.chop}\\A" : m }
148
+ pattern.gsub!(/\\*\$/u) { |m| m.size.odd? ? "#{m.chop}\\z" : m }
149
+ begin
150
+ @pattern = Regexp.new pattern
151
+ rescue RegexpError
152
+ raise InvalidParameterAttribute, 'pattern attribute must be a valid regexp'
153
+ end
154
+ else
155
+ raise InapplicableParameterAttribute, 'pattern attribute is only applicable to string parameters.'
156
+ end
157
+ end
158
+ end
159
+
160
+ def validate_min_length
161
+ if min_length
162
+ if type != 'string'
163
+ raise InapplicableParameterAttribute, 'minLength attributes are applicable only to string parameters.'
164
+ else
165
+ raise InvalidParameterAttribute, 'minLength attributes must be an integer' unless min_length.is_a? Integer
166
+ end
167
+ end
168
+ end
169
+
170
+ def validate_max_length
171
+ if max_length
172
+ if type != 'string'
173
+ raise InapplicableParameterAttribute, 'maxLength attributes are applicable only to string parameters.'
174
+ else
175
+ raise InvalidParameterAttribute, 'maxLength attributes must be an integer' unless max_length.is_a? Integer
176
+ end
177
+ end
178
+ end
179
+
180
+ def validate_minimum
181
+ if minimum
182
+ if %w(integer number).include? type
183
+ raise InvalidParameterAttribute, 'minimum attribute must be numeric' unless minimum.is_a? Numeric
184
+ else
185
+ raise InapplicableParameterAttribute,
186
+ 'minimum attribute applicable only to number or integer parameters.'
187
+ end
188
+ end
189
+ end
190
+
191
+ def validate_maximum
192
+ if maximum
193
+ if %w(integer number).include? type
194
+ raise InvalidParameterAttribute, 'maximum attribute must be numeric' unless maximum.is_a? Numeric
195
+ else
196
+ raise InapplicableParameterAttribute,
197
+ 'maximum attribute applicable only to number or integer parameters.'
198
+ end
199
+ end
200
+ end
201
+
202
+ def validate_example
203
+ validate_value :example
204
+ end
205
+
206
+ def validate_repeat
207
+ unless [true, false].include?(repeat)
208
+ raise InvalidParameterAttribute, 'repeat attribute must be true or false.'
209
+ end
210
+ end
211
+
212
+ def validate_required
213
+ unless [true, false].include?(required)
214
+ raise InvalidParameterAttribute, "required attribute must be true or false: #{required} (#{required.class})"
215
+ end
216
+ end
217
+
218
+ def validate_default
219
+ validate_value :default
220
+ end
221
+
222
+ def validate_value(which)
223
+ val = send which
224
+ if val
225
+ err_msg = "#{which} attribute for a %s parameter must be a %s: #{val} (#{val.class})"
226
+ case type
227
+ when 'string'
228
+ raise InvalidParameterAttribute,
229
+ ( err_msg % [ 'string' , 'string' ] ) unless val.is_a? String
230
+ when 'number'
231
+ raise InvalidParameterAttribute,
232
+ ( err_msg % [ 'number' , 'number' ] ) unless val.is_a? Numeric
233
+ when 'integer'
234
+ raise InvalidParameterAttribute,
235
+ ( err_msg % [ 'integer', 'integer' ] ) unless val.is_a? Integer
236
+ when 'date'
237
+ raise InvalidParameterAttribute,
238
+ ( err_msg % [ 'date' , 'string' ] ) unless val.is_a? String
239
+ when 'boolean'
240
+ raise InvalidParameterAttribute,
241
+ ( err_msg % [ 'boolean', 'boolean' ] ) unless [TrueClass, FalseClass].include? val.class
242
+ end
243
+ end
244
+ end
245
+
246
+ def reset
247
+ scalar_properties.each { |prop| instance_variable_set "@#{prop}", nil }
248
+ end
249
+ end
250
+ end
251
+ end