From 77e6e100c8f5a52aa71496236274c9f372d92786 Mon Sep 17 00:00:00 2001 From: tangcent Date: Sun, 19 Nov 2023 17:57:14 +0800 Subject: [PATCH] feat: Add support for exporting APIs as .http files --- .../api/export/http/HttpClientExporter.kt | 81 +++ .../api/export/http/HttpClientFileSaver.kt | 63 ++ .../api/export/http/HttpClientFormatter.kt | 176 +++++ .../plugin/api/export/suv/SuvApiExporter.kt | 63 +- .../api/export/http/HttpClientExporterTest.kt | 134 ++++ .../export/http/HttpClientFormatterTest.kt | 148 ++++ .../itangcent/mock/ConstantModuleHelper.kt | 10 +- ...tp.HttpClientFileSaverTest.existedFile.txt | 101 +++ ...t.http.HttpClientFileSaverTest.newFile.txt | 96 +++ ...pClientFormatterTest.testParseRequests.txt | 656 +++++++++++++++++ ...tterTest.testParseRequestsToExistedDoc.txt | 661 ++++++++++++++++++ 11 files changed, 2172 insertions(+), 17 deletions(-) create mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporter.kt create mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFileSaver.kt create mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatter.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporterTest.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatterTest.kt create mode 100644 idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.existedFile.txt create mode 100644 idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.newFile.txt create mode 100644 idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequests.txt create mode 100644 idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequestsToExistedDoc.txt diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporter.kt new file mode 100644 index 000000000..b10bd2a29 --- /dev/null +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporter.kt @@ -0,0 +1,81 @@ +package com.itangcent.idea.plugin.api.export.http + +import com.google.inject.Inject +import com.google.inject.Singleton +import com.itangcent.cache.HttpContextCacheHelper +import com.itangcent.common.logger.traceError +import com.itangcent.common.model.Request +import com.itangcent.idea.plugin.api.export.core.FormatFolderHelper +import com.itangcent.idea.utils.ModuleHelper +import com.itangcent.intellij.logger.Logger + +/** + * export requests as httpClient command + * @author tangcent + */ +@Singleton +class HttpClientExporter { + + @Inject + private lateinit var httpClientFormatter: HttpClientFormatter + + @Inject + private lateinit var httpClientFileSaver: HttpClientFileSaver + + @Inject + private lateinit var logger: Logger + + @Inject + private lateinit var moduleHelper: ModuleHelper + + @Inject + private lateinit var formatFolderHelper: FormatFolderHelper + + @Inject + private lateinit var httpContextCacheHelper: HttpContextCacheHelper + + fun export(requests: List) { + try { + if (requests.isEmpty()) { + logger.info("No api be found to export!") + return + } + exportToFile(requests) + } catch (e: Exception) { + logger.traceError("Apis save failed", e) + } + } + + private fun exportToFile(requests: List) { + // 1. Group requests by module and folder + val moduleFolderRequestMap = mutableMapOf>>() + + for (request in requests) { + val module = moduleHelper.findModule(request.resource!!) ?: "easy-yapi" + val folder = formatFolderHelper.resolveFolder(request.resource!!) + val folderRequestMap = moduleFolderRequestMap.getOrPut(module) { mutableMapOf() } + folderRequestMap.getOrPut(folder.name ?: "apis") { mutableListOf() }.add(request) + } + + // 2. Process each module + for ((module, folderRequestMap) in moduleFolderRequestMap) { + // 3. Process each folder within the module + for ((folder, folderRequests) in folderRequestMap) { + val host = httpContextCacheHelper.selectHost("Select Host For $module") + httpClientFileSaver.saveAndOpenHttpFile(module, "$folder.http") { existedContent -> + if (existedContent == null) { + httpClientFormatter.parseRequests( + host = host, requests = folderRequests + ) + } else { + httpClientFormatter.parseRequests( + existedDoc = existedContent, + host = host, + requests = folderRequests + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFileSaver.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFileSaver.kt new file mode 100644 index 000000000..e04dfec89 --- /dev/null +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFileSaver.kt @@ -0,0 +1,63 @@ +package com.itangcent.idea.plugin.api.export.http + +import com.google.inject.Inject +import com.google.inject.Singleton +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.io.createDirectories +import com.intellij.util.io.readText +import com.itangcent.intellij.context.ActionContext +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.writeText + +/** + * @author tangcent + */ +@Singleton +class HttpClientFileSaver { + + @Inject + private lateinit var project: Project + + @Inject + private lateinit var actionContext: ActionContext + + private val scratchesPath: Path by lazy { Paths.get(PathManager.getConfigPath(), "scratches") } + + private val localFileSystem by lazy { LocalFileSystem.getInstance() } + + fun saveAndOpenHttpFile( + module: String, + fileName: String, + content: (String?) -> String, + ) { + val file = saveHttpFile(module, fileName, content) + openInEditor(file) + } + + private fun saveHttpFile( + module: String, + fileName: String, + content: (String?) -> String, + ): VirtualFile { + val file = scratchesPath.resolve(module).resolve(fileName).apply { + parent.createDirectories() + } + file.writeText(content(file.takeIf { Files.exists(it) }?.readText())) + + return (localFileSystem.refreshAndFindFileByPath(file.toString()) + ?: throw IOException("Unable to find file: $file")) + } + + private fun openInEditor(file: VirtualFile) { + actionContext.runInWriteUI { + FileEditorManager.getInstance(project).openFile(file, true) + } + } +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatter.kt new file mode 100644 index 000000000..97937cfed --- /dev/null +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatter.kt @@ -0,0 +1,176 @@ +package com.itangcent.idea.plugin.api.export.http + +import com.google.inject.Inject +import com.google.inject.Singleton +import com.itangcent.common.model.Request +import com.itangcent.common.model.getContentType +import com.itangcent.common.utils.IDUtils +import com.itangcent.http.RequestUtils +import com.itangcent.idea.psi.resource +import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.psi.PsiClassUtils + +/** + * @author tangcent + */ +@Singleton +class HttpClientFormatter { + + @Inject + private lateinit var actionContext: ActionContext + + companion object { + const val REF = "### ref: " + } + + fun parseRequests( + host: String, + requests: List + ): String { + val sb = StringBuilder() + for (request in requests) { + parseRequest(host, request, sb) + } + return sb.toString() + } + + fun parseRequests( + existedDoc: String, + host: String, + requests: List + ): String { + val docs = splitDoc(existedDoc) + val requestMap = requests.associateBy { + it.ref() + } + val sb = StringBuilder() + + // Process and update existing entries + for (doc in docs) { + val request = requestMap[doc.first] + if (request != null) { + parseRequest(host, request, sb) + } else { + sb.append(REF).append(doc.first).append("\n") + .append(doc.second) + } + } + + // Process new requests + val processedRefs = docs.map { it.first }.toSet() + requests.filter { request -> + request.ref() !in processedRefs + }.forEach { request -> + parseRequest(host, request, sb) + } + + return sb.toString().trimEnd('\n', '#', ' ') + } + + private fun splitDoc(doc: String): List { + val refDocs = mutableListOf() + val lines = doc.lines() + var ref: String? = null + val sb = StringBuilder() + for (line in lines) { + if (line.startsWith(REF)) { + if (ref != null) { + refDocs.add(RefDoc(ref, sb.toString())) + sb.clear() + } + ref = line.substring(REF.length) + } else { + sb.append(line).append("\n") + } + } + if (ref != null) { + refDocs.add(RefDoc(ref, sb.toString())) + } + return refDocs + } + + private fun parseRequest( + host: String, + request: Request, + sb: StringBuilder + ) { + sb.appendRef(request) + val apiName = request.name ?: (request.method + ":" + request.path?.url()) + sb.append("### $apiName\n\n") + if (!request.desc.isNullOrEmpty()) { + request.desc!!.lines().forEach { + sb.append("// ").append(it).append("\n") + } + } + + sb.append(request.method).append(" ") + .append(RequestUtils.concatPath(host, request.path?.url() ?: "")) + if (!request.querys.isNullOrEmpty()) { + val query = request.querys!!.joinToString("&") { "${it.name}=${it.value ?: ""}" } + if (query.isNotEmpty()) { + sb.append("?").append(query) + } + } + + sb.append("\n") + + request.headers?.forEach { header -> + sb.appendHeader(header.name ?: "", header.value) + } + + val contentType = request.getContentType() + when { + contentType?.contains("application/json") == true -> { + request.body?.let { body -> + sb.append("\n") + sb.append(RequestUtils.parseRawBody(body)) + } + } + + contentType?.contains("application/x-www-form-urlencoded") == true -> { + request.formParams?.let { formParams -> + val formData = formParams.joinToString("&") { "${it.name}=${it.value ?: ""}" } + sb.append("\n") + sb.append(formData) + } + } + + contentType?.contains("multipart/form-data") == true -> { + request.formParams?.let { formParams -> + sb.append("\n") + sb.append("Content-Type: multipart/form-data; boundary=WebAppBoundary\n") + for (param in formParams) { + sb.append("\n--WebAppBoundary\n") + if (param.type == "file") { + sb.append("Content-Disposition: form-data; name=\"${param.name}\"; filename=\"${param.value ?: "file"}\"\n") + sb.append("\n< ./relative/path/to/${param.value ?: "file"}\n") + } else { + sb.append("Content-Disposition: form-data; name=\"${param.name}\"\n") + sb.append("\n${param.value ?: "[${param.name}]"}\n") + } + sb.append("--WebAppBoundary--\n") + } + } + } + } + + sb.appendEnd() + } + + private fun StringBuilder.appendEnd() { + append("\n\n###\n\n") + } + + private fun StringBuilder.appendHeader(name: String, value: String?) = + append(name).append(": ").append(value ?: "").append("\n") + + private fun StringBuilder.appendRef(request: Request) = + append(REF).append(request.ref()) + .append("\n") + + private fun Request.ref(): String = resource()?.let { + actionContext.callInReadUI { PsiClassUtils.fullNameOfMember(it) } + } ?: IDUtils.shortUUID() +} + +typealias RefDoc = Pair diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt index 997bd4b8b..d681254bb 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt @@ -21,6 +21,7 @@ import com.itangcent.idea.plugin.api.export.ExportChannel import com.itangcent.idea.plugin.api.export.ExportDoc import com.itangcent.idea.plugin.api.export.core.* import com.itangcent.idea.plugin.api.export.curl.CurlExporter +import com.itangcent.idea.plugin.api.export.http.HttpClientExporter import com.itangcent.idea.plugin.api.export.markdown.MarkdownFormatter import com.itangcent.idea.plugin.api.export.postman.* import com.itangcent.idea.plugin.api.export.yapi.* @@ -30,7 +31,6 @@ import com.itangcent.idea.plugin.rule.SuvRuleParser import com.itangcent.idea.plugin.settings.SettingBinder import com.itangcent.idea.plugin.settings.helper.* import com.itangcent.idea.psi.PsiResource -import com.itangcent.idea.swing.MessagesHelper import com.itangcent.idea.utils.CustomizedPsiClassHelper import com.itangcent.idea.utils.FileSaveHelper import com.itangcent.idea.utils.RuleComputeListenerRegistry @@ -55,7 +55,6 @@ import com.itangcent.suv.http.HttpClientProvider import java.util.* import kotlin.reflect.KClass import kotlin.reflect.full.createInstance -import kotlin.streams.toList open class SuvApiExporter { @@ -568,18 +567,9 @@ open class SuvApiExporter { class CurlApiExporterAdapter : ApiExporterAdapter() { - @Inject - private val fileSaveHelper: FileSaveHelper? = null - @Inject private lateinit var curlExporter: CurlExporter - @Inject - private lateinit var markdownSettingsHelper: MarkdownSettingsHelper - - @Inject - private lateinit var messagesHelper: MessagesHelper - override fun actionName(): String { return "CurlExportAction" } @@ -622,6 +612,54 @@ open class SuvApiExporter { } } + + class HttpClientApiExporterAdapter : ApiExporterAdapter() { + + @Inject + private lateinit var httpClientExporter: HttpClientExporter + + override fun actionName(): String { + return "HttpClientExportAction" + } + + override fun afterBuildActionContext( + actionContext: ActionContext, + builder: ActionContext.ActionContextBuilder, + ) { + super.afterBuildActionContext(actionContext, builder) + + builder.bind(LocalFileRepository::class) { it.with(DefaultLocalFileRepository::class).singleton() } + + builder.bind(ClassExporter::class) { it.with(CompositeClassExporter::class).singleton() } + + builder.bindInstance(ExportDoc::class, ExportDoc.of("request")) + + builder.bind(ConfigReader::class, "delegate_config_reader") { + it.with(EasyApiConfigReader::class).singleton() + } + builder.bind(ConfigReader::class) { it.with(RecommendConfigReader::class).singleton() } + + //always not read api from cache + builder.bindInstance("class.exporter.read.cache", false) + + builder.bindInstance("file.save.default", "easy-api-httpClient.http") + builder.bindInstance("file.save.last.location.key", "com.itangcent.httpClient.export.path") + } + + override fun doExportDocs(docs: MutableList) { + val requests = docs.filterAs(Request::class) + try { + if (docs.isEmpty()) { + logger!!.info("No api be found to export!") + return + } + httpClientExporter.export(requests) + } catch (e: Exception) { + logger!!.traceError("Apis save failed", e) + } + } + } + private fun doExport(channel: ApiExporterWrapper, requests: List) { if (requests.isEmpty()) { logger.info("no api has be selected") @@ -638,7 +676,8 @@ open class SuvApiExporter { ApiExporterWrapper(YapiApiExporterAdapter::class, "Yapi", Request::class, MethodDoc::class), ApiExporterWrapper(PostmanApiExporterAdapter::class, "Postman", Request::class), ApiExporterWrapper(MarkdownApiExporterAdapter::class, "Markdown", Request::class, MethodDoc::class), - ApiExporterWrapper(CurlApiExporterAdapter::class, "Curl", Request::class) + ApiExporterWrapper(CurlApiExporterAdapter::class, "Curl", Request::class), + ApiExporterWrapper(HttpClientApiExporterAdapter::class, "HttpClient", Request::class) ) } } \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporterTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporterTest.kt new file mode 100644 index 000000000..29380ddc6 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientExporterTest.kt @@ -0,0 +1,134 @@ +package com.itangcent.idea.plugin.api.export.http + +import com.google.inject.Inject +import com.intellij.openapi.application.PathManager +import com.intellij.psi.PsiClass +import com.itangcent.common.model.Request +import com.itangcent.common.utils.forceDelete +import com.itangcent.idea.plugin.api.export.core.ClassExporter +import com.itangcent.idea.plugin.api.export.core.requestOnly +import com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporter +import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.guice.singleton +import com.itangcent.intellij.extend.guice.with +import com.itangcent.intellij.extend.withBoundary +import com.itangcent.test.ResultLoader +import com.itangcent.testFramework.PluginContextLightCodeInsightFixtureTestCase +import java.nio.file.Path +import java.nio.file.Paths +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* + + +/** + * Test case of [HttpClientExporter] + * + * @author tangcent + */ +class HttpClientExporterTest : PluginContextLightCodeInsightFixtureTestCase() { + + @Inject + private lateinit var httpClientExporter: HttpClientExporter + + @Inject + private lateinit var classExporter: ClassExporter + + private lateinit var userCtrlPsiClass: PsiClass + + override fun beforeBind() { + super.beforeBind() + loadSource(Void::class) + loadSource(Object::class) + loadSource(java.lang.String::class) + loadSource(java.lang.Integer::class) + loadSource(java.lang.Long::class) + loadSource(Collection::class) + loadSource(Map::class) + loadSource(List::class) + loadSource(LinkedList::class) + loadSource(LocalDate::class) + loadSource(LocalDateTime::class) + loadSource(HashMap::class) + loadFile("annotation/Public.java") + loadFile("constant/UserType.java") + loadFile("model/IResult.java") + loadFile("model/Result.java") + loadFile("model/UserInfo.java") + loadFile("spring/ModelAttribute.java") + loadFile("spring/PutMapping.java") + loadFile("spring/PostMapping.java") + loadFile("spring/GetMapping.java") + loadFile("spring/RequestMapping.java") + loadFile("spring/RequestBody.java") + loadFile("spring/RestController.java") + loadFile("api/BaseController.java") + userCtrlPsiClass = loadClass("api/UserCtrl.java")!! + } + + override fun customConfig(): String { + return "#The ObjectId and Date will be parsed as strings\n" + + "json.rule.convert[org.bson.types.ObjectId]=java.lang.String\n" + + "json.rule.convert[java.util.Date]=java.lang.String\n" + + "json.rule.convert[java.sql.Timestamp]=java.lang.String\n" + + "json.rule.convert[java.time.LocalDateTime]=java.lang.String\n" + + "json.rule.convert[java.time.LocalDate]=java.lang.String\n" + } + + override fun bind(builder: ActionContext.ActionContextBuilder) { + super.bind(builder) + + builder.bind(ClassExporter::class) { it.with(SpringRequestClassExporter::class).singleton() } + } + + private val scratchesPath: Path by lazy { Paths.get(PathManager.getConfigPath(), "scratches") } + + fun testSaveAndOpenHttpFile() { + val targetFile = "$scratchesPath/test_default/apis about user.http" + Path.of(targetFile).toFile().forceDelete() + + val requests = ArrayList() + + actionContext.withBoundary { + classExporter.export(userCtrlPsiClass, requestOnly { + requests.add(it) + }) + } + + assertNoThrowable { httpClientExporter.export(emptyList()) } + + httpClientExporter.export(requests) + + assertEquals( + ResultLoader.load("newFile"), Path.of(targetFile).toFile().readText() + ) + + + val existedDoc = """ +### ref: com.itangcent.api.UserCtrl#greeting() +### say hello + +// anything here will be covered +// anything here will be covered +GET http://localhost:8080/user/greeting + + +### + +### ref: com.itangcent.api.UserCtrl#notExisted(java.lang.Long) +### api not existed in userCtrl should be kept + +GET http://localhost:8080/user/notExisted +token: + + +### +""" + Path.of(targetFile).toFile().writeText(existedDoc) + httpClientExporter.export(requests) + + assertEquals( + ResultLoader.load("existedFile"), Path.of(targetFile).toFile().readText() + ) + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatterTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatterTest.kt new file mode 100644 index 000000000..a4a29e826 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/http/HttpClientFormatterTest.kt @@ -0,0 +1,148 @@ +package com.itangcent.idea.plugin.api.export.http + +import com.google.inject.Inject +import com.intellij.psi.PsiClass +import com.itangcent.common.model.Request +import com.itangcent.idea.plugin.api.export.core.ClassExporter +import com.itangcent.idea.plugin.api.export.core.requestOnly +import com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporter +import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.guice.singleton +import com.itangcent.intellij.extend.guice.with +import com.itangcent.test.ResultLoader +import com.itangcent.testFramework.PluginContextLightCodeInsightFixtureTestCase +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* + +/** + * Test case of [HttpClientFormatter] + * + * @author tangcent + */ +internal class HttpClientFormatterTest : PluginContextLightCodeInsightFixtureTestCase() { + + companion object { + const val HOST = "http://localhost:8080" + } + + @Inject + internal lateinit var classExporter: ClassExporter + + @Inject + protected lateinit var httpClientFormatter: HttpClientFormatter + + protected lateinit var userCtrlPsiClass: PsiClass + + protected lateinit var testCtrlPsiClass: PsiClass + + override fun beforeBind() { + super.beforeBind() + loadSource(Void::class) + loadSource(Object::class) + loadSource(java.lang.String::class) + loadSource(java.lang.Integer::class) + loadSource(java.lang.Long::class) + loadSource(Collection::class) + loadSource(Map::class) + loadSource(List::class) + loadSource(LinkedList::class) + loadSource(LocalDate::class) + loadSource(LocalDateTime::class) + loadSource(HashMap::class) + loadFile("annotation/Public.java") + loadFile("constant/UserType.java") + loadFile("model/Node.java") + loadFile("model/Root.java") + loadFile("model/CustomMap.java") + loadFile("model/PageRequest.java") + loadFile("model/IResult.java") + loadFile("model/Result.java") + loadFile("model/UserInfo.java") + loadFile("spring/ModelAttribute.java") + loadFile("spring/PostMapping.java") + loadFile("spring/PutMapping.java") + loadFile("spring/GetMapping.java") + loadFile("spring/RequestBody.java") + loadFile("spring/RequestHeader.java") + loadFile("spring/RequestMapping.java") + loadFile("spring/RequestParam.java") + loadFile("spring/RestController.java") + loadFile("api/BaseController.java") + userCtrlPsiClass = loadClass("api/UserCtrl.java")!! + testCtrlPsiClass = loadClass("api/TestCtrl.java")!! + } + + override fun bind(builder: ActionContext.ActionContextBuilder) { + super.bind(builder) + + builder.bind(ClassExporter::class) { it.with(SpringRequestClassExporter::class).singleton() } + } + + override fun customConfig(): String { + return "method.additional.header[!@com.itangcent.annotation.Public]={name: \"token\",value: \"\",desc: \"auth token\",required:true, demo:\"123456\"}\n" + + "#[converts]*\n" + + "#The ObjectId and Date will be parsed as strings\n" + + "json.rule.convert[org.bson.types.ObjectId]=java.lang.String\n" + + "json.rule.convert[java.util.Date]=java.lang.String\n" + + "json.rule.convert[java.sql.Timestamp]=java.lang.String\n" + + "json.rule.convert[java.time.LocalDateTime]=java.lang.String\n" + + "json.rule.convert[java.time.LocalDate]=java.lang.String" + } + + fun testParseRequests() { + val requests = ArrayList(); + val boundary = actionContext.createBoundary(); + classExporter.export(userCtrlPsiClass, requestOnly { + requests.add(it); + }); + boundary.waitComplete(false); + classExporter.export(testCtrlPsiClass, requestOnly { + requests.add(it); + }); + boundary.waitComplete(); + + assertEquals(ResultLoader.load("testParseRequests"), httpClientFormatter.parseRequests(HOST, requests)); + } + + fun testParseRequestsToExistedDoc() { + val requests = ArrayList(); + val boundary = actionContext.createBoundary(); + classExporter.export(userCtrlPsiClass, requestOnly { + requests.add(it); + }); + boundary.waitComplete(false); + classExporter.export(testCtrlPsiClass, requestOnly { + requests.add(it); + }); + boundary.waitComplete(); + + val existedDoc = """ +### ref: com.itangcent.api.UserCtrl#greeting() +### say hello + +// anything here will be covered +// anything here will be covered +GET http://localhost:8080/user/greeting + + +### + +### ref: com.itangcent.api.UserCtrl#notExisted(java.lang.Long) +### api not existed in userCtrl should be kept + +GET http://localhost:8080/user/notExisted +token: + + +### +""" + + assertEquals( + ResultLoader.load("testParseRequestsToExistedDoc"), + httpClientFormatter.parseRequests( + existedDoc, HOST, requests + ) + ); + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/mock/ConstantModuleHelper.kt b/idea-plugin/src/test/kotlin/com/itangcent/mock/ConstantModuleHelper.kt index 02658b536..6727cc402 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/mock/ConstantModuleHelper.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/mock/ConstantModuleHelper.kt @@ -9,23 +9,23 @@ import com.itangcent.idea.utils.ModuleHelper @Singleton class ConstantModuleHelper(private val module: String) : ModuleHelper { - override fun findModule(resource: Any): String? { + override fun findModule(resource: Any): String { return module } - override fun findModule(psiMethod: PsiMethod): String? { + override fun findModule(psiMethod: PsiMethod): String { return module } - override fun findModule(cls: PsiClass): String? { + override fun findModule(cls: PsiClass): String { return module } - override fun findModule(psiFile: PsiFile): String? { + override fun findModule(psiFile: PsiFile): String { return module } - override fun findModuleByPath(path: String?): String? { + override fun findModuleByPath(path: String?): String { return module } diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.existedFile.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.existedFile.txt new file mode 100644 index 000000000..8dd27da70 --- /dev/null +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.existedFile.txt @@ -0,0 +1,101 @@ +### ref: com.itangcent.api.UserCtrl#greeting() +### say hello + +// not update anything +GET http://localhost:8080/user/greeting + + +### + +### ref: com.itangcent.api.UserCtrl#notExisted(java.lang.Long) +### api not existed in userCtrl should be kept + +GET http://localhost:8080/user/notExisted +token: + + +### + +### ref: com.itangcent.api.BaseController#ctrlName() +### current ctrl name + +GET http://localhost:8080/user/ctrl/name + + +### + +### ref: com.itangcent.api.UserCtrl#get(java.lang.Long) +### get user info + +GET http://localhost:8080/user/get/{id}?id=0 + + +### + +### ref: com.itangcent.api.UserCtrl#create(com.itangcent.model.UserInfo) +### create an user + +POST http://localhost:8080/user/add +Content-Type: application/json + +{ + "id": 0, + "type": 0, + "name": "", + "age": 0, + "sex": 0, + "birthDay": "", + "regtime": "" +} + +### + +### ref: com.itangcent.api.UserCtrl#update(com.itangcent.model.UserInfo) +### update user info + +PUT http://localhost:8080/user/update +Content-Type: multipart/form-data + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="id" + +[id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="type" + +[type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="name" + +[name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="age" + +[age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sex" + +[sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="birthDay" + +[birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="regtime" + +[regtime] +--WebAppBoundary-- \ No newline at end of file diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.newFile.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.newFile.txt new file mode 100644 index 000000000..ce585c5e0 --- /dev/null +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFileSaverTest.newFile.txt @@ -0,0 +1,96 @@ +### ref: com.itangcent.api.BaseController#ctrlName() +### current ctrl name + +GET http://localhost:8080/user/ctrl/name + + +### + +### ref: com.itangcent.api.UserCtrl#greeting() +### say hello + +// not update anything +GET http://localhost:8080/user/greeting + + +### + +### ref: com.itangcent.api.UserCtrl#get(java.lang.Long) +### get user info + +GET http://localhost:8080/user/get/{id}?id=0 + + +### + +### ref: com.itangcent.api.UserCtrl#create(com.itangcent.model.UserInfo) +### create an user + +POST http://localhost:8080/user/add +Content-Type: application/json + +{ + "id": 0, + "type": 0, + "name": "", + "age": 0, + "sex": 0, + "birthDay": "", + "regtime": "" +} + +### + +### ref: com.itangcent.api.UserCtrl#update(com.itangcent.model.UserInfo) +### update user info + +PUT http://localhost:8080/user/update +Content-Type: multipart/form-data + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="id" + +[id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="type" + +[type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="name" + +[name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="age" + +[age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sex" + +[sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="birthDay" + +[birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="regtime" + +[regtime] +--WebAppBoundary-- + + +### + diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequests.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequests.txt new file mode 100644 index 000000000..e35234a22 --- /dev/null +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequests.txt @@ -0,0 +1,656 @@ +### ref: com.itangcent.api.BaseController#ctrlName() +### current ctrl name + +GET http://localhost:8080/user/ctrl/name +token: + + +### + +### ref: com.itangcent.api.UserCtrl#greeting() +### say hello + +// not update anything +GET http://localhost:8080/user/greeting + + +### + +### ref: com.itangcent.api.UserCtrl#get(java.lang.Long) +### get user info + +GET http://localhost:8080/user/get/{id}?id=0 +token: + + +### + +### ref: com.itangcent.api.UserCtrl#create(com.itangcent.model.UserInfo) +### create an user + +POST http://localhost:8080/user/add +Content-Type: application/json +token: + +{ + "id": 0, + "type": 0, + "name": "", + "age": 0, + "sex": 0, + "birthDay": "", + "regtime": "" +} + +### + +### ref: com.itangcent.api.UserCtrl#update(com.itangcent.model.UserInfo) +### update user info + +PUT http://localhost:8080/user/update +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="id" + +[id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="type" + +[type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="name" + +[name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="age" + +[age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sex" + +[sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="birthDay" + +[birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="regtime" + +[regtime] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.BaseController#ctrlName() +### current ctrl name + +GET http://localhost:8080/test/ctrl/name +token: + + +### + +### ref: com.itangcent.api.TestCtrl#header(java.lang.String) +### test RequestHeader + +GET http://localhost:8080/test/header +x-token: +token: + + +### + +### ref: com.itangcent.api.TestCtrl#header(java.lang.String[],int[]) +### test query with array parameters + +GET http://localhost:8080/test/arrays?string=&int=1 +token: + + +### + +### ref: com.itangcent.api.TestCtrl#ignore() +### test ignored method + +GET http://localhost:8080/test/ignore +token: + + +### + +### ref: com.itangcent.api.TestCtrl#request(HttpServletRequest) +### test query with javax.servlet.http.HttpServletRequest + +GET http://localhost:8080/test/httpServletRequest +token: + + +### + +### ref: com.itangcent.api.TestCtrl#response(HttpServletResponse) +### test query with javax.servlet.http.HttpServletResponse + +GET http://localhost:8080/test/httpServletResponse +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnvoid() +### test api return void + +GET http://localhost:8080/test/return/void +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnVoid() +### test api return Void + +GET http://localhost:8080/test/return/Void +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnResultVoid() +### test api return Result + +GET http://localhost:8080/test/return/result/Void +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnEnum() +### test api return Enum + +GET http://localhost:8080/test/return/enum +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnResultEnum() +### test api return Result + +GET http://localhost:8080/test/return/result/enum +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnEnumField() +### test api return Enum field + +GET http://localhost:8080/test/return/enum/field +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnResultEnumField() +### test api return Result + +GET http://localhost:8080/test/return/result/enum/field +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnNode(com.itangcent.model.Node) +### return nested node + +POST http://localhost:8080/test/return/node +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="id" + +[id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="code" + +[code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.id" + +[parent.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.code" + +[parent.code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.parent.key" + +[parent.parent.key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.sub[0].key" + +[parent.sub[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.siblings[0].key" + +[parent.siblings[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].id" + +[sub[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].code" + +[sub[0].code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].parent.key" + +[sub[0].parent.key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].sub[0].key" + +[sub[0].sub[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].siblings[0].key" + +[sub[0].siblings[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].id" + +[siblings[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].code" + +[siblings[0].code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].parent.key" + +[siblings[0].parent.key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].sub[0].key" + +[siblings[0].sub[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].siblings[0].key" + +[siblings[0].siblings[0].key] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.TestCtrl#returnRoot(com.itangcent.model.Root) +### return root with nested nodes + +GET http://localhost:8080/test/return/root?id=&children[0].id=&children[0].code=&children[0].parent.key=&children[0].sub[0].key=&children[0].siblings[0].key= +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnAjaxResult(com.itangcent.model.CustomMap) +### return customMap + +GET http://localhost:8080/test/return/customMap?key= +token: + + +### + +### ref: com.itangcent.api.TestCtrl#pageRequest(com.itangcent.model.PageRequest) +### user page query + +GET http://localhost:8080/test/call/page/user?size=&user.id=0&user.type=0&user.name=&user.age=0&user.sex=0&user.birthDay=&user.regtime=&users[0].id=0&users[0].type=0&users[0].name=&users[0].age=0&users[0].sex=0&users[0].birthDay=&users[0].regtime=&t.id=0&t.type=0&t.name=&t.age=0&t.sex=0&t.birthDay=&t.regtime= +token: + + +### + +### ref: com.itangcent.api.TestCtrl#pageRequestWithModelAttribute(com.itangcent.model.PageRequest) +### user page query with ModelAttribute + +POST http://localhost:8080/test/call/page/user/form +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="size" + +[size] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.id" + +[user.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.type" + +[user.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.name" + +[user.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.age" + +[user.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.sex" + +[user.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.birthDay" + +[user.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.regtime" + +[user.regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].id" + +[users[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].type" + +[users[0].type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].name" + +[users[0].name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].age" + +[users[0].age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].sex" + +[users[0].sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].birthDay" + +[users[0].birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].regtime" + +[users[0].regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.id" + +[t.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.type" + +[t.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.name" + +[t.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.age" + +[t.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.sex" + +[t.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.birthDay" + +[t.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.regtime" + +[t.regtime] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.TestCtrl#pageRequestWithPost(com.itangcent.model.PageRequest) +### user page query with POST + +POST http://localhost:8080/test/call/page/user/post +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="size" + +[size] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.id" + +[user.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.type" + +[user.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.name" + +[user.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.age" + +[user.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.sex" + +[user.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.birthDay" + +[user.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.regtime" + +[user.regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].id" + +[users[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].type" + +[users[0].type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].name" + +[users[0].name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].age" + +[users[0].age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].sex" + +[users[0].sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].birthDay" + +[users[0].birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].regtime" + +[users[0].regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.id" + +[t.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.type" + +[t.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.name" + +[t.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.age" + +[t.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.sex" + +[t.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.birthDay" + +[t.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.regtime" + +[t.regtime] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.TestCtrl#listPageRequestWithArray(com.itangcent.model.UserInfo[]) +### user page query with array + +GET http://localhost:8080/test/call/page/user/array?id=0&type=0&name=&age=0&sex=0&birthDay=®time= +token: + + +### + diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequestsToExistedDoc.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequestsToExistedDoc.txt new file mode 100644 index 000000000..75d6eeaed --- /dev/null +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.http.HttpClientFormatterTest.testParseRequestsToExistedDoc.txt @@ -0,0 +1,661 @@ +### ref: com.itangcent.api.UserCtrl#greeting() +### say hello + +// not update anything +GET http://localhost:8080/user/greeting + + +### + +### ref: com.itangcent.api.UserCtrl#notExisted(java.lang.Long) +### api not existed in userCtrl should be kept + +GET http://localhost:8080/user/notExisted +token: + + +### + +### ref: com.itangcent.api.BaseController#ctrlName() +### current ctrl name + +GET http://localhost:8080/user/ctrl/name +token: + + +### + +### ref: com.itangcent.api.UserCtrl#get(java.lang.Long) +### get user info + +GET http://localhost:8080/user/get/{id}?id=0 +token: + + +### + +### ref: com.itangcent.api.UserCtrl#create(com.itangcent.model.UserInfo) +### create an user + +POST http://localhost:8080/user/add +Content-Type: application/json +token: + +{ + "id": 0, + "type": 0, + "name": "", + "age": 0, + "sex": 0, + "birthDay": "", + "regtime": "" +} + +### + +### ref: com.itangcent.api.UserCtrl#update(com.itangcent.model.UserInfo) +### update user info + +PUT http://localhost:8080/user/update +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="id" + +[id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="type" + +[type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="name" + +[name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="age" + +[age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sex" + +[sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="birthDay" + +[birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="regtime" + +[regtime] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.BaseController#ctrlName() +### current ctrl name + +GET http://localhost:8080/test/ctrl/name +token: + + +### + +### ref: com.itangcent.api.TestCtrl#header(java.lang.String) +### test RequestHeader + +GET http://localhost:8080/test/header +x-token: +token: + + +### + +### ref: com.itangcent.api.TestCtrl#header(java.lang.String[],int[]) +### test query with array parameters + +GET http://localhost:8080/test/arrays?string=&int=1 +token: + + +### + +### ref: com.itangcent.api.TestCtrl#ignore() +### test ignored method + +GET http://localhost:8080/test/ignore +token: + + +### + +### ref: com.itangcent.api.TestCtrl#request(HttpServletRequest) +### test query with javax.servlet.http.HttpServletRequest + +GET http://localhost:8080/test/httpServletRequest +token: + + +### + +### ref: com.itangcent.api.TestCtrl#response(HttpServletResponse) +### test query with javax.servlet.http.HttpServletResponse + +GET http://localhost:8080/test/httpServletResponse +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnvoid() +### test api return void + +GET http://localhost:8080/test/return/void +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnVoid() +### test api return Void + +GET http://localhost:8080/test/return/Void +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnResultVoid() +### test api return Result + +GET http://localhost:8080/test/return/result/Void +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnEnum() +### test api return Enum + +GET http://localhost:8080/test/return/enum +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnResultEnum() +### test api return Result + +GET http://localhost:8080/test/return/result/enum +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnEnumField() +### test api return Enum field + +GET http://localhost:8080/test/return/enum/field +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnResultEnumField() +### test api return Result + +GET http://localhost:8080/test/return/result/enum/field +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnNode(com.itangcent.model.Node) +### return nested node + +POST http://localhost:8080/test/return/node +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="id" + +[id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="code" + +[code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.id" + +[parent.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.code" + +[parent.code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.parent.key" + +[parent.parent.key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.sub[0].key" + +[parent.sub[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="parent.siblings[0].key" + +[parent.siblings[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].id" + +[sub[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].code" + +[sub[0].code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].parent.key" + +[sub[0].parent.key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].sub[0].key" + +[sub[0].sub[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="sub[0].siblings[0].key" + +[sub[0].siblings[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].id" + +[siblings[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].code" + +[siblings[0].code] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].parent.key" + +[siblings[0].parent.key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].sub[0].key" + +[siblings[0].sub[0].key] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="siblings[0].siblings[0].key" + +[siblings[0].siblings[0].key] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.TestCtrl#returnRoot(com.itangcent.model.Root) +### return root with nested nodes + +GET http://localhost:8080/test/return/root?id=&children[0].id=&children[0].code=&children[0].parent.key=&children[0].sub[0].key=&children[0].siblings[0].key= +token: + + +### + +### ref: com.itangcent.api.TestCtrl#returnAjaxResult(com.itangcent.model.CustomMap) +### return customMap + +GET http://localhost:8080/test/return/customMap?key= +token: + + +### + +### ref: com.itangcent.api.TestCtrl#pageRequest(com.itangcent.model.PageRequest) +### user page query + +GET http://localhost:8080/test/call/page/user?size=&user.id=0&user.type=0&user.name=&user.age=0&user.sex=0&user.birthDay=&user.regtime=&users[0].id=0&users[0].type=0&users[0].name=&users[0].age=0&users[0].sex=0&users[0].birthDay=&users[0].regtime=&t.id=0&t.type=0&t.name=&t.age=0&t.sex=0&t.birthDay=&t.regtime= +token: + + +### + +### ref: com.itangcent.api.TestCtrl#pageRequestWithModelAttribute(com.itangcent.model.PageRequest) +### user page query with ModelAttribute + +POST http://localhost:8080/test/call/page/user/form +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="size" + +[size] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.id" + +[user.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.type" + +[user.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.name" + +[user.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.age" + +[user.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.sex" + +[user.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.birthDay" + +[user.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.regtime" + +[user.regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].id" + +[users[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].type" + +[users[0].type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].name" + +[users[0].name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].age" + +[users[0].age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].sex" + +[users[0].sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].birthDay" + +[users[0].birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].regtime" + +[users[0].regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.id" + +[t.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.type" + +[t.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.name" + +[t.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.age" + +[t.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.sex" + +[t.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.birthDay" + +[t.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.regtime" + +[t.regtime] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.TestCtrl#pageRequestWithPost(com.itangcent.model.PageRequest) +### user page query with POST + +POST http://localhost:8080/test/call/page/user/post +Content-Type: multipart/form-data +token: + +Content-Type: multipart/form-data; boundary=WebAppBoundary + +--WebAppBoundary +Content-Disposition: form-data; name="size" + +[size] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.id" + +[user.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.type" + +[user.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.name" + +[user.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.age" + +[user.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.sex" + +[user.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.birthDay" + +[user.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="user.regtime" + +[user.regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].id" + +[users[0].id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].type" + +[users[0].type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].name" + +[users[0].name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].age" + +[users[0].age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].sex" + +[users[0].sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].birthDay" + +[users[0].birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="users[0].regtime" + +[users[0].regtime] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.id" + +[t.id] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.type" + +[t.type] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.name" + +[t.name] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.age" + +[t.age] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.sex" + +[t.sex] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.birthDay" + +[t.birthDay] +--WebAppBoundary-- + +--WebAppBoundary +Content-Disposition: form-data; name="t.regtime" + +[t.regtime] +--WebAppBoundary-- + + +### + +### ref: com.itangcent.api.TestCtrl#listPageRequestWithArray(com.itangcent.model.UserInfo[]) +### user page query with array + +GET http://localhost:8080/test/call/page/user/array?id=0&type=0&name=&age=0&sex=0&birthDay=®time= +token: \ No newline at end of file