dry-schema
dry-schema copied to clipboard
Make JSON schema resilient to any JSON-compatible value as input
The JSON standard defines the following data types
- number (WRT ruby both float and integer shall be supported)
- string
- boolean
- array
- object (represented as Hash in ruby)
null(=nil)
If any JSON schema receives any value of the types above it shouldn't raise an exception at least. Better yet it should provide a helpful error message.
We want this for 1.0.0?
I guess so, it's just about having a sanity check at the beginning of validation, no?
@flash-gordon yes, JSON is a Processor specialization, it supports adding arbitrary steps, it simply passes result-like objects from step to step, and can aggregate errors in this process. This means you should be able to add a special validation step to do your checks. I suspect we'll want to make step API nicer to work with though, but its core functionality is implemented already.
Can we make :float values in JSON schema to be coercible from integers?
Javascript from {a: 1.0} generates JSON {"a": 1}, and then ruby parses this 1 to integer value.
And json schema returns error that value must be float.
Looks like a bug
@aglushkov we can, but it looks like it would be a breaking change. I think it should be possible to use a custom type container to override json's float as a workaround for the time being.
I reckon we could have something like this @flash-gordon :
Dry::Schema.JSON(Array) do
# do your thing
end
Dry::Schema.JSON do
# do your thing
end
API-wise it's not a big deal, though I would expect .JSON and .JSON(Hash) to work the same way, just in case
I mean, no matter how we do this on the user side, dealing with arrays inside dry-schema will be non-trivial
I would propose to introduce the root keyword:
Dry::Schema.JSON do
root.array(:integer)
end
To implement this I would:
- add a special
__root__key to the schema - wrap input with hash before passing to the steps
- filter
__root__out from the result - return an error on "root" schema nesting
- handle a special case on passing "root" schema as value
It's a bit hacky, but still, wdyt? @solnic @flash-gordon
Why not having something like .JSONArray instead? It would be easier to implement IMO
besides, the biggest problem I think is producing error messages, not the DSL part
besides, the biggest problem I think is producing error messages, not the DSL part
I think you can pass a special option to the message_compiler to skip the first node of the ast. Still don't know how this will play with dry-v though.
Here's some food for thought:
require 'dry/schema'
Dry::Schema::ProcessorSteps.prepend(Module.new {
def call(result)
catch(:halt) { super }
result
end
})
class Dry::Schema::MessageCompiler
def visit_unexpected_input(node, opts)
input, * = node
Dry::Schema::Message.new(
path: [nil],
predicate: nil,
text: "we did not expect this",
input: input
)
end
end
schema = Dry::Schema.JSON do
required(:name).filled(:string)
before(:key_coercer) do |result|
unless result.output.is_a?(Hash)
result.add_error([:unexpected_input, [result.output]])
throw :halt
end
end
end
puts schema.("oops").errors.to_h.inspect
# {nil=>["we did not expect this"]}
#
# `nil` represents input itself in error hash
For anyone in the future, before using catch/throw pls consult me :) Maybe we'll be able to find a better solution.
@flash-gordon we can have result.halt that just sets it internally to a failure state and further processing won't happen
@solnic yeahyeah sure, I just mean using catch/throw without a real need may have undesirable consequences. And even though this doesn't seem to be the case here, I'd like to try other options if available