raml_ruby 0.1.1

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.
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,49 @@
1
+ module Raml
2
+ # @private
3
+ module Validation
4
+ def validate_property(name, value, classes)
5
+ classes = [ classes ] unless classes.is_a? Array
6
+ raise InvalidProperty, "#{camel_case name} property must be an #{classes_to_s classes}" unless classes.include? value.class
7
+ end
8
+
9
+ def validate_string(name, string)
10
+ validate_property name, string, String
11
+ raise InvalidProperty, "#{camel_case name} property must be a non-empty string." if string.empty?
12
+ end
13
+
14
+ def validate_array(name, array, element_classes=nil)
15
+ raise InvalidProperty, "#{camel_case name} property must be an array" unless
16
+ array.is_a? Array
17
+
18
+ if element_classes
19
+ element_classes = [ element_classes ] unless element_classes.is_a? Array
20
+ raise InvalidProperty, "#{camel_case name} property must be an array of #{classes_to_s element_classes}" unless
21
+ array.all? { |element| element_classes.include? element.class }
22
+ end
23
+ end
24
+
25
+ def validate_hash(name, hash, key_class=nil, value_class=nil)
26
+ raise InvalidProperty, "#{camel_case name} property must be a map" unless
27
+ hash.is_a? Hash
28
+
29
+ if key_class
30
+ if key_class.is_a? Array
31
+ raise InvalidProperty, "#{camel_case name} property must be a map with #{key_class} keys" unless
32
+ hash.keys.all? {|key| key_class.any? { |kc| key.is_a? kc } }
33
+ else
34
+ raise InvalidProperty, "#{camel_case name} property must be a map with #{key_class} keys" unless
35
+ hash.keys.all? {|key| key.is_a? key_class }
36
+ end
37
+ end
38
+
39
+ if value_class
40
+ raise InvalidProperty, "#{camel_case name} property must be a map with map values: #{hash}" unless
41
+ hash.values.all? {|value| value.is_a? Hash }
42
+ end
43
+ end
44
+
45
+ def classes_to_s(classes)
46
+ classes.join(', ').gsub(/, (\w)\z/, ' or \1')
47
+ end
48
+ end
49
+ end
data/lib/raml/node.rb ADDED
@@ -0,0 +1,219 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'rouge'
4
+ require 'slim'
5
+
6
+ module Raml
7
+ class Node
8
+ class_attribute :doc_template, :doc_template_compiled
9
+
10
+ class << self
11
+ # @private
12
+ def relative_path(file)
13
+ File.join(
14
+ *File.dirname(__FILE__).
15
+ split(File::SEPARATOR).
16
+ reverse.
17
+ drop_while { |p| p != 'lib' }.
18
+ drop(1).
19
+ reverse,
20
+ 'templates',
21
+ file
22
+ )
23
+ end
24
+ end
25
+
26
+ # @!attribute [r] name
27
+ # @return [String,Integer] the node name (e.g. resource path, header name, etc). Usually a
28
+ # String. Can be an Integer for methods.
29
+ attr_reader :name
30
+ # @!attribute [rw] parent
31
+ # @return [Raml::Node] the node's parent.
32
+ attr_accessor :parent
33
+
34
+ def initialize(name, parent)
35
+ @name = name
36
+ @parent = parent
37
+ end
38
+
39
+ # Returns HTML documenting the node and child nodes.
40
+ # @return [String] HTML documentation.
41
+ def document
42
+ if doc_template
43
+ self.doc_template_compiled ||= Slim::Template.new(doc_template, format: :html5, pretty: true)
44
+ doc_template_compiled.render self
45
+ else
46
+ nil
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def underscore(camel_cased_word)
53
+ camel_cased_word.to_s.gsub(/::/, '/').
54
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
55
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
56
+ tr("-", "_").
57
+ downcase
58
+ end
59
+
60
+ def camel_case(underscored_word)
61
+ w = underscored_word.to_s.split('_')
62
+ (w[0...1] + w[1..-1].map(&:capitalize)).join
63
+ end
64
+
65
+ def collapse(level, title, display_name=nil, &block)
66
+ @@cid ||= 0
67
+ @@cid += 1
68
+
69
+ @@context_class ||= Struct.new(:cid, :title, :level, :display_name, :content) do
70
+ def highlight_url_params(url)
71
+ url.gsub(/({[^}]+})/, '<span class="url_param">\1</span>')
72
+ end
73
+ end
74
+
75
+ context = @@context_class.new @@cid, title, level, display_name, yield
76
+
77
+ @@collapse ||= Slim::Template.new(self.class.relative_path('collapse.slim'), format: :html5, pretty: true)
78
+ @@collapse.render context
79
+ end
80
+
81
+ def highlight_url_params(url)
82
+ url.gsub(/({[^}]+})/, '<span class="url_param">\1</span>')
83
+ end
84
+
85
+ def highlight(source, mimetype=nil)
86
+ opts = { source: source }
87
+ opts[:mimetype] = mimetype if mimetype
88
+
89
+ formatter = Rouge::Formatters::HTML.new css_class: 'highlight'
90
+ lexer = Rouge::Lexer.guess(opts).new
91
+ formatter.format lexer.lex source
92
+ end
93
+ end
94
+
95
+ class ValueNode < Node
96
+ attr_accessor :value
97
+
98
+ def initialize(name, value, parent)
99
+ super name, parent
100
+ @value = value
101
+
102
+ validate_value if respond_to? :validate_value, true
103
+ end
104
+ end
105
+
106
+ class PropertiesNode < Node
107
+ class_attribute :scalar_properties, :non_scalar_properties, :_regexp_property
108
+ self.scalar_properties = []
109
+ self.non_scalar_properties = []
110
+ self._regexp_property = nil
111
+
112
+ class << self
113
+ private
114
+ def inherit_class_attributes
115
+ self.scalar_properties = self.scalar_properties.dup
116
+ self.non_scalar_properties = self.non_scalar_properties.dup
117
+ end
118
+
119
+ def scalar_property(*properties)
120
+ attr_accessor(*properties.map(&:to_sym))
121
+ _property(scalar_properties, *properties)
122
+ end
123
+
124
+ def non_scalar_property(*properties)
125
+ _property(non_scalar_properties, *properties)
126
+ end
127
+
128
+ def _property(type, *properties)
129
+ properties.map(&:to_s).each { |prop| type << prop unless type.include? prop }
130
+ end
131
+
132
+ def regexp_property(regexp, parse)
133
+ self._regexp_property = [ regexp, parse ]
134
+ end
135
+ end
136
+
137
+ # @private
138
+ def scalar_properties ; self.class.scalar_properties ; end
139
+ # @private
140
+ def non_scalar_properties; self.class.non_scalar_properties; end
141
+ # @private
142
+ def _regexp_property ; self.class._regexp_property ; end
143
+
144
+ # @!attribute [rw] optional
145
+ # @return [Boolean] whether the property is optional. Only valid
146
+ # for decendant nodes a {Trait::Instance} or {ResourceType::Instance}.
147
+ # Indicated by a trailing "?" on the property name in the RAML source.
148
+ attr_accessor :optional
149
+
150
+ def initialize(name, properties, parent)
151
+ if name.is_a? String and name.end_with? '?'
152
+ allow_optional? parent
153
+ name = name.dup.chomp! '?'
154
+ @optional = true
155
+ else
156
+ @optional = false
157
+ end
158
+
159
+ super name, parent
160
+
161
+ @children ||= []
162
+ parse_and_validate_props properties
163
+ end
164
+
165
+ private
166
+
167
+ def allow_optional?(parent)
168
+ until parent == parent.parent or parent.is_a? Root
169
+ return if parent.is_a? Trait::Instance or parent.is_a? ResourceType::Instance
170
+ parent = parent.parent
171
+ end
172
+ raise InvalidProperty, 'Optional properties are only allowed within a trait or resource type specification.'
173
+ end
174
+
175
+ def parse_and_validate_props(properties)
176
+ maybe_exec :validate_name
177
+ maybe_exec :validate_parent
178
+
179
+ properties.each do |prop_name, prop_value|
180
+ prop_name = prop_name.to_s
181
+ under_prop_name = underscore prop_name
182
+
183
+ if scalar_properties.include? under_prop_name
184
+ send "#{under_prop_name}=", prop_value
185
+ maybe_exec "validate_#{under_prop_name}"
186
+
187
+ elsif non_scalar_properties.include? under_prop_name
188
+ parsed = send "parse_#{under_prop_name}", prop_value
189
+ parsed = [ parsed ] unless parsed.is_a? Array
190
+ @children += parsed
191
+
192
+ elsif _regexp_property and _regexp_property[0].match prop_name
193
+ parsed = self.instance_exec(prop_name, prop_value, &_regexp_property[1])
194
+ parsed = [ parsed ] unless parsed.is_a? Array
195
+ @children += parsed
196
+
197
+ else
198
+ raise UnknownProperty, "#{prop_name} is an unknown property with value of #{prop_value}."
199
+ end
200
+ end
201
+
202
+ validate if respond_to? :validate, true
203
+ end
204
+
205
+ def maybe_exec(method, *args)
206
+ send(method,*args) if respond_to? method, true
207
+ end
208
+
209
+ def initialize_clone(other)
210
+ super
211
+ @children = @children.clone
212
+ @children.map! do |child|
213
+ child = child.clone
214
+ child.parent = self
215
+ child
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,61 @@
1
+ module Raml
2
+ class AbstractMethod < PropertiesNode
3
+ inherit_class_attributes
4
+
5
+ include Documentable
6
+ include Global
7
+ include Merge
8
+ include Parent
9
+ include Validation
10
+ include Bodies
11
+ include Headers
12
+
13
+ # @!attribute [rw] protocols
14
+ # @return [Array<String>, nil] the supported protocols. Nil or an array of up to two string
15
+ # elements from the set "HTTP" and "HTTPS".
16
+
17
+ # @!attribute [r] query_parameters
18
+ # @return [Hash<String, Raml::Parameter::QueryParameter>] the method query parameters, keyed
19
+ # by the parameter name.
20
+
21
+ # @!attribute [r] responses
22
+ # @return [Hash<Integer, Raml::Response>] the method responses, keyed by the HTTP status code.
23
+
24
+ scalar_property :protocols
25
+ non_scalar_property :query_parameters, :responses, :secured_by
26
+
27
+ attr_reader_default :protocols, []
28
+
29
+ children_by :query_parameters , :name , Parameter::QueryParameter
30
+ children_by :responses , :name , Response
31
+
32
+ private
33
+
34
+ def validate_protocols
35
+ if @protocols
36
+ validate_array :protocols, @protocols, String
37
+
38
+ @protocols.map!(&:upcase)
39
+
40
+ raise InvalidProperty, 'protocols property elements must be HTTP or HTTPS' unless
41
+ @protocols.all? { |p| [ 'HTTP', 'HTTPS'].include? p }
42
+ end
43
+ end
44
+
45
+ def parse_query_parameters(value)
46
+ validate_hash 'queryParameters', value, String, Hash
47
+ value.map { |p_name, p_data| Parameter::QueryParameter.new p_name, p_data, self }
48
+ end
49
+
50
+ def parse_responses(value)
51
+ validate_hash 'responses', value, [Integer, String], Hash
52
+ value.map { |r_name, r_data| Response.new r_name, r_data, self }
53
+ end
54
+
55
+ def parse_secured_by(data)
56
+ # XXX ignored for now
57
+ []
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,165 @@
1
+ module Raml
2
+ class AbstractResource < PropertiesNode
3
+ inherit_class_attributes
4
+
5
+ include Documentable
6
+ include Global
7
+ include Merge
8
+ include Parent
9
+ include Validation
10
+
11
+ # @!attribute [r] base_uri_parameters
12
+ # @return [Hash<String, Raml::Parameter::BaseUriParameter>] the base URI parameters, keyed
13
+ # by the parameter name.
14
+
15
+ # @!attribute [r] uri_parameters
16
+ # @return [Hash<String, Raml::Parameter::UriParameter>] the URI parameters, keyed
17
+ # by the parameter name.
18
+
19
+ # @!attribute [r] methods
20
+ # @return [Hash<String, Raml::Method>] the methods, keyed by the method name.
21
+
22
+ # @!attribute [r] traits
23
+ # @return [Array<Raml::Trait, Raml::TraitReference>] the traits and trait references.
24
+
25
+ non_scalar_property :uri_parameters, :base_uri_parameters, :is, :type, :secured_by,
26
+ *Raml::Method::NAMES, *Raml::Method::NAMES.map { |m| "#{m}?" }
27
+
28
+ children_by :methods , :name, Raml::Method
29
+ children_by :base_uri_parameters, :name, Parameter::BaseUriParameter, true
30
+ children_by :uri_parameters , :name, Parameter::UriParameter , true
31
+
32
+ children_of :traits, [ Raml::Trait, Raml::TraitReference ]
33
+
34
+ # @private
35
+ def apply_resource_type
36
+ if type
37
+ # We clone the resource as it currently is; apply the resource type to the
38
+ # resource, so that optional properties are correctly evaluated; then we
39
+ # apply the cloned resource with the initial state, so that scalar properties
40
+ # in the resource override the ones in the resource type.
41
+ cloned_self = self.clone
42
+ merge instantiate_resource_type
43
+ merge cloned_self
44
+ end
45
+ end
46
+
47
+ # @private
48
+ def merge(other)
49
+ raise MergeError, "Trying to merge #{other.class} into Resource." unless other.is_a? ResourceType::Instance or other.is_a? Resource
50
+
51
+ super
52
+
53
+ merge_properties other, :methods
54
+ merge_properties other, :base_uri_parameters
55
+ merge_properties other, :uri_parameters
56
+
57
+ # merge traits. insert the non-matching ones in the front, so they have the least priority.
58
+ match, no_match = other.traits.partition do |other_trait|
59
+ if other_trait.is_a? Trait
60
+ false
61
+ else # TraitReference
62
+ self.traits.any? do |self_trait|
63
+ self_trait.is_a?(TraitReference) &&
64
+ self_trait.name == other_trait.name &&
65
+ self_trait.parameters == other_trait.parameters
66
+ end
67
+ end
68
+ end
69
+ @children.unshift(*no_match)
70
+
71
+ self
72
+ end
73
+
74
+ # Returns the resource's full path.
75
+ # @return [String] the resource's full path.
76
+ def resource_path
77
+ @parent.resource_path + self.name
78
+ end
79
+
80
+ private
81
+
82
+ def validate_parent
83
+ raise InvalidParent, "Parent of resource cannot be nil." if @parent.nil?
84
+ end
85
+
86
+ def parse_uri_parameters(value)
87
+ validate_hash :uri_parameters, value, String, Hash
88
+ value.map { |uname, udata| Parameter::UriParameter.new uname, udata, self }
89
+ end
90
+
91
+ def parse_base_uri_parameters(value)
92
+ validate_hash :base_uri_parameters, value, String, Hash
93
+
94
+ raise InvalidProperty, 'baseUriParameters property can\'t contain reserved "version" parameter' if
95
+ value.include? 'version'
96
+
97
+ value.map { |bname, bdata| Parameter::BaseUriParameter.new bname, bdata, self }
98
+ end
99
+
100
+ def parse_is(value)
101
+ validate_array :is, value, [String, Hash]
102
+
103
+ value.map do |trait|
104
+ if trait.is_a? Hash
105
+ if trait.keys.size == 1 and trait_declarations.include? trait.keys.first
106
+ raise InvalidProperty, 'is property with map of trait name but params are not a map' unless
107
+ trait.values[0].is_a? Hash
108
+ TraitReference.new( *trait.first, self )
109
+ else
110
+ Trait.new '_', trait, self
111
+ end
112
+ else
113
+ raise UnknownTraitReference, "#{trait} referenced in resource but not found in traits declaration." unless
114
+ trait_declarations.include? trait
115
+ TraitReference.new trait, self
116
+ end
117
+ end
118
+ end
119
+
120
+ Raml::Method::NAMES.each do |method|
121
+ define_method("parse_#{method}") do |value|
122
+ Method.new method, value, self
123
+ end
124
+
125
+ define_method("parse_#{method}?") do |value|
126
+ Method.new "#{method}?", value, self
127
+ end
128
+ end
129
+
130
+ def parse_type(value)
131
+ validate_property :type, value, [ Hash, String ]
132
+
133
+ if value.is_a? Hash
134
+ if value.keys.size == 1 and resource_type_declarations.include? value.keys.first
135
+ raise InvalidProperty, 'type property with map of resource type name but params are not a map' unless
136
+ value.values[0].is_a? Hash
137
+ ResourceTypeReference.new( *value.first, self )
138
+ else
139
+ ResourceType.new '_', value, self
140
+ end
141
+ else
142
+ raise UnknownResourceTypeReference, "#{value} referenced in resource but not found in resource types declaration." unless
143
+ resource_type_declarations.include? value
144
+ ResourceTypeReference.new value, self
145
+ end
146
+ end
147
+
148
+ def parse_secured_by(data)
149
+ # XXX ignored for now
150
+ []
151
+ end
152
+
153
+ def instantiate_resource_type
154
+ reserved_params = {
155
+ 'resourcePath' => resource_path,
156
+ 'resourcePathName' => resource_path.split('/')[-1]
157
+ }
158
+ if ResourceTypeReference === type
159
+ resource_type_declarations[type.name].instantiate type.parameters.merge reserved_params
160
+ else
161
+ type.instantiate reserved_params
162
+ end
163
+ end
164
+ end
165
+ end