-
-
Notifications
You must be signed in to change notification settings - Fork 243
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
improve handling of fragments #382
base: master
Are you sure you want to change the base?
Conversation
lib/json-schema/schema.rb
Outdated
@@ -6,6 +6,8 @@ class Schema | |||
attr_accessor :schema, :uri, :validator | |||
|
|||
def initialize(schema,uri,parent_validator=nil) | |||
raise ArgumentError, "schema must be a hash; got: #{schema.inspect}" unless schema.is_a?(Hash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this change is kind of unrelated, but it results in a better error message when an array is passed as the schema
argument (as the previous fragment-handling code was prone to do) - before the error message would end up being TypeError: no implicit conversion of String into Integer
when trying to call @schema['id']
below, which is rather obscure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying (slowly) to move the codebase away from explicit type checking towards duck typing. Could you please alter this check from schema.is_a?(Hash)
to schema.respond_to?(:to_hash)
, and the line below from @schema = schema
to @schema = schema.to_hash
?
# If the :fragment option is set, try and validate against the fragment | ||
if opts[:fragment] | ||
@base_schema = schema_from_fragment(@base_schema, opts[:fragment]) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is moved before the validate_schema block so that the correct schema, at the fragment pointer, is validated
if match | ||
parse_pointer(match.post_match) | ||
else | ||
raise(PointerSyntaxError, "Invalid fragment syntax in #{fragment.inspect}: fragment must begin with #") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've also tried to improve the information given in these error messages, as the previous "Invalid fragment resolution for :fragment option"
was not very useful to debug
end | ||
|
||
def schema_from_fragment(base_schema, fragment) | ||
schema_uri = base_schema.uri | ||
fragments = fragment.split("/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another fix I just noticed: this should be split("/", -1)
; as it is empty string keys would be mishandled. #/foo//
should parse to fragments ['foo', '', '']
and in json {"foo": {"": {"": 7}}}
should evaluate to 7
.
added #383 and rebased this against it |
this thing alive? |
@notEthan Sorry, I've been immensely busy at work and at home for several weeks. I'll take a proper look at this once things calm down here |
sounds good, thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this. It seems to be mostly refactoring with a few targetted changes?
I've requested a few small changes. Could you also please add those bug-fixes to the CHANGELOG.md? (Ideally in the same commits as the code changes they refer to)
I believe there are other places in the code where we use pointers but I'm happy to update those later myself (unless you have time to do it yourself, in which case I'd be very grateful!)
lib/json-schema/schema.rb
Outdated
@@ -6,6 +6,8 @@ class Schema | |||
attr_accessor :schema, :uri, :validator | |||
|
|||
def initialize(schema,uri,parent_validator=nil) | |||
raise ArgumentError, "schema must be a hash; got: #{schema.inspect}" unless schema.is_a?(Hash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying (slowly) to move the codebase away from explicit type checking towards duck typing. Could you please alter this check from schema.is_a?(Hash)
to schema.respond_to?(:to_hash)
, and the line below from @schema = schema
to @schema = schema.to_hash
?
# - :reference_tokens - the representation is an array of tokens referencing a path in a document | ||
def initialize(type, representation) | ||
@type = type | ||
if type == :reference_tokens |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't see anywhere where this is used. Is it needed? (And if not we can remove the last clause in representation_s
too)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used it in code that I have been working on that relies on this Pointer class - I use the array of reference tokens there. dealing with the string (pointer or fragment) in code is less useful than the tokens. I'd like this to be part of the API of this class.
"#<#{self.class.name} #{@type} = #{representation_s}>" | ||
end | ||
|
||
def representation_s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make this and the methods below private, to remove them from the public api of the class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made this method private but #pointer and #fragment should be public. I've added method documents for those.
# - :fragment - the representation is a fragment containing a pointer (starting with #) | ||
# - :pointer - the representation is a pointer (starting with /) | ||
# - :reference_tokens - the representation is an array of tokens referencing a path in a document | ||
def initialize(type, representation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the more I think about this the more I feel that this aught to be refactored from a single Pointer class, into a Pointer parent class and a subclass for each type (eg. FragmentPointer and AbsolutePointer maybe?). Right now most instance methods work differently based on the type, and it feels like an instance of the Switch Statements Smell, where polymorphism would work better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started down this path but on further reflection I think I disagree. the class represents the same sort of pointer no matter what the external representation it came from. the only method that is any different, once the external representation has been parsed, is #to_s and that is only to keep track of where it came from - it's not really a part of what the Pointer is, just something I found useful for debugging.
You might need to rebase this, as I've merged a fragment-related bugfix (#217) |
oh my, what a long time I have neglected this. back to it. rebased and addressed some comments. |
if you agree that a Pointer is a Pointer, I hope this is ready to be merged. if you feel strongly that it should be polymorphic, I can do that. |
hmm. test failures on jruby that I'm not getting locally. one looks related to this change, others don't. will address what I can. |
3a6d994
to
127e482
Compare
…e schema of the root is incorrect; it is correct to validate the schema located at the fragment.
… can't be instantiated as a JSON::Schema
…rs that must be escaped in json pointer syntax
… dealing with pointers in fragments or in plain pointer syntax. use this in #schema_from_fragment.
…chema#initialize, use #to_hash
I put a commit fixing a require that was missing, causing failures on jruby, ahead of the content of this PR, it can be merged together or separately |
ping on this, hopefully you have time to look at it and we can bring it home |
Hi @iainbeeston thanks for the great gem! Could you please look at this PR again? Keys with slashes are all over the place in the swagger specs =) |
@iainbeeston is it still maintened? Would it be possible to add more contributors as a lot of PRs are sitting? Thanks! |
I've encountered a few problems dealing with fragments that this PR addresses:
~0
/~1
as defined in https://tools.ietf.org/html/rfc6901 are not handled correctly.I made a class JSON::Schema::Pointer to abstract the parsing and evaluation of pointers, and updated other code around this.