### ### $Rev: 18 $ ### $Release: 0.2.0 $ ### copyright(c) 2005 kuwata-lab all rights reserved. ### require 'kwalify/messages' require 'kwalify/errors' require 'kwalify/types' module Kwalify class Rule include Kwalify::ErrorHelper def initialize(hash=nil) configure(hash, "", {}) if hash end def configure(hash, path="", rule_table={}) unless hash.is_a?(Hash) #* key=:schema_notmap msg="schema definition is not a mapping." raise Kwalify.schema_error(:schema_notmap, nil, "/", nil) end rule = self rule_table[hash.__id__] = rule ## 'type:' @type = hash['type'] @type = Kwalify::DEFAULT_TYPE if @type == nil unless @type.is_a?(String) #* key=:type_notstr msg="not a string." raise schema_error(:type_notstr, rule, "#{path}/type", @type.to_s) end @klass = Kwalify.get_type_class(@type) #if @klass == nil # begin # @klass = Kernel.const_get(@type) # rescue NameError # end #end unless @klass #* key=:type_unknown msg="unknown type." raise schema_error(:type_unknown, rule, "#{path}/type", @type.to_s) end ## other key hash.each do |key, val| curr_path = "#{path}/#{key}" case key #when "id" # @id = val when "name" @name = val when "desc" @desc = val when "type" # done when "required" @required = val unless val == nil || val.is_a?(Boolean) #* key=:required_notbool msg="not a boolean." raise schema_error(:required_notbool, rule, curr_path, val) end when "pattern" pat = (val =~ /\A\/(.*)\/([mi]?[mi]?)\z/ ? $1 : val) begin @pattern = Regexp.compile(pat) rescue RegexpError => ex #* key=:pattern_syntaxerr msg="has regexp error." raise schema_error(:pattern_syntaxerr, rule, curr_path, val) end when "enum" @enum = val unless val.is_a?(Array) #* key=:enum_notseq msg="not a sequence." raise schema_error(:enum_notseq, rule, curr_path, val) end if @type == 'seq' || @type == 'map' # unless Kwalify.scalar_class?(@klass) #* key=:enum_notscalar msg="not available with seq or map." raise schema_error(:enum_notscalar, rule, path, 'enum:') end elem_table = {} @enum.each do |elem| unless !elem_table[elem] #* key=:enum_duplicate msg="duplicated enum value." raise schema_error(:enum_duplicate, rule, curr_path, elem.to_s) end elem_table[elem] = true unless elem.is_a?(@klass) #* key=:enum_type_unmatch msg="%s type expected." raise schema_error(:enum_type_unmatch, rule, curr_path, elem, [Kwalify.word(@type)]) end end when "assert" # or "cond-expr" ? unless val.is_a?(String) #* key=:assert_notstr msg="not a string." raise schema_error(:assert_notstr, rule, curr_path, val) end unless val =~ /\bval\b/ #* key=:assert_noval msg="'val' is not used." raise schema_error(:assert_noval, rule, curr_path, val) end begin @assert = val @assert_proc = eval "proc { |val| #{val} }" rescue SyntaxError => ex #* key=:assert_syntaxerr msg="expression syntax error." raise schema_error(:assert_syntaxerr, rule, curr_path, val) end when "range" @range = val unless val.is_a?(Hash) #* key=:range_notmap msg="not a mapping." raise schema_error(:range_notmap, rule, curr_path, val) end if @type == 'map' || @type == 'seq' || @type == 'bool' #* key=:range_notscalar msg="is available only with scalar type." raise schema_error(:range_notscalar, rule, path, 'range:') end val.each do |rkey, rval| case rkey when 'max', 'min' unless rval.is_a?(@klass) typename = Kwalify.word(@type) || @type #* key=:range_type_unmatch msg="not a %s." raise schema_error(:range_type_unmatch, rule, "#{curr_path}/#{rkey}", rval, [typename]) end else #* key=:range_undefined msg="undefined key." raise schema_error(:range_undefined, rule, curr_path, "#{rkey}:") end end when "length" # or "width" @length = val unless val.is_a?(Hash) #* key=:length_notmap msg="not a mapping." raise schema_error(:length_notmap, rule, curr_path, val) end unless @type == 'str' || @type == 'text' #* key=:length_nottext msg="is available only with string or text." raise schema_error(:length_nottext, rule, path, 'length:') end val.each do |lkey, lval| case lkey when 'max', 'min' unless lval.is_a?(Integer) #* key=:length_notint msg="not an integer." raise schema_error(:length_notint, rule, "#{curr_path}/#{lkey}", lval) end else #* key=:length_undefined msg="undefined key." raise schema_error(:length_undefined, rule, curr_path, "#{lkey}:") end end when "sequence" if val != nil && !val.is_a?(Array) #* key=:sequence_notseq msg="not a sequence." raise schema_error(:sequence_notseq, rule, curr_path, val) elsif val == nil || val.empty? #* key=:sequence_noelem msg="required one element." raise schema_error(:sequence_noelem, rule, curr_path, val) elsif val.length > 1 #* key=:sequence_toomany msg="required just one element." raise schema_error(:sequence_toomany, rule, curr_path, val) else elem = val[0] elem ||= {} i = 0 # or 1? *index* rule = rule_table[elem.__id__] rule ||= Rule.new.configure(elem, "#{curr_path}/#{i}", rule_table) @sequence = [ rule ] end when "mapping" if val != nil && !val.is_a?(Hash) #* key=:mapping_notmap msg="not a mapping." raise schema_error(:mapping_notmap, rule, curr_path, val) elsif val == nil || val.empty? #* key=:mapping_noelem msg="required at least one element." raise schema_error(:mapping_noelem, rule, curr_path, val) else @mapping = {} val.each do |key, elem| ##* key=:key_duplicate msg="key duplicated." #raise schema_error(:key_duplicate, rule, curr_path, key) if @mapping.key?(key) elem ||= {} rule = rule_table[elem.__id__] rule ||= Rule.new.configure(elem, "#{curr_path}/#{key}", rule_table) if key == '*' @mapping.default = rule else @mapping[key] = rule end end end else #* key=:key_unknown msg="unknown key." raise schema_error(:key_unknown, rule, path, "#{key}:") end end if @type == 'seq' #* key=:seq_nosequence msg="type 'seq' requires 'sequence:'." raise Kwalify.validate_error(:seq_nosequence, rule, path, nil) unless hash.key?('sequence') #* key=:seq_conflict msg="not available with sequence." raise schema_error(:seq_conflict, rule, path, 'enum:') if @enum raise schema_error(:seq_conflict, rule, path, 'pattern:') if @pattern raise schema_error(:seq_conflict, rule, path, 'mapping:') if @mapping raise schema_error(:seq_conflict, rule, path, 'range:') if @range raise schema_error(:seq_conflict, rule, path, 'length:') if @length elsif @type == 'map' #* key=:map_nomapping msg="type 'map' requires 'mapping:'." raise Kwalify.validate_error(:map_nomapping, rule, path, nil) unless hash.key?('mapping') #* key=:map_conflict msg="not available with mapping." raise schema_error(:map_conflict, rule, path, 'enum:') if @enum raise schema_error(:map_conflict, rule, path, 'pattern:') if @pattern raise schema_error(:map_conflict, rule, path, 'sequence:') if @sequence raise schema_error(:map_conflict, rule, path, 'range:') if @range raise schema_error(:map_conflict, rule, path, 'length:') if @length else #* key=:scalar_conflict msg="not available with scalar type." raise schema_error(:scalar_conflict, rule, path, 'sequence:') if @sequence raise schema_error(:scalar_conflict, rule, path, 'mapping:') if @mapping if @enum #* key=:enum_conflict msg="not available with 'enum:'." raise schema_error(:enum_conflict, rule, path, 'range:') if @range raise schema_error(:enum_conflict, rule, path, 'length:') if @length raise schema_error(:enum_conflict, rule, path, 'pattern:') if @pattern end end return self end # end of def configure #attr_reader :id attr_reader :name attr_reader :desc attr_reader :enum attr_reader :required attr_reader :type attr_reader :klass attr_reader :pattern attr_reader :sequence attr_reader :mapping attr_reader :assert attr_reader :assert_proc attr_reader :range attr_reader :length #def inspect() # str = ""; level = 0; done = {} # _inspect(str, level, done) # return str #end #protected def _inspect(str="", level=0, done={}) done[self.__id__] = true str << " " * level << "name: #{@name}\n" if @name != nil str << " " * level << "desc: #{@desc}\n" if @desc != nil str << " " * level << "type: #{@type}\n" if @type != nil str << " " * level << "klass: #{@klass.name}\n" if @klass != nil str << " " * level << "required: #{@required}\n" if @required != nil str << " " * level << "pattern: #{@pattern.inspect}\n" if @pattern != nil str << " " * level << "assert: #{@assert}\n" if @assert != nil if @enum != nil str << " " * level << "enum:\n" @enum.each do |item| str << " " * (level+1) << "- #{item}\n" end end if @range != nil str << " " * level str << "range: { " str << "max: #{@range['max'].inspect}" if @range['max'] != nil str << ", " if @range['max'] != nil && @range['min'] != nil str << "min: #{@range['min'].inspect}" if @range['min'] != nil str << "}\n" end if @length != nil str << " " * level str << "length: { " str << "max: #{@length['max'].inspect}" if @length['max'] != nil str << ", " if @length['max'] != nil && @length['min'] != nil str << "min: #{@length['min'].inspect}" if @length['min'] != nil str << "}\n" end @sequence.each do |rule| if done[rule.__id__] str << " " * (level+1) << "- ...\n" else str << " " * (level+1) << "- \n" rule._inspect(str, level+2, done) end end if @sequence @mapping.each do |key, rule| if done[rule.__id__] str << ' ' * (level+1) << '"' << key << "\": ...\n" else str << ' ' * (level+1) << '"' << key << "\":\n" rule._inspect(str, level+2, done) end end if @mapping return str end end end