Repository navigation
Conversation
parse_dsn reports every connection-string problem as an ArgumentError whose only content is the message, so a caller that needs to know which parameter failed has to match on text that can change between releases (JuliaDatabases#28). Add Postgres.validate_dsn, which returns nothing or a Postgres.ConnectionParameterError carrying the parameter name (never text from a value), a reason symbol, and the message parse_dsn throws. The parsing helpers now return the error instead of throwing, and both entry points share one parser; parse_dsn throws ArgumentError(err.msg), so its exception type, messages and error order are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
[comment generated via AI]
I fuzzed this with 200k generated keyword and URI connection strings, including quoting, escapes, % sequences, NUL bytes and random bytes. validate_dsn and parse_dsn agreed on every valid UTF-8 input: nothing exactly when parse_dsn succeeds, and otherwise the same message.
One gap: a string that isn't valid UTF-8 makes validate_dsn throw Base.InvalidCharError, for example Postgres.validate_dsn("host=h\xff"). lowercase(dsn) in try_parse_dsn throws it, and parse_dsn on main throws the same error. Since validate_dsn promises nothing or a ConnectionParameterError, an up-front isvalid(dsn) check returning a :malformed error would close the gap. parse_dsn would then throw an ArgumentError for such strings, like the other syntax errors.
A connection string with malformed UTF-8 made both validate_dsn and parse_dsn throw Base.InvalidCharError from lowercase. So did a percent-decoded URI value or an environment variable with such bytes in a boolean parameter, sslmode or gssencmode. validate_dsn is documented to return nothing or a ConnectionParameterError. Report a connection string with a malformed or overlong character as :malformed, and such a boolean, sslmode or gssencmode value as :invalid_value. Only what lowercase cannot decode is rejected: surrogates and code points above U+10FFFF parse as before, and values that are not lowercased, such as a percent-decoded password, are still taken as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for fuzzing this. Confirmed, and the gap was a little wider: a valid UTF-8 URI can percent-decode to malformed bytes ( 54fa9cf rejects exactly what
Drafted with Claude Code (Claude Opus 5.5) and reviewed by me before posting. |
parse_dsnreports every connection-string problem as anArgumentErrorwhose only content is the message, so a caller that needs to know which parameter failed has to match on the text. That breaks whenever the wording changes (#28).This adds
Postgres.validate_dsn(dsn), which returnsnothingwhenparse_dsn(dsn)would succeed and otherwise aPostgres.ConnectionParameterErrorwith three fields:key: the parameter that failed, ornothingfor an unknown or empty key, a malformed URI, and malformed UTF-8 in the connection string. It is always a parameter name Postgres.jl supports or accepts and ignores, never text taken from a value.reason::unknown_key,:invalid_value,:unsupported_ignored, or:malformed.msg: exactly the messageparse_dsnthrows for the same input;showerrorprints it.The parsing helpers now return a
ConnectionParameterErrorinstead of throwing, and both entry points share one internal parser.parse_dsnthrowsArgumentError(err.msg)when it gets one, so for input it already rejected with anArgumentError, its messages and the first error reported when several values are invalid are unchanged. Neither path wraps the other in atry/catch, so a successful parse doesn't pay for an exception and no exception chain can carry input. Environment defaults are applied as before, so an invalidPGPORTis reported by both.parse_dsnaccepts exactly the same input as before. Two kinds of rejected input get a different error:lowercasethrowBase.InvalidCharError, which carries part of the input. This happened for the connection string itself, and for a percent-decoded URI value or an environment variable used in a boolean parameter,sslmodeorgssencmode. It is now:malformed(connection string contains malformed UTF-8) or:invalid_valuewith the usual message. Forsslmode/gssencmodethat is the message proposed in Provide structured errors for invalid connection parameters #28 (…; expected one of disable, prefer, require, verify-full). Only whatlowercasecannot decode is rejected: surrogates and code points above U+10FFFF parse as before, and values that are not lowercased, such as passwords, are still taken as they are. Other invalidsslmode/gssencmodevalues are still rejected only when connecting.numeric_overflowvalue containing a NUL byte now gets the usualnumeric_overflow must be :warn or :errormessage instead of Julia'sSymbol name may not contain \0, which was raised before the other parameters were checked.Adds
validate_dsnandConnectionParameterErrorto the public API (publicon Julia 1.11+), with docstrings and a short paragraph in the manual. No runtime dependency changes.Part of #28. The
sslmode/gssencmodevalidation will be a separate PR. This one rejects only the malformed values above, which can never match a mode and cannot be lowercased. Their message already uses the shape proposed for that PR, so if you'd prefer different wording, it can change in both places at once.Validation:
Structured connection string errorstestset: for every current message shape (keyword and URI),validate_dsnreturns the expectedkey/reason, and itsmsgequals the message of theArgumentErrorthatparse_dsnthrows for the same input. It also covers valid input, environment-derived errors, the order in which several errors are reported, malformed UTF-8 (including that surrogates still parse), and that rejectedsslpasswordvalues never appear. The existing credential-safety cases now also checkvalidate_dsn.srcchange, dropping one error propagation inparams_from_values, changing onereason, swapping the order of two value checks, or letting the DSNnumeric_overflowmessage drift from the constructor's each makes the new tests fail. So does dropping the malformed-UTF-8 check on the connection string, on boolean values, or ongssencmode; usingisvalidin place of it; or moving thesslmode/gssencmodecheck beforeportor aftergssdelegation.parse_dsnaccepted exactly the same input with the same field values, and failed with the same message apart from the two cases above.validate_dsnnever threw, returnednothingexactly whenparse_dsnsucceeded, and otherwise returned its message.Pkg.test()with Docker integration (POSTGRES_REQUIRE_INTEGRATION=true, PostgreSQL 16, Linux x64): 9107 checks pass on Julia 1.13.2, 9081 on Julia 1.10.12, and 9107 with-t 2.Drafted with Claude Code (Claude Opus 5.5) and reviewed by me before posting.
🤖 Generated with Claude Code