From 489cd80b44645246c34e970f09d8cd296e59bd9d Mon Sep 17 00:00:00 2001 From: tangcent Date: Sun, 14 Apr 2024 21:00:09 +0800 Subject: [PATCH] feat: add rules `param.name`, `param.type` --- .../api/export/core/ClassExportRuleKeys.kt | 10 ++ .../plugin/api/export/core/ExportContext.kt | 111 +++++++++++++---- .../api/export/core/RequestClassExporter.kt | 115 +++++++++++++----- .../export/feign/FeignRequestClassExporter.kt | 2 +- .../generic/GenericRequestClassExporter.kt | 43 ++++--- .../export/jaxrs/JAXRSRequestClassExporter.kt | 10 +- .../export/spring/ActuatorEndpointExporter.kt | 4 +- .../spring/SpringRequestClassExporter.kt | 44 ++++--- .../spring/SpringRequestClassExporterTest.kt | 20 ++- .../src/test/resources/api/TestCtrl.java | 5 +- ...ugin.api.export.curl.CurlFormatterTest.txt | 2 +- ...pClientFormatterTest.testParseRequests.txt | 4 +- ...tterTest.testParseRequestsToExistedDoc.txt | 4 +- ...DirectorySpringMarkdownApiExporterTest.txt | 1 + ...DirectorySpringMarkdownApiExporterTest.txt | 1 + ....DirectorySpringPostmanApiExporterTest.txt | 12 ++ ...est.testExportFromTestCtrlWithExpanded.txt | 4 + ....testExportFromTestCtrlWithOutExpanded.txt | 4 + ...est.DirectorySpringYapiApiExporterTest.txt | 6 + 19 files changed, 296 insertions(+), 106 deletions(-) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ClassExportRuleKeys.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ClassExportRuleKeys.kt index 1cd83d608..19a0a5356 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ClassExportRuleKeys.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ClassExportRuleKeys.kt @@ -99,6 +99,16 @@ object ClassExportRuleKeys { BooleanRuleMode.ANY ) + val PARAM_NAME: RuleKey = SimpleRuleKey( + "param.name", + StringRuleMode.SINGLE + ) + + val PARAM_TYPE: RuleKey = SimpleRuleKey( + "param.type", + StringRuleMode.SINGLE + ) + val PARAM_DEFAULT_VALUE: RuleKey = SimpleRuleKey( "param.default.value", StringRuleMode.MERGE_DISTINCT diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ExportContext.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ExportContext.kt index f512026c9..664d67bdc 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ExportContext.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/ExportContext.kt @@ -13,6 +13,9 @@ import com.itangcent.intellij.jvm.element.ExplicitParameter import kotlin.reflect.KClass interface ExportContext : Extensible { + /** + * the parent context, allowing navigation up the context hierarchy. + */ fun parent(): ExportContext? /** @@ -23,22 +26,45 @@ interface ExportContext : Extensible { fun psi(): PsiElement } +/** + * Extends ExportContext for contexts dealing with variables (methods or parameters). + */ interface VariableExportContext : ExportContext { + /** + * the name of the variable. + */ fun name(): String + /** + * the type of the variable, which may be null if the type is not resolved. + */ fun type(): DuckType? + /** + * the explicit element representation of the variable. + */ fun element(): ExplicitElement<*> + + /** + * Sets a resolved name for the variable, typically used for renaming. + */ + fun setResolvedName(name: String) } //region kits of ExportContext +/** + * find specific contexts by type. + */ @Suppress("UNCHECKED_CAST") fun ExportContext.findContext(condition: KClass): T? { return findContext { condition.isInstance(it) } as? T } +/** + * find specific contexts by condition. + */ fun ExportContext.findContext(condition: (ExportContext) -> Boolean): ExportContext? { var exportContext: ExportContext? = this while (exportContext != null) { @@ -50,17 +76,11 @@ fun ExportContext.findContext(condition: (ExportContext) -> Boolean): ExportCont return null } -fun ExportContext.findExt(attr: String): T? { - var exportContext: ExportContext? = this - while (exportContext != null) { - exportContext.getExt(attr)?.let { return it } - exportContext = exportContext.parent() - } - return null -} - //endregion +/** + * Base context with no parent, typically used for top-level classes. + */ abstract class RootExportContext : SimpleExtensible(), ExportContext { override fun parent(): ExportContext? { @@ -68,21 +88,46 @@ abstract class RootExportContext : } } +/** + * General purpose context implementation with a specified parent context. + */ abstract class AbstractExportContext(private val parent: ExportContext) : - SimpleExtensible(), ExportContext { + SimpleExtensible(), VariableExportContext { + + private var resolvedName: String? = null + override fun parent(): ExportContext? { return this.parent } + + /** + * Returns the name of the element. + * + * @return the element name. + */ + override fun name(): String { + return resolvedName ?: element().name() + } + + override fun setResolvedName(name: String) { + this.resolvedName = name + } } +/** + * Context specifically for a class + */ class ClassExportContext(val cls: PsiClass) : RootExportContext() { override fun psi(): PsiClass { return cls } } +/** + * Context for a method, containing specifics about the method being exported. + */ class MethodExportContext( - private val parent: ExportContext, + parent: ExportContext, private val method: ExplicitMethod ) : AbstractExportContext(parent), VariableExportContext { @@ -113,19 +158,27 @@ class MethodExportContext( } } -class ParameterExportContext( - private val parent: ExportContext, - private val parameter: ExplicitParameter -) : AbstractExportContext(parent), VariableExportContext { +/** + * Context for a parameter, containing specifics about the parameter being exported. + */ +interface ParameterExportContext : VariableExportContext { - /** - * Returns the name of the element. - * - * @return the element name. - */ - override fun name(): String { - return parameter.name() - } + override fun element(): ExplicitParameter + + override fun psi(): PsiParameter +} + +fun ParameterExportContext( + parent: ExportContext, + parameter: ExplicitParameter +): ParameterExportContext { + return ParameterExportContextImpl(parent, parameter) +} + +class ParameterExportContextImpl( + parent: ExportContext, + private val parameter: ExplicitParameter +) : AbstractExportContext(parent), ParameterExportContext { /** * Returns the type of the variable. @@ -145,18 +198,30 @@ class ParameterExportContext( } } +/** + * retrieve ClassExportContext based on the current context. + */ fun ExportContext.classContext(): ClassExportContext? { return this.findContext(ClassExportContext::class) } +/** + * retrieve MethodExportContext based on the current context. + */ fun ExportContext.methodContext(): MethodExportContext? { return this.findContext(MethodExportContext::class) } +/** + * retrieve ParameterExportContext based on the current context. + */ fun ExportContext.paramContext(): ParameterExportContext? { return this.findContext(ParameterExportContext::class) } +/** + * Searches for an extended property, first locally then up the context hierarchy. + */ fun ExportContext.searchExt(attr: String): T? { this.getExt(attr)?.let { return it } this.parent()?.searchExt(attr)?.let { return it } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/RequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/RequestClassExporter.kt index e4aafcf44..9b34cef39 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/RequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/core/RequestClassExporter.kt @@ -509,13 +509,16 @@ abstract class RequestClassExporter : ClassExporter { continue } - parsedParams.add(ParameterExportContext(methodExportContext, param).also { it.raw() }) + parsedParams.add(ParameterExportContext(methodExportContext, param) + .adaptive() + .also { it.originalReturnObject() } + ) } finally { ruleComputer.computer(ClassExportRuleKeys.API_PARAM_AFTER, param) } } - val hasFile = parsedParams.any { it.raw().hasFile() } + val hasFile = parsedParams.any { it.originalReturnObject().hasFile() } if (hasFile) { if (request.method == HttpMethod.GET) { @@ -539,7 +542,7 @@ abstract class RequestClassExporter : ClassExporter { processMethodParameter( request, parameterExportContext, - KVUtils.getUltimateComment(paramDocComment, parameterExportContext.name()) + KVUtils.getUltimateComment(paramDocComment, parameterExportContext.originalName()) .append(readParamDoc(parameterExportContext.element())) ) } finally { @@ -589,7 +592,6 @@ abstract class RequestClassExporter : ClassExporter { return } - @Suppress("UNCHECKED_CAST") protected open fun addParamAsQuery( parameterExportContext: VariableExportContext, request: Request, typeObject: Any?, paramDesc: String? = null, @@ -600,7 +602,7 @@ abstract class RequestClassExporter : ClassExporter { if (typeObject == Magics.FILE_STR) { requestBuilderListener.addFormFileParam( parameterExportContext, - request, parameterExportContext.paramName(), + request, parameterExportContext.name(), parameterExportContext.required() ?: ruleComputer.computer(ClassExportRuleKeys.PARAM_REQUIRED, parameterExportContext.element()) ?: false, paramDesc @@ -612,7 +614,7 @@ abstract class RequestClassExporter : ClassExporter { requestBuilderListener.addParam( parameterExportContext, request, - parameterExportContext.paramName(), + parameterExportContext.name(), tinyQueryParam(parameterExportContext.defaultVal() ?: typeObject), parameterExportContext.required() ?: ruleComputer.computer(ClassExportRuleKeys.PARAM_REQUIRED, parameterExportContext.element()) @@ -683,7 +685,6 @@ abstract class RequestClassExporter : ClassExporter { } } - @Suppress("UNCHECKED_CAST") protected open fun addParamAsForm( parameterExportContext: VariableExportContext, request: Request, typeObject: Any?, paramDesc: String? = null, @@ -693,7 +694,7 @@ abstract class RequestClassExporter : ClassExporter { if (typeObject == Magics.FILE_STR) { requestBuilderListener.addFormFileParam( parameterExportContext, - request, parameterExportContext.paramName(), + request, parameterExportContext.name(), ruleComputer.computer( ClassExportRuleKeys.PARAM_REQUIRED, parameterExportContext.element() @@ -769,7 +770,7 @@ abstract class RequestClassExporter : ClassExporter { } else { requestBuilderListener.addFormParam( parameterExportContext, - request, parameterExportContext.paramName(), tinyQueryParam(typeObject)?.toString(), + request, parameterExportContext.name(), tinyQueryParam(typeObject)?.toString(), parameterExportContext.required() ?: ruleComputer.computer(ClassExportRuleKeys.PARAM_REQUIRED, parameterExportContext.element()) ?: false, paramDesc @@ -812,63 +813,113 @@ abstract class RequestClassExporter : ClassExporter { } protected fun deepComponent(obj: Any?): Any? { - if (obj == null) { - return null + val unboxed = obj.unbox() + if (unboxed is Map<*, *>) { + return RequestUtils.parseRawBody(unboxed) } - if (obj is Array<*>) { - if (obj.isEmpty()) return obj - return deepComponent(obj[0]) + return unboxed + } + + //region extent of ParameterExportContext + + /** + * Creates a new [ParameterExportContext] that adapts the output of parameters based on certain rules. + */ + fun ParameterExportContext.adaptive(): ParameterExportContext { + return AdaptiveParameterContext(this) + } + + /** + * Decorator for [ParameterExportContext] that adapts the output of + * parameters based on certain rules + */ + inner class AdaptiveParameterContext( + val delegate: ParameterExportContext + ) : ParameterExportContext by delegate { + + private val nameByRule by lazy { + ruleComputer.computer(ClassExportRuleKeys.PARAM_NAME, delegate.element()) } - if (obj is Collection<*>) { - if (obj.isEmpty()) return obj - return deepComponent(obj.first()) + + private val typeByRule by lazy { + ruleComputer.computer(ClassExportRuleKeys.PARAM_TYPE, delegate.element())?.let { + duckTypeHelper.findDuckType(it, delegate.psi()) + } } - if (obj is Map<*, *>) { - return RequestUtils.parseRawBody(obj) + + override fun name(): String { + return nameByRule ?: delegate.name() } - return obj - } - //region extent of ParameterExportContext - fun VariableExportContext.setParamName(name: String) { - this.setExt("param_name", name) + override fun type(): DuckType? { + return typeByRule ?: delegate.type() + } } - fun VariableExportContext.paramName(): String { - return this.getExt("param_name") ?: this.name() + /** + * the original name of the element within the [VariableExportContext] + */ + fun VariableExportContext.originalName(): String { + return this.element().name() } + /** + * Sets the 'required' status as an extended property within the [ExportContext] + */ fun ExportContext.setRequired(name: Boolean) { this.setExt("required", name) } + /** + * Retrieves the 'required' status from the extended properties of the [ExportContext] + */ fun ExportContext.required(): Boolean? { return this.getExt("required") } + /** + * Sets the default value as an extended property within the [ExportContext] + * + * @param defaultVal The default value to set. + */ fun ExportContext.setDefaultVal(defaultVal: String) { this.setExt("defaultVal", defaultVal) } + /** + * Retrieves the default value from the extended properties of the [ExportContext] + */ fun ExportContext.defaultVal(): String? { return this.getExt("defaultVal") } - fun VariableExportContext.raw(): Any? { - return this.cache("raw") { + /** + * Computes and caches the original return object (type representation) of a variable within [VariableExportContext]. + * The computed object is constant and is cached for future use. + * + * @return The computed type object or null if the type cannot be resolved. + */ + fun VariableExportContext.originalReturnObject(): Any? { + return this.cache("originalReturnObj") { val paramType = this.type() ?: return@cache null val typeObject = psiClassHelper!!.getTypeObject( paramType, this.psi(), this@RequestClassExporter.intelligentSettingsHelper.jsonOptionForInput(JsonOption.READ_COMMENT) ) - this.setExt("raw", typeObject) + this.setExt("originalReturnObj", typeObject) return@cache typeObject } } - fun VariableExportContext.unbox(): Any? { - return this.cache("unbox") { - return@cache raw().unbox() + /** + * Computes and caches the unboxed version of the original return object within [VariableExportContext]. + * Utilizes the previously cached original return object and performs an unboxing operation. + * + * @return The unboxed object or null if the original is null. + */ + fun VariableExportContext.unboxedReturnObject(): Any? { + return this.cache("unboxedReturnObj") { + return@cache originalReturnObject().unbox() } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/feign/FeignRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/feign/FeignRequestClassExporter.kt index 3d0e64da3..deb99d41f 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/feign/FeignRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/feign/FeignRequestClassExporter.kt @@ -171,7 +171,7 @@ open class FeignRequestClassExporter : SpringRequestClassExporter() { addParamAsQuery( parameterExportContext, request, readParamDefaultValue - ?: parameterExportContext.unbox(), ultimateComment + ?: parameterExportContext.unboxedReturnObject(), ultimateComment ) return } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt index e3bbe4ec9..8416ff20a 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/generic/GenericRequestClassExporter.kt @@ -69,17 +69,19 @@ open class GenericRequestClassExporter : RequestClassExporter() { //RequestBody(json) if (isJsonBody(parameterExportContext.psi())) { if (request.method == "GET") { - logger.warn("Attempted to use a request body with a GET method in ${parameterExportContext.psi().containingFile.name} at ${parameterExportContext.psi().textOffset}." + - " Please ensure the HTTP method supports a body or adjust the rule [generic.param.as.json.body].") + logger.warn( + "Attempted to use a request body with a GET method in ${parameterExportContext.psi().containingFile.name} at ${parameterExportContext.psi().textOffset}." + + " Please ensure the HTTP method supports a body or adjust the rule [generic.param.as.json.body]." + ) addParamAsQuery( parameterExportContext, request, parameterExportContext.defaultVal() - ?: parameterExportContext.unbox(), getUltimateComment(paramDesc, parameterExportContext) + ?: parameterExportContext.unboxedReturnObject(), getUltimateComment(paramDesc, parameterExportContext) ) return } setRequestBody( parameterExportContext, - request, parameterExportContext.raw(), paramDesc + request, parameterExportContext.originalReturnObject(), paramDesc ) return } @@ -89,9 +91,9 @@ open class GenericRequestClassExporter : RequestClassExporter() { requestBuilderListener.setMethodIfMissed(parameterExportContext, request, HttpMethod.POST) if (request.method == HttpMethod.GET) { logger.warn("Form is not supported for GET method, it will be resolved as query.") - addParamAsQuery(parameterExportContext, request, parameterExportContext.unbox()) + addParamAsQuery(parameterExportContext, request, parameterExportContext.unboxedReturnObject()) } else { - addParamAsForm(parameterExportContext, request, parameterExportContext.unbox(), paramDesc) + addParamAsForm(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), paramDesc) } return } @@ -103,14 +105,18 @@ open class GenericRequestClassExporter : RequestClassExporter() { val header = safe { additionalParseHelper.parseHeaderFromJson(headerStr) } when { header == null -> { - logger.error("Failed to parse additional header in ${parameterExportContext.psi().containingFile.name} at ${parameterExportContext.psi().textOffset}. Header content: '$headerStr'." + - " Verify the header format is correct.") + logger.error( + "Failed to parse additional header in ${parameterExportContext.psi().containingFile.name} at ${parameterExportContext.psi().textOffset}. Header content: '$headerStr'." + + " Verify the header format is correct." + ) return@cache null } + header.name.isNullOrBlank() -> { logger.error("no name had be found in: $headerStr") return@cache null } + else -> return@cache header } }?.let { @@ -166,7 +172,7 @@ open class GenericRequestClassExporter : RequestClassExporter() { //form/body/query var paramType: String? = null - findParamName(parameterExportContext.psi())?.let { parameterExportContext.setParamName(it) } + findParamName(parameterExportContext.psi())?.let { parameterExportContext.setResolvedName(it) } if (request.method == "GET") { paramType = "query" @@ -179,7 +185,7 @@ open class GenericRequestClassExporter : RequestClassExporter() { } if (request.method == HttpMethod.GET) { - addParamAsQuery(parameterExportContext, request, parameterExportContext.unbox(), ultimateComment) + addParamAsQuery(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), ultimateComment) return } @@ -194,24 +200,27 @@ open class GenericRequestClassExporter : RequestClassExporter() { when (paramType) { "body" -> { requestBuilderListener.setMethodIfMissed(parameterExportContext, request, HttpMethod.POST) - setRequestBody(parameterExportContext, request, parameterExportContext.raw(), ultimateComment) + setRequestBody(parameterExportContext, request, parameterExportContext.originalReturnObject(), ultimateComment) return } + "form" -> { requestBuilderListener.setMethodIfMissed(parameterExportContext, request, HttpMethod.POST) addParamAsForm( parameterExportContext, request, parameterExportContext.defaultVal() - ?: parameterExportContext.unbox(), ultimateComment + ?: parameterExportContext.unboxedReturnObject(), ultimateComment ) return } + "query" -> { addParamAsQuery( parameterExportContext, request, parameterExportContext.defaultVal() - ?: parameterExportContext.unbox(), ultimateComment + ?: parameterExportContext.unboxedReturnObject(), ultimateComment ) return } + else -> { logger.warn( "Unknown param type:$paramType." + @@ -222,8 +231,8 @@ open class GenericRequestClassExporter : RequestClassExporter() { } } - if (parameterExportContext.unbox().hasFile()) { - addParamAsForm(parameterExportContext, request, parameterExportContext.unbox(), ultimateComment) + if (parameterExportContext.unboxedReturnObject().hasFile()) { + addParamAsForm(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), ultimateComment) return } @@ -231,7 +240,7 @@ open class GenericRequestClassExporter : RequestClassExporter() { requestBuilderListener.addParam( parameterExportContext, request, - parameterExportContext.paramName(), + parameterExportContext.name(), parameterExportContext.defaultVal(), parameterExportContext.required() ?: false, @@ -246,7 +255,7 @@ open class GenericRequestClassExporter : RequestClassExporter() { // } //else - addParamAsQuery(parameterExportContext, request, parameterExportContext.unbox(), ultimateComment) + addParamAsQuery(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), ultimateComment) } private fun getUltimateComment( diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt index fc9e3a74d..7524deee3 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/jaxrs/JAXRSRequestClassExporter.kt @@ -129,14 +129,14 @@ open class JAXRSRequestClassExporter : RequestClassExporter() { ?: HttpMethod.POST ) } - addParamAsForm(exportContext, request, exportContext.unbox(), ultimateComment) + addParamAsForm(exportContext, request, exportContext.unboxedReturnObject(), ultimateComment) return } //@QueryParam if (annotationHelper.hasAnn(exportContext.psi(), JAXRSClassName.QUERY_PARAM_ANNOTATION)) { findParamName(exportContext, JAXRSClassName.QUERY_PARAM_ANNOTATION) - addParamAsQuery(exportContext, request, exportContext.unbox(), ultimateComment) + addParamAsQuery(exportContext, request, exportContext.unboxedReturnObject(), ultimateComment) return } @@ -192,11 +192,11 @@ open class JAXRSRequestClassExporter : RequestClassExporter() { //as body by default if (exportContext is FieldExportContext) { - addParamAsQuery(exportContext, request, exportContext.unbox(), ultimateComment) + addParamAsQuery(exportContext, request, exportContext.unboxedReturnObject(), ultimateComment) } else { setRequestBody( exportContext, - request, exportContext.raw(), paramDesc + request, exportContext.originalReturnObject(), paramDesc ) } return @@ -204,7 +204,7 @@ open class JAXRSRequestClassExporter : RequestClassExporter() { private fun findParamName(exportContext: VariableExportContext, annName: String): String { annotationHelper.findAttrAsString(exportContext.psi(), annName) - ?.also { exportContext.setParamName(it) } + ?.also { exportContext.setResolvedName(it) } ?.let { return it } return exportContext.name() } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/ActuatorEndpointExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/ActuatorEndpointExporter.kt index 3de3648b6..1073d67d9 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/ActuatorEndpointExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/ActuatorEndpointExporter.kt @@ -8,8 +8,6 @@ import com.itangcent.common.kit.KVUtils import com.itangcent.common.model.Request import com.itangcent.common.model.URL import com.itangcent.common.utils.cache -import com.itangcent.common.utils.firstOrNull -import com.itangcent.common.utils.mapNotNull import com.itangcent.common.utils.notNullOrBlank import com.itangcent.idea.condition.annotation.ConditionOnClass import com.itangcent.idea.plugin.api.export.core.* @@ -63,6 +61,7 @@ class ActuatorEndpointExporter : SpringRequestClassExporter() { request, HttpMethod.GET ) } + SpringClassName.WRITE_OPERATION_ANNOTATION -> { requestBuilderListener.setMethodIfMissed( methodExportContext, @@ -70,6 +69,7 @@ class ActuatorEndpointExporter : SpringRequestClassExporter() { ) methodExportContext.setExt("hasWriteOrDeleteOperation", true) } + SpringClassName.DELETE_OPERATION_ANNOTATION -> { requestBuilderListener.setMethodIfMissed( methodExportContext, diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporter.kt index 282a29dc7..4451a86a5 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporter.kt @@ -69,7 +69,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { if (isRequestBody(parameterExportContext.psi())) { setRequestBody( parameterExportContext, - request, parameterExportContext.raw(), paramDesc + request, parameterExportContext.originalReturnObject(), paramDesc ) return } @@ -87,9 +87,9 @@ open class SpringRequestClassExporter : RequestClassExporter() { ) } if (request.method == HttpMethod.GET) { - addParamAsQuery(parameterExportContext, request, parameterExportContext.unbox()) + addParamAsQuery(parameterExportContext, request, parameterExportContext.unboxedReturnObject()) } else { - addParamAsForm(parameterExportContext, request, parameterExportContext.unbox(), paramDesc) + addParamAsForm(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), paramDesc) } return } @@ -104,7 +104,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { if (headName.isNullOrEmpty()) { headName = parameterExportContext.name() } else { - parameterExportContext.setParamName(headName) + parameterExportContext.setResolvedName(headName) } var required = findRequired(requestHeaderAnn) @@ -120,7 +120,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { val header = Header() header.name = headName - header.value = defaultValue + header.value = parameterExportContext.defaultVal() ?: defaultValue header.desc = ultimateComment header.required = required requestBuilderListener.addHeader(parameterExportContext, request, header) @@ -136,7 +136,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { if (pathName == null) { pathName = parameterExportContext.name() } else { - parameterExportContext.setParamName(pathName) + parameterExportContext.setResolvedName(pathName) } requestBuilderListener.addPathParam(parameterExportContext, request, pathName, ultimateComment) @@ -152,7 +152,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { if (cookieName == null) { cookieName = parameterExportContext.name() } else { - parameterExportContext.setParamName(cookieName) + parameterExportContext.setResolvedName(cookieName) } @@ -189,7 +189,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { val requestParamAnn = findRequestParam(parameterExportContext.psi()) if (requestParamAnn != null) { - findParamName(requestParamAnn)?.let { parameterExportContext.setParamName(it) } + findParamName(requestParamAnn)?.let { parameterExportContext.setResolvedName(it) } parameterExportContext.setRequired(findRequired(requestParamAnn)) findDefaultValue(requestParamAnn)?.let { parameterExportContext.setDefaultVal(it) } @@ -211,7 +211,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { } if (request.method == HttpMethod.GET) { - addParamAsQuery(parameterExportContext, request, parameterExportContext.unbox(), ultimateComment) + addParamAsQuery(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), ultimateComment) return } @@ -226,24 +226,27 @@ open class SpringRequestClassExporter : RequestClassExporter() { when (paramType) { "body" -> { requestBuilderListener.setMethodIfMissed(parameterExportContext, request, HttpMethod.POST) - setRequestBody(parameterExportContext, request, parameterExportContext.raw(), ultimateComment) + setRequestBody(parameterExportContext, request, parameterExportContext.originalReturnObject(), ultimateComment) return } + "form" -> { requestBuilderListener.setMethodIfMissed(parameterExportContext, request, HttpMethod.POST) addParamAsForm( parameterExportContext, request, parameterExportContext.defaultVal() - ?: parameterExportContext.unbox(), ultimateComment + ?: parameterExportContext.unboxedReturnObject(), ultimateComment ) return } + "query" -> { addParamAsQuery( parameterExportContext, request, parameterExportContext.defaultVal() - ?: parameterExportContext.unbox(), ultimateComment + ?: parameterExportContext.unboxedReturnObject(), ultimateComment ) return } + else -> { logger.warn( "Unknown param type:$paramType." + @@ -254,8 +257,8 @@ open class SpringRequestClassExporter : RequestClassExporter() { } } - if (parameterExportContext.unbox().hasFile()) { - addParamAsForm(parameterExportContext, request, parameterExportContext.unbox(), ultimateComment) + if (parameterExportContext.unboxedReturnObject().hasFile()) { + addParamAsForm(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), ultimateComment) return } @@ -263,7 +266,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { requestBuilderListener.addParam( parameterExportContext, request, - parameterExportContext.paramName(), + parameterExportContext.name(), parameterExportContext.defaultVal(), parameterExportContext.required() ?: false, @@ -278,7 +281,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { // } //else - addParamAsQuery(parameterExportContext, request, parameterExportContext.unbox(), ultimateComment) + addParamAsQuery(parameterExportContext, request, parameterExportContext.unboxedReturnObject(), ultimateComment) } protected fun getUltimateCommentOfParam( @@ -363,6 +366,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { request, "parameter [${params.removeSuffix("!")}] should not be present" ) } + params.contains("!=") -> { val name = params.substringBefore("!=").trim() val value = params.substringAfter("!=").trim() @@ -377,6 +381,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { param.desc = param.desc.append("should not equal to [$value]", "\n") } } + !params.contains('=') -> { val param = request.querys?.find { it.name == params } if (param == null) { @@ -388,6 +393,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { param.required = true } } + else -> { val name = params.substringBefore("=").trim() val value = params.substringAfter("=").trim() @@ -431,6 +437,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { "header [${headers.removeSuffix("!")}] should not be present" ) } + headers.contains("!=") -> { val name = headers.substringBefore("!=").trim() val value = headers.substringAfter("!=").trim() @@ -445,6 +452,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { header.desc = header.desc.append("should not equal to [$value]", "\n") } } + !headers.contains('=') -> { val header = request.querys?.find { it.name == headers } if (header == null) { @@ -453,6 +461,7 @@ open class SpringRequestClassExporter : RequestClassExporter() { header.required = true } } + else -> { val name = headers.substringBefore("=").trim() val value = headers.substringAfter("=").trim() @@ -479,12 +488,15 @@ open class SpringRequestClassExporter : RequestClassExporter() { method.isBlank() -> { HttpMethod.NO_METHOD } + method.startsWith("RequestMethod.") -> { method.removePrefix("RequestMethod.") } + method.contains("RequestMethod.") -> { method.substringAfterLast("RequestMethod.") } + else -> method } } diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporterTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporterTest.kt index 59601e5d3..54d46ebe0 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporterTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/api/export/spring/SpringRequestClassExporterTest.kt @@ -24,7 +24,6 @@ import com.itangcent.mock.SettingBinderAdaptor import com.itangcent.mock.toUnixString import com.itangcent.test.ResultLoader import com.itangcent.testFramework.PluginContextLightCodeInsightFixtureTestCase -import junit.framework.Assert import java.time.LocalDate import java.time.LocalDateTime import java.util.* @@ -115,7 +114,10 @@ internal class SpringRequestClassExporterTest : PluginContextLightCodeInsightFix "api.param.parse.before=groovy:logger.info(\"before parse param:\"+it)\n" + "api.param.parse.after=groovy:logger.info(\"after parse param:\"+it)\n" + "method.return.main[groovy:it.returnType().isExtend(\"com.itangcent.model.Result\")]=data\n" + - "enum.use.by.type=true\n" + "enum.use.by.type=true\n" + + "param.name=groovy:if(it.name()==\"fake\") \"joke\"\n" + + "param.type=groovy:if(it.name()==\"fake\") \"java.lang.String\"\n" + + "param.default.value=groovy:if(it.name()==\"fake\") \"a joke\"\n" } override fun bind(builder: ActionContext.ActionContextBuilder) { @@ -218,6 +220,10 @@ internal class SpringRequestClassExporterTest : PluginContextLightCodeInsightFix assertEquals("1", querys[1].value) assertEquals("integer array", querys[1].desc) assertEquals(true, querys[1].required) + assertEquals("joke", querys[2].name) + assertEquals("a joke", querys[2].value) + assertEquals("it was only a joke", querys[2].desc) + assertEquals(true, querys[2].required) } requests[3].let { request -> assertEquals(testCtrlPsiClass.methods[3], (request.resource as PsiResource).resource()) @@ -470,6 +476,10 @@ internal class SpringRequestClassExporterTest : PluginContextLightCodeInsightFix assertEquals("1", querys[1].value) assertEquals("integer array", querys[1].desc) assertEquals(true, querys[1].required) + assertEquals("joke", querys[2].name) + assertEquals("a joke", querys[2].value) + assertEquals("it was only a joke", querys[2].desc) + assertEquals(true, querys[2].required) } requests[3].let { request -> assertEquals(testCtrlPsiClass.methods[3], (request.resource as PsiResource).resource()) @@ -663,8 +673,10 @@ internal class SpringRequestClassExporterTest : PluginContextLightCodeInsightFix ) } - assertEquals(ResultLoader.load("testExportFromTestCtrlWithOutExpanded"), - LoggerCollector.getLog().toUnixString()) + assertEquals( + ResultLoader.load("testExportFromTestCtrlWithOutExpanded"), + LoggerCollector.getLog().toUnixString() + ) } fun testExportFromUserApi() { diff --git a/idea-plugin/src/test/resources/api/TestCtrl.java b/idea-plugin/src/test/resources/api/TestCtrl.java index 908d4088f..eb328c7e0 100644 --- a/idea-plugin/src/test/resources/api/TestCtrl.java +++ b/idea-plugin/src/test/resources/api/TestCtrl.java @@ -47,10 +47,13 @@ public String header( * * @param strings string array * @param ints integer array + * @param fake it was only a joke */ @RequestMapping("/arrays") public String header(@RequestParam(name = "string", required = false) String[] strings, - @RequestParam(name = "int", defaultValue = "1") int[] ints) { + @RequestParam(name = "int", defaultValue = "1") int[] ints, + @RequestParam(name = "none", defaultValue = "1") int fake + ) { return "ok"; } diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.curl.CurlFormatterTest.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.curl.CurlFormatterTest.txt index f048db4d4..125c8286b 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.curl.CurlFormatterTest.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.curl.CurlFormatterTest.txt @@ -57,7 +57,7 @@ curl -X GET -H 'x-token: ' -H 'token: ' http://localhost:8080/test/header ## test query with array parameters ```bash -curl -X GET -H 'token: ' http://localhost:8080/test/arrays?string=\&int=1 +curl -X GET -H 'token: ' http://localhost:8080/test/arrays?string=\&int=1\&none=1 ``` --- 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 index e35234a22..7b3f3f7e7 100644 --- 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 @@ -117,10 +117,10 @@ token: ### -### ref: com.itangcent.api.TestCtrl#header(java.lang.String[],int[]) +### ref: com.itangcent.api.TestCtrl#header(java.lang.String[],int[],int) ### test query with array parameters -GET http://localhost:8080/test/arrays?string=&int=1 +GET http://localhost:8080/test/arrays?string=&int=1&none=1 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 index 75d6eeaed..dc6855139 100644 --- 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 @@ -126,10 +126,10 @@ token: ### -### ref: com.itangcent.api.TestCtrl#header(java.lang.String[],int[]) +### ref: com.itangcent.api.TestCtrl#header(java.lang.String[],int[],int) ### test query with array parameters -GET http://localhost:8080/test/arrays?string=&int=1 +GET http://localhost:8080/test/arrays?string=&int=1&none=1 token: diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.CustomizedDirectorySpringMarkdownApiExporterTest.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.CustomizedDirectorySpringMarkdownApiExporterTest.txt index e63c5fae7..804b88e21 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.CustomizedDirectorySpringMarkdownApiExporterTest.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.CustomizedDirectorySpringMarkdownApiExporterTest.txt @@ -122,6 +122,7 @@ test apis | ------------ | ------------ | ------------ | ----: | | strings | | N | string array | | ints | | N | integer array | +| fake | | N | it was only a joke | diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.DirectorySpringMarkdownApiExporterTest.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.DirectorySpringMarkdownApiExporterTest.txt index 11a316514..60d6e6e31 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.DirectorySpringMarkdownApiExporterTest.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.markdown.MarkdownApiExporterTest.DirectorySpringMarkdownApiExporterTest.txt @@ -120,6 +120,7 @@ test apis | ------------ | ------------ | ------------ | ------------ | | strings | | NO | string array | | ints | | NO | integer array | +| fake | | NO | it was only a joke | diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.postman.PostmanApiExporterTest.DirectorySpringPostmanApiExporterTest.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.postman.PostmanApiExporterTest.DirectorySpringPostmanApiExporterTest.txt index f16e33978..119d2958f 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.postman.PostmanApiExporterTest.DirectorySpringPostmanApiExporterTest.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.postman.PostmanApiExporterTest.DirectorySpringPostmanApiExporterTest.txt @@ -203,6 +203,12 @@ "value": "", "equals": true, "description": "integer array" + }, + { + "key": "fake", + "value": "", + "equals": true, + "description": "it was only a joke" } ], "host": "{{test_default}}", @@ -240,6 +246,12 @@ "value": "", "equals": true, "description": "integer array" + }, + { + "key": "fake", + "value": "", + "equals": true, + "description": "it was only a joke" } ], "host": "{{test_default}}", diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithExpanded.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithExpanded.txt index 8b4e7fb6f..e028b8da0 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithExpanded.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithExpanded.txt @@ -13,10 +13,14 @@ [INFO] after parse param:strings [INFO] before parse param:ints [INFO] after parse param:ints +[INFO] before parse param:fake +[INFO] after parse param:fake [INFO] before parse param:strings [INFO] after parse param:strings [INFO] before parse param:ints [INFO] after parse param:ints +[INFO] before parse param:fake +[INFO] after parse param:fake [INFO] after parse method:com.itangcent.api.TestCtrl#header [INFO] before parse method:com.itangcent.api.TestCtrl#request [INFO] before parse param:httpServletRequest diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithOutExpanded.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithOutExpanded.txt index 8b4e7fb6f..e028b8da0 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithOutExpanded.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.spring.SpringRequestClassExporterTest.testExportFromTestCtrlWithOutExpanded.txt @@ -13,10 +13,14 @@ [INFO] after parse param:strings [INFO] before parse param:ints [INFO] after parse param:ints +[INFO] before parse param:fake +[INFO] after parse param:fake [INFO] before parse param:strings [INFO] after parse param:strings [INFO] before parse param:ints [INFO] after parse param:ints +[INFO] before parse param:fake +[INFO] after parse param:fake [INFO] after parse method:com.itangcent.api.TestCtrl#header [INFO] before parse method:com.itangcent.api.TestCtrl#request [INFO] before parse param:httpServletRequest diff --git a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.yapi.YapiApiExporterTest.DirectorySpringYapiApiExporterTest.txt b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.yapi.YapiApiExporterTest.DirectorySpringYapiApiExporterTest.txt index 07ad5747c..03c27edfd 100644 --- a/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.yapi.YapiApiExporterTest.DirectorySpringYapiApiExporterTest.txt +++ b/idea-plugin/src/test/resources/result/com.itangcent.idea.plugin.api.export.yapi.YapiApiExporterTest.DirectorySpringYapiApiExporterTest.txt @@ -779,6 +779,12 @@ "value": 0, "desc": "integer array", "required": 0 + }, + { + "name": "fake", + "value": 0, + "desc": "it was only a joke", + "required": 0 } ], "api_opened": false,