diff --git a/README.rst b/README.rst index c530422b..d250b6a0 100644 --- a/README.rst +++ b/README.rst @@ -649,6 +649,20 @@ override parameters described on resource level. #... end +NestedValidator +------------- + +You can describe nested parameters in depth if you provide a block with +description of nested values. + +.. code:: ruby + + param :comments, Array, :desc => "User comments" do + param :name, String, :desc => "Name of the comment", :required => true + param :comment, String, :desc => "Full comment", :required => true + end + + Adding custom validator ----------------------- diff --git a/lib/apipie/validator.rb b/lib/apipie/validator.rb index f36b3776..3cc3ecc3 100644 --- a/lib/apipie/validator.rb +++ b/lib/apipie/validator.rb @@ -161,8 +161,8 @@ def validate(value) @array.include?(value.class) end - def self.build(param_description, argument, options, proc) - if argument.is_a?(Array) && argument.first.class == Class + def self.build(param_description, argument, options, block) + if argument.is_a?(Array) && argument.first.class == Class && !block.is_a?(Proc) self.new(param_description, argument) end end @@ -273,7 +273,7 @@ def prepare_hash_params # special type of validator: we say that it's not specified - class UndefValidator < Apipie::Validator::BaseValidator + class UndefValidator < BaseValidator def validate(value) true @@ -290,7 +290,7 @@ def description end end - class NumberValidator < Apipie::Validator::BaseValidator + class NumberValidator < BaseValidator def validate(value) self.class.validate(value) @@ -311,7 +311,7 @@ def self.validate(value) end end - class BooleanValidator < Apipie::Validator::BaseValidator + class BooleanValidator < BaseValidator def validate(value) %w[true false].include?(value.to_s) @@ -328,6 +328,41 @@ def description end end + class NestedValidator < BaseValidator + + def initialize(param_description, argument, param_group) + super(param_description) + @validator = Apipie::Validator:: HashValidator.new(param_description, argument, param_group) + @type = argument + end + + def validate(value) + value ||= [] # Rails convert empty array to nil + return false if value.class != Array + value.each do |child| + return false unless @validator.validate(child) + end + true + end + + def process_value(value) + value ||= [] # Rails convert empty array to nil + @values = [] + value.each do |child| + @values << @validator.process_value(child) + end + @values + end + + def self.build(param_description, argument, options, block) + self.new(param_description, block, options[:param_group]) if block.is_a?(Proc) && block.arity == 0 && argument == Array + end + + def description + "Must be an Array of nested elements" + end + end + end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index a2043e0b..7b6e80fe 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -237,6 +237,67 @@ def compare_hashes(h1, h2) assert_response :success end + describe "nested elements" do + + context "with valid input" do + it "should succeed" do + put :update, + { + :id => 5, + :user => { + :name => "root", + :pass => "12345" + }, + :comments => [ + { + :comment => 'comment1' + }, + { + :comment => 'comment2' + } + ] + } + + assert_response :success + end + end + context "with bad input" do + it "should raise an error" do + expect{ put :update, + { + :id => 5, + :user => { + :name => "root", + :pass => "12345" + }, + :comments => [ + { + :comment => 'comment1' + }, + { + :comment => {bad_input: 5} + } + ] + } + }.to raise_error(Apipie::ParamInvalid) + end + end + it "should work with empty array" do + put :update, + { + :id => 5, + :user => { + :name => "root", + :pass => "12345" + }, + :comments => [ + ] + } + + assert_response :success + end + end + end end diff --git a/spec/dummy/app/controllers/users_controller.rb b/spec/dummy/app/controllers/users_controller.rb index 79615616..be1e41d4 100644 --- a/spec/dummy/app/controllers/users_controller.rb +++ b/spec/dummy/app/controllers/users_controller.rb @@ -220,8 +220,11 @@ def create render :text => "OK #{params.inspect}" end - api :PUT, "/users/:id", "Create user" + api :PUT, "/users/:id", "Update an user" param_group :user + param :comments, Array do + param :comment, String + end def update render :text => "OK #{params.inspect}" end