Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global configuration options #170

Merged
merged 17 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 11 additions & 6 deletions lib/json_schemer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@
require 'json_schemer/openapi30/vocab/base'
require 'json_schemer/openapi30/vocab'
require 'json_schemer/openapi'
require 'json_schemer/configuration'
require 'json_schemer/schema'

module JSONSchemer
class UnsupportedMetaSchema < StandardError; end
class UnsupportedOpenAPIVersion < StandardError; end
class UnknownRef < StandardError; end
class UnknownFormat < StandardError; end
Expand Down Expand Up @@ -113,12 +113,9 @@ class InvalidEcmaRegexp < StandardError; end
end

class << self
def schema(schema, meta_schema: draft202012, **options)
def schema(schema, **options)
schema = resolve(schema, options)
unless meta_schema.is_a?(Schema)
meta_schema = META_SCHEMAS_BY_BASE_URI_STR[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
end
Schema.new(schema, :meta_schema => meta_schema, **options)
Schema.new(schema, **options)
end

def valid_schema?(schema, **options)
Expand Down Expand Up @@ -235,6 +232,14 @@ def openapi(document, **options)
OpenAPI.new(document, **options)
end

def configuration
@configuration ||= Configuration.new
end

def configure
omkarmoghe marked this conversation as resolved.
Show resolved Hide resolved
yield configuration
end

private

def resolve(schema, options)
Expand Down
32 changes: 32 additions & 0 deletions lib/json_schemer/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module JSONSchemer
Configuration = Struct.new(
:base_uri, :meta_schema, :vocabulary, :format, :formats, :content_encodings, :content_media_types, :keywords,
:before_property_validation, :after_property_validation, :insert_property_defaults, :property_default_resolver,
:ref_resolver, :regexp_resolver, :output_format, :resolve_enumerators, :access_mode,
keyword_init: true
) do
def initialize(
base_uri: URI('json-schemer://schema'),
meta_schema: Draft202012::BASE_URI.to_s,
vocabulary: nil,
format: true,
formats: {},
content_encodings: {},
content_media_types: {},
keywords: {},
before_property_validation: [],
after_property_validation: [],
insert_property_defaults: false,
property_default_resolver: nil,
ref_resolver: proc { |uri| raise UnknownRef, uri.to_s },
regexp_resolver: 'ruby',
output_format: 'classic',
resolve_enumerators: false,
access_mode: nil
)
super
end
end
end
3 changes: 3 additions & 0 deletions lib/json_schemer/keyword.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ def parse
end

def subschema(value, keyword = nil, **options)
options[:configuration] ||= schema.configuration
options[:base_uri] ||= schema.base_uri
options[:meta_schema] ||= schema.meta_schema
options[:ref_resolver] ||= schema.ref_resolver
options[:regexp_resolver] ||= schema.regexp_resolver
Schema.new(value, self, root, keyword, **options)
end
end
Expand Down
6 changes: 2 additions & 4 deletions lib/json_schemer/openapi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ def initialize(document, **options)
case version
when /\A3\.1\.\d+\z/
@document_schema = JSONSchemer.openapi31_document
json_schema_dialect = document.fetch('jsonSchemaDialect') { OpenAPI31::BASE_URI.to_s }
meta_schema = document.fetch('jsonSchemaDialect') { OpenAPI31::BASE_URI.to_s }
when /\A3\.0\.\d+\z/
@document_schema = JSONSchemer.openapi30_document
json_schema_dialect = OpenAPI30::BASE_URI.to_s
meta_schema = OpenAPI30::BASE_URI.to_s
else
raise UnsupportedOpenAPIVersion, version
end

meta_schema = META_SCHEMAS_BY_BASE_URI_STR[json_schema_dialect] || raise(UnsupportedMetaSchema, json_schema_dialect)

@schema = JSONSchemer.schema(@document, :meta_schema => meta_schema, **options)
end

Expand Down
91 changes: 39 additions & 52 deletions lib/json_schemer/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,13 @@ def original_instance(instance_location)

include Output

DEFAULT_SCHEMA = Draft202012::BASE_URI.to_s.freeze
SCHEMA_KEYWORD_CLASS = Draft202012::Vocab::Core::Schema
VOCABULARY_KEYWORD_CLASS = Draft202012::Vocab::Core::Vocabulary
ID_KEYWORD_CLASS = Draft202012::Vocab::Core::Id
UNKNOWN_KEYWORD_CLASS = Draft202012::Vocab::Core::UnknownKeyword
NOT_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Not
PROPERTIES_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Properties
DEFAULT_BASE_URI = URI('json-schemer://schema').freeze
DEFAULT_FORMATS = {}.freeze
DEFAULT_CONTENT_ENCODINGS = {}.freeze
DEFAULT_CONTENT_MEDIA_TYPES = {}.freeze
DEFAULT_KEYWORDS = {}.freeze
DEFAULT_BEFORE_PROPERTY_VALIDATION = [].freeze
DEFAULT_AFTER_PROPERTY_VALIDATION = [].freeze
DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }

NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
RUBY_REGEXP_RESOLVER = proc { |pattern| Regexp.new(pattern) }
ECMA_REGEXP_RESOLVER = proc { |pattern| Regexp.new(EcmaRegexp.ruby_equivalent(pattern)) }
Expand All @@ -51,37 +43,39 @@ def original_instance(instance_location)
end

attr_accessor :base_uri, :meta_schema, :keywords, :keyword_order
attr_reader :value, :parent, :root, :parsed
attr_reader :value, :parent, :root, :configuration, :parsed
attr_reader :vocabulary, :format, :formats, :content_encodings, :content_media_types, :custom_keywords, :before_property_validation, :after_property_validation, :insert_property_defaults

def initialize(
value,
parent = nil,
root = self,
keyword = nil,
base_uri: DEFAULT_BASE_URI,
meta_schema: nil,
vocabulary: nil,
format: true,
formats: DEFAULT_FORMATS,
content_encodings: DEFAULT_CONTENT_ENCODINGS,
content_media_types: DEFAULT_CONTENT_MEDIA_TYPES,
keywords: DEFAULT_KEYWORDS,
before_property_validation: DEFAULT_BEFORE_PROPERTY_VALIDATION,
after_property_validation: DEFAULT_AFTER_PROPERTY_VALIDATION,
insert_property_defaults: false,
property_default_resolver: nil,
ref_resolver: DEFAULT_REF_RESOLVER,
regexp_resolver: 'ruby',
output_format: 'classic',
resolve_enumerators: false,
access_mode: nil
configuration: JSONSchemer.configuration,
base_uri: configuration.base_uri,
meta_schema: configuration.meta_schema,
vocabulary: configuration.vocabulary,
format: configuration.format,
formats: configuration.formats,
content_encodings: configuration.content_encodings,
content_media_types: configuration.content_media_types,
keywords: configuration.keywords,
before_property_validation: configuration.before_property_validation,
after_property_validation: configuration.after_property_validation,
insert_property_defaults: configuration.insert_property_defaults,
property_default_resolver: configuration.property_default_resolver,
ref_resolver: configuration.ref_resolver,
regexp_resolver: configuration.regexp_resolver,
output_format: configuration.output_format,
resolve_enumerators: configuration.resolve_enumerators,
access_mode: configuration.access_mode
)
@value = deep_stringify_keys(value)
@parent = parent
@root = root
@keyword = keyword
@schema = self
@configuration = configuration
@base_uri = base_uri
@meta_schema = meta_schema
@vocabulary = vocabulary
Expand Down Expand Up @@ -194,16 +188,9 @@ def resolve_ref(uri)
uri.fragment = nil
remote_schema = JSONSchemer.schema(
ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s),
:configuration => configuration,
:base_uri => uri,
:meta_schema => meta_schema,
:format => format,
:formats => formats,
:content_encodings => content_encodings,
:content_media_types => content_media_types,
:keywords => custom_keywords,
:before_property_validation => before_property_validation,
:after_property_validation => after_property_validation,
:property_default_resolver => property_default_resolver,
:ref_resolver => ref_resolver,
:regexp_resolver => regexp_resolver
)
Expand Down Expand Up @@ -352,6 +339,21 @@ def error(formatted_instance_location:, **options)
end
end

def ref_resolver
@ref_resolver ||= @original_ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : @original_ref_resolver
end

def regexp_resolver
@regexp_resolver ||= case @original_regexp_resolver
when 'ecma'
CachedResolver.new(&ECMA_REGEXP_RESOLVER)
when 'ruby'
CachedResolver.new(&RUBY_REGEXP_RESOLVER)
else
@original_regexp_resolver
end
end

def inspect
"#<#{self.class.name} @value=#{@value.inspect} @parent=#{@parent.inspect} @keyword=#{@keyword.inspect}>"
end
Expand All @@ -363,8 +365,8 @@ def parse

if value.is_a?(Hash) && value.key?('$schema')
@parsed['$schema'] = SCHEMA_KEYWORD_CLASS.new(value.fetch('$schema'), self, '$schema')
elsif root == self && !meta_schema
SCHEMA_KEYWORD_CLASS.new(DEFAULT_SCHEMA, self, '$schema')
elsif meta_schema.is_a?(String)
SCHEMA_KEYWORD_CLASS.new(meta_schema, self, '$schema')
end

if value.is_a?(Hash) && value.key?('$vocabulary')
Expand Down Expand Up @@ -408,21 +410,6 @@ def property_default_resolver
@property_default_resolver ||= insert_property_defaults == :symbol ? SYMBOL_PROPERTY_DEFAULT_RESOLVER : DEFAULT_PROPERTY_DEFAULT_RESOLVER
end

def ref_resolver
@ref_resolver ||= @original_ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : @original_ref_resolver
end

def regexp_resolver
@regexp_resolver ||= case @original_regexp_resolver
when 'ecma'
CachedResolver.new(&ECMA_REGEXP_RESOLVER)
when 'ruby'
CachedResolver.new(&RUBY_REGEXP_RESOLVER)
else
@original_regexp_resolver
end
end

def resolve_enumerators!(output)
case output
when Hash
Expand Down