Skip to content

Commit

Permalink
feat: ✨ Add support for Map with enum type keys
Browse files Browse the repository at this point in the history
  • Loading branch information
linwumingshi committed Aug 8, 2024
1 parent c902619 commit 7aca4e5
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public Map<String, Object> buildPaths(ApiConfig apiConfig, ApiSchema<ApiDoc> api
String[] paths = methodDoc.getPath().split(";");
for (String path : paths) {
path = path.trim();
Map<String, Object> request = buildPathUrls(apiConfig, methodDoc, methodDoc.getClazzDoc(),
Map<String, Object> request = this.buildPathUrls(apiConfig, methodDoc, methodDoc.getClazzDoc(),
apiSchema.getApiExceptionStatuses());
if (!pathMap.containsKey(path)) {
pathMap.put(path, request);
Expand Down Expand Up @@ -449,8 +449,11 @@ public Map<String, Object> buildProperties(List<ApiParam> apiParam, Map<String,
}

/**
* component schema properties data
* @param apiParam ApiParam
* component schema properties
* @param apiParam list of ApiParam
* @param component component
* @param isResp is response
* @return properties
*/
private Map<String, Object> buildPropertiesData(ApiParam apiParam, Map<String, Object> component, boolean isResp) {
Map<String, Object> propertiesData = new HashMap<>();
Expand Down Expand Up @@ -540,12 +543,12 @@ public Map<String, Object> buildComponentData(ApiSchema<ApiDoc> apiSchema) {
// request components
String requestSchema = OpenApiSchemaUtil.getClassNameFromParams(method.getRequestParams());
List<ApiParam> requestParams = method.getRequestParams();
Map<String, Object> prop = buildProperties(requestParams, component, false);
Map<String, Object> prop = this.buildProperties(requestParams, component, false);
component.put(requestSchema, prop);
// response components
List<ApiParam> responseParams = method.getResponseParams();
String responseSchemaName = OpenApiSchemaUtil.getClassNameFromParams(method.getResponseParams());
component.put(responseSchemaName, buildProperties(responseParams, component, true));
component.put(responseSchemaName, this.buildProperties(responseParams, component, true));
});
});
// excption response components
Expand Down
49 changes: 18 additions & 31 deletions src/main/java/com/ly/doc/builder/openapi/OpenApiBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@
import com.ly.doc.constants.DocGlobalConstants;
import com.ly.doc.constants.Methods;
import com.ly.doc.constants.ParamTypeConstants;
import com.ly.doc.helper.JavaProjectBuilderHelper;
import com.ly.doc.model.*;
import com.ly.doc.model.openapi.OpenApiTag;
import com.ly.doc.utils.JsonUtil;
import com.ly.doc.utils.OpenApiSchemaUtil;
import com.power.common.util.CollectionUtil;
import com.power.common.util.FileUtil;
import com.ly.doc.helper.JavaProjectBuilderHelper;
import com.ly.doc.model.openapi.OpenApiTag;
import com.ly.doc.utils.JsonUtil;
import com.power.common.util.StringUtil;
import com.thoughtworks.qdox.JavaProjectBuilder;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -70,11 +69,6 @@ public static void buildOpenApi(ApiConfig config, JavaProjectBuilder projectBuil
INSTANCE.openApiCreate(config, apiSchema);
}

/**
* Build OpenApi
* @param config Configuration of smart-doc
* @param apiSchema List of API DOC
*/
@Override
public void openApiCreate(ApiConfig config, ApiSchema<ApiDoc> apiSchema) {
this.setComponentKey(getModuleName());
Expand All @@ -84,8 +78,8 @@ public void openApiCreate(ApiConfig config, ApiSchema<ApiDoc> apiSchema) {
json.put("servers", buildServers(config));
Set<OpenApiTag> tags = new HashSet<>();
json.put("tags", tags);
json.put("paths", buildPaths(config, apiSchema, tags));
json.put("components", buildComponentsSchema(apiSchema));
json.put("paths", this.buildPaths(config, apiSchema, tags));
json.put("components", this.buildComponentsSchema(apiSchema));

String filePath = config.getOutPath();
filePath = filePath + DocGlobalConstants.OPEN_API_JSON;
Expand All @@ -96,6 +90,7 @@ public void openApiCreate(ApiConfig config, ApiSchema<ApiDoc> apiSchema) {
/**
* Build openapi info
* @param apiConfig Configuration of smart-doc
* @return Map
*/
private static Map<String, Object> buildInfo(ApiConfig apiConfig) {
Map<String, Object> infoMap = new HashMap<>(8);
Expand All @@ -107,6 +102,7 @@ private static Map<String, Object> buildInfo(ApiConfig apiConfig) {
/**
* Build Servers
* @param config Configuration of smart-doc
* @return List of Map
*/
private static List<Map<String, Object>> buildServers(ApiConfig config) {
List<Map<String, Object>> serverList = new ArrayList<>();
Expand All @@ -116,12 +112,6 @@ private static List<Map<String, Object>> buildServers(ApiConfig config) {
return serverList;
}

/**
* Build request
* @param apiConfig Configuration of smart-doc
* @param apiMethodDoc ApiMethodDoc
* @param apiDoc apiDoc
*/
@Override
public Map<String, Object> buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc, ApiDoc apiDoc,
List<ApiExceptionStatus> apiExceptionStatuses) {
Expand All @@ -139,24 +129,26 @@ public Map<String, Object> buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDo
// request.put("tags", new String[]{tag});
// }
request.put("tags", apiMethodDoc.getTagRefs().stream().map(TagDoc::getTag).toArray());
request.put("requestBody", buildRequestBody(apiConfig, apiMethodDoc));
request.put("parameters", buildParameters(apiMethodDoc));
request.put("responses", buildResponses(apiConfig, apiMethodDoc, apiExceptionStatuses));
request.put("requestBody", this.buildRequestBody(apiConfig, apiMethodDoc));
request.put("parameters", this.buildParameters(apiMethodDoc));
request.put("responses", this.buildResponses(apiConfig, apiMethodDoc, apiExceptionStatuses));
request.put("deprecated", apiMethodDoc.isDeprecated());
List<String> paths = OpenApiSchemaUtil.getPatternResult("[A-Za-z0-9_{}]*", apiMethodDoc.getPath());
paths.add(apiMethodDoc.getType());
String operationId = paths.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining("-"));
request.put("operationId", operationId);
// add extension attribution
if (apiMethodDoc.getExtensions() != null) {
apiMethodDoc.getExtensions().entrySet().forEach(e -> request.put("x-" + e.getKey(), e.getValue()));
apiMethodDoc.getExtensions().forEach((key, value) -> request.put("x-" + key, value));
}
return request;
}

/**
* Build request body
* Build requestBody
* @param apiConfig Configuration of smart-doc
* @param apiMethodDoc ApiMethodDoc
* @return requestBody Map
*/
private Map<String, Object> buildRequestBody(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc) {
Map<String, Object> requestBody = new HashMap<>(8);
Expand All @@ -165,22 +157,17 @@ private Map<String, Object> buildRequestBody(ApiConfig apiConfig, ApiMethodDoc a
|| apiMethodDoc.getType().equals(Methods.PATCH.getValue()));
// add content of post method
if (isPost) {
requestBody.put("content", buildContent(apiConfig, apiMethodDoc, false));
requestBody.put("content", this.buildContent(apiConfig, apiMethodDoc, false));
return requestBody;
}
return null;
}

/**
* response body
* @param apiMethodDoc ApiMethodDoc
* @return response body
*/
@Override
public Map<String, Object> buildResponsesBody(ApiConfig apiConfig, ApiMethodDoc apiMethodDoc) {
Map<String, Object> responseBody = new HashMap<>(10);
responseBody.put("description", "OK");
responseBody.put("content", buildContent(apiConfig, apiMethodDoc, true));
responseBody.put("content", this.buildContent(apiConfig, apiMethodDoc, true));
return responseBody;
}

Expand Down Expand Up @@ -266,7 +253,7 @@ else if (desc.contains("string")) {
parameters.putAll(buildParametersSchema(apiParam));
}
if (apiParam.getExtensions() != null && !apiParam.getExtensions().isEmpty()) {
apiParam.getExtensions().entrySet().forEach(e -> parameters.put("x-" + e.getKey(), e.getValue()));
apiParam.getExtensions().forEach((key, value) -> parameters.put("x-" + key, value));
}

return parameters;
Expand All @@ -275,7 +262,7 @@ else if (desc.contains("string")) {
@Override
public Map<String, Object> buildComponentsSchema(ApiSchema<ApiDoc> apiSchema) {
Map<String, Object> schemas = new HashMap<>(4);
schemas.put("schemas", buildComponentData(apiSchema));
schemas.put("schemas", this.buildComponentData(apiSchema));
return schemas;
}

Expand Down
9 changes: 4 additions & 5 deletions src/main/java/com/ly/doc/builder/openapi/SwaggerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@
import com.ly.doc.constants.DocGlobalConstants;
import com.ly.doc.constants.MediaType;
import com.ly.doc.constants.ParamTypeConstants;
import com.ly.doc.helper.JavaProjectBuilderHelper;
import com.ly.doc.model.*;
import com.ly.doc.model.openapi.OpenApiTag;
import com.ly.doc.utils.DocUtil;
import com.ly.doc.utils.OpenApiSchemaUtil;
import com.ly.doc.utils.JsonUtil;
import com.power.common.util.CollectionUtil;
import com.power.common.util.FileUtil;
import com.power.common.util.StringUtil;
import com.ly.doc.helper.JavaProjectBuilderHelper;
import com.ly.doc.model.openapi.OpenApiTag;
import com.ly.doc.utils.JsonUtil;
import com.thoughtworks.qdox.JavaProjectBuilder;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -138,7 +137,7 @@ public Map<String, Object> buildPathUrlsRequest(ApiConfig apiConfig, ApiMethodDo
.stream()
.filter(StringUtil::isNotEmpty)
.toArray(n -> new String[n]));
List<Map<String, Object>> parameters = buildParameters(apiMethodDoc);
List<Map<String, Object>> parameters = this.buildParameters(apiMethodDoc);
// requestBody
if (CollectionUtil.isNotEmpty(apiMethodDoc.getRequestParams())) {
Map<String, Object> parameter = new HashMap<>();
Expand Down
36 changes: 29 additions & 7 deletions src/main/java/com/ly/doc/helper/JsonBuildHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,12 @@ else if (JavaClassValidateUtil.isMap(typeName)) {
data.append("{\"mapKey\":{}}");
return data.toString();
}
if ((!JavaTypeConstants.JAVA_STRING_FULLY.equals(getKeyValType[0])) && apiConfig.isStrict()) {
throw new RuntimeException("Map's key can only use String for json,but you use " + getKeyValType[0]);
JavaClass mapKeyClass = builder.getJavaProjectBuilder().getClassByName(getKeyValType[0]);
boolean mapKeyIsEnum = mapKeyClass.isEnum();
if ((!JavaTypeConstants.JAVA_STRING_FULLY.equals(getKeyValType[0]) || !mapKeyIsEnum
|| mapKeyClass.getEnumConstants().isEmpty()) && apiConfig.isStrict()) {
throw new RuntimeException(
"Map's key can only use String or Enum for json,but you use " + getKeyValType[0]);
}
String gicName = genericCanonicalName.substring(genericCanonicalName.indexOf(",") + 1,
genericCanonicalName.lastIndexOf(">"));
Expand All @@ -228,11 +232,29 @@ else if (gicName.contains("<")) {
data.append("{").append("\"mapKey\":").append(json).append("}");
}
else {
data.append("{")
.append("\"mapKey\":")
.append(buildJson(gicName, genericCanonicalName, isResp, counter + 1, registryClasses, groupClasses,
builder))
.append("}");
if (mapKeyIsEnum) {
data.append("{");
for (JavaField field : mapKeyClass.getFields()) {
data.append("\"")
.append(field.getName())
.append("\":")
.append(buildJson(gicName, genericCanonicalName, isResp, counter + 1, registryClasses,
groupClasses, builder))
.append(",");
}
// Remove the trailing comma
if (data.charAt(data.length() - 1) == ',') {
data.deleteCharAt(data.length() - 1);
}
data.append("}");
}
else {
data.append("{")
.append("\"mapKey\":")
.append(buildJson(gicName, genericCanonicalName, isResp, counter + 1, registryClasses,
groupClasses, builder))
.append("}");
}
}
return data.toString();
}
Expand Down
82 changes: 74 additions & 8 deletions src/main/java/com/ly/doc/helper/ParamsBuildHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public static List<ApiParam> buildParams(String className, String pre, int level
fieldNameConvert = PropertyNameHelper.translate(clsAnnotation);
}
JavaClassUtil.genericParamMap(genericMap, cls, globGicName);
List<DocJavaField> fields = JavaClassUtil.getFields(cls, 0, new LinkedHashMap<>(), classLoader);

if (JavaClassValidateUtil.isPrimitive(simpleName)) {
String processedType = processFieldTypeName(isShowJavaType, simpleName);
paramList.addAll(primitiveReturnRespComment(processedType, atomicInteger, pid));
Expand Down Expand Up @@ -166,7 +166,7 @@ else if (JavaClassValidateUtil.isReactor(simpleName)) {
}
else {
Map<String, String> ignoreFields = JavaClassUtil.getClassJsonIgnoreFields(cls);

List<DocJavaField> fields = JavaClassUtil.getFields(cls, 0, new LinkedHashMap<>(), classLoader);
out: for (DocJavaField docField : fields) {
JavaField field = docField.getJavaField();
String maxLength = JavaFieldUtil.getParamMaxLength(field.getAnnotations());
Expand Down Expand Up @@ -649,6 +649,22 @@ else if (simpleName.equals(subTypeName)) {
return paramList;
}

/**
* Builds a list of {@link ApiParam} objects for a map parameter.
* @param globGicName the global generic name array
* @param pre the prefix string
* @param level the level of the parameter
* @param isRequired the requirement status of the parameter
* @param isResp the response flag
* @param registryClasses the map of registry classes
* @param projectBuilder the project configuration builder
* @param groupClasses the set of group classes
* @param pid the parent ID
* @param jsonRequest the JSON request flag
* @param nextLevel the next level of the parameter
* @param atomicInteger the atomic integer for generating unique IDs
* @return a list of {@link ApiParam} objects
*/
private static List<ApiParam> buildMapParam(String[] globGicName, String pre, int level, String isRequired,
boolean isResp, Map<String, String> registryClasses, ProjectDocConfigBuilder projectBuilder,
Set<String> groupClasses, int pid, boolean jsonRequest, int nextLevel, AtomicInteger atomicInteger) {
Expand All @@ -659,11 +675,13 @@ private static List<ApiParam> buildMapParam(String[] globGicName, String pre, in
// mock map key param
String mapKeySimpleName = DocClassUtil.getSimpleName(globGicName[0]);
String valueSimpleName = DocClassUtil.getSimpleName(globGicName[1]);
// get map key class
JavaClass mapKeyClass = projectBuilder.getJavaProjectBuilder().getClassByName(mapKeySimpleName);

boolean isShowJavaType = projectBuilder.getApiConfig().getShowJavaType();
String valueSimpleNameType = processFieldTypeName(isShowJavaType, valueSimpleName);
List<ApiParam> paramList = new ArrayList<>();
if (JavaClassValidateUtil.isPrimitive(mapKeySimpleName)) {
boolean isShowJavaType = projectBuilder.getApiConfig().getShowJavaType();
String valueSimpleNameType = processFieldTypeName(isShowJavaType, valueSimpleName);
ApiParam apiParam = ApiParam.of()
.setField(pre + "mapKey")
.setType(valueSimpleNameType)
Expand All @@ -676,18 +694,66 @@ private static List<ApiParam> buildMapParam(String[] globGicName, String pre, in
.setId(atomicOrDefault(atomicInteger, ++pid));
paramList.add(apiParam);
}
else if (Objects.nonNull(mapKeyClass) && mapKeyClass.isEnum() && !mapKeyClass.getEnumConstants().isEmpty()) {
Integer keyParentId = null;
for (JavaField enumConstant : mapKeyClass.getEnumConstants()) {
ApiParam apiParam = ApiParam.of()
.setField(pre + enumConstant.getName())
.setType(valueSimpleNameType)
.setClassName(valueSimpleName)
.setDesc(Optional.ofNullable(projectBuilder.getClassByName(valueSimpleName))
.map(JavaClass::getComment)
.orElse("A map key."))
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setPid(null == keyParentId ? pid : keyParentId)
.setId(paramList.size() + 1);
if (null == keyParentId) {
keyParentId = apiParam.getPid();
}
paramList.add(apiParam);
List<ApiParam> apiParams = addValueParams(valueSimpleName, globGicName, level, isRequired, isResp,
registryClasses, projectBuilder, groupClasses, apiParam.getId(), jsonRequest, nextLevel,
atomicInteger);
paramList.addAll(apiParams);
}
return paramList;
}
paramList.addAll(addValueParams(valueSimpleName, globGicName, level, isRequired, isResp, registryClasses,
projectBuilder, groupClasses, pid, jsonRequest, nextLevel, atomicInteger));
return paramList;
}

/**
* Adds parameters for the map value to the parameter list.
* @param valueSimpleName the simple name of the value type
* @param globGicName the global generic name array
* @param level the level of the parameter
* @param isRequired the requirement status of the parameter
* @param isResp the response flag
* @param registryClasses the map of registry classes
* @param projectBuilder the project configuration builder
* @param groupClasses the set of group classes
* @param pid the parent ID
* @param jsonRequest the JSON request flag
* @param nextLevel the next level of the parameter
* @param atomicInteger the atomic integer for generating unique IDs
* @return the list of {@link ApiParam} objects
*/
private static List<ApiParam> addValueParams(String valueSimpleName, String[] globGicName, int level,
String isRequired, boolean isResp, Map<String, String> registryClasses,
ProjectDocConfigBuilder projectBuilder, Set<String> groupClasses, int pid, boolean jsonRequest,
int nextLevel, AtomicInteger atomicInteger) {
// build param when map value is not primitive
if (JavaClassValidateUtil.isPrimitive(valueSimpleName)) {
return paramList;
return Collections.emptyList();
}
StringBuilder preBuilder = new StringBuilder();
for (int j = 0; j < level; j++) {
preBuilder.append(DocGlobalConstants.FIELD_SPACE);
}
preBuilder.append(DocGlobalConstants.PARAM_PREFIX);
paramList.addAll(buildParams(globGicName[1], preBuilder.toString(), ++nextLevel, isRequired, isResp,
registryClasses, projectBuilder, groupClasses, pid, jsonRequest, atomicInteger));
return paramList;
return buildParams(globGicName[1], preBuilder.toString(), ++nextLevel, isRequired, isResp, registryClasses,
projectBuilder, groupClasses, pid, jsonRequest, atomicInteger);
}

public static String dictionaryListComment(List<EnumDictionary> enumDataDict) {
Expand Down
Loading

0 comments on commit 7aca4e5

Please sign in to comment.