Skip to content

Conversation

@Shanzeee
Copy link
Contributor

Konvert currently enforces non-nullability using !!, which results in raw NPEs without context.

Introducing a strategy-based approach allows producing clearer, debuggable errors via requireNotNull(...).

New configuration:

konvert.enforce-not-null-strategy = ASSERTION_OPERATOR | REQUIRE_NOT_NULL

Strategies:

ASSERTION_OPERATOR (current and default strategy)

  • generates: value!!
  • throws: NullPointerException

REQUIRE_NOT_NULL

  • generates: requireNotNull(value) { "value must not be null" }
  • throws: IllegalArgumentException: value must not be null

The default remains ASSERTION_OPERATOR, so this is fully backward-compatible and introduces no breaking changes for existing users.

This PR implements the strategy only in SameTypeConverter as a PoC.
If the idea is accepted, I can extend it across all converters and generation paths.

Let me know what you think!

@mcarleio
Copy link
Owner

Thank you very much, I like the idea 👍

I will have a look on the code in the next days

@adampoplawski
Copy link

Hello @mcarleio
We can support with MR if you would provide code review

@mcarleio mcarleio self-requested a review December 2, 2025 17:23
Copy link
Owner

@mcarleio mcarleio left a comment

Choose a reason for hiding this comment

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

Sorry for the delay. I think there are only minor changes to do.

* generates `requireNotNull(expression) { "Value for '<expression>' must not be null" }`
*/
protected fun applyNotNullEnforcementIfNeeded(
fieldName: String,
Copy link
Owner

Choose a reason for hiding this comment

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

In most cases a fieldName should work, but I think there are places, where there is no fieldName 🤔 If I am not mistaken, the IterableToX and MapToX have special cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I slightly adjusted the implementation so it works uniformly for all cases.

@Shanzeee
Copy link
Contributor Author

Shanzeee commented Dec 3, 2025

@mcarleio
Pushed the changes related to your review.
Additionally, I applied the new not-null enforcement strategy across all converters.

Let me know if you think more tests are needed, or if the current coverage is sufficient.

@mcarleio mcarleio requested a review from Copilot December 4, 2025 19:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a configurable strategy for enforcing non-null mapping in Konvert. Currently, Konvert uses the !! assertion operator which throws raw NullPointerExceptions. The new konvert.enforce-not-null-strategy option allows choosing between the existing ASSERTION_OPERATOR (default) and a new REQUIRE_NOT_NULL strategy that generates requireNotNull() calls with descriptive error messages. This change is backward-compatible and initially implemented in SameTypeConverter as proof-of-concept.

Key changes:

  • Added EnforceNotNullStrategy enum with ASSERTION_OPERATOR and REQUIRE_NOT_NULL options
  • Introduced ENFORCE_NOT_NULL_STRATEGY_OPTION configuration option with default ASSERTION_OPERATOR
  • Refactored converters to use applyNotNullEnforcementIfNeeded() instead of string concatenation

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
converter-api/src/main/kotlin/io/mcarle/konvert/converter/api/config/EnforceNotNullStrategy.kt Defines the new strategy enum with documentation
converter-api/src/main/kotlin/io/mcarle/konvert/converter/api/config/KonvertOptions.kt Declares the configuration option
converter-api/src/main/kotlin/io/mcarle/konvert/converter/api/config/extensions.kt Implements configuration accessor with parsing logic
converter-api/src/main/kotlin/io/mcarle/konvert/converter/api/AbstractTypeConverter.kt Implements strategy logic in applyNotNullEnforcementIfNeeded() method
converter/src/main/kotlin/io/mcarle/konvert/converter/SameTypeConverter.kt Applies new enforcement method
Multiple converter files Refactor to use new enforcement approach
processor/src/main/kotlin/io/mcarle/konvert/processor/codegen/MappingCodeGenerator.kt Updates top-level mapping generation to support new strategy
processor/src/test/kotlin/io/mcarle/konvert/processor/codegen/MappingCodeGeneratorITest.kt Adds integration tests for both strategies
converter/src/test/kotlin/io/mcarle/konvert/converter/SameTypeConverterITest.kt Adds parameterized tests for REQUIRE_NOT_NULL strategy
converter/src/test/kotlin/io/mcarle/konvert/converter/utils/ConverterITest.kt Extends test infrastructure to support options parameter

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +68 to +69
?.let { "Value for '$it' must not be null" }
?: "Value must not be null"
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The error message construction doesn't match the test expectation. Test line 84 expects the format 'it.test0' must not be null but this produces Value for 'it.test0' must not be null. Consider using just \"$it must not be null\" to match test expectations and provide more concise error messages.

Suggested change
?.let { "Value for '$it' must not be null" }
?: "Value must not be null"
?.let { "'$it' must not be null" }
?: "must not be null"

Copilot uses AI. Check for mistakes.

EnforceNotNullStrategy.REQUIRE_NOT_NULL -> {
val requireCode =
"return·requireNotNull(${context.paramName})·{·\"${context.paramName}·must·not·be·null\"·}·.let·{\n⇥%L%L⇤\n}"
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The error message format here (\"${context.paramName}·must·not·be·null\") is inconsistent with the format in AbstractTypeConverter.applyNotNullEnforcementIfNeeded (\"Value for '$it' must not be null\"). These should use the same message format for consistency across the codebase.

Suggested change
"return·requireNotNull(${context.paramName})·{·\"${context.paramName}·must·not·be·null\"·}·.let·{\n⇥%L%L⇤\n}"
"return·requireNotNull(${context.paramName})·{·\"Value·for·'${context.paramName}'·must·not·be·null\"·}·.let·{\n⇥%L%L⇤\n}"

Copilot uses AI. Check for mistakes.
@adampoplawski
Copy link

@mcarleio do you want us to answer to AI comments?

@mcarleio
Copy link
Owner

mcarleio commented Dec 9, 2025

No, sorry, I am quite busy at the moment and do not find enough time for Konvert. Pre holiday stress...

I just wanted to try out what an AI identifies, so that I get a better feeling.

Of course you can have a look and think about what the AI identified, but my plan was, that I would validate and if I think it is important, then tag the creator of the PR on that (probably with some comment from myself).

I really like it that you want to contribute! I am really sorry for the poor experience so far, but currently I have (too) little time for Konvert.

@adampoplawski
Copy link

adampoplawski commented Jan 6, 2026

Hello @mcarleio
Any update for us :) ? Sorry for bothering, just reminder we still wait for review.

@mcarleio
Copy link
Owner

mcarleio commented Jan 6, 2026

I hope to find time by end of the week.

@mcarleio mcarleio merged commit d9c9364 into mcarleio:main Jan 11, 2026
3 checks passed
@codecov
Copy link

codecov bot commented Jan 11, 2026

Codecov Report

❌ Patch coverage is 93.29268% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.92%. Comparing base (3505108) to head (7a88ed2).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...rle/konvert/converter/api/AbstractTypeConverter.kt 50.00% 5 Missing and 2 partials ⚠️
.../mcarle/konvert/converter/api/config/extensions.kt 42.85% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #199      +/-   ##
==========================================
- Coverage   89.97%   89.92%   -0.05%     
==========================================
  Files          81       82       +1     
  Lines        2754     2850      +96     
  Branches      389      397       +8     
==========================================
+ Hits         2478     2563      +85     
- Misses        112      118       +6     
- Partials      164      169       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mcarleio
Copy link
Owner

Sorry to let you wait that long.

Nevertheless, today I finally had a look on the code, and it looks good 🥳 Thank you for the effort and the contribution!

I decided to directly merge it, so that we can proceed here in the upcoming days. I have some local changes (started over a month ago...), which I try to finalize before the next release, but not sure how much time I will find.

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.

3 participants