このパッケージは構造体のタグを使って外部データとのやりとりをするライブラリを作成するためのヘルパーライブラリです。
リフレクションやGoのコード解析、型のマッピングの処理をカプセル化します。
主に3つの機能があります。
- 構造体のデータを外部に書き出す(
runtimescan.Encode()
) - 外部のデータを構造体の書き込む(
runtimescan.Decode()
) - 構造体を元にコード生成を行う(
staticscan.Scan()
)
runtimescan
パッケージは、実行時に動的に構造体をパースして処理します。
staticscan
パッケージは、静的解析・コードジェネレータ用です。
用語としては、構造体に書き込む方をデコード、構造体からの読み出しをエンコードと呼んでいます(encoding/json
と同じ)。
デコードではDecoder
インタフェースを、エンコードではEncoder
インタフェースを実装します。
インタフェースのインスタンスをそれぞれ、runtimescan.Decode()
、runtimescan.Encode()
関数に渡します。
エンコード、デコードの両方で、まずは構造体のフィールドを解析し、上記のインタフェースのParseTag()
を呼び出します。
この中でタグの値を分析したりします。このメソッドの返り値は次の処理で利用されます。
デコード処理ではExtractValue()
が呼ばれます。呼ばれるときにはParseTag()
の返したタグの分析情報のインスタンスが引数として渡されます。
このメソッドが返した値が、構造体にセットされます。
エンコード処理ではVisitField()
が呼ばれます。こちらも呼ばれるときにはParseTag()
の返したタグの分析情報のインスタンスが引数として渡されます。
それ以外にフィールドの値も引数として渡されます。
まずはEncoder
インタフェースを満たす構造体を作ります。出力先を構造体のフィールドに設定しておきます。
encoding/json
の基本のように、タグにはフィールド名のみを入れたい場合はruntimescan.BasicParseTag()
というヘルパー関数もあります。
VisitField()
に渡されてくる値を出力先に設定していけば実装完了です。
type encoder struct {
dest map[string]any
}
func (m encoder) ParseTag(name, tagStr, pathStr string, elemType reflect.Type) (tag any, err error) {
return runtimescan.BasicParseTag(name, tagStr, pathStr, elemType)
}
func (m *encoder) VisitField(tag, value any) (err error) {
t := tag.(*runtimescan.BasicTag)
m.dest[t.Tag] = value
return nil
}
func (m encoder) EnterChild(tag any) (err error) {
return nil
}
func (m encoder) LeaveChild(tag any) (err error) {
return nil
}
最後に、ユーザー向けのAPIの関数を作ります。
func Encode(dest map[string]any, src any) error {
enc := &encoder{
dest: dest,
}
return runtimescan.Encode(src, "map", enc)
}
まずはDecoder
インタフェースを満たす構造体を作ります。入力元を構造体のフィールドに設定しておきます。
このExtractValue()
の返り値が最終的に構造体に書き込まれます。構造体に設定した入力用データから、タグの情報を元に取り出してきて返り値として返すことで、
あとはライブラリがフィールドに値を設定します。
type decoder struct {
src map[string]any
}
func (m decoder) ParseTag(name, tagStr, pathStr string, elemType reflect.Type) (tag any, err error) {
return runtimescan.BasicParseTag(name, tagStr, pathStr, elemType)
}
func (m *decoder) ExtractValue(tag any) (value any, err error) {
t := tag.(*runtimescan.BasicTag)
v, ok := m.src[t.Tag]
if !ok {
return nil, runtimescan.Skip
}
return v, nil
}
こちらも、最後にユーザー向けのAPIの関数を定義します。
func Decode(dest any, src map[string]any) error {
dec := &decoder{
src: src,
}
return runtimescan.Decode(dest, []string{"map"}, dec)
}
こちらはソースコードの構造体を静的解析します。
いくつか便利関数を提供しています。
-
runtimescan.BasicParseTag(name, tagStr, pathStr string, elemType reflect.Type)
ParseTag()
でencoding/json
のように出力先のキー名(省略時はフィールド名を小文字にしたもの) -
runtimescan.Str2PrimitiveValue(v str)
1やtrueなどの文字列表現からプリミティブを作成します。タグ中に文字列で書かれたプリミティブをデフォルト値などで使う場合に利用します。
-
runtimescan.IsPointerOfStruct(i any)
,runtimescan.IsPointerOfSliceOfStruct(i any)
,runtimescan.IsPointerOfSliceOfPointerOfStruct(i any)
any
に渡されたポインタ型が*struct
か*[]struct
か*[]*struct
かをそれぞれ判定します。Decode()
を実装する時の型チェックに使います。 -
runtimescan.NewStructInstance(i any)
引数で渡されたポインタ型を元に、インスタンスを生成して返します。引数は
*struct
か*[]struct
か*[]*struct
のいずれかを受け付け、*struct
を返します。
runtimescan.Encode()
をそれぞれのインスタンスごとに呼び、結果をmap
に入れてから比較することで構造体の比較が実現できます。
runtimescan.Encode()
をソースのインスタンスに対して呼び出し、map
に一時的に値を入れてからそれを元にruntimescan.Decode()
を呼び出すことで、構造体間でフィールドのコピーが行えます。
runtimescan.Decode
を使い、HTTPリクエストの内容を構造体にマップします。
次のような構造体を作成して、restmap.Decode()
にhttp.Request
とこの構造体のインスタンスをわたします。
body
のタグはリクエストのContent-Type
ヘッダーを見て、application/x-www-form-urlencoded
, multipart/form-data
, application/json
のいづれかであればパースしてデータを読み込みます。
ファイルのアップロードにも対応します。``rest:"path:param"`で、パスの一部から文字列を取り出しますが、今のところchi routerにのみ対応しています。
type Request struct {
Method string `rest:"method"`
Auth string `rest:"header:Authorization"`
TraceID string `rest:"cookie:trace-id"`
Title string `rest:"body:title-field"`
File multipart.File `rest:"body:file-field"`
Header *multipart.FileHeader `rest:"body:file-field"`
Ctx context.Context `rest:"context"`
}
runtimescan.Decode
を使い、バイナリをロードします。タグに記述されたルールに従い、バイト列を読み込みアサインします。
数値
でビット数かバイト数を指定します<< >>
で即値を指定します。文字列か数値が指定可能です。数値の場合は/
でビット数かバイト数を指定します
type Image struct {
Header string `bytes:"<<HEAD>>"`
Height int32 `bytes:"4"`
Width int32 `bytes:"4"`
ReadOnly bool `bits:"1"`
_ byte `bits:"3"`
ColorType byte `bits:"4"'`
CheckDigit byte `bits:"<<0x5/6>>"`
}
func main() {
var image Image
f, _ := os.Open("imagefile")
err := binarypatternmatch.Decode(&image, f)
}
ソースコードを静的スキャンして構造体情報を取り出します。タグを元にしたコード生成のための機能です。
staticscan.Scan(rootPath, tagName: string) ([]staticscan.Struct, error)
Apache2