From 6aa84223d01304074dfc20e106cd51f2737bbfeb Mon Sep 17 00:00:00 2001 From: Arai Takashi Date: Wed, 21 Aug 2019 23:45:02 +0900 Subject: [PATCH 1/4] Add `go fmt` command --- cw/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/cw/Makefile b/cw/Makefile index c0d1e4d..0499126 100644 --- a/cw/Makefile +++ b/cw/Makefile @@ -19,6 +19,7 @@ default: build build: $(TARGET) $(TARGET): *.go + go fmt go build test: test-cli test-unit From f92b896b6aa0cf83f111fdf54d6a51adc7543dde Mon Sep 17 00:00:00 2001 From: Arai Takashi Date: Wed, 21 Aug 2019 23:45:55 +0900 Subject: [PATCH 2/4] Apply go fmt --- cw/api.go | 190 +++++++++++++++++----------------- cw/cli_test.go | 70 +++++++------ cw/config.go | 103 ++++++++++--------- cw/config_test.go | 60 +++++------ cw/endpoint.go | 149 ++++++++++++++------------- cw/main.go | 252 +++++++++++++++++++++++----------------------- 6 files changed, 409 insertions(+), 415 deletions(-) diff --git a/cw/api.go b/cw/api.go index 8da87e9..c02cb3b 100644 --- a/cw/api.go +++ b/cw/api.go @@ -1,143 +1,141 @@ package main import ( - "fmt" - "net/http" - "net/url" - "io/ioutil" - "os" - "strings" - "regexp" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "regexp" + "strings" ) const ( - DefaultHost = "api.chatwork.com" - DefaultVersion = "v2" - DefaultTokenEnvName = "CW_API_TOKEN" + DefaultHost = "api.chatwork.com" + DefaultVersion = "v2" + DefaultTokenEnvName = "CW_API_TOKEN" ) // APIへのリクエストに必要な情報を集めた構造体 type CwApi struct { - // HTTPメソッド - Method string + // HTTPメソッド + Method string - // APIのホスト - Host string + // APIのホスト + Host string - // APIバージョン - Version string + // APIバージョン + Version string - // エンドポイントのパスまでの配列 - Paths []string + // エンドポイントのパスまでの配列 + Paths []string - // リクエストパラメタ - Param url.Values + // リクエストパラメタ + Param url.Values - // リクエストに認証情報をつけるオブジェクト - Auth CwApiAuthorizer + // リクエストに認証情報をつけるオブジェクト + Auth CwApiAuthorizer } func NewCwApi() *CwApi { - api := CwApi{} - api.Host = DefaultHost - api.Version = DefaultVersion - api.Auth = &TokenFromEnvAuthorizer{DefaultTokenEnvName} - return &api + api := CwApi{} + api.Host = DefaultHost + api.Version = DefaultVersion + api.Auth = &TokenFromEnvAuthorizer{DefaultTokenEnvName} + return &api } func NewCwApiByConfig(cfg *ApiConfig, profile string) (*CwApi, error) { - if cfg == nil { - return NewCwApi(), nil - } - - if profile == "" { - profile = cfg.DefaultProfile - } - - prof, ok := cfg.Profiles[profile] - if ok { - return NewCwApiWithProfile(&prof), nil - } else { - return nil, fmt.Errorf("profile not found: %s", profile) - } + if cfg == nil { + return NewCwApi(), nil + } + + if profile == "" { + profile = cfg.DefaultProfile + } + + prof, ok := cfg.Profiles[profile] + if ok { + return NewCwApiWithProfile(&prof), nil + } else { + return nil, fmt.Errorf("profile not found: %s", profile) + } } func NewCwApiWithProfile(prof *ApiConfigProfile) *CwApi { - api := NewCwApi() - if prof.Host != "" { - api.Host = prof.Host - } - if prof.Version != "" { - api.Version = prof.Version - } - if prof.Token != "" { - api.Auth = &TokenFromValueAuthorizer{prof.Token} - } - return api + api := NewCwApi() + if prof.Host != "" { + api.Host = prof.Host + } + if prof.Version != "" { + api.Version = prof.Version + } + if prof.Token != "" { + api.Auth = &TokenFromValueAuthorizer{prof.Token} + } + return api } // http.Requestをつくる func (a *CwApi) toRequest() (*http.Request, error) { - meth := strings.ToUpper(a.Method) - ok, _ := regexp.MatchString(`^[A-Z]+$`, meth) - if !ok { - return nil, fmt.Errorf("invalid method: %s", a.Method) - } - - url := "https://" + a.Host + "/" + a.Version + "/" + strings.Join(a.Paths, "/") - req, err := http.NewRequest(meth, url, nil) - if err != nil { - return nil, err - } - - req.Header.Set("User-Agent", getVersion()) - - if a.Param != nil && 0 < len(a.Param) { - query := a.Param.Encode() - if meth == "GET" { - req.URL.RawQuery = query - } else { - req.Body = ioutil.NopCloser(strings.NewReader(query)) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Content-Length", fmt.Sprintf("%d", len(query))) - } - } - - err = a.Auth.Authorize(req) - if err != nil { - return nil, err - } - - return req, nil + meth := strings.ToUpper(a.Method) + ok, _ := regexp.MatchString(`^[A-Z]+$`, meth) + if !ok { + return nil, fmt.Errorf("invalid method: %s", a.Method) + } + + url := "https://" + a.Host + "/" + a.Version + "/" + strings.Join(a.Paths, "/") + req, err := http.NewRequest(meth, url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("User-Agent", getVersion()) + + if a.Param != nil && 0 < len(a.Param) { + query := a.Param.Encode() + if meth == "GET" { + req.URL.RawQuery = query + } else { + req.Body = ioutil.NopCloser(strings.NewReader(query)) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Content-Length", fmt.Sprintf("%d", len(query))) + } + } + + err = a.Auth.Authorize(req) + if err != nil { + return nil, err + } + + return req, nil } - // 何かの方法でリクエストに認証情報をつけるオブジェクトを示すinterface type CwApiAuthorizer interface { - Authorize(r *http.Request) error + Authorize(r *http.Request) error } // 環境変数からAPIトークンを読み取るAuthorizerの実装 type TokenFromEnvAuthorizer struct { - EnvName string + EnvName string } func (ta *TokenFromEnvAuthorizer) Authorize(r *http.Request) error { - token, ok := os.LookupEnv(ta.EnvName) - if !ok { - return fmt.Errorf("environment variable not set: " + ta.EnvName) - } - r.Header.Add("X-ChatWorkToken", token) - return nil + token, ok := os.LookupEnv(ta.EnvName) + if !ok { + return fmt.Errorf("environment variable not set: " + ta.EnvName) + } + r.Header.Add("X-ChatWorkToken", token) + return nil } // APIトークンをそのまま設定するAuthorizerの実装 type TokenFromValueAuthorizer struct { - Token string + Token string } func (ta *TokenFromValueAuthorizer) Authorize(r *http.Request) error { - r.Header.Add("X-ChatWorkToken", ta.Token) - return nil + r.Header.Add("X-ChatWorkToken", ta.Token) + return nil } - diff --git a/cw/cli_test.go b/cw/cli_test.go index 3c2a835..75c6c98 100644 --- a/cw/cli_test.go +++ b/cw/cli_test.go @@ -1,59 +1,57 @@ package main import ( - "testing" + "testing" - cli "github.com/rendon/testcli" + cli "github.com/rendon/testcli" ) const ( - targetBinary = "./cw" - helpMessage = "cw -- Simple command line tool for chatwork API" - versionRegex = `chatwork-cli/cw.*ver\.` + targetBinary = "./cw" + helpMessage = "cw -- Simple command line tool for chatwork API" + versionRegex = `chatwork-cli/cw.*ver\.` ) func TestSmokeRun(t *testing.T) { - cli.Run(targetBinary) - if !cli.Success() { - t.Log(cli.Error()) - t.Fatalf("Command with no argument will be successful, but failed") - } + cli.Run(targetBinary) + if !cli.Success() { + t.Log(cli.Error()) + t.Fatalf("Command with no argument will be successful, but failed") + } } func TestShowHelp(t *testing.T) { - cli.Run(targetBinary, "-h") - if !cli.StdoutContains(helpMessage) { - t.Log(cli.Stdout()) - t.Fatalf("Command with '-h' will show help, but not supplied") - } + cli.Run(targetBinary, "-h") + if !cli.StdoutContains(helpMessage) { + t.Log(cli.Stdout()) + t.Fatalf("Command with '-h' will show help, but not supplied") + } } func TestShowVersion(t *testing.T) { - cli.Run(targetBinary, "-version") - if !cli.StdoutMatches(versionRegex) { - t.Log(cli.Stdout()) - t.Fatalf("Command with '-version' will show version, but not supplied") - } + cli.Run(targetBinary, "-version") + if !cli.StdoutMatches(versionRegex) { + t.Log(cli.Stdout()) + t.Fatalf("Command with '-version' will show version, but not supplied") + } } func TestArgumentError(t *testing.T) { - cli.Run(targetBinary, "-invalidflag") - if !cli.Failure() { - t.Fatalf("Command with invalid flag will fail, but succeeded") - } + cli.Run(targetBinary, "-invalidflag") + if !cli.Failure() { + t.Fatalf("Command with invalid flag will fail, but succeeded") + } } func TestRunWithEnvironment(t *testing.T) { - cmd := cli.Command(targetBinary, "get", "me") - cmd.SetEnv([]string{"CW_API_TOKEN=token"}) - cmd.Run() - - if !cmd.Success() { - t.Log(cmd.Error()) - t.Log(cmd.Stdout()) - t.Log(cmd.Stderr()) - t.Fatalf("Command using CW_API_TOKEN environment will send request, but failed") - } + cmd := cli.Command(targetBinary, "get", "me") + cmd.SetEnv([]string{"CW_API_TOKEN=token"}) + cmd.Run() + + if !cmd.Success() { + t.Log(cmd.Error()) + t.Log(cmd.Stdout()) + t.Log(cmd.Stderr()) + t.Fatalf("Command using CW_API_TOKEN environment will send request, but failed") + } } - - diff --git a/cw/config.go b/cw/config.go index 3441869..79f7a01 100644 --- a/cw/config.go +++ b/cw/config.go @@ -1,79 +1,78 @@ package main import ( - "os" - "os/user" - "path/filepath" - "fmt" - "strings" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" - "github.com/BurntSushi/toml" + "github.com/BurntSushi/toml" ) const DefaultConfigFile = ".chatwork.toml" type ApiConfig struct { - DefaultProfile string `toml:"default_profile"` - Values map[string]string - Profiles map[string]ApiConfigProfile + DefaultProfile string `toml:"default_profile"` + Values map[string]string + Profiles map[string]ApiConfigProfile } func (cfg *ApiConfig) ApplyValues(args []string) []string { - if cfg == nil || len(cfg.Values) == 0 { - // noop - return args - } + if cfg == nil || len(cfg.Values) == 0 { + // noop + return args + } - oldnew := make([]string, len(cfg.Values) * 2) - i := 0 - for key, value := range cfg.Values { - oldnew[i] = fmt.Sprintf("{%s}", key) - oldnew[i+1] = value - i += 2 - } + oldnew := make([]string, len(cfg.Values)*2) + i := 0 + for key, value := range cfg.Values { + oldnew[i] = fmt.Sprintf("{%s}", key) + oldnew[i+1] = value + i += 2 + } - rep := strings.NewReplacer(oldnew...) - result := make([]string, len(args)) - for i, a := range args { - result[i] = rep.Replace(a) - } + rep := strings.NewReplacer(oldnew...) + result := make([]string, len(args)) + for i, a := range args { + result[i] = rep.Replace(a) + } - return result + return result } type ApiConfigProfile struct { - Host string - Version string - Token string + Host string + Version string + Token string } func ReadConfig(filename string) (*ApiConfig, error) { - if filename == "" { - filename = getDefaultConfigPath() - } + if filename == "" { + filename = getDefaultConfigPath() + } - _, err := os.Stat(filename) - if err != nil { - if os.IsNotExist(err) { - return nil, nil // no file is not an error - } else { - return nil, err - } - } + _, err := os.Stat(filename) + if err != nil { + if os.IsNotExist(err) { + return nil, nil // no file is not an error + } else { + return nil, err + } + } - var cfg ApiConfig - _, err = toml.DecodeFile(filename, &cfg) - if err != nil { - return nil, err - } - return &cfg, nil + var cfg ApiConfig + _, err = toml.DecodeFile(filename, &cfg) + if err != nil { + return nil, err + } + return &cfg, nil } func getDefaultConfigPath() string { - user, err := user.Current() - if err != nil { - panic(err) - } - return filepath.Join(user.HomeDir, DefaultConfigFile) + user, err := user.Current() + if err != nil { + panic(err) + } + return filepath.Join(user.HomeDir, DefaultConfigFile) } - diff --git a/cw/config_test.go b/cw/config_test.go index 46fb8fd..529d128 100644 --- a/cw/config_test.go +++ b/cw/config_test.go @@ -1,40 +1,40 @@ package main import ( - "testing" + "testing" ) const ExampleConfig = "example.toml" func Test_ReadConfig_canReadExampleFile(t *testing.T) { - cfg, err := ReadConfig(ExampleConfig) - if err != nil { - t.Errorf("Failed to read example: %s", err) - } - - if cfg.DefaultProfile != "default" { - t.Errorf("DefaultProfile expected 'default', but: %s", cfg.DefaultProfile) - } - - if cfg.Values["room_id"] != "95297208" { - t.Errorf("Values[room_id] is not expected value: %s", cfg.Values["room_id"]) - } - - prof, ok := cfg.Profiles["default"] - if !ok { - t.Errorf("Profile[default] expected to be existed, but nil") - } - - if prof.Token != "mysecrettoken" { - t.Errorf("profiles.default.token is not expected value: %s", prof.Token) - } - - if prof.Version != "" { - t.Errorf("profiles.default.version is not expected value: %s", prof.Version) - } - - if prof.Host != "" { - t.Errorf("profiles.default.host is not expected value: %s", prof.Host) - } + cfg, err := ReadConfig(ExampleConfig) + if err != nil { + t.Errorf("Failed to read example: %s", err) + } + + if cfg.DefaultProfile != "default" { + t.Errorf("DefaultProfile expected 'default', but: %s", cfg.DefaultProfile) + } + + if cfg.Values["room_id"] != "95297208" { + t.Errorf("Values[room_id] is not expected value: %s", cfg.Values["room_id"]) + } + + prof, ok := cfg.Profiles["default"] + if !ok { + t.Errorf("Profile[default] expected to be existed, but nil") + } + + if prof.Token != "mysecrettoken" { + t.Errorf("profiles.default.token is not expected value: %s", prof.Token) + } + + if prof.Version != "" { + t.Errorf("profiles.default.version is not expected value: %s", prof.Version) + } + + if prof.Host != "" { + t.Errorf("profiles.default.host is not expected value: %s", prof.Host) + } } diff --git a/cw/endpoint.go b/cw/endpoint.go index d9387fe..8a6c1c0 100644 --- a/cw/endpoint.go +++ b/cw/endpoint.go @@ -1,104 +1,103 @@ package main import ( - "fmt" - "net/http" - "net/url" - "io/ioutil" - "sort" - "strings" - - "gopkg.in/yaml.v2" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strings" + + "gopkg.in/yaml.v2" ) const ( - OfficialRamlFileUrl = "https://raw.githubusercontent.com/chatwork/api/master/RAML/api-ja.raml" + OfficialRamlFileUrl = "https://raw.githubusercontent.com/chatwork/api/master/RAML/api-ja.raml" ) type EndPoint struct { - Method string - Path string - Description string + Method string + Path string + Description string } func ShowEndPoints(raml string) error { - if raml == "" { - raml = OfficialRamlFileUrl - } + if raml == "" { + raml = OfficialRamlFileUrl + } - bytes, err := GetRaml(raml) - if err != nil { - return err - } + bytes, err := GetRaml(raml) + if err != nil { + return err + } - endpoints, err := ParseRaml(bytes) - if err != nil { - return err - } + endpoints, err := ParseRaml(bytes) + if err != nil { + return err + } - for _, e := range endpoints { - fmt.Printf("%s\t%s -- %s\n", e.Method, e.Path, e.Description) - } + for _, e := range endpoints { + fmt.Printf("%s\t%s -- %s\n", e.Method, e.Path, e.Description) + } - return nil + return nil } func GetRaml(location string) ([]byte, error) { - var data []byte - var err error - - u, err := url.Parse(location) - if err == nil && (u.Scheme == "http" || u.Scheme == "https") { - // It's a URL - resp, err := http.Get(location) - if err == nil { - data, err = ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - } - } else { - // It may be a file - data, err = ioutil.ReadFile(location) - } - if err != nil { - return nil, err - } - - return data, nil + var data []byte + var err error + + u, err := url.Parse(location) + if err == nil && (u.Scheme == "http" || u.Scheme == "https") { + // It's a URL + resp, err := http.Get(location) + if err == nil { + data, err = ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + } + } else { + // It may be a file + data, err = ioutil.ReadFile(location) + } + if err != nil { + return nil, err + } + + return data, nil } func ParseRaml(data []byte) ([]EndPoint, error) { - var raml map[interface{}]interface{} - var ep []EndPoint + var raml map[interface{}]interface{} + var ep []EndPoint - err := yaml.Unmarshal(data, &raml) - if err != nil { - return ep, err - } + err := yaml.Unmarshal(data, &raml) + if err != nil { + return ep, err + } - parse(&ep, "", raml) - sort.Slice(ep, func(i, j int) bool { - return (ep)[i].Path < (ep)[j].Path - }) + parse(&ep, "", raml) + sort.Slice(ep, func(i, j int) bool { + return (ep)[i].Path < (ep)[j].Path + }) - return ep, nil + return ep, nil } func parse(ep *[]EndPoint, current string, node map[interface{}]interface{}) { - for k, v := range node { - ks := k.(string) - switch ks { - case "GET", "POST", "PUT", "DELETE": - m := v.(map[interface{}]interface{}) - d := strings.Trim(m["description"].(string), " \n") - e := EndPoint{ks, current, d} - // fmt.Println(e) - *ep = append(*ep, e) - default: - if ks[0] == '/' { - m := v.(map[interface{}]interface{}) - parse(ep, current + ks, m) - } - } - } + for k, v := range node { + ks := k.(string) + switch ks { + case "GET", "POST", "PUT", "DELETE": + m := v.(map[interface{}]interface{}) + d := strings.Trim(m["description"].(string), " \n") + e := EndPoint{ks, current, d} + // fmt.Println(e) + *ep = append(*ep, e) + default: + if ks[0] == '/' { + m := v.(map[interface{}]interface{}) + parse(ep, current+ks, m) + } + } + } } - diff --git a/cw/main.go b/cw/main.go index ec33a79..ae04612 100644 --- a/cw/main.go +++ b/cw/main.go @@ -1,177 +1,177 @@ package main import ( - "fmt" - "flag" - "net/http" - "net/url" - "io" - "os" - "strings" - "sort" - "log" + "flag" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "sort" + "strings" ) const ( - AppCommand = "cw" - AppName = "chatwork-cli/cw" - AppVersion = "1.0" + AppCommand = "cw" + AppName = "chatwork-cli/cw" + AppVersion = "1.0" ) var ( - optHelp bool - optVerbose bool - optProfile string - optConfigFile string - optVersion bool - optEndpoint bool - optRamlFile string + optHelp bool + optVerbose bool + optProfile string + optConfigFile string + optVersion bool + optEndpoint bool + optRamlFile string ) func init() { - flag.BoolVar(&optHelp, "h", false, "Show help message") - flag.BoolVar(&optVerbose, "v", false, "Dump http headers") - flag.StringVar(&optProfile, "p", "", "Specify `profile` name to use") - flag.StringVar(&optConfigFile, "f", "", "Specify `configfile` to use") - flag.BoolVar(&optVersion, "version", false, "Show version number") - flag.BoolVar(&optEndpoint, "endpoint", false, "List endpoints") - flag.StringVar(&optRamlFile, "raml", "", "Specify `ramlfile` url or path for -endpoint option") - - // set up logger for error messaget - log.SetOutput(os.Stderr) - log.SetPrefix("Error: ") - log.SetFlags(0) + flag.BoolVar(&optHelp, "h", false, "Show help message") + flag.BoolVar(&optVerbose, "v", false, "Dump http headers") + flag.StringVar(&optProfile, "p", "", "Specify `profile` name to use") + flag.StringVar(&optConfigFile, "f", "", "Specify `configfile` to use") + flag.BoolVar(&optVersion, "version", false, "Show version number") + flag.BoolVar(&optEndpoint, "endpoint", false, "List endpoints") + flag.StringVar(&optRamlFile, "raml", "", "Specify `ramlfile` url or path for -endpoint option") + + // set up logger for error messaget + log.SetOutput(os.Stderr) + log.SetPrefix("Error: ") + log.SetFlags(0) } func main() { - flag.Parse() + flag.Parse() - if optVersion { - fmt.Println(getVersion()) - return - } + if optVersion { + fmt.Println(getVersion()) + return + } - if optEndpoint { - ShowEndPoints(optRamlFile) - return - } + if optEndpoint { + ShowEndPoints(optRamlFile) + return + } - if optHelp || len(flag.Args()) < 2 { - fmt.Printf(`%s -- Simple command line tool for chatwork API + if optHelp || len(flag.Args()) < 2 { + fmt.Printf(`%s -- Simple command line tool for chatwork API Usage: %s [options] [paths...] Available options: `, AppCommand, AppCommand) - flag.PrintDefaults() - return - } + flag.PrintDefaults() + return + } - doRequest() + doRequest() } func parseArguments(args []string) (string, []string, url.Values) { - method := "GET" - paths := []string{"me"} - params := url.Values{} - - switch num := len(args); { - case 3 <= num: - for _, a := range args[2:] { - if strings.Contains(a, "=") { - p := strings.SplitN(a, "=", 2) - params.Set(p[0], p[1]) - } else { - // パラメタ名が無いものはとりあえずパスとして扱う - paths = append(paths, a) - } - } - fallthrough - case 2 <= num: - paths[0] = args[1] - fallthrough - case 1 <= num: - method = args[0] - } - - return method, paths, params + method := "GET" + paths := []string{"me"} + params := url.Values{} + + switch num := len(args); { + case 3 <= num: + for _, a := range args[2:] { + if strings.Contains(a, "=") { + p := strings.SplitN(a, "=", 2) + params.Set(p[0], p[1]) + } else { + // パラメタ名が無いものはとりあえずパスとして扱う + paths = append(paths, a) + } + } + fallthrough + case 2 <= num: + paths[0] = args[1] + fallthrough + case 1 <= num: + method = args[0] + } + + return method, paths, params } func getVersion() string { - return fmt.Sprintf("%s ver.%s", AppName, AppVersion) + return fmt.Sprintf("%s ver.%s", AppName, AppVersion) } func doRequest() { - cfg, err := ReadConfig(optConfigFile) - if err != nil { - log.Fatalf("failed to load config: %s\n", err) - } - - api, err := NewCwApiByConfig(cfg, optProfile) - if err != nil { - log.Fatalln(err) - } - - args := cfg.ApplyValues(flag.Args()) - - meth, paths, param := parseArguments(args) - api.Method = meth - api.Paths = paths - api.Param = param - - req, err := api.toRequest() - if err != nil { - log.Fatalln(err) - } - - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - log.Fatalln(err) - } - - if (optVerbose) { - printReqHeader(req) - printResHeader(res) - } - - printResBody(res) + cfg, err := ReadConfig(optConfigFile) + if err != nil { + log.Fatalf("failed to load config: %s\n", err) + } + + api, err := NewCwApiByConfig(cfg, optProfile) + if err != nil { + log.Fatalln(err) + } + + args := cfg.ApplyValues(flag.Args()) + + meth, paths, param := parseArguments(args) + api.Method = meth + api.Paths = paths + api.Param = param + + req, err := api.toRequest() + if err != nil { + log.Fatalln(err) + } + + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + log.Fatalln(err) + } + + if optVerbose { + printReqHeader(req) + printResHeader(res) + } + + printResBody(res) } func stderr(format string, args ...interface{}) { - fmt.Fprintf(os.Stderr, format, args...) + fmt.Fprintf(os.Stderr, format, args...) } func printReqHeader(req *http.Request) { - stderr("> %s %s\n", req.Method, req.URL) - printHeader(">", req.Header) - stderr(">\n") + stderr("> %s %s\n", req.Method, req.URL) + printHeader(">", req.Header) + stderr(">\n") } func printResHeader(res *http.Response) { - stderr("< %s\n", res.Status) - printHeader("<", res.Header) - stderr("<\n") + stderr("< %s\n", res.Status) + printHeader("<", res.Header) + stderr("<\n") } func printHeader(prefix string, h http.Header) { - // get keys - keys := make([]string, len(h)) - for k := range h { - keys = append(keys, k) - } - // sort by name - sort.Strings(keys) - for _, name := range keys { - for _, v := range h[name] { - stderr("%s %s: %s\n", prefix, name, v) - } - } + // get keys + keys := make([]string, len(h)) + for k := range h { + keys = append(keys, k) + } + // sort by name + sort.Strings(keys) + for _, name := range keys { + for _, v := range h[name] { + stderr("%s %s: %s\n", prefix, name, v) + } + } } func printResBody(res *http.Response) { - io.Copy(os.Stdout, res.Body) - res.Body.Close() + io.Copy(os.Stdout, res.Body) + res.Body.Close() } From 246bcaf76ae879a5d173caea9e05f670c7ba8866 Mon Sep 17 00:00:00 2001 From: ARAI Takashi Date: Thu, 22 Aug 2019 01:14:03 +0900 Subject: [PATCH 3/4] WIP --- cw/api_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 cw/api_test.go diff --git a/cw/api_test.go b/cw/api_test.go new file mode 100644 index 0000000..f6dd0eb --- /dev/null +++ b/cw/api_test.go @@ -0,0 +1,40 @@ +package main + +import ( + "log" + "net/url" + "testing" +) + +func Test_Api(t *testing.T) { + + api := NewCwApi() + api.Method = "POST" + api.Version = "v2" + api.Host = "api.chatwork.com" + api.Paths = []string{"aaa", "bbbb"} + + param, _ := url.ParseQuery("foo=bar&hoge=fufa&file=@path") + + // log.Printf("%#v", api) + log.Printf("%#v", param) + + var f string + + for key, val := range param { + if val[0][0] == '@' { + param.Del(key) + f = val[0][1:] + } + log.Printf("%s -> %s\n", key, val) + log.Printf("%T -> %T\n", key, val) + } + + api.Param = param + req, _ := api.toRequest() + log.Printf("%#v", req.URL.String()) + log.Printf("%#v", f) + + t.Errorf("api test error end", api) + +} From c2a56654f549dd5fe615843d81bb9b6a1d64540a Mon Sep 17 00:00:00 2001 From: ARAI Takashi Date: Thu, 29 Aug 2019 02:59:48 +0900 Subject: [PATCH 4/4] It works --- cw/api.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ cw/api_test.go | 18 +++++++++--------- cw/main.go | 3 +++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/cw/api.go b/cw/api.go index c02cb3b..0a74170 100644 --- a/cw/api.go +++ b/cw/api.go @@ -1,11 +1,15 @@ package main import ( + "bytes" "fmt" + "io" "io/ioutil" + "mime/multipart" "net/http" "net/url" "os" + "path/filepath" "regexp" "strings" ) @@ -35,6 +39,9 @@ type CwApi struct { // リクエストに認証情報をつけるオブジェクト Auth CwApiAuthorizer + + // multipartで送るか + UseMultiPart bool } func NewCwApi() *CwApi { @@ -96,7 +103,44 @@ func (a *CwApi) toRequest() (*http.Request, error) { query := a.Param.Encode() if meth == "GET" { req.URL.RawQuery = query + } else if a.UseMultiPart { + // multipart + body := &bytes.Buffer{} + mw := multipart.NewWriter(body) + for key, val := range a.Param { + v := val[0] // 1つめを常に見る + // attach file + if v[0] == '@' { + // open file + path := v[1:] + fh, err := os.Open(string(path)) + if err != nil { + return nil, err + } + defer fh.Close() + + // attach + fw, err := mw.CreateFormFile(key, filepath.Base(path)) + _, err = io.Copy(fw, fh) + if err != nil { + return nil, err + } + } else { + fw, err := mw.CreateFormField(key) + _, err = fw.Write([]byte(v)) + if err != nil { + return nil, err + } + } + } + err = mw.Close() + if err != nil { + return nil, err + } + req.Body = ioutil.NopCloser(body) + req.Header.Set("Content-Type", mw.FormDataContentType()) } else { + // form-urlencoded req.Body = ioutil.NopCloser(strings.NewReader(query)) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Length", fmt.Sprintf("%d", len(query))) diff --git a/cw/api_test.go b/cw/api_test.go index f6dd0eb..a8abec8 100644 --- a/cw/api_test.go +++ b/cw/api_test.go @@ -8,11 +8,11 @@ import ( func Test_Api(t *testing.T) { - api := NewCwApi() - api.Method = "POST" - api.Version = "v2" - api.Host = "api.chatwork.com" - api.Paths = []string{"aaa", "bbbb"} + // api := NewCwApi() + // api.Method = "POST" + // api.Version = "v2" + // api.Host = "api.chatwork.com" + // api.Paths = []string{"aaa", "bbbb"} param, _ := url.ParseQuery("foo=bar&hoge=fufa&file=@path") @@ -30,11 +30,11 @@ func Test_Api(t *testing.T) { log.Printf("%T -> %T\n", key, val) } - api.Param = param - req, _ := api.toRequest() - log.Printf("%#v", req.URL.String()) + // api.Param = param + // req, _ := api.toRequest() + // log.Printf("%#v", req.URL.String()) log.Printf("%#v", f) - t.Errorf("api test error end", api) + t.Errorf("api test error end") } diff --git a/cw/main.go b/cw/main.go index ae04612..79ee10d 100644 --- a/cw/main.go +++ b/cw/main.go @@ -23,6 +23,7 @@ var ( optVerbose bool optProfile string optConfigFile string + optMultiPart bool optVersion bool optEndpoint bool optRamlFile string @@ -33,6 +34,7 @@ func init() { flag.BoolVar(&optVerbose, "v", false, "Dump http headers") flag.StringVar(&optProfile, "p", "", "Specify `profile` name to use") flag.StringVar(&optConfigFile, "f", "", "Specify `configfile` to use") + flag.BoolVar(&optMultiPart, "m", false, "Use mutipart/form-data") flag.BoolVar(&optVersion, "version", false, "Show version number") flag.BoolVar(&optEndpoint, "endpoint", false, "List endpoints") flag.StringVar(&optRamlFile, "raml", "", "Specify `ramlfile` url or path for -endpoint option") @@ -120,6 +122,7 @@ func doRequest() { api.Method = meth api.Paths = paths api.Param = param + api.UseMultiPart = optMultiPart req, err := api.toRequest() if err != nil {