diff --git a/cmd/api/README.md b/cmd/api/README.md new file mode 100644 index 0000000..2b7e998 --- /dev/null +++ b/cmd/api/README.md @@ -0,0 +1 @@ +## api is the root of the cli for interacting with the rest api diff --git a/cmd/api/api b/cmd/api/api new file mode 100755 index 0000000..a9a84ee Binary files /dev/null and b/cmd/api/api differ diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 0000000..12ea67d --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,90 @@ +//Package api is the root of the cli for our rest api and lives in cmd/api it is a convention in Golang to have "sub" binaries +//live inside a cmd package +package api + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" +) + +//configure our flags using the builtin flags lib +var hostFlag = flag.String("host", "http://localhost:3001", "host is used to change the default host to call") + +//declare some constants to use +const ( + usage = "available commands: \n echo \n map \n slice " + ECHO = "echo" + MAP = "map" + SLICE = "slice" +) + +func main() { + var err error + //set up some usage info + flag.Usage = func() { + fmt.Printf("Usage of %s:\n", os.Args[0]) + fmt.Println(usage) + flag.PrintDefaults() + } + //ensure we parse our flags + flag.Parse() + if flag.NArg() == 0 { + flag.Usage() + os.Exit(1) + } + args := flag.Args() //cuts off things like ./api and any flags that are passed + cmd := args[0] + switch cmd { + case ECHO: + //configure the poster as the default http implementation and the writer as stdout + echo := echoCmd{poster: http.Post, writer: os.Stdout} + err = echo.Echo(args[1:]) //pass in all after echo + } + log.Fatalf("error running command %s : %s", cmd, err.Error()) +} + +//define a custom type that takes a poster and a writer this allows for cleaner simpler testing +type echoCmd struct { + poster func(string, string, io.Reader) (*http.Response, error) + writer io.Writer +} + +//Echo calls the echo api in the web server writing the respose to the writer +func (cmd echoCmd) Echo(args []string) error { + if len(args) != 1 { + printAndExit("echo expects a message. echo ") + } + url := fmt.Sprintf("%s/api/echo", *hostFlag) + msg := map[string]string{ + "message": args[0], + } + data, err := json.Marshal(msg) + if err != nil { + return fmt.Errorf("failed to prepare data for posting %s ", err.Error()) + } + res, err := cmd.poster(url, "application/json", bytes.NewReader(data)) + if err != nil { + return fmt.Errorf("failed to post to echo endpoint %s ", err.Error()) + } + defer res.Body.Close() + resData, err := ioutil.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("failed to post to echo endpoint %s ", err.Error()) + } + if _, err := cmd.writer.Write(resData); err != nil { + return fmt.Errorf("failed to write to out %s", err.Error()) + } + return nil +} + +func printAndExit(msg string) { + log.Println(msg) + os.Exit(0) +} diff --git a/cmd/api/main_test.go b/cmd/api/main_test.go new file mode 100644 index 0000000..5463633 --- /dev/null +++ b/cmd/api/main_test.go @@ -0,0 +1,44 @@ +package api + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + "testing" +) + +func TestEchoCmd(t *testing.T) { + mockPoster := func(api, contentType string, body io.Reader) (*http.Response, error) { + u, err := url.Parse(api) + if err != nil { + t.Fatal(err.Error()) + } + if u.Path != "/api/echo" { + t.Fatal(err.Error()) + } + resBody := `{"message":"test"}` + bodyRC := ioutil.NopCloser(bytes.NewReader([]byte(resBody))) + res := &http.Response{StatusCode: 200, Body: bodyRC} + return res, nil + } + var buffer bytes.Buffer + cmd := &echoCmd{poster: mockPoster, writer: &buffer} + args := []string{"test"} + if err := cmd.Echo(args); err != nil { + t.Fatal(err.Error) + } + message := map[string]string{} + if err := json.Unmarshal(buffer.Bytes(), &message); err != nil { + t.Fatal(err.Error()) + } + if _, ok := message["message"]; !ok { + t.Fatal("expected their to be a message") + } + m := message["message"] + if m != "test" { + t.Fatal("expected the messag to be test") + } +} diff --git a/main.go b/main.go index 37136de..f0f76c3 100644 --- a/main.go +++ b/main.go @@ -49,10 +49,48 @@ func Echo(res http.ResponseWriter, req *http.Request) { } } +func MapMe(rw http.ResponseWriter, req *http.Request) { + var ( + jsonEncoder = json.NewEncoder(rw) + ) + + myMap := map[string]Message{ + "message": Message{ + Message: "hello map", + Stamp: time.Now().Unix(), + }, + } + + if err := jsonEncoder.Encode(myMap); err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } +} + +func SliceMe(rw http.ResponseWriter, req *http.Request) { + var ( + jsonEncoder = json.NewEncoder(rw) + ) + + mySlice := []Message{ + Message{ + Message: "hello map", + Stamp: time.Now().Unix(), + }, + } + + if err := jsonEncoder.Encode(mySlice); err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } +} + //Setup our simple router func router() http.Handler { //http.HandleFunc expects a func that takes a http.ResponseWriter and http.Request http.HandleFunc("/api/echo", Echo) + http.HandleFunc("/api/map", MapMe) + http.HandleFunc("/api/slice", SliceMe) return http.DefaultServeMux //this is a stdlib http.Handler }