lib/aws-sdk-resources/validator.rb in aws-sdk-resources-2.0.10.pre vs lib/aws-sdk-resources/validator.rb in aws-sdk-resources-2.0.11.pre

- old
+ new

@@ -1,98 +1,189 @@ -require 'multi_json' require 'json-schema' require 'aws-sdk-resources/validator/context' +require 'aws-sdk-resources/validator/dsl' +require 'aws-sdk-resources/validator/path_resolver' require 'aws-sdk-resources/validator/rule' -require 'aws-sdk-resources/validator/shape_validator' -require 'aws-sdk-resources/validator/identifier_validator' require 'aws-sdk-resources/validator/operation_validator' module Aws module Resources module Validator - # @api private - RULES = [] + extend Validator::DSL - # @api private - SCHEMA_PATH = File.expand_path(File.join([ - File.dirname(__FILE__), '..', '..', 'resources.schema.json' - ])) + # identifiers_may_not_be_prefixed_by_their_resource_name + match('#/resources/(\w+)/identifiers/\d+/name') do |c, matches| + resource_name = matches[1] + if c.value.match(/^#{resource_name}/) + c.error("#{c.path} must not be prefixed with '#{resource_name}'") + end + end - class << self + # identifiers_must_have_unique_names + match('#/resources/\w+/identifiers') do |c| + identifiers = c.value.map { |v| v['name'] } + identifiers.each.with_index do |identifier, n| + unless identifiers.count(identifier) == 1 + c.error("#{c.path}/#{n}/name must be uniq within #{c.path}/*/name") + end + end + end - def match(pattern, &block) - RULES << Rule.new(pattern, &block) + # identifiers_with_memberName_require_the_resource_to_have_a_shape + match('#/resources/(\w+)/identifiers/(\d+)/memberName') do |c, matches| + resource_name = matches[1] + shape_path = "#/resources/#{resource_name}/shape" + unless c.definition['resources'][resource_name]['shape'] + c.error("#{c.path} requires #{shape_path} to be set") end + end - # @param [Hash] definition - # @param [Hash] api - # @return [Array<String>] - def validate(definition, api) - errors = apply_schema(definition) - errors = lint('#', definition, definition, api) if errors.empty? - errors + # identifiers_with_memberName_require_the_resource_shape_to_have_that_member + match('#/resources/(\w+)/identifiers/(\d+)/memberName') do |c, matches| + resource_name = matches[1] + shape_path = "#/resources/#{resource_name}/shape" + shape_name = c.definition['resources'][resource_name]['shape'] + if + shape_name && + c.api['shapes'][shape_name] && + c.api['shapes'][shape_name]['type'] == 'structure' && + !c.api['shapes'][shape_name]['members'].key?(c.value) + then + c.error("#{c.path} is not defined at api#/shapes/#{shape_name}/members/#{c.value}") end + end - private + # shape_must_be_defined_in_the_api + match('#/resources/\w+/shape') do |c| + unless c.api['shapes'][c.value] + c.error("#{c.path} not found at api#/shapes/#{c.value}") + end + end - # Validates the resource definition document against the JSON - # schema for resources. - # @param [Hash] definition - # @return [Array<String>] Returns an array of schema validation errors. - # Returns an empty array if there are no errors. - def apply_schema(definition) - schema = MultiJson.load(File.read(SCHEMA_PATH)) - JSON::Validator.fully_validate(schema, definition) + # shape_must_be_a_structure + # load_requires_shape_to_be_a_structure + match('#/resources/\w+/shape') do |c| + shape = c.api['shapes'][c.value] + if shape && shape['type'] != 'structure' + c.error("#{c.path} must resolve to a structure") end + end - # Recursively lints the resource definition hash against the given - # api. - # @param [Hash] definition - # @param [Hash] api - # @return [Array<String>] Returns an array of schema validation errors. - # Returns an empty array if there are no errors. - def lint(prefix, node, definition, api, errors = []) - lint_node(prefix, node, definition, api, errors) - case node - when Hash - node.each do |key, value| - lint("#{prefix}/#{key}", value, definition, api, errors) - end - when Array - node.each.with_index do |value, index| - lint("#{prefix}/#{index}", value, definition, api, errors) - end - end - errors + # load_requires_shape_to_be_set + match('#/resources/(\w+)/load') do |c, matches| + resource = matches[1] + unless c.definition['resources'][resource]['shape'] + c.error("#{c.path} requires #/resources/#{resource}/shape to be set") end + end - def lint_node(path, value, definition, api, errors) - RULES.each do |rule| - if rule.applies?(path) - errors.concat(rule.validate(path, value, definition, api)) + + # load_operation_must_exist + # TODO : add assertions for service/actions, service/hasMany, actions, etc + match(*%w( + #/service/actions/\w+/request/operation + #/service/hasMany/\w+/request/operation + #/resources/\w+/load/request/operation + #/resources/\w+/actions/\w+/request/operation + #/resources/\w+/batchActions/\w+/request/operation + #/resources/\w+/hasMany/\w+/request/operation + )) do |c| + unless c.api['operations'][c.value] + c.error("#{c.path} is set but is not defined at api#/operations/#{c.value}") + end + end + + # load_operation_must_accept_input_if_params_given + match(*%w( + #/service/actions/\w+/request + #/service/hasMany/\w+/request + #/resources/\w+/load/request + #/resources/\w+/actions/\w+/request + #/resources/\w+/batchActions/\w+/request + #/resources/\w+/hasMany/\w+/request + )) do |c| + # ... + end + + # load_path_must_resolve_to_shape + match('#/resources/(\w+)/load/path') do |c, matches| + operation_name = + c.definition['resources'][matches[1]]['load']['request']['operation'] + if operation = c.api['operations'][operation_name] + if from = operation['output'] + if shape_name = c.definition['resources'][matches[1]]['shape'] + resolved = PathResolver.new(c.api).resolve(c.value, from) + unless resolved == shape_name + c.error("#{c.path} must resolve to a '#{shape_name}' shape") + end + else + # resource defines load but does not define shape end + else + # resource load operation does not have output shape reference end + else + # resource load operation not in API model end + end + match(*%w( + #/service/actions/\w+/request + #/resources/\w+/load/request + #/resources/\w+/actions/\w+/request + #/resources/\w+/batchActions/\w+/request + #/resources/\w+/hasMany/\w+/request + )) do |c| end - match('#/resources/(\w+)/shape') do |context| - ShapeValidator.new(context, context.value).validate + match(*%w( + #/service/hasMany/\w+/resource/type + #/resources/\w+/actions/\w+/resource/type + #/resources/\w+/batchActions/\w+/resource/type + #/resources/\w+/hasMany/\w+/resource/type + )) do |c| end - match('#/resources/(\w+)/identifiers') do |context| - context.value.each.with_index do |identifier, index| - IdentifierValidator.new(context, identifier, index).validate + match(*%w( + #/service/hasMany/\w+/resource/identifiers + #/resources/\w+/actions/\w+/resource/identifiers + #/resources/\w+/batchActions/\w+/resource/identifiers + #/resources/\w+/hasMany/\w+/resource/identifiers + )) do |c| + # must provide ALL of the identifiers for the target resource + # each identifier must resolve from its + end + + match(*%w( + #/service/hasMany/\w+/resource/path + #/resources/\w+/actions/\w+/resource/path + #/resources/\w+/batchActions/\w+/resource/path + #/resources/\w+/hasMany/\w+/resource/path + )) do |c| + type = c.parent['type'] + from = c.parent.parent['request']['operation'] + from = c.api['operations'][from]['output'] + expected = c.definition['resources'][type]['shape'] + resolved = PathResolver.new(c.api).resolve(c.value, from) + unless expected == resolved + c.error("#{c.path} must resolve to a \"#{expected}\" shape") end end + # load_params_can_be_nested + # load_params_must_match_their_static_types + # load_params_must_not_be_dataMembers + # load_params_must_resolve + # load_path_accepts_dollar_signs + # load_path_accepts_nested_paths + match('#/resources/(\w+)/load') do |context| v = OperationValidator.new(context) v.validate_request do v.validate_param_source_type_not_used('dataMember') - v.validate_path(origin: :response, target: :self) + #v.validate_path(origin: :response, target: :self) end v.validate_path_set v.validate_resource_not_set end @@ -121,21 +212,21 @@ v.validate_path(origin: :response, target: :resource) v.validate_path_is_plural end end - match('#/resources/(\w+)/(hasSome|hasOne)/\w+') do |context| + match('#/resources/(\w+)/belongsTo/\w+') do |context, matches| v = OperationValidator.new(context) v.validate_request_not_set v.validate_resource do # disallow requestParameter # disallow responsepath end # path must resolve FROM the resource data shape # to the target resource data v.validate_path(origin: :self, target: :resource) - if context.matches[2] == 'hasSome' + if matches[2] == 'hasSome' # at least one identifier source must be plural # path must be plural if given else # identifier sources must be singular # path must be singular if given