Repository: bitcoin/bitcoin
File: src/rpc/client.cpp (C++)

Code before:
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
    UniValue params(UniValue::VOBJ);
    UniValue positional_args{UniValue::VARR};

    for (std::string_view s: strParams) {
        size_t pos = s.find('=');
        if (pos == std::string_view::npos) {
            positional_args.push_back(ParseParam(ParamFromPosition(strMethod, positional_args.size()), s));
            continue;
        }

        std::string name{s.substr(0, pos)};
        std::string_view value{s.substr(pos+1)};

        const CRPCConvertParam* named_param{ParamFromName(strMethod, name)};
        if (!named_param) {
              /**
             * This handles two cases where a named parameter (param=value) is not recognized:
             *
             * 1. The RPC method was listed in the vRPCConvertParams table with a given index, but the parameter is not a known parameter
             *    and has a param format STRING; in such cases we pass the whole parameter as positional.
             *
             * 2. The RPC method was listed in the vRPCConvertParams table with a given index, but the parameter is not a known parameter
             *    and has a param format JSON; in such cases we first parse the whole parameter as JSON and then treat as positional.
             */
            const CRPCConvertParam* positional_param = ParamFromPosition(strMethod, positional_args.size());
            UniValue parsed_value;
            if (positional_param && positional_param->format == ParamFormat::JSON && parsed_value.read(s)) {
                positional_args.push_back(std::move(parsed_value));
                continue;
            } else if (positional_param && positional_param->format == ParamFormat::STRING) {
                positional_args.push_back(s);
                continue;
            }
        }

        // Intentionally overwrite earlier named values with later ones as a
        // convenience for scripts and command line users that want to merge
        // options.
        params.pushKV(name, ParseParam(named_param, value));
    }

    if (!positional_args.empty()) {
        // Use pushKVEnd instead of pushKV to avoid overwriting an explicit
        // "args" value with an implicit one. Let the RPC server handle the
        // request as given.
        params.pushKVEnd("args", std::move(positional_args));
    }

    return params;

Review comment (suggestion):
In commit "rpc: Handle -named argument parsing where '=' character is used" (fbeacfabe802c24b427bee2cd82da1f12ef9899d)

Maybe consider deleting this comment. It seems like it is mostly just repeating what code is doing without explaining why it is doing these things. If you do think a comment would be helpful here, probably the most useful thing might be some examples of the things being parsed. I suggested some documentation with examples in da919fb63c519ac91c9704c58f1bbe522d7a8879 ([tag](https://github.com/ryanofsky/bitcoin/commits/review.32821.7-edit.3)) in case it might be useful.

Code after:
 * positional arguments without names. The syntax is inherently ambiguous if
 * names are omitted and values contain '=', so a heuristic is used to
 * disambiguate:
 *
 * - Arguments that do not contain '=' are treated as positional parameters.
 *
 * - Arguments that do contain '=' are assumed to be named parameters in
 *   NAME=VALUE format except for two special cases:
 *
 *   1. The case where NAME is not a known parameter name, and the next
 *      positional parameter requires a JSON value, and the argument parses as
 *      JSON. E.g. ["list", "with", "="].
 *
 *   2. The case where NAME is not a known parameter name and the next
 *      positional parameter requires a string value. E.g. "my=wallet".
 *
 * For example, the command `bitcoin-cli -named createwallet "my=wallet"`,
 * the parser initially sees "my=wallet" and attempts to process it as a
 * parameter named "my". When it finds that "my" is not a valid named parameter
 * parameter for this method, it falls back to checking the rule for the
 * next available positional parameter (index 0). Because it finds the rule
 * that this parameter is a ParamFormat::STRING, it correctly treats the entire
 * "my=wallet" as a single positional string, successfully creating a
 * wallet with that literal name.
 */
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
{
    UniValue params(UniValue::VOBJ);
    UniValue positional_args{UniValue::VARR};

    for (std::string_view s: strParams) {
        size_t pos = s.find('=');
        if (pos == std::string_view::npos) {
            positional_args.push_back(ParseParam(rpc_convert::FromPosition(strMethod, positional_args.size()), s));
            continue;
        }

        std::string name{s.substr(0, pos)};
        std::string_view value{s.substr(pos+1)};

        const CRPCConvertParam* named_param{rpc_convert::FromName(strMethod, name)};
        if (!named_param) {
            const CRPCConvertParam* positional_param = rpc_convert::FromPosition(strMethod, positional_args.size());
            UniValue parsed_value;
            if (positional_param && positional_param->format == ParamFormat::JSON && parsed_value.read(s)) {
                positional_args.push_back(std::move(parsed_value));
                continue;
            } else if (positional_param && positional_param->format == ParamFormat::STRING) {
                positional_args.push_back(s);
                continue;
            }