diff --git a/CHANGELOG.md b/CHANGELOG.md index ae6ca989..22075691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Users may have to change how they access the system ### Corrected Bugs +* Fixed: [Behaviour of Empty LIST Input in RESTCONF JSON #166](https://github.com/clicon/clixon/issues/166) * Netconf split lines input (input fragments) fixed * Netconf input split on several lines, eg using stdin: "\nfoo]]>]]>" could under some circumstances be split so that only "]]>]]>" be properly processed. This could also happen to a socket receiving a sub-string and then after a delay receive the rest. * Fixed by storing residue and add that to the input string if later input is received on the same socket. diff --git a/lib/src/clixon_json_parse.y b/lib/src/clixon_json_parse.y index 6fdf7913..9ebbf30d 100644 --- a/lib/src/clixon_json_parse.y +++ b/lib/src/clixon_json_parse.y @@ -133,6 +133,13 @@ object. #include "clixon_json_parse.h" +/* Enable for debugging, steals some cycles otherwise */ +#if 0 +#define _PARSE_DEBUG(s) clicon_debug(1,(s)) +#else +#define _PARSE_DEBUG(s) +#endif + extern int clixon_json_parseget_lineno (void); /* @@ -239,56 +246,63 @@ json_current_body(clixon_json_yacc *jy, return retval; } +static int +json_empty_list(clixon_json_yacc *jy) +{ + xml_rm(jy->jy_current); + xml_free(jy->jy_current); + jy->jy_current = NULL; + return 0; +} %} %% - - /* */ /* top: json -> value is also possible */ -json : value J_EOF { clicon_debug(3,"json->object"); YYACCEPT; } +json : value J_EOF { _PARSE_DEBUG("json->value"); YYACCEPT; } ; -value : J_TRUE { json_current_body(_JY, "true");} - | J_FALSE { json_current_body(_JY, "false");} - | J_NULL { json_current_body(_JY, NULL);} - | object - | array - | number { json_current_body(_JY, $1); free($1);} - | string { json_current_body(_JY, $1); free($1);} +value : J_TRUE { json_current_body(_JY, "true"); _PARSE_DEBUG("value->TRUE");} + | J_FALSE { json_current_body(_JY, "false"); _PARSE_DEBUG("value->FALSE");} + | J_NULL { json_current_body(_JY, NULL); _PARSE_DEBUG("value->NULL");} + | object { _PARSE_DEBUG("value->object"); } + | array { _PARSE_DEBUG("value->array"); } + | number { json_current_body(_JY, $1); free($1); _PARSE_DEBUG("value->number");} + | string { json_current_body(_JY, $1); free($1); _PARSE_DEBUG("value->string");} ; -object : '{' '}' { clicon_debug(3,"object->{}");} - | '{' objlist '}' { clicon_debug(3,"object->{ objlist }");} +object : '{' '}' { _PARSE_DEBUG("object->{}"); _PARSE_DEBUG("object->{}");} + | '{' objlist '}' { _PARSE_DEBUG("object->{ objlist }"); _PARSE_DEBUG("object->{ objlist }");} ; -objlist : pair { clicon_debug(3,"objlist->pair");} - | objlist ',' pair { clicon_debug(3,"objlist->objlist , pair");} +objlist : pair { _PARSE_DEBUG("objlist->pair");} + | objlist ',' pair { _PARSE_DEBUG("objlist->objlist , pair");} ; pair : string { json_current_new(_JY, $1);free($1);} ':' - value { json_current_pop(_JY);}{ clicon_debug(3,"pair->string : value");} + value { json_current_pop(_JY);}{ _PARSE_DEBUG("pair->string : value");} ; -array : '[' ']' - | '[' valuelist ']' +array : '[' ']' { json_empty_list(_JY); _PARSE_DEBUG("array->[]"); } + | '[' valuelist ']' { _PARSE_DEBUG("array->[ valuelist ]"); } ; -valuelist : value - | valuelist { if (json_current_clone(_JY)< 0) _YYERROR("stack?");} ',' value +valuelist : value { _PARSE_DEBUG("valuelist->value"); } + | valuelist { if (json_current_clone(_JY)< 0) _YYERROR("stack?");} + ',' value { _PARSE_DEBUG("valuelist->valuelist , value");} ; /* quoted string */ -string : J_DQ ustring J_DQ { clicon_debug(3,"string->\" ustring \"");$$=$2; } - | J_DQ J_DQ { clicon_debug(3,"string->\" ustring \"");$$=strdup(""); } +string : J_DQ ustring J_DQ { _PARSE_DEBUG("string->\" ustring \"");$$=$2; } + | J_DQ J_DQ { _PARSE_DEBUG("string->\" \"");$$=strdup(""); } ; -/* unquoted string */ +/* unquoted string: can be optimized by reading whole string in lex */ ustring : ustring J_CHAR { int len = strlen($1); diff --git a/test/test_json.sh b/test/test_json.sh index ee5aaec9..747c1a31 100755 --- a/test/test_json.sh +++ b/test/test_json.sh @@ -59,9 +59,14 @@ expecteofx "$clixon_util_json" 0 '{"foo": -23}' "-23" new "json parse to json" # should be {"foo": -23} expecteofx "$clixon_util_json -j" 0 '{"foo": -23}' '{"foo":"-23"}' -new "json parse list xml" +new "json parse list to xml" expecteofx "$clixon_util_json" 0 '{"a":[0,1,2,3]}' "0123" +# See test_restconf.sh +new "json parse empty list to xml" +expecteofx "$clixon_util_json" 0 '{"a":[]}' " +" # XXX empty + new "json parse list json" # should be {"a":[0,1,2,3]} expecteofx "$clixon_util_json -j" 0 '{"a":[0,1,2,3]}' '{"a":"0"}{"a":"1"}{"a":"2"}{"a":"3"}' diff --git a/test/test_restconf.sh b/test/test_restconf.sh index ca421f9e..58854c4a 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -264,6 +264,10 @@ testrun() new "restconf Add subtree eth/0/0 to datastore using POST" expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $proto://$addr/restconf/data/ietf-interfaces:interfaces" +# See test_json.sh + new "restconf empty list" + expectpart "$(curl $CURLOPTS -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:route":{"ipv4":[]}}' $proto://$addr/restconf/data)" 0 'HTTP/1.1 201 Created' "Location: $proto://$addr/restconf/data/clixon-example:route" + new "restconf Re-add subtree eth/0/0 which should give error" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://$addr/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"data-exists","error-severity":"error","error-message":"Data already exists; cannot create new resource"}}}'