Skip to content

Commit

Permalink
PR fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
cdworak committed Oct 4, 2018
1 parent ae87424 commit 5ec35ee
Show file tree
Hide file tree
Showing 17 changed files with 1,138 additions and 612 deletions.
1 change: 1 addition & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func NewClient(config *Config) *Client {
c.AssetService = &AssetService{client: c}
c.AnalyticsService = &AnalyticsService{client: c}
c.EntityService = &EntityService{client: c}
c.EntityService.RegisterDefaultEntities()
return c
}

Expand Down
21 changes: 21 additions & 0 deletions customfield.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,31 @@ func (v *VideoGallery) CustomFieldTag() string {
return CUSTOMFIELDTYPE_VIDEO
}

// HoursCustom is the Hours custom field format used by locations API
// Entities API uses the Hours struct in location_entities.go (profile and custom hours are defined the same way for entities)
type HoursCustom struct {
AdditionalText string `json:"additionalHoursText,omitempty"`
Hours string `json:"hours,omitempty"`
HolidayHours []LocationHolidayHours `json:"holidayHours,omitempty"`
}

func (h HoursCustom) CustomFieldTag() string {
return CUSTOMFIELDTYPE_HOURS
}

func (h Hours) CustomFieldTag() string {
return CUSTOMFIELDTYPE_HOURS
}

// TODO: This is the old structure of daily times. Figure out a better way of naming
type DailyTimesCustom struct {
DailyTimes string `json:"dailyTimes,omitempty"`
}

func (d DailyTimesCustom) CustomFieldTag() string {
return CUSTOMFIELDTYPE_DAILYTIMES
}

type DailyTimes struct {
Sunday string `json:"sunday,omitempty"`
Monday string `json:"monday,omitempty"`
Expand Down
3 changes: 2 additions & 1 deletion customfield_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st
return parsed, nil
}

func validateCustomFieldsKeys(cfs map[string]interface{}) error {
// validateLocationCustomFieldsKeys can be used with Location API to validate custom fields
func validateLocationCustomFieldsKeys(cfs map[string]interface{}) error {
for k, _ := range cfs {
if !customFieldKeyRegex.MatchString(k) {
return errors.New(fmt.Sprintf("custom fields must be specified by their id, not name: %s", k))
Expand Down
42 changes: 36 additions & 6 deletions customfield_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ var (
"clickthroughUrl": "https://yext.com/event",
"url": "https://mktgcdn.com/awesome.jpg",
}
hoursRaw = map[string]interface{}{
// hoursRawForEntites is the format used by entities API
hoursRawForEntity = map[string]interface{}{
"additionalHoursText": "This is an example of extra hours info",
"monday": []map[string]interface{}{
map[string]interface{}{
Expand Down Expand Up @@ -105,15 +106,29 @@ var (
},
map[string]interface{}{
"date": "2016-05-31",
"hours": []*Times{
&Times{
Start: "9:00",
End: "17:00",
"hours": []interface{}{
map[string]interface{}{
"start": "9:00",
"end": "17:00",
},
},
},
},
}
hoursRawForLocation = map[string]interface{}{
"additionalHoursText": "This is an example of extra hours info",
"hours": "1:9:00:17:00,3:15:00:12:00,3:5:00:11:00,4:9:00:17:00,5:0:00:0:00,6:9:00:17:00,7:9:00:17:00",
"holidayHours": []interface{}{
map[string]interface{}{
"date": "2016-05-30",
"hours": "",
},
map[string]interface{}{
"date": "2016-05-31",
"hours": "9:00:17:00",
},
},
}
videoRaw = map[string]interface{}{
"description": "An example caption for a video",
"url": "http://www.youtube.com/watch?v=M80FTIcEgZM",
Expand Down Expand Up @@ -159,7 +174,22 @@ var (
Url: "http://www.youtube.com/watch?v=M80FTIcEgZM",
Description: "An example caption for a video",
}},
customFieldParseTest{"HOURS", hoursRaw, Hours{
// TODO: re-enable
// customFieldParseTest{"HOURS", hoursRawForLocation, HoursCustom{
// AdditionalText: "This is an example of extra hours info",
// Hours: "1:9:00:17:00,3:15:00:12:00,3:5:00:11:00,4:9:00:17:00,5:0:00:0:00,6:9:00:17:00,7:9:00:17:00",
// HolidayHours: []LocationHolidayHours{
// LocationHolidayHours{
// Date: "2016-05-30",
// Hours: "",
// },
// LocationHolidayHours{
// Date: "2016-05-31",
// Hours: "9:00:17:00",
// },
// },
// }},
customFieldParseTest{"HOURS", hoursRawForEntity, Hours{
Monday: []*Times{
&Times{Start: "9:00", End: "17:00"},
},
Expand Down
21 changes: 20 additions & 1 deletion entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,24 @@ type EntityMeta struct {
CategoryIds *[]string `json:"categoryIds,omitempty"`
Language *string `json:"language,omitempty"`
CountryCode *string `json:"countryCode,omitempty"`
nilIsEmpty bool
// TODO: See if we still need and implement
nilIsEmpty bool
}

type BaseEntity struct {
Meta *EntityMeta `json:"meta,omitempty"`
}

func (b *BaseEntity) GetEntityId() string {
if b.Meta != nil && b.Meta.Id != nil {
return *b.Meta.Id
}
return ""
}

func (b *BaseEntity) GetEntityType() EntityType {
if b.Meta != nil {
return b.Meta.EntityType
}
return ""
}
47 changes: 19 additions & 28 deletions entity_service.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package yext

import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"net/url"
Expand Down Expand Up @@ -33,39 +31,29 @@ func (e *EntityService) RegisterDefaultEntities() {
e.RegisterEntity(ENTITYTYPE_EVENT, &Event{})
}

func (e *EntityService) RegisterEntity(entityType EntityType, entity interface{}) {
e.registry.Register(string(entityType), entity)
func (e *EntityService) RegisterEntity(t EntityType, entity interface{}) {
e.registry.Register(string(t), entity)
}

func (e *EntityService) LookupEntity(entityType EntityType) (interface{}, error) {
return e.registry.Lookup(string(entityType))
func (e *EntityService) CreateEntity(t EntityType) (interface{}, error) {
return e.registry.Create(string(t))
}

func GetBytes(key interface{}) ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(key)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

func (e *EntityService) toEntityTypes(entityInterfaces []interface{}) ([]Entity, error) {
var entities = []Entity{}
for _, entityInterface := range entityInterfaces {
func (e *EntityService) toEntityTypes(entities []interface{}) ([]Entity, error) {
var types = []Entity{}
for _, entityInterface := range entities {
entity, err := e.toEntityType(entityInterface)
if err != nil {
return nil, err
}
entities = append(entities, entity)
types = append(types, entity)
}
return entities, nil
return types, nil
}

func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error) {
func (e *EntityService) toEntityType(entity interface{}) (Entity, error) {
// Determine Entity Type
var entityValsByKey = entityInterface.(map[string]interface{})
var entityValsByKey = entity.(map[string]interface{})
meta, ok := entityValsByKey["meta"]
if !ok {
return nil, fmt.Errorf("Unable to find meta attribute in %v", entityValsByKey)
Expand All @@ -77,22 +65,22 @@ func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error
return nil, fmt.Errorf("Unable to find entityType attribute in %v", metaByKey)
}

entityObj, err := e.LookupEntity(EntityType(entityType.(string)))
// TODO: Re-examine what happens when we get an error here. Do we want to procede with a generic type?
entityObj, err := e.CreateEntity(EntityType(entityType.(string)))
if err != nil {
return nil, err
}

// Convert into struct of Entity Type
entityJSON, err := json.Marshal(entityValsByKey)
if err != nil {
return nil, fmt.Errorf("Error marshaling entity to JSON: %s", err)
return nil, fmt.Errorf("Marshaling entity to JSON: %s", err)
}

err = json.Unmarshal(entityJSON, &entityObj)
if err != nil {
return nil, fmt.Errorf("Error unmarshaling entity JSON: %s", err)
return nil, fmt.Errorf("Unmarshaling entity JSON: %s", err)
}

return entityObj.(Entity), nil
}

Expand Down Expand Up @@ -185,13 +173,16 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error
}

func (e *EntityService) Get(id string) (Entity, *Response, error) {
var v interface{}
var v map[string]interface{}
r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityPath, id), &v)
if err != nil {
return nil, r, err
}

entity, err := e.toEntityType(v)
if err != nil {
return nil, r, err
}

// TODO: nil is emtpy
//v.nilIsEmpty = true
Expand Down
45 changes: 11 additions & 34 deletions entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import (
"encoding/json"
"reflect"
"testing"

"github.com/mohae/deepcopy"
)

// Note to self: fields with json tag HAVE to be exported
// See: https://stackoverflow.com/questions/11126793/json-and-dealing-with-unexported-fields
type CustomLocationEntity struct {
Location
LocationEntity
CFHours *Hours `json:"cf_Hours,omitempty"`
CFUrl *string `json:"cf_Url,omitempty"` // TODO: do we want to continue to use these types or just the underlying type?
CFDailyTimes *DailyTimes `json:"cf_DailyTimes,omitempty"`
Expand All @@ -25,27 +23,6 @@ type CustomLocationEntity struct {
CFMultiOption *[]string `json:"cf_MultiOption,omitempty"`
}

func (l *CustomLocationEntity) EntityId() string {
return ""
}

func (l *CustomLocationEntity) Type() EntityType {
return ENTITYTYPE_LOCATION
}

func (l *CustomLocationEntity) PathName() string {
return locationsPath
}

func (l *CustomLocationEntity) Copy() Entity {
return deepcopy.Copy(l).(*CustomLocationEntity)
}

func (l *CustomLocationEntity) String() string {
b, _ := json.Marshal(l)
return string(b)
}

func entityToJSONString(entity Entity) (error, string) {
buf, err := json.Marshal(entity)
if err != nil {
Expand All @@ -63,14 +40,14 @@ func TestEntityJSONSerialization(t *testing.T) {

tests := []test{
{&CustomLocationEntity{}, `{}`},
{&CustomLocationEntity{Location: Location{Address: &Address{City: nil}}}, `{"address":{}}`}, // TODO: verify this is correct
{&CustomLocationEntity{Location: Location{Address: &Address{City: String("")}}}, `{"address":{"city":""}}`},
{&CustomLocationEntity{Location: Location{Languages: nil}}, `{}`},
{&CustomLocationEntity{Location: Location{Languages: nil}}, `{}`},
{&CustomLocationEntity{Location: Location{Languages: &[]string{}}}, `{"languages":[]}`},
{&CustomLocationEntity{Location: Location{Languages: &[]string{"English"}}}, `{"languages":["English"]}`},
{&CustomLocationEntity{Location: Location{Hours: nil}}, `{}`},
{&CustomLocationEntity{Location: Location{Hours: &Hours{}}}, `{"hours":{}}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: nil}}}, `{"address":{}}`}, // TODO: verify this is correct
{&CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: String("")}}}, `{"address":{"city":""}}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Languages: nil}}, `{}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Languages: nil}}, `{}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{}}}, `{"languages":[]}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{"English"}}}, `{"languages":["English"]}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Hours: nil}}, `{}`},
{&CustomLocationEntity{LocationEntity: LocationEntity{Hours: &Hours{}}}, `{"hours":{}}`},
{&CustomLocationEntity{CFUrl: String("")}, `{"cf_Url":""}`},
{&CustomLocationEntity{CFUrl: nil}, `{}`},
{&CustomLocationEntity{CFTextList: &[]string{}}, `{"cf_TextList":[]}`},
Expand All @@ -94,8 +71,8 @@ func TestEntityJSONDeserialization(t *testing.T) {

tests := []test{
{`{}`, &CustomLocationEntity{}},
{`{"emails": []}`, &CustomLocationEntity{Location: Location{Emails: Strings([]string{})}}},
{`{"emails": ["[email protected]", "[email protected]"]}`, &CustomLocationEntity{Location: Location{Emails: Strings([]string{"[email protected]", "[email protected]"})}}},
{`{"emails": []}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{})}}},
{`{"emails": ["[email protected]", "[email protected]"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{"[email protected]", "[email protected]"})}}},
{`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CFUrl: String("www.yext.com")}},
{`{"cf_TextList": ["a", "b", "c"]}`, &CustomLocationEntity{CFTextList: Strings([]string{"a", "b", "c"})}},
}
Expand Down
22 changes: 5 additions & 17 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,12 @@ import (

const ENTITYTYPE_EVENT EntityType = "EVENT"

// TODO: "Event" conflicts with the Event struct in list.go, but should consider better name
type EventEntity struct {
EntityMeta *EntityMeta `json:"meta,omitempty"`
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
EntityType EntityType `json:"entityType,omitempty"`
}

func (e *EventEntity) GetEntityId() string {
if e.Id != nil {
return *e.Id
}
return ""
}

func (e *EventEntity) GetEntityType() EntityType {
return ENTITYTYPE_EVENT
BaseEntity
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
EntityType EntityType `json:"entityType,omitempty"`
}

func (e *EventEntity) String() string {
Expand Down
2 changes: 1 addition & 1 deletion language_profile_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (l *LanguageProfileService) Upsert(y *LanguageProfile, languageCode string)
}
delete(asMap, "id")

if err := validateCustomFieldsKeys(y.CustomFields); err != nil {
if err := validateLocationCustomFieldsKeys(y.CustomFields); err != nil {
return nil, err
}
r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s/%s", locationsPath, id, profilesPath, languageCode), asMap, nil)
Expand Down
Loading

0 comments on commit 5ec35ee

Please sign in to comment.