From 15d115b1637902f505417d0f4c52b1c1264a2bd6 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Sun, 14 Jun 2026 20:40:42 +0900 Subject: [PATCH 1/8] Reorder parameters in newHeader function for improved clarity --- .../documentify/core/specification/element/header/Header.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/header/Header.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/header/Header.kt index 3bec2d4..ebe7a51 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/header/Header.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/header/Header.kt @@ -15,8 +15,8 @@ class Header( companion object { fun newHeader( key: String, - sample: String, description: String, + sample: String, requirement: Requirement ): Header { val descriptor = HeaderDocumentation.headerWithName(key) From cc607a798ddf6ca5448806ff3f577e8ff19c11f6 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Sun, 14 Jun 2026 20:40:51 +0900 Subject: [PATCH 2/8] Simplify schema assignment in ResourceSpec by removing random suffix --- .../documentify/core/specification/schema/ResourceSpec.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/schema/ResourceSpec.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/schema/ResourceSpec.kt index f4e98c2..73a4a19 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/schema/ResourceSpec.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/schema/ResourceSpec.kt @@ -23,9 +23,8 @@ class ResourceSpec( init { summary = "" - val randomSuffix = randomSuffix() - request.schema = "$documentName Request ($randomSuffix)" - response.schema = "$documentName Response ($randomSuffix)" + request.schema = "$documentName Request" + response.schema = "$documentName Response" } fun link(rel: String): Link { From 749aa1812f0784e6ac2492ac2fdb6fafc2084a81 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Sun, 14 Jun 2026 20:40:56 +0900 Subject: [PATCH 3/8] Refactor optionalField method to include return statement for clarity --- .../bgmsound/documentify/core/specification/FieldSchema.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/FieldSchema.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/FieldSchema.kt index e1cb912..5eed7d9 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/FieldSchema.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/FieldSchema.kt @@ -20,8 +20,8 @@ interface FieldSchema { fun optionalField(path: String, description: String): Field - fun optionalField(description: String) { - optionalField("", description) + fun optionalField(description: String): Field { + return optionalField("", description) } fun optionalField(path: String, description: String, sample: Any): Field From b106ce88fe02e703be8a7a4d7daca9fda4bea2d5 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Sun, 14 Jun 2026 23:28:43 +0900 Subject: [PATCH 4/8] Refactor WebTestClientDocumentEmitter to improve response handling by consolidating snippets --- .../documentify/core/emitter/WebTestClientDocumentEmitter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt index ff471be..e248ce5 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt @@ -75,7 +75,7 @@ class WebTestClientDocumentEmitter( .configureClient() .filter(WebTestClientRestDocumentation.documentationConfiguration(provider)) .build() - + val snippets = response.build() webTestClient .method(method()) .uri(uriTemplate, samplePathVariables + queryVariables) @@ -91,7 +91,7 @@ class WebTestClientDocumentEmitter( "${documentSpec.name}-case-${index + 1}", preprocessRequest(prettyPrint(), *requestPreprocessors), preprocessResponse(prettyPrint(), *responsePreprocessors), - response.buildResource(index) + *(snippets + listOf(response.buildResource(index))).toTypedArray() ) ) } From 9c181c7937b91d256fbea8ef5b43aacd6b06b0b9 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Tue, 16 Jun 2026 16:08:20 +0900 Subject: [PATCH 5/8] Remove unnecessary whitespace in AbstractDocumentifySupport.kt --- .../bgmsound/documentify/core/AbstractDocumentifySupport.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/AbstractDocumentifySupport.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/AbstractDocumentifySupport.kt index c7d86f4..493652c 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/AbstractDocumentifySupport.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/AbstractDocumentifySupport.kt @@ -56,6 +56,4 @@ abstract class AbstractDocumentifySupport : DocumentifySupport { environment = applicationContextEnvironment(provider, context) this.provider = provider } - - companion object } \ No newline at end of file From 0b8640a5dd60efa1fa96a75769879a14055ce57d Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Sun, 28 Jun 2026 17:20:55 +0900 Subject: [PATCH 6/8] Refactor AbstractDocumentResult to enhance matcher logic and add equalityMatcher for numeric comparisons --- .../core/emitter/AbstractDocumentResult.kt | 53 +++++++++++++++---- .../emitter/WebTestClientDocumentResult.kt | 5 -- .../core/specification/element/SpecElement.kt | 14 +++-- .../documentify/core/DocumentResultTest.kt | 44 +++++++++++++++ .../bgmsound/documentify/core/FieldTest.kt | 14 +++++ 5 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/DocumentResultTest.kt diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/AbstractDocumentResult.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/AbstractDocumentResult.kt index 44233c7..a6b2f02 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/AbstractDocumentResult.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/AbstractDocumentResult.kt @@ -2,14 +2,15 @@ package io.github.bgmsound.documentify.core.emitter import io.github.bgmsound.documentify.core.specification.element.field.Field import io.github.bgmsound.documentify.core.specification.schema.response.ResponseSpec +import org.hamcrest.BaseMatcher +import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.Matchers +import java.math.BigDecimal abstract class AbstractDocumentResult : DocumentResult { abstract fun expect(jsonPath: String, matcher: Matcher<*>) - abstract fun expectValue(jsonPath: String, value: Any) - override fun validateWith(responseSpec: ResponseSpec) { val matchers = aggregateMatchers(responseSpec.fields) if (matchers.isEmpty()) { @@ -18,15 +19,47 @@ abstract class AbstractDocumentResult : DocumentResult { matchers.forEach { matcher -> val jsonPath = matcher.jsonPath val value = matcher.expectedValue - if (jsonPath.endsWith("[*]")) { - if (value !is List<*>) { - throw IllegalArgumentException("sample value type must be List") + when { + jsonPath.endsWith("[*]") -> { + if (value !is List<*>) { + throw IllegalArgumentException("sample value type must be List") + } + expect( + jsonPath.substringBeforeLast("[*]"), + Matchers.containsInAnyOrder(value.map { equalityMatcher(it) }) + ) + } + jsonPath.contains("[*]") -> { + expect(jsonPath, Matchers.hasItem(equalityMatcher(value))) } - expect(jsonPath.substringBeforeLast("[*]"), Matchers.containsInAnyOrder(*value.toTypedArray())) - } else if (jsonPath.contains("[*]") && !jsonPath.endsWith("[*]")) { - expect(jsonPath, Matchers.hasItem(value)) - } else { - expectValue(jsonPath, value) + else -> { + expect(jsonPath, equalityMatcher(value)) + } + } + } + } + + private fun equalityMatcher(expected: Any?): Matcher { + if (expected is Number) { + val expectedDecimal = BigDecimal(expected.toString()) + return object : BaseMatcher() { + override fun matches(actual: Any?): Boolean { + val number = actual as? Number ?: return false + return runCatching { + BigDecimal(number.toString()).compareTo(expectedDecimal) == 0 + }.getOrDefault(false) + } + + override fun describeTo(description: Description) { + description.appendText("a number numerically equal to ").appendValue(expected) + } + } + } + return object : BaseMatcher() { + override fun matches(actual: Any?): Boolean = actual == expected + + override fun describeTo(description: Description) { + description.appendValue(expected) } } } diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentResult.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentResult.kt index 436ddf4..ce21b09 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentResult.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentResult.kt @@ -2,7 +2,6 @@ package io.github.bgmsound.documentify.core.emitter import io.github.bgmsound.documentify.core.specification.schema.response.ResponseSpec import org.hamcrest.Matcher -import org.hamcrest.Matchers import org.springframework.test.web.reactive.server.WebTestClient class WebTestClientDocumentResult( @@ -12,10 +11,6 @@ class WebTestClientDocumentResult( actualResponse.jsonPath(jsonPath).value(matcher) } - override fun expectValue(jsonPath: String, value: Any) { - actualResponse.jsonPath(jsonPath).value(Matchers.equalToObject(value)) - } - companion object { fun WebTestClient.BodyContentSpec.validateWith(responseSpec: ResponseSpec) { WebTestClientDocumentResult(this).validateWith(responseSpec) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/SpecElement.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/SpecElement.kt index fab92c9..d105dd2 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/SpecElement.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/specification/element/SpecElement.kt @@ -1,6 +1,7 @@ package io.github.bgmsound.documentify.core.specification.element import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature import org.springframework.restdocs.snippet.AbstractDescriptor import java.time.LocalDate import java.time.LocalDateTime @@ -8,15 +9,14 @@ import java.time.LocalDateTime abstract class SpecElement( private val descriptor: AbstractDescriptor<*> ) { - private val objectMapper = ObjectMapper() - abstract val key: String val description get() = descriptor.description as String val sample: Any get() { - return if (descriptor.sample() is LocalDate || descriptor.sample() is LocalDateTime) { - objectMapper.writeValueAsString(descriptor.sample()) + val raw = descriptor.sample() + return if (raw is LocalDate || raw is LocalDateTime) { + OBJECT_MAPPER.writeValueAsString(raw).removeSurrounding("\"") } else { - descriptor.sample() + raw } } @@ -30,6 +30,10 @@ abstract class SpecElement( companion object { const val SAMPLE_KEY = "sample" + + private val OBJECT_MAPPER: ObjectMapper = ObjectMapper() + .findAndRegisterModules() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) } enum class Requirement { diff --git a/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/DocumentResultTest.kt b/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/DocumentResultTest.kt new file mode 100644 index 0000000..db4e3ce --- /dev/null +++ b/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/DocumentResultTest.kt @@ -0,0 +1,44 @@ +package io.github.bgmsound.documentify.core + +import io.github.bgmsound.documentify.core.emitter.AbstractDocumentResult +import io.github.bgmsound.documentify.core.specification.schema.response.ResponseSpec +import org.assertj.core.api.Assertions.assertThat +import org.hamcrest.Matcher +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class DocumentResultTest { + + private class CapturingDocumentResult : AbstractDocumentResult() { + val captured = mutableListOf>>() + override fun expect(jsonPath: String, matcher: Matcher<*>) { + captured += jsonPath to matcher + } + } + + private fun matcherFor(spec: ResponseSpec.() -> Unit): Matcher<*> { + val response = ResponseSpec().apply(spec) + val result = CapturingDocumentResult() + result.validateWith(response) + return result.captured.single().second + } + + @Test + fun `numeric matcher treats integer long and big decimal as equal`() { + val matcher = matcherFor { body { field("amount", "amount", 1L) } } + + assertThat(matcher.matches(1)).isTrue() + assertThat(matcher.matches(1L)).isTrue() + assertThat(matcher.matches(BigDecimal.ONE)).isTrue() + assertThat(matcher.matches(1.0)).isTrue() + assertThat(matcher.matches(2)).isFalse() + } + + @Test + fun `non numeric value uses strict equality`() { + val matcher = matcherFor { body { field("name", "name", "John") } } + + assertThat(matcher.matches("John")).isTrue() + assertThat(matcher.matches("Jane")).isFalse() + } +} diff --git a/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/FieldTest.kt b/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/FieldTest.kt index d793123..230236a 100644 --- a/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/FieldTest.kt +++ b/documentify-project/documentify-core/src/test/kotlin/io/github/bgmsound/documentify/core/FieldTest.kt @@ -77,6 +77,20 @@ class FieldTest { assertThat(color.build().single().type).isEqualTo(JsonFieldType.STRING) } + @Test + fun `local date sample serializes to iso string without quotes`() { + val field = ResponseBodySpec().field("date", "date", LocalDate.of(2024, 1, 1)) + + assertThat(field.sample).isEqualTo("2024-01-01") + } + + @Test + fun `local date time sample serializes to iso string without quotes`() { + val field = ResponseBodySpec().field("dateTime", "dateTime", LocalDateTime.of(2024, 1, 1, 12, 30, 45)) + + assertThat(field.sample).isEqualTo("2024-01-01T12:30:45") + } + @Test fun `adding a child to a scalar field throws`() { val name = ResponseBodySpec().field("name", "name", "John") From 14bc5c5ab3379d1b6b0e160618187cf3286dfc74 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Wed, 1 Jul 2026 15:50:27 +0900 Subject: [PATCH 7/8] Refactor WebTestClientDocumentEmitter to improve header handling by replacing multi-value map with explicit key-value addition --- .../documentify/core/emitter/WebTestClientDocumentEmitter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt index e248ce5..2e66041 100644 --- a/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt +++ b/documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/WebTestClientDocumentEmitter.kt @@ -39,7 +39,9 @@ class WebTestClientDocumentEmitter( .method(method()) .uri(uriTemplate, samplePathVariables + queryVariables) .headers { headers -> - headers.addAll(sampleHeaders.toMultiValueMap()) + sampleHeaders.forEach { (key, value) -> + headers.add(key, value.toString()) + } } .bodyIfExist(sampleFields) .exchange() From 4c7e15a5b4083970820f4c280a7e038a479ec4d1 Mon Sep 17 00:00:00 2001 From: Gyumin Bae Date: Wed, 1 Jul 2026 15:50:40 +0900 Subject: [PATCH 8/8] Bump project version to 1.4.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ed92657..839a03d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ project.name=documentify project.group=io.github.bgmsound -project.version.id=1.4.0 +project.version.id=1.4.1 project.artifact=documentify project.description=Documentify is a tool to generate API documentation from source code.