From 180351d57b734b4fd600006b89fe8dd99689f3f7 Mon Sep 17 00:00:00 2001 From: 2Cubed <2Cubed@users.noreply.github.com> Date: Fri, 18 Aug 2017 19:32:18 -0400 Subject: [PATCH 1/2] Implement global CactusBot packet specification - Message.Text can not be filled, as described in the FIXME comment (schemas/schemas.go). This is critical to fix! - Packets other than Message are not currently supported, as I was not able to figure out how to make that work with Go's type system. This would be a nice feature to have, though, so custom commands could potentially return other packets (for example, triggering a ban after a custom warning or something.) - util/jsonapi.go was changed to allow for the standard placement of JSON-API tags (e.g. "attr,foo" instead of "foo,attr"). Is this an issue, @RPiAwesomeness? - createdAt and id are not being populated, but I suspect this has simply not been completed yet. --- command/schema.go | 43 ++++++------------- schemas/schemas.go | 101 ++++++++++++++++++++++++++++++++++++++++++--- util/jsonapi.go | 27 ++++++------ 3 files changed, 121 insertions(+), 50 deletions(-) diff --git a/command/schema.go b/command/schema.go index ccf7c74..352c3ab 100644 --- a/command/schema.go +++ b/command/schema.go @@ -7,44 +7,27 @@ import ( // ResponseSchema is the schema for the data that will be sent out to the client type ResponseSchema struct { - ID string `jsonapi:"id,primary"` - Arguments []schemas.MessagePacket `jsonapi:"arguments,attr"` - Count int `jsonapi:"count,attr"` - CreatedAt string `jsonapi:"createdAt,meta"` - Enabled bool `jsonapi:"enabled,attr"` - Name string `jsonapi:"name,attr"` - Response EmbeddedResponseSchema `jsonapi:"response,attr"` - Token string `jsonapi:"token,meta"` + ID string `jsonapi:"primary,id"` + Name string `jsonapi:"attr,name"` + Response schemas.Message `jsonapi:"attr,response"` + Count int `jsonapi:"attr,count"` + Enabled bool `jsonapi:"attr,enabled"` + CreatedAt string `jsonapi:"meta,createdAt"` + Token string `jsonapi:"meta,token"` } // ClientSchema is the schema the data from the client will be marshalled into type ClientSchema struct { - Arguments []schemas.MessagePacket `json:"arguments" validate:"required"` - Enabled bool `json:"enabled" validate:"required"` - Response EmbeddedResponseSchema `json:"response" validate:"required"` + Context schemas.Context `json:"context" validate:"required"` + Enabled bool `json:"enabled" validate:"required"` // Ignore these fields in user input, they will be filled automatically by the API - ID string `json:"id" validate:"-"` - Count int `json:"count" validate:"-"` - CreatedAt string `json:"createdAt" validate:"-"` - Token string `json:"token" validate:"-"` - Name string `json:"name" validate:"-"` -} - -// EmbeddedResponseSchema is the schema that is stored under the response key in ResponseSchema -type EmbeddedResponseSchema struct { - Action bool `jsonapi:"action,attr" validate:"required"` - Message []schemas.MessagePacket `jsonapi:"message,attr" validate:"required,gt=0"` - Role int `jsonapi:"role,attr" validate:"gte=0,lte=256"` - Target string `jsonapi:"target,attr"` - User string `jsonapi:"user,attr"` + ID string `json:"id" validate:"-"` + Count int `json:"count" validate:"-"` + Token string `json:"token" validate:"-"` + Name string `json:"name" validate:"-"` } // GetAPITag allows each of these types to implement the JSONAPISchema interface func (r ResponseSchema) GetAPITag(lookup string) string { return util.FieldTag(r, lookup, "jsonapi") } - -// GetAPITag allows each of these types to implement the JSONAPISchema interface -func (r EmbeddedResponseSchema) GetAPITag(lookup string) string { - return util.FieldTag(r, lookup, "jsonapi") -} diff --git a/schemas/schemas.go b/schemas/schemas.go index b88adf7..d205dc2 100644 --- a/schemas/schemas.go +++ b/schemas/schemas.go @@ -1,8 +1,99 @@ package schemas -// MessagePacket is the low-level format for storing contents of a message and arguments -type MessagePacket struct { - Data string `json:"data" validate:"required"` - Text string `json:"text" validate:"required"` - Type string `json:"type" validate:"required"` +import ( + "strings" +) + +type Message struct { + Text []Component `json:"text"` // FIXME: Anything besides [] breaks this. It is assumed that this is due to the interface being used for Component. + Action bool `json:"action"` +} + +type Component interface { + Text() string +} + +func (t Text) Text() string { + return t.Data +} + +func (e Emoji) Text() string { + return e.Data +} + +func (t Tag) Text() string { + return t.Data +} + +func (u URL) Text() string { + return u.Data +} + +func (v Variable) Text() string { + return v.Data +} + +type Text struct { + Data string `json:"data"` +} +type Emoji struct { + Data string `json:"data"` +} +type Tag struct { + Data string `json:"data"` +} +type URL struct { + Data string `json:"data"` +} +type Variable struct { + Data string `json:"data"` +} + +type Channel string +type User string +type Service string + +type Context struct { + Packet Message `json:"packet"` + Channel Channel `json:"channel"` + User User `json:"user,omitempty"` + Role Role `json:"role,omitempty"` + Target User `json:"target,omitempty"` + Service Service `json:"service"` +} + +type Role int + +const ( + banned Role = iota + user + subscriber + moderator + owner +) + +func (r *Role) UnmarshalJSON(b []byte) error { + str := strings.Trim(string(b), `"`) + + switch { + case str == "banned": + *r = banned + + case str == "user": + *r = user + + case str == "subscriber": + *r = subscriber + + case str == "moderator": + *r = moderator + + case str == "owner": + *r = owner + + default: + *r = user + } + + return nil } diff --git a/util/jsonapi.go b/util/jsonapi.go index e927546..360d8ac 100644 --- a/util/jsonapi.go +++ b/util/jsonapi.go @@ -55,21 +55,18 @@ func pullVals(ift reflect.Type, ifv reflect.Value) (map[string]interface{}, map[ id = subID } } - // Anything after the first element is tags, figure out which we want - for _, tag := range split[1:] { - // Need to set the keys w/ their names here if it's a struct - switch tag { - case "attr": - // Attribute - attr[split[0]] = value - case "meta": - // Meta information about the request - meta[split[0]] = value - case "primary": - // It's the primary key/record ID - id = ifv.Field(i).String() - default: // Ignore any other tags - } + // Need to set the keys w/ their names here if it's a struct + switch split[0] { + case "attr": + // Attribute + attr[split[1]] = value + case "meta": + // Meta information about the request + meta[split[1]] = value + case "primary": + // It's the primary key/record ID + id = ifv.Field(i).String() + default: // Ignore any other tags } } From 61ef6367748374e7fd5f013e643d44b17429aa7b Mon Sep 17 00:00:00 2001 From: 2Cubed <2Cubed@users.noreply.github.com> Date: Sun, 27 Aug 2017 12:40:11 -0400 Subject: [PATCH 2/2] Fix context returning Not ready for production; see HACK/FIXME/TODO comments. --- command/handler.go | 8 +++---- command/schema.go | 2 +- schemas/schemas.go | 60 +++++++++++----------------------------------- util/jsonapi.go | 3 +++ 4 files changed, 22 insertions(+), 51 deletions(-) diff --git a/command/handler.go b/command/handler.go index dca694e..4b107b0 100644 --- a/command/handler.go +++ b/command/handler.go @@ -131,10 +131,10 @@ func (c *Command) Create(ctx *gin.Context) { log.Debugf("%+v", toCreate) - // if _, err := c.Conn.Create(c.Table, toCreate); err != nil { - // util.NiceError(ctx, err, http.StatusBadRequest) - // return - // } + if _, err := c.Conn.Create(c.Table, toCreate); err != nil { + util.NiceError(ctx, err, http.StatusBadRequest) + return + } // Pass control off to GetSingle since we don't want to duplicate logic c.GetSingle(ctx) diff --git a/command/schema.go b/command/schema.go index 352c3ab..3f4b8f8 100644 --- a/command/schema.go +++ b/command/schema.go @@ -9,7 +9,7 @@ import ( type ResponseSchema struct { ID string `jsonapi:"primary,id"` Name string `jsonapi:"attr,name"` - Response schemas.Message `jsonapi:"attr,response"` + Context schemas.Context `jsonapi:"attr,context"` // TODO: use only data packet Count int `jsonapi:"attr,count"` Enabled bool `jsonapi:"attr,enabled"` CreatedAt string `jsonapi:"meta,createdAt"` diff --git a/schemas/schemas.go b/schemas/schemas.go index d205dc2..c55b6bb 100644 --- a/schemas/schemas.go +++ b/schemas/schemas.go @@ -4,62 +4,30 @@ import ( "strings" ) +// TODO: Remove `jsonapi:` tags. Should just be `json:`. type Message struct { - Text []Component `json:"text"` // FIXME: Anything besides [] breaks this. It is assumed that this is due to the interface being used for Component. - Action bool `json:"action"` + Text []Component `jsonapi:"attr,text"` + Action bool `jsonapi:"attr,action"` } -type Component interface { - Text() string -} - -func (t Text) Text() string { - return t.Data -} - -func (e Emoji) Text() string { - return e.Data -} - -func (t Tag) Text() string { - return t.Data -} - -func (u URL) Text() string { - return u.Data -} - -func (v Variable) Text() string { - return v.Data -} - -type Text struct { - Data string `json:"data"` -} -type Emoji struct { - Data string `json:"data"` -} -type Tag struct { - Data string `json:"data"` -} -type URL struct { - Data string `json:"data"` -} -type Variable struct { - Data string `json:"data"` +// TODO: Remove `jsonapi:` tags. Should just be `json:`. +type Component struct { + Type string `jsonapi:"attr,type"` // HACK: need to validate type + Data string `jsonapi:"attr,data"` } type Channel string type User string type Service string +// TODO: Remove `jsonapi:` tags. Should just be `json:`. type Context struct { - Packet Message `json:"packet"` - Channel Channel `json:"channel"` - User User `json:"user,omitempty"` - Role Role `json:"role,omitempty"` - Target User `json:"target,omitempty"` - Service Service `json:"service"` + Packet Message `jsonapi:"attr,packet"` + Channel Channel `jsonapi:"attr,channel"` + User User `jsonapi:"attr,user,omitempty"` + Role Role `jsonapi:"attr,role,omitempty"` // FIXME: should return string ("owner"), not int (5). See UnmarshalJSON below. + Target User `jsonapi:"attr,target,omitempty"` + Service Service `jsonapi:"attr,service"` } type Role int diff --git a/util/jsonapi.go b/util/jsonapi.go index 360d8ac..9698f9f 100644 --- a/util/jsonapi.go +++ b/util/jsonapi.go @@ -60,12 +60,15 @@ func pullVals(ift reflect.Type, ifv reflect.Value) (map[string]interface{}, map[ case "attr": // Attribute attr[split[1]] = value + break case "meta": // Meta information about the request meta[split[1]] = value + break case "primary": // It's the primary key/record ID id = ifv.Field(i).String() + break default: // Ignore any other tags } }