From 84dc70448a63b0444bbccde2eb5cd42afbc52afe Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Tue, 7 Jul 2026 09:26:54 +0900 Subject: [PATCH 1/4] Config: accept a bare scalar for an array option in YAML syntax In YAML config a single scalar value for an array option (for example retryable_response_codes: 503) reached Config.array_value as an Integer and was rejected with "array required but got 503", while the classic syntax retryable_response_codes 503 works because it arrives as the String "503" and gets split into ["503"]. Wrap a non-String, non-Array, non-Hash scalar into a one-element array so both syntaxes behave the same. Hash still raises as before. Fixes #5149 Signed-off-by: Arpit Jain --- lib/fluent/config/types.rb | 4 +++- test/config/test_types.rb | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/fluent/config/types.rb b/lib/fluent/config/types.rb index f0b61fa056..76360b225f 100644 --- a/lib/fluent/config/types.rb +++ b/lib/fluent/config/types.rb @@ -229,8 +229,10 @@ def self.array_value(val, opts = {}, name = nil) param = if val.is_a?(String) val.start_with?('[') ? JSON.parse(val, Fluent::DEFAULT_JSON_PARSE_OPTIONS) : val.strip.split(/\s*,\s*/) - else + elsif val.is_a?(Array) || val.is_a?(Hash) val + else + [val] end if param.class != Array raise ConfigError, "array required but got #{val.inspect}" diff --git a/test/config/test_types.rb b/test/config/test_types.rb index 3a6eb8f4ce..0c46b24a5c 100644 --- a/test/config/test_types.rb +++ b/test/config/test_types.rb @@ -375,7 +375,10 @@ class TestConfigTypes < ::Test::Unit::TestCase "space in a value w/o qupte" => [["a a","b","c"], 'a a,b,c', {}], "integers" => [[1,2,1], '[1,2,1]', {}], "value_type: :integer w/ quote" => [[1,2,1], '["1","2","1"]', {value_type: :integer}], - "value_type: :integer w/o quote" => [[1,2,1], '1,2,1', {value_type: :integer}]) + "value_type: :integer w/o quote" => [[1,2,1], '1,2,1', {value_type: :integer}], + "bare integer (YAML scalar)" => [[503], 503, {}], + "bare integer w/ value_type" => [[503], 503, {value_type: :integer}], + "already an array is untouched" => [[503], [503], {}]) test 'array' do |(expected, val, opts)| assert_equal(expected, Config::ARRAY_TYPE.call(val, opts)) end From 5c314d9150ae7587d81cdc740ed4bd4241c01fa4 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Tue, 7 Jul 2026 12:21:56 +0900 Subject: [PATCH 2/4] Config: test that a hash still raises for an array option Add a test case asserting Config::ARRAY_TYPE keeps raising Fluent::ConfigError with the "array required but got ..." message for a hash input, so the scalar-wrapping change does not accidentally accept a mapping under an array option. Covers a hash with and without value_type. Signed-off-by: Arpit Jain --- test/config/test_types.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/config/test_types.rb b/test/config/test_types.rb index 0c46b24a5c..b09490cbea 100644 --- a/test/config/test_types.rb +++ b/test/config/test_types.rb @@ -383,6 +383,14 @@ class TestConfigTypes < ::Test::Unit::TestCase assert_equal(expected, Config::ARRAY_TYPE.call(val, opts)) end + data("hash" => [{"key" => "value"}, {}], + "hash w/ value_type" => [{"key" => "1"}, {value_type: :integer}]) + test 'array w/ hash still raises' do |(val, opts)| + assert_raise(Fluent::ConfigError.new("array required but got #{val.inspect}")) do + Config::ARRAY_TYPE.call(val, opts) + end + end + data('["1","2"]' => [["1","2"], '["1","2"]'], '["3"]' => [["3"], '["3"]']) test 'array w/ default values' do |(expected, val)| From 48e587c6e91b617df7c9e7436eb67f4958206122 Mon Sep 17 00:00:00 2001 From: arpitjain099 Date: Tue, 7 Jul 2026 19:35:10 +0900 Subject: [PATCH 3/4] Config: narrow array scalar wrapping to real YAML scalar types Limit the bare-scalar wrapping in Config.array_value to the scalar types Psych/YAML actually produces here (Numeric, true, false; nil is handled by the early return). Any other type such as a Symbol, Time, or Hash now falls through and still raises "array required", preserving the previous defensive rejection of unexpected values. Add tests covering bare float/true/false wrapping and that a Symbol and a Time still raise Fluent::ConfigError alongside the existing Hash case. Signed-off-by: arpitjain099 --- lib/fluent/config/types.rb | 10 ++++++++-- test/config/test_types.rb | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/fluent/config/types.rb b/lib/fluent/config/types.rb index 76360b225f..2fe5cd4c8e 100644 --- a/lib/fluent/config/types.rb +++ b/lib/fluent/config/types.rb @@ -229,10 +229,16 @@ def self.array_value(val, opts = {}, name = nil) param = if val.is_a?(String) val.start_with?('[') ? JSON.parse(val, Fluent::DEFAULT_JSON_PARSE_OPTIONS) : val.strip.split(/\s*,\s*/) - elsif val.is_a?(Array) || val.is_a?(Hash) + elsif val.is_a?(Array) val - else + elsif val.is_a?(Numeric) || val == true || val == false + # Wrap only the bare scalars that Psych/YAML actually produces + # here (nil is handled by the early return above). Any other + # type (Hash, Symbol, Time, arbitrary objects) falls through and + # still raises "array required" below. [val] + else + val end if param.class != Array raise ConfigError, "array required but got #{val.inspect}" diff --git a/test/config/test_types.rb b/test/config/test_types.rb index b09490cbea..8e7691c44d 100644 --- a/test/config/test_types.rb +++ b/test/config/test_types.rb @@ -378,14 +378,19 @@ class TestConfigTypes < ::Test::Unit::TestCase "value_type: :integer w/o quote" => [[1,2,1], '1,2,1', {value_type: :integer}], "bare integer (YAML scalar)" => [[503], 503, {}], "bare integer w/ value_type" => [[503], 503, {value_type: :integer}], + "bare float (YAML scalar)" => [[1.5], 1.5, {}], + "bare true (YAML scalar)" => [[true], true, {}], + "bare false (YAML scalar)" => [[false], false, {}], "already an array is untouched" => [[503], [503], {}]) test 'array' do |(expected, val, opts)| assert_equal(expected, Config::ARRAY_TYPE.call(val, opts)) end data("hash" => [{"key" => "value"}, {}], - "hash w/ value_type" => [{"key" => "1"}, {value_type: :integer}]) - test 'array w/ hash still raises' do |(val, opts)| + "hash w/ value_type" => [{"key" => "1"}, {value_type: :integer}], + "symbol" => [:foo, {}], + "time" => [Time.at(0), {}]) + test 'array w/ unexpected type still raises' do |(val, opts)| assert_raise(Fluent::ConfigError.new("array required but got #{val.inspect}")) do Config::ARRAY_TYPE.call(val, opts) end From 2abefb620c00e6de48e3de0a4f2fc8e3dc5a33b1 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Wed, 8 Jul 2026 22:05:52 +0900 Subject: [PATCH 4/4] Add YAML pipeline tests for scalar and sequence array parameters Signed-off-by: Arpit Jain --- test/config/test_yaml_parser.rb | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/config/test_yaml_parser.rb b/test/config/test_yaml_parser.rb index 143d5aa5ff..c563c78ac3 100644 --- a/test/config/test_yaml_parser.rb +++ b/test/config/test_yaml_parser.rb @@ -1,5 +1,6 @@ require 'helper' require 'fluent/config/yaml_parser' +require 'fluent/configurable' require 'socket' require 'json' require 'date' @@ -558,4 +559,38 @@ def test_yaml_values assert_equal(:bar, config.elements[0]['symbol2']) assert_equal(/^$/, config.elements[0]['regexp']) end + + class ArrayParamOutput + include Fluent::Configurable + + config_param :retryable_response_codes, :array, value_type: :integer, default: nil + end + + sub_test_case 'array parameter' do + def test_array_param_accepts_bare_scalar + write_config "#{TMP_DIR}/test_array_param_accepts_bare_scalar.yaml", <<~EOS + config: + - match: + $tag: "**" + $type: http + retryable_response_codes: 503 + EOS + config = Fluent::Config::YamlParser.parse("#{TMP_DIR}/test_array_param_accepts_bare_scalar.yaml") + target = ArrayParamOutput.new.configure(config.elements[0]) + assert_equal([503], target.retryable_response_codes) + end + + def test_array_param_accepts_sequence + write_config "#{TMP_DIR}/test_array_param_accepts_sequence.yaml", <<~EOS + config: + - match: + $tag: "**" + $type: http + retryable_response_codes: [502, 503] + EOS + config = Fluent::Config::YamlParser.parse("#{TMP_DIR}/test_array_param_accepts_sequence.yaml") + target = ArrayParamOutput.new.configure(config.elements[0]) + assert_equal([502, 503], target.retryable_response_codes) + end + end end