跳到主要内容

api语法

api示例

/**
* api语法示例及语法说明
*/

// api语法版本
syntax = "v1"

// import literal
import "foo.api"

// import group
import (
"bar.api"
"foo/bar.api"
)
info(
author: "songmeizi"
date: "2020-01-08"
desc: "api语法示例及语法说明"
)

// type literal

type Foo{
Foo int `json:"foo"`
}

// type group

type(
Bar{
Bar int `json:"bar"`
}
)

// service block
@server(
jwt: Auth
group: foo
)
service foo-api{
@doc "foo"
@handler foo
post /foo (Foo) returns (Bar)
}

api语法结构

  • syntax语法声明
  • import语法块
  • info语法块
  • type语法块
  • service语法块
  • 隐藏通道
提示

在以上语法结构中,各个语法块从语法上来说,按照语法块为单位,可以在.api文件中任意位置声明, 但是为了提高阅读效率,我们建议按照以上顺序进行声明,因为在将来可能会通过严格模式来控制语法块的顺序。

syntax语法声明

syntax是新加入的语法结构,该语法的引入可以解决:

  • 快速针对api版本定位存在问题的语法结构
  • 针对版本做语法解析
  • 防止api语法大版本升级导致前后不能向前兼容
警告

被import的api必须要和main api的syntax版本一致。

语法定义

'syntax'={checkVersion(p)}STRING

语法说明

syntax:固定token,标志一个syntax语法结构的开始

checkVersion:自定义go方法,检测STRING是否为一个合法的版本号,目前检测逻辑为,STRING必须是满足(?m)"v[1-9][0-9]*"正则。

STRING:一串英文双引号包裹的字符串,如"v1"

一个api语法文件只能有0或者1个syntax语法声明,如果没有syntax,则默认为v1版本

正确语法示例

eg1:不规范写法

syntax="v1"

eg2:规范写法(推荐)

syntax = "v2"

错误语法示例

eg1:

syntax = "v0"

eg2:

syntax = v1

eg3:

syntax = "V1"

import语法块

随着业务规模增大,api中定义的结构体和服务越来越多,所有的语法描述均为一个api文件,这是多么糟糕的一个问题, 其会大大增加了阅读难度和维护难度,import语法块可以帮助我们解决这个问题,通过拆分api文件, 不同的api文件按照一定规则声明,可以降低阅读难度和维护难度。

警告

这里import不像golang那样包含package声明,仅仅是一个文件路径的引入,最终解析后会把所有的声明都汇聚到一个spec.Spec中。 不能import多个相同路径,否则会解析错误。

语法定义

'import' {checkImportValue(p)}STRING  
|'import' '(' ({checkImportValue(p)}STRING)+ ')'

语法说明

import:固定token,标志一个import语法的开始

checkImportValue:自定义go方法,检测STRING是否为一个合法的文件路径,目前检测逻辑为,STRING必须是满足(?m)"(/?[a-zA-Z0-9_#-])+\.api"正则。

STRING:一串英文双引号包裹的字符串,如"foo.api"

正确语法示例

eg:

import "foo.api"
import "foo/bar.api"

import(
"bar.api"
"foo/bar/foo.api"
)

错误语法示例

eg:

import foo.api
import "foo.txt"
import (
bar.api
bar.api
)

info语法块

info语法块是一个包含了多个键值对的语法体,其作用相当于一个api服务的描述,解析器会将其映射到spec.Spec中, 以备用于翻译成其他语言(golang、java等) 时需要携带的meta元素。如果仅仅是对当前api的一个说明,而不考虑其翻译 时传递到其他语言,则使用简单的多行注释或者java风格的文档注释即可,关于注释说明请参考下文的 隐藏通道

警告

不能使用重复的key,每个api文件只能有0或者1个info语法块

语法定义

'info' '(' (ID {checkKeyValue(p)}VALUE)+ ')'

语法说明

info:固定token,标志一个info语法块的开始

checkKeyValue:自定义go方法,检测VALUE是否为一个合法值。

VALUE:key对应的值,可以为单行的除'\r','\n','/'后的任意字符,多行请以""包裹,不过强烈建议所有都以""包裹

正确语法示例

eg1:不规范写法

info(
foo: foo value
bar:"bar value"
desc:"long long long long
long long text"
)

eg2:规范写法(推荐)

info(
foo: "foo value"
bar: "bar value"
desc: "long long long long long long text"
)

错误语法示例

eg1:没有key-value内容

info()

eg2:不包含冒号

info(
foo value
)

eg3:key-value没有换行

info(foo:"value")

eg4:没有key

info(
: "value"
)

eg5:非法的key

info(
12: "value"
)

eg6:移除旧版本多行语法

info(
foo: >
some text
<
)

type语法块

在api服务中,我们需要用到一个结构体(类)来作为请求体,响应体的载体,因此我们需要声明一些结构体来完成这件事情, type语法块由golang的type演变而来,当然也保留着一些golang type的特性,沿用golang特性有:

  • 保留了golang内置数据类型bool,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr ,float32,float64,complex64,complex128,string,byte,rune,
  • 兼容golang struct风格声明
  • 保留golang关键字
警告
  • 不支持alias
  • 不支持time.Time数据类型
  • 结构体名称、字段名称、不能为golang关键字

语法定义

由于其和golang相似,因此不做详细说明,具体语法定义请在 ApiParser.g4 中查看typeSpec定义。

语法说明

参考golang写法

正确语法示例

eg1:不规范写法

type Foo struct{
Id int `path:"id"` // ①
Foo int `json:"foo"`
}

type Bar struct{
// 非导出型字段
bar int `form:"bar"`
}

type(
// 非导出型结构体
fooBar struct{
FooBar int `json:"fooBar"`
}
)

eg2:规范写法(推荐)

type Foo{
Id int `path:"id"`
Foo int `json:"foo"`
}

type Bar{
Bar int `form:"bar"`
}

type(
FooBar{
FooBar int `json:"fooBar"`
}
)

错误语法示例

eg

type Gender int // 不支持

// 非struct token
type Foo structure{
CreateTime time.Time // 不支持time.Time,且没有声明 tag
}

// golang关键字 var
type var{}

type Foo{
// golang关键字 interface
Foo interface // 没有声明 tag
}


type Foo{
foo int
// map key必须要golang内置数据类型,且没有声明 tag
m map[Bar]string
}
提示

tag定义和golang中json tag语法一样,除了json tag外,go-zero还提供了另外一些tag来实现对字段的描述, 详情见下表。

  • tag表
tag key描述提供方有效范围示例
jsonjson序列化taggolangrequest、responsejson:"fooo"
path路由path,如/foo/:idgo-zerorequestpath:"id"
form标志请求体是一个form(POST方法时)或者一个query(GET方法时/search?name=keyword)go-zerorequestform:"name"
headerHTTP header,如 Name: valuego-zerorequestheader:"name"
  • tag修饰符

    常见参数校验描述 |tag key|描述|提供方|有效范围|示例| |---|---|---|---|---| | optional | typeBlock | go-zero | request | json:"name,optional" | | options | typeLit | go-zero | request | json:"gender,options=male" | | default | atServer | go-zero | request | json:"gender,default=male" | | range | serviceRoute | go-zero | request | json:"age,range=[0:120]" |

提示

tag修饰符需要在tag value后以引文逗号,隔开

service语法块

service语法块用于定义api服务,包含服务名称,服务metadata,中间件声明,路由,handler等。

警告
  • main api和被import的api服务名称必须一致,不能出现服务名称歧义。
  • handler名称不能重复
  • 路由(请求方法+请求path)名称不能重复
  • 请求体必须声明为普通(非指针)struct,响应体做了一些向前兼容处理,详请见下文说明

语法定义

serviceSpec:    atServer? serviceApi;
atServer: '@server' lp='(' kvLit+ rp=')';
serviceApi: {match(p,"service")}serviceToken=ID serviceName lbrace='{' serviceRoute* rbrace='}';
serviceRoute: atDoc? (atServer|atHandler) route;
atDoc: '@doc' lp='('? ((kvLit+)|STRING) rp=')'?;
atHandler: '@handler' ID;
route: {checkHttpMethod(p)}httpMethod=ID path request=body? returnToken=ID? response=replybody?;
body: lp='(' (ID)? rp=')';
replybody: lp='(' dataType? rp=')';
// kv
kvLit: key=ID {checkKeyValue(p)}value=LINE_VALUE;

serviceName: (ID '-'?)+;
path: (('/' (ID ('-' ID)*))|('/:' (ID ('-' ID)?)))+;

语法说明

serviceSpec:包含了一个可选语法块atServerserviceApi语法块,其遵循序列模式(编写service必须要按照顺序,否则会解析出错)

atServer: 可选语法块,定义key-value结构的server metadata,'@server' 表示这一个server语法块的开始,其可以用于描述serviceApi或者route语法块,其用于描述不同语法块时有一些特殊关键key 需要值得注意,见 atServer关键key描述说明

serviceApi:包含了1到多个serviceRoute语法块

serviceRoute:按照序列模式包含了atDoc,handler和route

atDoc:可选语法块,一个路由的key-value描述,其在解析后会传递到spec.Spec结构体,如果不关心传递到spec.Spec, 推荐用单行注释替代。

handler:是对路由的handler层描述,可以通过atServer指定handler key来指定handler名称, 也可以直接用atHandler语法块来定义handler名称

atHandler:'@handler' 固定token,后接一个遵循正则[_a-zA-Z][a-zA-Z_-]*)的值,用于声明一个handler名称

route:路由,有httpMethodpath、可选request、可选response组成,httpMethod是必须是小写。

body:api请求体语法定义,必须要由()包裹的可选的ID值

replyBody:api响应体语法定义,必须由()包裹的struct、array(向前兼容处理,后续可能会废弃,强烈推荐以struct包裹,不要直接用array作为响应体)

kvLit: 同info key-value

serviceName: 可以有多个'-'join的ID值

path:api请求路径,必须以'/'或者'/:'开头,切不能以'/'结尾,中间可包含ID或者多个以'-'join的ID字符串

atServer关键key描述说明

修饰service时

key描述示例
jwt声明当前service下所有路由需要jwt鉴权,且会自动生成包含jwt逻辑的代码jwt: Auth
group声明当前service或者路由文件分组group: login
middleware声明当前service需要开启中间件middleware: AuthMiddleware
prefix添加路由分组prefix: /api

修饰route时

key描述示例
handler声明一个handler

正确语法示例

eg1:不规范写法

@server(
jwt: Auth
group: foo
middleware: AuthMiddleware
prefix /api
)
service foo-api{
@doc(
summary: foo
)
@server(
handler: foo
)
// 非导出型body
post /foo/:id (foo) returns (bar)

@doc "bar"
@handler bar
post /bar returns ([]int)// 不推荐数组作为响应体

@handler fooBar
post /foo/bar (Foo) returns // 可以省略'returns'
}

eg2:规范写法(推荐)

@server(
jwt: Auth
group: foo
middleware: AuthMiddleware
prefix: /api
)
service foo-api{
@doc "foo"
@handler foo
post /foo/:id (Foo) returns (Bar)
}

service foo-api{
@handler ping
get /ping

@doc "foo"
@handler bar
post /bar/:id (Foo)
}

错误语法示例

// 不支持空的server语法块
@server(
)
// 不支持空的service语法块
service foo-api{
}

service foo-api{
@doc kkkk // 简版doc必须用英文双引号引起来
@handler foo
post /foo

@handler foo // 重复的handler
post /bar

@handler fooBar
post /bar // 重复的路由

// @handler和@doc顺序错误
@handler someHandler
@doc "some doc"
post /some/path

// handler缺失
post /some/path/:id

@handler reqTest
post /foo/req (*Foo) // 不支持除普通结构体外的其他数据类型作为请求体

@handler replyTest
post /foo/reply returns (*Foo) // 不支持除普通结构体、数组(向前兼容,后续考虑废弃)外的其他数据类型作为响应体
}

隐藏通道

隐藏通道目前主要为空白符号、换行符号以及注释,这里我们只说注释,因为空白符号和换行符号我们目前拿来也无用。

单行注释

语法定义

'//' ~[\r\n]*

语法说明 由语法定义可知道,单行注释必须要以//开头,内容为不能包含换行符

正确语法示例

// doc
// comment

错误语法示例

// break
line comments

java风格文档注释

语法定义

'/*' .*? '*/'

语法说明

由语法定义可知道,单行注释必须要以/*开头,*/结尾的任意字符。

正确语法示例

/**
* java-style doc
*/

错误语法示例

/*
* java-style doc */
*/

Doc&Comment

如果想获取某一个元素的doc或者comment开发人员需要怎么定义?

Doc

我们规定上一个语法块(非隐藏通道内容)的行数line+1到当前语法块第一个元素前的所有注释(单行,或者多行)均为doc, 且保留了///**/原始标记。

Comment

我们规定当前语法块最后一个元素所在行开始的一个注释块(当行,或者多行)为comment 且保留了///**/原始标记。 语法块Doc和Comment的支持情况

语法块parent语法块DocComment
syntaxLitapi
kvLitinfoSpec
importLitimportSpec
typeLitapi
typeLittypeBlock
fieldtypeLit
key-valueatServer
atHandlerserviceRoute
routeserviceRoute
syntaxLitapi
syntaxLitapi

以下为对应语法块解析后细带doc和comment的写法

// syntaxLit doc
syntax = "v1" // syntaxLit commnet

info(
// kvLit doc
author: songmeizi // kvLit comment
)

// typeLit doc
type Foo {}

type(
// typeLit doc
Bar{}

FooBar{
// filed doc
Name int // filed comment
}
)

@server(
/**
* kvLit doc
* 开启jwt鉴权
*/
jwt: Auth /**kvLit comment*/
)
service foo-api{
// atHandler doc
@handler foo //atHandler comment

/*
* route doc
* post请求
* path为 /foo
* 请求体:Foo
* 响应体:Foo
*/
post /foo (Foo) returns (Foo) // route comment
}