Skip to content

KTOR-4206 Wrap Typesafe exceptions in HOCON config - #5915

Open
Marko Cevrljakovic (MARKOCEVRLJAKOVIC) wants to merge 2 commits into
ktorio:release/3.xfrom
MARKOCEVRLJAKOVIC:ktor-4206-wrap-hocon-exceptions
Open

Marko Cevrljakovic (MARKOCEVRLJAKOVIC) wants to merge 2 commits into
ktorio:release/3.xfrom
MARKOCEVRLJAKOVIC:ktor-4206-wrap-hocon-exceptions

Conversation

@MARKOCEVRLJAKOVIC

Copy link
Copy Markdown
Contributor

Subsystem

Server, Config

Motivation

Fixes KTOR-4206

HoconApplicationConfig.config() and configList() call Typesafe Config directly, so a missing or invalid path throws com.typesafe.config.ConfigException instead of ApplicationConfigurationException. This is inconsistent with property() in the same class and with the YAML backend, which both throw ApplicationConfigurationException.

Solution

config() and configList() now route through a private helper, wrapConfigException, which translates Typesafe exceptions into ApplicationConfigurationException while preserving the original exception as cause:

  • ConfigException.Missing - "Path $path not found." (the most common case, with a clear message).
  • Any other ConfigException - "Failed to read path $path: <original message>".

I deliberately used a generic catch (cause: ConfigException) for everything except Missing. This covers WrongType, BadPath, NotResolved, and any future subtypes, guaranteeing that no Typesafe exception can leak through these methods. The original message is preserved in the text and the typed exception remains accessible via cause, so no diagnostic information is lost.

An alternative would be catching each subtype separately (WrongType, BadPath, NotResolved, etc.) with adjusted messages. That offers slightly more specific error messages, but it requires more code and unlisted subtypes could still leak. I'm happy to switch to per-subtype handling if maintainers prefer it.

Testing

HoconConfigTest:

  • Missing path: config() for top-level, nested, and relative-to-sub-config paths, and configList(). Each asserts the message and that cause is ConfigException.Missing.
  • Wrong type: config() on a scalar and configList() on an object. Each asserts that cause is ConfigException.WrongType.
  • Invalid path expressions: "", "ktor." and "ktor..deployment" for both methods, asserting the "Failed to read path ..." message.
  • Unresolved config: config() on a non-resolved substitution, asserting that cause is ConfigException.NotResolved.
  • Regression: an empty object and an empty list still return successfully, with no exception.

MergedApplicationConfigTest:

  • Added a test to verify that config() properly wraps exceptions when reading missing paths from a merged/fallback configuration setup.

Targeting release/3.x as a bug fix. Happy to retarget to main if needed.

HoconApplicationConfig.config() and configList() called Typesafe's
getConfig/getConfigList directly, so a missing or mistyped path leaked
com.typesafe.config.ConfigException to callers instead of Ktor's
ApplicationConfigurationException. property() already follows the
ApplicationConfig contract; these two methods did not.

Route both methods through a helper that translates the exceptions and
keeps the original one as the cause:

 - ConfigException.Missing is reported as "Path <path> not found."

 - Any other ConfigException (WrongType, BadPath, NotResolved, ...) is
   reported as "Failed to read path <path>: <original message>"

A single generic catch is used for the non-Missing cases so that no
Typesafe exception, including future subtypes, can leak through.
Comment thread ktor-server/ktor-server-core/jvm/test/io/ktor/tests/config/HoconConfigTest.kt Outdated

@zibet27 Pantus Oleh (zibet27) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Marko Cevrljakovic (@MARKOCEVRLJAKOVIC),

Thank you! The code looks good to me.

I think we should also wrap exceptions in HoconApplicationConfigValue and add @throws to its KDoc; other implementations already throw that.
One exception, YamlNodeConfigValue.getMap throwing IllegalStateException which rather typo.

Will you do that?

HoconApplicationConfigValue called Typesafe directly, so getString,
getList, getMap, getAs and type leaked ConfigException. They now go
through wrapConfigException and throw ApplicationConfigurationException.

property() and propertyOrNull() also wrap hasPath(), which throws
ConfigException for an invalid path or an unresolved substitution.

Align the other implementations with the documented contract:

 - YamlNodeConfigValue.getMap throws ApplicationConfigurationException
   instead of IllegalStateException.

 - MapApplicationConfigValue.getString and getList throw
   ApplicationConfigurationException instead of NullPointerException
   for a missing key.

Add @throws to the KDoc of ApplicationConfigValue.getString, getList
and getMap, and shorten the test names.

Update HoconDecoderTest "invalid types": getAs() now throws
ApplicationConfigurationException with ConfigException.WrongType as
the cause, instead of leaking WrongType directly.
@MARKOCEVRLJAKOVIC

Copy link
Copy Markdown
Contributor Author

Hi Pantus Oleh (@zibet27) , thanks for the review! I've pushed the changes, please let me know if everything looks good now.

Additionally I had to update an existing test in HoconDecoderTest. It expected getAs() to throw ConfigException.WrongType directly, which is the leak this PR fixes.

I also changed MapApplicationConfigValue to throw ApplicationConfigurationException instead of NullPointerException, so it matches the new KDoc. Let me know if that's okay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants