goaのDSLのAttributeについて

スニペットづくりのためにコード読んでいて気づいたのでメモ。 v1の方。

Attributeとは

AttributeはView, Type, Attribute, Attributesの中で使える関数で、属性を指定するのに使用する。nested attributeってのがあるらしく、Attributeの中でAttributeが使えるらしいが、よくわからない。

Attributeの引数

ドキュメントによると、Attributeの定義は以下のようになっている。
https://goa.design/reference/goa/design/apidsl/#func-attribute-a-name-apidsl-attribute-a

func Attribute(name string, args ...interface{})

1つ目がAttributeの名前になるstringで、2つ目はinterface{}の可変長引数になっている。

2つ目の可変長引数には何渡しても良いのかというとそうではなくて、実際に使える値のパターンもドキュメントに記載されている。(ちゃんと読んでなかった)

Attribute(name string, dataType DataType, description string, dsl func())

Attribute(name string, dataType DataType, description string)

Attribute(name string, dataType DataType, dsl func())

Attribute(name string, dataType DataType)

Attribute(name string, dsl func())  // dataType is String or Object (if DSL defines child attributes)

Attribute(name string)          // dataType is String

Attributeの仲間

DSLとしてAttributeのaliasになっているものがある。

  • Param
  • Member
  • Header

の3つ。

Param

https://goa.design/reference/goa/design/apidsl/#func-param-a-name-apidsl-param-a

func Param(name string, args ...interface{})

Param can be used in: Params
Param is an alias of Attribute.

とのこと。

Member

https://goa.design/reference/goa/design/apidsl/#func-member-a-name-apidsl-member-a

func Member(name string, args ...interface{})

Member can be used in: Payload
Member is an alias of Attribute.

とのこと。

Header

https://goa.design/reference/goa/design/apidsl/#func-header-a-name-apidsl-header-a

func Header(name string, args ...interface{})

Headerだけちょっと異なっている。

Header can be used in: Headers, APIKeySecurity, JWTSecurity

Header is an alias of Attribute for the most part.

Within an APIKeySecurity or JWTSecurity definition, Header defines that an implementation must check the given header to get the API Key. In this case, no args parameter is necessary.

APIKeySecurity, JWTSecurityと使う場合には、 args を指定してはいけないとのこと。

こういう利用のされ方になる。

JWTSecurity("jwt", func() {
    Header("Authorization")
    TokenURL("<a href="https://example.com/token">https://example.com/token</a>")
    Scope("my_system:write", "Write to the system")
    Scope("my_system:read", "Read anything in there")
})

コードを確認

ParamとMember

// Member is an alias of Attribute.
func Member(name string, args ...interface{}) {
    Attribute(name, args...)
}

// Param is an alias of Attribute.
func Param(name string, args ...interface{}) {
    Attribute(name, args...)
}

ParamとMemberは内部でAttributeを呼んでいるだけ。

Header

func Header(name string, args ...interface{}) {
    if _, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
        if len(args) != 0 {
            dslengine.ReportError("do not specify args")
            return
        }
        inHeader(name)
        return
    }

    Attribute(name, args...)
}

Headerは処理中のデザイン定義を表す dslengine.CurrentDefinition が、 *design.SecuritySchemeDefinition (APIKeySecurity or JWTSecurity) だった場合には args の長さをチェックして、0だったらinHeader を呼び出して終了する。

inHeader はどのヘッダーにAPIキーが指定されているかを設定する処理。

*design.SecuritySchemeDefinition ではなければ、そのまま Attribute を呼び出す。

感想

まとめは特にないので、感想書いとく。

汎用的な関数を作るときの名前って大事だけど、今回のような、Attribute、Param、Memberの総称とか簡単に思いつかない。aliasという形で別名にして提供するってのは良いやり方だと思った。

ドキュメントもちゃんと読むだけじゃなくて、コードも合わせて確認すると理解度が高まる。調べようと思っていたこと以外にも、参考になることがある。

ところで、スニペットの進捗はあまり進んでない。来週とかかな…