Open23

Goのあれこれ

きょんきょん

type switcher

var x interface{} = "foo"

switch v := x.(type) {
case nil:
    fmt.Println("x is nil")            // here v has type interface{}
case int: 
    fmt.Println("x is", v)             // here v has type int
case bool, string:
    fmt.Println("x is bool or string") // here v has type interface{}
default:
    fmt.Println("type unknown")        // here v has type interface{}
}
きょんきょん

file uploaderのテスト例

    filePath := "file.jpg"
    fieldName := "file"
    body := new(bytes.Buffer)

    mw := multipart.NewWriter(body)

    file, err := os.Open(filePath)
    if err != nil {
        t.Fatal(err)
    }

    w, err := mw.CreateFormFile(fieldName, filePath)
    if err != nil {
        t.Fatal(err)
    }

    if _, err := io.Copy(w, file); err != nil {
        t.Fatal(err)
    }

    // close the writer before making the request
    mw.Close()

    req := httptest.NewRequest(http.MethodPost, "/upload", body)
    
    req.Header.Add("Content-Type", mw.FormDataContentType())

    res := httptest.NewRecorder()

    // router is of type http.Handler
    router.ServeHTTP(res, req)

https://stackoverflow.com/questions/43904974/testing-go-http-request-formfile

きょんきょん
  • bodyにstringで詰め合わせればいいんだ
const (
	fileaContents = "This is a test file."
	filebContents = "Another test file."
	textaValue    = "foo"
	textbValue    = "bar"
	boundary      = `MyBoundary`
)

const message = `
--MyBoundary
Content-Disposition: form-data; name="filea"; filename="filea.txt"
Content-Type: text/plain

` + fileaContents + `
--MyBoundary
Content-Disposition: form-data; name="fileb"; filename="fileb.txt"
Content-Type: text/plain

` + filebContents + `
--MyBoundary
Content-Disposition: form-data; name="texta"

` + textaValue + `
--MyBoundary
Content-Disposition: form-data; name="textb"

` + textbValue + `
--MyBoundary--
`

func newTestMultipartRequest(t *testing.T) *Request {
	b := strings.NewReader(strings.ReplaceAll(message, "\n", "\r\n"))
	req, err := NewRequest("POST", "/", b)
	if err != nil {
		t.Fatal("NewRequest:", err)
	}
	ctype := fmt.Sprintf(`multipart/form-data; boundary="%s"`, boundary)
	req.Header.Set("Content-type", ctype)
	return req
}

https://go.dev/src/net/http/request_test.go

きょんきょん

APIでNullを扱う

ケース

  • request.body設定がない場合string:nullが入る。
    • pointer型にするのが王道。
    • ただ、keyがoptionalな場合、keyが含まれない場合とnullで区別ができない(両方nilになる)
きょんきょん
package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gopkg.in/guregu/null.v3"
    "gopkg.in/guregu/null.v3/zero"
)

type (
    User struct {
        Name null.String `json:"name"`
        Age null.Int `json:"age"`
    }
)

func main() {
    db, err := sql.Open("mysql", "root:@/guregu")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM user")
    if err != nil {
        return
    }

    for rows.Next() {
        var user User
        rows.Scan(&user.Name, &user.Age)
        JSON, err := json.Marshal(user)
        if err != nil {
            continue
        }
        fmt.Println(string(JSON))
    }
}
{"name":"tarou","age":12}
{"name":null,"age":null}
{"name":"","age":0}
きょんきょん
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
)

type NullSegments struct {
    Segments Segments
    Valid    bool // Valid is true if Segments is not NULL
}

type Range struct {
    From int `json:"from"`
    To   int `json:"to"`
}

type Segments struct {
    Status int   `json:"status"`
    Range  Range `json:"range"`
}

type Settings struct {
    Segments NullSegments `json:"segments"`
}

var nullLiteral = []byte("null")

func (s *NullSegments) UnmarshalJSON(b []byte) error {
    if bytes.Equal(b, nullLiteral) {
        return nil
    }

    err := json.Unmarshal(b, &s.Segments)
    if err == nil {
        s.Valid = true
        return nil
    }

    return err
}

func (s NullSegments) MarshalJSON() ([]byte, error) {
    if s.Valid {
        return json.Marshal(s.Segments)
    } else {
        return nullLiteral, nil
    }
}

func main() {
    b := []byte(`{"segments": {"status": 1, "range": {"from": 10, "to": 20}}}`)
    // b := []byte(`{"segments": null }`)

    s := &Settings{}

    json.Unmarshal(b, s)

    bb, _ := json.Marshal(s)

    fmt.Println(string(bb))
}
きょんきょん

...引数(スプレット構文的使用法)

package main

import (
	"fmt"
)

func Greeting(prefix string, who ...string) {
	for _, w := range who {
		fmt.Printf("%s %s\n", prefix, w)
	}
}

func main() {
	Greeting("hello:", "Joe", "Anna", "Eileen")
	s := []string{"James", "Jasmine", "John", "Jules"}
	Greeting("good afternoon:", s...)
	Greeting("goodbye:", s[2:]...)
	Greeting("こんにちは", "私")
}
hello: Joe
hello: Anna
hello: Eileen
good afternoon: James
good afternoon: Jasmine
good afternoon: John
good afternoon: Jules
goodbye: John
goodbye: Jules
こんにちは 私

Program exited.

https://go.dev/play/p/UuFGV5uJIpA

きょんきょん

スプレット構文となっている第二引数は存在しなくても実行可能

package main

import (
	"fmt"
)

func Greeting(prefix string, who ...string) {
	for _, w := range who {
		fmt.Printf("%s %s\n", prefix, w)
	}
}

func main() {
	Greeting("こんにちは")
}
Program exited.
きょんきょん

Mapの要素数取得

package main

import "fmt"

func main() {
	m := map[string]int{"apple": 150, "banana": 300, "lemon": 300}

	fmt.Println(len(m))
}
3

Program exited.
きょんきょん

github.com/volatiletech/null/v8

  • nullでない場合、Validがtrueになる
package main

import (
	"fmt"

	"github.com/volatiletech/null/v8"
)

func main() {
	i := null.IntFrom(12)
	fmt.Printf("%+v", i)
}
{Int:12 Valid:true}
Program exited.

https://go.dev/play/p/c7bm8aL__i-

きょんきょん

go testを複数回実行する

makeで実行

.PHONY: go-test-carefully
go-test-c:
	for ((i=0; i<100; i++)); \
	do go test -count=1; \
	done
きょんきょん

mapの比較

go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var m1 map[int]int
	m2 := map[int]int{}
	m3 := make(map[int]int)

	fmt.Printf("%p\n%p\n%p\n", m1, m2, m3)

	if reflect.DeepEqual(m1, m2) {
		fmt.Println("m1 same m2")
	} else {
		fmt.Println("m1 different m2")
	}

	if reflect.DeepEqual(m1, m3) {
		fmt.Println("m1 same m3")
	} else {
		fmt.Println("m1 different m3")
	}

	if reflect.DeepEqual(m2, m3) {
		fmt.Println("m2 same m3")
	} else {
		fmt.Println("m2 different m3")
	}

	if m1 == nil {
		fmt.Println("m1 is nil!!!")
	}
	if m2 == nil {
		fmt.Println("m2 is nil!!!")
	}
	if m3 == nil {
		fmt.Println("m3 is nil!!!")
	}
}
0x0
0xc0000121b0
0xc0000121e0
m1 different m2
m1 different m3
m2 same m3
m1 is nil!!!

Program exited.
きょんきょん

html/template の#ZgotmplZについて

  • base64でencodeされた画像をParseしようとして発生
<img src={{ .EncodedImage }}>
import (
    "html/template"
    "os"
)

func main() {
    funcMap := template.FuncMap{
        "attr":func(s string) template.HTMLAttr{
            return template.HTMLAttr(s)
        },
        "safe": func(s string) template.HTML {
            return template.HTML(s)
         },
    }

    template.Must(template.New("Template").Funcs(funcMap).Parse(`
    <option {{  .attr |attr }} >test</option>
        {{.html|safe}}
     `)).Execute(os.Stdout,   map[string]string{"attr":`selected="selected"`,"html":`<option selected="selected">option</option>`})
}
きょんきょん

packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server

  • sqlxのExecで複数のsqlを一度に実行しようとすると発生するエラー
  • max_allowed_packetの設定値を改善しても発生する
  • 対応
    • sqlをそれぞれ分離して実行する
きょんきょん

timeDurationをhh:mm:ssで出力する

// You can edit this code!
// Click here and start typing.
package main

import (
	"fmt"
	"strings"
	"time"
)

func main() {
	start := time.Now()
	end := time.Now().Add(1 * time.Hour).Add(1 * time.Minute).Add(1 * time.Second)
	duration := end.Sub(start).String()
	hour := duration[:strings.Index(duration, "h")]
	minute := duration[strings.Index(duration, "h")+1 : strings.Index(duration, "m")]
	second := duration[strings.Index(duration, "m")+1 : strings.Index(duration, "s")]

	d := fmt.Sprintf("%02s:%02s:%02s", hour, minute, second)

	fmt.Println(d)
}

https://go.dev/play/p/kTxPi1YWS24

きょんきょん
  • go testでFAILするまで10繰り返すshell script
#!/bin/bash

i=0
while [ $i -lt 10 ]
do
  go test
  if [ $? --ne 0 ]; then
    break
  fi
  i=$((i+1))
done