// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® module Crafter.Review:PgWire_impl; import :PgWire; import :Json; import :Text; import std; namespace Crafter::Review::PgWire { Url ParseUrl(std::string_view text) { const Dyn::SplitUrl split = Dyn::UrlSplit(text); Url url; url.scheme = split.scheme; url.host = Dyn::Lower(split.Hostname().value_or("")); const std::optional port = split.Port(); url.port = port && *port != 0 ? *port : 5432; const std::optional user = split.Username(); url.user = Dyn::Unquote(user && !user->empty() ? *user : "postgres"); url.password = Dyn::Unquote(split.Password().value_or("")); const std::string path = split.path.empty() ? "/" : split.path; const std::string database = Dyn::LStrip(path, "/"); url.database = Dyn::Unquote(database.empty() ? "postgres" : database); return url; } std::optional CheckUrl(const Json& url) { if (!url.is_string() || url.get_ref().empty()) return "no database URL"; Url parsed; try { parsed = ParseUrl(url.get_ref()); } catch (const std::invalid_argument& error) { return std::format("not a URL ({})", error.what()); } if (parsed.scheme != "postgres" && parsed.scheme != "postgresql") return "not a postgres:// or postgresql:// URL"; if (!std::ranges::contains(Loopback, parsed.host)) { const std::vector names(Loopback.begin(), Loopback.end()); return std::format("host {} is not this machine ({}): the database under test is a local stack, never a hosted one", parsed.host.empty() ? "(none)" : parsed.host, Dyn::Join(names, ", ")); } return std::nullopt; } std::string Redact(std::string_view url) { try { const Dyn::SplitUrl split = Dyn::UrlSplit(url); if (!split.Password()) return std::string(url); const std::string host = split.netloc.substr(split.netloc.rfind('@') + 1); const std::string netloc = std::format("{}:***@{}", split.Username().value_or(""), host); return Dyn::UrlUnsplit(split.scheme, netloc, split.path, split.query, split.fragment); } catch (const std::invalid_argument&) { return "(unparsable URL)"; } } std::string Where(std::string_view url) { const Url parsed = ParseUrl(url); return std::format("{} on {}:{} as {}", parsed.database, parsed.host, parsed.port, parsed.user); } }