Skip to content

Commit

Permalink
Merge remote-tracking branches 'origin/schema_registry', 'origin/gem'…
Browse files Browse the repository at this point in the history
…, 'origin/test', 'origin/misc' and 'origin/doc' into HEAD

schema_registry #217
gem #220
test #219
misc #216
doc #221
  • Loading branch information
notEthan committed Jan 21, 2022
5 parents 9da5630 + 1d6f988 + 79c86c8 + 1d4696c + fec06cc commit 9caae90
Show file tree
Hide file tree
Showing 23 changed files with 329 additions and 156 deletions.
10 changes: 7 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
source "https://rubygems.org"

# Specify your gem's dependencies in jsi.gemspec
gemspec

gem 'irb'
gem 'byebug'
gem 'spreedly_openapi', github: 'notEthan/spreedly_openapi', tag: 'v0.2.0'
gem 'scorpio', github: 'notEthan/scorpio', ref: 'dcb642564341c5815a5cbecc13ac6308836d7f70'
gem 'rake'
gem 'minitest'
gem 'minitest-around'
gem 'minitest-reporters'
gem 'simplecov'
gem 'simplecov-lcov'
gem 'scorpio', '~> 0.5', github: 'notEthan/scorpio', ref: 'dcb642564341c5815a5cbecc13ac6308836d7f70'
gem 'spreedly_openapi', github: 'notEthan/spreedly_openapi', tag: 'v0.2.0'
gem 'activesupport'
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ bill.jsi_valid?
# => true
```

... and validations on the nested schema instances (`#phone` here), showing in this example validation failure:
... and validations on the nested schema instances (`#phone` here), showing in this example validation failure on /phone/0/number:

```ruby
bad = Contact.new_jsi({'phone' => [{'number' => [5, 5, 5]}]})
Expand All @@ -113,6 +113,10 @@ bad.phone.jsi_validate
# #<Set: {#<JSI::Validation::Error
# message: "instance type does not match `type` value",
# keyword: "type",
# schema: #{<JSI (JSI::JSONSchemaOrgDraft07) Schema> "type" => "string"},
# instance_ptr: JSI::Ptr["phone", 0, "number"],
# instance_document: {"phone"=>[{"number"=>[5, 5, 5]}]}
# >,
# ...
# >
```
Expand Down Expand Up @@ -142,6 +146,16 @@ There's plenty more JSI has to offer, but this should give you a pretty good ide
- a JSI instance (or just "a JSI") is a ruby object instantiating a JSI schema class (subclass of `JSI::Base`). This wraps the content of the schema instance (see `JSI::Base#jsi_instance`), and ties it to the schemas which describe the instance (`JSI::Base#jsi_schemas`).
- "schema" refers to either a parsed JSON schema (generally a ruby Hash) or a JSI schema.

## Supported specification versions

JSI supports these JSON Schema specification versions:

| Version | `$schema` URI | JSI Schema Module |
| --- | --- | --- |
| Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaOrgDraft04} |
| Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaOrgDraft06} |
| Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaOrgDraft07} |

## JSI and Object Oriented Programming

Instantiating your schema is a starting point. But, since the major point of object-oriented programming is applying methods to your objects, of course you want to be able to define your own methods. To do this we reopen the JSI module we defined. Referring back to the Example section above, we reopen the `Contact` module:
Expand Down Expand Up @@ -221,6 +235,14 @@ end

The classes used to instantiate JSIs are dynamically generated subclasses of JSI::Base which include the JSI Schema Module of each schema describing the given instance. These are mostly intended to be ignored: applications aren't expected to instantiate these directly (rather, `#new_jsi` on a Schema or Schema Module is intended), and they are not intended for subclassing or method definition (applications should instead define methods on a schema's {JSI::Schema#jsi_schema_module}).

## Registration

In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry which associates URIs with schemas (or resources containing schemas). This registry is accessible on {JSI.schema_registry} and is a {JSI::SchemaRegistry}.

Schemas instantiated with `.new_schema`, and their subschemas, are automatically registered with `JSI.schema_registry` if they identify an absolute URI.

Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::SchemaRegistry#autoload_uri} (see its documentation).

## Validation

JSI implements all required features, and many optional features, for validation according to supported JSON Schema specifications. To validate instances, see methods {JSI::Base#jsi_validate}, {JSI::Base#jsi_valid?}, {JSI::Schema#instance_validate}, {JSI::Schema#instance_valid?}.
Expand Down
3 changes: 2 additions & 1 deletion Rakefile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
.gitmodules
Gemfile
jsi.gemspec
Rakefile.rb
test/**/*
\\{resources\\}/icons/**/*
\\{resources\\}/test/**/*
Expand Down Expand Up @@ -57,7 +58,7 @@
if JSI_GEM_IGNORE_FILES.include?(file)
# pass
else
file_error.("git file not in spec: #{file}")
file_error.("git file not in gemspec: #{file}")
end
end
else
Expand Down
8 changes: 1 addition & 7 deletions jsi.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,11 @@ Gem::Specification.new do |spec|
'README.md',
'readme.rb',
'.yardopts',
'Rakefile.rb',
*Dir['lib/**/*'],
*Dir['\\{resources\\}/schemas/**/*'],
].reject { |f| File.lstat(f).ftype == 'directory' }

spec.require_paths = ["lib"]

spec.add_development_dependency "rake"
spec.add_development_dependency "minitest"
spec.add_development_dependency "minitest-around"
spec.add_development_dependency "minitest-reporters"
spec.add_development_dependency "scorpio", "~> 0.5"
spec.add_development_dependency "activesupport"
spec.add_dependency "addressable", '~> 2.3'
end
4 changes: 2 additions & 2 deletions lib/jsi/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def [](token, as_jsi: :auto, use_default: true)
token_in_range = jsi_node_content_ary_pubsend(:each_index).include?(token)
value = jsi_node_content_ary_pubsend(:[], token)
else
raise(CannotSubscriptError, "cannot subcript (using token: #{token.inspect}) from instance: #{jsi_instance.pretty_inspect.chomp}")
raise(CannotSubscriptError, "cannot subscript (using token: #{token.inspect}) from instance: #{jsi_instance.pretty_inspect.chomp}")
end

begin
Expand Down Expand Up @@ -415,7 +415,7 @@ def [](token, as_jsi: :auto, use_default: true)
# @param value [JSI::Base, Object] the value to be assigned
def []=(token, value)
unless respond_to?(:to_hash) || respond_to?(:to_ary)
raise(NoMethodError, "cannot assign subcript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}")
raise(NoMethodError, "cannot assign subscript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}")
end
if value.is_a?(Base)
self[token] = value.jsi_instance
Expand Down
2 changes: 1 addition & 1 deletion lib/jsi/metaschema_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def [](token, as_jsi: :auto)
token_in_range = jsi_node_content_ary_pubsend(:each_index).include?(token)
value = jsi_node_content_ary_pubsend(:[], token)
else
raise(NoMethodError, "cannot subcript (using token: #{token.inspect}) from content: #{jsi_node_content.pretty_inspect.chomp}")
raise(NoMethodError, "cannot subscript (using token: #{token.inspect}) from content: #{jsi_node_content.pretty_inspect.chomp}")
end

begin
Expand Down
2 changes: 1 addition & 1 deletion lib/jsi/ptr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def modified_document_copy(document, &block)
else
modified_document = document.respond_to?(:[]=) ? document.dup :
document.respond_to?(:to_hash) ? document.to_hash.dup :
document_child.respond_to?(:to_ary) ? document.to_ary.dup :
document.respond_to?(:to_ary) ? document.to_ary.dup :
raise(Bug) # not possible; node_subscript_token_child would have raised
modified_document[token] = modified_document_child
modified_document
Expand Down
29 changes: 13 additions & 16 deletions lib/jsi/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,14 @@ module DescribesSchema
# the result schema will be registered with this URI in the {JSI.schema_registry}.
# @return [JSI::Base, JSI::Schema] a JSI whose instance is the given schema_content and whose schemas
# are inplace applicators matched from self to the schema being instantiated.
def new_schema(schema_content, uri: nil)
new_jsi(Util.deep_stringify_symbol_keys(schema_content),
def new_schema(schema_content,
uri: nil
)
schema_jsi = new_jsi(Util.deep_stringify_symbol_keys(schema_content),
uri: uri,
).tap(&:register_schema)
)
JSI.schema_registry.register(schema_jsi)
schema_jsi
end

# instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
Expand Down Expand Up @@ -215,7 +219,7 @@ def default_metaschema=(default_metaschema)
# a '$schema' property. this may be a metaschema or a metaschema's schema module
# (e.g. `JSI::JSONSchemaOrgDraft07`).
# @return [JSI::Schema] a JSI::Schema representing the given schema_object
def new_schema(schema_object, uri: nil, default_metaschema: nil)
def new_schema(schema_object, default_metaschema: nil, **kw)
default_metaschema_new_schema = -> {
default_metaschema ||= JSI::Schema.default_metaschema
if default_metaschema.nil?
Expand All @@ -230,7 +234,7 @@ def new_schema(schema_object, uri: nil, default_metaschema: nil)
if !default_metaschema.respond_to?(:new_schema)
raise(TypeError, "given default_metaschema does not respond to #new_schema: #{default_metaschema.pretty_inspect.chomp}")
end
default_metaschema.new_schema(schema_object, uri: uri)
default_metaschema.new_schema(schema_object, **kw)
}
if schema_object.is_a?(Schema)
schema_object
Expand All @@ -242,7 +246,7 @@ def new_schema(schema_object, uri: nil, default_metaschema: nil)
unless metaschema.describes_schema?
raise(Schema::ReferenceError, "given schema_object contains a $schema but the resource it identifies does not describe a schema")
end
metaschema.new_schema(schema_object, uri: uri)
metaschema.new_schema(schema_object, **kw)
else
default_metaschema_new_schema.call
end
Expand Down Expand Up @@ -404,16 +408,9 @@ def jsi_schema_class
# @return [JSI::Base subclass] a JSI whose instance is the given instance and whose schemas are matched
# from this schema.
def new_jsi(instance,
uri: nil
**kw
)
SchemaSet[self].new_jsi(instance, uri: uri)
end

# registers this schema with `JSI.schema_registry`
#
# @return [void]
def register_schema
JSI.schema_registry.register(self)
SchemaSet[self].new_jsi(instance, **kw)
end

# does this schema itself describe a schema?
Expand Down Expand Up @@ -595,7 +592,7 @@ def validate_schema!
end

# schema resources which are ancestors of any subschemas below this schema.
# this may include this JSI if this is a schema resource root.
# this may include this schema if this is a schema resource root.
# @private
# @return [Array<JSI::Schema>]
def jsi_subschema_resource_ancestors
Expand Down
16 changes: 14 additions & 2 deletions lib/jsi/schema_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,23 @@ def register(resource)
nil
end

# takes a URI identifying a schema to be loaded by the given block
# takes a URI identifying a resource to be loaded by the given block
# when a reference to the URI is followed.
#
# for example:
#
# JSI.schema_registry.autoload_uri('http://example.com/schema.json') do
# JSI.new_schema({
# '$schema' => 'http://json-schema.org/draft-07/schema#',
# '$id' => 'http://example.com/schema.json',
# 'title' => 'my schema',
# })
# end
#
# the block would normally load JSON from the filesystem or similar.
#
# @param uri [Addressable::URI]
# @yieldreturn [JSI::Base] a document containing the schema identified by the given uri
# @yieldreturn [JSI::Base] a JSI instance containing the resource identified by the given uri
# @return [void]
def autoload_uri(uri, &block)
uri = Addressable::URI.parse(uri)
Expand Down
4 changes: 3 additions & 1 deletion lib/jsi/schema_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ def new_jsi(instance,
)
applied_schemas = inplace_applicator_schemas(instance)

JSI::SchemaClasses.class_for_schemas(applied_schemas).new(instance,
jsi = JSI::SchemaClasses.class_for_schemas(applied_schemas).new(instance,
jsi_schema_base_uri: uri,
)

jsi
end

# a set of inplace applicator schemas of each schema in this set which apply to the given instance.
Expand Down
4 changes: 2 additions & 2 deletions lib/jsi/simple_wrap.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# frozen_string_literal: true

module JSI
SimpleWrap = JSI::JSONSchemaOrgDraft06.new_schema({
SimpleWrap = JSI::JSONSchemaOrgDraft06.new_schema_module({
"additionalProperties": {"$ref": "#"},
"items": {"$ref": "#"}
}).jsi_schema_module
})

# SimpleWrap is a JSI schema module which recursively wraps nested structures
module SimpleWrap
Expand Down
2 changes: 1 addition & 1 deletion lib/jsi/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def deep_stringify_symbol_keys(object)
# returns the param if it is already that, otherwise initializes and freezes such a Set.
#
# @param modules [Set, Enumerable] the object to ensure becomes a frozen Set of Modules
# @return [SchemaSet] the given SchemaSet, or a SchemaSet initialized from the given Enumerable
# @return [Set] frozen Set containing the given modules
# @raise [ArgumentError] when the modules param is not an Enumerable
# @raise [Schema::NotASchemaError] when the modules param contains objects which are not Schemas
def ensure_module_set(modules)
Expand Down
2 changes: 1 addition & 1 deletion lib/jsi/validation/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def child_subschema_validate(subschema_ptr, subinstance_ptr)
end

# @param other_result [JSI::Validation::Result]
# @return [Void]
# @return [void]
def merge_schema_issues(other_result)
unless validate_only
# schema_issues are always merged from subschema results (not depending on validation results)
Expand Down
Loading

0 comments on commit 9caae90

Please sign in to comment.