Skip to content

Commit 1802a4b

Browse files
authored
Merge pull request #21 from BGMSound/develop
[Feature] Support custom document emitter
2 parents 95b842c + f46f695 commit 1802a4b

38 files changed

Lines changed: 307 additions & 172 deletions

File tree

.github/workflows/deploy-to-mavencentral.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111
MAVEN_CENTRAL_PASSWORD:
1212
required: true
1313
jobs:
14-
publish-to-maven-central:
14+
publish-to-mavencentral:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: GitHub 리포지토리 체크아웃

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# 📝 Documentify
2+
[![Kotlin](https://img.shields.io/badge/kotlin-2.0.0-blue.svg?logo=kotlin)](http://kotlinlang.org)
23
![Latest Release](https://img.shields.io/github/v/release/BGMSound/documentify)
34
[![Apache 2.0 license](https://img.shields.io/badge/License-APACHE%202.0-green.svg?logo=APACHE&style=flat)](https://opensource.org/licenses/Apache-2.0)
45
<br>
@@ -35,7 +36,7 @@ fun setUp(provider: RestDocumentationContextProvider) {
3536
```
3637
You can also set up the test environment with an application context or an auto-configured MockMvc (or WebTestClient).
3738
<br><br>
38-
`Mvc Example`
39+
`MVC Example`
3940
```kotlin
4041
webApplicationContext(provider, context)
4142
mockMvc(provider, mockMvc)
@@ -77,11 +78,29 @@ fun documentationGetApi() {
7778
}
7879
```
7980

81+
Additional validation of the mock response generated during the tests for document creation is also possible.
82+
```kotlin
83+
@Test
84+
fun documentationGetApi() {
85+
documentation("test-get-api") {
86+
information {
87+
summary("test get api")
88+
description("this is test get api")
89+
tag("test")
90+
}
91+
requestLine(Method.GET, "/api/test/{path}")
92+
responseBody {
93+
field("testField", "test", "test")
94+
}
95+
}.expect(jsonPath("$testField").value("test"))
96+
}
97+
```
98+
8099
### Generate OpenAPI Specification
81100
After setting up the test environment and writing the test code, run the test.
82101
The OpenAPI specification document will be generated in the `build/generated-snippets` directory.
83102

84-
First, apply documentify plugin to your `build.gradle.kts` file:
103+
First, apply documentify plugin to your `build.gradle.kts` file *(need gradle plugin portal)*:
85104
```kotlin
86105
plugins {
87106
id("io.github.bgmsound.documentify") version "${version}"
@@ -112,3 +131,6 @@ you can also create Postman collection by running the following command:
112131

113132
## Documentify Development Story
114133
If you want to check out the development story of Documentify, please refer to the [blog post](https://bgmsound.medium.com/documentify-선언형-rest-docs-dsl-제작기-0a09f651be2c).
134+
135+
## License
136+
documentify is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html).

build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ version = extra["project.version.id"] as String
1919

2020
val publicModulePathSet = setOf(
2121
rootProject.projects.documentifyProject.documentifyCore.identityPath.path,
22-
rootProject.projects.documentifyProject.documentifyPlugin.identityPath.path,
22+
rootProject.projects.documentifyProject.documentifyGradlePlugin.identityPath.path,
2323
rootProject.projects.documentifyProject.documentifyMvc.identityPath.path,
2424
rootProject.projects.documentifyProject.documentifyReactive.identityPath.path,
2525
rootProject.projects.documentifyStarters.documentifyStarterMvc.identityPath.path,
@@ -67,7 +67,7 @@ subprojects {
6767
configure<MavenPublishBaseExtension> {
6868
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
6969

70-
val artifactId = name.replace("-plugin", ".gradle.plugin")
70+
val artifactId = name.replace("-gradle-plugin", ".gradle.plugin")
7171
val projectGroup = property("project.group").toString()
7272
val projectName = property("project.name").toString()
7373
val projectVersion = property("project.version.id").toString()

documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/AbstractDocumentEmitter.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import org.springframework.restdocs.snippet.Snippet
1010

1111
abstract class AbstractDocumentEmitter(
1212
protected val provider: RestDocumentationContextProvider,
13-
protected val documentSpec: DocumentSpec
14-
) {
13+
protected val documentSpec: DocumentSpec,
14+
protected val sampleAggregator: DocumentSpecSampleAggregator = DefaultDocumentSpecSampleAggregator
15+
) : DocumentEmitter {
1516
protected fun ResponseSpec.buildResource(index: Int): Snippet {
1617
val resourceBuilder = ResourceSnippetParameters.builder()
1718
if (documentSpec.tags.isNotEmpty()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.github.bgmsound.documentify.core.emitter
2+
3+
import io.github.bgmsound.documentify.core.specification.element.field.Field
4+
import io.github.bgmsound.documentify.core.specification.schema.response.ResponseSpec
5+
6+
abstract class AbstractDocumentResult : DocumentResult {
7+
abstract fun validateJsonPath(jsonResultMatcher: JsonResultMatcher)
8+
9+
override fun validateWith(responseSpec: ResponseSpec) {
10+
val matchers = aggregateMatchers(responseSpec.fields)
11+
if (matchers.isEmpty()) {
12+
return
13+
}
14+
matchers.forEach { matcher ->
15+
validateJsonPath(matcher)
16+
}
17+
}
18+
19+
private fun aggregateMatchers(fields: List<Field>): List<JsonResultMatcher> {
20+
return fields.filter {
21+
it.hasSample() || it.canHaveChild() || !it.isIgnored()
22+
}.flatMap {
23+
it.aggregateMatchers()
24+
}
25+
}
26+
27+
private fun Field.aggregateMatchers(): List<JsonResultMatcher> {
28+
if (isIgnored()) {
29+
return emptyList()
30+
}
31+
if (!hasSample() && !canHaveChild()) {
32+
return emptyList()
33+
}
34+
val matchers = mutableListOf<JsonResultMatcher>()
35+
if (hasSample()) {
36+
val jsonPath = StringBuilder("$.${path}").apply {
37+
if (isArray()) {
38+
append("[*]")
39+
}
40+
}.toString().replace("[]", "[*]")
41+
matchers.add(JsonResultMatcher.of(jsonPath, sample))
42+
} else if (childFields().isNotEmpty()) {
43+
val childMatchers = aggregateMatchers(childFields())
44+
matchers.addAll(childMatchers)
45+
}
46+
return matchers
47+
}
48+
}

documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/SpecElementSampleAssociater.kt renamed to documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/DefaultDocumentSpecSampleAggregator.kt

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,26 @@ package io.github.bgmsound.documentify.core.emitter
33
import io.github.bgmsound.documentify.core.specification.element.SpecElement
44
import io.github.bgmsound.documentify.core.specification.element.field.Field
55

6-
object SpecElementSampleAssociater {
7-
fun List<Field>.associatedFieldSample(): Map<String, Any> {
8-
return filter {
6+
object DefaultDocumentSpecSampleAggregator : DocumentSpecSampleAggregator {
7+
@Suppress("UNCHECKED_CAST")
8+
override fun <T : SpecElement> aggregate(specElements: List<T>): Map<String, Any> {
9+
if (specElements.isEmpty()) {
10+
return emptyMap()
11+
}
12+
if (!specElements.isField()) {
13+
return specElements.associate {
14+
it.key to it.sample
15+
}
16+
}
17+
val fieldElements = specElements as List<Field>
18+
return fieldElements.filter {
919
it.hasSample() || it.canHaveChild() || !it.isIgnored()
1020
}.associate {
11-
it.associatedSample()
21+
it.aggregateSample()
1222
}
1323
}
1424

15-
fun Field.associatedSample(): Pair<String, Any> {
25+
private fun Field.aggregateSample(): Pair<String, Any> {
1626
if (isIgnored()) {
1727
throw IllegalStateException("can't associate ignored field $key")
1828
}
@@ -25,7 +35,7 @@ object SpecElementSampleAssociater {
2535
if (childFields().isEmpty()) {
2636
throw IllegalStateException("Field $key must have child fields")
2737
}
28-
val sample = childFields().associate { it.associatedSample() }
38+
val sample = childFields().associate { it.aggregateSample() }
2939
if (isArray()) {
3040
listOf(sample)
3141
} else {
@@ -34,9 +44,9 @@ object SpecElementSampleAssociater {
3444
}
3545
}
3646

37-
fun List<SpecElement>.associatedSample(): Map<String, Any> {
38-
return associate {
39-
it.key to it.sample
47+
private fun List<SpecElement>.isField(): Boolean {
48+
return all {
49+
it is Field
4050
}
4151
}
4252
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.github.bgmsound.documentify.core.emitter
2+
3+
interface DocumentEmitter
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.bgmsound.documentify.core.emitter
2+
3+
import io.github.bgmsound.documentify.core.specification.schema.response.ResponseSpec
4+
5+
interface DocumentResult {
6+
7+
fun validateWith(responseSpec: ResponseSpec)
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.bgmsound.documentify.core.emitter
2+
3+
import io.github.bgmsound.documentify.core.specification.element.SpecElement
4+
5+
interface DocumentSpecSampleAggregator{
6+
7+
fun <T : SpecElement> aggregate(specElements: List<T>) : Map<String, Any>
8+
9+
}

documentify-project/documentify-core/src/main/kotlin/io/github/bgmsound/documentify/core/emitter/FieldJsonMatcherAssociater.kt

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)