Open23
Goのあれこれ
utf-8の文字列から指定した位置から部分抽出する
package main
import "fmt"
func main() {
s := "テスト文字列で指定の文字だけ抽出したい"
fmt.Println(string([]rune(s)[2:5]))
}
// output:
// ト文字
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)
- 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
}
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}
- requestのbodyでnullableにする場合は、NullStringの構造体(型)を用意してdecodeを自前で用意する必要がありそう
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))
}
template/html Tips
rendering時の条件分岐
- 値が存在しない場合に出力する
{{if not $.Value}} {{end}}
...引数(スプレット構文的使用法)
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.
スプレット構文となっている第二引数は存在しなくても実行可能
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.
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 }}>
- 安全でない文字列としてescapeされた様子
- hugoにも同様の記載あり
“ZgotmplZ” is a special value that indicates that unsafe content reached a CSS or URL context.
- funcMapによって拡張することで一応renderingはできる
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>`})
}
panic: unknown time zone Asia/Tokyo
- 以下のpkgをimportすれば解決
import _ time/tzdata
- DBへのdriverにtimezoneを指定する場合に注意
- time.FixedZoneは回避可能
jst = time.FixedZone("Asia/Tokyo", 9*60*60)
packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server
- sqlxのExecで複数のsqlを一度に実行しようとすると発生するエラー
-
max_allowed_packet
の設定値を改善しても発生する - 対応
- sqlをそれぞれ分離して実行する
htttp.Requestのretry処理
- request.Bodyのseekをもとに戻す必要あり
https://zenn.dev/imamura_sh/articles/retry-46aa586aeb5c3c28244e#リクエストの内容を巻き戻す
-
html/tempalte
のcontinue/break
https://mattn.kaoriya.net/software/lang/go/20210924011409.htm
- goのfloat64をjson.Marshalするとnumberになる
https://go.dev/play/p/uBfuxnxF9Gz
- map referance
https://go.dev/play/p/SAIZg7JTVyA
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)
}
- 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