niuhe 语法

niuhe idl 简称 niuhe, 是智品网络后端团队开发的一门接口定义语言。

语法主要包括 注释, 常量(枚举), 数据类型(class/struct), 接口定义(路由), 数据字段定义五个部分。 语法示例图 niuhe 语法生成代码示意图

插件自定义了 .niuhe 后缀的代码高亮、代码提示和补全,也可选择 python 来做语法高亮

应用名称

应用名称需定义在入口文件 niuhe/all.niuhe 中定义,使用 #app=应用名 来定义。

#app=demo

注释

注释同 python 相同, 单行注释使用 #, 多行注释使用三个引号 ''', 类和常量的说明用注释的方式写在类定义的下一行。 当多行注释出现在 class 后时注释将作为 class 的说明

常量(枚举)

class Language(ConstGroup):
    '''语言枚举类'''
    ZH = Item("zh", "中文")
    EN = Item("en", "英文")

class LanguageType(ConstGroup):
    '''语言类型枚举'''
    ZH_CN = Item(1, "简体中文")
    ZH_TW = Item(2, "繁体中文")

# 下面为新语法定义 ConstGroup
# 新语法中支持通过属性名指定属性值, 支持 value, desc 和 depr 三个属性, 可混用
# depr 可出现在字段和类上, 表示该字段或类已废弃
class LanguageType(ConstGroup, depr=True):
    '''语言类型枚举'''
    ZH_CN = Item(value=1, desc="简体中文")
    ZH_TW = Item(2, desc="繁体中文")
    EN = Item(3, desc="英文", depr=True)
    FRANCE = Item(4, "法语", depr=True)

常量定义以 class 开头, 继承 ConstGroup, 内部通过 Item 来定义具体的常量值。常量仅支持 StringInteger 类型,且必须指定一个唯一标识符和一个显示名称。上述定义生成后的代码(src/demo/app/common/consts/gen_consts.go)为

package consts

// Generated by niuhe.idl

import "github.com/ma-guo/niuhe"

var Language struct {
	*niuhe.StringConstGroup
	ZH niuhe.StringConstItem `name:"中文" value:"zh"` // value: zh, name: 中文
	EN niuhe.StringConstItem `name:"英文" value:"en"` // value: en, name: 英文
}

// 语言类型枚举
var LanguageType struct {
	*niuhe.IntConstGroup
	ZH_CN niuhe.IntConstItem `name:"简体中文" value:"1"` // value: 1, name: 简体中文
	ZH_TW niuhe.IntConstItem `name:"繁体中文" value:"2"` // value: 2, name: 繁体中文
}

func init() {
	niuhe.InitConstGroup(&Language)
	niuhe.InitIntConstGroup(&LanguageType)
}

数据类型(class/struct)

在上一节 hello world 实战中,我们定义了一个接口的入参和出参类

class HelloReq():
    '''测试请求'''
    name = required.String(desc='用户名')

class HelloRsp(Message):
   '''测试响应'''
    greeting = required.String(desc='问候语')

# 可在 class 中通过 desc 和 depr 来定义类的描述和废弃状态
class HelloReq(depr=True, desc='测试请求', ver='1.1', used=True):
    name = required.String(desc='用户名')

数据类型定义以 class 开头, 继承 Message, Message 可不写, 类后跟随以注释形式的类说明(可选)和字段定义。 当类无字段时以 pass 做标记即可,可如下:

class HelloReq():
    '''测试请求, 无参数'''
    pass

上述定义生成的 struct 代码为:

package protos

// Generated by niuhe.idl
// 此文件由 niuhe.idl 自动生成, 请勿手动修改

// 测试请求
type HelloReq struct {
	Name string `json:"name" zpf_name:"name" zpf_reqd:"true"` //	用户名
}

// 测试响应
type HelloRsp struct {
	Greeting string `json:"greeting" zpf_name:"greeting" zpf_reqd:"true"` //	问候语
}

类的继承

类继承自Message可以继承自自定义的其他类, 通过在类名后添加括号的方式实现:

class NihaoReq(HelloReq):
    '''你好请求'''
    mingzi = required.String(desc='名字')

生成的 struct 代码为:

// 你好请求
type NihaoReq struct {
	Name   string `json:"name" zpf_name:"name" zpf_reqd:"true"`     //	用户名
	Mingzi string `json:"mingzi" zpf_name:"mingzi" zpf_reqd:"true"` //	名字
}

字段定义

字段定于语法为:

  member_name  = label.type(desc='', cls=..., group=..., ...)

其中

  • label 可以是 required(必填的), optional(可选的), repeated(重复的/数组) 三种
  • type 可以是 Integer, Decimal, Float, Long, String, Boolean, Message, Enum,StringEnum, File, Any 11种数据类型。

这里 requiredoptional 仅对入参有意义, 出参在 go 中无此限制。 repeated 在入参中同optional

字段可选属性有 desc, group, cls, depr, ver, demo, json,omit, value, reg, minlen, maxlen, minnum, maxnum, format 十五个属性。它们的说明如下

  • desc: 描述信息, 可选, 建议填写字段说明, 否则失去文档定义语言意义。
  • group: 指定 enumgroup, 仅当 typeEnumStringEnum 时有效和必填, 如 lang=required.Enum(desc='语言枚举', group=LanguageType)
  • cls: 指定类的类型,仅当 typeMessage 时有效和必填,如 message=optional.Message(desc='消息体',cls=HelloReq)
  • depr: depr=True/depr=true,将字段标记为 deprecated, 这在 docs 文档中有体现, 如导入到 apifox 中时字段将被标记为废弃(删除线), 便于同前端进行沟通.
  • ver: 指定字段的版本号, 如 ver='1.0.0。新加字段时建议标记,同 depr 一样,主要用于前后端沟通
  • demo: 指定字段的示例值, 如 demo=1, 这里赋予 demo 值的类型要同 type 字段标记的一致
  • json: 修改字段对应的 json 名, 如 json='name', 默认为空.
  • omit: 指定字段为 omitempty, 如 omit=True; 通常情况下,结构体中的零值字段(如int的0,string的"",bool的false等)会被序列化为JSON,即使它们没有被明确地设置。如果你不希望包含这些零值字段,可指定字段的 omit.
  • value: 定义 ConstGroup 时指定其值, 其他类型中无实际意义
  • reg: typeString 时验证其正则表达式
  • minlen: typeString 时验证其最小长度; 如 minlen=1
  • maxlen: typeString 时验证其最大长度; 如 maxlen=10
  • minnum: type 数字时验证其最小值; 如 minnum=1
  • maxnum: type 数字时验证其最大值; 如 maxnum=10
  • format: 用于指定日期格式, 如 format="2006-01-02"

实际上记住 desc, cls, group 三个即可,其他的 12 个是本插件拓展属性,不是高频使用属性

attrsreg, minlen, maxlen, minnum, maxnum 的属性请参考 zpform struct 定义

接口定义(路由)

接口定义以 with services(): 为开始标记, 后面跟随一个或多个路由定义,每个路由定义如下:

http_method('接口说明', 'api_path(/mode/view/method/)', 入参类名(可选), 出参类名(可选)[, author='xxx', status=tested])

其中

  • http_method 可以是 GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS 等 http method
  • 接口说明: 接口的简要描述, 如 '获取用户信息'
  • api_path: 必须是三段式 api 的路径, 如 /api/hello/world/
  • 入参类名: 可选, 如 HelloReq 当不填写时,得自己处理入参和出参的解析, 入参和出参得同时可选或同时必填。
  • 出参类名: 可选, 如 HelloRsp 当不填写时,得自己处理入参和出参的解析, 入参和出参得同时可选或同时必填。
  • author apifox 支持, 指定接口作者/责任人, 字符串
  • status apifox 支持, 指定 API状态, 可选值 designing|pending|developing|integrating|testing|tested|released|deprecated:exception:obsolete

本章节第二小节定义的路由如下:

with services():
    GET('示例接口', '/api/hello/world/', NihaoReq, HelloRsp)

生成的代码在 src/demo/app/api/views/[gen_]hello_views.go 文件中。

[] 表示可选, 下同

RPC 方式定义接口

在定义接口时,可以通过 RPC 关键字来定义一个接口。语法如下:

RPC('接口说明', [http_method/url=]'api_path(/mode/view/method/)', [author='xxx', status=tested])[.args(...)][.returns(...)][.codes(...)]

其中:

  • http_method/url=: 可选,指定 http 方法和路径,如 get='/api/hello/world/', url='/api/hello/world/' 或者直接写 '/api/hello/world/', 此时 http_methodGETPOST, http_method 可选值为 GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, 因此 RPC 方式中, 请求方法有九种定义方式。
  • .args(...): 可选,指定接口的入参,如 .args(name=required.String(desc='用户名'),...)
  • .returns(...): 可选,指定接口的出参,如 .returns(greeting=required.String(desc='问候语'),...)
  • .codes(...): 可选,指定接口的状态码和描述, 可用于约束 api 可返回的错误状态码范围,如 .codes(LanguageType.ZH_CN, LanguageType.ZH_TW, Item(100, '错误描述')), codes 中为定义的 Integer 枚举值。服务端返回值检测请参考 错误代码定义和检测
  • author apifox 支持, 指定接口作者/责任人, 字符串
  • status apifox 支持, 指定 API状态, 可选值 designing|pending|developing|integrating|testing|tested|released|deprecated:exception:obsolete

RPC 方式同 GET, POST 的差异是不用直接定义一个 class, 在一个文件中定义过多 class 时不便于一目了然地查阅接口的入参和出参。

同时 RPC 方式支持只定义入参和出参的情况, 此时会生成一个空结构来填补另一个未定义的参数结构

include 引用

在定义类时,可以通过 include 关键字引用其他文件中的类. 如在 comm.niuhe 文件中定义了如下内容:

# 公共类型定义

class NoneResp():
    '''空响应'''
    pass

class NoneReq():
    '''空请求'''
    pass

此时可在 *.niuhe 文件中通过 include 引入:

include('comm.niuhe')
# 然后通过 comm.NoneReq, comm.NoneResp 来使用这些类
with services():
    GET('include 引用示例', '/api/include/demo/', comm.NoneReq, comm.NoneResp)

include 支持跨文件引用其他目录中的 *.niuhe 文件

其他说明

入参参数中, 并不支持解析复杂结构, 如 Message, Any, Dict 等. 而出参则无此限制, 可以自由定义返回的 json 数据格式。

niuhe idl 中的更多关键字请参阅 niuhe 关键字