基于OpenAPI 3.0.1规范的接口定义文件实现的一种单参数异常自动化测试工具(带异常场景Trace信息)— 第2期:OpenAPI解析为模型

说明

在生成用例之前,需要将OpenAPI 3.0.1 的文件解析为对应的Api和Parameter模型

解析


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.snycedu.platform.test.model.Api;
import com.snycedu.platform.test.model.Parameter;

import java.io.File;
import java.math.BigDecimal;
import java.util.*;

public class OpenApiParser {

    public static List<Api> parseOpenApi(String yamlFilePath) throws Exception {
        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        Map<String, Object> openApiMap = mapper.readValue(new File(yamlFilePath), Map.class);

        // 全局组件(用于解析$ref)
        Map<String, Object> components = (Map<String, Object>) openApiMap.get("components");
        Map<String, Object> schemas = components != null ? (Map<String, Object>) components.get("schemas") : new HashMap<>();
        Map<String, Object> parameters = components != null ? (Map<String, Object>) components.get("parameters") : new HashMap<>();

        List<Api> apiList = new ArrayList<>();

        // 遍历所有路径
        Map<String, Object> paths = (Map<String, Object>) openApiMap.get("paths");
        for (String apiPath : paths.keySet()) {
            Map<String, Object> pathItem = (Map<String, Object>) paths.get(apiPath);

            // 遍历所有HTTP方法(get/post/put等)
            for (String method : pathItem.keySet()) {
                if (method.startsWith("x-")) continue; // 跳过扩展字段

                Map<String, Object> operation = (Map<String, Object>) pathItem.get(method);
                Api api = new Api();
                api.setApi(apiPath);
                api.setMethod(method.toUpperCase());
                api.setPath(new ArrayList<>());
                api.setQuery(new ArrayList<>());
                api.setBody(new ArrayList<>());

                // ----------------- 1. 解析路径参数 -----------------
                // 从URL路径中提取显式声明的路径参数(如 /api/{id})
                extractPathParamsFromUrl(apiPath, api.getPath());

                // 解析OpenAPI定义的parameters中的路径参数(覆盖自动提取的值)
                parseOperationParameters(operation, api, "path", parameters, schemas);

                // ----------------- 2. 解析查询参数 -----------------
                parseOperationParameters(operation, api, "query", parameters, schemas);

                // ----------------- 3. 解析请求体 -----------------
                Map<String, Object> requestBody = (Map<String, Object>) operation.get("requestBody");
                if (requestBody != null) {
                    Map<String, Object> content = (Map<String, Object>) ((Map<String, Object>) requestBody.get("content")).values().iterator().next();
                    Map<String, Object> schema = (Map<String, Object>) content.get("schema");
                    List<Parameter> bodyParams = parseSchema(schema, schemas, "#/components/schemas/", "");
                    api.getBody().addAll(bodyParams);
                }

                apiList.add(api);
            }
        }

        return apiList;
    }

    // 从URL路径(如 /users/{id}/detail)提取路径参数
    private static void extractPathParamsFromUrl(String apiPath, List<Parameter> pathParams) {
        String[] segments = apiPath.split("/");
        for (String segment : segments) {
            if (segment.startsWith("{") && segment.endsWith("}")) {
                String paramName = segment.substring(1, segment.length() - 1);
                Parameter param = new Parameter();
                param.setName(paramName);
                param.setLocation(paramName);
                param.setParamType("string"); // 默认类型,实际类型由parameters定义覆盖
                param.setRequired(true);      // 路径参数必填
                pathParams.add(param);
            }
        }
    }

    // 解析操作中的参数(path/query/header等)
    private static void parseOperationParameters(
            Map<String, Object> operation,
            Api api,
            String paramIn,
            Map<String, Object> globalParameters,
            Map<String, Object> schemas
    ) {
        List<Map<String, Object>> parameters = (List<Map<String, Object>>) operation.get("parameters");
        if (parameters == null) return;

        for (Map<String, Object> paramDef : parameters) {
            // 处理参数引用(如 $ref: '#/components/parameters/UserId')
            if (paramDef.containsKey("$ref")) {
                String ref = ((String) paramDef.get("$ref")).substring("#/components/parameters/".length());
                paramDef = (Map<String, Object>) globalParameters.get(ref);
            }

            String inType = (String) paramDef.get("in");
            if (!paramIn.equals(inType)) continue;

            Parameter param = new Parameter();
            param.setName((String) paramDef.get("name"));
            param.setLocation(param.getName());
            param.setRequired(Boolean.TRUE.equals(paramDef.get("required")));

            // 解析参数类型和约束
            Map<String, Object> schema = (Map<String, Object>) paramDef.get("schema");
            if (schema != null) {
                param.setParamType((String) schema.get("type"));
                applySchemaConstraints(param, schema);
                // 处理枚举
                if (schema.containsKey("enum")) {
                    param.setEnums((List<?>) schema.get("enum"));
                }
                // 处理嵌套对象/数组
                if ("object".equals(param.getParamType()) || "array".equals(param.getParamType())) {
                    param.setRefBody(parseSchema(schema, schemas, "#/components/schemas/", ""));
                }
            }

            // 添加到对应的参数列表
            if ("path".equals(inType)) {
                api.getPath().add(param);
            } else if ("query".equals(inType)) {
                api.getQuery().add(param);
            }
        }
    }

    private static List<Parameter> parseSchema(Map<String, Object> schema,
                                               Map<String, Object> schemas,
                                               String refPrefix,
                                               String parentPath) { // 新增parentPath参数
        List<Parameter> parameters = new ArrayList<>();

        // 处理$ref引用
        if (schema.containsKey("$ref")) {
            String ref = ((String) schema.get("$ref")).substring(refPrefix.length());
            Map<String, Object> refSchema = (Map<String, Object>) schemas.get(ref);
            return parseSchema(refSchema, schemas, refPrefix, parentPath); // 传递当前父路径
        }

        String type = (String) schema.get("type");
        List<String> required = (List<String>) schema.get("required");

        if ("object".equals(type)) {
            Map<String, Object> properties = (Map<String, Object>) schema.get("properties");
            for (String propName : properties.keySet()) {
                // 构建当前参数的完整路径
                String currentPath = parentPath.isEmpty() ? propName : parentPath + "." + propName;

                Map<String, Object> propSchema = (Map<String, Object>) properties.get(propName);
                Parameter param = createParameter(propName, propSchema, schemas, required, refPrefix, currentPath);
                parameters.add(param);
            }
        }

        return parameters;
    }

    private static Parameter createParameter(String name,
                                             Map<String, Object> schema,
                                             Map<String, Object> schemas,
                                             List<String> required,
                                             String refPrefix,
                                             String currentPath) { // 新增currentPath参数
        Parameter param = new Parameter();
        param.setName(name);
        param.setLocation(currentPath); // 直接使用构建好的路径

        String type = (String) schema.get("type");
        param.setParamType(type);
        param.setRequired(required != null && required.contains(name));
        applySchemaConstraints(param, schema);

        // 处理嵌套结构(递归调用时传递当前路径)
        if ("object".equals(type)) {
            param.setRefBody(parseSchema(schema, schemas, refPrefix, currentPath)); // 传递当前路径
        } else if ("array".equals(type)) {
            Map<String, Object> items = (Map<String, Object>) schema.get("items");
            param.setRefBody(parseSchema(items, schemas, refPrefix, currentPath)); // 传递当前路径
        } else if (schema.containsKey("$ref")) {
            String ref = ((String) schema.get("$ref")).substring(refPrefix.length());
            Map<String, Object> refSchema = (Map<String, Object>) schemas.get(ref);
            param.setParamType("object");
            param.setRefBody(parseSchema(refSchema, schemas, refPrefix, currentPath)); // 传递当前路径
        }

        return param;
    }

    // 应用Schema约束(最大值、正则等)
    private static void applySchemaConstraints(Parameter param, Map<String, Object> schema) {
        if (schema.containsKey("maximum")) {
            param.setMaximum(new BigDecimal(schema.get("maximum").toString()));
        }
        if (schema.containsKey("minimum")) {
            param.setMinimum(new BigDecimal(schema.get("minimum").toString()));
        }
        if (schema.containsKey("maxLength")) {
            param.setMaxLength((int) schema.get("maxLength"));
        }
        if (schema.containsKey("minLength")) {
            param.setMinLength((int) schema.get("minLength"));
        }
        if (schema.containsKey("pattern")) {
            param.setPattern((String) schema.get("pattern"));
        }
        if(schema.containsKey("enum")){
            param.setEnums((List<?>) schema.get("enum"));
        }
    }
}

编写测试

package com.snycedu.platform.test;

import com.alibaba.fastjson.JSON;
import com.snycedu.platform.test.model.Api;
import com.snycedu.platform.test.model.TestCaseScenario;
import com.snycedu.platform.test.parse.OpenApiParser;

import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception {
        // 构造基础请求负载
        List<Api> apiList = OpenApiParser.parseOpenApi("/Users/xiyubaby.17/Desktop/04workspace/testtools/src/main/resources/article-tmp.yaml");
        System.out.println(JSON.toJSONString(apiList));
    }
}

输出测试结果

[{
	"api": "/api/v1/article",
	"body": [{
		"location": "title",
		"maxLength": 32,
		"minLength": 10,
		"name": "title",
		"paramType": "string",
		"required": true
	}, {
		"enums": ["CSDN", "HEAD_NEWS"],
		"location": "type",
		"name": "type",
		"paramType": "string",
		"required": true
	}, {
		"location": "content",
		"name": "content",
		"paramType": "object",
		"refBody": [{
			"location": "content.id",
			"maximum": 99999999,
			"minimum": 100,
			"name": "id",
			"paramType": "integer",
			"required": false
		}, {
			"enums": ["MARKDOWN", "TEXT"],
			"location": "content.format",
			"name": "format",
			"paramType": "string",
			"required": false
		}],
		"required": true
	}],
	"method": "POST",
	"path": [],
	"query": []
}]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiyubaby.17

您的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值