diff --git a/lectures/08-reflect/display/display.go b/lectures/08-reflect/display/display.go new file mode 100644 index 0000000..ef49f1e --- /dev/null +++ b/lectures/08-reflect/display/display.go @@ -0,0 +1,92 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 333. + +// Package display provides a means to display structured data. +package display + +import ( + "fmt" + "reflect" + "strconv" +) + +//!+Display + +func Display(name string, x interface{}) { + fmt.Printf("Display %s (%T):\n", name, x) + display(name, reflect.ValueOf(x)) +} + +// OMIT + +// formatAtom formats a value without inspecting its internal structure. +// It is a copy of the the function in gopl.io/ch11/format. +func formatAtom(v reflect.Value) string { + switch v.Kind() { + case reflect.Invalid: + return "invalid" + case reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64: + return strconv.FormatInt(v.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(v.Uint(), 10) + // ...floating-point and complex cases omitted for brevity... + case reflect.Bool: + if v.Bool() { + return "true" + } + return "false" + case reflect.String: + return strconv.Quote(v.String()) + case reflect.Chan, reflect.Func, reflect.Ptr, + reflect.Slice, reflect.Map: + return v.Type().String() + " 0x" + + strconv.FormatUint(uint64(v.Pointer()), 16) + default: // reflect.Array, reflect.Struct, reflect.Interface + return v.Type().String() + " value" + } +} + +//!+display +func display(path string, v reflect.Value) { + switch v.Kind() { + case reflect.Invalid: + fmt.Printf("%s = invalid\n", path) + case reflect.Slice, reflect.Array: + for i := 0; i < v.Len(); i++ { + display(fmt.Sprintf("%s[%d]", path, i), v.Index(i)) + } + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name) + display(fieldPath, v.Field(i)) + } + case reflect.Map: + for _, key := range v.MapKeys() { + display(fmt.Sprintf("%s[%s]", path, + formatAtom(key)), v.MapIndex(key)) + } + + // OMIT + case reflect.Ptr: + if v.IsNil() { + fmt.Printf("%s = nil\n", path) + } else { + display(fmt.Sprintf("(*%s)", path), v.Elem()) + } + case reflect.Interface: + if v.IsNil() { + fmt.Printf("%s = nil\n", path) + } else { + fmt.Printf("%s.type = %s\n", path, v.Elem().Type()) + display(path+".value", v.Elem()) + } + default: // basic types, channels, funcs + fmt.Printf("%s = %s\n", path, formatAtom(v)) + } +} + +// OMIT diff --git a/lectures/08-reflect/display/display_test.go b/lectures/08-reflect/display/display_test.go new file mode 100644 index 0000000..bc847ce --- /dev/null +++ b/lectures/08-reflect/display/display_test.go @@ -0,0 +1,259 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package display + +import ( + "io" + "net" + "os" + "reflect" + "sync" + "testing" + + "gopl.io/ch7/eval" +) + +// NOTE: we can't use !+..!- comments to excerpt these tests +// into the book because it defeats the Example mechanism, +// which requires the // Output comment to be at the end +// of the function. + +func Example_expr() { + e, _ := eval.Parse("sqrt(A / pi)") + Display("e", e) + // Output: + // Display e (eval.call): + // e.fn = "sqrt" + // e.args[0].type = eval.binary + // e.args[0].value.op = 47 + // e.args[0].value.x.type = eval.Var + // e.args[0].value.x.value = "A" + // e.args[0].value.y.type = eval.Var + // e.args[0].value.y.value = "pi" +} + +func Example_slice() { + Display("slice", []*int{new(int), nil}) + // Output: + // Display slice ([]*int): + // (*slice[0]) = 0 + // slice[1] = nil +} + +func Example_nilInterface() { + var w io.Writer + Display("w", w) + // Output: + // Display w (): + // w = invalid +} + +func Example_ptrToInterface() { + var w io.Writer + Display("&w", &w) + // Output: + // Display &w (*io.Writer): + // (*&w) = nil +} + +func Example_struct() { + Display("x", struct{ x interface{} }{3}) + // Output: + // Display x (struct { x interface {} }): + // x.x.type = int + // x.x.value = 3 +} + +func Example_interface() { + var i interface{} = 3 + Display("i", i) + // Output: + // Display i (int): + // i = 3 +} + +func Example_ptrToInterface2() { + var i interface{} = 3 + Display("&i", &i) + // Output: + // Display &i (*interface {}): + // (*&i).type = int + // (*&i).value = 3 +} + +func Example_array() { + Display("x", [1]interface{}{3}) + // Output: + // Display x ([1]interface {}): + // x[0].type = int + // x[0].value = 3 +} + +func Example_movie() { + //!+movie + type Movie struct { + Title, Subtitle string + Year int + Color bool + Actor map[string]string + Oscars []string + Sequel *string + } + //!-movie + //!+strangelove + strangelove := Movie{ + Title: "Dr. Strangelove", + Subtitle: "How I Learned to Stop Worrying and Love the Bomb", + Year: 1964, + Color: false, + Actor: map[string]string{ + "Dr. Strangelove": "Peter Sellers", + "Grp. Capt. Lionel Mandrake": "Peter Sellers", + "Pres. Merkin Muffley": "Peter Sellers", + "Gen. Buck Turgidson": "George C. Scott", + "Brig. Gen. Jack D. Ripper": "Sterling Hayden", + `Maj. T.J. "King" Kong`: "Slim Pickens", + }, + + Oscars: []string{ + "Best Actor (Nomin.)", + "Best Adapted Screenplay (Nomin.)", + "Best Director (Nomin.)", + "Best Picture (Nomin.)", + }, + } + //!-strangelove + Display("strangelove", strangelove) + + // We don't use an Output: comment since displaying + // a map is nondeterministic. + /* + //!+output + Display strangelove (display.Movie): + strangelove.Title = "Dr. Strangelove" + strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb" + strangelove.Year = 1964 + strangelove.Color = false + strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott" + strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden" + strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens" + strangelove.Actor["Dr. Strangelove"] = "Peter Sellers" + strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers" + strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers" + strangelove.Oscars[0] = "Best Actor (Nomin.)" + strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)" + strangelove.Oscars[2] = "Best Director (Nomin.)" + strangelove.Oscars[3] = "Best Picture (Nomin.)" + strangelove.Sequel = nil + //!-output + */ +} + +// This test ensures that the program terminates without crashing. +func Test(t *testing.T) { + // Some other values (YMMV) + Display("os.Stderr", os.Stderr) + // Output: + // Display os.Stderr (*os.File): + // (*(*os.Stderr).file).fd = 2 + // (*(*os.Stderr).file).name = "/dev/stderr" + // (*(*os.Stderr).file).nepipe = 0 + + var w io.Writer = os.Stderr + Display("&w", &w) + // Output: + // Display &w (*io.Writer): + // (*&w).type = *os.File + // (*(*(*&w).value).file).fd = 2 + // (*(*(*&w).value).file).name = "/dev/stderr" + // (*(*(*&w).value).file).nepipe = 0 + + var locker sync.Locker = new(sync.Mutex) + Display("(&locker)", &locker) + // Output: + // Display (&locker) (*sync.Locker): + // (*(&locker)).type = *sync.Mutex + // (*(*(&locker)).value).state = 0 + // (*(*(&locker)).value).sema = 0 + + Display("locker", locker) + // Output: + // Display locker (*sync.Mutex): + // (*locker).state = 0 + // (*locker).sema = 0 + // (*(&locker)) = nil + + locker = nil + Display("(&locker)", &locker) + // Output: + // Display (&locker) (*sync.Locker): + // (*(&locker)) = nil + + ips, _ := net.LookupHost("golang.org") + Display("ips", ips) + // Output: + // Display ips ([]string): + // ips[0] = "173.194.68.141" + // ips[1] = "2607:f8b0:400d:c06::8d" + + // Even metarecursion! (YMMV) + Display("rV", reflect.ValueOf(os.Stderr)) + // Output: + // Display rV (reflect.Value): + // (*rV.typ).size = 8 + // (*rV.typ).ptrdata = 8 + // (*rV.typ).hash = 871609668 + // (*rV.typ)._ = 0 + // ... + + // a pointer that points to itself + type P *P + var p P + p = &p + if false { + Display("p", p) + // Output: + // Display p (display.P): + // ...stuck, no output... + } + + // a map that contains itself + type M map[string]M + m := make(M) + m[""] = m + if false { + Display("m", m) + // Output: + // Display m (display.M): + // ...stuck, no output... + } + + // a slice that contains itself + type S []S + s := make(S, 1) + s[0] = s + if false { + Display("s", s) + // Output: + // Display s (display.S): + // ...stuck, no output... + } + + // a linked list that eats its own tail + type Cycle struct { + Value int + Tail *Cycle + } + var c Cycle + c = Cycle{42, &c} + if false { + Display("c", c) + // Output: + // Display c (display.Cycle): + // c.Value = 42 + // (*c.Tail).Value = 42 + // (*(*c.Tail).Tail).Value = 42 + // ...ad infinitum... + } +} diff --git a/lectures/08-reflect/format/format.go b/lectures/08-reflect/format/format.go new file mode 100644 index 0000000..a82c0b0 --- /dev/null +++ b/lectures/08-reflect/format/format.go @@ -0,0 +1,41 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 332. + +// Package format provides an Any function that can format any value. +//!+ +package format + +import ( + "reflect" + "strconv" +) + +// Any formats any value as a string. +func Any(value interface{}) string { + return formatAtom(reflect.ValueOf(value)) +} + +// formatAtom formats a value without inspecting its internal structure. +func formatAtom(v reflect.Value) string { + switch v.Kind() { + case reflect.Invalid: + return "invalid" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(v.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(v.Uint(), 10) + // ...floating-point and complex cases omitted for brevity... + case reflect.Bool: + return strconv.FormatBool(v.Bool()) + case reflect.String: + return strconv.Quote(v.String()) + case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map: + return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16) + default: // reflect.Array, reflect.Struct, reflect.Interface + return v.Type().String() + " value" + } +} + +// OMIT diff --git a/lectures/08-reflect/format/format_test.go b/lectures/08-reflect/format/format_test.go new file mode 100644 index 0000000..77c0846 --- /dev/null +++ b/lectures/08-reflect/format/format_test.go @@ -0,0 +1,24 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package format_test + +import ( + "fmt" + "testing" + "time" + + "gopl.io/ch12/format" +) + +func Test(t *testing.T) { + // The pointer values are just examples, and may vary from run to run. + //!+time + var x int64 = 1 + var d time.Duration = 1 * time.Nanosecond + fmt.Println(format.Any(x)) // "1" + fmt.Println(format.Any(d)) // "1" + fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0" + fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0" + //!-time +} diff --git a/lectures/08-reflect/lecture.slide b/lectures/08-reflect/lecture.slide new file mode 100644 index 0000000..2151957 --- /dev/null +++ b/lectures/08-reflect/lecture.slide @@ -0,0 +1,276 @@ +reflect +Лекция 7 + +Короткий Фёдор + +* Reflection + +- Механизм для чтения и изменения значений, не зная их реальный тип момент компиляции. +- Пример использования `reflect`: `fmt`, `encoding/json`. +- Эти пакеты используют `reflect` в реализации, но *не*светят* reflect в интерфейсе. + +* Why Reflection? + +Иногда нам нужно API, которое работает со значениями, которые не +объединены общим интерфейсом. + + func Sprint(x interface{}) string { + type stringer interface { + String() string + } + + switch x := x.(type) { + case stringer: + return x.String() + case string: + return x + case int: + return strconv.Itoa(x) + // ...similar cases for int16, uint32, and so on... + default: + // array, chan, func, map, pointer, slice, struct + return "???" + } + } + +* reflect.Type and reflect.Value + +- Пакет `reflect` определяет два основных типа: `reflect.Type` и `reflect.Value`. +- Единственная реализация `reflect.Type` - _type_descriptor_. + + func TypeOf(interface{}) Type + +- Пример: + + t := reflect.TypeOf(3) // a reflect.Type + fmt.Println(t.String()) // "int" + fmt.Println(t) // "int" + +- `reflect.TypeOf` всегда возвращает конкретный тип. + + var w io.Writer = os.Stdout + fmt.Println(reflect.TypeOf(w)) // "*os.File" + +- `Sprintf` формат `%T` использует `TypeOf` внутри. + + fmt.Printf("%T\n", 3) // "int" + +* reflect.Type and reflect.Value + +- `reflect.Value` хранит значение любого типа. + + v := reflect.ValueOf(3) + fmt.Println(v) // "3" + fmt.Printf("%v\n", v) // "3" + fmt.Println(v.String()) // NOTE: "" + +- Метод `.Type()` возвращает тип значения, хранящегося внутри `Value`. + + t := v.Type() // a reflect.Type + fmt.Println(t.String()) // "int" + +- Метод `.Interface()` - обратная операция к `reflect.ValueOf`. + + v := reflect.ValueOf(3) // a reflect.Value + x := v.Interface() // an interface{} + i := x.(int) // an int + fmt.Printf("%d\n", i) // "3" + +* Разница между reflect.Value и interface{} + +- Оба хранят любое значение. +- `interface{}` - хранит значение, но не даёт доступа к нему. Можно достать конкретный тип, через _type_assertion_. +- `reflect.Value` - предоставляет доступ к значению внутри, вне зависимости от типа этого значения. + +* format.Any + +.code format/format.go /func Any/,/OMIT/ + +* format.Any + + var x int64 = 1 + var d time.Duration = 1 * time.Nanosecond + fmt.Println(format.Any(x)) // "1" + fmt.Println(format.Any(d)) // "1" + fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0" + fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0" + +* Debug Display + + type Movie struct { + Title, Subtitle string + Year int + Color bool + Actor map[string]string + Oscars []string + Sequel *string + } + + strangelove := Movie{ + Title: "Dr. Strangelove", + Subtitle: "How I Learned to Stop Worrying and Love the Bomb", + Year: 1964, + Color: false, + Actor: map[string]string{ + "Dr. Strangelove": "Peter Sellers", + "Grp. Capt. Lionel Mandrake": "Peter Sellers", + "Pres. Merkin Muffley": "Peter Sellers", + "Gen. Buck Turgidson": "George C. Scott", + // ... + }, + // ... + } + +* Debug Display + + Display("strangelove", strangelove) + +Вывод: + + Display strangelove (display.Movie): + strangelove.Title = "Dr. Strangelove" + strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb" + strangelove.Year = 1964 + strangelove.Color = false + strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott" + strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden" + strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens" + strangelove.Actor["Dr. Strangelove"] = "Peter Sellers" + strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers" + strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers" + strangelove.Oscars[0] = "Best Actor (Nomin.)" + strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)" + strangelove.Oscars[2] = "Best Director (Nomin.)" + strangelove.Oscars[3] = "Best Picture (Nomin.)" + strangelove.Sequel = nil + +* Debug Display + + Display("os.Stderr", os.Stderr) + // Output: + // Display os.Stderr (*os.File): + // (*(*os.Stderr).file).fd = 2 + // (*(*os.Stderr).file).name = "/dev/stderr" + // (*(*os.Stderr).file).nepipe = 0 + +* Debug Display + +.code display/display.go /func Display/,/OMIT/ + +* Debug Display + +.code display/display.go /func display/,/OMIT/ + +* Debug Display + +.code display/display.go /case reflect.Ptr/,/OMIT/ + +* Setting Variables + +- Некоторые выражения являются переменными: `x`, `x.f[1]`, `*p`. +- Некоторые выражения не являются переменными: `x`+`1`, `f(2)`. +- Переменные имеют _адрес_. Значение переменной можно изменить. + + x := 2 // value type variable? + a := reflect.ValueOf(2) // 2 int no + b := reflect.ValueOf(x) // 2 int no + c := reflect.ValueOf(&x) // &x *int no + d := c.Elem() // 2 int yes + +- `CanAddr` + + fmt.Println(a.CanAddr()) // "false" + fmt.Println(b.CanAddr()) // "false" + fmt.Println(c.CanAddr()) // "false" + fmt.Println(d.CanAddr()) // "true" + +* Setting Variables + + x := 2 + d := reflect.ValueOf(&x).Elem() // d refers to the variable x + px := d.Addr().Interface().(*int) // px := &x + *px = 3 // x = 3 + fmt.Println(x) // "3" + +- Или напрямую + + d.Set(reflect.ValueOf(4)) + fmt.Println(x) // "4" + +- Или через специальный метод + + d := reflect.ValueOf(&x).Elem() + d.SetInt(3) + fmt.Println(x) // "3" + +- Нельзя менять _не_адресуемое_ значение. + + x := 2 + b := reflect.ValueOf(x) + b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value + +* Setting Variables + +Приватные поля можно читать, но нельзя менять. + + stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var + fmt.Println(stdout.Type()) // "os.File" + fd := stdout.FieldByName("fd") + fmt.Println(fd.Int()) // "1" + fd.SetInt(2) // panic: unexported field + + fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false" + +* Struct Tags + +.code search/main.go /func search/,/^}/ + +* Struct Tags + +.code params/params.go /func Unpack/,/OMIT/ + +.code params/params.go /Build map of/,/OMIT/ + +* Struct Tags + +.code params/params.go /Update/,/^}/ + +* Struct Tags + +.code params/params.go /func populate/,/^}/ + +* Method Set + +.code methods/methods.go /func Print/,/^}/ + + methods.Print(time.Hour) + // Output: + // type time.Duration + // func (time.Duration) Hours() float64 + // func (time.Duration) Minutes() float64 + // func (time.Duration) Nanoseconds() int64 + // func (time.Duration) Seconds() float64 + // func (time.Duration) String() string + +* Interface + +- Результат `ValueOf` - всегда конкретный тип. + + var w io.Writer + t := reflect.TypeOf(w) // t == ??? + + var v interface{} = w // v == nil + t = reflect.TypeOf(v) // t == ??? + +- Чтобы получить `reflect.Type` равный интерфейсу, нужно использовать промежуточный указатель. + + var w io.Writer + ptrT := reflect.TypeOf(&w) // ptrT == *io.Writer + t := ptrT.Elem() // t == io.Writer + +* Заключение + +- `reflect` создаёт хрупкий код. Там, где была бы ошибка компилятора, возникает `panic` во время исполнения. +- Поведение `reflect` кода нужно документировать отдельно, и оно не ясно из типов аргументов. +- `reflect` работает медленнее, чем специализация под конкретный тип. + diff --git a/lectures/08-reflect/methods/methods.go b/lectures/08-reflect/methods/methods.go new file mode 100644 index 0000000..3b6b560 --- /dev/null +++ b/lectures/08-reflect/methods/methods.go @@ -0,0 +1,29 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 351. + +// Package methods provides a function to print the methods of any value. +package methods + +import ( + "fmt" + "reflect" + "strings" +) + +//!+print +// Print prints the method set of the value x. +func Print(x interface{}) { + v := reflect.ValueOf(x) + t := v.Type() + fmt.Printf("type %s\n", t) + + for i := 0; i < v.NumMethod(); i++ { + methType := v.Method(i).Type() + fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name, + strings.TrimPrefix(methType.String(), "func")) + } +} + +//!-print diff --git a/lectures/08-reflect/methods/methods_test.go b/lectures/08-reflect/methods/methods_test.go new file mode 100644 index 0000000..0b509fe --- /dev/null +++ b/lectures/08-reflect/methods/methods_test.go @@ -0,0 +1,49 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +package methods_test + +import ( + "strings" + "time" + + "gopl.io/ch12/methods" +) + +func ExamplePrintDuration() { + methods.Print(time.Hour) + // Output: + // type time.Duration + // func (time.Duration) Hours() float64 + // func (time.Duration) Minutes() float64 + // func (time.Duration) Nanoseconds() int64 + // func (time.Duration) Seconds() float64 + // func (time.Duration) String() string +} + +func ExamplePrintReplacer() { + methods.Print(new(strings.Replacer)) + // Output: + // type *strings.Replacer + // func (*strings.Replacer) Replace(string) string + // func (*strings.Replacer) WriteString(io.Writer, string) (int, error) +} + +/* +//!+output +methods.Print(time.Hour) +// Output: +// type time.Duration +// func (time.Duration) Hours() float64 +// func (time.Duration) Minutes() float64 +// func (time.Duration) Nanoseconds() int64 +// func (time.Duration) Seconds() float64 +// func (time.Duration) String() string + +methods.Print(new(strings.Replacer)) +// Output: +// type *strings.Replacer +// func (*strings.Replacer) Replace(string) string +// func (*strings.Replacer) WriteString(io.Writer, string) (int, error) +//!-output +*/ diff --git a/lectures/08-reflect/params/params.go b/lectures/08-reflect/params/params.go new file mode 100644 index 0000000..232beb1 --- /dev/null +++ b/lectures/08-reflect/params/params.go @@ -0,0 +1,94 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 349. + +// Package params provides a reflection-based parser for URL parameters. +package params + +import ( + "fmt" + "net/http" + "reflect" + "strconv" + "strings" +) + +//!+Unpack + +// Unpack populates the fields of the struct pointed to by ptr +// from the HTTP request parameters in req. +func Unpack(req *http.Request, ptr interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + + // OMIT + + // Build map of fields keyed by effective name. + fields := make(map[string]reflect.Value) + v := reflect.ValueOf(ptr).Elem() // the struct variable + for i := 0; i < v.NumField(); i++ { + fieldInfo := v.Type().Field(i) // a reflect.StructField + tag := fieldInfo.Tag // a reflect.StructTag + name := tag.Get("http") + if name == "" { + name = strings.ToLower(fieldInfo.Name) + } + fields[name] = v.Field(i) + } + + // OMIT + + // Update struct field for each parameter in the request. + for name, values := range req.Form { + f := fields[name] + if !f.IsValid() { + continue // ignore unrecognized HTTP parameters + } + for _, value := range values { + if f.Kind() == reflect.Slice { + elem := reflect.New(f.Type().Elem()).Elem() + if err := populate(elem, value); err != nil { + return fmt.Errorf("%s: %v", name, err) + } + f.Set(reflect.Append(f, elem)) + } else { + if err := populate(f, value); err != nil { + return fmt.Errorf("%s: %v", name, err) + } + } + } + } + return nil +} + +//!-Unpack + +//!+populate +func populate(v reflect.Value, value string) error { + switch v.Kind() { + case reflect.String: + v.SetString(value) + + case reflect.Int: + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + v.SetInt(i) + + case reflect.Bool: + b, err := strconv.ParseBool(value) + if err != nil { + return err + } + v.SetBool(b) + + default: + return fmt.Errorf("unsupported kind %s", v.Type()) + } + return nil +} + +//!-populate diff --git a/lectures/08-reflect/search/main.go b/lectures/08-reflect/search/main.go new file mode 100644 index 0000000..9a94e82 --- /dev/null +++ b/lectures/08-reflect/search/main.go @@ -0,0 +1,60 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 348. + +// Search is a demo of the params.Unpack function. +package main + +import ( + "fmt" + "log" + "net/http" +) + +//!+ + +import "gopl.io/ch12/params" + +// search implements the /search URL endpoint. +func search(resp http.ResponseWriter, req *http.Request) { + var data struct { + Labels []string `http:"l"` + MaxResults int `http:"max"` + Exact bool `http:"x"` + } + data.MaxResults = 10 // set default + if err := params.Unpack(req, &data); err != nil { + http.Error(resp, err.Error(), http.StatusBadRequest) // 400 + return + } + + // ...rest of handler... + fmt.Fprintf(resp, "Search: %+v\n", data) +} + +//!- + +func main() { + http.HandleFunc("/search", search) + log.Fatal(http.ListenAndServe(":12345", nil)) +} + +/* +//!+output +$ go build gopl.io/ch12/search +$ ./search & +$ ./fetch 'http://localhost:12345/search' +Search: {Labels:[] MaxResults:10 Exact:false} +$ ./fetch 'http://localhost:12345/search?l=golang&l=programming' +Search: {Labels:[golang programming] MaxResults:10 Exact:false} +$ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100' +Search: {Labels:[golang programming] MaxResults:100 Exact:false} +$ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming' +Search: {Labels:[golang programming] MaxResults:10 Exact:true} +$ ./fetch 'http://localhost:12345/search?q=hello&x=123' +x: strconv.ParseBool: parsing "123": invalid syntax +$ ./fetch 'http://localhost:12345/search?q=hello&max=lots' +max: strconv.ParseInt: parsing "lots": invalid syntax +//!-output +*/