Last active
May 23, 2020 18:40
-
-
Save felixyz/62130f9f6b9aa309c3ddfb9fd534a0fc to your computer and use it in GitHub Desktop.
dry-validation form => json schema
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module DryJsonSchema | |
# Transforms a dry-validation form to json-schema-compatible objects (hash or array) | |
# Usage: | |
# DryJsonSchema::Converter.(my_form) | |
# => { :type => :object, | |
# :properties => { :abc => { :type => :integer }, | |
# :xyz => { :type => :integer } | |
# }, | |
# :required => [:abc]} | |
class Converter | |
class << self | |
def call(dry_schema, params = nil, &block) | |
properties, required_keys = convert_schema dry_schema, params | |
yield_parameters properties, required_keys, block if block_given? | |
{ | |
type: :object, | |
properties: properties, | |
required: required_keys, | |
additionalProperties: false | |
} | |
end | |
private | |
def yield_parameters(parameters, required_keys, block) | |
parameters.each do |name, properties| | |
required = required_keys.include? name | |
block.(name, properties.merge(required: required)) | |
end | |
end | |
def convert_schema(dry_schema, params) | |
properties = Hash.new { |h, k| h[k] = {} } | |
initial = { properties: properties, required_keys: [] } | |
extracted = dry_schema | |
.rules | |
.select { |rule_key, _| params.nil? || params.include?(rule_key) } | |
.each_with_object(initial) do |(rule_key, root), acc| | |
required, definition = required_and_definition(root) | |
acc[:properties][rule_key] = definition | |
acc[:required_keys] << rule_key if required | |
end | |
[extracted[:properties], extracted[:required_keys]] | |
end | |
def required_and_definition(root) | |
case root | |
when Dry::Logic::Operations::And | |
[true, property_definition(root.right)] | |
when Dry::Logic::Operations::Implication | |
[false, property_definition(root.right)] | |
end | |
end | |
# In the future, this could return *both* enum and type objects, along with others | |
def property_definition(rule) | |
return array_definition rule if array?(rule) | |
return enum_definition rule if enum?(rule) | |
return pattern_definition rule if pattern?(rule) | |
type_definition rule | |
end | |
def array_definition(rule) | |
type = type_definition rule.left | |
item_type = type_definition rule.right.rule | |
type.merge(items: item_type) | |
end | |
def enum_definition(rule) | |
{ | |
enum: rule.right.rule.options[:args].flatten, | |
type: :string # Not good enough | |
} | |
end | |
def pattern_definition(rule) | |
{ type: :string, pattern: rule.right.rule.options[:args] } | |
end | |
def type_definition(rule) | |
case rule | |
when Dry::Logic::Operations::Implication | |
type = [:null, type_from_predicate(rule.right.rule)] | |
when Dry::Logic::Operations::And | |
type = type_from_predicate(rule.right.rule) | |
end | |
{ type: type } | |
end | |
TYPES = { | |
array?: :array, | |
bool?: :boolean, | |
date_time?: :string, | |
decimal?: :number, | |
float?: :number, | |
int?: :integer, | |
str?: :string | |
}.freeze | |
def type_from_predicate(predicate) | |
name = predicate.name | |
raise ArgumentError, "Unknown type: #{name}" unless TYPES.keys.include? name | |
TYPES[name] | |
end | |
def array?(rule) | |
rule.left.is_a? Dry::Logic::Operations::And | |
end | |
def enum?(rule) | |
rule.right.rule.name == :included_in? | |
end | |
def pattern?(rule) | |
rule.right.rule.name == :format? | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment