🌊

gorilla/schemaにデフォルトタグが追加されたので書き留める

2024/03/30に公開

https://github.com/gorilla/schema/releases/tag/v1.3.0
gorilla/schemaにv.1.3.0でデフォルトタグが設定できるようになった。
自分が使い出した頃には実装はされていたが、リリースはされておらず悶々としていた。
とりあえず何かと今後も使う気がするので、自分宛にメモすることにする。

gorilla/schemaとは

Package gorilla/schema converts structs to and from form values.

リクエストのPOSTの値やパスパラメータを解析し、構造体にデコードしてくれるパッケージ

https://github.com/gorilla/schema?tab=readme-ov-file#setting-defaults

パスパラメータのデフォルト値を設定できるようになった

具体的に実装の部分を見ていく
https://github.com/gorilla/schema/blob/main/decoder.go#L98-L158

ざっくり処理の流れ

107行目から見ていく。
構造体のFieldをループし、
110行目からフィールドの種類が構造体でデフォルト値が設定されていない場合
その構造体を再帰的にsetDefaultsメソッドを実行します。
ポインター型の場合isPointerToStructメソッドでゼロ値ではなく、ポインター型で構造体でかつデフォルト値が設定されていない場合再帰的にsetDefaultsメソッドを実行します。


116行目でデフォルト値が設定されていて、Requiredが設定されている場合エラーを返しています。


118.119でデフォルト値が設定されていてフィールドの値がゼロでRequiredがついていない場合中の処理が走る
119で構造体の場合エラーを返しています。


121-137でスライス型の場合|で値を区切り、map型のbuildinConvertersで型がサポートされているかチェックする。
チェック後に|で区切ったvalsをループし、各valbuiltinConvertersからフィールドのタイプをキにメソッドを取り出し、valを引数に型を検証しています。
現状の実装では要素の型がフィールドの型と違った場合はそのデフォルト値を無視するようになっているそうです。
https://github.com/gorilla/schema/issues/210#issuecomment-2027456715


138-148
フィールド型ポインターで構造体かスライスの場合エラーを返す
値をコンバートしてコンバートに成功すればデフォルト値をセット


それ以外のケースはbuiltinConverterで値を検証し、成功すれば値をセット

具体例

クエリパラメータをデコードし、Nameフィールドを返してます。


var decoder = schema.NewDecoder()

type Person struct {
	Name string `schema:"name,default:test1"`
    Names  []string `schema:"names,default:test1|test2"``
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		var d = Person{}
		if err := decoder.Decode(&d, r.URL.Query()); err != nil {
			log.Fatal("Error while decoding:", err)
		}
		fmt.Fprintf(w, d.Name)
	})
	http.ListenAndServe(":8080", nil)
}

カスタムスキーマを作ってみる。
Validateメソッドが引数のdstに定義されていれば実行します。
カスタムで何かしらの処理を追加したいときに使う。


func main() {
	var decoder = schema.NewDecoder()
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		var d = Person{}
		if err := decode(decoder, &d, r.URL.Query()); err != nil {
			log.Fatal("Error while decoding:", err)
		}
		fmt.Fprintf(w, d.Name, d.Num)
	})
	http.ListenAndServe(":8080", nil)
}

func decode(decoder *schema.Decoder, dst interface{}, src map[string][]string) error {
	if err := decoder.Decode(&dst, src); err != nil {
		log.Fatal("Error while decoding:", err)
	}
	d, ok := dst.(interface {
		Validate() error
	})
	if !ok {
		return nil
	}
	return d.Validate()
}

Discussion