api语法介绍

    api语法结构

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

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

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

    **[!WARNING] 被import的api必须要和main api的syntax版本一致。

    语法定义

    语法说明

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

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

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

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

    正确语法示例

    eg1:不规范写法

    1. syntax="v1"

    eg2:规范写法(推荐)

    1. syntax = "v2"

    错误语法示例

    eg1:

    1. syntax = "v0"

    eg2:

    1. syntax = v1

    eg3:

    1. syntax = "V1"

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

    语法定义

    1. 'import' {checkImportValue(p)}STRING
    2. |'import' '(' ({checkImportValue(p)}STRING)+ ')'

    语法说明

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

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

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

    正确语法示例

    eg:

    1. import "foo.api"
    2. import "foo/bar.api"
    3. import(
    4. "bar.api"
    5. "foo/bar/foo.api"
    6. )

    错误语法示例

    eg:

    1. import foo.api
    2. import "foo.txt"
    3. import (
    4. bar.api
    5. bar.api
    6. )

    info语法块

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

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

    语法定义

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

    语法说明

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

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

    正确语法示例

    eg1:不规范写法

    eg2:规范写法(推荐)

    1. info(
    2. foo: "foo value"
    3. bar: "bar value"
    4. desc: "long long long long long long text"
    5. )

    错误语法示例

    eg1:没有key-value内容

    1. info()

    eg2:不包含冒号

    1. info(
    2. foo value
    3. )

    eg3:key-value没有换行

    1. info(foo:"value")

    eg4:没有key

    1. info(
    2. : "value"
    3. )

    eg5:非法的key

    1. info(
    2. 12: "value"
    3. )

    eg6:移除旧版本多行语法

    1. info(
    2. foo: >
    3. some text
    4. <
    5. )

    在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关键字

    语法定义

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

    语法说明

    参考golang写法

    正确语法示例

    eg1:不规范写法

    1. type Foo struct{
    2. Id int `path:"id"` // ①
    3. Foo int `json:"foo"`
    4. }
    5. type Bar struct{
    6. // 非导出型字段
    7. bar int `form:"bar"`
    8. }
    9. type(
    10. // 非导出型结构体
    11. fooBar struct{
    12. FooBar int `json:"fooBar"`
    13. }

    eg2:规范写法(推荐)

    1. type Foo{
    2. Id int `path:"id"`
    3. Foo int `json:"foo"`
    4. }
    5. type Bar{
    6. Bar int `form:"bar"`
    7. }
    8. type(
    9. FooBar{
    10. }
    11. )

    错误语法示例

    eg

    1. type Gender int // 不支持
    2. // 非struct token
    3. type Foo structure{
    4. CreateTime time.Time // 不支持time.Time,且没有声明 tag
    5. }
    6. // golang关键字 var
    7. type var{}
    8. type Foo{
    9. // golang关键字 interface
    10. Foo interface // 没有声明 tag
    11. }
    12. type Foo{
    13. foo int
    14. // map key必须要golang内置数据类型,且没有声明 tag
    15. m map[Bar]string
    16. }

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

    • tag表

      绑定参数时,以下四个tag只能选择其中一个

    • tag修饰符

      常见参数校验描述

    service语法块

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

    **[!WARNING]️

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

    语法定义

    语法说明

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

    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时

    修饰route时

    正确语法示例

    eg1:不规范写法

    1. @server(
    2. jwt: Auth
    3. group: foo
    4. middleware: AuthMiddleware
    5. prefix /api
    6. )
    7. service foo-api{
    8. @doc(
    9. summary: foo
    10. )
    11. @server(
    12. handler: foo
    13. )
    14. // 非导出型body
    15. post /foo/:id (foo) returns (bar)
    16. @doc "bar"
    17. @handler bar
    18. post /bar returns ([]int)// 不推荐数组作为响应体
    19. @handler fooBar
    20. post /foo/bar (Foo) returns // 可以省略'returns'
    21. }

    eg2:规范写法(推荐)

    1. @server(
    2. jwt: Auth
    3. group: foo
    4. middleware: AuthMiddleware
    5. prefix: /api
    6. )
    7. service foo-api{
    8. @doc "foo"
    9. @handler foo
    10. post /foo/:id (Foo) returns (Bar)
    11. }
    12. service foo-api{
    13. @handler ping
    14. get /ping
    15. @doc "foo"
    16. @handler bar
    17. post /bar/:id (Foo)
    18. }

    错误语法示例

    1. @server(
    2. )
    3. // 不支持空的service语法块
    4. service foo-api{
    5. }
    6. service foo-api{
    7. @doc kkkk // 简版doc必须用英文双引号引起来
    8. @handler foo
    9. post /foo
    10. @handler foo // 重复的handler
    11. post /bar
    12. @handler fooBar
    13. post /bar // 重复的路由
    14. // @handler和@doc顺序错误
    15. @handler someHandler
    16. @doc "some doc"
    17. post /some/path
    18. // handler缺失
    19. post /some/path/:id
    20. @handler reqTest
    21. post /foo/req (*Foo) // 不支持除普通结构体外的其他数据类型作为请求体
    22. @handler replyTest
    23. post /foo/reply returns (*Foo) // 不支持除普通结构体、数组(向前兼容,后续考虑废弃)外的其他数据类型作为响应体
    24. }

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

    语法定义

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

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

    正确语法示例

    1. // doc
    2. // comment

    错误语法示例

    1. // break
    2. line comments

    语法定义

    1. '/*' .*? '*/'

    语法说明

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

    正确语法示例

    1. /**
    2. * java-style doc
    3. */

    错误语法示例

    1. /*
    2. * java-style doc */
    3. */

    Doc&Comment

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

    Doc

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

    Comment

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

    1. // syntaxLit doc
    2. syntax = "v1" // syntaxLit commnet
    3. info(
    4. // kvLit doc
    5. author: songmeizi // kvLit comment
    6. )
    7. // typeLit doc
    8. type Foo {}
    9. type(
    10. // typeLit doc
    11. Bar{}
    12. FooBar{
    13. // filed doc
    14. Name int // filed comment
    15. }
    16. )
    17. @server(
    18. /**
    19. * kvLit doc
    20. * 开启jwt鉴权
    21. */
    22. jwt: Auth /**kvLit comment*/
    23. )
    24. service foo-api{
    25. // atHandler doc
    26. @handler foo //atHandler comment
    27. /*
    28. * route doc
    29. * post请求
    30. * path为 /foo
    31. * 请求体:Foo
    32. * 响应体:Foo
    33. */
    34. post /foo (Foo) returns (Foo) // route comment
    35. }