diff --git a/lib/jsi/base.rb b/lib/jsi/base.rb index 2b655e5f..6007c3f4 100644 --- a/lib/jsi/base.rb +++ b/lib/jsi/base.rb @@ -220,7 +220,7 @@ def jsi_each_descendent_node(propertyNames: false, &block) end jsi_each_child_token do |token| - jsi_child(token, as_jsi: true).jsi_each_descendent_node(propertyNames: propertyNames, &block) + jsi_child_node(token).jsi_each_descendent_node(propertyNames: propertyNames, &block) end nil @@ -239,7 +239,7 @@ def jsi_select_descendents_node_first(&block) if jsi_array? || jsi_hash? res = instance.class.new jsi_each_child_token do |token| - v = jsi_child(token, as_jsi: true) + v = jsi_child_node(token) if yield(v) res_v = v.jsi_select_descendents_node_first(&block).jsi_node_content if jsi_array? @@ -269,7 +269,7 @@ def jsi_select_descendents_leaf_first(&block) if jsi_array? || jsi_hash? res = instance.class.new jsi_each_child_token do |token| - v = jsi_child(token, as_jsi: true).jsi_select_descendents_leaf_first(&block) + v = jsi_child_node(token).jsi_select_descendents_leaf_first(&block) if yield(v) res_v = v.jsi_node_content if jsi_array? @@ -294,7 +294,7 @@ def jsi_parent_nodes jsi_ptr.tokens.map do |token| parent.tap do - parent = parent[token, as_jsi: true] + parent = parent.jsi_child_node(token) end end.reverse!.freeze end @@ -315,7 +315,7 @@ def jsi_ancestor_nodes ancestors << ancestor jsi_ptr.tokens.each do |token| - ancestor = ancestor[token, as_jsi: true] + ancestor = ancestor.jsi_child_node(token) ancestors << ancestor end ancestors.reverse!.freeze @@ -408,6 +408,12 @@ def jsi_child(token, as_jsi: ) end private :jsi_child # internals for #[] but idk, could be public + # @param token (see Base#[]) + # @return [JSI::Base] + protected def jsi_child_node(token) + jsi_child(token, as_jsi: true) + end + # A default value for a child of this node identified by the given token, if schemas describing # that child define a default value. # @@ -437,7 +443,7 @@ def jsi_default_child(token, as_jsi: ) jsi_child_as_jsi(defaults.first, child_applied_schemas, as_jsi) do jsi_modified_copy do |i| i.dup.tap { |i_dup| i_dup[token] = defaults.first } - end[token, as_jsi: true] + end.jsi_child_node(token) end else child_content diff --git a/lib/jsi/base/node.rb b/lib/jsi/base/node.rb index 497b66a3..e5898eca 100644 --- a/lib/jsi/base/node.rb +++ b/lib/jsi/base/node.rb @@ -98,7 +98,7 @@ def as_json(options = {}) k.is_a?(Symbol) ? k.to_s : k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr : raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}") - hash[ks] = jsi_child(k, as_jsi: true).as_json(**options) + hash[ks] = jsi_child_node(k).as_json(**options) end hash end @@ -263,7 +263,7 @@ def to_ary(**kw) # See {Base#as_json} def as_json(options = {}) - each_index.map { |i| jsi_child(i, as_jsi: true).as_json(**options) } + each_index.map { |i| jsi_child_node(i).as_json(**options) } end include Util::Arraylike diff --git a/lib/jsi/jsi_coder.rb b/lib/jsi/jsi_coder.rb index cccdafb5..e78c9f9a 100644 --- a/lib/jsi/jsi_coder.rb +++ b/lib/jsi/jsi_coder.rb @@ -31,7 +31,7 @@ class JSICoder # or an array of them. note that it may be preferable to simply use an array schema. # @param jsi_opt [Hash] keyword arguments to pass to {Schema#new_jsi} when loading # @param as_json_opt [Hash] keyword arguments to pass to `#as_json` when dumping - def initialize(schema, array: false, jsi_opt: {}, as_json_opt: {}) + def initialize(schema, array: false, jsi_opt: Util::EMPTY_HASH, as_json_opt: Util::EMPTY_HASH) unless schema.respond_to?(:new_jsi) raise(ArgumentError, "schema param does not respond to #new_jsi: #{schema.inspect}") end diff --git a/lib/jsi/metaschema_node.rb b/lib/jsi/metaschema_node.rb index 5983d09a..96b491f4 100644 --- a/lib/jsi/metaschema_node.rb +++ b/lib/jsi/metaschema_node.rb @@ -229,7 +229,7 @@ def our_initialize_params jsi_schema_base_uri: jsi_schema_base_uri, jsi_schema_registry: jsi_schema_registry, jsi_content_to_immutable: jsi_content_to_immutable, - } + }.freeze end # note: not for root node diff --git a/lib/jsi/ptr.rb b/lib/jsi/ptr.rb index 33f3c5f2..38f14263 100644 --- a/lib/jsi/ptr.rb +++ b/lib/jsi/ptr.rb @@ -167,19 +167,25 @@ def parent tokens.size == 1 ? EMPTY : Ptr.new(tokens[0...-1].freeze) end - # whether this pointer contains the other_ptr - that is, whether this pointer is an ancestor - # of `other_ptr`, a descendent pointer. `contains?` is inclusive; a pointer does contain itself. + # whether this pointer is an ancestor of `other_ptr`, a descendent pointer. + # `ancestor_of?` is inclusive; a pointer is an ancestor of itself. + # # @return [Boolean] - def contains?(other_ptr) + def ancestor_of?(other_ptr) tokens == other_ptr.tokens[0...tokens.size] end + # @deprecated + def contains?(other_ptr) + ancestor_of?(other_ptr) + end + # part of this pointer relative to the given ancestor_ptr # @return [JSI::Ptr] # @raise [JSI::Ptr::Error] if the given ancestor_ptr is not an ancestor of this pointer def relative_to(ancestor_ptr) return self if ancestor_ptr.empty? - unless ancestor_ptr.contains?(self) + unless ancestor_ptr.ancestor_of?(self) raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}") end ancestor_ptr.tokens.size == tokens.size ? EMPTY : Ptr.new(tokens[ancestor_ptr.tokens.size..-1].freeze) diff --git a/lib/jsi/schema.rb b/lib/jsi/schema.rb index 2448b59a..66a39d29 100644 --- a/lib/jsi/schema.rb +++ b/lib/jsi/schema.rb @@ -376,16 +376,17 @@ def ensure_schema(schema, msg: "indicated object is not a schema:", reinstantiat if reinstantiate_as && schema.is_a?(JSI::Base) # TODO warn; behavior is undefined and I hate this implementation - result_schema_schemas = schema.jsi_schemas + reinstantiate_as + result_schema_indicated_schemas = SchemaSet.new(schema.jsi_indicated_schemas + reinstantiate_as) + result_schema_applied_schemas = result_schema_indicated_schemas.inplace_applicator_schemas(schema.jsi_node_content) - result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_schemas, + result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_applied_schemas, includes: SchemaClasses.includes_for(schema.jsi_node_content), mutable: schema.jsi_mutable?, ) result_schema_class.new(schema.jsi_document, jsi_ptr: schema.jsi_ptr, - jsi_indicated_schemas: schema.jsi_indicated_schemas, + jsi_indicated_schemas: result_schema_indicated_schemas, jsi_schema_base_uri: schema.jsi_schema_base_uri, jsi_schema_resource_ancestors: schema.jsi_schema_resource_ancestors, jsi_schema_registry: schema.jsi_schema_registry, @@ -466,7 +467,7 @@ def schema_absolute_uri end # a nonrelative URI which refers to this schema. - # nil if no parent of this schema defines an id. + # `nil` if no ancestor of this schema defines an id. # see {#schema_uris} for all URIs known to refer to this schema. # @return [Addressable::URI, nil] def schema_uri @@ -491,22 +492,22 @@ def each_schema_uri yield schema_absolute_uri if schema_absolute_uri - parent_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource| + ancestor_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource| resource.schema_absolute_uri end anchored = respond_to?(:anchor) ? anchor : nil - parent_schemas.each do |parent_schema| + ancestor_schemas.each do |ancestor_schema| if anchored - if parent_schema.jsi_anchor_subschema(anchor) == self - yield parent_schema.schema_absolute_uri.merge(fragment: anchor).freeze + if ancestor_schema.jsi_anchor_subschema(anchor) == self + yield(ancestor_schema.schema_absolute_uri.merge(fragment: anchor).freeze) else anchored = false end end - relative_ptr = jsi_ptr.relative_to(parent_schema.jsi_ptr) - yield parent_schema.schema_absolute_uri.merge(fragment: relative_ptr.fragment).freeze + relative_ptr = jsi_ptr.relative_to(ancestor_schema.jsi_ptr) + yield(ancestor_schema.schema_absolute_uri.merge(fragment: relative_ptr.fragment).freeze) end nil @@ -603,10 +604,10 @@ def describes_schema!(schema_implementation_modules) # a resource containing this schema. # - # if any parent, or this schema itself, is a schema with an absolute uri (see {#schema_absolute_uri}), + # If any ancestor, or this schema itself, is a schema with an absolute uri (see {#schema_absolute_uri}), # the resource root is the closest schema with an absolute uri. # - # If no parent schema has an absolute uri, the schema_resource_root is the {Base#jsi_root_node document's root node}. + # If no ancestor schema has an absolute uri, the schema_resource_root is the {Base#jsi_root_node document's root node}. # In this case, the resource root may or may not be a schema itself. # # @return [JSI::Base] resource containing this schema @@ -767,7 +768,14 @@ def internal_validate_instance( else result = JSI::Validation::FullResult.new end - result_builder = result.builder(self, instance_ptr, instance_document, validate_only, visited_refs) + result_builder = result.class::Builder.new( + result: result, + schema: self, + instance_ptr: instance_ptr, + instance_document: instance_document, + validate_only: validate_only, + visited_refs: visited_refs, + ) catch(:jsi_validation_result) do # note: true/false are not valid as schemas in draft 4; they are only values of diff --git a/lib/jsi/schema/schema_ancestor_node.rb b/lib/jsi/schema/schema_ancestor_node.rb index 4291ce37..9cd247e6 100644 --- a/lib/jsi/schema/schema_ancestor_node.rb +++ b/lib/jsi/schema/schema_ancestor_node.rb @@ -18,7 +18,7 @@ def initialize(*, **) # the base URI used to resolve the ids of schemas at or below this JSI. # this is always an absolute URI (with no fragment). - # this may be the absolute schema URI of a parent schema or the URI from which the document was retrieved. + # This may be the absolute schema URI of an ancestor schema or the URI from which the document was retrieved. # @api private # @return [Addressable::URI, nil] attr_reader :jsi_schema_base_uri @@ -34,7 +34,7 @@ def initialize(*, **) # the URI of the resource containing this node. # this is always an absolute URI (with no fragment). - # if this node is a schema with an id, this is its absolute URI; otherwise a parent resource's URI, + # If this node is a schema with an id, this is its absolute URI; otherwise an ancestor resource's URI, # or nil if not contained by a resource with a URI. # @return [Addressable::URI, nil] def jsi_resource_ancestor_uri @@ -97,7 +97,7 @@ def jsi_schema_resource_ancestors=(jsi_schema_resource_ancestors) #chkbug end #chkbug if anc.jsi_ptr == jsi_ptr #chkbug raise(Bug, "ancestor is self") - #chkbug elsif !anc.jsi_ptr.contains?(jsi_ptr) + #chkbug elsif !anc.jsi_ptr.ancestor_of?(jsi_ptr) #chkbug raise(Bug, "ancestor does not contain self") #chkbug end #chkbug last_anc_ptr = anc.jsi_ptr diff --git a/lib/jsi/schema_set.rb b/lib/jsi/schema_set.rb index dfdef5a5..4951b83e 100644 --- a/lib/jsi/schema_set.rb +++ b/lib/jsi/schema_set.rb @@ -6,14 +6,12 @@ module JSI # any schema instance is described by a set of schemas. class SchemaSet < ::Set class << self - # builds a SchemaSet from a mutable Set which is added to by the given block + # Builds a SchemaSet, yielding a yielder to be called with each schema of the SchemaSet. # - # @yield [Set] a Set to which the block may add schemas + # @yield [Enumerator::Yielder] # @return [SchemaSet] - def build - mutable_set = Set.new - yield mutable_set - new(mutable_set) + def build(&block) + new(Enumerator.new(&block)) end # ensures the given param becomes a SchemaSet. returns the param if it is already SchemaSet, otherwise @@ -192,8 +190,9 @@ def each_child_applicator_schema(token, instance, &block) # @param instance [Object] the instance to validate against our schemas # @return [JSI::Validation::Result] def instance_validate(instance) - results = map { |schema| schema.instance_validate(instance) } - results.inject(Validation::FullResult.new, &:merge).freeze + inject(Validation::FullResult.new) do |result, schema| + result.merge(schema.instance_validate(instance)) + end.freeze end # whether the given instance is valid against our schemas diff --git a/lib/jsi/simple_wrap.rb b/lib/jsi/simple_wrap.rb index 9ce77a7d..d9eac6a4 100644 --- a/lib/jsi/simple_wrap.rb +++ b/lib/jsi/simple_wrap.rb @@ -15,7 +15,7 @@ def internal_validate_keywords(result_builder) end simple_wrap_metaschema = JSI.new_metaschema(nil, schema_implementation_modules: [simple_wrap_implementation]) - SimpleWrap = simple_wrap_metaschema.new_schema_module({}) + SimpleWrap = simple_wrap_metaschema.new_schema_module(Util::EMPTY_HASH) # SimpleWrap is a JSI schema module which recursively wraps nested structures module SimpleWrap diff --git a/lib/jsi/util/private.rb b/lib/jsi/util/private.rb index 68ba26c3..677949ba 100644 --- a/lib/jsi/util/private.rb +++ b/lib/jsi/util/private.rb @@ -12,6 +12,8 @@ module Util::Private EMPTY_ARY = [].freeze + EMPTY_HASH = {}.freeze + EMPTY_SET = Set[].freeze CLASSES_ALWAYS_FROZEN = Set[TrueClass, FalseClass, NilClass, Integer, Float, BigDecimal, Rational, Symbol].freeze @@ -179,24 +181,5 @@ def freeze super end end - - module Virtual - class InstantiationError < StandardError - end - - # this virtual class is not intended to be instantiated except by its subclasses, which override #initialize - def initialize - # :nocov: - raise(InstantiationError, "cannot instantiate virtual class #{self.class}") - # :nocov: - end - - # virtual_method is used to indicate that the method calling it must be implemented on the (non-virtual) subclass - def virtual_method - # :nocov: - raise(Bug, "class #{self.class} must implement #{caller_locations.first.label}") - # :nocov: - end - end end end diff --git a/lib/jsi/validation/error.rb b/lib/jsi/validation/error.rb index fe8f39ac..045ecbd3 100644 --- a/lib/jsi/validation/error.rb +++ b/lib/jsi/validation/error.rb @@ -29,6 +29,10 @@ module Validation # document containing the instance at instance_ptr # @return [Object] class Error + def initialize(attributes = {}) + super + freeze + end end end end diff --git a/lib/jsi/validation/result.rb b/lib/jsi/validation/result.rb index 80847215..3b7fed51 100644 --- a/lib/jsi/validation/result.rb +++ b/lib/jsi/validation/result.rb @@ -3,10 +3,7 @@ module JSI module Validation # a result of validating an instance against schemas which describe it. - # virtual base class. class Result - include Util::Virtual - Builder = Util::AttrStruct[*%w( result schema @@ -24,7 +21,6 @@ def instance end def schema_issue(*_) - virtual_method end def schema_error(message, keyword = nil) @@ -74,23 +70,10 @@ def merge_schema_issues(other_result) end class Result - def builder(schema, instance_ptr, instance_document, validate_only, visited_refs) - self.class::Builder.new( - result: self, - schema: schema, - instance_ptr: instance_ptr, - instance_document: instance_document, - validate_only: validate_only, - visited_refs: visited_refs, - ) - end - # is the instance valid against its schemas? # @return [Boolean] def valid? - # :nocov: - virtual_method - # :nocov: + #dbg raise(NotImplementedError) end include Util::FingerprintHash @@ -144,7 +127,6 @@ def valid? end def freeze - @validation_errors.each(&:freeze) @schema_issues.each(&:freeze) @validation_errors.freeze @schema_issues.freeze @@ -160,10 +142,6 @@ def merge(result) self end - def +(result) - FullResult.new.merge(self).merge(result) - end - # see {Util::Private::FingerprintHash} # @api private def jsi_fingerprint diff --git a/test/metaschema_node_test.rb b/test/metaschema_node_test.rb index d1136dc8..867357c7 100644 --- a/test/metaschema_node_test.rb +++ b/test/metaschema_node_test.rb @@ -301,7 +301,7 @@ def assert_metaschema_behaves metaschema.instance_validate(metaschema), ] metaschema.jsi_each_descendent_node do |node| - if node.jsi_ptr.contains?(JSI::Ptr['title']) + if node.jsi_ptr.ancestor_of?(JSI::Ptr['title']) results << node.jsi_validate else assert(node.jsi_valid?)