Repository: bitcoin/bitcoin
File: src/bitcoin-cli.cpp (C++)

Code before:
            return " testnet";
        case ChainType::SIGNET:
            return " signet";
        case ChainType::REGTEST:
            return " regtest";
        case ChainType::MAIN:
            return "";
        }
        assert(false);
    }
    std::string PingTimeToString(double seconds) const
    {
        if (seconds < 0) return "";
        const double milliseconds{round(1000 * seconds)};
        return milliseconds > 999999 ? "-" : ToString(milliseconds);
    }
    std::string ConnectionTypeForNetinfo(const std::string& conn_type) const
    {
        if (conn_type == "outbound-full-relay") return "full";
        if (conn_type == "block-relay-only") return "block";
        if (conn_type == "manual" || conn_type == "feeler") return conn_type;
        if (conn_type == "addr-fetch") return "addr";
        return "";
    }
    std::string FormatServices(const UniValue& services)
    {
        std::string str;
        for (size_t i = 0; i < services.size(); ++i) {
            const std::string s{services[i].get_str()};
            str += s == "NETWORK_LIMITED" ? 'l' : s == "P2P_V2" ? '2' : ToLower(s[0]);
        }
        return str;
    }
    std::string ServicesList(const UniValue& services)
    {
        std::vector<std::string> v;
        for (size_t i = 0; i < services.size(); ++i) {
            std::string s{ToLower((services[i].get_str()))};
            std::ranges::replace(s, '_', ' ');
            v.push_back(s);
        }
        return Join(v, ", ");
    }

public:
    static constexpr int ID_PEERINFO = 0;
    static constexpr int ID_NETWORKINFO = 1;

    UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
    {
        if (!args.empty()) {

Review comment (refactor):
We're currently getting each string, recreating it as lowercase, iterating it again for potential replacement, adding it to a vector, which we're recreating at the end again as a string.

We could refactor it slightly to be similar to `FormatServices`, by doing the lowercase and replace operations once on the final joined string instead of processing each string individually:
```suggestion
    static std::string ServicesList(const UniValue& services)
    {
        std::string res{services.size() ? services[0].get_str() : ""};
        for (size_t i{1}; i < services.size(); ++i) res += ", " + services[i].get_str();
        for (auto& c: res) c = (c == '_' ? ' ' : ToLower(c));
        return res;
    }
```

Which constructs the final string directly, sanitizing it in one separate step.

Or if you prefer the sorted alternative I suggested above:
```suggestion
    static std::string ServicesList(const UniValue& services)
    {
        std::set<std::string> sorted;
        for (auto& s :  services.getValues()) sorted.emplace(s.get_str());
        auto res{Join(sorted, ", ")};
        for (auto& c : res) c = (c == '_' ? ' ' : ToLower(c));
        return res;
    }
```

----

But if you decide to keep the original, consider the following nits:

Nit - extra parenthesis:
```C++
std::string s{ToLower(services[i].get_str())};
```
Nit - consistent brace init:
```C++
for (size_t i{0}; i < services.size(); ++i) {
```
Nit - can be made static:
```C++
static std::string ServicesList(const UniValue& services)
```
Nit - we can iterate the values directly:
```C++
for (auto& s : services.getValues())
```


Code after:
        if (conn_type == "outbound-full-relay") return "full";
        if (conn_type == "block-relay-only") return "block";
        if (conn_type == "manual" || conn_type == "feeler") return conn_type;
        if (conn_type == "addr-fetch") return "addr";
        return "";
    }
    std::string FormatServices(const UniValue& services)
    {
        std::string str;
        for (size_t i = 0; i < services.size(); ++i) {
            const std::string s{services[i].get_str()};
            str += s == "NETWORK_LIMITED" ? 'l' : s == "P2P_V2" ? '2' : ToLower(s[0]);
        }
        return str;
    }
    static std::string ServicesList(const UniValue& services)
    {
        std::string str{services.size() ? services[0].get_str() : ""};
        for (size_t i{1}; i < services.size(); ++i) {
            str += ", " + services[i].get_str();
        }
        for (auto& c: str) {
            c = (c == '_' ? ' ' : ToLower(c));
        }
        return str;
    }

public:
    static constexpr int ID_PEERINFO = 0;
    static constexpr int ID_NETWORKINFO = 1;

    UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
    {
        if (!args.empty()) {
            uint8_t n{0};
            if (const auto res{ToIntegral<uint8_t>(args.at(0))}) {
                n = *res;
                m_details_level = std::min(n, NETINFO_MAX_LEVEL);
            } else {
                throw std::runtime_error(strprintf("invalid -netinfo level argument: %s\nFor more information, run: bitcoin-cli -netinfo help", args.at(0)));
            }
            if (args.size() > 1) {
                if (std::string_view s{args.at(1)}; n && (s == "o" || s == "outonly")) {
                    m_outbound_only_selected = true;
                } else if (n) {
                    throw std::runtime_error(strprintf("invalid -netinfo outonly argument: %s\nFor more information, run: bitcoin-cli -netinfo help", s));
                } else {
                    throw std::runtime_error(strprintf("invalid -netinfo outonly argument: %s\nThe outonly argument is only valid for a level greater than 0 (the first argument). For more information, run: bitcoin-cli -netinfo help", s));
                }
            }
        }