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 来定义具体的常量值。常量仅支持 String 和 Integer 类型,且必须指定一个唯一标识符和一个显示名称。上述定义生成后的代码(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,Any11种数据类型。
这里
required和optional仅对入参有意义, 出参在go中无此限制。repeated在入参中同optional
字段可选属性有 desc, group, cls, depr, ver, demo, json,omit, value, reg, minlen, maxlen, minnum, maxnum, format 十五个属性。它们的说明如下
desc: 描述信息, 可选, 建议填写字段说明, 否则失去文档定义语言意义。group: 指定enum的group, 仅当type为Enum和StringEnum时有效和必填, 如lang=required.Enum(desc='语言枚举', group=LanguageType)cls: 指定类的类型,仅当type为Message时有效和必填,如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:type为String时验证其正则表达式minlen:type为String时验证其最小长度; 如minlen=1maxlen:type为String时验证其最大长度; 如maxlen=10minnum:type数字时验证其最小值; 如minnum=1maxnum:type数字时验证其最大值; 如maxnum=10format: 用于指定日期格式, 如format="2006-01-02"
实际上记住
desc,cls,group三个即可,其他的 12 个是本插件拓展属性,不是高频使用属性
attrs中reg,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当不填写时,得自己处理入参和出参的解析, 入参和出参得同时可选或同时必填。authorapifox 支持, 指定接口作者/责任人, 字符串statusapifox 支持, 指定 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_method为GET和POST,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枚举值。服务端返回值检测请参考 错误代码定义和检测authorapifox 支持, 指定接口作者/责任人, 字符串statusapifox 支持, 指定 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 关键字