goaで指定するホスト名はRFC1035に準拠させること

goaのコード読んでて気づいたのでメモ。

DSLのHost

APIが想定しているホスト名を指定するためのDSL(func)で、以下のように実装されている。

// Regular expression used to validate RFC1035 hostnames*/
var hostnameRegex = regexp.MustCompile(`^[[:alnum:]][[:alnum:]\-]{0,61}[[:alnum:]]|[[:alpha:]]$`)

// Host sets the API hostname.
func Host(host string) {
    if !hostnameRegex.MatchString(host) {
        dslengine.ReportError(`invalid hostname value "%s"`, host)
        return
    }

    if a, ok := apiDefinition(); ok {
        a.Host = host
    }
}

入力されたホスト名に対して hostnameRegex正規表現にマッチするかのチェックを行っている。

RFC1035 とは

DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION ということで色々書いてある。

2.3.1. Preferred name syntax では以下のように言及されている。

They must start with a letter, end with a letter or digit,
and have as interior characters only letters, digits, and hyphen.
There are also some restrictions on the length.
Labels must be 63 characters or less.

この辺りの内容を正規表現で表しているっぽい。

git-configのConditional includesでユーザ情報を切り替える

会社のPCで、githubからcloneしてきたリポジトリには、個人のメールアドレスを使用するように設定するんだけど、たまに設定ミスってて会社のメールアドレスでコミットすることがある。別に弊害はないんだけど、あまりうれしくない。

git 2.13 から提供された機能にConditional includes というのがあって、条件に応じて設定ファイルを読み込むことができるようになっている。 この機能を使用して、コミット時のユーザー情報を切り替えるように設定した。

設定方法

gitdirがどこにあるか、で設定を変更できるらしい。

; include for all repositories inside $HOME/to/group
[includeIf "gitdir:~/to/group/"]
    path = /path/to/foo.inc

今はGOPATHを ~/projects に設定しているので、

会社のコード:~/projects/src/gitlab.syanai/projectA/repositoryA
個人のコード:~/projects/src/github.com/kawaken/repository

のような形でリポジトリが配置されている。

~/projects/src/github.com 配下のリポジトリはすべて個人のメールアドレスを使用するように設定する。

$HOME/.gitconfig

[user]
    name = Kentaro Kawano
    email = kaisyano@mail.dayo
[includeIf "gitdir:~/projects/src/github.com/"]
    path = .gitconfig.github

$HOME/.gitconfig.github

[user]
    email = kojinno@mailaddress.desu

これでうまくできた。

VS CodeのMarketplaceでコードスニペットを公開した

以前作ってgistに置いていたものをマーケットプレイスに公開した。

goa snippets - Visual Studio Marketplace

VSCodeのサイドメニューから、goaで検索したらちゃんと出てくるから!

こういうことができる。

https://gist.githubusercontent.com/kawaken/f33592f483e21f3e38d66b63b6fb76ec/raw/6c9d9ed7f276c54c70972ca51494c212bfa0981e/goa-snippets.gif

今対応してるのは、以下のDSL

  • API
  • APIKeySecurity
  • BasicAuthSecurity
  • Resource
  • Action
  • MediaType
  • Routing
  • Attribute
  • Param
  • Member
  • Header

少ないんだけど、揃うまで待つといつまでも公開できそうになかったので、とりあえず公開して補充していく作戦。

平日の昼休みにでも1日1funcって感じて追加していくと思われる。 ちなみに、 APIKeySecurity, BasicAuthSecurity はさっき追加した。

umeda.go #2 で発表してきた

書くのが遅くなったけど、7/7に大阪のGoの勉強会に参加&発表してきた。

www.slideshare.net

発表内容の経緯

時刻のテストでどこまでできるか、どういう選択肢があるのかというところを過去に調べていたので、それを今回整理し直して発表した。

Goの修正について

自分も正直どうかなと思ってるので、決してオススメはしてないんだけど、一度やってみたかったんだよねってところが大きい。

時刻の取得と処理の責務を明確に分けることができれば、それが一番良いのではと思う。

懇親会

すごくいい話を聞いた気がするんだけど、忘れてしまった。

  • 標準パッケージ勉強になる
  • Goのコードが上手くハマらない時は設計に問題があるのでは?
  • goa良いよgoa

みたいな話をした気がする。

Go言語を修正してソースからビルドする

Go自体に手を入れたいということがあって、ソースからビルドしてみた。

Goのソースコードを入手

公式のサイトから手に入る。開発中のものが欲しい場合にはgithubから取得する。

$ cd /tmp
$ wget https://storage.googleapis.com/golang/go1.8.3.src.tar.gz
$ tar xf go1.8.3.src.tar.gz
$ cd go

GOROOT_BOOTSTRAP を設定する

Goをビルドするために必要なGoを指定する環境変数。デフォルトでは $HOME/go1.4 になっている。必ずしも1.4が必要なわけではなく、1.4以上のバージョンであれば良い

$ which go
/usr/local/go/bin/go
$ go version
go version go1.8 darwin/amd64
$ export GOROOT_BOOTSTRAP=/usr/local/go

ビルド

src に移動して all.bash を実行する。 all.bashsrc に移動しないと実行できない。完了までには数分かかる。

$ cd src
$ ./all.bash

完了すると bin/go が出来ている。

$ cd ../bin
$ ./go version
go version go1.8.3 darwin/amd64

独自の修正を加えた場合

修正した内容にexportedなものがあれば、必ずapi配下の goX.Y.txt (X.Yはバージョン番号)を修正する。

例えば、fmtパッケージに

func PrintTwice(args ...interface{}) {
    Print(args...)
    Print(args...)
}

という変更を加えた場合、go1.8.txtに

pkg fmt, PrintTwice(...interface{})

というのを追記する。

ビルドの最後の方に、apiの変更をチェックするタスクがあって、そこで整合性の確認が行われるため、この記載がないとエラーになってしまう。

all.bash を使わない方法がありそうだけど中身は見てない。

まとめ

  • Goをソースからビルドするのは難しくない
  • 必要なバージョンは1.4以上で、1.4必須ではない
  • exportされた修正がある場合は、goX.Y.txtも修正する

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という形で別名にして提供するってのは良いやり方だと思った。

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

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

VSCode用のgoaのコードスニペットを作ってみた

昨日行われたgoa勉強会 in 六本木一丁目 - connpassに参加するついでに、 以前作っていたVSCode用のgoaのコードスニペットの紹介を飛び込みLTで行ってきた。

資料

LTの資料はこちら。

goaのdesignをラクに書く

コードスニペットこちら。

vscode goa snippets

実際に動いているところをキャプチャしたやつ。

https://gist.githubusercontent.com/kawaken/f33592f483e21f3e38d66b63b6fb76ec/raw/6c9d9ed7f276c54c70972ca51494c212bfa0981e/goa-snippets.gif

飛び込みLTは初めてだったせいかめっちゃ手が震えてしまって、うまくタイピングできない感じだったけど、Twitterの方では反応もらえてたので良かった。

スニペットについて

スニペット自体は簡単に書けるので別に難しい作業ではないんだけど、同じDSLでも引数が異なるものもあるので、組み合わせるとけっこう膨大になると思う。 なので、必要最小限って感じで上手くまとめようと思って書き始めたものの、手が止まっていた。

例えば、Routingは揃ってない。昨日LTでいるなぁと思ったのでGETだけ追加した。

誰かやってくれたらそれでも良いんだけど、昨日LTした反応が素直に嬉しかったし、VSCode自体はすごく気に入っているので、 マーケットプレイスに上げるところも視野にがんばってみようかなとか思った。