Skip to content

Commit

Permalink
Add support for custom actions in client
Browse files Browse the repository at this point in the history
Gohan client will parse custom actions defined in schema
and support them in CLI.
  • Loading branch information
Adam Boniecki committed Aug 24, 2016
1 parent 431510b commit 519dfd7
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 1 deletion.
39 changes: 39 additions & 0 deletions cli/client/arguments.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,45 @@ import (
"github.com/cloudwan/gohan/schema"
)

func (gohanClientCLI *GohanClientCLI) getCustomArgsAsMap(
args []string,
actionInput string,
action schema.Action,
) (argsMap map[string]interface{}, err error) {
argsMap = map[string]interface{}{}
if action.InputSchema != nil {
var value interface{}
inputType, ok := action.InputSchema["type"].(string)
if !ok {
return nil, fmt.Errorf("Invalid input schema")
}
switch inputType {
case "integer", "number":
value, err = strconv.ParseInt(actionInput, 10, 64)
case "boolean":
value, err = strconv.ParseBool(actionInput)
case "array", "object":
err = json.Unmarshal([]byte(actionInput), &value)
default:
value = actionInput
}
if err != nil {
return nil, fmt.Errorf("Error parsing action input %s:", err)
}
argsMap[action.ID] = value
}
for i := 0; i < len(args); i += 2 {
key := strings.TrimPrefix(args[i], "--")
if _, ok := commonParams[key]; !ok {
return nil, fmt.Errorf("Error parsing parameter %s", key)
}
value := args[i+1]
argsMap[key] = value
}
err = gohanClientCLI.handleCommonArguments(argsMap)
return
}

func (gohanClientCLI *GohanClientCLI) handleArguments(args []string, s *schema.Schema) (map[string]interface{}, error) {
argsMap, err := getArgsAsMap(args, s)
if err != nil {
Expand Down
126 changes: 125 additions & 1 deletion cli/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"io/ioutil"
"os"
"sort"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -423,6 +424,7 @@ var _ = Describe("CLI functions", func() {
Describe("Commands", func() {
var towerSchema *schema.Schema
var castleSchema *schema.Schema
const customCommandsTotal int = 2

BeforeEach(func() {
towerSchema, _ = schema.NewSchemaFromObj(getTowerSchema())
Expand All @@ -435,7 +437,7 @@ var _ = Describe("CLI functions", func() {
castleSchema,
}
commands := gohanClientCLI.getCommands()
Expect(commands).To(HaveLen(2 * 5))
Expect(commands).To(HaveLen(10+customCommandsTotal))
Expect(commands[0].Name).To(Equal("tower list"))
Expect(commands[1].Name).To(Equal("tower show"))
Expect(commands[2].Name).To(Equal("tower create"))
Expand All @@ -447,6 +449,9 @@ var _ = Describe("CLI functions", func() {
Expect(commands[7].Name).To(Equal("castle create"))
Expect(commands[8].Name).To(Equal("castle set"))
Expect(commands[9].Name).To(Equal("castle delete"))
sort.Sort(ByName(commands[10:10+customCommandsTotal]))
Expect(commands[10].Name).To(Equal("castle close_gates"))
Expect(commands[11].Name).To(Equal("castle open_gates"))
})

Describe("Execute command", func() {
Expand Down Expand Up @@ -1146,6 +1151,125 @@ var _ = Describe("CLI functions", func() {
Expect(err).To(MatchError("Unexpected response: 500 Internal Server Error"))
})
})

Describe("Custom commands", func() {
var customCommands []gohanCommand
const openGatesInput string = `{ "gate_id": 42 }`

BeforeEach(func() {
customCommands = gohanClientCLI.getCustomCommands(castleSchema)
sort.Sort(ByName(customCommands))
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest(
"GET",
"/v2.0/castles/"+castleID,
),
ghttp.RespondWithJSONEncoded(
200,
map[string]interface{}{
"id": castleID,
"name": "Camelot",
},
),
),
)
})

It("Should create proper number of custom commands with proper names", func() {
Expect(len(customCommands)).To(Equal(customCommandsTotal))
Expect(customCommands[0].Name).To(Equal("castle close_gates"))
Expect(customCommands[1].Name).To(Equal("castle open_gates"))
})

It("Should 'open gates' successfully", func() {
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest(
"GET",
"/v2.0/castles/"+castleID+"/open_gates",
),
ghttp.RespondWithJSONEncoded(200, openGates()),
),
)
Expect(customCommands[1].Name).To(Equal("castle open_gates"))
result, err := customCommands[1].Action([]string{
`{ "gate_id": 42 }`,
castleID,
})
Expect(err).ToNot(HaveOccurred())
Expect(result).To(Equal(openGates()))
})

It("Should 'close gates' successfully", func() {
server.SetHandler(1,
ghttp.CombineHandlers(
ghttp.VerifyRequest(
"GET",
"/v2.0/castles/close_all_gates",
),
ghttp.RespondWithJSONEncoded(200, closeGates()),
),
)
Expect(customCommands[0].Name).To(Equal("castle close_gates"))
result, err := customCommands[0].Action([]string{})
Expect(err).ToNot(HaveOccurred())
Expect(result).To(Equal(closeGates()))
})

It("Should show error - wrong number of arguments", func() {
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest(
"GET",
"/v2.0/castles/"+castleID+"/open_gates",
),
ghttp.RespondWithJSONEncoded(200, openGates()),
),
)
result, err := customCommands[1].Action([]string{castleID})
Expect(result).To(BeNil())
Expect(err).To(MatchError("Wrong number of arguments"))
})

It("Should show error - error parsing arguments", func() {
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest(
"GET",
"/v2.0/castles/"+castleID+"/open_gates",
),
ghttp.RespondWithJSONEncoded(200, closeGates()),
),
)
result, err := customCommands[1].Action([]string{
"--opt", "yes",
openGatesInput,
castleID,
})
Expect(result).To(BeNil())
Expect(err).To(MatchError(HavePrefix("Error parsing parameter")))
})

It("Should show error - resource not found", func() {
server.SetHandler(1, ghttp.RespondWith(404, nil))
server.AppendHandlers(ghttp.RespondWith(404, nil))
result, err := customCommands[1].Action([]string{
openGatesInput,
"wrongID",
})
Expect(result).To(BeNil())
Expect(err).To(MatchError("Resource not found"))
})

It("Should show error - unexpected response", func() {
server.SetHandler(1, ghttp.RespondWith(500, nil))
result, err := customCommands[0].Action([]string{})
Expect(result).To(BeNil())
Expect(err).To(MatchError("Unexpected response: 500 Internal Server Error"))
})
// TODO more?
})
})

Describe("Name -> ID mapping", func() {
Expand Down
51 changes: 51 additions & 0 deletions cli/client/client_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@ func getCastleSchema() map[string]interface{} {
"name",
},
},
"actions": map[string]interface{}{
"open_gates": map[string]interface{}{
"method": "GET",
"path": "/:id/open_gates",
"input": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"gate_id": map[string]interface{}{
"type": "number",
},
},
},
"output": map[string]interface{}{
"type": "string",
},
},
"close_gates": map[string]interface{}{
"method": "GET",
"path": "/close_all_gates",
"output": map[string]interface{}{
"type": "string",
},
},
},
}
}

Expand Down Expand Up @@ -234,11 +258,20 @@ func getSchemas() []*schema.Schema {
}

var (
castleID = "de305d54-75b4-431b-adb2-eb6b9e546013"
icyTowerID = "de305d54-75b4-431b-adb2-eb6b9e546014"
icyTowerName = "Icy Tower"
babylonTowerID = "de305d54-75b4-431b-adb2-eb6b9e546015"
)

func openGates() string {
return "gates opened"
}

func closeGates() string {
return "gates closed"
}

func getIcyTower() map[string]interface{} {
return map[string]interface{}{
"id": icyTowerID,
Expand Down Expand Up @@ -280,6 +313,8 @@ func compareSchemas(actual, expected []*schema.Schema) {
for i, s := range actual {
sortProperties(s)
sortProperties(expected[i])
sortActions(s)
sortActions(expected[i])
g.Expect(s).To(g.Equal(expected[i]))
}
}
Expand All @@ -293,3 +328,19 @@ func (p properties) Less(i, j int) bool { return p[i].ID < p[j].ID }
func sortProperties(schema *schema.Schema) {
sort.Sort(properties(schema.Properties))
}

type actions []schema.Action

func (a actions) Len() int { return len(a) }
func (a actions) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a actions) Less(i, j int) bool { return a[i].ID < a[j].ID }

func sortActions(schema *schema.Schema) {
sort.Sort(actions(schema.Actions))
}

type ByName []gohanCommand

func (a ByName) Len() int { return len(a) }
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
Loading

0 comments on commit 519dfd7

Please sign in to comment.