Skip to content

fix: fail fast when mandatory AsyncApi fields are missing#232

Open
Varadraj75 wants to merge 1 commit intoasyncapi:masterfrom
Varadraj75:fix-mandatory-fields-validation
Open

fix: fail fast when mandatory AsyncApi fields are missing#232
Varadraj75 wants to merge 1 commit intoasyncapi:masterfrom
Varadraj75:fix-mandatory-fields-validation

Conversation

@Varadraj75
Copy link

@Varadraj75 Varadraj75 commented Feb 7, 2026

What

Adds runtime validation to ensure mandatory `AsyncApi` fields (`info` and `channels`) are initialized before an instance can be used.

Why

Previously, it was possible to create an invalid AsyncApi instance without mandatory fields, which caused a runtime crash later during JSON serialization due to uninitialized lateinit properties. This change fails fast with a clear and descriptive error instead of allowing an invalid object to propagate.

How

  • Introduces a lightweight runtime validation step in the DSL entry point
  • Detects uninitialized mandatory fields and throws a clear IllegalStateException
  • Avoids reflection and breaking API changes
  • Updates tests to assert the new fail-fast behavior

Fixes #228

Summary by CodeRabbit

  • Improvements
    • Builders now perform post-build validation to enforce required fields (e.g., info and channels) and surface clear errors for incomplete configurations; internal validation added to support serialization readiness.
  • Tests
    • Added tests to verify fail-fast behavior for missing required properties, success when required fields are present, and coverage for the new validation utility and extension scenarios.

Copilot AI review requested due to automatic review settings February 7, 2026 11:28
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our contributors guide useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.

@coderabbitai
Copy link

coderabbitai bot commented Feb 7, 2026

Warning

Rate limit exceeded

@Varadraj75 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 34 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Removed inline from AsyncApi builder methods and added runtime initialization checks: a new checkInitialized utility and an validateForSerialization() hook ensure info and channels are verified after building, causing fast failure if missing.

Changes

Cohort / File(s) Summary
Core DSL
kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt
Removed inline from builder methods (info, servers, channels, components, tags, externalDocs, companion asyncApi). Added internal validateForSerialization() which calls checkInitialized("info" to { info }, "channels" to { channels }) and imports checkInitialized.
Validation util
kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/util/ValidationUtils.kt
Added internal extension fun <T> T.checkInitialized(vararg checks: Pair<String, T.() -> Unit>): T that runs given checks and throws IllegalStateException listing missing properties when lateinit access fails.
Core tests
kotlin-asyncapi-core/src/test/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApiTest.kt
Added tests: one asserts IllegalStateException when required fields are missing, another verifies successful construction when info and channels are provided. Added necessary imports.
Validation util tests
kotlin-asyncapi-core/src/test/kotlin/com/asyncapi/kotlinasyncapi/util/ValidationUtilsTest.kt
Added unit tests covering checkInitialized: success when initialized, failure when missing single or multiple properties, and message assertions for missing keys.
Context tests
kotlin-asyncapi-context/src/test/kotlin/com/asyncapi/kotlinasyncapi/context/service/AsyncApiExtensionTest.kt
Adjusted test inputs to include explicit info { ... } blocks and empty channels {} in several AsyncApi builder usages; minor import reordering to accommodate required fields.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Factory as AsyncApi.asyncApi()
    participant AsyncApiObj as AsyncApi instance
    participant Checker as checkInitialized

    User->>Factory: call asyncApi { ...build... }
    Factory->>AsyncApiObj: instantiate AsyncApi and apply build
    Factory->>Checker: call validateForSerialization() -> checkInitialized("info","channels")
    alt required fields missing
        Checker-->>Factory: throw IllegalStateException("Missing required properties: ...")
        Factory-->>User: propagate exception
    else all required fields present
        Checker-->>Factory: return AsyncApi instance
        Factory-->>User: return AsyncApi object
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through builders, quiet and spry,

un-inline my steps, then I gave them a try.
I poked at info, I peered in channels' den —
Missing ones shouted, "Check us again!"
A tidy thump — no surprises at the pen.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the main change: adding fail-fast validation for mandatory AsyncApi fields (info and channels) instead of crashing during serialization.
Linked Issues check ✅ Passed Code changes fully address issue #228 requirements: fail-fast validation with clear error messages for missing info/channels fields is implemented through validateForSerialization method.
Out of Scope Changes check ✅ Passed All changes directly support the fail-fast validation objective: validation utility, AsyncApi builder modifications, test cases, and extension test updates are all in scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Adds fail-fast validation for mandatory AsyncApi fields (info, channels) to prevent late lateinit crashes and updates tests to assert the new behavior.

Changes:

  • Add runtime validation after building AsyncApi via the DSL factory (AsyncApi.asyncApi { ... })
  • Introduce a generic checkInitialized helper to detect uninitialized lateinit properties
  • Add tests for failure when required fields are missing and success when they are set

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt Adds post-build validation for required fields and introduces checkInitialized helper; also removes inline from DSL builder methods.
kotlin-asyncapi-core/src/test/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApiTest.kt Adds tests covering fail-fast behavior and successful construction when required fields are set.

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

@Varadraj75 Varadraj75 force-pushed the fix-mandatory-fields-validation branch from 23faf01 to 5031049 Compare February 7, 2026 11:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt`:
- Around line 61-76: The top-level extension function checkInitialized should be
made internal to hide it from the public API; update the function declaration
for fun <T> T.checkInitialized(...) to use internal visibility (i.e., internal
fun <T> T.checkInitialized(...)) so it remains usable within the file/module but
is not exported publicly—apply this change to the checkInitialized declaration
in AsyncApi.kt.
- Around line 27-46: The member builder methods info, servers, channels,
components, tags, and externalDocs should be declared inline to match the DSL
pattern and avoid unnecessary lambda allocations; update their signatures (e.g.,
fun info(build: Info.() -> Unit): Info) to be inline fun info(build: Info.() ->
Unit): Info and do the same for servers(ReferencableServersMap.() -> Unit),
channels(ReferencableChannelsMap.() -> Unit), components(Components.() -> Unit),
tags(TagsList.() -> Unit), and externalDocs(ExternalDocumentation.() -> Unit)
while leaving the asyncApi companion function behavior unchanged.
🧹 Nitpick comments (1)
kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt (1)

51-58: Validation only runs through the companion asyncApi() entry point.

Direct construction via AsyncApi().apply { … } (as in the existing test on Line 34 of the test file) bypasses checkInitialized entirely. This is a reasonable design choice for a DSL entry-point guard, but worth documenting — users constructing AsyncApi() directly will not get fail-fast validation.

Consider adding a KDoc comment on the companion asyncApi function noting it is the recommended entry point because it validates mandatory fields.

@Varadraj75 Varadraj75 changed the title Fail fast when mandatory AsyncApi fields are missing fix: fail fast when mandatory AsyncApi fields are missing Feb 7, 2026
@Varadraj75
Copy link
Author

Hi @lorenzsimon ,
This PR fixes a runtime crash by failing fast when mandatory AsyncApi fields (info, channels) are missing.

  • No breaking API changes
  • No reflection
  • Tests added

Would really appreciate a quick review when you have time. Thanks!

Comment on lines +73 to +76
info {
title("titleValue")
version("versionValue")
}
Copy link
Member

Choose a reason for hiding this comment

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

It is actually expected that we build a partial asyncapi spec here. the context module is a core feature of the library which lets users build asnycapi documentation from multiple sources. for example you can define the info in code and extend it with the channels from annotation processors.

My initial idea does not seem to work since we need the ability to build partial asyncapi in order to extend it with context providers.

So our only option is to introduce a separate validation step before we serialise it.

fun id(value: String): String =
value.also { id = it }

inline fun info(build: Info.() -> Unit): Info =
Copy link
Member

Choose a reason for hiding this comment

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

why do you remove the inline modifier?

}
}

fun <T> T.checkInitialized(
Copy link
Member

Choose a reason for hiding this comment

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

this should be moved to a separate utility file. its not part of the asyncapi dsl model

TestUtils.assertJsonEquals(expected, actual)
}

@Test
Copy link
Member

Choose a reason for hiding this comment

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

After moving the T.checkInitialized to a separate file. You could write a unit test for it, not in the model test.

}
}

fun <T> T.checkInitialized(
Copy link
Member

Choose a reason for hiding this comment

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

this should be internal.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt`:
- Around line 59-60: The DSL entry function asyncApi(build: AsyncApi.() ->
Unit): AsyncApi should enforce validation before returning; after constructing
and applying the build to AsyncApi() (the AsyncApi() instance returned by
AsyncApi().apply(build)), call the instance's validateForSerialization() (or
equivalent validation method on AsyncApi) and let any validation exceptions
propagate (or throw a descriptive exception) so invalid documents fail fast;
update the asyncApi function to perform this validation step on the built
AsyncApi object prior to returning it.
- Around line 49-54: validateForSerialization() is never called, so invalid
AsyncApi instances can be serialized; call validateForSerialization() before
actual serialization to enforce required fields. Update
DefaultAsyncApiSerializer.serialize() to invoke
AsyncApi.validateForSerialization() (or a public wrapper) immediately before
objectMapper.writeValueAsString(this), and/or ensure the asyncApi() DSL
entrypoint triggers the same validation at build/return time so any missing
"info" or "channels" causes an error rather than producing invalid JSON. Ensure
you reference the existing validateForSerialization() method on AsyncApi when
adding the call.

In
`@kotlin-asyncapi-core/src/test/kotlin/com/asyncapi/kotlinasyncapi/util/ValidationUtilsTest.kt`:
- Line 38: Replace the JVM-dependent Kotlin assert() calls in
ValidationUtilsTest (specifically the assertions at the locations checking
exception.message!!.contains(...), currently at lines referenced around the
checks) with JUnit assertions so the tests run reliably; change each assert(...)
to Assertions.assertTrue(...) (or import assertTrue from
org.junit.jupiter.api.Assertions and call assertTrue(...)) for the checks in the
test methods in ValidationUtilsTest.kt (the exception message contains
assertions around requiredProperty) so they execute regardless of JVM assertion
flags.

Comment on lines +49 to +54
internal fun validateForSerialization() {
checkInitialized(
"info" to { info },
"channels" to { channels }
)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n 'validateForSerialization' --type=kotlin -C3

Repository: asyncapi/kotlin-asyncapi

Length of output: 1824


🏁 Script executed:

# Look at the full test context
sed -n '45,70p' kotlin-asyncapi-core/src/test/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApiTest.kt

# Search for any serialization implementations or hooks
rg -n 'KSerializer|@Serializable|toJson|serialize' --type=kotlin kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt

# Check if validateForSerialization is called elsewhere outside tests
rg -n 'validateForSerialization' --type=kotlin -C2 | grep -v "Test.kt"

Repository: asyncapi/kotlin-asyncapi

Length of output: 1458


🏁 Script executed:

# Look for TestUtils.json implementation to see if it calls validateForSerialization
fd -t f -name 'TestUtils.kt' kotlin-asyncapi-core/src/test

# Search for any toJson, json, or serialization methods in AsyncApi
rg -n 'toJson|json|serialize' --type=kotlin kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt

# Check if there's a custom serializer defined
rg -n 'KSerializer|@Serializable' --type=kotlin kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/

Repository: asyncapi/kotlin-asyncapi

Length of output: 298


🏁 Script executed:

# Find TestUtils.kt file
fd -t f -name 'TestUtils.kt' kotlin-asyncapi-core/src/test

# Search for json method definition and validateForSerialization calls
rg 'fun json|TestUtils.json' --type=kotlin -A5 -B2

Repository: asyncapi/kotlin-asyncapi

Length of output: 48147


🏁 Script executed:

# Look for any custom Jackson serializer for AsyncApi
rg 'AsyncApi' --type=kotlin | grep -i 'serializer\|json\|deserializer\|custom' | head -20

# Check if there's a `@JsonSerialize` annotation on AsyncApi class
sed -n '1,70p' kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt

Repository: asyncapi/kotlin-asyncapi

Length of output: 5463


🏁 Script executed:

# Find and examine the DefaultAsyncApiSerializer implementation
fd -type f -name 'DefaultAsyncApiSerializer.kt'

# Also check the AsyncApiSerializer interface
rg 'interface AsyncApiSerializer' --type=kotlin -A10

Repository: asyncapi/kotlin-asyncapi

Length of output: 1640


🏁 Script executed:

# Read the full DefaultAsyncApiSerializer implementation
cat kotlin-asyncapi-context/src/main/kotlin/com/asyncapi/kotlinasyncapi/context/service/AsyncApiSerializer.kt

Repository: asyncapi/kotlin-asyncapi

Length of output: 748


validateForSerialization() is never invoked during actual serialization.

The method is defined but DefaultAsyncApiSerializer.serialize() only calls objectMapper.writeValueAsString(this) without invoking validateForSerialization(). The DSL entry point asyncApi() also does not call it. As a result, users can create and serialize invalid AsyncApi instances (missing required info or channels fields) without any error, making the validation dead code.

🤖 Prompt for AI Agents
In
`@kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt`
around lines 49 - 54, validateForSerialization() is never called, so invalid
AsyncApi instances can be serialized; call validateForSerialization() before
actual serialization to enforce required fields. Update
DefaultAsyncApiSerializer.serialize() to invoke
AsyncApi.validateForSerialization() (or a public wrapper) immediately before
objectMapper.writeValueAsString(this), and/or ensure the asyncApi() DSL
entrypoint triggers the same validation at build/return time so any missing
"info" or "channels" causes an error rather than producing invalid JSON. Ensure
you reference the existing validateForSerialization() method on AsyncApi when
adding the call.

Comment on lines +59 to 60
fun asyncApi(build: AsyncApi.() -> Unit): AsyncApi =
AsyncApi().apply(build)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

DSL entry point does not enforce validation.

The asyncApi() function builds and returns an AsyncApi without calling validateForSerialization(). Per the PR objectives, the intent is to "fail fast" so invalid documents can't be created. Consider calling the validation here:

Proposed fix
         fun asyncApi(build: AsyncApi.() -> Unit): AsyncApi =
-            AsyncApi().apply(build)
+            AsyncApi().apply(build).also { it.validateForSerialization() }
🤖 Prompt for AI Agents
In
`@kotlin-asyncapi-core/src/main/kotlin/com/asyncapi/kotlinasyncapi/model/AsyncApi.kt`
around lines 59 - 60, The DSL entry function asyncApi(build: AsyncApi.() ->
Unit): AsyncApi should enforce validation before returning; after constructing
and applying the build to AsyncApi() (the AsyncApi() instance returned by
AsyncApi().apply(build)), call the instance's validateForSerialization() (or
equivalent validation method on AsyncApi) and let any validation exceptions
propagate (or throw a descriptive exception) so invalid documents fail fast;
update the asyncApi function to perform this validation step on the built
AsyncApi object prior to returning it.

)
}

assert(exception.message!!.contains("Missing required properties: requiredProperty"))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

assert() may silently pass when JVM assertions are disabled.

Kotlin's assert() is a no-op unless the JVM is started with -ea. If the test runner doesn't enable assertions, lines 38, 52, and 53 will never actually check anything. Use JUnit's assertTrue instead for reliable test behavior.

Proposed fix
-        assert(exception.message!!.contains("Missing required properties: requiredProperty"))
+        assertTrue(exception.message!!.contains("Missing required properties: requiredProperty"))

And similarly for lines 52–53:

-        assert(exception.message!!.contains("requiredProperty"))
-        assert(exception.message!!.contains("anotherMissing"))
+        assertTrue(exception.message!!.contains("requiredProperty"))
+        assertTrue(exception.message!!.contains("anotherMissing"))

Add the import at the top:

 import org.junit.jupiter.api.Assertions.assertDoesNotThrow
 import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.assertTrue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert(exception.message!!.contains("Missing required properties: requiredProperty"))
assertTrue(exception.message!!.contains("Missing required properties: requiredProperty"))
🤖 Prompt for AI Agents
In
`@kotlin-asyncapi-core/src/test/kotlin/com/asyncapi/kotlinasyncapi/util/ValidationUtilsTest.kt`
at line 38, Replace the JVM-dependent Kotlin assert() calls in
ValidationUtilsTest (specifically the assertions at the locations checking
exception.message!!.contains(...), currently at lines referenced around the
checks) with JUnit assertions so the tests run reliably; change each assert(...)
to Assertions.assertTrue(...) (or import assertTrue from
org.junit.jupiter.api.Assertions and call assertTrue(...)) for the checks in the
test methods in ValidationUtilsTest.kt (the exception message contains
assertions around requiredProperty) so they execute regardless of JVM assertion
flags.

@Varadraj75 Varadraj75 force-pushed the fix-mandatory-fields-validation branch from 397446c to ab65729 Compare February 11, 2026 13:55
- Remove validation from DSL entry point
- Move checkInitialized to internal utility file
- Add dedicated utility tests
- Introduce validateForSerialization() in AsyncApi
@Varadraj75 Varadraj75 force-pushed the fix-mandatory-fields-validation branch from ab65729 to 9ca7fc0 Compare February 11, 2026 13:58
@Varadraj75
Copy link
Author

@lorenzsimon
Thanks for the detailed feedback and clarification regarding partial AsyncApi construction for context providers.

I’ve updated the PR to:

  • Remove validation from the DSL entry point
  • Move checkInitialized to a separate internal utility file
  • Add dedicated unit tests for the utility
  • Introduce validateForSerialization() inside AsyncApi

Validation is no longer triggered during DSL construction and is now aligned with the intended serialization lifecycle.

I’ve rebased onto the latest master, squashed the changes into a single clean commit, and verified that the full Maven build passes locally across all modules.

Please let me know if this aligns with the expected design.

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.

[BUG] Runtime crash when serializing AsyncApi without mandatory fields

2 participants