aws-sdk-resources 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aws-sdk-resources.rb +36 -0
  3. data/lib/aws-sdk-resources/batch.rb +100 -0
  4. data/lib/aws-sdk-resources/builder.rb +86 -0
  5. data/lib/aws-sdk-resources/builder_sources.rb +136 -0
  6. data/lib/aws-sdk-resources/collection.rb +137 -0
  7. data/lib/aws-sdk-resources/definition.rb +363 -0
  8. data/lib/aws-sdk-resources/documenter.rb +18 -0
  9. data/lib/aws-sdk-resources/documenter/base_operation_documenter.rb +269 -0
  10. data/lib/aws-sdk-resources/documenter/data_operation_documenter.rb +47 -0
  11. data/lib/aws-sdk-resources/documenter/enumerate_data_operation_documenter.rb +50 -0
  12. data/lib/aws-sdk-resources/documenter/enumerate_resource_operation_documenter.rb +69 -0
  13. data/lib/aws-sdk-resources/documenter/operation_documenter.rb +43 -0
  14. data/lib/aws-sdk-resources/documenter/reference_operation_documenter.rb +102 -0
  15. data/lib/aws-sdk-resources/documenter/resource_operation_documenter.rb +65 -0
  16. data/lib/aws-sdk-resources/documenter/waiter_operation_documenter.rb +81 -0
  17. data/lib/aws-sdk-resources/errors.rb +15 -0
  18. data/lib/aws-sdk-resources/operation_methods.rb +53 -0
  19. data/lib/aws-sdk-resources/operations.rb +294 -0
  20. data/lib/aws-sdk-resources/options.rb +23 -0
  21. data/lib/aws-sdk-resources/request.rb +39 -0
  22. data/lib/aws-sdk-resources/request_params.rb +225 -0
  23. data/lib/aws-sdk-resources/resource.rb +137 -0
  24. data/lib/aws-sdk-resources/source.rb +39 -0
  25. data/lib/aws-sdk-resources/validator.rb +152 -0
  26. data/lib/aws-sdk-resources/validator/context.rb +60 -0
  27. data/lib/aws-sdk-resources/validator/identifier_validator.rb +107 -0
  28. data/lib/aws-sdk-resources/validator/operation_validator.rb +352 -0
  29. data/lib/aws-sdk-resources/validator/rule.rb +45 -0
  30. data/lib/aws-sdk-resources/validator/shape_validator.rb +47 -0
  31. metadata +87 -0
@@ -0,0 +1,137 @@
1
+ module Aws
2
+ module Resources
3
+ class Resource
4
+
5
+ extend OperationMethods
6
+
7
+ # @overload initialize(options = {})
8
+ # @overload initialize(*identifiers, options = {})
9
+ # @param options Options except `:data` and identifier options are
10
+ # used to construct a {Client} unless `:client` is given.
11
+ # @option options [Client] :client
12
+ def initialize(*args)
13
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
14
+ @identifiers = extract_identifiers(args, options)
15
+ @data = options.delete(:data)
16
+ @client = extract_client(options)
17
+ end
18
+
19
+ # Marked private to prevent double documentation
20
+ # @return [Client]
21
+ attr_reader :client
22
+
23
+ # Marked private to prevent double documentation
24
+ # @return [Hash<Symbol,String>]
25
+ attr_reader :identifiers
26
+
27
+ # @return [Struct]
28
+ def data
29
+ load unless @data
30
+ @data
31
+ end
32
+
33
+ # @return [Boolean] Returns `true` if {#data} has been loaded.
34
+ def data_loaded?
35
+ !@data.nil?
36
+ end
37
+
38
+ # Loads data for this resource.
39
+ # @note Calling this method will send a request to AWS.
40
+ # @return [self]
41
+ def load
42
+ if load_operation = self.class.load_operation
43
+ @data = load_operation.call(resource:self, client:client)
44
+ self
45
+ else
46
+ raise NotImplementedError, "#load not defined for #{self.class.name}"
47
+ end
48
+ end
49
+ alias reload load
50
+
51
+ # @api private
52
+ def inspect
53
+ identifiers = self.identifiers.map do |name, value|
54
+ "#{name}=#{value.inspect}"
55
+ end.join(', ')
56
+ "#<#{[self.class.name, identifiers].join(' ').strip}>"
57
+ end
58
+
59
+ private
60
+
61
+ def extract_client(options)
62
+ if options[:client]
63
+ options[:client]
64
+ else
65
+ self.class.client_class.new(options)
66
+ end
67
+ end
68
+
69
+ def extract_identifiers(args, options)
70
+ identifiers = {}
71
+ self.class.identifiers.each.with_index do |name, n|
72
+ if args[n]
73
+ identifiers[name] = args[n]
74
+ elsif options.key?(name)
75
+ identifiers[name] = options.delete(name)
76
+ else
77
+ raise ArgumentError, "missing required option #{name.inspect}"
78
+ end
79
+ end
80
+ identifiers
81
+ end
82
+
83
+ class << self
84
+
85
+ # @return [String, nil] The resource name.
86
+ attr_accessor :resource_name
87
+
88
+ # @return [Class<Client>, nil] When constructing
89
+ # a resource, the client will default to an instance of the
90
+ # this class.
91
+ attr_accessor :client_class
92
+
93
+ # @return [Operations::DataOperation, nil]
94
+ attr_accessor :load_operation
95
+
96
+ # @return [Array<Symbol>]
97
+ # @see add_identifier
98
+ # @see #identifiers
99
+ def identifiers
100
+ @identifiers.dup
101
+ end
102
+
103
+ # @param [Symbol] name
104
+ # @return [void]
105
+ def add_identifier(name)
106
+ name = name.to_sym
107
+ safe_define_method(name) { @identifiers[name] }
108
+ @identifiers << name
109
+ end
110
+
111
+ # Registers a data attribute. This defines a simple getter
112
+ # for the attribute which will access {#data}, loading the
113
+ # resource if necessary.
114
+ # @param [Symbol] name
115
+ # @return [void]
116
+ def add_data_attribute(name)
117
+ safe_define_method(name) { data[name] }
118
+ @data_attributes << name
119
+ end
120
+
121
+ # @return [Array<Symbol>] Returns an array of symbolized data
122
+ # attribute names.
123
+ def data_attributes
124
+ @data_attributes.dup
125
+ end
126
+
127
+ # @api private
128
+ def inherited(subclass)
129
+ subclass.send(:instance_variable_set, "@identifiers", [])
130
+ subclass.send(:instance_variable_set, "@data_attributes", [])
131
+ super
132
+ end
133
+
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,39 @@
1
+ require 'json'
2
+
3
+ module Aws
4
+ module Resources
5
+ class Source
6
+
7
+ def initialize(definition, file = nil)
8
+ @definition = definition
9
+ @file = file
10
+ end
11
+
12
+ # @return [Hash]
13
+ attr_reader :definition
14
+
15
+ # @return [String, nil]
16
+ attr_reader :file
17
+
18
+ def format
19
+ json = JSON.pretty_generate(definition, indent: ' ', space: '')
20
+ stack = [[]]
21
+ json.lines.each do |line|
22
+ if line.match(/({|\[)$/)
23
+ stack.push([])
24
+ end
25
+ stack.last.push(line)
26
+ if line.match(/(}|\]),?$/)
27
+ frame = stack.pop
28
+ if frame.size == 3 && !frame[1].match(/[{}]/)
29
+ frame = [frame[0].rstrip, '', frame[1].strip, '', frame[2].lstrip]
30
+ end
31
+ stack.last.push(frame.join)
32
+ end
33
+ end
34
+ stack.last.join
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,152 @@
1
+ require 'multi_json'
2
+ require 'json-schema'
3
+ require 'aws-sdk-resources/validator/context'
4
+ require 'aws-sdk-resources/validator/rule'
5
+ require 'aws-sdk-resources/validator/shape_validator'
6
+ require 'aws-sdk-resources/validator/identifier_validator'
7
+ require 'aws-sdk-resources/validator/operation_validator'
8
+
9
+ module Aws
10
+ module Resources
11
+ module Validator
12
+
13
+ # @api private
14
+ RULES = []
15
+
16
+ # @api private
17
+ SCHEMA_PATH = File.expand_path(File.join([
18
+ File.dirname(__FILE__), '..', '..', 'resources.schema.json'
19
+ ]))
20
+
21
+ class << self
22
+
23
+ def match(pattern, &block)
24
+ RULES << Rule.new(pattern, &block)
25
+ end
26
+
27
+ # @param [Hash] definition
28
+ # @param [Hash] api
29
+ # @return [Array<String>]
30
+ def validate(definition, api)
31
+ errors = apply_schema(definition)
32
+ errors = lint('#', definition, definition, api) if errors.empty?
33
+ errors
34
+ end
35
+
36
+ private
37
+
38
+ # Validates the resource definition document against the JSON
39
+ # schema for resources.
40
+ # @param [Hash] definition
41
+ # @return [Array<String>] Returns an array of schema validation errors.
42
+ # Returns an empty array if there are no errors.
43
+ def apply_schema(definition)
44
+ schema = MultiJson.load(File.read(SCHEMA_PATH))
45
+ JSON::Validator.fully_validate(schema, definition)
46
+ end
47
+
48
+ # Recursively lints the resource definition hash against the given
49
+ # api.
50
+ # @param [Hash] definition
51
+ # @param [Hash] api
52
+ # @return [Array<String>] Returns an array of schema validation errors.
53
+ # Returns an empty array if there are no errors.
54
+ def lint(prefix, node, definition, api, errors = [])
55
+ lint_node(prefix, node, definition, api, errors)
56
+ case node
57
+ when Hash
58
+ node.each do |key, value|
59
+ lint("#{prefix}/#{key}", value, definition, api, errors)
60
+ end
61
+ when Array
62
+ node.each.with_index do |value, index|
63
+ lint("#{prefix}/#{index}", value, definition, api, errors)
64
+ end
65
+ end
66
+ errors
67
+ end
68
+
69
+ def lint_node(path, value, definition, api, errors)
70
+ RULES.each do |rule|
71
+ if rule.applies?(path)
72
+ errors.concat(rule.validate(path, value, definition, api))
73
+ end
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ match('#/resources/(\w+)/shape') do |context|
80
+ ShapeValidator.new(context, context.value).validate
81
+ end
82
+
83
+ match('#/resources/(\w+)/identifiers') do |context|
84
+ context.value.each.with_index do |identifier, index|
85
+ IdentifierValidator.new(context, identifier, index).validate
86
+ end
87
+ end
88
+
89
+ match('#/resources/(\w+)/load') do |context|
90
+ v = OperationValidator.new(context)
91
+ v.validate_request do
92
+ v.validate_param_source_type_not_used('dataMember')
93
+ v.validate_path(origin: :response, target: :self)
94
+ end
95
+ v.validate_path_set
96
+ v.validate_resource_not_set
97
+ end
98
+
99
+ match('#/resources/(\w+)/actions/\w+') do |context|
100
+ v = OperationValidator.new(context)
101
+ v.validate_request
102
+ if v.resource_set?
103
+ v.validate_resource
104
+ v.validate_path(origin: :response, target: :resource)
105
+ else
106
+ v.validate_path(origin: :response)
107
+ end
108
+ end
109
+
110
+ match('#/resources/(\w+)/batchActions/\w+') do |context|
111
+ # TODO : much like a normal action???
112
+ end
113
+
114
+ match('#/resources/(\w+)/hasMany/\w+') do |context|
115
+ v = OperationValidator.new(context)
116
+ v.validate_request
117
+ v.validate_resource do
118
+ # at least one of the identifier sources must be plural
119
+ end
120
+ if v.path_set?
121
+ v.validate_path(origin: :response, target: :resource)
122
+ v.validate_path_is_plural
123
+ end
124
+ end
125
+
126
+ match('#/resources/(\w+)/(hasSome|hasOne)/\w+') do |context|
127
+ v = OperationValidator.new(context)
128
+ v.validate_request_not_set
129
+ v.validate_resource do
130
+ # disallow requestParameter
131
+ # disallow responsepath
132
+ end
133
+ # path must resolve FROM the resource data shape
134
+ # to the target resource data
135
+ v.validate_path(origin: :self, target: :resource)
136
+
137
+ if context.matches[2] == 'hasSome'
138
+ # at least one identifier source must be plural
139
+ # path must be plural if given
140
+ else
141
+ # identifier sources must be singular
142
+ # path must be singular if given
143
+ end
144
+ end
145
+
146
+ match('#/resources/(\w+)/subResources') do |context|
147
+ # TODO : validate subResources
148
+ end
149
+
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,60 @@
1
+ module Aws
2
+ module Resources
3
+ module Validator
4
+ class Context
5
+
6
+ # @option options [required, String] :path
7
+ # @option options [required, Object] :value
8
+ # @option options [required, Hash] :definition
9
+ # @option options [required, Hash] :api
10
+ # @option options [required, MatchData] :matches
11
+ def initialize(options = {})
12
+ [:path, :value, :definition, :api, :matches].each do |opt|
13
+ if options.key?(opt)
14
+ instance_variable_set("@#{opt}", options[opt])
15
+ else
16
+ raise ArgumentError, "missing required option :#{opt}"
17
+ end
18
+ end
19
+ @errors = []
20
+ end
21
+
22
+ # @return [String]
23
+ attr_reader :path
24
+
25
+ # @return [Object]
26
+ attr_reader :value
27
+
28
+ # @return [Hash]
29
+ attr_reader :definition
30
+
31
+ # @return [Hash]
32
+ attr_reader :api
33
+
34
+ # @return [MatchData]
35
+ attr_reader :matches
36
+
37
+ # @return [Array<String>]
38
+ attr_reader :errors
39
+
40
+ def error(msg)
41
+ @errors << msg
42
+ false
43
+ end
44
+
45
+ def resource
46
+ definition['resources'][matches[1]]
47
+ end
48
+
49
+ def resource_name
50
+ matches[1]
51
+ end
52
+
53
+ def shape(name)
54
+ api['shapes'][name]
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,107 @@
1
+ module Aws
2
+ module Resources
3
+ module Validator
4
+ class IdentifierValidator
5
+
6
+ # @param [Validator::Context] context
7
+ # @param [Hash] identifier
8
+ # @param [Integer] index
9
+ def initialize(context, identifier, index)
10
+ @context = context
11
+ @identifier = identifier
12
+ @index = index
13
+ end
14
+
15
+ attr_reader :identifier
16
+
17
+ attr_reader :index
18
+
19
+ def validate
20
+ validate_name_is_uniq
21
+ validate_name_is_not_prefixed
22
+ if member_name
23
+ validate_resource_has_shape && validate_resource_shape_contains_member
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def validate_name_is_uniq
30
+ if all_identifier_names.count(name) > 1
31
+ error("#{path("#{index}/name")} must be uniq within #{path('*/name')}.")
32
+ else
33
+ true
34
+ end
35
+ end
36
+
37
+ def validate_name_is_not_prefixed
38
+ if name.match(/^#{resource_name}/)
39
+ error("#{path("#{index}/name")} must not be prefixed with '#{resource_name}'.")
40
+ else
41
+ true
42
+ end
43
+ end
44
+
45
+ def validate_resource_has_shape
46
+ if resource_shape_name
47
+ true
48
+ else
49
+ error("#{path("#{index}/memberName")} requires '#/resources/#{resource_name}/shape' to be set.")
50
+ end
51
+ end
52
+
53
+ def validate_resource_shape_contains_member
54
+ if
55
+ resource_shape['type'] == 'structure' &&
56
+ resource_shape['members'].key?(member_name)
57
+ then
58
+ true
59
+ else
60
+ error("#{path("#{index}/memberName")} is not defined at 'api#/shapes/#{resource_shape_name}/members/#{member_name}'.")
61
+ end
62
+ end
63
+
64
+ def error(msg)
65
+ @context.error(msg)
66
+ end
67
+
68
+ def path(suffix = nil)
69
+ if suffix
70
+ "'#{@context.path}/#{suffix}'"
71
+ else
72
+ "'#{@context.path}'"
73
+ end
74
+ end
75
+
76
+ def name
77
+ @identifier['name']
78
+ end
79
+
80
+ def member_name
81
+ @identifier['memberName']
82
+ end
83
+
84
+ def resource_name
85
+ @context.matches[1]
86
+ end
87
+
88
+ def all_identifier_names
89
+ @context.value.map { |i| i['name'] }
90
+ end
91
+
92
+ def resource
93
+ @context.definition['resources'][resource_name]
94
+ end
95
+
96
+ def resource_shape_name
97
+ resource['shape']
98
+ end
99
+
100
+ def resource_shape
101
+ @context.api['shapes'][resource_shape_name]
102
+ end
103
+
104
+ end
105
+ end
106
+ end
107
+ end