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,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