Skip to content

Commit f08c663

Browse files
committed
ResumableParser: accept only keyword arguments
Fix: #1016 (comment) `json` takes option hashes across the board, mostly because its API predates the introduction of keyword arguments. I'd like to change that to only take keyword arguments and error when an unknown argument is passed, but I'm not yet sure of the backward compatibility consequences, so it might wait for the next major. But in the meantime, `ResumableParser` being a new API, it can safely use keyword arguments.
1 parent 9d8efcb commit f08c663

2 files changed

Lines changed: 51 additions & 15 deletions

File tree

ext/json/ext/parser/parser.c

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,8 @@ static VALUE convert_encoding(VALUE source)
19661966
struct parser_config_init_args {
19671967
JSON_ParserConfig *config;
19681968
VALUE self;
1969+
VALUE unknown_keywords;
1970+
bool strict;
19691971
};
19701972

19711973
static void parser_config_wb_write(VALUE self, VALUE *dest, VALUE val)
@@ -2019,27 +2021,43 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
20192021
}
20202022
}
20212023
}
2024+
else if (args->strict) {
2025+
if (!args->unknown_keywords) {
2026+
args->unknown_keywords = rb_obj_hide(rb_ary_new());
2027+
}
2028+
rb_ary_push(args->unknown_keywords, key);
2029+
}
20222030

20232031
return ST_CONTINUE;
20242032
}
20252033

2026-
static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self)
2034+
static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self, bool strict)
20272035
{
20282036
config->max_nesting = 100;
20292037

20302038
struct parser_config_init_args args = {
20312039
.config = config,
20322040
.self = self,
2041+
.strict = strict,
20332042
};
20342043

2035-
if (!NIL_P(opts)) {
2036-
Check_Type(opts, T_HASH);
2037-
if (RHASH_SIZE(opts) > 0) {
2038-
// We assume in most cases few keys are set so it's faster to go over
2039-
// the provided keys than to check all possible keys.
2040-
rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
2041-
}
2044+
if (NIL_P(opts)) return;
2045+
Check_Type(opts, T_HASH);
2046+
if (RHASH_SIZE(opts) == 0) return;
2047+
2048+
// We assume in most cases few keys are set so it's faster to go over
2049+
// the provided keys than to check all possible keys.
2050+
rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
20422051

2052+
if (RB_UNLIKELY(args.unknown_keywords)) {
2053+
if (RARRAY_LEN(args.unknown_keywords) == 1) {
2054+
rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, RARRAY_AREF(args.unknown_keywords, 0));
2055+
}
2056+
else {
2057+
VALUE keywords = rb_ary_join(args.unknown_keywords, rb_utf8_str_new_cstr(", "));
2058+
rb_raise(rb_eArgError, "unknown keywords: %s", RSTRING_PTR(keywords));
2059+
RB_GC_GUARD(keywords);
2060+
}
20432061
}
20442062
}
20452063

@@ -2057,7 +2075,7 @@ static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
20572075
rb_check_frozen(self);
20582076
GET_PARSER_CONFIG;
20592077

2060-
parser_config_init(config, opts, self);
2078+
parser_config_init(config, opts, self, false);
20612079

20622080
return self;
20632081
}
@@ -2153,7 +2171,7 @@ static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts)
21532171
{
21542172
JSON_ParserConfig _config = {0};
21552173
JSON_ParserConfig *config = &_config;
2156-
parser_config_init(config, opts, false);
2174+
parser_config_init(config, opts, Qfalse, false);
21572175

21582176
return cParser_parse(config, Vsource);
21592177
}
@@ -2342,12 +2360,14 @@ static inline JSON_ResumableParser *cResumableParser_get(VALUE self)
23422360
*/
23432361
static VALUE cResumableParser_initialize(int argc, VALUE *argv, VALUE self)
23442362
{
2345-
rb_check_arity(argc, 0, 1);
23462363
rb_check_frozen(self);
2364+
2365+
VALUE opts = Qfalse;
2366+
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "0:", &opts);
23472367
JSON_ResumableParser *parser = cResumableParser_get(self);
23482368

2349-
VALUE opts = argc > 0 ? argv[0] : Qnil;
2350-
parser_config_init(&parser->config, opts, self);
2369+
opts = argc > 0 ? argv[0] : Qnil;
2370+
parser_config_init(&parser->config, opts, self, true);
23512371

23522372
return self;
23532373
}

test/json/resumable_parser_test.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ def setup
99
@parser = new_parser
1010
end
1111

12+
def test_keyword_arguments
13+
new_parser
14+
new_parser({})
15+
new_parser(allow_nan: true)
16+
17+
error = assert_raise(ArgumentError) do
18+
new_parser(doesnt_exist: true, allow_nan: true)
19+
end
20+
assert_equal "unknown keyword: doesnt_exist", error.message
21+
22+
error = assert_raise(ArgumentError) do
23+
new_parser(doesnt_exist: true, allow_nan: true, a: 1, b: 2)
24+
end
25+
assert_equal "unknown keywords: doesnt_exist, a, b", error.message
26+
end
27+
1228
def test_value
1329
refute_predicate @parser, :value?
1430
assert_raise(ArgumentError) { @parser.value }
@@ -444,7 +460,7 @@ def assert_parse_stream(expected, json, parser = @parser)
444460
assert_equal(expected, actual)
445461
end
446462

447-
def new_parser(options = nil)
448-
JSON::ResumableParser.new(options)
463+
def new_parser(...)
464+
JSON::ResumableParser.new(...)
449465
end
450466
end

0 commit comments

Comments
 (0)