合作QA是大聪明?撸个接口校验工具保命(2)

186 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的27天,点击查看活动详情

一、 背景 & 前情回顾

上一篇小作文给大家介绍了一下这个工具诞生的背景,从网上搜了一下似乎没有啥好的方案,经过对比得到了这个工具技术方案,工具需要满足以下的要求:

  1. 校验参数类型,目前设计的是 schema 形式,只要 schema 中声明的参数都认定为必须传;

  2. 我们设计了一种 schema 的形式来解决类型声明的问题,另外设计了 assert 函数来解决参数间有关联的场景;

  3. 当检测到参数异常的时候需要设计异常处理的出口;

这一篇我们继续讨论工具的细节问题

二、 支持的类型

2.1 某个指定的值

当某个参数是固定值是,参数类型对象实例:

export default {
  'some/api/name': {
    'paramName': {
       value: 'someVal '
    }
  }
}

或者简化成

export default {
  'some/api/name': {
    'paramName': 'someVal'
  }
}

2.2 符合某个规则的字符串

通过正则约束,示例:

export default {
  'some/api/name': {
    'paramName': { 
       type: /\d+/g 
     }
  }
}

2.3 基本类型的值

注意这个类型需要使用字面量的字符串,'string'、'number'、 'null'、 'undefined'、'boolean',

export default {
  'some/api/name': {
    'paramName': { 
       type: 'string'
     }
  }
}

此时参数的类型对象同样可以简写:

export default {
  'some/api/name': {
    'paramName': 'string'
  }
}

2.4 基本类型的联合类型

例如,'string|number',支持简写 { paramName: 'string|number' }

2.5 枚举值:'enum'

须指定枚举范围,示例

export default {
  'some/api/name': {
    'paramName': { 
       type: 'enum',
       enumRange: [28, 29] 
     }
  }
}

2.6 interface

当要校验的参数值是一个 json 时使用,类型需要使用 interface= 开头,等号后紧跟interface 名字,接口的声明须在和 type 同级的实现。

接口是可以无限制嵌套的,例如下面的 gradeInterface 就是 aInterfaceName 的子级接口,当然了,如果这个接口是复用的,建议单独将这个接口抽离成一个对象;

例如:

{ 
  paramName: {
    type: 'interface=aInterfaceName',
    aInterfaceName: {
      name: 'string',
      age: 'number',
      grades: 'interface=gradeInterface', // 接口可以无限制嵌套
      gradeInterface: { // 需要在使用的对象中声明这个接口
        math: 'number',
        chinese: 'number',
        pe: 'number',
        chemestry: 'number'
      }
    }
  },  
}

2.7 数组

数组分两种情况,精确类型数组和任意数组

  1. 精确类型数组,支持联合类型和 interface,例如 { paramName: { type: 'array[string]' } }

  2. 任意数组,注意不是 'array[any]',例如:{ paramName: { type: 'array' } }

export default {
  'aaa/bbb': {
    // 精确数组
    testAry: {
      type: 'array[number]'
    },
    
    // 也可以简写
    testAry2: 'array[number]',
    
    // 使用接口:
    testAry3: {
      type: 'array[interface=aryItemInterface]',
      aryItemIterface: {
        eid: 'string',
        charge: 'string|number'
      }
    },
    
    // 任意数组:
    testAry4: 'array' 
  }
}

2.8 处理特殊的参数间联动的场景

在该场景下,不再需要指定 type,取而代之的是 assert 断言函数以及该参数需要联动的其他参数 provide;

assert 函数接收三个参数:

  1. param 是 当前要校验的参数名对应的值;
  2. ctx 是参数校验工具提供的上下文对象,包含辅助校验需要的工具函数等
  3. inject 则是下面 provide 指定的与 xx 联动的参数对象,key 是参数名,例如下面的 reactive_with_xxx,值就是其对应的值

这里为啥需要显示的透传 provide 指定当前参数需要与哪个参数进行联动呢?直接把整个参数对象都传过来不好吗?这么设计不是考虑便利性,而是考虑确定性,provide 显示声明,不管谁看都能迅速发现 multi_uire_duc 参数与那些参数发生了联动;

注意!在所有这些类型中,assert 拥有最高的的优先级,一旦发现类型对象中有 assert 函数存在,则忽略其他类型校验;

export default {
  'aa/bb/cc/dd/ee': {
    multi_uire_duc: {
      assert (param, ctx, inject) {
        let isString = ctx.getTypeOf(param) === 'String'
        switch (+inject.urm_v1) {
          case 0:
          case 1:
            return !param || (isString && param.length === 0)
          case 2:
            let pJson = JSON.parse(param)
      
            return pJson.someValue ? true : '未通过校验!'
        }
      },
      provide: ['urm_v1']
    }
  },
}

三、来一个 schema 示例

在给出具体的 schema 之前,要提议一种组织 schema 的方式:

首先声明的是公参 schema,公参 schema 的接口使用 '*' 作为接口,它对应的参数对象将会 mixin 到各个接口参数中;

其次,根据业务功能抽离剩下功能模块的 schema,例如下单和购物车用一个 schema 对象;

3.1 公参 schema

这个 schema 表示公参有:xx、ee、zzz 三个参数;

export default {
  '*': {
    xx: {
      assert (param, ctx, inject) {
        if (isInWhiteList(ctx.api)) return true
        return ctx.getTypeOf(param) === 'String' ? true : 'xx is a CommonParam, please check'
      }
    },
    ee: {
      type: 'number'
    },

    zzz: {
      type: 'enum',
      enumRange: [28, 29]
    }
  }
}

3.2 某个业务模块的接口 schema


// 白名单列表,不推荐使用,但结合 assert 函数可以作为紧急出口
let commonParamsWhiteList = [
  // 'xx/xx'
]
const isInWhiteList = k => commonParamsWhiteList.includes(k)

// schema 主体
export default {
  'some/api/name': {
    zzz: {
      type: 'enum',
      enumRange: [28, 29]
    },
    xxx: {
      assert (param, ctx, inject) {
         // assert 需要返回 true 表示通过校验,
         // 其他返回值值均视为校验失败,并且作为异常信息抛出
         return inject.reactive_with_xx ? true : 'reactive_with_xx 不存在'
      },
      
      // xxx 参数会和 reactive_with_xx 参数联动
      provide: ['reactive_with_xx']
    }
  }
}

四、总结

本篇小作文详细讨论了这个接口校验工具支持的类型:

  1. 基本类型;
  2. 基本联合类型;
  3. 正则;
  4. 固定值;
  5. 接口;
  6. 数组,包含精确数组和任意数组,精确数组项直至以上所有类型;
  7. assert 函数,搭配 provide 指定联动参数,可以解决参数互相联动的问题;

下一篇,我们开始讨论具体的实现;