From 02136984b79bb043992c5cd06905db1c82e0a7cf Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 18 Jul 2018 12:52:05 -0400 Subject: [PATCH 001/285] List entities works --- client.go | 4 +- entity.go | 62 ++++++++++ entity_diff.go | 172 +++++++++++++++++++++++++++ entity_service.go | 294 ++++++++++++++++++++++++++++++++++++++++++++++ event.go | 36 ++++++ location.go | 28 +++-- 6 files changed, 588 insertions(+), 8 deletions(-) create mode 100644 entity.go create mode 100644 entity_diff.go create mode 100644 entity_service.go create mode 100644 event.go diff --git a/client.go b/client.go index 2162466..9993134 100644 --- a/client.go +++ b/client.go @@ -36,6 +36,7 @@ type Client struct { LanguageProfileService *LanguageProfileService AssetService *AssetService AnalyticsService *AnalyticsService + EntityService *EntityService } func NewClient(config *Config) *Client { @@ -51,6 +52,7 @@ func NewClient(config *Config) *Client { c.LanguageProfileService = &LanguageProfileService{client: c} c.AssetService = &AssetService{client: c} c.AnalyticsService = &AnalyticsService{client: c} + c.EntityService = &EntityService{client: c, registry: YextEntityRegistry} return c } @@ -349,4 +351,4 @@ func Must(err error) { if err != nil { panic(err) } -} \ No newline at end of file +} diff --git a/entity.go b/entity.go new file mode 100644 index 0000000..2f7e3b3 --- /dev/null +++ b/entity.go @@ -0,0 +1,62 @@ +package yext + +import ( + "encoding/json" + "fmt" +) + +type EntityType string + +type Entity interface { + EntityId() string // Would this be necessary if it's always in the Core? + Type() EntityType + PathName() string +} + +type EntityMeta struct { + Id *string `json:"id,omitempty"` + AccountId *string `json:"accountId,omitempty"` + EntityType *EntityType `json:"locationType,omitempty"` + FolderId *string `json:"folderId,omitempty"` + LabelIds *UnorderedStrings `json:"labelIds,omitempty"` + CategoryIds *[]string `json:"categoryIds,omitempty"` + Language *string `json:"language,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` +} + +type EntityList []Entity + +func (e *EntityList) UnmarshalJSON(b []byte) error { + var raw []json.RawMessage + err := json.Unmarshal(b, &raw) + if err != nil { + return err + } + + for _, r := range raw { + var obj map[string]interface{} + err := json.Unmarshal(r, &obj) + if err != nil { + return err + } + + var entityType string + if t, ok := obj["entityType"].(string); ok { + entityType = t + } + + typedEntity, ok := YextEntityRegistry[EntityType(entityType)] + if !ok { + return fmt.Errorf("Entity type %s is not in the registry", entityType) + } + + err = json.Unmarshal(r, typedEntity) + if err != nil { + return err + } + *e = append(*e, typedEntity) + + } + return nil + +} diff --git a/entity_diff.go b/entity_diff.go new file mode 100644 index 0000000..a1871f0 --- /dev/null +++ b/entity_diff.go @@ -0,0 +1,172 @@ +package yext + +// +// import ( +// "reflect" +// ) +// +// type Comparable interface { +// Equal(Comparable) bool +// } +// +// // Diff calculates the differences between a base Location (a) and a proposed set of changes +// // represented by a second Location (b). The diffing logic will ignore fields in the proposed +// // Location that aren't set (nil). This characteristic makes the function ideal for +// // calculting the minimal PUT required to bring an object "up-to-date" via the API. +// // +// // Example: +// // var ( +// // // Typically, this would come from an authoritative source, like the API +// // base = Location{Name: "Yext, Inc.", Address1: "123 Mulberry St"} +// // proposed = Location{Name: "Yext, Inc.", Address1: "456 Washington St"} +// // ) +// // +// // delta, isDiff := base.Diff(proposed) +// // // isDiff -> true +// // // delta -> &Location{Address1: "456 Washington St"} +// // +// // delta, isDiff = base.Diff(base) +// // // isDiff -> false +// // // delta -> nil +// func (y Entity) Diff(b Entity) (d Entity, diff bool) { +// d = &Location{Id: String(y.GetId())} +// +// var ( +// aV, bV = reflect.ValueOf(y), reflect.ValueOf(b).Elem() +// aT = reflect.TypeOf(y) +// numA = aV.NumField() +// ) +// +// for i := 0; i < numA; i++ { +// var ( +// nameA = aT.Field(i).Name +// valA = aV.Field(i) +// valB = bV.Field(i) +// ) +// +// if !valB.CanSet() { +// continue +// } +// +// if valB.IsNil() || nameA == "Id" { +// continue +// } +// +// if nameA == "Hours" { +// if !valA.IsNil() && !valB.IsNil() && HoursAreEquivalent(getUnderlyingValue(valA.Interface()).(string), getUnderlyingValue(valB.Interface()).(string)) { +// continue +// } +// } +// +// // if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { +// // continue +// // } +// +// aI, bI := valA.Interface(), valB.Interface() +// +// comparableA, aOk := aI.(Comparable) +// comparableB, bOk := bI.(Comparable) +// +// if aOk && bOk { +// if !comparableA.Equal(comparableB) { +// diff = true +// reflect.ValueOf(d).Elem().FieldByName(nameA).Set(valB) +// } +// } else if !reflect.DeepEqual(aI, bI) { +// if nameA == "CustomFields" { +// d.CustomFields = make(map[string]interface{}) +// for field, value := range b.CustomFields { +// value = getUnderlyingValue(value) +// if aValue, ok := y.CustomFields[field]; ok { +// aValue = getUnderlyingValue(aValue) +// var valueDiff bool +// if v, ok := aValue.(Comparable); ok { +// valueDiff = !v.Equal(value.(Comparable)) +// } else if !reflect.DeepEqual(aValue, value) { +// valueDiff = true +// } +// if valueDiff { +// diff = true +// d.CustomFields[field] = value +// } +// } else if !(isZeroValue(reflect.ValueOf(value), b.nilIsEmpty) && y.nilIsEmpty) { +// d.CustomFields[field] = value +// diff = true +// } +// } +// } else { +// diff = true +// reflect.ValueOf(d).Elem().FieldByName(nameA).Set(valB) +// } +// } +// } +// if diff == false { +// // ensure d remains nil if nothing has changed +// d = nil +// } +// +// return d, diff +// } +// +// // getUnderlyingValue unwraps a pointer/interface to get the underlying value. +// // If the value is already unwrapped, it returns it as is. +// func getUnderlyingValue(v interface{}) interface{} { +// if v == nil { +// return nil +// } +// +// rv := reflect.ValueOf(v) +// +// switch rv.Kind() { +// case reflect.Ptr, reflect.Interface: +// if rv.IsNil() { +// return nil +// } +// rv = rv.Elem() +// } +// +// return rv.Interface() +// } +// +// func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { +// if !v.IsValid() { +// return true +// } +// +// switch v.Kind() { +// case reflect.Slice, reflect.Map: +// return v.Len() == 0 +// case reflect.Bool: +// return v.Bool() == false +// case reflect.String: +// return v.String() == "" +// case reflect.Float64: +// return v.Float() == 0.0 +// case reflect.Ptr, reflect.Interface: +// if v.IsNil() && !interpretNilAsZeroValue { +// return false +// } +// return isZeroValue(v.Elem(), interpretNilAsZeroValue) +// case reflect.Struct: +// for i, n := 0, v.NumField(); i < n; i++ { +// if !isZeroValue(v.Field(i), interpretNilAsZeroValue) { +// return false +// } +// } +// return true +// default: +// return v.IsNil() && interpretNilAsZeroValue +// } +// } +// +// var closedHoursEquivalents = map[string]struct{}{ +// "": struct{}{}, +// HoursClosedAllWeek: struct{}{}, +// } +// +// func HoursAreEquivalent(a, b string) bool { +// _, aIsClosed := closedHoursEquivalents[a] +// _, bIsClosed := closedHoursEquivalents[b] +// +// return a == b || aIsClosed && bIsClosed +// } diff --git a/entity_service.go b/entity_service.go new file mode 100644 index 0000000..45e86b8 --- /dev/null +++ b/entity_service.go @@ -0,0 +1,294 @@ +package yext + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "os" +) + +const entityPath = "entities" + +type EntityRegistry map[EntityType]Entity + +var YextEntityRegistry = EntityRegistry{ + ENTITYTYPE_EVENT: &EventEntity{}, + ENTITYTYPE_LOCATION: &Location{}, +} + +type EntityService struct { + client *Client + registry map[EntityType]Entity +} + +type EntityListOptions struct { + ListOptions + SearchID string + ResolvePlaceholders bool + EntityTypes []EntityType +} + +type EntityListResponse struct { + Count int `json:"count"` + Entities EntityList `json:"entities"` + NextPageToken string `json:"nextPageToken"` +} + +func (e *EntityService) RegisterEntity(entityType EntityType, entity Entity) { + e.registry[entityType] = entity +} + +func (e *EntityService) LookupEntity(entityType EntityType) (Entity, error) { + entity, ok := e.registry[entityType] + if !ok { + return nil, fmt.Errorf("Unable to find entity type %s in entity registry %v", entityType, e.registry) + } + return entity, nil +} + +func (e *EntityService) PathName(entityType EntityType) (string, error) { + entity, err := e.LookupEntity(entityType) + if err != nil { + return "", err + } + return entity.PathName(), nil +} + +func (e *EntityService) ListTest() ([]Entity, error) { + jsonFile, err := os.Open("entitiesList.json") + if err != nil { + return nil, err + } + defer jsonFile.Close() + + byteValue, err := ioutil.ReadAll(jsonFile) + if err != nil { + return nil, err + } + var resp EntityListResponse + err = json.Unmarshal(byteValue, &resp) + if err != nil { + return nil, err + } + return resp.Entities, nil +} + +func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { + var entities []Entity + if opts == nil { + opts = &EntityListOptions{} + } + opts.ListOptions = ListOptions{Limit: LocationListMaxLimit} + var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { + opts.ListOptions = *listOptions + resp, _, err := e.List(opts) + if err != nil { + return "", err + } + // for _, entity := range resp.Entities { + // entities = append(entities, entity) + // } + return resp.NextPageToken, err + } + + if err := tokenListHelper(lg, &opts.ListOptions); err != nil { + return nil, err + } else { + return entities, nil + } +} + +func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Response, error) { + var ( + requrl = entityPath + err error + ) + + if opts != nil { + requrl, err = addEntityListOptions(requrl, opts) + if err != nil { + return nil, nil, err + } + } + + if opts != nil { + requrl, err = addListOptions(requrl, &opts.ListOptions) + if err != nil { + return nil, nil, err + } + } + + v := &EntityListResponse{} + r, err := e.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + + // if _, err := e.HydrateLocations(v.Locations); err != nil { + // return nil, r, err + // } + + // for _, l := range v.Entities { + // l.nilIsEmpty = true + // } + + return v, r, nil +} + +func (e *EntityService) ListAllOfType(opts *EntityListOptions, entityType EntityType) ([]Entity, error) { + var entities []Entity + if opts == nil { + opts = &EntityListOptions{} + } + opts.ListOptions = ListOptions{Limit: LocationListMaxLimit} + var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { + opts.ListOptions = *listOptions + resp, _, err := e.ListOfType(opts, entityType) + if err != nil { + return "", err + } + // for _, entity := range resp.Entities { + // entities = append(entities, entity) + // } + return resp.NextPageToken, err + } + + if err := tokenListHelper(lg, &opts.ListOptions); err != nil { + return nil, err + } else { + return entities, nil + } +} + +func (e *EntityService) ListOfType(opts *EntityListOptions, entityType EntityType) (*EntityListResponse, *Response, error) { + var ( + requrl string + err error + ) + + pathName, err := e.PathName(entityType) + if err != nil { + return nil, nil, err + } + requrl = pathName + + if opts != nil { + requrl, err = addEntityListOptions(requrl, opts) + if err != nil { + return nil, nil, err + } + } + + if opts != nil { + requrl, err = addListOptions(requrl, &opts.ListOptions) + if err != nil { + return nil, nil, err + } + } + + v := &EntityListResponse{} + r, err := e.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + + // if _, err := e.HydrateLocations(v.Locations); err != nil { + // return nil, r, err + // } + + // for _, l := range v.Entities { + // l.nilIsEmpty = true + // } + + return v, r, nil +} + +func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error) { + if opts == nil { + return requrl, nil + } + + u, err := url.Parse(requrl) + if err != nil { + return "", err + } + + q := u.Query() + if opts.SearchID != "" { + q.Add("searchId", opts.SearchID) + } + if opts.ResolvePlaceholders { + q.Add("resolvePlaceholders", "true") + } + if opts.EntityTypes != nil && len(opts.EntityTypes) > 0 { + // TODO: does this want a list? + } + u.RawQuery = q.Encode() + + return u.String(), nil +} + +func (e *EntityService) Get(id string, entityType EntityType) (Entity, *Response, error) { + entity, err := e.LookupEntity(entityType) + if err != nil { + return nil, nil, err + } + r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entity.PathName(), id), entity) + if err != nil { + return nil, r, err + } + + // if _, err := HydrateLocation(&v, l.CustomFields); err != nil { + // return nil, r, err + // } + + //v.nilIsEmpty = true + + return entity, r, nil +} + +func (e *EntityService) Create(y Entity) (*Response, error) { + // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { + // return nil, err + // } + r, err := e.client.DoRequestJSON("POST", fmt.Sprintf("%s", y.PathName()), y, nil) + if err != nil { + return r, err + } + + return r, nil +} + +func (e *EntityService) Edit(y Entity) (*Response, error) { + // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { + // return nil, err + // } + r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", y.PathName(), y.EntityId()), y, nil) + if err != nil { + return r, err + } + + return r, nil +} + +// func (e *EntityService) ListEntitiesBySearchId(searchId string, entityType EntityType) ([]Entity, error) { +// //var locations []*Location +// var llo = &LocationListOptions{SearchID: searchId} +// llo.ListOptions = ListOptions{Limit: LocationListMaxLimit} +// var lg tokenListRetriever = func(opts *ListOptions) (string, error) { +// llo.ListOptions = *opts +// llr, _, err := l.List(llo) +// if err != nil { +// return "", err +// } +// entities = append(entities, llr.Locations...) +// return llr.NextPageToken, err +// } +// +// if err := tokenListHelper(lg, &llo.ListOptions); err != nil { +// return nil, err +// } else { +// return entities, nil +// } +// } diff --git a/event.go b/event.go new file mode 100644 index 0000000..96ca503 --- /dev/null +++ b/event.go @@ -0,0 +1,36 @@ +package yext + +import "encoding/json" + +const ( + ENTITYTYPE_EVENT EntityType = "EVENT" + EntityPathNameEvents = "events" // TODO: rename +) + +type EventEntity struct { // TODO: rename + //EntityMeta + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + EntityType EntityType `json:"entityType,omitempty"` +} + +func (e *EventEntity) EntityId() string { + if e.Id != nil { + return *e.Id + } + return "" +} + +func (e *EventEntity) Type() EntityType { + return ENTITYTYPE_EVENT +} + +func (e *EventEntity) PathName() string { + return EntityPathNameEvents +} + +func (e *EventEntity) String() string { + b, _ := json.Marshal(e) + return string(b) +} diff --git a/location.go b/location.go index 43a25bc..0b22832 100644 --- a/location.go +++ b/location.go @@ -10,13 +10,15 @@ import ( "fmt" ) +const ENTITYTYPE_LOCATION EntityType = "LOCATION" + // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { // Admin Id *string `json:"id,omitempty"` AccountId *string `json:"accountId,omitempty"` - LocationType *string `json:"locationType,omitempty"` + EntityType *string `json:"entityType,omitempty"` FolderId *string `json:"folderId,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` CategoryIds *[]string `json:"categoryIds,omitempty"` @@ -160,6 +162,18 @@ type Location struct { */ } +func (y *Location) EntityId() string { + return y.GetId() +} + +func (y *Location) Type() EntityType { + return ENTITYTYPE_LOCATION +} + +func (y *Location) PathName() string { + return locationsPath +} + func (y Location) GetId() string { if y.Id != nil { return *y.Id @@ -167,12 +181,12 @@ func (y Location) GetId() string { return "" } -func (y Location) GetLocationType() string { - if y.LocationType != nil { - return *y.LocationType - } - return "" -} +// func (y Location) GetLocationType() string { +// if y.LocationType != nil { +// return *y.LocationType +// } +// return "" +// } func (y Location) GetName() string { if y.Name != nil { From b5059b2cf1e506e7c94da2fa0ed8702befa27ab1 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 18 Jul 2018 13:48:23 -0400 Subject: [PATCH 002/285] List entities works with custom entity types --- entity.go | 42 ----------- entity_diff.go | 172 ---------------------------------------------- entity_service.go | 29 ++++++-- 3 files changed, 25 insertions(+), 218 deletions(-) delete mode 100644 entity_diff.go diff --git a/entity.go b/entity.go index 2f7e3b3..d3ed949 100644 --- a/entity.go +++ b/entity.go @@ -1,10 +1,5 @@ package yext -import ( - "encoding/json" - "fmt" -) - type EntityType string type Entity interface { @@ -23,40 +18,3 @@ type EntityMeta struct { Language *string `json:"language,omitempty"` CountryCode *string `json:"countryCode,omitempty"` } - -type EntityList []Entity - -func (e *EntityList) UnmarshalJSON(b []byte) error { - var raw []json.RawMessage - err := json.Unmarshal(b, &raw) - if err != nil { - return err - } - - for _, r := range raw { - var obj map[string]interface{} - err := json.Unmarshal(r, &obj) - if err != nil { - return err - } - - var entityType string - if t, ok := obj["entityType"].(string); ok { - entityType = t - } - - typedEntity, ok := YextEntityRegistry[EntityType(entityType)] - if !ok { - return fmt.Errorf("Entity type %s is not in the registry", entityType) - } - - err = json.Unmarshal(r, typedEntity) - if err != nil { - return err - } - *e = append(*e, typedEntity) - - } - return nil - -} diff --git a/entity_diff.go b/entity_diff.go deleted file mode 100644 index a1871f0..0000000 --- a/entity_diff.go +++ /dev/null @@ -1,172 +0,0 @@ -package yext - -// -// import ( -// "reflect" -// ) -// -// type Comparable interface { -// Equal(Comparable) bool -// } -// -// // Diff calculates the differences between a base Location (a) and a proposed set of changes -// // represented by a second Location (b). The diffing logic will ignore fields in the proposed -// // Location that aren't set (nil). This characteristic makes the function ideal for -// // calculting the minimal PUT required to bring an object "up-to-date" via the API. -// // -// // Example: -// // var ( -// // // Typically, this would come from an authoritative source, like the API -// // base = Location{Name: "Yext, Inc.", Address1: "123 Mulberry St"} -// // proposed = Location{Name: "Yext, Inc.", Address1: "456 Washington St"} -// // ) -// // -// // delta, isDiff := base.Diff(proposed) -// // // isDiff -> true -// // // delta -> &Location{Address1: "456 Washington St"} -// // -// // delta, isDiff = base.Diff(base) -// // // isDiff -> false -// // // delta -> nil -// func (y Entity) Diff(b Entity) (d Entity, diff bool) { -// d = &Location{Id: String(y.GetId())} -// -// var ( -// aV, bV = reflect.ValueOf(y), reflect.ValueOf(b).Elem() -// aT = reflect.TypeOf(y) -// numA = aV.NumField() -// ) -// -// for i := 0; i < numA; i++ { -// var ( -// nameA = aT.Field(i).Name -// valA = aV.Field(i) -// valB = bV.Field(i) -// ) -// -// if !valB.CanSet() { -// continue -// } -// -// if valB.IsNil() || nameA == "Id" { -// continue -// } -// -// if nameA == "Hours" { -// if !valA.IsNil() && !valB.IsNil() && HoursAreEquivalent(getUnderlyingValue(valA.Interface()).(string), getUnderlyingValue(valB.Interface()).(string)) { -// continue -// } -// } -// -// // if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { -// // continue -// // } -// -// aI, bI := valA.Interface(), valB.Interface() -// -// comparableA, aOk := aI.(Comparable) -// comparableB, bOk := bI.(Comparable) -// -// if aOk && bOk { -// if !comparableA.Equal(comparableB) { -// diff = true -// reflect.ValueOf(d).Elem().FieldByName(nameA).Set(valB) -// } -// } else if !reflect.DeepEqual(aI, bI) { -// if nameA == "CustomFields" { -// d.CustomFields = make(map[string]interface{}) -// for field, value := range b.CustomFields { -// value = getUnderlyingValue(value) -// if aValue, ok := y.CustomFields[field]; ok { -// aValue = getUnderlyingValue(aValue) -// var valueDiff bool -// if v, ok := aValue.(Comparable); ok { -// valueDiff = !v.Equal(value.(Comparable)) -// } else if !reflect.DeepEqual(aValue, value) { -// valueDiff = true -// } -// if valueDiff { -// diff = true -// d.CustomFields[field] = value -// } -// } else if !(isZeroValue(reflect.ValueOf(value), b.nilIsEmpty) && y.nilIsEmpty) { -// d.CustomFields[field] = value -// diff = true -// } -// } -// } else { -// diff = true -// reflect.ValueOf(d).Elem().FieldByName(nameA).Set(valB) -// } -// } -// } -// if diff == false { -// // ensure d remains nil if nothing has changed -// d = nil -// } -// -// return d, diff -// } -// -// // getUnderlyingValue unwraps a pointer/interface to get the underlying value. -// // If the value is already unwrapped, it returns it as is. -// func getUnderlyingValue(v interface{}) interface{} { -// if v == nil { -// return nil -// } -// -// rv := reflect.ValueOf(v) -// -// switch rv.Kind() { -// case reflect.Ptr, reflect.Interface: -// if rv.IsNil() { -// return nil -// } -// rv = rv.Elem() -// } -// -// return rv.Interface() -// } -// -// func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { -// if !v.IsValid() { -// return true -// } -// -// switch v.Kind() { -// case reflect.Slice, reflect.Map: -// return v.Len() == 0 -// case reflect.Bool: -// return v.Bool() == false -// case reflect.String: -// return v.String() == "" -// case reflect.Float64: -// return v.Float() == 0.0 -// case reflect.Ptr, reflect.Interface: -// if v.IsNil() && !interpretNilAsZeroValue { -// return false -// } -// return isZeroValue(v.Elem(), interpretNilAsZeroValue) -// case reflect.Struct: -// for i, n := 0, v.NumField(); i < n; i++ { -// if !isZeroValue(v.Field(i), interpretNilAsZeroValue) { -// return false -// } -// } -// return true -// default: -// return v.IsNil() && interpretNilAsZeroValue -// } -// } -// -// var closedHoursEquivalents = map[string]struct{}{ -// "": struct{}{}, -// HoursClosedAllWeek: struct{}{}, -// } -// -// func HoursAreEquivalent(a, b string) bool { -// _, aIsClosed := closedHoursEquivalents[a] -// _, bIsClosed := closedHoursEquivalents[b] -// -// return a == b || aIsClosed && bIsClosed -// } diff --git a/entity_service.go b/entity_service.go index 45e86b8..a32d2f5 100644 --- a/entity_service.go +++ b/entity_service.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "net/url" "os" + + "github.com/mitchellh/mapstructure" ) const entityPath = "entities" @@ -30,9 +32,9 @@ type EntityListOptions struct { } type EntityListResponse struct { - Count int `json:"count"` - Entities EntityList `json:"entities"` - NextPageToken string `json:"nextPageToken"` + Count int `json:"count"` + Entities []interface{} `json:"entities"` + NextPageToken string `json:"nextPageToken"` } func (e *EntityService) RegisterEntity(entityType EntityType, entity Entity) { @@ -71,7 +73,26 @@ func (e *EntityService) ListTest() ([]Entity, error) { if err != nil { return nil, err } - return resp.Entities, nil + + var entities = []Entity{} + for _, entity := range resp.Entities { + var entityValsByKey = entity.(map[string]interface{}) + entityType, ok := entityValsByKey["entityType"] + if !ok { + return nil, fmt.Errorf("Unable to find enityType attribute in %v", entityValsByKey) + } + + entityObj, err := e.LookupEntity(EntityType(entityType.(string))) + if err != nil { + return nil, err + } + err = mapstructure.Decode(entityValsByKey, entityObj) + if err != nil { + return nil, fmt.Errorf("Error decoding entity: %s", err) + } + entities = append(entities, entityObj) + } + return entities, nil } func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { From 33e382ed0de4833a6bbb10d936e7f34bfd9bc3c4 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 18 Jul 2018 15:13:16 -0400 Subject: [PATCH 003/285] cleaning up --- entity_service.go | 107 ++++++++++++++++++++-------------------------- fake_response.go | 91 +++++++++++++++++++++++++++++++++++++++ location.go | 12 +++--- 3 files changed, 143 insertions(+), 67 deletions(-) create mode 100644 fake_response.go diff --git a/entity_service.go b/entity_service.go index a32d2f5..0f22d2b 100644 --- a/entity_service.go +++ b/entity_service.go @@ -1,11 +1,8 @@ package yext import ( - "encoding/json" "fmt" - "io/ioutil" "net/url" - "os" "github.com/mitchellh/mapstructure" ) @@ -57,42 +54,34 @@ func (e *EntityService) PathName(entityType EntityType) (string, error) { return entity.PathName(), nil } -func (e *EntityService) ListTest() ([]Entity, error) { - jsonFile, err := os.Open("entitiesList.json") - if err != nil { - return nil, err +func (e *EntityService) toEntityTypes(entityInterfaces []interface{}) ([]Entity, error) { + var entities = []Entity{} + for _, entityInterface := range entityInterfaces { + entity, err := e.toEntityType(entityInterface) + if err != nil { + return nil, err + } + entities = append(entities, entity) } - defer jsonFile.Close() + return entities, nil +} - byteValue, err := ioutil.ReadAll(jsonFile) - if err != nil { - return nil, err +func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error) { + var entityValsByKey = entityInterface.(map[string]interface{}) + entityType, ok := entityValsByKey["entityType"] + if !ok { + return nil, fmt.Errorf("Unable to find enityType attribute in %v", entityValsByKey) } - var resp EntityListResponse - err = json.Unmarshal(byteValue, &resp) + + entityObj, err := e.LookupEntity(EntityType(entityType.(string))) if err != nil { return nil, err } - - var entities = []Entity{} - for _, entity := range resp.Entities { - var entityValsByKey = entity.(map[string]interface{}) - entityType, ok := entityValsByKey["entityType"] - if !ok { - return nil, fmt.Errorf("Unable to find enityType attribute in %v", entityValsByKey) - } - - entityObj, err := e.LookupEntity(EntityType(entityType.(string))) - if err != nil { - return nil, err - } - err = mapstructure.Decode(entityValsByKey, entityObj) - if err != nil { - return nil, fmt.Errorf("Error decoding entity: %s", err) - } - entities = append(entities, entityObj) + err = mapstructure.Decode(entityValsByKey, entityObj) + if err != nil { + return nil, fmt.Errorf("Error decoding entity: %s", err) } - return entities, nil + return entityObj, nil } func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { @@ -107,9 +96,14 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { if err != nil { return "", err } - // for _, entity := range resp.Entities { - // entities = append(entities, entity) - // } + + typedEntities, err := e.toEntityTypes(resp.Entities) + if err != nil { + return "", err + } + for _, entity := range typedEntities { + entities = append(entities, entity) + } return resp.NextPageToken, err } @@ -140,12 +134,19 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res } } - v := &EntityListResponse{} - r, err := e.client.DoRequest("GET", requrl, v) + // TODO: use once we no longer have to fake the response + // v := &EntityListResponse{} + // r, err := e.client.DoRequest("GET", requrl, v) + // if err != nil { + // return nil, r, err + // } + + v, err := fakeEntityListResponse() if err != nil { - return nil, r, err + return nil, nil, err } + // TODO: handle hyrdation and nil is empty // if _, err := e.HydrateLocations(v.Locations); err != nil { // return nil, r, err // } @@ -154,9 +155,10 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res // l.nilIsEmpty = true // } - return v, r, nil + return v, nil, nil } +// TODO: This function is a stub func (e *EntityService) ListAllOfType(opts *EntityListOptions, entityType EntityType) ([]Entity, error) { var entities []Entity if opts == nil { @@ -214,6 +216,7 @@ func (e *EntityService) ListOfType(opts *EntityListOptions, entityType EntityTyp return nil, r, err } + // TODO: handle hydration and nil is empty // if _, err := e.HydrateLocations(v.Locations); err != nil { // return nil, r, err // } @@ -243,7 +246,7 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error q.Add("resolvePlaceholders", "true") } if opts.EntityTypes != nil && len(opts.EntityTypes) > 0 { - // TODO: does this want a list? + // TODO: add entity types } u.RawQuery = q.Encode() @@ -260,6 +263,7 @@ func (e *EntityService) Get(id string, entityType EntityType) (Entity, *Response return nil, r, err } + // TODO: handle hydration and nil is empty // if _, err := HydrateLocation(&v, l.CustomFields); err != nil { // return nil, r, err // } @@ -270,6 +274,7 @@ func (e *EntityService) Get(id string, entityType EntityType) (Entity, *Response } func (e *EntityService) Create(y Entity) (*Response, error) { + // TODO: custom field validation // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { // return nil, err // } @@ -282,6 +287,7 @@ func (e *EntityService) Create(y Entity) (*Response, error) { } func (e *EntityService) Edit(y Entity) (*Response, error) { + // TODO: custom field validation // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { // return nil, err // } @@ -292,24 +298,3 @@ func (e *EntityService) Edit(y Entity) (*Response, error) { return r, nil } - -// func (e *EntityService) ListEntitiesBySearchId(searchId string, entityType EntityType) ([]Entity, error) { -// //var locations []*Location -// var llo = &LocationListOptions{SearchID: searchId} -// llo.ListOptions = ListOptions{Limit: LocationListMaxLimit} -// var lg tokenListRetriever = func(opts *ListOptions) (string, error) { -// llo.ListOptions = *opts -// llr, _, err := l.List(llo) -// if err != nil { -// return "", err -// } -// entities = append(entities, llr.Locations...) -// return llr.NextPageToken, err -// } -// -// if err := tokenListHelper(lg, &llo.ListOptions); err != nil { -// return nil, err -// } else { -// return entities, nil -// } -// } diff --git a/fake_response.go b/fake_response.go new file mode 100644 index 0000000..510e86f --- /dev/null +++ b/fake_response.go @@ -0,0 +1,91 @@ +package yext + +import "encoding/json" + +// TODO: delete this file when the new endpoints become available + +var listResponse = `{ + "count": 4, + "entities": [ + { + "id": "US5523", + "uid": "0Za9W", + "timestamp": 1530543889575, + "accountId": "648517391220866580", + "locationName": "Safelite AutoGlass", + "address": "2124 W 2nd St", + "city": "Grand Island", + "state": "NE", + "zip": "68803", + "countryCode": "US", + "language": "en", + "suppressAddress": false, + "phone": "8888432798", + "isPhoneTracked": false, + "localPhone": "3083828689", + "alternatePhone": "8008002727", + "categoryIds": [ + "28", + "2040", + "26", + "27", + "588", + "524", + "1325579" + ], + "entityType": "LOCATION" + }, + { + "id": "Entity2", + "uid": "0Za9W", + "timestamp": 1530543889575, + "accountId": "648517391220866580", + "locationName": "Safelite AutoGlass", + "address": "2124 W 2nd St", + "city": "Grand Island", + "state": "NE", + "zip": "68803", + "countryCode": "US", + "language": "en", + "suppressAddress": false, + "phone": "8888432798", + "isPhoneTracked": false, + "localPhone": "3083828689", + "alternatePhone": "8008002727", + "categoryIds": [ + "28", + "2040", + "26", + "27", + "588", + "524", + "1325579" + ], + "entityType": "LOCATION" + }, + { + "id": "eventId", + "name": "event name", + "description": "event description", + "entityType": "EVENT" + }, + { + "id": "12345", + "firstName": "Catherine", + "nickname": "CTG", + "entityType": "CONSULTING_ENGINEER" + } + ] + } +` + +// Used to fake a JSON response for Enitites:List +func fakeEntityListResponse() (*EntityListResponse, error) { + byteValue := []byte(listResponse) + var resp *EntityListResponse + err := json.Unmarshal(byteValue, &resp) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/location.go b/location.go index 0b22832..3fc7c62 100644 --- a/location.go +++ b/location.go @@ -181,12 +181,12 @@ func (y Location) GetId() string { return "" } -// func (y Location) GetLocationType() string { -// if y.LocationType != nil { -// return *y.LocationType -// } -// return "" -// } +func (y Location) GetEntityType() string { + if y.EntityType != nil { + return *y.EntityType + } + return "" +} func (y Location) GetName() string { if y.Name != nil { From a9478dd3a706bdda3029aa18ced5a1f889f1da8f Mon Sep 17 00:00:00 2001 From: cdworak Date: Thu, 27 Sep 2018 14:52:11 -0500 Subject: [PATCH 004/285] got json unmarshal working --- client.go | 1 + customfield.go | 10 +- entitiestest/main.go | 55 ++++ entity.go | 1 + entity_service.go | 60 +++-- event.go | 10 +- fake_response.go | 91 ------- location.go | 194 ++++++++------ location_diff.go | 24 +- location_diff_test.go | 530 ++++++++++++++++++++++++--------------- location_service_test.go | 3 +- location_test.go | 27 +- 12 files changed, 593 insertions(+), 413 deletions(-) create mode 100644 entitiestest/main.go delete mode 100644 fake_response.go diff --git a/client.go b/client.go index 9993134..d2882a8 100644 --- a/client.go +++ b/client.go @@ -338,6 +338,7 @@ func addListOptions(requrl string, opts *ListOptions) (string, error) { q.Add("limit", strconv.Itoa(opts.Limit)) } if opts.PageToken != "" { + // TODO: this is not compatible with the old parameter name q.Add("pageToken", opts.PageToken) } else if opts.Offset != 0 { q.Add("offset", strconv.Itoa(opts.Offset)) diff --git a/customfield.go b/customfield.go index 32dc890..632fd6b 100644 --- a/customfield.go +++ b/customfield.go @@ -232,9 +232,17 @@ func (v *VideoGallery) CustomFieldTag() string { } type Hours struct { + // TODO: Check how AdditonalText and HolidayHours are represented AdditionalText string `json:"additionalHoursText,omitempty"` - Hours string `json:"hours,omitempty"` HolidayHours []HolidayHours `json:"holidayHours,omitempty"` + Hours string `json:"holidayHours,omitempty"` + // Monday []*DayHours `json:"monday,omitempty"` + // Tuesday []*DayHours `json:"tuesday,omitempty"` + // Wednesday []*DayHours `json:"wednesday,omitempty"` + // Thursday []*DayHours `json:"thursday,omitempty"` + // Friday []*DayHours `json:"friday,omitempty"` + // Saturday []*DayHours `json:"saturday,omitempty"` + // Sunday []*DayHours `json:"sunday,omitempty"` } func (h Hours) CustomFieldTag() string { diff --git a/entitiestest/main.go b/entitiestest/main.go new file mode 100644 index 0000000..b256acd --- /dev/null +++ b/entitiestest/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "log" + + "github.com/mohae/deepcopy" + yext "gopkg.in/yext/yext-go.v2" +) + +type MorganLocation struct { + yext.Location + CMultiText *string `json:"c_multiText"` +} + +//TODO: Revisit this...what if m.Location is nil? +func (m *MorganLocation) EntityId() string { + return m.Location.EntityId() +} + +func (m *MorganLocation) Type() yext.EntityType { + return m.Location.Type() +} + +func (m *MorganLocation) PathName() string { + return m.Location.PathName() +} + +func (m *MorganLocation) Copy() yext.Entity { + return deepcopy.Copy(m).(*MorganLocation) +} + +func main() { + client := yext.NewClient(yext.NewDefaultConfig().WithApiKey("e929153c956b051cea51ec289bfd2383")) + client.EntityService.RegisterEntity(yext.ENTITYTYPE_LOCATION, &MorganLocation{}) + entities, err := client.EntityService.ListAll(nil) + if err != nil { + log.Fatal(err) + } + + log.Printf("ListAll: Got %d entities", len(entities)) + for _, entity := range entities { + + morganEntity := entity.(*MorganLocation) + log.Printf("PROFILE description: %s", GetString(morganEntity.Description)) + log.Printf("CUSTOM multi: %s", GetString(morganEntity.CMultiText)) + } + +} + +func GetString(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/entity.go b/entity.go index d3ed949..df9df73 100644 --- a/entity.go +++ b/entity.go @@ -6,6 +6,7 @@ type Entity interface { EntityId() string // Would this be necessary if it's always in the Core? Type() EntityType PathName() string + Copy() Entity } type EntityMeta struct { diff --git a/entity_service.go b/entity_service.go index 0f22d2b..98c1194 100644 --- a/entity_service.go +++ b/entity_service.go @@ -1,10 +1,11 @@ package yext import ( + "bytes" + "encoding/gob" + "encoding/json" "fmt" "net/url" - - "github.com/mitchellh/mapstructure" ) const entityPath = "entities" @@ -29,9 +30,9 @@ type EntityListOptions struct { } type EntityListResponse struct { - Count int `json:"count"` - Entities []interface{} `json:"entities"` - NextPageToken string `json:"nextPageToken"` + Count int `json:"count"` + Entities []interface{} `json:"entities"` + PageToken string `json:"nextPageToken"` } func (e *EntityService) RegisterEntity(entityType EntityType, entity Entity) { @@ -43,7 +44,8 @@ func (e *EntityService) LookupEntity(entityType EntityType) (Entity, error) { if !ok { return nil, fmt.Errorf("Unable to find entity type %s in entity registry %v", entityType, e.registry) } - return entity, nil + // This "Copy" is pretty hacky...but works for now + return entity.Copy(), nil } func (e *EntityService) PathName(entityType EntityType) (string, error) { @@ -54,6 +56,16 @@ func (e *EntityService) PathName(entityType EntityType) (string, error) { return entity.PathName(), nil } +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 { @@ -68,18 +80,30 @@ func (e *EntityService) toEntityTypes(entityInterfaces []interface{}) ([]Entity, func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error) { var entityValsByKey = entityInterface.(map[string]interface{}) - entityType, ok := entityValsByKey["entityType"] + meta, ok := entityValsByKey["meta"] if !ok { - return nil, fmt.Errorf("Unable to find enityType attribute in %v", entityValsByKey) + return nil, fmt.Errorf("Unable to find meta attribute in %v", entityValsByKey) + } + + var metaByKey = meta.(map[string]interface{}) + entityType, ok := metaByKey["entityType"] + if !ok { + return nil, fmt.Errorf("Unable to find entityType attribute in %v", metaByKey) } entityObj, err := e.LookupEntity(EntityType(entityType.(string))) if err != nil { return nil, err } - err = mapstructure.Decode(entityValsByKey, entityObj) + + entityJSON, err := json.Marshal(entityValsByKey) + if err != nil { + return nil, fmt.Errorf("Error marshaling entity to JSON: %s", err) + } + + err = json.Unmarshal(entityJSON, entityObj) if err != nil { - return nil, fmt.Errorf("Error decoding entity: %s", err) + return nil, fmt.Errorf("Error unmarshaling entity JSON: %s", err) } return entityObj, nil } @@ -104,7 +128,7 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { for _, entity := range typedEntities { entities = append(entities, entity) } - return resp.NextPageToken, err + return resp.PageToken, err } if err := tokenListHelper(lg, &opts.ListOptions); err != nil { @@ -134,16 +158,10 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res } } - // TODO: use once we no longer have to fake the response - // v := &EntityListResponse{} - // r, err := e.client.DoRequest("GET", requrl, v) - // if err != nil { - // return nil, r, err - // } - - v, err := fakeEntityListResponse() + v := &EntityListResponse{} + r, err := e.client.DoRequest("GET", requrl, v) if err != nil { - return nil, nil, err + return nil, r, err } // TODO: handle hyrdation and nil is empty @@ -174,7 +192,7 @@ func (e *EntityService) ListAllOfType(opts *EntityListOptions, entityType Entity // for _, entity := range resp.Entities { // entities = append(entities, entity) // } - return resp.NextPageToken, err + return resp.PageToken, err } if err := tokenListHelper(lg, &opts.ListOptions); err != nil { diff --git a/event.go b/event.go index 96ca503..ebfa688 100644 --- a/event.go +++ b/event.go @@ -1,6 +1,10 @@ package yext -import "encoding/json" +import ( + "encoding/json" + + "github.com/mohae/deepcopy" +) const ( ENTITYTYPE_EVENT EntityType = "EVENT" @@ -30,6 +34,10 @@ func (e *EventEntity) PathName() string { return EntityPathNameEvents } +func (y *EventEntity) Copy() Entity { + return deepcopy.Copy(y).(*EventEntity) +} + func (e *EventEntity) String() string { b, _ := json.Marshal(e) return string(b) diff --git a/fake_response.go b/fake_response.go deleted file mode 100644 index 510e86f..0000000 --- a/fake_response.go +++ /dev/null @@ -1,91 +0,0 @@ -package yext - -import "encoding/json" - -// TODO: delete this file when the new endpoints become available - -var listResponse = `{ - "count": 4, - "entities": [ - { - "id": "US5523", - "uid": "0Za9W", - "timestamp": 1530543889575, - "accountId": "648517391220866580", - "locationName": "Safelite AutoGlass", - "address": "2124 W 2nd St", - "city": "Grand Island", - "state": "NE", - "zip": "68803", - "countryCode": "US", - "language": "en", - "suppressAddress": false, - "phone": "8888432798", - "isPhoneTracked": false, - "localPhone": "3083828689", - "alternatePhone": "8008002727", - "categoryIds": [ - "28", - "2040", - "26", - "27", - "588", - "524", - "1325579" - ], - "entityType": "LOCATION" - }, - { - "id": "Entity2", - "uid": "0Za9W", - "timestamp": 1530543889575, - "accountId": "648517391220866580", - "locationName": "Safelite AutoGlass", - "address": "2124 W 2nd St", - "city": "Grand Island", - "state": "NE", - "zip": "68803", - "countryCode": "US", - "language": "en", - "suppressAddress": false, - "phone": "8888432798", - "isPhoneTracked": false, - "localPhone": "3083828689", - "alternatePhone": "8008002727", - "categoryIds": [ - "28", - "2040", - "26", - "27", - "588", - "524", - "1325579" - ], - "entityType": "LOCATION" - }, - { - "id": "eventId", - "name": "event name", - "description": "event description", - "entityType": "EVENT" - }, - { - "id": "12345", - "firstName": "Catherine", - "nickname": "CTG", - "entityType": "CONSULTING_ENGINEER" - } - ] - } -` - -// Used to fake a JSON response for Enitites:List -func fakeEntityListResponse() (*EntityListResponse, error) { - byteValue := []byte(listResponse) - var resp *EntityListResponse - err := json.Unmarshal(byteValue, &resp) - if err != nil { - return nil, err - } - return resp, nil -} diff --git a/location.go b/location.go index 3fc7c62..379ed55 100644 --- a/location.go +++ b/location.go @@ -8,6 +8,8 @@ package yext import ( "encoding/json" "fmt" + + "github.com/mohae/deepcopy" ) const ENTITYTYPE_LOCATION EntityType = "LOCATION" @@ -15,10 +17,9 @@ const ENTITYTYPE_LOCATION EntityType = "LOCATION" // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { + EntityMeta + // Admin - Id *string `json:"id,omitempty"` - AccountId *string `json:"accountId,omitempty"` - EntityType *string `json:"entityType,omitempty"` FolderId *string `json:"folderId,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` CategoryIds *[]string `json:"categoryIds,omitempty"` @@ -31,16 +32,11 @@ type Location struct { nilIsEmpty bool // Address Fields - Name *string `json:"locationName,omitempty"` - Address *string `json:"address,omitempty"` - Address2 *string `json:"address2,omitempty"` - DisplayAddress *string `json:"displayAddress,omitempty"` - City *string `json:"city,omitempty"` - State *string `json:"state,omitempty"` - Sublocality *string `json:"sublocality,omitempty"` - Zip *string `json:"zip,omitempty"` - CountryCode *string `json:"countryCode,omitempty"` - SuppressAddress *bool `json:"suppressAddress,omitempty"` + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + DisplayAddress *string `json:"displayAddress,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` + SuppressAddress *bool `json:"suppressAddress,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -70,9 +66,9 @@ type Location struct { // Location Info Description *string `json:"description,omitempty"` HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` - Hours *string `json:"hours,omitempty"` + Hours *ProfileHours `json:"hours,omitempty"` AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - YearEstablished *string `json:"yearEstablished,omitempty"` + YearEstablished *float64 `json:"yearEstablished,omitempty"` Associations *[]string `json:"associations,omitempty"` Certifications *[]string `json:"certifications,omitempty"` Brands *[]string `json:"brands,omitempty"` @@ -84,16 +80,15 @@ type Location struct { PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs - DisplayLat *float64 `json:"displayLat,omitempty"` - DisplayLng *float64 `json:"displayLng,omitempty"` - DropoffLat *float64 `json:"dropoffLat,omitempty"` - DropoffLng *float64 `json:"dropoffLng,omitempty"` - WalkableLat *float64 `json:"walkableLat,omitempty"` - WalkableLng *float64 `json:"walkableLng,omitempty"` - RoutableLat *float64 `json:"routableLat,omitempty"` - RoutableLng *float64 `json:"routableLng,omitempty"` - PickupLat *float64 `json:"pickupLat,omitempty"` - PickupLng *float64 `json:"pickupLng,omitempty"` + DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` + // TODO: Update below + DropoffLat *float64 `json:"dropoffLat,omitempty"` + DropoffLng *float64 `json:"dropoffLng,omitempty"` + WalkableLat *float64 `json:"walkableLat,omitempty"` + WalkableLng *float64 `json:"walkableLng,omitempty"` + RoutableCoordinate *Coordinate `json:"yextRoutableCoordinate,omitempty"` + PickupLat *float64 `json:"pickupLat,omitempty"` + PickupLng *float64 `json:"pickupLng,omitempty"` // ECLS BioListIds *[]string `json:"bioListIds,omitempty"` @@ -106,16 +101,11 @@ type Location struct { ProductListsLabel *string `json:"productListsLabel,omitempty"` // Urls - MenuUrl *string `json:"menuUrl,omitempty"` - DisplayMenuUrl *string `json:"displayMenuUrl,omitempty"` - OrderUrl *string `json:"orderUrl,omitempty"` - DisplayOrderUrl *string `json:"displayOrderUrl,omitempty"` - ReservationUrl *string `json:"reservationUrl,omitempty"` - DisplayReservationUrl *string `json:"displayReservationUrl,omitempty"` - DisplayWebsiteUrl *string `json:"displayWebsiteUrl,omitempty"` - WebsiteUrl *string `json:"websiteUrl,omitempty"` - FeaturedMessage *string `json:"featuredMessage,omitempty"` - FeaturedMessageUrl *string `json:"featuredMessageUrl,omitempty"` + MenuUrl *Website `json:"menuUrl,omitempty"` + OrderUrl *Website `json:"orderUrl,omitempty"` + ReservationUrl *Website `json:"reservationUrl,omitempty"` + WebsiteUrl *Website `json:"websiteUrl,omitempty"` + FeaturedMessage *FeaturedMessage `json:"featuredMessage,omitempty"` // Uber UberClientId *string `json:"uberClientId,omitempty"` @@ -162,6 +152,47 @@ type Location struct { */ } +type Address struct { + Line1 *string `json:"line1,omitempty"` + Line2 *string `json:"line2,omitempty"` + City *string `json:"city,omitempty"` + Region *string `json:"region,omitempty"` + Sublocality *string `json:"sublocality,omitempty"` // check on this one?? + PostalCode *string `json:"postalCode,omitempty"` +} + +type FeaturedMessage struct { + Description *string `json:"description,omitempty"` + Url *string `json:"url,omitempty"` +} + +type Website struct { + DisplayUrl *string `json:"displayUrl,omitempty"` + Url *string `json:"url,omitempty"` + PreferDisplayUrl *bool `json:"preferDisplayUrl,omitempty"` +} + +type Coordinate struct { + Latitude *float64 `json:"latitude,omitempty"` + Longitude *float64 `json:"longitude,omitempty"` +} + +// TODO: Need to rename +type ProfileHours struct { + Monday []*DayHours `json:"monday,omitempty"` + Tuesday []*DayHours `json:"tuesday,omitempty"` + Wednesday []*DayHours `json:"wednesday,omitempty"` + Thursday []*DayHours `json:"thursday,omitempty"` + Friday []*DayHours `json:"friday,omitempty"` + Saturday []*DayHours `json:"saturday,omitempty"` + Sunday []*DayHours `json:"sunday,omitempty"` +} + +type DayHours struct { + Start *string `json:"start,omitempty"` + End *string `json:"end,omitempty"` +} + func (y *Location) EntityId() string { return y.GetId() } @@ -174,6 +205,10 @@ func (y *Location) PathName() string { return locationsPath } +func (y *Location) Copy() Entity { + return deepcopy.Copy(y).(*Location) +} + func (y Location) GetId() string { if y.Id != nil { return *y.Id @@ -181,12 +216,12 @@ func (y Location) GetId() string { return "" } -func (y Location) GetEntityType() string { - if y.EntityType != nil { - return *y.EntityType - } - return "" -} +// func (y Location) GetEntityType() string { +// if y.EntityType != nil { +// return *y.EntityType +// } +// return "" +// } func (y Location) GetName() string { if y.Name != nil { @@ -265,16 +300,16 @@ func (y Location) GetAccountId() string { return "" } -func (y Location) GetAddress() string { - if y.Address != nil { - return *y.Address +func (y Location) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return *y.Address.Line1 } return "" } -func (y Location) GetAddress2() string { - if y.Address2 != nil { - return *y.Address2 +func (y Location) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return *y.Address.Line2 } return "" } @@ -294,22 +329,22 @@ func (y Location) GetDisplayAddress() string { } func (y Location) GetCity() string { - if y.City != nil { - return *y.City + if y.Address != nil && y.Address.City != nil { + return *y.Address.City } return "" } -func (y Location) GetState() string { - if y.State != nil { - return *y.State +func (y Location) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return *y.Address.Region } return "" } -func (y Location) GetZip() string { - if y.Zip != nil { - return *y.Zip +func (y Location) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return *y.Address.PostalCode } return "" } @@ -377,44 +412,37 @@ func (y Location) GetTtyPhone() string { return "" } -func (y Location) GetFeaturedMessage() string { - if y.FeaturedMessage != nil { - return *y.FeaturedMessage +func (y Location) GetFeaturedMessageDescription() string { + if y.FeaturedMessage != nil && y.FeaturedMessage.Description != nil { + return *y.FeaturedMessage.Description } return "" } func (y Location) GetFeaturedMessageUrl() string { - if y.FeaturedMessageUrl != nil { - return *y.FeaturedMessageUrl + if y.FeaturedMessage != nil && y.FeaturedMessage.Url != nil { + return *y.FeaturedMessage.Url } return "" } func (y Location) GetWebsiteUrl() string { - if y.WebsiteUrl != nil { - return *y.WebsiteUrl + if y.WebsiteUrl != nil && y.WebsiteUrl.Url != nil { + return *y.WebsiteUrl.Url } return "" } func (y Location) GetDisplayWebsiteUrl() string { - if y.DisplayWebsiteUrl != nil { - return *y.DisplayWebsiteUrl + if y.WebsiteUrl != nil && y.WebsiteUrl.DisplayUrl != nil { + return *y.WebsiteUrl.DisplayUrl } return "" } func (y Location) GetReservationUrl() string { - if y.ReservationUrl != nil { - return *y.ReservationUrl - } - return "" -} - -func (y Location) GetHours() string { - if y.Hours != nil { - return *y.Hours + if y.ReservationUrl != nil && y.ReservationUrl.Url != nil { + return *y.ReservationUrl.Url } return "" } @@ -447,37 +475,37 @@ func (y Location) GetFacebookPageUrl() string { return "" } -func (y Location) GetYearEstablished() string { +func (y Location) GetYearEstablished() float64 { if y.YearEstablished != nil { return *y.YearEstablished } - return "" + return 0 } func (y Location) GetDisplayLat() float64 { - if y.DisplayLat != nil { - return *y.DisplayLat + if y.DisplayCoordinate != nil && y.DisplayCoordinate.Latitude != nil { + return *y.DisplayCoordinate.Latitude } return 0 } func (y Location) GetDisplayLng() float64 { - if y.DisplayLng != nil { - return *y.DisplayLng + if y.DisplayCoordinate != nil && y.DisplayCoordinate.Longitude != nil { + return *y.DisplayCoordinate.Longitude } return 0 } func (y Location) GetRoutableLat() float64 { - if y.RoutableLat != nil { - return *y.RoutableLat + if y.RoutableCoordinate != nil && y.RoutableCoordinate.Latitude != nil { + return *y.RoutableCoordinate.Latitude } return 0 } func (y Location) GetRoutableLng() float64 { - if y.RoutableLng != nil { - return *y.RoutableLng + if y.RoutableCoordinate != nil && y.RoutableCoordinate.Longitude != nil { + return *y.RoutableCoordinate.Longitude } return 0 } diff --git a/location_diff.go b/location_diff.go index e65450f..e5d1304 100644 --- a/location_diff.go +++ b/location_diff.go @@ -28,7 +28,8 @@ type Comparable interface { // // isDiff -> false // // delta -> nil func (y Location) Diff(b *Location) (d *Location, diff bool) { - d = &Location{Id: String(y.GetId())} + d = &Location{EntityMeta: EntityMeta{ + Id: String(y.GetId())}} var ( aV, bV = reflect.ValueOf(y), reflect.ValueOf(b).Elem() @@ -47,14 +48,17 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { continue } - if valB.IsNil() || nameA == "Id" { + // Issue here because EntityMeta is an embedded struct + if nameA == "Id" || nameA == "EntityMeta" || valB.IsNil() { continue } if nameA == "Hours" { - if !valA.IsNil() && !valB.IsNil() && HoursAreEquivalent(getUnderlyingValue(valA.Interface()).(string), getUnderlyingValue(valB.Interface()).(string)) { - continue - } + // TODO: need to re-enable + continue + // if !valA.IsNil() && !valB.IsNil() && HoursAreEquivalent(getUnderlyingValue(valA.Interface()).(string), getUnderlyingValue(valB.Interface()).(string)) { + // continue + // } } if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { @@ -163,9 +167,11 @@ var closedHoursEquivalents = map[string]struct{}{ HoursClosedAllWeek: struct{}{}, } +// TODO: need to re-enable func HoursAreEquivalent(a, b string) bool { - _, aIsClosed := closedHoursEquivalents[a] - _, bIsClosed := closedHoursEquivalents[b] - - return a == b || aIsClosed && bIsClosed + return false + // _, aIsClosed := closedHoursEquivalents[a] + // _, bIsClosed := closedHoursEquivalents[b] + // + // return a == b || aIsClosed && bIsClosed } diff --git a/location_diff_test.go b/location_diff_test.go index 41ae0dd..c6351fa 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -13,7 +13,9 @@ var examplePhoto = LocationPhoto{ } var complexOne = &Location{ - Id: String("lock206"), + EntityMeta: EntityMeta{ + Id: String("lock206"), + }, Name: String("Farmers Insurance - Stephen Lockhart "), CustomFields: map[string]interface{}{ "1857": "", @@ -54,14 +56,16 @@ var complexOne = &Location{ "7299": "1046846", "7300": true, }, - Address: String("2690 Sheridan Dr W"), - Address2: String(""), - City: String("Tonawanda"), - State: String("NY"), - Zip: String("14150-9425"), + Address: &Address{ + Line1: String("2690 Sheridan Dr W"), + Line2: String(""), + City: String("Tonawanda"), + Region: String("NY"), + PostalCode: String("14150-9425"), + }, Phone: String("716-835-0306"), FaxPhone: String("716-835-0415"), - YearEstablished: String("2015"), + YearEstablished: Float(2015), Emails: &[]string{"slockhart@farmersagent.com"}, Services: &[]string{ "Auto Insurance", @@ -78,7 +82,9 @@ var complexOne = &Location{ } var complexTwo = &Location{ - Id: String("lock206"), + EntityMeta: EntityMeta{ + Id: String("lock206"), + }, Name: String("Farmers Insurance - Stephen Lockhart "), CustomFields: map[string]interface{}{ "1857": "", @@ -119,14 +125,16 @@ var complexTwo = &Location{ "7299": "1046846", "7300": true, }, - Address: String("2690 Sheridan Dr W"), - Address2: String(""), - City: String("Tonawanda"), - State: String("NY"), - Zip: String("14150-9425"), + Address: &Address{ + Line1: String("2690 Sheridan Dr W"), + Line2: String(""), + City: String("Tonawanda"), + Region: String("NY"), + PostalCode: String("14150-9425"), + }, Phone: String("716-835-0306"), FaxPhone: String("716-835-0415"), - YearEstablished: String("2015"), + YearEstablished: Float(2015), Emails: &[]string{"slockhart@farmersagent.com"}, Services: &[]string{ "Auto Insurance", @@ -142,44 +150,101 @@ var complexTwo = &Location{ FolderId: String("91760"), } -var jsonData string = `{"id":"phai514","locationName":"Farmers Insurance - Aroun Phaisan ","customFields":{"1857":"","1858":"122191","1859":"Aroun","1871":"Phaisan","3004":"Agent","7240":"Aroun Phaisan","7251":true,"7253":"1221","7254":"aphaisan","7255":"2685","7256":["phai514"],"7261":false,"7263":true,"7265":"","7266":"","7269":"91","7270":true,"7271":"21","7272":"User_Dup","7273":"Lincoln","7274":"NE","7275":"5730 R St Ste B","7276":"68505-2309","7277":"12","7278":false,"7279":true,"7283":"","7284":"","7285":"16133384","7286":"","7287":true,"7288":true,"7296":"16133384","7297":"","7298":"","7299":"786200","7300":true},"address":"5730 R St","address2":"Ste B","city":"Lincoln","state":"NE","zip":"68505-2309","phone":"402-417-4266","faxPhone":"402-423-3141","yearEstablished":"2011","emails":["aphaisan@farmersagent.com"],"services":["Auto Insurance","Home Insurance","Homeowners Insurance","Business Insurance","Motorcyle Insurance","Recreational Insurance","Renters Insurance","Umbrella Insurance","Term Life Insurance","Whole Life Insurance"],"languages":["English"],"folderId":"91760"}` +var jsonData string = `{"id":"phai514","locationName":"Farmers Insurance - Aroun Phaisan ","customFields":{"1857":"","1858":"122191","1859":"Aroun","1871":"Phaisan","3004":"Agent","7240":"Aroun Phaisan","7251":true,"7253":"1221","7254":"aphaisan","7255":"2685","7256":["phai514"],"7261":false,"7263":true,"7265":"","7266":"","7269":"91","7270":true,"7271":"21","7272":"User_Dup","7273":"Lincoln","7274":"NE","7275":"5730 R St Ste B","7276":"68505-2309","7277":"12","7278":false,"7279":true,"7283":"","7284":"","7285":"16133384","7286":"","7287":true,"7288":true,"7296":"16133384","7297":"","7298":"","7299":"786200","7300":true},"address": {"line1":"5730 R St","line2":"Ste B","city":"Lincoln","state":"NE","zip":"68505-2309"},"phone":"402-417-4266","faxPhone":"402-423-3141","yearEstablished":2011,"emails":["aphaisan@farmersagent.com"],"services":["Auto Insurance","Home Insurance","Homeowners Insurance","Business Insurance","Motorcyle Insurance","Recreational Insurance","Renters Insurance","Umbrella Insurance","Term Life Insurance","Whole Life Insurance"],"languages":["English"],"folderId":"91760"}` var baseLocation Location = Location{ - Id: String("ding"), - Name: String("ding"), - AccountId: String("ding"), - Address: String("ding"), - Address2: String("ding"), - DisplayAddress: String("ding"), - City: String("ding"), - State: String("ding"), - Zip: String("ding"), - CountryCode: String("ding"), - Phone: String("ding"), - LocalPhone: String("ding"), - AlternatePhone: String("ding"), - FaxPhone: String("ding"), - MobilePhone: String("ding"), - TollFreePhone: String("ding"), - TtyPhone: String("ding"), - FeaturedMessage: String("ding"), - FeaturedMessageUrl: String("ding"), - WebsiteUrl: String("ding"), - DisplayWebsiteUrl: String("ding"), - ReservationUrl: String("ding"), - Hours: String("ding"), - AdditionalHoursText: String("ding"), - Description: String("ding"), - TwitterHandle: String("ding"), - FacebookPageUrl: String("ding"), - YearEstablished: String("ding"), - FolderId: String("ding"), - SuppressAddress: Bool(false), - IsPhoneTracked: Bool(true), - DisplayLat: Float(1234.0), - DisplayLng: Float(1234.0), - RoutableLat: Float(1234.0), - RoutableLng: Float(1234.0), + EntityMeta: EntityMeta{ + Id: String("ding"), + AccountId: String("ding"), + }, + Name: String("ding"), + DisplayAddress: String("ding"), + Address: &Address{ + Line1: String("ding"), + Line2: String("ding"), + City: String("ding"), + Region: String("ding"), + PostalCode: String("ding"), + }, + CountryCode: String("ding"), + Phone: String("ding"), + LocalPhone: String("ding"), + AlternatePhone: String("ding"), + FaxPhone: String("ding"), + MobilePhone: String("ding"), + TollFreePhone: String("ding"), + TtyPhone: String("ding"), + FeaturedMessage: &FeaturedMessage{ + Description: String("ding"), + Url: String("ding"), + }, + WebsiteUrl: &Website{ + Url: String("ding"), + DisplayUrl: String("ding"), + }, + ReservationUrl: &Website{ + Url: String("ding"), + }, + Hours: &ProfileHours{ + Monday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Tuesday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Wednesday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Thursday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Friday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Saturday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Sunday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + }, + AdditionalHoursText: String("ding"), + Description: String("ding"), + TwitterHandle: String("ding"), + FacebookPageUrl: String("ding"), + YearEstablished: Float(1234), + FolderId: String("ding"), + SuppressAddress: Bool(false), + IsPhoneTracked: Bool(true), + DisplayCoordinate: &Coordinate{ + Latitude: Float(1234.0), + Longitude: Float(1234.0), + }, + RoutableCoordinate: &Coordinate{ + Latitude: Float(1234.0), + Longitude: Float(1234.0), + }, Keywords: &[]string{"ding", "ding"}, PaymentOptions: &[]string{"ding", "ding"}, VideoUrls: &[]string{"ding", "ding"}, @@ -203,41 +268,98 @@ var baseLocation Location = Location{ func TestDiffIdentical(t *testing.T) { secondLocation := &Location{ - Id: String("ding"), - Name: String("ding"), - AccountId: String("ding"), - Address: String("ding"), - Address2: String("ding"), - DisplayAddress: String("ding"), - City: String("ding"), - State: String("ding"), - Zip: String("ding"), - CountryCode: String("ding"), - Phone: String("ding"), - LocalPhone: String("ding"), - AlternatePhone: String("ding"), - FaxPhone: String("ding"), - MobilePhone: String("ding"), - TollFreePhone: String("ding"), - TtyPhone: String("ding"), - FeaturedMessage: String("ding"), - FeaturedMessageUrl: String("ding"), - WebsiteUrl: String("ding"), - DisplayWebsiteUrl: String("ding"), - ReservationUrl: String("ding"), - Hours: String("ding"), - AdditionalHoursText: String("ding"), - Description: String("ding"), - TwitterHandle: String("ding"), - FacebookPageUrl: String("ding"), - YearEstablished: String("ding"), - FolderId: String("ding"), - SuppressAddress: Bool(false), - IsPhoneTracked: Bool(true), - DisplayLat: Float(1234.0), - DisplayLng: Float(1234.0), - RoutableLat: Float(1234.0), - RoutableLng: Float(1234.0), + EntityMeta: EntityMeta{ + Id: String("ding"), + AccountId: String("ding"), + }, + Name: String("ding"), + DisplayAddress: String("ding"), + Address: &Address{ + Line1: String("ding"), + Line2: String("ding"), + City: String("ding"), + Region: String("ding"), + PostalCode: String("ding"), + }, + CountryCode: String("ding"), + Phone: String("ding"), + LocalPhone: String("ding"), + AlternatePhone: String("ding"), + FaxPhone: String("ding"), + MobilePhone: String("ding"), + TollFreePhone: String("ding"), + TtyPhone: String("ding"), + FeaturedMessage: &FeaturedMessage{ + Description: String("ding"), + Url: String("ding"), + }, + WebsiteUrl: &Website{ + Url: String("ding"), + DisplayUrl: String("ding"), + }, + ReservationUrl: &Website{ + Url: String("ding"), + }, + Hours: &ProfileHours{ + Monday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Tuesday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Wednesday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Thursday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Friday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Saturday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + Sunday: []*DayHours{ + &DayHours{ + Start: String("ding"), + End: String("ding"), + }, + }, + }, + AdditionalHoursText: String("ding"), + Description: String("ding"), + TwitterHandle: String("ding"), + FacebookPageUrl: String("ding"), + YearEstablished: Float(1234), + FolderId: String("ding"), + SuppressAddress: Bool(false), + IsPhoneTracked: Bool(true), + DisplayCoordinate: &Coordinate{ + Latitude: Float(1234.0), + Longitude: Float(1234.0), + }, + RoutableCoordinate: &Coordinate{ + Latitude: Float(1234.0), + Longitude: Float(1234.0), + }, Keywords: &[]string{"ding", "ding"}, PaymentOptions: &[]string{"ding", "ding"}, VideoUrls: &[]string{"ding", "ding"}, @@ -656,7 +778,8 @@ func (t floatTest) formatErrorBase(index int) string { func TestFloatDiffs(t *testing.T) { a, b := *new(Location), new(Location) for i, data := range floatTests { - a.DisplayLat, b.DisplayLat = data.baseValue, data.newValue + a.DisplayCoordinate = &Coordinate{Latitude: data.baseValue} + b.DisplayCoordinate = &Coordinate{Latitude: data.newValue} a.nilIsEmpty, b.nilIsEmpty = data.nilIsEmpty, data.nilIsEmpty d, isDiff := a.Diff(b) if isDiff != data.isDiff { @@ -668,7 +791,7 @@ func TestFloatDiffs(t *testing.T) { t.Errorf("%v\ndelta was nil but expected %v\n", data.formatErrorBase(i), formatFloatPtr(data.expectedFieldValue)) } else if d != nil && data.expectedFieldValue == nil { t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) - } else if *d.DisplayLat != *data.expectedFieldValue { + } else if *d.DisplayCoordinate.Latitude != *data.expectedFieldValue { t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) } } @@ -1239,11 +1362,15 @@ func TestLabels(t *testing.T) { tests = []Scenario{ Scenario{ A: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &one, }, B: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &two, }, WantDelta: nil, @@ -1251,11 +1378,15 @@ func TestLabels(t *testing.T) { }, Scenario{ A: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &one, }, B: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &three, }, WantDelta: nil, @@ -1263,15 +1394,21 @@ func TestLabels(t *testing.T) { }, Scenario{ A: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &one, }, B: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &four, }, WantDelta: &Location{ - Id: String("1"), + EntityMeta: EntityMeta{ + Id: String("1"), + }, LabelIds: &four, }, WantDiff: true, @@ -1390,98 +1527,99 @@ func TestZeroValuesAndNilDiffing(t *testing.T) { } } -var hoursTests = []struct { - A, B *string - WantEquivalent, WantDiff bool -}{ - { - A: String(""), - B: String(""), - WantEquivalent: true, - WantDiff: false, - }, - { - A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), - B: String(""), - WantEquivalent: true, - WantDiff: false, - }, - // This might seem odd, but we're still working out hours semantics with Product, so I'd rather err on the side - // of a limited set of 'closed' equivalencies for now: - { - A: String("1:closed"), - B: String(""), - WantEquivalent: false, - WantDiff: true, - }, - { - A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), - B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), - WantEquivalent: true, - WantDiff: false, - }, - { - A: String("1:11:00"), - B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), - WantEquivalent: false, - WantDiff: true, - }, - { - A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), - B: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), - WantEquivalent: true, - WantDiff: false, - }, - { - A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), - B: String("1:11:01:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), - WantEquivalent: false, - WantDiff: true, - }, - { - A: String("1:11:00:20:00"), - B: String("1:11:00:20:00"), - WantEquivalent: true, - WantDiff: false, - }, - { - A: String("1:11:00:20:00"), - B: String("1:11:01:20:00"), - WantEquivalent: false, - WantDiff: true, - }, - { - A: nil, - B: String("1:11:01:20:00"), - WantEquivalent: false, - WantDiff: true, - }, - { - A: String("1:11:01:20:00"), - B: nil, - WantEquivalent: false, - WantDiff: false, - }, - { - A: nil, - B: nil, - WantEquivalent: true, - WantDiff: false, - }, -} - -func TestHoursAreEquivalent(t *testing.T) { - for _, test := range hoursTests { - if test.A != nil && test.B != nil { - if got := HoursAreEquivalent(*test.A, *test.B); got != test.WantEquivalent { - t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), got, test.WantEquivalent) - } - if got := HoursAreEquivalent(*test.B, *test.A); got != test.WantEquivalent { - t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.B), stringify(test.A), got, test.WantEquivalent) - } - } - } -} +// TODO: re-enable tests +// var hoursTests = []struct { +// A, B *ProfileHours +// WantEquivalent, WantDiff bool +// }{ +// { +// A: nil, +// B: nil, +// WantEquivalent: true, +// WantDiff: false, +// }, +// { +// A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), +// B: String(""), +// WantEquivalent: true, +// WantDiff: false, +// }, +// // This might seem odd, but we're still working out hours semantics with Product, so I'd rather err on the side +// // of a limited set of 'closed' equivalencies for now: +// { +// A: String("1:closed"), +// B: String(""), +// WantEquivalent: false, +// WantDiff: true, +// }, +// { +// A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), +// B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), +// WantEquivalent: true, +// WantDiff: false, +// }, +// { +// A: String("1:11:00"), +// B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), +// WantEquivalent: false, +// WantDiff: true, +// }, +// { +// A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), +// B: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), +// WantEquivalent: true, +// WantDiff: false, +// }, +// { +// A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), +// B: String("1:11:01:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), +// WantEquivalent: false, +// WantDiff: true, +// }, +// { +// A: String("1:11:00:20:00"), +// B: String("1:11:00:20:00"), +// WantEquivalent: true, +// WantDiff: false, +// }, +// { +// A: String("1:11:00:20:00"), +// B: String("1:11:01:20:00"), +// WantEquivalent: false, +// WantDiff: true, +// }, +// { +// A: nil, +// B: String("1:11:01:20:00"), +// WantEquivalent: false, +// WantDiff: true, +// }, +// { +// A: String("1:11:01:20:00"), +// B: nil, +// WantEquivalent: false, +// WantDiff: false, +// }, +// { +// A: nil, +// B: nil, +// WantEquivalent: true, +// WantDiff: false, +// }, +// } +// +// func TestHoursAreEquivalent(t *testing.T) { +// for _, test := range hoursTests { +// if test.A != nil && test.B != nil { +// if got := HoursAreEquivalent(*test.A, *test.B); got != test.WantEquivalent { +// t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), got, test.WantEquivalent) +// } +// if got := HoursAreEquivalent(*test.B, *test.A); got != test.WantEquivalent { +// t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.B), stringify(test.A), got, test.WantEquivalent) +// } +// } +// } +// } func stringify(v *string) string { if v != nil { @@ -1490,13 +1628,13 @@ func stringify(v *string) string { return "nil" } -func TestHoursAreEquivalentDiff(t *testing.T) { - for _, test := range hoursTests { - a := &Location{Hours: test.A} - b := &Location{Hours: test.B} - - if _, isDiff := a.Diff(b); isDiff != test.WantDiff { - t.Errorf(`Diff("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), isDiff, test.WantDiff) - } - } -} +// func TestHoursAreEquivalentDiff(t *testing.T) { +// for _, test := range hoursTests { +// a := &Location{Hours: test.A} +// b := &Location{Hours: test.B} +// +// if _, isDiff := a.Diff(b); isDiff != test.WantDiff { +// t.Errorf(`Diff("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), isDiff, test.WantDiff) +// } +// } +// } diff --git a/location_service_test.go b/location_service_test.go index 8996072..b725d7a 100644 --- a/location_service_test.go +++ b/location_service_test.go @@ -87,7 +87,8 @@ func makeLocs(n int) []*Location { var locs []*Location for i := 0; i < n; i++ { - new := &Location{Id: String(strconv.Itoa(i))} + new := &Location{EntityMeta: EntityMeta{ + Id: String(strconv.Itoa(i))}} locs = append(locs, new) } diff --git a/location_test.go b/location_test.go index 2673348..5b7ce3b 100644 --- a/location_test.go +++ b/location_test.go @@ -23,8 +23,8 @@ func TestJSONSerialization(t *testing.T) { tests := []test{ {&Location{}, `{}`}, - {&Location{City: nil}, `{}`}, - {&Location{City: String("")}, `{"city":""}`}, + {&Location{Address: &Address{City: nil}}, `{"address":{}}`}, // TODO: verify this is correct + {&Location{Address: &Address{City: String("")}}, `{"address":{"city":""}}`}, {&Location{Languages: nil}, `{}`}, {&Location{Languages: nil}, `{}`}, {&Location{Languages: &[]string{}}, `{"languages":[]}`}, @@ -77,10 +77,12 @@ var sampleLocationJSON = `{ "timestamp":1483891079283, "accountId":"479390", "locationName":"Best Buy", - "address":"105 Topsham Fair Mall Rd", - "city":"Topsham", - "state":"ME", - "zip":"04086", + "address":{ + "line1": "105 Topsham Fair Mall Rd", + "city":"Topsham", + "state":"ME", + "zip":"04086" + }, "countryCode":"US", "language":"en", "phone":"8882378289", @@ -143,10 +145,15 @@ var sampleLocationJSON = `{ ] } ], - "featuredMessage":"Black Friday 2016", - "featuredMessageUrl":"http://www.bestbuy.com/site/electronics/black-friday/pcmcat225600050002.c?id\u003dpcmcat225600050002\u0026ref\u003dNS\u0026loc\u003dns100", - "websiteUrl":"[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", - "displayWebsiteUrl":"[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", + "featuredMessage":{ + "description": "Black Friday 2016", + "url":"http://www.bestbuy.com/site/electronics/black-friday/pcmcat225600050002.c?id\u003dpcmcat225600050002\u0026ref\u003dNS\u0026loc\u003dns100" + }, + "websiteUrl": { + "url": "[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", + "displayWebsiteUrl":"[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", + "preferDisplayUrl":true + }, "logo":{ "url":"http://www.yext-static.com/cms/79fb6255-2e84-435f-9864-aa0572fe4cbd.png" }, From 312e7692a1d7f3014bf98c1a6317e84c3cc01aed Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 3 Oct 2018 14:56:13 -0400 Subject: [PATCH 005/285] EntityService: done except for techops bugs --- client.go | 2 +- customfield.go | 26 +-- customfield_service.go | 1 + customfield_service_test.go | 108 +++++++++-- entitiestest/main.go | 74 ++++++-- entity.go | 10 +- entity_service.go | 132 +++---------- entity_test.go | 359 ++++++++++++++++++++++++++++++++++++ event.go | 4 +- hydrate.go | 2 +- language_profile_service.go | 40 ++-- location.go | 82 ++++---- location_diff.go | 2 +- location_diff_test.go | 169 ++++++++--------- location_service_test.go | 2 +- location_test.go | 4 +- 16 files changed, 712 insertions(+), 305 deletions(-) create mode 100644 entity_test.go diff --git a/client.go b/client.go index d2882a8..014806c 100644 --- a/client.go +++ b/client.go @@ -326,7 +326,7 @@ func tokenListHelper(lr tokenListRetriever, opts *ListOptions) error { func addListOptions(requrl string, opts *ListOptions) (string, error) { u, err := url.Parse(requrl) if err != nil { - return "", nil + return "", err } if opts == nil { diff --git a/customfield.go b/customfield.go index 632fd6b..701923a 100644 --- a/customfield.go +++ b/customfield.go @@ -1,6 +1,8 @@ package yext -import "fmt" +import ( + "fmt" +) type CustomFieldType string @@ -231,26 +233,18 @@ func (v *VideoGallery) CustomFieldTag() string { return CUSTOMFIELDTYPE_VIDEO } -type Hours struct { - // TODO: Check how AdditonalText and HolidayHours are represented - AdditionalText string `json:"additionalHoursText,omitempty"` - HolidayHours []HolidayHours `json:"holidayHours,omitempty"` - Hours string `json:"holidayHours,omitempty"` - // Monday []*DayHours `json:"monday,omitempty"` - // Tuesday []*DayHours `json:"tuesday,omitempty"` - // Wednesday []*DayHours `json:"wednesday,omitempty"` - // Thursday []*DayHours `json:"thursday,omitempty"` - // Friday []*DayHours `json:"friday,omitempty"` - // Saturday []*DayHours `json:"saturday,omitempty"` - // Sunday []*DayHours `json:"sunday,omitempty"` -} - func (h Hours) CustomFieldTag() string { return CUSTOMFIELDTYPE_HOURS } type DailyTimes struct { - DailyTimes string `json:"dailyTimes,omitempty"` + Sunday string `json:"sunday,omitempty"` + Monday string `json:"monday,omitempty"` + Tuesday string `json:"tuesday,omitempty"` + Wednesday string `json:"wednesday,omitempty"` + Thursday string `json:"thursday,omitempty"` + Friday string `json:"friday,omitempty"` + Saturday string `json:"saturday,omitempty"` } func (d DailyTimes) CustomFieldTag() string { diff --git a/customfield_service.go b/customfield_service.go index 307af55..8d91363 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -531,6 +531,7 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st return parsed, nil } +// TODO: Can we do this anymore? func validateCustomFieldsKeys(cfs map[string]interface{}) error { for k, _ := range cfs { if !customFieldKeyRegex.MatchString(k) { diff --git a/customfield_service_test.go b/customfield_service_test.go index 7007117..9e5fb88 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -42,11 +42,11 @@ func runParseTest(t *testing.T, index int, c customFieldParseTest) { ) if cfType != expectType { - t.Errorf("test #%d (%s) failed: expected type %s, got type %s", index, c.TypeName, expectType, cfType) + t.Errorf("test #%d (%s) failed:\nexpected type %s\ngot type %s", index, c.TypeName, expectType, cfType) } if !reflect.DeepEqual(cf, c.Expected) { - t.Errorf("test #%d (%s) failed: expected value %v, got value %v", index, c.TypeName, c.Expected, cf) + t.Errorf("test #%d (%s) failed\nexpected value %v\ngot value %v", index, c.TypeName, c.Expected, cf) } } @@ -59,15 +59,58 @@ var ( } hoursRaw = 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", + "monday": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, + }, + "tuesday": []map[string]interface{}{ + map[string]interface{}{ + "start": "12:00", + "end": "15:00", + }, + { + "start": "5:00", + "end": "11:00", + }, + }, + "wednesday": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, + }, + "thursday": []map[string]interface{}{ + map[string]interface{}{ + "start": "0:00", + "end": "23:59", + }, + }, + "friday": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, + }, + "saturday": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, + }, "holidayHours": []interface{}{ map[string]interface{}{ - "date": "2016-05-30", - "hours": "", + "date": "2016-05-30", }, map[string]interface{}{ - "date": "2016-05-31", - "hours": "9:00:17:00", + "date": "2016-05-31", + "hours": []*Times{ + &Times{ + Start: "9:00", + End: "17:00", + }, + }, }, }, } @@ -76,7 +119,13 @@ var ( "url": "http://www.youtube.com/watch?v=M80FTIcEgZM", } dailyTimesRaw = map[string]interface{}{ - "dailyTimes": "2:7:00,3:7:00,4:7:00,5:7:00,6:7:00,7:7:00,1:7:00", + "monday": "4:00", + "tuesday": "5:00", + "wednesday": "6:00", + "thursday": "7:00", + "friday": "8:00", + "saturday": "9:00", + "sunday": "10:00", } parseTests = []customFieldParseTest{ customFieldParseTest{"BOOLEAN", false, YesNo(false)}, @@ -111,21 +160,48 @@ var ( Description: "An example caption for a video", }}, customFieldParseTest{"HOURS", hoursRaw, Hours{ - 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: []HolidayHours{ + Monday: []*Times{ + &Times{Start: "9:00", End: "17:00"}, + }, + Tuesday: []*Times{ + &Times{Start: "12:00", End: "15:00"}, + &Times{Start: "5:00", End: "11:00"}, + }, + Wednesday: []*Times{ + &Times{Start: "9:00", End: "17:00"}, + }, + Thursday: []*Times{ + &Times{Start: "0:00", End: "23:59"}, + }, + Friday: []*Times{ + &Times{Start: "9:00", End: "17:00"}, + }, + Saturday: []*Times{ + &Times{Start: "9:00", End: "17:00"}, + }, + HolidayHours: &[]HolidayHours{ HolidayHours{ - Date: "2016-05-30", - Hours: "", + Date: "2016-05-30", }, HolidayHours{ - Date: "2016-05-31", - Hours: "9:00:17:00", + Date: "2016-05-31", + Hours: []*Times{ + &Times{ + Start: "9:00", + End: "17:00", + }, + }, }, }, }}, customFieldParseTest{"DAILY_TIMES", dailyTimesRaw, DailyTimes{ - DailyTimes: "2:7:00,3:7:00,4:7:00,5:7:00,6:7:00,7:7:00,1:7:00", + Monday: "4:00", + Tuesday: "5:00", + Wednesday: "6:00", + Thursday: "7:00", + Friday: "8:00", + Saturday: "9:00", + Sunday: "10:00", }}, customFieldParseTest{"LOCATION_LIST", []string{"a", "b", "c"}, LocationList([]string{"a", "b", "c"})}, } diff --git a/entitiestest/main.go b/entitiestest/main.go index b256acd..39dcf65 100644 --- a/entitiestest/main.go +++ b/entitiestest/main.go @@ -13,16 +13,12 @@ type MorganLocation struct { } //TODO: Revisit this...what if m.Location is nil? -func (m *MorganLocation) EntityId() string { - return m.Location.EntityId() +func (m *MorganLocation) GetEntityId() string { + return m.Location.GetEntityId() } -func (m *MorganLocation) Type() yext.EntityType { - return m.Location.Type() -} - -func (m *MorganLocation) PathName() string { - return m.Location.PathName() +func (m *MorganLocation) GetEntityType() yext.EntityType { + return m.Location.GetEntityType() } func (m *MorganLocation) Copy() yext.Entity { @@ -32,18 +28,68 @@ func (m *MorganLocation) Copy() yext.Entity { func main() { client := yext.NewClient(yext.NewDefaultConfig().WithApiKey("e929153c956b051cea51ec289bfd2383")) client.EntityService.RegisterEntity(yext.ENTITYTYPE_LOCATION, &MorganLocation{}) - entities, err := client.EntityService.ListAll(nil) + // entities, err := client.EntityService.ListAll(nil) + // if err != nil { + // log.Fatal(err) + // } + // + // log.Printf("ListAll: Got %d entities", len(entities)) + //for _, entity := range entities { + //morganEntity := entity.(*MorganLocation) + //log.Printf("PROFILE description: %s", GetString(morganEntity.Description)) + //log.Printf("CUSTOM multi: %s", GetString(morganEntity.CMultiText)) + //} + + entity, _, err := client.EntityService.Get("CTG") if err != nil { log.Fatal(err) } + morganEntity := entity.(*MorganLocation) + log.Printf("Get: Got %s", morganEntity.GetName()) + + // morganEntity.Name = yext.String(morganEntity.GetName() + "- 1") + // update := &MorganLocation{ + // Location: yext.Location{ + // // EntityMeta: yext.EntityMeta{ + // // Id: yext.String("CTG"), + // // }, + // Name: yext.String("CTG"), + // }, + // } + // _, err = client.EntityService.Edit(update) + // if err != nil { + // log.Fatal(err) + // } + // log.Printf("Edit: Edited %s", morganEntity.GetName()) - log.Printf("ListAll: Got %d entities", len(entities)) - for _, entity := range entities { + morganEntity = &MorganLocation{ + Location: yext.Location{ + Name: yext.String("Yext Consulting"), + EntityMeta: &yext.EntityMeta{ + //Id: yext.String("CTG2"), + EntityType: yext.ENTITYTYPE_LOCATION, + CategoryIds: &[]string{"2374", "708"}, + }, + MainPhone: yext.String("8888888888"), + // Address: &yext.Address{ + // Line1: yext.String("7900 Westpark"), + // City: yext.String("McLean"), + // Region: yext.String("VA"), + // PostalCode: yext.String("22102"), + // }, - morganEntity := entity.(*MorganLocation) - log.Printf("PROFILE description: %s", GetString(morganEntity.Description)) - log.Printf("CUSTOM multi: %s", GetString(morganEntity.CMultiText)) + FeaturedMessage: &yext.FeaturedMessage{ + Url: yext.String("www.yext.com"), + Description: yext.String("Yext Consulting"), + }, + }, + } + + _, err = client.EntityService.Create(morganEntity) + if err != nil { + log.Fatalf("Create error: %s", err) } + log.Printf("Create: Created %s", morganEntity.GetEntityId()) } diff --git a/entity.go b/entity.go index df9df73..dfff912 100644 --- a/entity.go +++ b/entity.go @@ -2,20 +2,22 @@ package yext type EntityType string +// This is a little dangerous because if you embed an Entity (like Location) within another Entity +// You don't have to re-define these...but we'd need to re-define copy... type Entity interface { - EntityId() string // Would this be necessary if it's always in the Core? - Type() EntityType - PathName() string + GetEntityId() string + GetEntityType() EntityType Copy() Entity } type EntityMeta struct { Id *string `json:"id,omitempty"` AccountId *string `json:"accountId,omitempty"` - EntityType *EntityType `json:"locationType,omitempty"` + EntityType EntityType `json:"entityType,omitempty"` FolderId *string `json:"folderId,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` CategoryIds *[]string `json:"categoryIds,omitempty"` Language *string `json:"language,omitempty"` CountryCode *string `json:"countryCode,omitempty"` + nilIsEmpty bool } diff --git a/entity_service.go b/entity_service.go index 98c1194..ec264e9 100644 --- a/entity_service.go +++ b/entity_service.go @@ -5,6 +5,7 @@ import ( "encoding/gob" "encoding/json" "fmt" + "log" "net/url" ) @@ -26,13 +27,12 @@ type EntityListOptions struct { ListOptions SearchID string ResolvePlaceholders bool - EntityTypes []EntityType } type EntityListResponse struct { Count int `json:"count"` Entities []interface{} `json:"entities"` - PageToken string `json:"nextPageToken"` + PageToken string `json:"pageToken"` } func (e *EntityService) RegisterEntity(entityType EntityType, entity Entity) { @@ -44,16 +44,9 @@ func (e *EntityService) LookupEntity(entityType EntityType) (Entity, error) { if !ok { return nil, fmt.Errorf("Unable to find entity type %s in entity registry %v", entityType, e.registry) } - // This "Copy" is pretty hacky...but works for now - return entity.Copy(), nil -} -func (e *EntityService) PathName(entityType EntityType) (string, error) { - entity, err := e.LookupEntity(entityType) - if err != nil { - return "", err - } - return entity.PathName(), nil + // TODO: This "Copy" is pretty hacky...but works for now + return entity.Copy(), nil } func GetBytes(key interface{}) ([]byte, error) { @@ -79,6 +72,7 @@ func (e *EntityService) toEntityTypes(entityInterfaces []interface{}) ([]Entity, } func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error) { + // Determine Entity Type var entityValsByKey = entityInterface.(map[string]interface{}) meta, ok := entityValsByKey["meta"] if !ok { @@ -96,6 +90,7 @@ func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error 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) @@ -105,15 +100,18 @@ func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error if err != nil { return nil, fmt.Errorf("Error unmarshaling entity JSON: %s", err) } + return entityObj, nil } +// TODO: Paging is not working here. Waiting on techops +// TODO: SearchID func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { var entities []Entity if opts == nil { opts = &EntityListOptions{} } - opts.ListOptions = ListOptions{Limit: LocationListMaxLimit} + opts.ListOptions = ListOptions{Limit: LocationListMaxLimit} // TODO: should this be EntityListMaxLimit var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { opts.ListOptions = *listOptions resp, _, err := e.List(opts) @@ -128,6 +126,7 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { for _, entity := range typedEntities { entities = append(entities, entity) } + log.Println(resp.PageToken) return resp.PageToken, err } @@ -164,11 +163,7 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res return nil, r, err } - // TODO: handle hyrdation and nil is empty - // if _, err := e.HydrateLocations(v.Locations); err != nil { - // return nil, r, err - // } - + // TODO: nil is empty // for _, l := range v.Entities { // l.nilIsEmpty = true // } @@ -176,76 +171,6 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res return v, nil, nil } -// TODO: This function is a stub -func (e *EntityService) ListAllOfType(opts *EntityListOptions, entityType EntityType) ([]Entity, error) { - var entities []Entity - if opts == nil { - opts = &EntityListOptions{} - } - opts.ListOptions = ListOptions{Limit: LocationListMaxLimit} - var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { - opts.ListOptions = *listOptions - resp, _, err := e.ListOfType(opts, entityType) - if err != nil { - return "", err - } - // for _, entity := range resp.Entities { - // entities = append(entities, entity) - // } - return resp.PageToken, err - } - - if err := tokenListHelper(lg, &opts.ListOptions); err != nil { - return nil, err - } else { - return entities, nil - } -} - -func (e *EntityService) ListOfType(opts *EntityListOptions, entityType EntityType) (*EntityListResponse, *Response, error) { - var ( - requrl string - err error - ) - - pathName, err := e.PathName(entityType) - if err != nil { - return nil, nil, err - } - requrl = pathName - - if opts != nil { - requrl, err = addEntityListOptions(requrl, opts) - if err != nil { - return nil, nil, err - } - } - - if opts != nil { - requrl, err = addListOptions(requrl, &opts.ListOptions) - if err != nil { - return nil, nil, err - } - } - - v := &EntityListResponse{} - r, err := e.client.DoRequest("GET", requrl, v) - if err != nil { - return nil, r, err - } - - // TODO: handle hydration and nil is empty - // if _, err := e.HydrateLocations(v.Locations); err != nil { - // return nil, r, err - // } - - // for _, l := range v.Entities { - // l.nilIsEmpty = true - // } - - return v, r, nil -} - func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error) { if opts == nil { return requrl, nil @@ -263,40 +188,42 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if opts.ResolvePlaceholders { q.Add("resolvePlaceholders", "true") } - if opts.EntityTypes != nil && len(opts.EntityTypes) > 0 { - // TODO: add entity types - } u.RawQuery = q.Encode() return u.String(), nil } -func (e *EntityService) Get(id string, entityType EntityType) (Entity, *Response, error) { - entity, err := e.LookupEntity(entityType) - if err != nil { - return nil, nil, err - } - r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entity.PathName(), id), entity) +func (e *EntityService) Get(id string) (Entity, *Response, error) { + var v interface{} + r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityPath, id), &v) if err != nil { return nil, r, err } - // TODO: handle hydration and nil is empty - // if _, err := HydrateLocation(&v, l.CustomFields); err != nil { - // return nil, r, err - // } + entity, err := e.toEntityType(v) + // TODO: nil is emtpy //v.nilIsEmpty = true return entity, r, nil } +// TODO: Currently an error with API. Need to test this func (e *EntityService) Create(y Entity) (*Response, error) { // TODO: custom field validation // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { // return nil, err // } - r, err := e.client.DoRequestJSON("POST", fmt.Sprintf("%s", y.PathName()), y, nil) + var requrl = entityPath + u, err := url.Parse(requrl) + if err != nil { + return nil, err + } + + q := u.Query() + q.Add("entityType", string(y.GetEntityType())) + u.RawQuery = q.Encode() + r, err := e.client.DoRequestJSON("POST", u.String(), y, nil) if err != nil { return r, err } @@ -304,12 +231,13 @@ func (e *EntityService) Create(y Entity) (*Response, error) { return r, nil } +// TODO: Sanket is including the Id in the request but we may have to remove other things like account func (e *EntityService) Edit(y Entity) (*Response, error) { // TODO: custom field validation // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { // return nil, err // } - r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", y.PathName(), y.EntityId()), y, nil) + r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, y.GetEntityId()), y, nil) if err != nil { return r, err } diff --git a/entity_test.go b/entity_test.go new file mode 100644 index 0000000..0727b22 --- /dev/null +++ b/entity_test.go @@ -0,0 +1,359 @@ +package yext + +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 + 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"` + CFTextList *[]string `json:"cf_TextList,omitempty"` + CFGallery []*Photo `json:"cf_Gallery,omitempty"` + CFPhoto *Photo `json:"cf_Photo,omitempty"` + CFVideos []*Video `json:"cf_Videos,omitempty"` + CFVideo *Video `json:"cf_Video,omitempty"` + CFDate *Date `json:"cf_Date,omitempty"` + CFSingleOption *string `json:"cf_SingleOtpion,omitempty"` + 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 { + return err, "" + } + + return nil, string(buf) +} + +func TestEntityJSONSerialization(t *testing.T) { + type test struct { + entity Entity + want string + } + + 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{CFUrl: String("")}, `{"cf_Url":""}`}, + {&CustomLocationEntity{CFUrl: nil}, `{}`}, + {&CustomLocationEntity{CFTextList: &[]string{}}, `{"cf_TextList":[]}`}, + {&CustomLocationEntity{CFTextList: nil}, `{}`}, + } + + for _, test := range tests { + if err, got := entityToJSONString(test.entity); err != nil { + t.Error("Unable to convert", test.entity, "to JSON:", err) + } else if got != test.want { + t.Errorf("json.Marshal()\nGot: %s\nExpected: %s", got, test.want) + } + } +} + +func TestEntityJSONDeserialization(t *testing.T) { + type test struct { + json string + want Entity + } + + tests := []test{ + {`{}`, &CustomLocationEntity{}}, + {`{"emails": []}`, &CustomLocationEntity{Location: Location{Emails: Strings([]string{})}}}, + {`{"emails": ["mhupman@yext.com", "bmcginnis@yext.com"]}`, &CustomLocationEntity{Location: Location{Emails: Strings([]string{"mhupman@yext.com", "bmcginnis@yext.com"})}}}, + {`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CFUrl: String("www.yext.com")}}, + {`{"cf_TextList": ["a", "b", "c"]}`, &CustomLocationEntity{CFTextList: Strings([]string{"a", "b", "c"})}}, + } + + for _, test := range tests { + v := &CustomLocationEntity{} + if err := json.Unmarshal([]byte(test.json), v); err != nil { + t.Error("Unable to deserialize", test.json, "from JSON:", err) + } else if !reflect.DeepEqual(v, test.want) { + t.Errorf("json.Unmarshal()\nGot: %s\nExpected: %s", v, test.want) + } + } +} + +func TestEntitySampleJSONResponseDeserialization(t *testing.T) { + + entityService := EntityService{ + registry: map[EntityType]Entity{ + ENTITYTYPE_LOCATION: &CustomLocationEntity{}, + }, + } + mapOfStringToInterface := make(map[string]interface{}) + err := json.Unmarshal([]byte(sampleEntityJSON), &mapOfStringToInterface) + if err != nil { + t.Errorf("Unable to unmarshal sample entity JSON. Err: %s", err) + } + if _, err := entityService.toEntityType(mapOfStringToInterface); err != nil { + t.Errorf("Unable to convert JSON to entity type. Err: %s", err) + } + // TODO: Add diff test +} + +var sampleEntityJSON = `{ + "additionalHoursText": "Some additional hours text", + "address": { + "line1": "7900 Westpark", + "city": "McLean", + "region": "VA", + "postalCode": "22102", + "extraDescription": "Galleria Shopping Center" + }, + "addressHidden": false, + "description": "This is my description", + "hours": { + "monday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "tuesday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "wednesday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "thursday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "friday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "sunday": [ + { + "start": "00:00", + "end": "23:59" + } + ], + "holidayHours": [ + { + "date": "2018-12-25", + "isRegularHours": false + } + ] + }, + "name": "Yext Consulting", + "cf_Hours": { + "monday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "tuesday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "wednesday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "thursday": [ + { + "start": "09:00", + "end": "17:00" + } + ], + "friday": [ + { + "start": "09:00", + "end": "14:00" + }, + { + "start": "15:00", + "end": "17:00" + } + ], + "saturday": [ + { + "start": "00:00", + "end": "23:59" + } + ], + "holidayHours": [ + { + "date": "2018-10-13", + "hours": [ + { + "start": "10:00", + "end": "16:00" + } + ], + "isRegularHours": false + } + ] + }, + "cf_DailyTimes": { + "monday": "09:00", + "tuesday": "09:00", + "wednesday": "09:00", + "thursday": "09:00", + "friday": "09:00", + "saturday": "09:00" + }, + "cf_Date": "2018-09-28", + "cf_Gallery": [ + { + "image": { + "url": "http://a.mktgcdn.com/p/CCCUglaMWv5i6Ede4KJuEFttpN416TTKGppUz-vcBqI/920x640.jpg", + "width": 920, + "height": 640, + "derivatives": [ + { + "url": "http://a.mktgcdn.com/p/CCCUglaMWv5i6Ede4KJuEFttpN416TTKGppUz-vcBqI/619x430.jpg", + "width": 619, + "height": 430 + }, + { + "url": "http://a.mktgcdn.com/p/CCCUglaMWv5i6Ede4KJuEFttpN416TTKGppUz-vcBqI/600x417.jpg", + "width": 600, + "height": 417 + }, + { + "url": "http://a.mktgcdn.com/p/CCCUglaMWv5i6Ede4KJuEFttpN416TTKGppUz-vcBqI/196x136.jpg", + "width": 196, + "height": 136 + } + ] + }, + "description": "Corgi Puppy" + }, + { + "image": { + "url": "http://a.mktgcdn.com/p/T88YcpE1osKn6higLxP5W0yYr3PU7iQJueQ29nsCFkA/103x103.jpg", + "width": 103, + "height": 103 + }, + "description": "[[name]]" + } + ], + "cf_MultiOpion": [ + "4118", + "8938" + ], + "cf_Photo": { + "image": { + "url": "http://a.mktgcdn.com/p/aYN_mOHTqqLfizsnR7b17ldLAPe5P3vX--wBkpTHx14/590x350.jpg", + "width": 590, + "height": 350, + "derivatives": [ + { + "url": "http://a.mktgcdn.com/p/aYN_mOHTqqLfizsnR7b17ldLAPe5P3vX--wBkpTHx14/196x116.jpg", + "width": 196, + "height": 116 + } + ] + } + }, + "cf_SingleOption": "3035", + "cf_TextList": [ + "List Item 1", + "List Item 2", + "List Item 3" + ], + "cf_Videos": [ + { + "video": { + "url": "http://www.youtube.com/watch?v=TYRDgd3Tb44" + }, + "description": "video description" + } + ], + "cf_Video": { + "video": { + "url": "http://www.youtube.com/watch?v=fC3Cthm0HFU" + }, + "description": "video description" + }, + "cf_Url": "http://yext.com/careers", + "emails": [ + "cdworak@yext.com" + ], + "featuredMessage": { + "description": "This is my featured message", + "url":"http://www.bestbuy.com/site/electronics/black-friday/pcmcat225600050002.c?id\u003dpcmcat225600050002\u0026ref\u003dNS\u0026loc\u003dns100" + }, + "isoRegionCode": "VA", + "mainPhone": "+18888888888", + "timezone": "America/New_York", + "websiteUrl": { + "url": "http://yext.com", + "displayUrl": "http://yext.com", + "preferDisplayUrl": false + }, + "yextDisplayCoordinate": { + "latitude": 38.92475, + "longitude": -77.21718 + }, + "yextRoutableCoordinate": { + "latitude": 38.9243983751914, + "longitude": -77.2178385786886 + }, + "meta": { + "accountId": "3549951188342570541", + "uid": "b3JxON", + "id": "CTG", + "categoryIds": [ + "668" + ], + "folderId": "0", + "language": "en", + "countryCode": "US", + "entityType": "LOCATION" + } +}` diff --git a/event.go b/event.go index ebfa688..696b724 100644 --- a/event.go +++ b/event.go @@ -19,14 +19,14 @@ type EventEntity struct { // TODO: rename EntityType EntityType `json:"entityType,omitempty"` } -func (e *EventEntity) EntityId() string { +func (e *EventEntity) GetEntityId() string { if e.Id != nil { return *e.Id } return "" } -func (e *EventEntity) Type() EntityType { +func (e *EventEntity) GetEntityType() EntityType { return ENTITYTYPE_EVENT } diff --git a/hydrate.go b/hydrate.go index 53517cf..c1ffefb 100644 --- a/hydrate.go +++ b/hydrate.go @@ -2,7 +2,7 @@ package yext import "fmt" -// TODO: This mutates the location, no need to return the value +// TODO: I think this can be deleted (unless we keep the location-service around) func HydrateLocation(loc *Location, customFields []*CustomField) (*Location, error) { if loc == nil || loc.CustomFields == nil || customFields == nil { return loc, nil diff --git a/language_profile_service.go b/language_profile_service.go index 6a47fb9..6c11d8c 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -31,9 +31,9 @@ func (l *LanguageProfileService) GetAll(id string) (*LanguageProfileListResponse return nil, r, err } - if _, err := l.HydrateLocations(v.LanguageProfiles); err != nil { - return nil, r, err - } + // if _, err := l.HydrateLocations(v.LanguageProfiles); err != nil { + // return nil, r, err + // } return &v, r, nil } @@ -45,9 +45,9 @@ func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageP return nil, r, err } - if _, err := HydrateLocation(&v.Location, l.CustomFields); err != nil { - return nil, r, err - } + // if _, err := HydrateLocation(&v.Location, l.CustomFields); err != nil { + // return nil, r, err + // } return &v, r, nil } @@ -88,17 +88,17 @@ func (l *LanguageProfileService) Delete(id string, languageCode string) (*Respon return r, nil } -func (l *LanguageProfileService) HydrateLocations(languageProfiles []*LanguageProfile) ([]*LanguageProfile, error) { - if l.CustomFields == nil { - return languageProfiles, nil - } - - for _, profile := range languageProfiles { - _, err := HydrateLocation(&profile.Location, l.CustomFields) - if err != nil { - return languageProfiles, err - } - } - - return languageProfiles, nil -} +// func (l *LanguageProfileService) HydrateLocations(languageProfiles []*LanguageProfile) ([]*LanguageProfile, error) { +// if l.CustomFields == nil { +// return languageProfiles, nil +// } +// +// // for _, profile := range languageProfiles { +// // // _, err := HydrateLocation(&profile.Location, l.CustomFields) +// // // if err != nil { +// // // return languageProfiles, err +// // // } +// // } +// +// return languageProfiles, nil +// } diff --git a/location.go b/location.go index 379ed55..7bcb5d9 100644 --- a/location.go +++ b/location.go @@ -17,7 +17,7 @@ const ENTITYTYPE_LOCATION EntityType = "LOCATION" // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { - EntityMeta + EntityMeta *EntityMeta `json:"meta,omitempty"` // Admin FolderId *string `json:"folderId,omitempty"` @@ -43,7 +43,7 @@ type Location struct { FaxPhone *string `json:"faxPhone,omitempty"` LocalPhone *string `json:"localPhone,omitempty"` MobilePhone *string `json:"mobilePhone,omitempty"` - Phone *string `json:"phone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` TollFreePhone *string `json:"tollFreePhone,omitempty"` TtyPhone *string `json:"ttyPhone,omitempty"` IsPhoneTracked *bool `json:"isPhoneTracked,omitempty"` @@ -64,20 +64,19 @@ type Location struct { Degrees *[]string `json:"degrees,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` - Hours *ProfileHours `json:"hours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - YearEstablished *float64 `json:"yearEstablished,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Certifications *[]string `json:"certifications,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Products *[]string `json:"products,omitempty"` - Services *[]string `json:"services,omitempty"` - Specialties *[]string `json:"specialties,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo *LocationPhoto `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Description *string `json:"description,omitempty"` + Hours *Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished *float64 `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + Specialties *[]string `json:"specialties,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo *LocationPhoto `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` @@ -177,27 +176,28 @@ type Coordinate struct { Longitude *float64 `json:"longitude,omitempty"` } -// TODO: Need to rename -type ProfileHours struct { - Monday []*DayHours `json:"monday,omitempty"` - Tuesday []*DayHours `json:"tuesday,omitempty"` - Wednesday []*DayHours `json:"wednesday,omitempty"` - Thursday []*DayHours `json:"thursday,omitempty"` - Friday []*DayHours `json:"friday,omitempty"` - Saturday []*DayHours `json:"saturday,omitempty"` - Sunday []*DayHours `json:"sunday,omitempty"` +type Hours struct { + Monday []*Times `json:"monday,omitempty"` + Tuesday []*Times `json:"tuesday,omitempty"` + Wednesday []*Times `json:"wednesday,omitempty"` + Thursday []*Times `json:"thursday,omitempty"` + Friday []*Times `json:"friday,omitempty"` + Saturday []*Times `json:"saturday,omitempty"` + Sunday []*Times `json:"sunday,omitempty"` + HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` } -type DayHours struct { - Start *string `json:"start,omitempty"` - End *string `json:"end,omitempty"` +// TODO: This will become OpenIntervals +type Times struct { + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` } -func (y *Location) EntityId() string { +func (y *Location) GetEntityId() string { return y.GetId() } -func (y *Location) Type() EntityType { +func (y *Location) GetEntityType() EntityType { return ENTITYTYPE_LOCATION } @@ -210,8 +210,8 @@ func (y *Location) Copy() Entity { } func (y Location) GetId() string { - if y.Id != nil { - return *y.Id + if y.EntityMeta != nil && y.EntityMeta.Id != nil { + return *y.EntityMeta.Id } return "" } @@ -294,8 +294,8 @@ func (y Location) GetDegrees() []string { } func (y Location) GetAccountId() string { - if y.AccountId != nil { - return *y.AccountId + if y.EntityMeta.AccountId != nil { + return *y.EntityMeta.AccountId } return "" } @@ -356,9 +356,9 @@ func (y Location) GetCountryCode() string { return "" } -func (y Location) GetPhone() string { - if y.Phone != nil { - return *y.Phone +func (y Location) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone } return "" } @@ -672,8 +672,8 @@ func (y Location) GetGoogleAttributes() GoogleAttributes { } func (y Location) GetHolidayHours() []HolidayHours { - if y.HolidayHours != nil { - return *y.HolidayHours + if y.Hours != nil && y.Hours.HolidayHours != nil { + return *y.Hours.HolidayHours } return nil } @@ -712,8 +712,8 @@ func (l LocationClosed) String() string { // HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager. // For details see type HolidayHours struct { - Date string `json:"date"` - Hours string `json:"hours"` + Date string `json:"date"` + Hours []*Times `json:"hours"` } // UnorderedStrings masks []string properties for which Order doesn't matter, such as LabelIds diff --git a/location_diff.go b/location_diff.go index e5d1304..a2f68b9 100644 --- a/location_diff.go +++ b/location_diff.go @@ -28,7 +28,7 @@ type Comparable interface { // // isDiff -> false // // delta -> nil func (y Location) Diff(b *Location) (d *Location, diff bool) { - d = &Location{EntityMeta: EntityMeta{ + d = &Location{EntityMeta: &EntityMeta{ Id: String(y.GetId())}} var ( diff --git a/location_diff_test.go b/location_diff_test.go index c6351fa..ae86fec 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -13,7 +13,7 @@ var examplePhoto = LocationPhoto{ } var complexOne = &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("lock206"), }, Name: String("Farmers Insurance - Stephen Lockhart "), @@ -63,7 +63,7 @@ var complexOne = &Location{ Region: String("NY"), PostalCode: String("14150-9425"), }, - Phone: String("716-835-0306"), + MainPhone: String("716-835-0306"), FaxPhone: String("716-835-0415"), YearEstablished: Float(2015), Emails: &[]string{"slockhart@farmersagent.com"}, @@ -82,7 +82,7 @@ var complexOne = &Location{ } var complexTwo = &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("lock206"), }, Name: String("Farmers Insurance - Stephen Lockhart "), @@ -132,7 +132,7 @@ var complexTwo = &Location{ Region: String("NY"), PostalCode: String("14150-9425"), }, - Phone: String("716-835-0306"), + MainPhone: String("716-835-0306"), FaxPhone: String("716-835-0415"), YearEstablished: Float(2015), Emails: &[]string{"slockhart@farmersagent.com"}, @@ -153,7 +153,7 @@ var complexTwo = &Location{ var jsonData string = `{"id":"phai514","locationName":"Farmers Insurance - Aroun Phaisan ","customFields":{"1857":"","1858":"122191","1859":"Aroun","1871":"Phaisan","3004":"Agent","7240":"Aroun Phaisan","7251":true,"7253":"1221","7254":"aphaisan","7255":"2685","7256":["phai514"],"7261":false,"7263":true,"7265":"","7266":"","7269":"91","7270":true,"7271":"21","7272":"User_Dup","7273":"Lincoln","7274":"NE","7275":"5730 R St Ste B","7276":"68505-2309","7277":"12","7278":false,"7279":true,"7283":"","7284":"","7285":"16133384","7286":"","7287":true,"7288":true,"7296":"16133384","7297":"","7298":"","7299":"786200","7300":true},"address": {"line1":"5730 R St","line2":"Ste B","city":"Lincoln","state":"NE","zip":"68505-2309"},"phone":"402-417-4266","faxPhone":"402-423-3141","yearEstablished":2011,"emails":["aphaisan@farmersagent.com"],"services":["Auto Insurance","Home Insurance","Homeowners Insurance","Business Insurance","Motorcyle Insurance","Recreational Insurance","Renters Insurance","Umbrella Insurance","Term Life Insurance","Whole Life Insurance"],"languages":["English"],"folderId":"91760"}` var baseLocation Location = Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("ding"), AccountId: String("ding"), }, @@ -167,7 +167,7 @@ var baseLocation Location = Location{ PostalCode: String("ding"), }, CountryCode: String("ding"), - Phone: String("ding"), + MainPhone: String("ding"), LocalPhone: String("ding"), AlternatePhone: String("ding"), FaxPhone: String("ding"), @@ -185,47 +185,47 @@ var baseLocation Location = Location{ ReservationUrl: &Website{ Url: String("ding"), }, - Hours: &ProfileHours{ - Monday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Hours: &Hours{ + Monday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Tuesday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Tuesday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Wednesday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Wednesday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Thursday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Thursday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Friday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Friday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Saturday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Saturday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Sunday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Sunday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, }, @@ -268,7 +268,7 @@ var baseLocation Location = Location{ func TestDiffIdentical(t *testing.T) { secondLocation := &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("ding"), AccountId: String("ding"), }, @@ -282,7 +282,7 @@ func TestDiffIdentical(t *testing.T) { PostalCode: String("ding"), }, CountryCode: String("ding"), - Phone: String("ding"), + MainPhone: String("ding"), LocalPhone: String("ding"), AlternatePhone: String("ding"), FaxPhone: String("ding"), @@ -300,47 +300,47 @@ func TestDiffIdentical(t *testing.T) { ReservationUrl: &Website{ Url: String("ding"), }, - Hours: &ProfileHours{ - Monday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Hours: &Hours{ + Monday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Tuesday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Tuesday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Wednesday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Wednesday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Thursday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Thursday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Friday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Friday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Saturday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Saturday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, - Sunday: []*DayHours{ - &DayHours{ - Start: String("ding"), - End: String("ding"), + Sunday: []*Times{ + &Times{ + Start: "ding", + End: "ding", }, }, }, @@ -778,8 +778,8 @@ func (t floatTest) formatErrorBase(index int) string { func TestFloatDiffs(t *testing.T) { a, b := *new(Location), new(Location) for i, data := range floatTests { - a.DisplayCoordinate = &Coordinate{Latitude: data.baseValue} - b.DisplayCoordinate = &Coordinate{Latitude: data.newValue} + a.YearEstablished = data.baseValue + b.YearEstablished = data.newValue a.nilIsEmpty, b.nilIsEmpty = data.nilIsEmpty, data.nilIsEmpty d, isDiff := a.Diff(b) if isDiff != data.isDiff { @@ -791,7 +791,7 @@ func TestFloatDiffs(t *testing.T) { t.Errorf("%v\ndelta was nil but expected %v\n", data.formatErrorBase(i), formatFloatPtr(data.expectedFieldValue)) } else if d != nil && data.expectedFieldValue == nil { t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) - } else if *d.DisplayCoordinate.Latitude != *data.expectedFieldValue { + } else if *d.YearEstablished != *data.expectedFieldValue { t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) } } @@ -1362,13 +1362,13 @@ func TestLabels(t *testing.T) { tests = []Scenario{ Scenario{ A: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &one, }, B: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &two, @@ -1378,13 +1378,13 @@ func TestLabels(t *testing.T) { }, Scenario{ A: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &one, }, B: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &three, @@ -1394,19 +1394,19 @@ func TestLabels(t *testing.T) { }, Scenario{ A: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &one, }, B: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &four, }, WantDelta: &Location{ - EntityMeta: EntityMeta{ + EntityMeta: &EntityMeta{ Id: String("1"), }, LabelIds: &four, @@ -1445,7 +1445,7 @@ func TestLocationNils(t *testing.T) { func TestLocationCustomFieldEmptyComparision(t *testing.T) { a, b := *new(Location), new(Location) - a.Id = String("blah") + a.EntityMeta = &EntityMeta{Id: String("blah")} b.CustomFields = map[string]interface{}{} a.hydrated, b.hydrated = true, true @@ -1458,13 +1458,14 @@ func TestLocationCustomFieldEmptyComparision(t *testing.T) { func TestCustomFieldPointerComparison(t *testing.T) { a, b := *new(Location), new(Location) - a.Id = String("blah") - a.CustomFields = map[string]interface{}{ - "1": Hours{Hours: "1:09:00:18:00"}, - } - b.CustomFields = map[string]interface{}{ - "1": &Hours{Hours: "1:09:00:18:00"}, - } + a.EntityMeta = &EntityMeta{Id: String("blah")} + // TODO: Fix + // a.CustomFields = map[string]interface{}{ + // "1": Hours{Hours: "1:09:00:18:00"}, + // } + // b.CustomFields = map[string]interface{}{ + // "1": &Hours{Hours: "1:09:00:18:00"}, + // } a.hydrated, b.hydrated = true, true d, isDiff := a.Diff(b) diff --git a/location_service_test.go b/location_service_test.go index b725d7a..54cc231 100644 --- a/location_service_test.go +++ b/location_service_test.go @@ -87,7 +87,7 @@ func makeLocs(n int) []*Location { var locs []*Location for i := 0; i < n; i++ { - new := &Location{EntityMeta: EntityMeta{ + new := &Location{EntityMeta: &EntityMeta{ Id: String(strconv.Itoa(i))}} locs = append(locs, new) } diff --git a/location_test.go b/location_test.go index 5b7ce3b..546abad 100644 --- a/location_test.go +++ b/location_test.go @@ -29,8 +29,8 @@ func TestJSONSerialization(t *testing.T) { {&Location{Languages: nil}, `{}`}, {&Location{Languages: &[]string{}}, `{"languages":[]}`}, {&Location{Languages: &[]string{"English"}}, `{"languages":["English"]}`}, - {&Location{HolidayHours: nil}, `{}`}, - {&Location{HolidayHours: &[]HolidayHours{}}, `{"holidayHours":[]}`}, + {&Location{Photos: nil}, `{}`}, + {&Location{Photos: &[]LocationPhoto{}}, `{"photos":[]}`}, } for _, test := range tests { From 64cfaba5e3150ab6cb6d3cf2b868887ca99c7576 Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 3 Oct 2018 23:46:12 -0400 Subject: [PATCH 006/285] Getting registry to work without Copy() --- client.go | 3 +- entitiestest/main.go | 101 ------------------------------------------- entity.go | 1 - entity_service.go | 45 +++++++++++-------- entity_test.go | 7 +-- event.go | 10 ----- location.go | 19 +------- 7 files changed, 30 insertions(+), 156 deletions(-) delete mode 100644 entitiestest/main.go diff --git a/client.go b/client.go index 014806c..accaa72 100644 --- a/client.go +++ b/client.go @@ -52,8 +52,7 @@ func NewClient(config *Config) *Client { c.LanguageProfileService = &LanguageProfileService{client: c} c.AssetService = &AssetService{client: c} c.AnalyticsService = &AnalyticsService{client: c} - c.EntityService = &EntityService{client: c, registry: YextEntityRegistry} - + c.EntityService = &EntityService{client: c} return c } diff --git a/entitiestest/main.go b/entitiestest/main.go deleted file mode 100644 index 39dcf65..0000000 --- a/entitiestest/main.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "log" - - "github.com/mohae/deepcopy" - yext "gopkg.in/yext/yext-go.v2" -) - -type MorganLocation struct { - yext.Location - CMultiText *string `json:"c_multiText"` -} - -//TODO: Revisit this...what if m.Location is nil? -func (m *MorganLocation) GetEntityId() string { - return m.Location.GetEntityId() -} - -func (m *MorganLocation) GetEntityType() yext.EntityType { - return m.Location.GetEntityType() -} - -func (m *MorganLocation) Copy() yext.Entity { - return deepcopy.Copy(m).(*MorganLocation) -} - -func main() { - client := yext.NewClient(yext.NewDefaultConfig().WithApiKey("e929153c956b051cea51ec289bfd2383")) - client.EntityService.RegisterEntity(yext.ENTITYTYPE_LOCATION, &MorganLocation{}) - // entities, err := client.EntityService.ListAll(nil) - // if err != nil { - // log.Fatal(err) - // } - // - // log.Printf("ListAll: Got %d entities", len(entities)) - //for _, entity := range entities { - //morganEntity := entity.(*MorganLocation) - //log.Printf("PROFILE description: %s", GetString(morganEntity.Description)) - //log.Printf("CUSTOM multi: %s", GetString(morganEntity.CMultiText)) - //} - - entity, _, err := client.EntityService.Get("CTG") - if err != nil { - log.Fatal(err) - } - morganEntity := entity.(*MorganLocation) - log.Printf("Get: Got %s", morganEntity.GetName()) - - // morganEntity.Name = yext.String(morganEntity.GetName() + "- 1") - // update := &MorganLocation{ - // Location: yext.Location{ - // // EntityMeta: yext.EntityMeta{ - // // Id: yext.String("CTG"), - // // }, - // Name: yext.String("CTG"), - // }, - // } - // _, err = client.EntityService.Edit(update) - // if err != nil { - // log.Fatal(err) - // } - // log.Printf("Edit: Edited %s", morganEntity.GetName()) - - morganEntity = &MorganLocation{ - Location: yext.Location{ - Name: yext.String("Yext Consulting"), - EntityMeta: &yext.EntityMeta{ - //Id: yext.String("CTG2"), - EntityType: yext.ENTITYTYPE_LOCATION, - CategoryIds: &[]string{"2374", "708"}, - }, - MainPhone: yext.String("8888888888"), - // Address: &yext.Address{ - // Line1: yext.String("7900 Westpark"), - // City: yext.String("McLean"), - // Region: yext.String("VA"), - // PostalCode: yext.String("22102"), - // }, - - FeaturedMessage: &yext.FeaturedMessage{ - Url: yext.String("www.yext.com"), - Description: yext.String("Yext Consulting"), - }, - }, - } - - _, err = client.EntityService.Create(morganEntity) - if err != nil { - log.Fatalf("Create error: %s", err) - } - log.Printf("Create: Created %s", morganEntity.GetEntityId()) - -} - -func GetString(s *string) string { - if s == nil { - return "" - } - return *s -} diff --git a/entity.go b/entity.go index dfff912..06b854b 100644 --- a/entity.go +++ b/entity.go @@ -7,7 +7,6 @@ type EntityType string type Entity interface { GetEntityId() string GetEntityType() EntityType - Copy() Entity } type EntityMeta struct { diff --git a/entity_service.go b/entity_service.go index ec264e9..186f2d8 100644 --- a/entity_service.go +++ b/entity_service.go @@ -5,22 +5,15 @@ import ( "encoding/gob" "encoding/json" "fmt" - "log" "net/url" + "reflect" ) const entityPath = "entities" -type EntityRegistry map[EntityType]Entity - -var YextEntityRegistry = EntityRegistry{ - ENTITYTYPE_EVENT: &EventEntity{}, - ENTITYTYPE_LOCATION: &Location{}, -} - type EntityService struct { client *Client - registry map[EntityType]Entity + registry map[EntityType]interface{} } type EntityListOptions struct { @@ -35,18 +28,33 @@ type EntityListResponse struct { PageToken string `json:"pageToken"` } -func (e *EntityService) RegisterEntity(entityType EntityType, entity Entity) { - e.registry[entityType] = entity +func (e *EntityService) RegisterDefaultEntities() { + e.registry = make(map[EntityType]interface{}) + e.RegisterEntity(ENTITYTYPE_LOCATION, &Location{}) + e.RegisterEntity(ENTITYTYPE_EVENT, &Event{}) +} + +func (e *EntityService) RegisterEntity(entityType EntityType, entity interface{}) { + //From: https://github.com/reggo/reggo/blob/master/common/common.go#L169 + isPtr := reflect.ValueOf(entity).Kind() == reflect.Ptr + var newVal interface{} + var tmp interface{} + if isPtr { + tmp = reflect.ValueOf(entity).Elem().Interface() + } else { + tmp = entity + } + newVal = reflect.New(reflect.TypeOf(tmp)).Elem().Interface() + e.registry[entityType] = newVal } -func (e *EntityService) LookupEntity(entityType EntityType) (Entity, error) { - entity, ok := e.registry[entityType] +func (e *EntityService) LookupEntity(entityType EntityType) (interface{}, error) { + val, ok := e.registry[entityType] if !ok { return nil, fmt.Errorf("Unable to find entity type %s in entity registry %v", entityType, e.registry) } - // TODO: This "Copy" is pretty hacky...but works for now - return entity.Copy(), nil + return reflect.New(reflect.TypeOf(val)).Interface(), nil } func GetBytes(key interface{}) ([]byte, error) { @@ -96,12 +104,12 @@ func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error return nil, fmt.Errorf("Error marshaling entity to JSON: %s", err) } - err = json.Unmarshal(entityJSON, entityObj) + err = json.Unmarshal(entityJSON, &entityObj) if err != nil { return nil, fmt.Errorf("Error unmarshaling entity JSON: %s", err) } - return entityObj, nil + return entityObj.(Entity), nil } // TODO: Paging is not working here. Waiting on techops @@ -126,7 +134,6 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { for _, entity := range typedEntities { entities = append(entities, entity) } - log.Println(resp.PageToken) return resp.PageToken, err } @@ -231,7 +238,7 @@ func (e *EntityService) Create(y Entity) (*Response, error) { return r, nil } -// TODO: Sanket is including the Id in the request but we may have to remove other things like account +// TODO: There is an outstanding techops QA issue to allow the Id in the request but we may have to remove other things like account func (e *EntityService) Edit(y Entity) (*Response, error) { // TODO: custom field validation // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { diff --git a/entity_test.go b/entity_test.go index 0727b22..8dbaaa5 100644 --- a/entity_test.go +++ b/entity_test.go @@ -111,12 +111,10 @@ func TestEntityJSONDeserialization(t *testing.T) { } func TestEntitySampleJSONResponseDeserialization(t *testing.T) { - entityService := EntityService{ - registry: map[EntityType]Entity{ - ENTITYTYPE_LOCATION: &CustomLocationEntity{}, - }, + registry: map[EntityType]interface{}{}, } + entityService.RegisterEntity("LOCATION", &CustomLocationEntity{}) mapOfStringToInterface := make(map[string]interface{}) err := json.Unmarshal([]byte(sampleEntityJSON), &mapOfStringToInterface) if err != nil { @@ -125,7 +123,6 @@ func TestEntitySampleJSONResponseDeserialization(t *testing.T) { if _, err := entityService.toEntityType(mapOfStringToInterface); err != nil { t.Errorf("Unable to convert JSON to entity type. Err: %s", err) } - // TODO: Add diff test } var sampleEntityJSON = `{ diff --git a/event.go b/event.go index 696b724..658c52f 100644 --- a/event.go +++ b/event.go @@ -2,8 +2,6 @@ package yext import ( "encoding/json" - - "github.com/mohae/deepcopy" ) const ( @@ -30,14 +28,6 @@ func (e *EventEntity) GetEntityType() EntityType { return ENTITYTYPE_EVENT } -func (e *EventEntity) PathName() string { - return EntityPathNameEvents -} - -func (y *EventEntity) Copy() Entity { - return deepcopy.Copy(y).(*EventEntity) -} - func (e *EventEntity) String() string { b, _ := json.Marshal(e) return string(b) diff --git a/location.go b/location.go index 7bcb5d9..d69e731 100644 --- a/location.go +++ b/location.go @@ -8,8 +8,6 @@ package yext import ( "encoding/json" "fmt" - - "github.com/mohae/deepcopy" ) const ENTITYTYPE_LOCATION EntityType = "LOCATION" @@ -187,7 +185,7 @@ type Hours struct { HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` } -// TODO: This will become OpenIntervals +// TODO: *Times will become *OpenIntervals after Techops change type Times struct { Start string `json:"start,omitempty"` End string `json:"end,omitempty"` @@ -201,14 +199,6 @@ func (y *Location) GetEntityType() EntityType { return ENTITYTYPE_LOCATION } -func (y *Location) PathName() string { - return locationsPath -} - -func (y *Location) Copy() Entity { - return deepcopy.Copy(y).(*Location) -} - func (y Location) GetId() string { if y.EntityMeta != nil && y.EntityMeta.Id != nil { return *y.EntityMeta.Id @@ -216,13 +206,6 @@ func (y Location) GetId() string { return "" } -// func (y Location) GetEntityType() string { -// if y.EntityType != nil { -// return *y.EntityType -// } -// return "" -// } - func (y Location) GetName() string { if y.Name != nil { return *y.Name From 2ed61e8dcab91351eb05e6cf406aea83b74a3a82 Mon Sep 17 00:00:00 2001 From: cdworak Date: Thu, 4 Oct 2018 00:30:17 -0400 Subject: [PATCH 007/285] pull out registry --- entity_service.go | 24 ++++-------------------- entity_test.go | 2 +- registry.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 registry.go diff --git a/entity_service.go b/entity_service.go index 186f2d8..1ef0921 100644 --- a/entity_service.go +++ b/entity_service.go @@ -6,14 +6,13 @@ import ( "encoding/json" "fmt" "net/url" - "reflect" ) const entityPath = "entities" type EntityService struct { client *Client - registry map[EntityType]interface{} + registry Registry } type EntityListOptions struct { @@ -29,32 +28,17 @@ type EntityListResponse struct { } func (e *EntityService) RegisterDefaultEntities() { - e.registry = make(map[EntityType]interface{}) + e.registry = make(Registry) e.RegisterEntity(ENTITYTYPE_LOCATION, &Location{}) e.RegisterEntity(ENTITYTYPE_EVENT, &Event{}) } func (e *EntityService) RegisterEntity(entityType EntityType, entity interface{}) { - //From: https://github.com/reggo/reggo/blob/master/common/common.go#L169 - isPtr := reflect.ValueOf(entity).Kind() == reflect.Ptr - var newVal interface{} - var tmp interface{} - if isPtr { - tmp = reflect.ValueOf(entity).Elem().Interface() - } else { - tmp = entity - } - newVal = reflect.New(reflect.TypeOf(tmp)).Elem().Interface() - e.registry[entityType] = newVal + e.registry.Register(string(entityType), entity) } func (e *EntityService) LookupEntity(entityType EntityType) (interface{}, error) { - val, ok := e.registry[entityType] - if !ok { - return nil, fmt.Errorf("Unable to find entity type %s in entity registry %v", entityType, e.registry) - } - - return reflect.New(reflect.TypeOf(val)).Interface(), nil + return e.registry.Lookup(string(entityType)) } func GetBytes(key interface{}) ([]byte, error) { diff --git a/entity_test.go b/entity_test.go index 8dbaaa5..0a87149 100644 --- a/entity_test.go +++ b/entity_test.go @@ -112,7 +112,7 @@ func TestEntityJSONDeserialization(t *testing.T) { func TestEntitySampleJSONResponseDeserialization(t *testing.T) { entityService := EntityService{ - registry: map[EntityType]interface{}{}, + registry: make(Registry), } entityService.RegisterEntity("LOCATION", &CustomLocationEntity{}) mapOfStringToInterface := make(map[string]interface{}) diff --git a/registry.go b/registry.go new file mode 100644 index 0000000..14a9bb2 --- /dev/null +++ b/registry.go @@ -0,0 +1,30 @@ +package yext + +import ( + "fmt" + "reflect" +) + +type Registry map[string]interface{} + +func (r Registry) Register(key string, val interface{}) { + //From: https://github.com/reggo/reggo/blob/master/common/common.go#L169 + isPtr := reflect.ValueOf(val).Kind() == reflect.Ptr + var newVal interface{} + var tmp interface{} + if isPtr { + tmp = reflect.ValueOf(val).Elem().Interface() + } else { + tmp = val + } + newVal = reflect.New(reflect.TypeOf(tmp)).Elem().Interface() + r[key] = newVal +} + +func (r Registry) Lookup(key string) (interface{}, error) { + val, ok := r[key] + if !ok { + return nil, fmt.Errorf("Unable to find key %s in registry %v", key, r) + } + return reflect.New(reflect.TypeOf(val)).Interface(), nil +} From d1ad58ea74893007600e1fb0aa720814510f1ad4 Mon Sep 17 00:00:00 2001 From: cdworak Date: Thu, 4 Oct 2018 00:46:16 -0400 Subject: [PATCH 008/285] Cleaning up --- client.go | 2 +- customfield_service.go | 1 - entity.go | 2 -- entity_service.go | 2 +- event.go | 18 ++++++++--------- hydrate.go | 1 - language_profile_service.go | 40 ++++++++++++++++++------------------- location.go | 2 +- location_diff.go | 4 +++- 9 files changed, 34 insertions(+), 38 deletions(-) diff --git a/client.go b/client.go index accaa72..b163bdb 100644 --- a/client.go +++ b/client.go @@ -337,7 +337,7 @@ func addListOptions(requrl string, opts *ListOptions) (string, error) { q.Add("limit", strconv.Itoa(opts.Limit)) } if opts.PageToken != "" { - // TODO: this is not compatible with the old parameter name + // TODO: Outstanding techops issue because this seems to have changed to page_token (similar to api_key) q.Add("pageToken", opts.PageToken) } else if opts.Offset != 0 { q.Add("offset", strconv.Itoa(opts.Offset)) diff --git a/customfield_service.go b/customfield_service.go index 8d91363..307af55 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -531,7 +531,6 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st return parsed, nil } -// TODO: Can we do this anymore? func validateCustomFieldsKeys(cfs map[string]interface{}) error { for k, _ := range cfs { if !customFieldKeyRegex.MatchString(k) { diff --git a/entity.go b/entity.go index 06b854b..13f93df 100644 --- a/entity.go +++ b/entity.go @@ -2,8 +2,6 @@ package yext type EntityType string -// This is a little dangerous because if you embed an Entity (like Location) within another Entity -// You don't have to re-define these...but we'd need to re-define copy... type Entity interface { GetEntityId() string GetEntityType() EntityType diff --git a/entity_service.go b/entity_service.go index 1ef0921..8e981aa 100644 --- a/entity_service.go +++ b/entity_service.go @@ -97,7 +97,7 @@ func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error } // TODO: Paging is not working here. Waiting on techops -// TODO: SearchID +// TODO: Add List for SearchID (similar to location-service). Follow up with Techops to see if SearchID is implemented func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { var entities []Entity if opts == nil { diff --git a/event.go b/event.go index 658c52f..244a81d 100644 --- a/event.go +++ b/event.go @@ -4,17 +4,15 @@ import ( "encoding/json" ) -const ( - ENTITYTYPE_EVENT EntityType = "EVENT" - EntityPathNameEvents = "events" // TODO: rename -) +const ENTITYTYPE_EVENT EntityType = "EVENT" -type EventEntity struct { // TODO: rename - //EntityMeta - Id *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - EntityType EntityType `json:"entityType,omitempty"` +// 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 { diff --git a/hydrate.go b/hydrate.go index c1ffefb..e61088b 100644 --- a/hydrate.go +++ b/hydrate.go @@ -2,7 +2,6 @@ package yext import "fmt" -// TODO: I think this can be deleted (unless we keep the location-service around) func HydrateLocation(loc *Location, customFields []*CustomField) (*Location, error) { if loc == nil || loc.CustomFields == nil || customFields == nil { return loc, nil diff --git a/language_profile_service.go b/language_profile_service.go index 6c11d8c..6a47fb9 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -31,9 +31,9 @@ func (l *LanguageProfileService) GetAll(id string) (*LanguageProfileListResponse return nil, r, err } - // if _, err := l.HydrateLocations(v.LanguageProfiles); err != nil { - // return nil, r, err - // } + if _, err := l.HydrateLocations(v.LanguageProfiles); err != nil { + return nil, r, err + } return &v, r, nil } @@ -45,9 +45,9 @@ func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageP return nil, r, err } - // if _, err := HydrateLocation(&v.Location, l.CustomFields); err != nil { - // return nil, r, err - // } + if _, err := HydrateLocation(&v.Location, l.CustomFields); err != nil { + return nil, r, err + } return &v, r, nil } @@ -88,17 +88,17 @@ func (l *LanguageProfileService) Delete(id string, languageCode string) (*Respon return r, nil } -// func (l *LanguageProfileService) HydrateLocations(languageProfiles []*LanguageProfile) ([]*LanguageProfile, error) { -// if l.CustomFields == nil { -// return languageProfiles, nil -// } -// -// // for _, profile := range languageProfiles { -// // // _, err := HydrateLocation(&profile.Location, l.CustomFields) -// // // if err != nil { -// // // return languageProfiles, err -// // // } -// // } -// -// return languageProfiles, nil -// } +func (l *LanguageProfileService) HydrateLocations(languageProfiles []*LanguageProfile) ([]*LanguageProfile, error) { + if l.CustomFields == nil { + return languageProfiles, nil + } + + for _, profile := range languageProfiles { + _, err := HydrateLocation(&profile.Location, l.CustomFields) + if err != nil { + return languageProfiles, err + } + } + + return languageProfiles, nil +} diff --git a/location.go b/location.go index d69e731..c64ee00 100644 --- a/location.go +++ b/location.go @@ -154,7 +154,7 @@ type Address struct { Line2 *string `json:"line2,omitempty"` City *string `json:"city,omitempty"` Region *string `json:"region,omitempty"` - Sublocality *string `json:"sublocality,omitempty"` // check on this one?? + Sublocality *string `json:"sublocality,omitempty"` PostalCode *string `json:"postalCode,omitempty"` } diff --git a/location_diff.go b/location_diff.go index a2f68b9..7bb08d4 100644 --- a/location_diff.go +++ b/location_diff.go @@ -8,6 +8,8 @@ type Comparable interface { Equal(Comparable) bool } +// TODO: Need to fix the comments below and update this to work with the new EntityMeta structure + // Diff calculates the differences between a base Location (a) and a proposed set of changes // represented by a second Location (b). The diffing logic will ignore fields in the proposed // Location that aren't set (nil). This characteristic makes the function ideal for @@ -48,7 +50,7 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { continue } - // Issue here because EntityMeta is an embedded struct + // TODO: Issue here because EntityMeta is an embedded struct if nameA == "Id" || nameA == "EntityMeta" || valB.IsNil() { continue } From f28a871352054ec6d2fc39675067b4100ca99cd8 Mon Sep 17 00:00:00 2001 From: cdworak Date: Thu, 4 Oct 2018 10:40:57 -0400 Subject: [PATCH 009/285] PR fixes --- client.go | 1 + customfield.go | 21 ++ customfield_service.go | 3 +- customfield_service_test.go | 42 ++- entity.go | 21 +- entity_service.go | 47 +-- entity_test.go | 45 +-- event.go | 22 +- language_profile_service.go | 2 +- location.go | 243 ++++++------- location_diff.go | 26 +- location_diff_test.go | 551 +++++++++++------------------ location_entity.go | 667 ++++++++++++++++++++++++++++++++++++ location_service.go | 4 +- location_service_test.go | 3 +- location_test.go | 31 +- registry.go | 21 +- 17 files changed, 1138 insertions(+), 612 deletions(-) create mode 100644 location_entity.go diff --git a/client.go b/client.go index b163bdb..46b2610 100644 --- a/client.go +++ b/client.go @@ -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 } diff --git a/customfield.go b/customfield.go index 701923a..f6abf3c 100644 --- a/customfield.go +++ b/customfield.go @@ -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"` diff --git a/customfield_service.go b/customfield_service.go index 307af55..ff7cb33 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -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)) diff --git a/customfield_service_test.go b/customfield_service_test.go index 9e5fb88..8708270 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -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{}{ @@ -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", @@ -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"}, }, diff --git a/entity.go b/entity.go index 13f93df..582f882 100644 --- a/entity.go +++ b/entity.go @@ -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 "" } diff --git a/entity_service.go b/entity_service.go index 8e981aa..8e4517b 100644 --- a/entity_service.go +++ b/entity_service.go @@ -1,8 +1,6 @@ package yext import ( - "bytes" - "encoding/gob" "encoding/json" "fmt" "net/url" @@ -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) @@ -77,7 +65,8 @@ 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 } @@ -85,14 +74,13 @@ func (e *EntityService) toEntityType(entityInterface interface{}) (Entity, error // 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 } @@ -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 diff --git a/entity_test.go b/entity_test.go index 0a87149..e0d117a 100644 --- a/entity_test.go +++ b/entity_test.go @@ -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"` @@ -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 { @@ -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":[]}`}, @@ -94,8 +71,8 @@ func TestEntityJSONDeserialization(t *testing.T) { tests := []test{ {`{}`, &CustomLocationEntity{}}, - {`{"emails": []}`, &CustomLocationEntity{Location: Location{Emails: Strings([]string{})}}}, - {`{"emails": ["mhupman@yext.com", "bmcginnis@yext.com"]}`, &CustomLocationEntity{Location: Location{Emails: Strings([]string{"mhupman@yext.com", "bmcginnis@yext.com"})}}}, + {`{"emails": []}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{})}}}, + {`{"emails": ["mhupman@yext.com", "bmcginnis@yext.com"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{"mhupman@yext.com", "bmcginnis@yext.com"})}}}, {`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CFUrl: String("www.yext.com")}}, {`{"cf_TextList": ["a", "b", "c"]}`, &CustomLocationEntity{CFTextList: Strings([]string{"a", "b", "c"})}}, } diff --git a/event.go b/event.go index 244a81d..5759dd6 100644 --- a/event.go +++ b/event.go @@ -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 { diff --git a/language_profile_service.go b/language_profile_service.go index 6a47fb9..d2d5665 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -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) diff --git a/location.go b/location.go index c64ee00..8d32ee5 100644 --- a/location.go +++ b/location.go @@ -10,14 +10,13 @@ import ( "fmt" ) -const ENTITYTYPE_LOCATION EntityType = "LOCATION" - // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { - EntityMeta *EntityMeta `json:"meta,omitempty"` - // Admin + Id *string `json:"id,omitempty"` + AccountId *string `json:"accountId,omitempty"` + LocationType *string `json:"locationType,omitempty"` FolderId *string `json:"folderId,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` CategoryIds *[]string `json:"categoryIds,omitempty"` @@ -30,18 +29,23 @@ type Location struct { nilIsEmpty bool // Address Fields - Name *string `json:"name,omitempty"` - Address *Address `json:"address,omitempty"` - DisplayAddress *string `json:"displayAddress,omitempty"` - CountryCode *string `json:"countryCode,omitempty"` - SuppressAddress *bool `json:"suppressAddress,omitempty"` + Name *string `json:"locationName,omitempty"` + Address *string `json:"address,omitempty"` + Address2 *string `json:"address2,omitempty"` + DisplayAddress *string `json:"displayAddress,omitempty"` + City *string `json:"city,omitempty"` + State *string `json:"state,omitempty"` + Sublocality *string `json:"sublocality,omitempty"` + Zip *string `json:"zip,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` + SuppressAddress *bool `json:"suppressAddress,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` FaxPhone *string `json:"faxPhone,omitempty"` LocalPhone *string `json:"localPhone,omitempty"` MobilePhone *string `json:"mobilePhone,omitempty"` - MainPhone *string `json:"mainPhone,omitempty"` + Phone *string `json:"phone,omitempty"` TollFreePhone *string `json:"tollFreePhone,omitempty"` TtyPhone *string `json:"ttyPhone,omitempty"` IsPhoneTracked *bool `json:"isPhoneTracked,omitempty"` @@ -62,30 +66,32 @@ type Location struct { Degrees *[]string `json:"degrees,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours *Hours `json:"hours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - YearEstablished *float64 `json:"yearEstablished,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Certifications *[]string `json:"certifications,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Products *[]string `json:"products,omitempty"` - Services *[]string `json:"services,omitempty"` - Specialties *[]string `json:"specialties,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo *LocationPhoto `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Description *string `json:"description,omitempty"` + HolidayHours *[]LocationHolidayHours `json:"holidayHours,omitempty"` + Hours *string `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished *string `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + Specialties *[]string `json:"specialties,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo *LocationPhoto `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs - DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` - // TODO: Update below - DropoffLat *float64 `json:"dropoffLat,omitempty"` - DropoffLng *float64 `json:"dropoffLng,omitempty"` - WalkableLat *float64 `json:"walkableLat,omitempty"` - WalkableLng *float64 `json:"walkableLng,omitempty"` - RoutableCoordinate *Coordinate `json:"yextRoutableCoordinate,omitempty"` - PickupLat *float64 `json:"pickupLat,omitempty"` - PickupLng *float64 `json:"pickupLng,omitempty"` + DisplayLat *float64 `json:"displayLat,omitempty"` + DisplayLng *float64 `json:"displayLng,omitempty"` + DropoffLat *float64 `json:"dropoffLat,omitempty"` + DropoffLng *float64 `json:"dropoffLng,omitempty"` + WalkableLat *float64 `json:"walkableLat,omitempty"` + WalkableLng *float64 `json:"walkableLng,omitempty"` + RoutableLat *float64 `json:"routableLat,omitempty"` + RoutableLng *float64 `json:"routableLng,omitempty"` + PickupLat *float64 `json:"pickupLat,omitempty"` + PickupLng *float64 `json:"pickupLng,omitempty"` // ECLS BioListIds *[]string `json:"bioListIds,omitempty"` @@ -98,11 +104,16 @@ type Location struct { ProductListsLabel *string `json:"productListsLabel,omitempty"` // Urls - MenuUrl *Website `json:"menuUrl,omitempty"` - OrderUrl *Website `json:"orderUrl,omitempty"` - ReservationUrl *Website `json:"reservationUrl,omitempty"` - WebsiteUrl *Website `json:"websiteUrl,omitempty"` - FeaturedMessage *FeaturedMessage `json:"featuredMessage,omitempty"` + MenuUrl *string `json:"menuUrl,omitempty"` + DisplayMenuUrl *string `json:"displayMenuUrl,omitempty"` + OrderUrl *string `json:"orderUrl,omitempty"` + DisplayOrderUrl *string `json:"displayOrderUrl,omitempty"` + ReservationUrl *string `json:"reservationUrl,omitempty"` + DisplayReservationUrl *string `json:"displayReservationUrl,omitempty"` + DisplayWebsiteUrl *string `json:"displayWebsiteUrl,omitempty"` + WebsiteUrl *string `json:"websiteUrl,omitempty"` + FeaturedMessage *string `json:"featuredMessage,omitempty"` + FeaturedMessageUrl *string `json:"featuredMessageUrl,omitempty"` // Uber UberClientId *string `json:"uberClientId,omitempty"` @@ -149,59 +160,16 @@ type Location struct { */ } -type Address struct { - Line1 *string `json:"line1,omitempty"` - Line2 *string `json:"line2,omitempty"` - City *string `json:"city,omitempty"` - Region *string `json:"region,omitempty"` - Sublocality *string `json:"sublocality,omitempty"` - PostalCode *string `json:"postalCode,omitempty"` -} - -type FeaturedMessage struct { - Description *string `json:"description,omitempty"` - Url *string `json:"url,omitempty"` -} - -type Website struct { - DisplayUrl *string `json:"displayUrl,omitempty"` - Url *string `json:"url,omitempty"` - PreferDisplayUrl *bool `json:"preferDisplayUrl,omitempty"` -} - -type Coordinate struct { - Latitude *float64 `json:"latitude,omitempty"` - Longitude *float64 `json:"longitude,omitempty"` -} - -type Hours struct { - Monday []*Times `json:"monday,omitempty"` - Tuesday []*Times `json:"tuesday,omitempty"` - Wednesday []*Times `json:"wednesday,omitempty"` - Thursday []*Times `json:"thursday,omitempty"` - Friday []*Times `json:"friday,omitempty"` - Saturday []*Times `json:"saturday,omitempty"` - Sunday []*Times `json:"sunday,omitempty"` - HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` -} - -// TODO: *Times will become *OpenIntervals after Techops change -type Times struct { - Start string `json:"start,omitempty"` - End string `json:"end,omitempty"` -} - -func (y *Location) GetEntityId() string { - return y.GetId() -} - -func (y *Location) GetEntityType() EntityType { - return ENTITYTYPE_LOCATION +func (y Location) GetId() string { + if y.Id != nil { + return *y.Id + } + return "" } -func (y Location) GetId() string { - if y.EntityMeta != nil && y.EntityMeta.Id != nil { - return *y.EntityMeta.Id +func (y Location) GetLocationType() string { + if y.LocationType != nil { + return *y.LocationType } return "" } @@ -277,22 +245,22 @@ func (y Location) GetDegrees() []string { } func (y Location) GetAccountId() string { - if y.EntityMeta.AccountId != nil { - return *y.EntityMeta.AccountId + if y.AccountId != nil { + return *y.AccountId } return "" } -func (y Location) GetLine1() string { - if y.Address != nil && y.Address.Line1 != nil { - return *y.Address.Line1 +func (y Location) GetAddress() string { + if y.Address != nil { + return *y.Address } return "" } -func (y Location) GetLine2() string { - if y.Address != nil && y.Address.Line2 != nil { - return *y.Address.Line2 +func (y Location) GetAddress2() string { + if y.Address2 != nil { + return *y.Address2 } return "" } @@ -312,22 +280,22 @@ func (y Location) GetDisplayAddress() string { } func (y Location) GetCity() string { - if y.Address != nil && y.Address.City != nil { - return *y.Address.City + if y.City != nil { + return *y.City } return "" } -func (y Location) GetRegion() string { - if y.Address != nil && y.Address.Region != nil { - return *y.Address.Region +func (y Location) GetState() string { + if y.State != nil { + return *y.State } return "" } -func (y Location) GetPostalCode() string { - if y.Address != nil && y.Address.PostalCode != nil { - return *y.Address.PostalCode +func (y Location) GetZip() string { + if y.Zip != nil { + return *y.Zip } return "" } @@ -339,9 +307,9 @@ func (y Location) GetCountryCode() string { return "" } -func (y Location) GetMainPhone() string { - if y.MainPhone != nil { - return *y.MainPhone +func (y Location) GetPhone() string { + if y.Phone != nil { + return *y.Phone } return "" } @@ -395,37 +363,44 @@ func (y Location) GetTtyPhone() string { return "" } -func (y Location) GetFeaturedMessageDescription() string { - if y.FeaturedMessage != nil && y.FeaturedMessage.Description != nil { - return *y.FeaturedMessage.Description +func (y Location) GetFeaturedMessage() string { + if y.FeaturedMessage != nil { + return *y.FeaturedMessage } return "" } func (y Location) GetFeaturedMessageUrl() string { - if y.FeaturedMessage != nil && y.FeaturedMessage.Url != nil { - return *y.FeaturedMessage.Url + if y.FeaturedMessageUrl != nil { + return *y.FeaturedMessageUrl } return "" } func (y Location) GetWebsiteUrl() string { - if y.WebsiteUrl != nil && y.WebsiteUrl.Url != nil { - return *y.WebsiteUrl.Url + if y.WebsiteUrl != nil { + return *y.WebsiteUrl } return "" } func (y Location) GetDisplayWebsiteUrl() string { - if y.WebsiteUrl != nil && y.WebsiteUrl.DisplayUrl != nil { - return *y.WebsiteUrl.DisplayUrl + if y.DisplayWebsiteUrl != nil { + return *y.DisplayWebsiteUrl } return "" } func (y Location) GetReservationUrl() string { - if y.ReservationUrl != nil && y.ReservationUrl.Url != nil { - return *y.ReservationUrl.Url + if y.ReservationUrl != nil { + return *y.ReservationUrl + } + return "" +} + +func (y Location) GetHours() string { + if y.Hours != nil { + return *y.Hours } return "" } @@ -458,37 +433,37 @@ func (y Location) GetFacebookPageUrl() string { return "" } -func (y Location) GetYearEstablished() float64 { +func (y Location) GetYearEstablished() string { if y.YearEstablished != nil { return *y.YearEstablished } - return 0 + return "" } func (y Location) GetDisplayLat() float64 { - if y.DisplayCoordinate != nil && y.DisplayCoordinate.Latitude != nil { - return *y.DisplayCoordinate.Latitude + if y.DisplayLat != nil { + return *y.DisplayLat } return 0 } func (y Location) GetDisplayLng() float64 { - if y.DisplayCoordinate != nil && y.DisplayCoordinate.Longitude != nil { - return *y.DisplayCoordinate.Longitude + if y.DisplayLng != nil { + return *y.DisplayLng } return 0 } func (y Location) GetRoutableLat() float64 { - if y.RoutableCoordinate != nil && y.RoutableCoordinate.Latitude != nil { - return *y.RoutableCoordinate.Latitude + if y.RoutableLat != nil { + return *y.RoutableLat } return 0 } func (y Location) GetRoutableLng() float64 { - if y.RoutableCoordinate != nil && y.RoutableCoordinate.Longitude != nil { - return *y.RoutableCoordinate.Longitude + if y.RoutableLng != nil { + return *y.RoutableLng } return 0 } @@ -654,9 +629,9 @@ func (y Location) GetGoogleAttributes() GoogleAttributes { return nil } -func (y Location) GetHolidayHours() []HolidayHours { - if y.Hours != nil && y.Hours.HolidayHours != nil { - return *y.Hours.HolidayHours +func (y Location) GetHolidayHours() []LocationHolidayHours { + if y.HolidayHours != nil { + return *y.HolidayHours } return nil } @@ -694,9 +669,9 @@ func (l LocationClosed) String() string { // HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager. // For details see -type HolidayHours struct { - Date string `json:"date"` - Hours []*Times `json:"hours"` +type LocationHolidayHours struct { + Date string `json:"date"` + Hours string `json:"hours"` } // UnorderedStrings masks []string properties for which Order doesn't matter, such as LabelIds diff --git a/location_diff.go b/location_diff.go index 7bb08d4..e65450f 100644 --- a/location_diff.go +++ b/location_diff.go @@ -8,8 +8,6 @@ type Comparable interface { Equal(Comparable) bool } -// TODO: Need to fix the comments below and update this to work with the new EntityMeta structure - // Diff calculates the differences between a base Location (a) and a proposed set of changes // represented by a second Location (b). The diffing logic will ignore fields in the proposed // Location that aren't set (nil). This characteristic makes the function ideal for @@ -30,8 +28,7 @@ type Comparable interface { // // isDiff -> false // // delta -> nil func (y Location) Diff(b *Location) (d *Location, diff bool) { - d = &Location{EntityMeta: &EntityMeta{ - Id: String(y.GetId())}} + d = &Location{Id: String(y.GetId())} var ( aV, bV = reflect.ValueOf(y), reflect.ValueOf(b).Elem() @@ -50,17 +47,14 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { continue } - // TODO: Issue here because EntityMeta is an embedded struct - if nameA == "Id" || nameA == "EntityMeta" || valB.IsNil() { + if valB.IsNil() || nameA == "Id" { continue } if nameA == "Hours" { - // TODO: need to re-enable - continue - // if !valA.IsNil() && !valB.IsNil() && HoursAreEquivalent(getUnderlyingValue(valA.Interface()).(string), getUnderlyingValue(valB.Interface()).(string)) { - // continue - // } + if !valA.IsNil() && !valB.IsNil() && HoursAreEquivalent(getUnderlyingValue(valA.Interface()).(string), getUnderlyingValue(valB.Interface()).(string)) { + continue + } } if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { @@ -169,11 +163,9 @@ var closedHoursEquivalents = map[string]struct{}{ HoursClosedAllWeek: struct{}{}, } -// TODO: need to re-enable func HoursAreEquivalent(a, b string) bool { - return false - // _, aIsClosed := closedHoursEquivalents[a] - // _, bIsClosed := closedHoursEquivalents[b] - // - // return a == b || aIsClosed && bIsClosed + _, aIsClosed := closedHoursEquivalents[a] + _, bIsClosed := closedHoursEquivalents[b] + + return a == b || aIsClosed && bIsClosed } diff --git a/location_diff_test.go b/location_diff_test.go index ae86fec..80791ca 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -13,9 +13,7 @@ var examplePhoto = LocationPhoto{ } var complexOne = &Location{ - EntityMeta: &EntityMeta{ - Id: String("lock206"), - }, + Id: String("lock206"), Name: String("Farmers Insurance - Stephen Lockhart "), CustomFields: map[string]interface{}{ "1857": "", @@ -56,16 +54,14 @@ var complexOne = &Location{ "7299": "1046846", "7300": true, }, - Address: &Address{ - Line1: String("2690 Sheridan Dr W"), - Line2: String(""), - City: String("Tonawanda"), - Region: String("NY"), - PostalCode: String("14150-9425"), - }, - MainPhone: String("716-835-0306"), + Address: String("2690 Sheridan Dr W"), + Address2: String(""), + City: String("Tonawanda"), + State: String("NY"), + Zip: String("14150-9425"), + Phone: String("716-835-0306"), FaxPhone: String("716-835-0415"), - YearEstablished: Float(2015), + YearEstablished: String("2015"), Emails: &[]string{"slockhart@farmersagent.com"}, Services: &[]string{ "Auto Insurance", @@ -82,9 +78,7 @@ var complexOne = &Location{ } var complexTwo = &Location{ - EntityMeta: &EntityMeta{ - Id: String("lock206"), - }, + Id: String("lock206"), Name: String("Farmers Insurance - Stephen Lockhart "), CustomFields: map[string]interface{}{ "1857": "", @@ -125,16 +119,14 @@ var complexTwo = &Location{ "7299": "1046846", "7300": true, }, - Address: &Address{ - Line1: String("2690 Sheridan Dr W"), - Line2: String(""), - City: String("Tonawanda"), - Region: String("NY"), - PostalCode: String("14150-9425"), - }, - MainPhone: String("716-835-0306"), + Address: String("2690 Sheridan Dr W"), + Address2: String(""), + City: String("Tonawanda"), + State: String("NY"), + Zip: String("14150-9425"), + Phone: String("716-835-0306"), FaxPhone: String("716-835-0415"), - YearEstablished: Float(2015), + YearEstablished: String("2015"), Emails: &[]string{"slockhart@farmersagent.com"}, Services: &[]string{ "Auto Insurance", @@ -150,101 +142,44 @@ var complexTwo = &Location{ FolderId: String("91760"), } -var jsonData string = `{"id":"phai514","locationName":"Farmers Insurance - Aroun Phaisan ","customFields":{"1857":"","1858":"122191","1859":"Aroun","1871":"Phaisan","3004":"Agent","7240":"Aroun Phaisan","7251":true,"7253":"1221","7254":"aphaisan","7255":"2685","7256":["phai514"],"7261":false,"7263":true,"7265":"","7266":"","7269":"91","7270":true,"7271":"21","7272":"User_Dup","7273":"Lincoln","7274":"NE","7275":"5730 R St Ste B","7276":"68505-2309","7277":"12","7278":false,"7279":true,"7283":"","7284":"","7285":"16133384","7286":"","7287":true,"7288":true,"7296":"16133384","7297":"","7298":"","7299":"786200","7300":true},"address": {"line1":"5730 R St","line2":"Ste B","city":"Lincoln","state":"NE","zip":"68505-2309"},"phone":"402-417-4266","faxPhone":"402-423-3141","yearEstablished":2011,"emails":["aphaisan@farmersagent.com"],"services":["Auto Insurance","Home Insurance","Homeowners Insurance","Business Insurance","Motorcyle Insurance","Recreational Insurance","Renters Insurance","Umbrella Insurance","Term Life Insurance","Whole Life Insurance"],"languages":["English"],"folderId":"91760"}` +var jsonData string = `{"id":"phai514","locationName":"Farmers Insurance - Aroun Phaisan ","customFields":{"1857":"","1858":"122191","1859":"Aroun","1871":"Phaisan","3004":"Agent","7240":"Aroun Phaisan","7251":true,"7253":"1221","7254":"aphaisan","7255":"2685","7256":["phai514"],"7261":false,"7263":true,"7265":"","7266":"","7269":"91","7270":true,"7271":"21","7272":"User_Dup","7273":"Lincoln","7274":"NE","7275":"5730 R St Ste B","7276":"68505-2309","7277":"12","7278":false,"7279":true,"7283":"","7284":"","7285":"16133384","7286":"","7287":true,"7288":true,"7296":"16133384","7297":"","7298":"","7299":"786200","7300":true},"address":"5730 R St","address2":"Ste B","city":"Lincoln","state":"NE","zip":"68505-2309","phone":"402-417-4266","faxPhone":"402-423-3141","yearEstablished":"2011","emails":["aphaisan@farmersagent.com"],"services":["Auto Insurance","Home Insurance","Homeowners Insurance","Business Insurance","Motorcyle Insurance","Recreational Insurance","Renters Insurance","Umbrella Insurance","Term Life Insurance","Whole Life Insurance"],"languages":["English"],"folderId":"91760"}` var baseLocation Location = Location{ - EntityMeta: &EntityMeta{ - Id: String("ding"), - AccountId: String("ding"), - }, - Name: String("ding"), - DisplayAddress: String("ding"), - Address: &Address{ - Line1: String("ding"), - Line2: String("ding"), - City: String("ding"), - Region: String("ding"), - PostalCode: String("ding"), - }, - CountryCode: String("ding"), - MainPhone: String("ding"), - LocalPhone: String("ding"), - AlternatePhone: String("ding"), - FaxPhone: String("ding"), - MobilePhone: String("ding"), - TollFreePhone: String("ding"), - TtyPhone: String("ding"), - FeaturedMessage: &FeaturedMessage{ - Description: String("ding"), - Url: String("ding"), - }, - WebsiteUrl: &Website{ - Url: String("ding"), - DisplayUrl: String("ding"), - }, - ReservationUrl: &Website{ - Url: String("ding"), - }, - Hours: &Hours{ - Monday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Tuesday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Wednesday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Thursday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Friday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Saturday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Sunday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - }, - AdditionalHoursText: String("ding"), - Description: String("ding"), - TwitterHandle: String("ding"), - FacebookPageUrl: String("ding"), - YearEstablished: Float(1234), - FolderId: String("ding"), - SuppressAddress: Bool(false), - IsPhoneTracked: Bool(true), - DisplayCoordinate: &Coordinate{ - Latitude: Float(1234.0), - Longitude: Float(1234.0), - }, - RoutableCoordinate: &Coordinate{ - Latitude: Float(1234.0), - Longitude: Float(1234.0), - }, + Id: String("ding"), + Name: String("ding"), + AccountId: String("ding"), + Address: String("ding"), + Address2: String("ding"), + DisplayAddress: String("ding"), + City: String("ding"), + State: String("ding"), + Zip: String("ding"), + CountryCode: String("ding"), + Phone: String("ding"), + LocalPhone: String("ding"), + AlternatePhone: String("ding"), + FaxPhone: String("ding"), + MobilePhone: String("ding"), + TollFreePhone: String("ding"), + TtyPhone: String("ding"), + FeaturedMessage: String("ding"), + FeaturedMessageUrl: String("ding"), + WebsiteUrl: String("ding"), + DisplayWebsiteUrl: String("ding"), + ReservationUrl: String("ding"), + Hours: String("ding"), + AdditionalHoursText: String("ding"), + Description: String("ding"), + TwitterHandle: String("ding"), + FacebookPageUrl: String("ding"), + YearEstablished: String("ding"), + FolderId: String("ding"), + SuppressAddress: Bool(false), + IsPhoneTracked: Bool(true), + DisplayLat: Float(1234.0), + DisplayLng: Float(1234.0), + RoutableLat: Float(1234.0), + RoutableLng: Float(1234.0), Keywords: &[]string{"ding", "ding"}, PaymentOptions: &[]string{"ding", "ding"}, VideoUrls: &[]string{"ding", "ding"}, @@ -268,98 +203,41 @@ var baseLocation Location = Location{ func TestDiffIdentical(t *testing.T) { secondLocation := &Location{ - EntityMeta: &EntityMeta{ - Id: String("ding"), - AccountId: String("ding"), - }, - Name: String("ding"), - DisplayAddress: String("ding"), - Address: &Address{ - Line1: String("ding"), - Line2: String("ding"), - City: String("ding"), - Region: String("ding"), - PostalCode: String("ding"), - }, - CountryCode: String("ding"), - MainPhone: String("ding"), - LocalPhone: String("ding"), - AlternatePhone: String("ding"), - FaxPhone: String("ding"), - MobilePhone: String("ding"), - TollFreePhone: String("ding"), - TtyPhone: String("ding"), - FeaturedMessage: &FeaturedMessage{ - Description: String("ding"), - Url: String("ding"), - }, - WebsiteUrl: &Website{ - Url: String("ding"), - DisplayUrl: String("ding"), - }, - ReservationUrl: &Website{ - Url: String("ding"), - }, - Hours: &Hours{ - Monday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Tuesday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Wednesday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Thursday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Friday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Saturday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - Sunday: []*Times{ - &Times{ - Start: "ding", - End: "ding", - }, - }, - }, - AdditionalHoursText: String("ding"), - Description: String("ding"), - TwitterHandle: String("ding"), - FacebookPageUrl: String("ding"), - YearEstablished: Float(1234), - FolderId: String("ding"), - SuppressAddress: Bool(false), - IsPhoneTracked: Bool(true), - DisplayCoordinate: &Coordinate{ - Latitude: Float(1234.0), - Longitude: Float(1234.0), - }, - RoutableCoordinate: &Coordinate{ - Latitude: Float(1234.0), - Longitude: Float(1234.0), - }, + Id: String("ding"), + Name: String("ding"), + AccountId: String("ding"), + Address: String("ding"), + Address2: String("ding"), + DisplayAddress: String("ding"), + City: String("ding"), + State: String("ding"), + Zip: String("ding"), + CountryCode: String("ding"), + Phone: String("ding"), + LocalPhone: String("ding"), + AlternatePhone: String("ding"), + FaxPhone: String("ding"), + MobilePhone: String("ding"), + TollFreePhone: String("ding"), + TtyPhone: String("ding"), + FeaturedMessage: String("ding"), + FeaturedMessageUrl: String("ding"), + WebsiteUrl: String("ding"), + DisplayWebsiteUrl: String("ding"), + ReservationUrl: String("ding"), + Hours: String("ding"), + AdditionalHoursText: String("ding"), + Description: String("ding"), + TwitterHandle: String("ding"), + FacebookPageUrl: String("ding"), + YearEstablished: String("ding"), + FolderId: String("ding"), + SuppressAddress: Bool(false), + IsPhoneTracked: Bool(true), + DisplayLat: Float(1234.0), + DisplayLng: Float(1234.0), + RoutableLat: Float(1234.0), + RoutableLng: Float(1234.0), Keywords: &[]string{"ding", "ding"}, PaymentOptions: &[]string{"ding", "ding"}, VideoUrls: &[]string{"ding", "ding"}, @@ -778,8 +656,7 @@ func (t floatTest) formatErrorBase(index int) string { func TestFloatDiffs(t *testing.T) { a, b := *new(Location), new(Location) for i, data := range floatTests { - a.YearEstablished = data.baseValue - b.YearEstablished = data.newValue + a.DisplayLat, b.DisplayLat = data.baseValue, data.newValue a.nilIsEmpty, b.nilIsEmpty = data.nilIsEmpty, data.nilIsEmpty d, isDiff := a.Diff(b) if isDiff != data.isDiff { @@ -791,7 +668,7 @@ func TestFloatDiffs(t *testing.T) { t.Errorf("%v\ndelta was nil but expected %v\n", data.formatErrorBase(i), formatFloatPtr(data.expectedFieldValue)) } else if d != nil && data.expectedFieldValue == nil { t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) - } else if *d.YearEstablished != *data.expectedFieldValue { + } else if *d.DisplayLat != *data.expectedFieldValue { t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) } } @@ -1362,15 +1239,11 @@ func TestLabels(t *testing.T) { tests = []Scenario{ Scenario{ A: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &one, }, B: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &two, }, WantDelta: nil, @@ -1378,15 +1251,11 @@ func TestLabels(t *testing.T) { }, Scenario{ A: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &one, }, B: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &three, }, WantDelta: nil, @@ -1394,21 +1263,15 @@ func TestLabels(t *testing.T) { }, Scenario{ A: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &one, }, B: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &four, }, WantDelta: &Location{ - EntityMeta: &EntityMeta{ - Id: String("1"), - }, + Id: String("1"), LabelIds: &four, }, WantDiff: true, @@ -1445,7 +1308,7 @@ func TestLocationNils(t *testing.T) { func TestLocationCustomFieldEmptyComparision(t *testing.T) { a, b := *new(Location), new(Location) - a.EntityMeta = &EntityMeta{Id: String("blah")} + a.Id = String("blah") b.CustomFields = map[string]interface{}{} a.hydrated, b.hydrated = true, true @@ -1458,14 +1321,13 @@ func TestLocationCustomFieldEmptyComparision(t *testing.T) { func TestCustomFieldPointerComparison(t *testing.T) { a, b := *new(Location), new(Location) - a.EntityMeta = &EntityMeta{Id: String("blah")} - // TODO: Fix - // a.CustomFields = map[string]interface{}{ - // "1": Hours{Hours: "1:09:00:18:00"}, - // } - // b.CustomFields = map[string]interface{}{ - // "1": &Hours{Hours: "1:09:00:18:00"}, - // } + a.Id = String("blah") + a.CustomFields = map[string]interface{}{ + "1": HoursCustom{Hours: "1:09:00:18:00"}, + } + b.CustomFields = map[string]interface{}{ + "1": &HoursCustom{Hours: "1:09:00:18:00"}, + } a.hydrated, b.hydrated = true, true d, isDiff := a.Diff(b) @@ -1528,99 +1390,98 @@ func TestZeroValuesAndNilDiffing(t *testing.T) { } } -// TODO: re-enable tests -// var hoursTests = []struct { -// A, B *ProfileHours -// WantEquivalent, WantDiff bool -// }{ -// { -// A: nil, -// B: nil, -// WantEquivalent: true, -// WantDiff: false, -// }, -// { -// A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), -// B: String(""), -// WantEquivalent: true, -// WantDiff: false, -// }, -// // This might seem odd, but we're still working out hours semantics with Product, so I'd rather err on the side -// // of a limited set of 'closed' equivalencies for now: -// { -// A: String("1:closed"), -// B: String(""), -// WantEquivalent: false, -// WantDiff: true, -// }, -// { -// A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), -// B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), -// WantEquivalent: true, -// WantDiff: false, -// }, -// { -// A: String("1:11:00"), -// B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), -// WantEquivalent: false, -// WantDiff: true, -// }, -// { -// A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), -// B: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), -// WantEquivalent: true, -// WantDiff: false, -// }, -// { -// A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), -// B: String("1:11:01:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), -// WantEquivalent: false, -// WantDiff: true, -// }, -// { -// A: String("1:11:00:20:00"), -// B: String("1:11:00:20:00"), -// WantEquivalent: true, -// WantDiff: false, -// }, -// { -// A: String("1:11:00:20:00"), -// B: String("1:11:01:20:00"), -// WantEquivalent: false, -// WantDiff: true, -// }, -// { -// A: nil, -// B: String("1:11:01:20:00"), -// WantEquivalent: false, -// WantDiff: true, -// }, -// { -// A: String("1:11:01:20:00"), -// B: nil, -// WantEquivalent: false, -// WantDiff: false, -// }, -// { -// A: nil, -// B: nil, -// WantEquivalent: true, -// WantDiff: false, -// }, -// } -// -// func TestHoursAreEquivalent(t *testing.T) { -// for _, test := range hoursTests { -// if test.A != nil && test.B != nil { -// if got := HoursAreEquivalent(*test.A, *test.B); got != test.WantEquivalent { -// t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), got, test.WantEquivalent) -// } -// if got := HoursAreEquivalent(*test.B, *test.A); got != test.WantEquivalent { -// t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.B), stringify(test.A), got, test.WantEquivalent) -// } -// } -// } -// } +var hoursTests = []struct { + A, B *string + WantEquivalent, WantDiff bool +}{ + { + A: String(""), + B: String(""), + WantEquivalent: true, + WantDiff: false, + }, + { + A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), + B: String(""), + WantEquivalent: true, + WantDiff: false, + }, + // This might seem odd, but we're still working out hours semantics with Product, so I'd rather err on the side + // of a limited set of 'closed' equivalencies for now: + { + A: String("1:closed"), + B: String(""), + WantEquivalent: false, + WantDiff: true, + }, + { + A: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), + B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), + WantEquivalent: true, + WantDiff: false, + }, + { + A: String("1:11:00"), + B: String("1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed"), + WantEquivalent: false, + WantDiff: true, + }, + { + A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), + B: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), + WantEquivalent: true, + WantDiff: false, + }, + { + A: String("1:11:00:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), + B: String("1:11:01:20:00,2:10:00:21:00,3:10:00:21:00,4:10:00:21:00,5:10:00:21:00,6:10:00:21:00,7:10:00:21:00"), + WantEquivalent: false, + WantDiff: true, + }, + { + A: String("1:11:00:20:00"), + B: String("1:11:00:20:00"), + WantEquivalent: true, + WantDiff: false, + }, + { + A: String("1:11:00:20:00"), + B: String("1:11:01:20:00"), + WantEquivalent: false, + WantDiff: true, + }, + { + A: nil, + B: String("1:11:01:20:00"), + WantEquivalent: false, + WantDiff: true, + }, + { + A: String("1:11:01:20:00"), + B: nil, + WantEquivalent: false, + WantDiff: false, + }, + { + A: nil, + B: nil, + WantEquivalent: true, + WantDiff: false, + }, +} + +func TestHoursAreEquivalent(t *testing.T) { + for _, test := range hoursTests { + if test.A != nil && test.B != nil { + if got := HoursAreEquivalent(*test.A, *test.B); got != test.WantEquivalent { + t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), got, test.WantEquivalent) + } + if got := HoursAreEquivalent(*test.B, *test.A); got != test.WantEquivalent { + t.Errorf(`HoursAreEquivalent("%s", "%s")=%t, wanted %t`, stringify(test.B), stringify(test.A), got, test.WantEquivalent) + } + } + } +} func stringify(v *string) string { if v != nil { @@ -1629,13 +1490,13 @@ func stringify(v *string) string { return "nil" } -// func TestHoursAreEquivalentDiff(t *testing.T) { -// for _, test := range hoursTests { -// a := &Location{Hours: test.A} -// b := &Location{Hours: test.B} -// -// if _, isDiff := a.Diff(b); isDiff != test.WantDiff { -// t.Errorf(`Diff("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), isDiff, test.WantDiff) -// } -// } -// } +func TestHoursAreEquivalentDiff(t *testing.T) { + for _, test := range hoursTests { + a := &Location{Hours: test.A} + b := &Location{Hours: test.B} + + if _, isDiff := a.Diff(b); isDiff != test.WantDiff { + t.Errorf(`Diff("%s", "%s")=%t, wanted %t`, stringify(test.A), stringify(test.B), isDiff, test.WantDiff) + } + } +} diff --git a/location_entity.go b/location_entity.go new file mode 100644 index 0000000..e81c94c --- /dev/null +++ b/location_entity.go @@ -0,0 +1,667 @@ +package yext + +// TODO +// * Need better custom field accessors and helpers +// * The API will accept some things and return them in a different format - this makes diff'ing difficult: +// ** Phone: Send in 540-444-4444, get back 5404444444 + +import ( + "encoding/json" +) + +const ENTITYTYPE_LOCATION EntityType = "LOCATION" + +// Location is the representation of a Location in Yext Location Manager. +// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm +type LocationEntity struct { + BaseEntity + + // Admin + FolderId *string `json:"folderId,omitempty"` + LabelIds *UnorderedStrings `json:"labelIds,omitempty"` + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed *LocationClosed `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + Language *string `json:"language,omitempty"` + CustomFields map[string]interface{} `json:"customFields,omitempty"` + + hydrated bool + nilIsEmpty bool + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + DisplayAddress *string `json:"displayAddress,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` + SuppressAddress *bool `json:"suppressAddress,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + FaxPhone *string `json:"faxPhone,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + IsPhoneTracked *bool `json:"isPhoneTracked,omitempty"` + Emails *[]string `json:"emails,omitempty"` + + // HealthCare fields + FirstName *string `json:"firstName,omitempty"` + MiddleName *string `json:"middleName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Gender *string `json:"gender,omitempty"` + Headshot *LocationPhoto `json:"headshot,omitempty"` + AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` + AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` + ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` + InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` + NPI *string `json:"npi,omitempty"` + OfficeName *string `json:"officeName,omitempty"` + Degrees *[]string `json:"degrees,omitempty"` + + // Location Info + Description *string `json:"description,omitempty"` + Hours *Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished *float64 `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + Specialties *[]string `json:"specialties,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo *LocationPhoto `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + + // Lats & Lngs + DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` + // TODO: Update below + DropoffLat *float64 `json:"dropoffLat,omitempty"` + DropoffLng *float64 `json:"dropoffLng,omitempty"` + WalkableLat *float64 `json:"walkableLat,omitempty"` + WalkableLng *float64 `json:"walkableLng,omitempty"` + RoutableCoordinate *Coordinate `json:"yextRoutableCoordinate,omitempty"` + PickupLat *float64 `json:"pickupLat,omitempty"` + PickupLng *float64 `json:"pickupLng,omitempty"` + + // ECLS + BioListIds *[]string `json:"bioListIds,omitempty"` + BioListsLabel *string `json:"bioListsLabel,omitempty"` + EventListIds *[]string `json:"eventListIds,omitempty"` + EventListsLabel *string `json:"eventListsLabel,omitempty"` + MenuListsLabel *string `json:"menusLabel,omitempty"` + MenuListIds *[]string `json:"menuIds,omitempty"` + ProductListIds *[]string `json:"productListIds,omitempty"` + ProductListsLabel *string `json:"productListsLabel,omitempty"` + + // Urls + MenuUrl *Website `json:"menuUrl,omitempty"` + OrderUrl *Website `json:"orderUrl,omitempty"` + ReservationUrl *Website `json:"reservationUrl,omitempty"` + WebsiteUrl *Website `json:"websiteUrl,omitempty"` + FeaturedMessage *FeaturedMessage `json:"featuredMessage,omitempty"` + + // Uber + UberClientId *string `json:"uberClientId,omitempty"` + UberLinkText *string `json:"uberLinkText,omitempty"` + UberLinkType *string `json:"uberLinkType,omitempty"` + UberTripBrandingText *string `json:"uberTripBrandingText,omitempty"` + UberTripBrandingUrl *string `json:"uberTripBrandingUrl,omitempty"` + + // Social Media + FacebookCoverPhoto *LocationPhoto `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePicture *LocationPhoto `json:"facebookProfilePicture,omitempty"` + + GoogleCoverPhoto *LocationPhoto `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto *LocationPhoto `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + + Photos *[]LocationPhoto `json:"photos,omitempty"` + VideoUrls *[]string `json:"videoUrls,omitempty"` + + GoogleAttributes *GoogleAttributes `json:"googleAttributes,omitempty"` + + // Reviews + ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` + FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` + + /** TODO(bmcginnis) add the following fields: + + ServiceArea struct { + Places *[]string `json:"places,omitempty"` + Radius *int `json:"radius,omitempty"` + Unit *string `json:"unit,omitempty"` + } `json:"serviceArea,omitempty"` + + EducationList []struct { + InstitutionName *string `json:"institutionName,omitempty"` + Type *string `json:"type,omitempty"` + YearCompleted *string `json:"yearCompleted,omitempty"` + } `json:"educationList,omitempty"` + */ +} + +type Address struct { + Line1 *string `json:"line1,omitempty"` + Line2 *string `json:"line2,omitempty"` + City *string `json:"city,omitempty"` + Region *string `json:"region,omitempty"` + Sublocality *string `json:"sublocality,omitempty"` + PostalCode *string `json:"postalCode,omitempty"` +} + +type FeaturedMessage struct { + Description *string `json:"description,omitempty"` + Url *string `json:"url,omitempty"` +} + +type Website struct { + DisplayUrl *string `json:"displayUrl,omitempty"` + Url *string `json:"url,omitempty"` + PreferDisplayUrl *bool `json:"preferDisplayUrl,omitempty"` +} + +type Coordinate struct { + Latitude *float64 `json:"latitude,omitempty"` + Longitude *float64 `json:"longitude,omitempty"` +} + +type Hours struct { + Monday []*Times `json:"monday,omitempty"` + Tuesday []*Times `json:"tuesday,omitempty"` + Wednesday []*Times `json:"wednesday,omitempty"` + Thursday []*Times `json:"thursday,omitempty"` + Friday []*Times `json:"friday,omitempty"` + Saturday []*Times `json:"saturday,omitempty"` + Sunday []*Times `json:"sunday,omitempty"` + HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` +} + +// TODO: *Times will become *OpenIntervals after Techops change +type Times struct { + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` +} + +func (y LocationEntity) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y LocationEntity) GetName() string { + if y.Name != nil { + return *y.Name + } + return "" +} + +func (y LocationEntity) GetFirstName() string { + if y.FirstName != nil { + return *y.FirstName + } + return "" +} + +func (y LocationEntity) GetMiddleName() string { + if y.MiddleName != nil { + return *y.MiddleName + } + return "" +} + +func (y LocationEntity) GetLastName() string { + if y.LastName != nil { + return *y.LastName + } + return "" +} + +func (y LocationEntity) GetGender() string { + if y.Gender != nil { + return *y.Gender + } + return "" +} + +func (y LocationEntity) GetAcceptingNewPatients() bool { + if y.AcceptingNewPatients != nil { + return *y.AcceptingNewPatients + } + return false +} + +func (y LocationEntity) GetCertifications() []string { + if y.Certifications != nil { + return *y.Certifications + } + return nil +} + +func (y LocationEntity) GetNPI() string { + if y.NPI != nil { + return *y.NPI + } + return "" +} + +func (y LocationEntity) GetOfficeName() string { + if y.OfficeName != nil { + return *y.OfficeName + } + return "" +} + +func (y LocationEntity) GetDegrees() []string { + if y.Degrees != nil { + return *y.Degrees + } + return nil +} + +func (y LocationEntity) GetAccountId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { + return *y.BaseEntity.Meta.AccountId + } + return "" +} + +func (y LocationEntity) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return *y.Address.Line1 + } + return "" +} + +func (y LocationEntity) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return *y.Address.Line2 + } + return "" +} + +func (y LocationEntity) GetSuppressAddress() bool { + if y.SuppressAddress != nil { + return *y.SuppressAddress + } + return false +} + +func (y LocationEntity) GetDisplayAddress() string { + if y.DisplayAddress != nil { + return *y.DisplayAddress + } + return "" +} + +func (y LocationEntity) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return *y.Address.City + } + return "" +} + +func (y LocationEntity) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return *y.Address.Region + } + return "" +} + +func (y LocationEntity) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return *y.Address.PostalCode + } + return "" +} + +func (y LocationEntity) GetCountryCode() string { + if y.CountryCode != nil { + return *y.CountryCode + } + return "" +} + +func (y LocationEntity) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone + } + return "" +} + +func (y LocationEntity) GetIsPhoneTracked() bool { + if y.IsPhoneTracked != nil { + return *y.IsPhoneTracked + } + return false +} + +func (y LocationEntity) GetLocalPhone() string { + if y.LocalPhone != nil { + return *y.LocalPhone + } + return "" +} + +func (y LocationEntity) GetAlternatePhone() string { + if y.AlternatePhone != nil { + return *y.AlternatePhone + } + return "" +} + +func (y LocationEntity) GetFaxPhone() string { + if y.FaxPhone != nil { + return *y.FaxPhone + } + return "" +} + +func (y LocationEntity) GetMobilePhone() string { + if y.MobilePhone != nil { + return *y.MobilePhone + } + return "" +} + +func (y LocationEntity) GetTollFreePhone() string { + if y.TollFreePhone != nil { + return *y.TollFreePhone + } + return "" +} + +func (y LocationEntity) GetTtyPhone() string { + if y.TtyPhone != nil { + return *y.TtyPhone + } + return "" +} + +func (y LocationEntity) GetFeaturedMessageDescription() string { + if y.FeaturedMessage != nil && y.FeaturedMessage.Description != nil { + return *y.FeaturedMessage.Description + } + return "" +} + +func (y LocationEntity) GetFeaturedMessageUrl() string { + if y.FeaturedMessage != nil && y.FeaturedMessage.Url != nil { + return *y.FeaturedMessage.Url + } + return "" +} + +func (y LocationEntity) GetWebsiteUrl() string { + if y.WebsiteUrl != nil && y.WebsiteUrl.Url != nil { + return *y.WebsiteUrl.Url + } + return "" +} + +func (y LocationEntity) GetDisplayWebsiteUrl() string { + if y.WebsiteUrl != nil && y.WebsiteUrl.DisplayUrl != nil { + return *y.WebsiteUrl.DisplayUrl + } + return "" +} + +func (y LocationEntity) GetReservationUrl() string { + if y.ReservationUrl != nil && y.ReservationUrl.Url != nil { + return *y.ReservationUrl.Url + } + return "" +} + +func (y LocationEntity) GetAdditionalHoursText() string { + if y.AdditionalHoursText != nil { + return *y.AdditionalHoursText + } + return "" +} + +func (y LocationEntity) GetDescription() string { + if y.Description != nil { + return *y.Description + } + return "" +} + +func (y LocationEntity) GetTwitterHandle() string { + if y.TwitterHandle != nil { + return *y.TwitterHandle + } + return "" +} + +func (y LocationEntity) GetFacebookPageUrl() string { + if y.FacebookPageUrl != nil { + return *y.FacebookPageUrl + } + return "" +} + +func (y LocationEntity) GetYearEstablished() float64 { + if y.YearEstablished != nil { + return *y.YearEstablished + } + return 0 +} + +func (y LocationEntity) GetDisplayLat() float64 { + if y.DisplayCoordinate != nil && y.DisplayCoordinate.Latitude != nil { + return *y.DisplayCoordinate.Latitude + } + return 0 +} + +func (y LocationEntity) GetDisplayLng() float64 { + if y.DisplayCoordinate != nil && y.DisplayCoordinate.Longitude != nil { + return *y.DisplayCoordinate.Longitude + } + return 0 +} + +func (y LocationEntity) GetRoutableLat() float64 { + if y.RoutableCoordinate != nil && y.RoutableCoordinate.Latitude != nil { + return *y.RoutableCoordinate.Latitude + } + return 0 +} + +func (y LocationEntity) GetRoutableLng() float64 { + if y.RoutableCoordinate != nil && y.RoutableCoordinate.Longitude != nil { + return *y.RoutableCoordinate.Longitude + } + return 0 +} + +func (y LocationEntity) GetBioListIds() (v []string) { + if y.BioListIds != nil { + v = *y.BioListIds + } + return v +} + +func (y LocationEntity) GetEventListIds() (v []string) { + if y.EventListIds != nil { + v = *y.EventListIds + } + return v +} + +func (y LocationEntity) GetProductListIds() (v []string) { + if y.ProductListIds != nil { + v = *y.ProductListIds + } + return v +} + +func (y LocationEntity) GetMenuListIds() (v []string) { + if y.MenuListIds != nil { + v = *y.MenuListIds + } + return v +} + +func (y LocationEntity) GetFolderId() string { + if y.FolderId != nil { + return *y.FolderId + } + return "" +} + +func (y LocationEntity) GetReviewBalancingURL() string { + if y.ReviewBalancingURL != nil { + return *y.ReviewBalancingURL + } + return "" +} + +func (y LocationEntity) GetFirstPartyReviewPage() string { + if y.FirstPartyReviewPage != nil { + return *y.FirstPartyReviewPage + } + return "" +} + +func (y LocationEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (y LocationEntity) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (y LocationEntity) GetLanguage() (v string) { + if y.Language != nil { + v = *y.Language + } + return v +} + +func (y LocationEntity) GetAssociations() (v []string) { + if y.Associations != nil { + v = *y.Associations + } + return v +} + +func (y LocationEntity) GetEmails() (v []string) { + if y.Emails != nil { + v = *y.Emails + } + return v +} + +func (y LocationEntity) GetSpecialties() (v []string) { + if y.Specialties != nil { + v = *y.Specialties + } + return v +} + +func (y LocationEntity) GetServices() (v []string) { + if y.Services != nil { + v = *y.Services + } + return v +} + +func (y LocationEntity) GetBrands() (v []string) { + if y.Brands != nil { + v = *y.Brands + } + return v +} + +func (y LocationEntity) GetLanguages() (v []string) { + if y.Languages != nil { + v = *y.Languages + } + return v +} + +func (y LocationEntity) GetLabelIds() (v UnorderedStrings) { + if y.LabelIds != nil { + v = *y.LabelIds + } + return v +} + +func (y LocationEntity) SetLabelIds(v []string) { + l := UnorderedStrings(v) + y.SetLabelIdsWithUnorderedStrings(l) +} + +func (y LocationEntity) SetLabelIdsWithUnorderedStrings(v UnorderedStrings) { + y.LabelIds = &v +} + +func (y LocationEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + +func (y LocationEntity) GetPaymentOptions() (v []string) { + if y.PaymentOptions != nil { + v = *y.PaymentOptions + } + return v +} + +func (y LocationEntity) GetVideoUrls() (v []string) { + if y.VideoUrls != nil { + v = *y.VideoUrls + } + return v +} + +func (y LocationEntity) GetAdmittingHospitals() (v []string) { + if y.AdmittingHospitals != nil { + v = *y.AdmittingHospitals + } + return v +} + +func (y LocationEntity) GetGoogleAttributes() GoogleAttributes { + if y.GoogleAttributes != nil { + return *y.GoogleAttributes + } + return nil +} + +func (y LocationEntity) GetHolidayHours() []HolidayHours { + if y.Hours != nil && y.Hours.HolidayHours != nil { + return *y.Hours.HolidayHours + } + return nil +} + +func (y LocationEntity) IsClosed() bool { + if y.Closed != nil && y.Closed.IsClosed { + return true + } + return false +} + +// HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager. +// For details see +type HolidayHours struct { + Date string `json:"date"` + Hours []*Times `json:"hours"` +} diff --git a/location_service.go b/location_service.go index 13786fc..709ed36 100644 --- a/location_service.go +++ b/location_service.go @@ -135,7 +135,7 @@ func addGetOptions(requrl string, opts *LocationListOptions) (string, error) { } func (l *LocationService) Edit(y *Location) (*Response, error) { - 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", locationsPath, y.GetId()), y, nil) @@ -147,7 +147,7 @@ func (l *LocationService) Edit(y *Location) (*Response, error) { } func (l *LocationService) Create(y *Location) (*Response, error) { - if err := validateCustomFieldsKeys(y.CustomFields); err != nil { + if err := validateLocationCustomFieldsKeys(y.CustomFields); err != nil { return nil, err } r, err := l.client.DoRequestJSON("POST", fmt.Sprintf("%s", locationsPath), y, nil) diff --git a/location_service_test.go b/location_service_test.go index 54cc231..8996072 100644 --- a/location_service_test.go +++ b/location_service_test.go @@ -87,8 +87,7 @@ func makeLocs(n int) []*Location { var locs []*Location for i := 0; i < n; i++ { - new := &Location{EntityMeta: &EntityMeta{ - Id: String(strconv.Itoa(i))}} + new := &Location{Id: String(strconv.Itoa(i))} locs = append(locs, new) } diff --git a/location_test.go b/location_test.go index 546abad..63ec052 100644 --- a/location_test.go +++ b/location_test.go @@ -23,14 +23,14 @@ func TestJSONSerialization(t *testing.T) { tests := []test{ {&Location{}, `{}`}, - {&Location{Address: &Address{City: nil}}, `{"address":{}}`}, // TODO: verify this is correct - {&Location{Address: &Address{City: String("")}}, `{"address":{"city":""}}`}, + {&Location{City: nil}, `{}`}, + {&Location{City: String("")}, `{"city":""}`}, {&Location{Languages: nil}, `{}`}, {&Location{Languages: nil}, `{}`}, {&Location{Languages: &[]string{}}, `{"languages":[]}`}, {&Location{Languages: &[]string{"English"}}, `{"languages":["English"]}`}, - {&Location{Photos: nil}, `{}`}, - {&Location{Photos: &[]LocationPhoto{}}, `{"photos":[]}`}, + {&Location{HolidayHours: nil}, `{}`}, + {&Location{HolidayHours: &[]LocationHolidayHours{}}, `{"holidayHours":[]}`}, } for _, test := range tests { @@ -77,12 +77,10 @@ var sampleLocationJSON = `{ "timestamp":1483891079283, "accountId":"479390", "locationName":"Best Buy", - "address":{ - "line1": "105 Topsham Fair Mall Rd", - "city":"Topsham", - "state":"ME", - "zip":"04086" - }, + "address":"105 Topsham Fair Mall Rd", + "city":"Topsham", + "state":"ME", + "zip":"04086", "countryCode":"US", "language":"en", "phone":"8882378289", @@ -145,15 +143,10 @@ var sampleLocationJSON = `{ ] } ], - "featuredMessage":{ - "description": "Black Friday 2016", - "url":"http://www.bestbuy.com/site/electronics/black-friday/pcmcat225600050002.c?id\u003dpcmcat225600050002\u0026ref\u003dNS\u0026loc\u003dns100" - }, - "websiteUrl": { - "url": "[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", - "displayWebsiteUrl":"[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", - "preferDisplayUrl":true - }, + "featuredMessage":"Black Friday 2016", + "featuredMessageUrl":"http://www.bestbuy.com/site/electronics/black-friday/pcmcat225600050002.c?id\u003dpcmcat225600050002\u0026ref\u003dNS\u0026loc\u003dns100", + "websiteUrl":"[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", + "displayWebsiteUrl":"[[BEST BUY LOCAL PAGES URL]]/?ref\u003dNS\u0026loc\u003dns100", "logo":{ "url":"http://www.yext-static.com/cms/79fb6255-2e84-435f-9864-aa0572fe4cbd.png" }, diff --git a/registry.go b/registry.go index 14a9bb2..4386172 100644 --- a/registry.go +++ b/registry.go @@ -3,15 +3,18 @@ package yext import ( "fmt" "reflect" + "strings" ) type Registry map[string]interface{} func (r Registry) Register(key string, val interface{}) { //From: https://github.com/reggo/reggo/blob/master/common/common.go#L169 - isPtr := reflect.ValueOf(val).Kind() == reflect.Ptr - var newVal interface{} - var tmp interface{} + var ( + isPtr = reflect.ValueOf(val).Kind() == reflect.Ptr + newVal interface{} + tmp interface{} + ) if isPtr { tmp = reflect.ValueOf(val).Elem().Interface() } else { @@ -21,10 +24,18 @@ func (r Registry) Register(key string, val interface{}) { r[key] = newVal } -func (r Registry) Lookup(key string) (interface{}, error) { +func (r Registry) Create(key string) (interface{}, error) { val, ok := r[key] if !ok { - return nil, fmt.Errorf("Unable to find key %s in registry %v", key, r) + return nil, fmt.Errorf("Unable to find key %s in registry. Known types: %s", key, strings.Join(r.Keys(), ", ")) } return reflect.New(reflect.TypeOf(val)).Interface(), nil } + +func (r Registry) Keys() []string { + var keys = []string{} + for key, _ := range r { + keys = append(keys, key) + } + return keys +} From e5d4cccde841c1bf7aa168dfb1fca8b6d2b3330a Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 10 Oct 2018 20:42:58 -0400 Subject: [PATCH 010/285] reflection fun --- entity_diff.go | 119 ++++++++++++++++++ entity_diff_test.go | 296 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 entity_diff.go create mode 100644 entity_diff_test.go diff --git a/entity_diff.go b/entity_diff.go new file mode 100644 index 0000000..b57442a --- /dev/null +++ b/entity_diff.go @@ -0,0 +1,119 @@ +package yext + +import ( + "reflect" +) + +func copyEntity(val interface{}) interface{} { + var ( + isPtr = reflect.ValueOf(val).Kind() == reflect.Ptr + newVal interface{} + tmp interface{} + ) + if isPtr { + tmp = reflect.ValueOf(val).Elem().Interface() + } else { + tmp = val + } + newVal = reflect.New(reflect.TypeOf(tmp)).Elem().Interface() + return reflect.New(reflect.TypeOf(newVal)).Interface() +} + +func diff(a interface{}, b interface{}, delta interface{}) bool { + var ( + aV, bV = reflect.ValueOf(a), reflect.ValueOf(b) + isDiff = false + ) + + if aV.Kind() == reflect.Ptr { + aV = aV.Elem() + } + if bV.Kind() == reflect.Ptr { + bV = bV.Elem() + } + + var ( + aT = aV.Type() + numA = aV.NumField() + ) + + for i := 0; i < numA; i++ { + var ( + nameA = aT.Field(i).Name + valA = aV.Field(i) + valB = bV.Field(i) + ) + + if nameA == "BaseEntity" { // TODO: Need to handle this case + continue + } + + if valA.Kind() == reflect.Struct { + diff := diff(valA.Addr().Interface(), valB.Addr().Interface(), reflect.ValueOf(delta).Elem().FieldByName(nameA).Addr().Interface()) + if diff { + isDiff = true + } + continue + } else if valA.Kind() == reflect.Ptr { + if !valB.IsNil() || !valB.CanSet() { // should we check can set? + valAIndirect := valA.Elem() + valBIndirect := valB.Elem() + if valAIndirect.Kind() == reflect.Struct { + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + diff := diff(valAIndirect.Addr().Interface(), valBIndirect.Addr().Interface(), reflect.ValueOf(delta).Elem().FieldByName(nameA).Interface()) + if diff { + isDiff = true + } + continue + } + } + } + + if valB.IsNil() || !valB.CanSet() { + continue + } + + // if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { + // continue + // } + // + + aI, bI := valA.Interface(), valB.Interface() + + comparableA, aOk := aI.(Comparable) + comparableB, bOk := bI.(Comparable) + + if aOk && bOk { + if !comparableA.Equal(comparableB) { + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + isDiff = true + } + } else if !reflect.DeepEqual(aI, bI) { + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + isDiff = true + } + } + return isDiff +} + +func Diff(a Entity, b Entity) (Entity, bool) { + if a.GetEntityType() != b.GetEntityType() { + return nil, true + } + + baseEntity := BaseEntity{ + Meta: &EntityMeta{ + Id: String(a.GetEntityId()), + }, + } + + // TODO: figure out how to handle other base entity attributes + delta := copyEntity(a) + reflect.ValueOf(delta).Elem().FieldByName("BaseEntity").Set(reflect.ValueOf(baseEntity)) + + isDiff := diff(a, b, delta) + if !isDiff { + return nil, isDiff + } + return delta.(Entity), isDiff +} diff --git a/entity_diff_test.go b/entity_diff_test.go new file mode 100644 index 0000000..3d965da --- /dev/null +++ b/entity_diff_test.go @@ -0,0 +1,296 @@ +package yext + +import ( + "reflect" + "testing" +) + +type diffTest struct { + name string + property string + isDiff bool + baseValue interface{} + newValue interface{} + deltaValue interface{} +} + +func SetValOnProperty(val interface{}, property string, entity Entity) { + reflect.ValueOf(entity).Elem().FieldByName(property).Set(reflect.ValueOf(val)) + +} + +func (c *CustomLocationEntity) SetValOnProperty(val interface{}, property string) *CustomLocationEntity { + if val != nil { + reflect.ValueOf(c).Elem().FieldByName(property).Set(reflect.ValueOf(val)) + } + return c +} + +func (c *CustomLocationEntity) SetName(name string) *CustomLocationEntity { + SetValOnProperty(name, "Name", c) + return c +} + +func TestEntityDiff(t *testing.T) { + /* + ORDER MATTERS + locA := &Location{ + Name: String("CTG"), + } + locB := &Location{} + delta, isDiff := locA.Diff(locB) + log.Println(isDiff) + log.Println(delta) + delta, isDiff = locB.Diff(locA) + log.Println(isDiff) + log.Println(delta) + */ + tests := []diffTest{ + diffTest{ + name: "Base Entity (Entity Meta) is equal", + property: "BaseEntity", + baseValue: BaseEntity{ + Meta: &EntityMeta{ + CategoryIds: Strings([]string{"123", "456"}), + }, + }, + newValue: BaseEntity{ + Meta: &EntityMeta{ + CategoryIds: Strings([]string{"123", "456"}), + }, + }, + isDiff: false, + }, + diffTest{ + name: "String not equal", + property: "Name", + baseValue: String("Hupman's Hotdogs"), + newValue: String("Bryan's Bagels"), + isDiff: true, + deltaValue: String("Bryan's Bagels"), + }, + diffTest{ + name: "*String equal", + property: "Name", + baseValue: String("Hupman's Hotdogs"), + newValue: String("Hupman's Hotdogs"), + isDiff: false, + }, + diffTest{ + name: "*Float not equal", + property: "YearEstablished", + baseValue: Float(2018), + newValue: Float(1993), + isDiff: true, + deltaValue: Float(1993), + }, + diffTest{ + name: "*Float equal", + property: "YearEstablished", + baseValue: Float(2018), + newValue: Float(2018), + isDiff: false, + }, + diffTest{ + name: "*Bool not equal", + property: "SuppressAddress", + baseValue: Bool(true), + newValue: Bool(false), + isDiff: true, + deltaValue: Bool(false), + }, + diffTest{ + name: "*Bool equal", + property: "SuppressAddress", + baseValue: Bool(true), + newValue: Bool(true), + isDiff: false, + }, + diffTest{ + name: "Address Equal", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark"), + }, + newValue: &Address{ + Line1: String("7900 Westpark"), + }, + isDiff: false, + }, + diffTest{ + name: "Address Not Equal", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark"), + }, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + isDiff: true, + deltaValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + }, + diffTest{ + name: "Address Not Equal (New Value Non-Empty String)", + property: "Address", + baseValue: &Address{ + Line1: String(""), + }, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + isDiff: true, + deltaValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + }, + diffTest{ + name: "Address Non Equal (New Value Empty String)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + newValue: &Address{ + Line1: String(""), + }, + isDiff: true, + deltaValue: &Address{ + Line1: String(""), + }, + }, + diffTest{ + name: "Address Equal (New Value nil)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + newValue: nil, + isDiff: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + //a := new(CustomLocationEntity).SetName("Name A") + var ( + a = new(CustomLocationEntity) + b = new(CustomLocationEntity) + ) + if test.property != "" { + a.SetValOnProperty(test.baseValue, test.property) + b.SetValOnProperty(test.newValue, test.property) + } + delta, isDiff := Diff(a, b) + if isDiff != test.isDiff { + t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) + } else if test.isDiff == false && delta != nil { + t.Errorf("Expected isDiff: %t. Got delta: %v", test.isDiff, delta) + } else if isDiff { + expectedDelta := new(CustomLocationEntity) + expectedDelta.SetValOnProperty(&EntityMeta{Id: String("")}, "Meta") + if test.property != "" && test.deltaValue != nil { + expectedDelta.SetValOnProperty(test.deltaValue, test.property) + } + if !reflect.DeepEqual(delta, expectedDelta) { + t.Errorf("Expected delta: %v. Got: %v", expectedDelta, delta) + } + } + }) + } + + // tests := []struct { + // EntityA Entity + // EntityB Entity + // isDiff bool + // }{ + // { + // EntityA: &LocationEntity{ + // BaseEntity: BaseEntity{ + // Meta: &EntityMeta{EntityType: ENTITYTYPE_LOCATION}, + // }, + // }, + // EntityB: &LocationEntity{ + // BaseEntity: BaseEntity{ + // Meta: &EntityMeta{EntityType: ENTITYTYPE_LOCATION}, + // }, + // }, + // isDiff: false, + // }, + // { + // EntityA: &LocationEntity{ + // BaseEntity: BaseEntity{ + // Meta: &EntityMeta{EntityType: ENTITYTYPE_LOCATION}, + // }, + // }, + // EntityB: &LocationEntity{ + // BaseEntity: BaseEntity{ + // Meta: &EntityMeta{EntityType: ENTITYTYPE_EVENT}, + // }, + // }, + // isDiff: true, + // }, + // { + // EntityA: &LocationEntity{ + // Name: String("Consulting"), + // }, + // EntityB: &LocationEntity{ + // Name: String("Yext Consulting"), + // }, + // isDiff: true, + // }, + // { + // EntityA: &LocationEntity{ + // Address: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // EntityB: &LocationEntity{ + // Address: &Address{ + // Line1: String("7900 Westpark St"), + // }, + // }, + // isDiff: true, + // }, + // { + // EntityA: &LocationEntity{ + // Address: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // EntityB: &LocationEntity{ + // Address: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // isDiff: false, + // }, + // { + // EntityA: &LocationEntity{ + // Address: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // EntityB: &LocationEntity{}, + // isDiff: false, + // }, + // { + // EntityA: &LocationEntity{}, + // EntityB: &LocationEntity{ + // Address: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // isDiff: true, + // }, + // } + // for _, test := range tests { + // _, isDiff := Diff(test.EntityA, test.EntityB) + // if isDiff != test.isDiff { + // t.Errorf("Expected diff to be %t was %t", test.isDiff, isDiff) + // } + // } +} + +//SetValOnProperty("Name", "") +//https://blog.golang.org/subtests +// subtests From 42543508b780064fdab7f09a054fd7397f306c8a Mon Sep 17 00:00:00 2001 From: cdworak Date: Tue, 16 Oct 2018 10:29:22 -0400 Subject: [PATCH 011/285] wip --- acl_diff_test.go | 852 ++++++++++++++++++----------------- cmd/main.go | 137 ++++++ customfield_service_test.go | 118 +++-- diff/main.go | 10 + entity.go | 13 +- entity_diff.go | 89 ++-- entity_diff_test.go | 868 ++++++++++++++++++++++++++---------- entity_service.go | 23 +- entity_service_test.go | 47 ++ entity_test.go | 195 ++++---- location.go | 10 + location_diff.go | 4 + location_diff_test.go | 2 +- location_entity.go | 52 +-- role_diff_test.go | 126 +++--- 15 files changed, 1622 insertions(+), 924 deletions(-) create mode 100644 cmd/main.go create mode 100644 diff/main.go create mode 100644 entity_service_test.go diff --git a/acl_diff_test.go b/acl_diff_test.go index 5d28975..a6fcb7b 100644 --- a/acl_diff_test.go +++ b/acl_diff_test.go @@ -1,432 +1,424 @@ package yext_test -import ( - "fmt" - "reflect" - "testing" - - "github.com/yext/yext-go" -) - -func TestACL_Diff(t *testing.T) { - tests := []struct { - name string - aclA yext.ACL - aclB yext.ACL - wantDelta *yext.ACL - wantDiff bool - }{ - { - name: "Identical ACLs", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - wantDelta: nil, - wantDiff: false, - }, - { - name: "Different Roles in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - wantDelta: &yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - }, - wantDiff: true, - }, - { - name: "Different 'On' params in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "123456", - AccessOn: yext.ACCESS_FOLDER, - }, - wantDelta: &yext.ACL{ - On: "123456", - }, - wantDiff: true, - }, - { - name: "Different 'AccessOn' params in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_LOCATION, - }, - wantDelta: &yext.ACL{ - AccessOn: yext.ACCESS_LOCATION, - }, - wantDiff: true, - }, - } - - for _, test := range tests { - if gotDelta, gotDiff := test.aclA.Diff(test.aclB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { - t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) - } - } -} - -func TestACLList_Diff(t *testing.T) { - tests := []struct { - name string - aclListA yext.ACLList - aclListB yext.ACLList - wantDelta yext.ACLList - wantDiff bool - }{ - { - name: "Identical ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDelta: nil, - wantDiff: false, - }, - { - name: "Identical ACLs in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - }, - wantDelta: nil, - wantDiff: false, - }, - { - name: "Different Length in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), - }, - On: "1234567", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), - }, - On: "1234567", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDiff: true, - }, - { - name: "Different Items in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("33"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("44"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("33"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("44"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDiff: true, - }, - { - name: "Some Identical and Some Different Items in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), - }, - On: "1234567", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - On: "12345", - AccessOn: yext.ACCESS_FOLDER, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), - }, - On: "123456", - AccessOn: yext.ACCESS_LOCATION, - }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), - }, - On: "1234567", - AccessOn: yext.ACCESS_LOCATION, - }, - }, - wantDiff: true, - }, - } - - for _, test := range tests { - if gotDelta, gotDiff := test.aclListA.Diff(test.aclListB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { - t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) - } - } -} +// func TestACL_Diff(t *testing.T) { +// tests := []struct { +// name string +// aclA yext.ACL +// aclB yext.ACL +// wantDelta *yext.ACL +// wantDiff bool +// }{ +// { +// name: "Identical ACLs", +// aclA: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// aclB: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// wantDelta: nil, +// wantDiff: false, +// }, +// { +// name: "Different Roles in ACL", +// aclA: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// aclB: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// wantDelta: &yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// }, +// wantDiff: true, +// }, +// { +// name: "Different 'On' params in ACL", +// aclA: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// aclB: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// wantDelta: &yext.ACL{ +// On: "123456", +// }, +// wantDiff: true, +// }, +// { +// name: "Different 'AccessOn' params in ACL", +// aclA: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// aclB: yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// wantDelta: &yext.ACL{ +// AccessOn: yext.ACCESS_LOCATION, +// }, +// wantDiff: true, +// }, +// } +// +// for _, test := range tests { +// if gotDelta, gotDiff := test.aclA.Diff(test.aclB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { +// t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) +// } +// } +// } +// +// func TestACLList_Diff(t *testing.T) { +// tests := []struct { +// name string +// aclListA yext.ACLList +// aclListB yext.ACLList +// wantDelta yext.ACLList +// wantDiff bool +// }{ +// { +// name: "Identical ACLLists", +// aclListA: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// aclListB: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDelta: nil, +// wantDiff: false, +// }, +// { +// name: "Identical ACLs in ACLLists", +// aclListA: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// }, +// aclListB: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// }, +// wantDelta: nil, +// wantDiff: false, +// }, +// { +// name: "Different Length in ACLLists", +// aclListA: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// aclListB: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("5"), +// Name: yext.String("Example Role Three"), +// }, +// On: "1234567", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDelta: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("5"), +// Name: yext.String("Example Role Three"), +// }, +// On: "1234567", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDiff: true, +// }, +// { +// name: "Different Items in ACLLists", +// aclListA: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// aclListB: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("33"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("44"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDelta: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("33"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("44"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDiff: true, +// }, +// { +// name: "Some Identical and Some Different Items in ACLLists", +// aclListA: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// aclListB: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("5"), +// Name: yext.String("Example Role Three"), +// }, +// On: "1234567", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDelta: yext.ACLList{ +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// On: "12345", +// AccessOn: yext.ACCESS_FOLDER, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role Two"), +// }, +// On: "123456", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// yext.ACL{ +// Role: yext.Role{ +// Id: yext.String("5"), +// Name: yext.String("Example Role Three"), +// }, +// On: "1234567", +// AccessOn: yext.ACCESS_LOCATION, +// }, +// }, +// wantDiff: true, +// }, +// } +// +// for _, test := range tests { +// if gotDelta, gotDiff := test.aclListA.Diff(test.aclListB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { +// t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) +// } +// } +// } diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..ceaa3dd --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "log" + + yext "gopkg.in/yext/yext-go.v2" +) + +type MorganLocation struct { + yext.LocationEntity + CMultiText *string `json:"c_multiText"` +} + +type HealthcareProfessional struct { + yext.Location +} + +type PersonEntity interface { + MyType() string +} + +type Teacher struct { + Person + Blah string +} + +func (t *Teacher) MyType() string { + return "Teacher" +} + +type Person struct { + Name string +} + +func (p *Person) MyType() string { + return "Person" +} + +func whatAmI(it interface{}) { + + if _, ok := it.(*Teacher); ok { + log.Println("is a teacher") + } + if _, ok := it.(*Person); ok { + log.Println("is a person") + } +} + +func main() { + // teacher := &Teacher{ + // Person: Person{ + // Name: "Catherine", + // }, + // Blah: "Blah", + // } + // whatAmI(teacher) + client := yext.NewClient(yext.NewDefaultConfig().WithApiKey("e929153c956b051cea51ec289bfd2383")) + client.EntityService.RegisterEntity(yext.ENTITYTYPE_LOCATION, &MorganLocation{}) + client.EntityService.RegisterEntity("HEALTHCARE_PROFESSIONAL", &HealthcareProfessional{}) + client.EntityService.RegisterEntity("HEALTHCARE_FACILITY", &HealthcareProfessional{}) + client.EntityService.RegisterEntity("ATM", &HealthcareProfessional{}) + // entities, err := client.EntityService.ListAll(nil) + // if err != nil { + // log.Fatal(err) + // } + // + // log.Printf("ListAll: Got %d entities", len(entities)) + // for _, entity := range entities { + // switch entity.(type) { + // case *MorganLocation: + // log.Println("I am a Morgan Location") + // morganEntity := entity.(*MorganLocation) + // log.Printf("PROFILE description: %s", GetString(morganEntity.Description)) + // log.Printf("CUSTOM multi: %s", GetString(morganEntity.CMultiText)) + // } + // } + + entity, _, err := client.EntityService.Get("CTG") + if err != nil { + log.Fatal(err) + } + morganEntity := entity.(*MorganLocation) + log.Printf("Get: Got %s", morganEntity.GetName()) + + // morganEntity.Name = yext.String(morganEntity.GetName() + "- 1") + // update := &MorganLocation{ + // Location: yext.Location{ + // // EntityMeta: yext.EntityMeta{ + // // Id: yext.String("CTG"), + // // }, + // Name: yext.String("CTG"), + // }, + // } + // _, err = client.EntityService.Edit(update) + // if err != nil { + // log.Fatal(err) + // } + + // log.Printf("Edit: Edited %s", morganEntity.GetName()) + + // morganEntity = &MorganLocation{ + // Location: yext.Location{ + // Name: yext.String("Yext Consulting"), + // EntityMeta: &yext.EntityMeta{ + // //Id: yext.String("CTG2"), + // EntityType: yext.ENTITYTYPE_LOCATION, + // CategoryIds: &[]string{"2374", "708"}, + // }, + // MainPhone: yext.String("8888888888"), + // Address: &yext.Address{ + // Line1: yext.String("7900 Westpark"), + // City: yext.String("McLean"), + // Region: yext.String("VA"), + // PostalCode: yext.String("22102"), + // }, + + // FeaturedMessage: &yext.FeaturedMessage{ + // Url: yext.String("www.yext.com"), + // Description: yext.String("Yext Consulting"), + // }, + // }, + // } + // + // _, err = client.EntityService.Create(morganEntity) + // if err != nil { + // log.Fatalf("Create error: %s", err) + // } + // log.Printf("Create: Created %s", morganEntity.GetEntityId()) + +} + +func GetString(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/customfield_service_test.go b/customfield_service_test.go index 8708270..9e1e2d6 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -60,53 +60,68 @@ var ( // 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{}{ - "start": "9:00", - "end": "17:00", + "monday": map[string]interface{}{ + "openIntervals": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, }, }, - "tuesday": []map[string]interface{}{ - map[string]interface{}{ - "start": "12:00", - "end": "15:00", - }, - { - "start": "5:00", - "end": "11:00", + "tuesday": map[string]interface{}{ + "openIntervals": []map[string]interface{}{ + map[string]interface{}{ + "start": "12:00", + "end": "15:00", + }, + { + "start": "5:00", + "end": "11:00", + }, }, }, - "wednesday": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", + "wednesday": map[string]interface{}{ + "openIntervals": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, }, }, - "thursday": []map[string]interface{}{ - map[string]interface{}{ - "start": "0:00", - "end": "23:59", + "thursday": map[string]interface{}{ + "openIntervals": []map[string]interface{}{ + map[string]interface{}{ + "start": "0:00", + "end": "23:59", + }, }, }, - "friday": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", + "friday": map[string]interface{}{ + "openIntervals": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, }, }, - "saturday": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", + "saturday": map[string]interface{}{ + "openIntervals": []map[string]interface{}{ + map[string]interface{}{ + "start": "9:00", + "end": "17:00", + }, }, }, + "sunday": map[string]interface{}{ + "isClosed": true, + }, "holidayHours": []interface{}{ map[string]interface{}{ "date": "2016-05-30", }, map[string]interface{}{ "date": "2016-05-31", - "hours": []interface{}{ + "openIntervals": []map[string]interface{}{ map[string]interface{}{ "start": "9:00", "end": "17:00", @@ -190,24 +205,39 @@ var ( // }, // }}, customFieldParseTest{"HOURS", hoursRawForEntity, Hours{ - Monday: []*Times{ - &Times{Start: "9:00", End: "17:00"}, + Monday: &DayHours{ + OpenIntervals: []*Interval{ + &Interval{Start: "9:00", End: "17:00"}, + }, }, - Tuesday: []*Times{ - &Times{Start: "12:00", End: "15:00"}, - &Times{Start: "5:00", End: "11:00"}, + Tuesday: &DayHours{ + OpenIntervals: []*Interval{ + &Interval{Start: "12:00", End: "15:00"}, + &Interval{Start: "5:00", End: "11:00"}, + }, }, - Wednesday: []*Times{ - &Times{Start: "9:00", End: "17:00"}, + Wednesday: &DayHours{ + OpenIntervals: []*Interval{ + &Interval{Start: "9:00", End: "17:00"}, + }, }, - Thursday: []*Times{ - &Times{Start: "0:00", End: "23:59"}, + Thursday: &DayHours{ + OpenIntervals: []*Interval{ + &Interval{Start: "0:00", End: "23:59"}, + }, }, - Friday: []*Times{ - &Times{Start: "9:00", End: "17:00"}, + Friday: &DayHours{ + OpenIntervals: []*Interval{ + &Interval{Start: "9:00", End: "17:00"}, + }, + }, + Saturday: &DayHours{ + OpenIntervals: []*Interval{ + &Interval{Start: "9:00", End: "17:00"}, + }, }, - Saturday: []*Times{ - &Times{Start: "9:00", End: "17:00"}, + Sunday: &DayHours{ + IsClosed: Bool(true), }, HolidayHours: &[]HolidayHours{ HolidayHours{ @@ -215,8 +245,8 @@ var ( }, HolidayHours{ Date: "2016-05-31", - Hours: []*Times{ - &Times{ + OpenIntervals: []*Interval{ + &Interval{ Start: "9:00", End: "17:00", }, diff --git a/diff/main.go b/diff/main.go new file mode 100644 index 0000000..297ae78 --- /dev/null +++ b/diff/main.go @@ -0,0 +1,10 @@ +package main + +import yext "gopkg.in/yext/yext-go.v2" + +func main() { + locA := &yext.Location{} + locB := &yext.Location{} + + locA.Diff(locB) +} diff --git a/entity.go b/entity.go index 582f882..da62d78 100644 --- a/entity.go +++ b/entity.go @@ -16,12 +16,11 @@ type EntityMeta struct { CategoryIds *[]string `json:"categoryIds,omitempty"` Language *string `json:"language,omitempty"` CountryCode *string `json:"countryCode,omitempty"` - // TODO: See if we still need and implement - nilIsEmpty bool } type BaseEntity struct { - Meta *EntityMeta `json:"meta,omitempty"` + Meta *EntityMeta `json:"meta,omitempty"` + nilIsEmpty bool } func (b *BaseEntity) GetEntityId() string { @@ -37,3 +36,11 @@ func (b *BaseEntity) GetEntityType() EntityType { } return "" } + +func (b *BaseEntity) GetNilIsEmpty() bool { + return b.nilIsEmpty +} + +func (b *BaseEntity) SetNilIsEmpty(val bool) { + b.nilIsEmpty = val +} diff --git a/entity_diff.go b/entity_diff.go index b57442a..e6dacec 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -1,28 +1,28 @@ package yext import ( + "log" "reflect" ) -func copyEntity(val interface{}) interface{} { +func instanceOf(val interface{}) interface{} { var ( - isPtr = reflect.ValueOf(val).Kind() == reflect.Ptr - newVal interface{} - tmp interface{} + isPtr = reflect.ValueOf(val).Kind() == reflect.Ptr + tmp interface{} ) if isPtr { tmp = reflect.ValueOf(val).Elem().Interface() } else { tmp = val } - newVal = reflect.New(reflect.TypeOf(tmp)).Elem().Interface() - return reflect.New(reflect.TypeOf(newVal)).Interface() + return reflect.New(reflect.TypeOf(tmp)).Interface() } -func diff(a interface{}, b interface{}, delta interface{}) bool { +func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (interface{}, bool) { var ( aV, bV = reflect.ValueOf(a), reflect.ValueOf(b) isDiff = false + delta = instanceOf(a) ) if aV.Kind() == reflect.Ptr { @@ -43,40 +43,82 @@ func diff(a interface{}, b interface{}, delta interface{}) bool { valA = aV.Field(i) valB = bV.Field(i) ) + log.Println(nameA) - if nameA == "BaseEntity" { // TODO: Need to handle this case + if nameA == "nilIsEmpty" { continue } + log.Println("valA", valA) + log.Println("valB", valB) + + // If Kind() == struct, this is likely an embedded struct if valA.Kind() == reflect.Struct { - diff := diff(valA.Addr().Interface(), valB.Addr().Interface(), reflect.ValueOf(delta).Elem().FieldByName(nameA).Addr().Interface()) + log.Println("is struct") + d, diff := diff(valA.Addr().Interface(), valB.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d).Elem()) } continue } else if valA.Kind() == reflect.Ptr { - if !valB.IsNil() || !valB.CanSet() { // should we check can set? + // log.Println("is pointer") + // if valA.IsNil() { // implies valB is non-nil + // log.Println("I am nil") + // //valBIndirect := valB.Elem() + // log.Println("Nil is empty b", nilIsEmptyB) + // log.Println("Nil is empty a", nilIsEmptyA) + // log.Println("is zero a", isZeroValue(valA, nilIsEmptyA)) + // log.Println("is zero b", isZeroValue(valB.Elem(), nilIsEmptyB)) + // if isZeroValue(valA, nilIsEmptyA) && isZeroValue(valB, nilIsEmptyB) { + // continue + // } + // isDiff = true + // reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + // continue + log.Println("isZeroA", isZeroValue(valA, nilIsEmptyA)) + log.Println("isZeroB", isZeroValue(valB, nilIsEmptyB)) + if !valB.IsNil() && valB.CanSet() { + log.Println("val B is not nil") valAIndirect := valA.Elem() valBIndirect := valB.Elem() + // log.Println("valAInd", valAIndirect) + // log.Println("valBInd", valBIndirect) + // log.Println("kind:", valAIndirect.Kind()) if valAIndirect.Kind() == reflect.Struct { + // If base is &Address{Line1:"abc"} and new is &Address{}, we want &Address for the diff + // if isZeroValue(valBIndirect, getNilIsEmpty(valBIndirect)) { + // log.Println("val b is zero") + // if !(isZeroValue(valAIndirect, getNilIsEmpty(valAIndirect))) { + // log.Println("val a is not zero") + // isDiff = true + // reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + // } + // continue + // } reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) - diff := diff(valAIndirect.Addr().Interface(), valBIndirect.Addr().Interface(), reflect.ValueOf(delta).Elem().FieldByName(nameA).Interface()) + d, diff := diff(valAIndirect.Addr().Interface(), valBIndirect.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d)) } continue } } } - if valB.IsNil() || !valB.CanSet() { + // before we want to recur we want to make sure neither is zoer + + if valB.Kind() == reflect.Ptr && valB.IsNil() { + continue + } + if !valB.CanSet() { continue } - // if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { - // continue - // } - // + if isZeroValue(valA, getNilIsEmpty(a)) && isZeroValue(valB, getNilIsEmpty(b)) { + continue + } aI, bI := valA.Interface(), valB.Interface() @@ -93,25 +135,16 @@ func diff(a interface{}, b interface{}, delta interface{}) bool { isDiff = true } } - return isDiff + return delta, isDiff } +// Diff(a, b): a is base, b is new func Diff(a Entity, b Entity) (Entity, bool) { if a.GetEntityType() != b.GetEntityType() { return nil, true } - baseEntity := BaseEntity{ - Meta: &EntityMeta{ - Id: String(a.GetEntityId()), - }, - } - - // TODO: figure out how to handle other base entity attributes - delta := copyEntity(a) - reflect.ValueOf(delta).Elem().FieldByName("BaseEntity").Set(reflect.ValueOf(baseEntity)) - - isDiff := diff(a, b, delta) + delta, isDiff := diff(a, b, getNilIsEmpty(a), getNilIsEmpty(b)) if !isDiff { return nil, isDiff } diff --git a/entity_diff_test.go b/entity_diff_test.go index 3d965da..9d57311 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1,193 +1,666 @@ package yext import ( + "log" "reflect" "testing" ) type diffTest struct { - name string - property string - isDiff bool - baseValue interface{} - newValue interface{} - deltaValue interface{} -} - -func SetValOnProperty(val interface{}, property string, entity Entity) { - reflect.ValueOf(entity).Elem().FieldByName(property).Set(reflect.ValueOf(val)) + name string + property string + isDiff bool + baseValue interface{} + newValue interface{} + baseNilIsEmpty bool + newNilIsEmpty bool + deltaValue interface{} } -func (c *CustomLocationEntity) SetValOnProperty(val interface{}, property string) *CustomLocationEntity { +func setValOnProperty(val interface{}, property string, entity Entity) { if val != nil { - reflect.ValueOf(c).Elem().FieldByName(property).Set(reflect.ValueOf(val)) + reflect.ValueOf(entity).Elem().FieldByName(property).Set(reflect.ValueOf(val)) } - return c } -func (c *CustomLocationEntity) SetName(name string) *CustomLocationEntity { - SetValOnProperty(name, "Name", c) +func (c *CustomLocationEntity) SetValOnProperty(val interface{}, property string) *CustomLocationEntity { + setValOnProperty(val, property, c) return c } func TestEntityDiff(t *testing.T) { - /* - ORDER MATTERS - locA := &Location{ - Name: String("CTG"), - } - locB := &Location{} - delta, isDiff := locA.Diff(locB) - log.Println(isDiff) - log.Println(delta) - delta, isDiff = locB.Diff(locA) - log.Println(isDiff) - log.Println(delta) - */ + // BASIC EQUALITY: + // A) equal + // B) not equal + + // BASIC EQUALITY WITH ZERO VALUES: + // C) base is zero value, new is non-zero value + // D) base is non-zero value, new is zero value + // E) both are zero value + + // EQUALITY WITH NIL (and no nil is empty) + // F) both are nil + // G) base is non-zero value, new is nil + // H) base is nil, new is non-zero value + // I) base is nil, new is zero value + // J) base is zero value, new is nil + + // EQUALITY WITH NIL (and nil is empty) + // K) base is nil (nil is empty), new is zero value + // L) base is nil (nil is empty), new is zero value (nil is empty) + // M) base is zero value, new is nil (nil is empty) + // N) base is zero value (nil is empty), new is nil (nil is empty) + tests := []diffTest{ + // Meta + // diffTest{ + // name: "Base Entity: not equal", + // property: "BaseEntity", + // baseValue: BaseEntity{ + // Meta: &EntityMeta{ + // Id: String("CTG"), + // CategoryIds: Strings([]string{"123"}), + // }, + // }, + // newValue: BaseEntity{ + // Meta: &EntityMeta{ + // Id: String("CTG"), + // CategoryIds: Strings([]string{"123", "456"}), + // }, + // }, + // isDiff: true, + // deltaValue: BaseEntity{ + // Meta: &EntityMeta{ + // Id: String("CTG"), + // CategoryIds: Strings([]string{"123", "456"}), + // }, + // }, + // }, + // diffTest{ + // name: "Base Entity: equal", + // property: "BaseEntity", + // baseValue: BaseEntity{ + // Meta: &EntityMeta{ + // CategoryIds: Strings([]string{"123", "456"}), + // }, + // }, + // newValue: BaseEntity{ + // Meta: &EntityMeta{ + // CategoryIds: Strings([]string{"123", "456"}), + // }, + // }, + // isDiff: false, + // }, + // // String tests + // diffTest{ + // name: "*String: equal (A)", + // property: "Name", + // baseValue: String("Hupman's Hotdogs"), + // newValue: String("Hupman's Hotdogs"), + // isDiff: false, + // }, + // diffTest{ + // name: "*String: not equal (B)", + // property: "Name", + // baseValue: String("Hupman's Hotdogs"), + // newValue: String("Bryan's Bagels"), + // isDiff: true, + // deltaValue: String("Bryan's Bagels"), + // }, + // diffTest{ + // name: "*String: base is empty string, new is not (C)", + // property: "Name", + // baseValue: String(""), + // newValue: String("Bryan's Bagels"), + // isDiff: true, + // deltaValue: String("Bryan's Bagels"), + // }, + // diffTest{ + // name: "*String: new is empty string, base is not (D)", + // property: "Name", + // baseValue: String("Hupman's Hotdogs"), + // newValue: String(""), + // isDiff: true, + // deltaValue: String(""), + // }, + // diffTest{ + // name: "*String: both are empty (E)", + // property: "Name", + // baseValue: String(""), + // newValue: String(""), + // isDiff: false, + // }, + // diffTest{ + // name: "*String: both are nil (F)", + // property: "Name", + // baseValue: nil, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*String: base is not nil, new is nil (G)", + // property: "Name", + // baseValue: String("Bryan's Bagels"), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*String: base is nil, new is not nil (H)", + // property: "Name", + // baseValue: nil, + // newValue: String("Bryan's Bagels"), + // isDiff: true, + // deltaValue: String("Bryan's Bagels"), + // }, + // diffTest{ + // name: "*String: base is nil, new is empty string (I)", + // property: "Name", + // baseValue: nil, + // newValue: String(""), + // isDiff: true, + // deltaValue: String(""), + // }, + // diffTest{ + // name: "*String: base is empty string, new is nil (J)", + // property: "Name", + // baseValue: String(""), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*String: base is nil (with nil is empty), new is empty string (K)", + // property: "Name", + // baseValue: nil, + // newValue: String(""), + // baseNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*String: base is nil (with nil is empty), new is empty string (with nil is empty) (L)", + // property: "Name", + // baseValue: nil, + // newValue: String(""), + // baseNilIsEmpty: true, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*String: base is empty string, new is nil (with nil is empty) (M)", + // property: "Name", + // baseValue: String(""), + // newValue: nil, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*String: base is empty string (with nil is empty), new is nil (with nil is empty) (N)", + // property: "Name", + // baseValue: String(""), + // baseNilIsEmpty: true, + // newValue: nil, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // + // //Float tests + // diffTest{ + // name: "*Float: equal (A)", + // property: "YearEstablished", + // baseValue: Float(2018), + // newValue: Float(2018), + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: not equal (B)", + // property: "YearEstablished", + // baseValue: Float(2018), + // newValue: Float(2006), + // isDiff: true, + // deltaValue: Float(2006), + // }, + // diffTest{ + // name: "*Float: base is 0, new is not 0 (C)", + // property: "YearEstablished", + // baseValue: Float(0), + // newValue: Float(2006), + // isDiff: true, + // deltaValue: Float(2006), + // }, + // diffTest{ + // name: "*Float: base is not 0, new is 0 (D)", + // property: "YearEstablished", + // baseValue: Float(2006), + // newValue: Float(0), + // isDiff: true, + // deltaValue: Float(0), + // }, + // diffTest{ + // name: "*Float: both are 0 (E)", + // property: "YearEstablished", + // baseValue: Float(2018), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: both are nil (F)", + // property: "YearEstablished", + // baseValue: nil, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: base is not 0, new is nil (G)", + // property: "YearEstablished", + // baseValue: Float(1993), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: base is nil, new is not 0 (H)", + // property: "YearEstablished", + // baseValue: nil, + // newValue: Float(1993), + // isDiff: true, + // deltaValue: Float(1993), + // }, + // diffTest{ + // name: "*Float: base is nil, new is 0 (I)", + // property: "YearEstablished", + // baseValue: nil, + // newValue: Float(0), + // isDiff: true, + // deltaValue: Float(0), + // }, + // diffTest{ + // name: "*Float: base is 0, new is nil (J)", + // property: "YearEstablished", + // baseValue: Float(0), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: base is nil (nil is empty), new is 0 (K)", + // property: "YearEstablished", + // baseValue: nil, + // newValue: Float(0), + // baseNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: base is nil (nil is empty), new is 0 (nil is empty) (L)", + // property: "YearEstablished", + // baseValue: nil, + // newValue: Float(0), + // baseNilIsEmpty: true, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: base is 0, new is nil (nil is empty) (M)", + // property: "YearEstablished", + // baseValue: Float(0), + // newValue: nil, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*Float: base is 0 (nil is empty), new is nil (nil is empty) (N)", + // property: "YearEstablished", + // baseValue: Float(0), + // baseNilIsEmpty: true, + // newValue: nil, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // + // // Bool tests + // diffTest{ + // name: "*Bool: both true (A)", + // property: "SuppressAddress", + // baseValue: Bool(true), + // newValue: Bool(true), + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: both false (A/E)", + // property: "SuppressAddress", + // baseValue: Bool(false), + // newValue: Bool(false), + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: not equal, base true, new false (B/D)", + // property: "SuppressAddress", + // baseValue: Bool(true), + // newValue: Bool(false), + // isDiff: true, + // deltaValue: Bool(false), + // }, + // diffTest{ + // name: "*Bool: not equal, base is false, new is true (B/C)", + // property: "SuppressAddress", + // baseValue: Bool(false), + // newValue: Bool(true), + // isDiff: true, + // deltaValue: Bool(true), + // }, + // diffTest{ + // name: "*Bool: both are nil (F)", + // property: "SuppressAddress", + // baseValue: nil, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: base is non-zero, new is nil (G)", + // property: "SuppressAddress", + // baseValue: Bool(true), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: base is nil, new is non-zero value (H)", + // property: "SuppressAddress", + // baseValue: nil, + // newValue: Bool(true), + // isDiff: true, + // deltaValue: Bool(true), + // }, + // diffTest{ + // name: "*Bool: base is nil, new is zero value (I)", + // property: "SuppressAddress", + // baseValue: nil, + // newValue: Bool(false), + // isDiff: true, + // deltaValue: Bool(false), + // }, + // diffTest{ + // name: "*Bool: base is zero value, new is nil (J)", + // property: "SuppressAddress", + // baseValue: Bool(false), + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: base is nil (nil is empty), new is zero value (K)", + // property: "SuppressAddress", + // baseValue: nil, + // newValue: Bool(false), + // baseNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", + // property: "SuppressAddress", + // baseValue: nil, + // newValue: Bool(false), + // baseNilIsEmpty: true, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: base is zero value, new is nil (nil is empty) (L)", + // property: "SuppressAddress", + // baseValue: Bool(false), + // newValue: nil, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // diffTest{ + // name: "*Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", + // property: "SuppressAddress", + // baseValue: Bool(false), + // newValue: nil, + // baseNilIsEmpty: true, + // newNilIsEmpty: true, + // isDiff: false, + // }, + // + // Struct Diffs + // diffTest{ + // name: "Address: equal (A)", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark"), + // }, + // newValue: &Address{ + // Line1: String("7900 Westpark"), + // }, + // isDiff: false, + // }, + // diffTest{ + // name: "Address: line 1 and line 2 equal (A)", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // Line2: String("Suite 500"), + // }, + // newValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // Line2: String("Suite 500"), + // }, + // isDiff: false, + // }, + // diffTest{ + // name: "Address: not equal (B)", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark"), + // }, + // newValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // isDiff: true, + // deltaValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // diffTest{ + // name: "Address: line 1 equal, line 2 not equal (B)", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // Line2: String("Suite 500"), + // }, + // newValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // Line2: String("Suite 700"), + // }, + // isDiff: true, + // deltaValue: &Address{ + // Line2: String("Suite 700"), + // }, + // }, + // diffTest{ + // name: "Address: base is empty struct, new is not empty (C)", + // property: "Address", + // baseValue: &Address{}, + // newValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // isDiff: true, + // deltaValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // diffTest{ + // name: "Address: base is not empty struct, new is empty struct (D)", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // newValue: &Address{}, + // isDiff: true, + // deltaValue: &Address{}, + // }, + // diffTest{ + // name: "List: base is not empty struct, new is empty struct (D)", + // property: "CFTextList", + // baseValue: &[]string{"a", "b"}, + // newValue: &[]string{}, + // isDiff: true, + // deltaValue: &[]string{}, + // }, + // diffTest{ + // name: "Address: both are empty struct (E)", + // property: "Address", + // baseValue: &Address{}, + // newValue: &Address{}, + // isDiff: false, + // }, + // diffTest{ + // name: "Address: both are nil (F)", + // property: "Address", + // baseValue: nil, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "Address: base is non-empty struct, new is nil (G)", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "Address: base is nil, new is non-empty struct (H)", + // property: "Address", + // baseValue: nil, + // newValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // isDiff: true, + // deltaValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // diffTest{ + // name: "Address: base is nil, new is empty struct (I)", + // property: "Address", + // baseValue: nil, + // newValue: &Address{}, + // isDiff: true, + // deltaValue: &Address{}, + // }, + // diffTest{ + // name: "Address: base is empty struct, new is nil (J)", + // property: "Address", + // baseValue: &Address{}, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "Address: base is nil (nil is empty), new is empty struct (K)", + // property: "Address", + // baseValue: nil, + // newValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, // NOT &Address{} + // baseNilIsEmpty: true, + // isDiff: false, + // }, diffTest{ - name: "Base Entity (Entity Meta) is equal", - property: "BaseEntity", - baseValue: BaseEntity{ - Meta: &EntityMeta{ - CategoryIds: Strings([]string{"123", "456"}), - }, - }, - newValue: BaseEntity{ - Meta: &EntityMeta{ - CategoryIds: Strings([]string{"123", "456"}), - }, - }, - isDiff: false, - }, - diffTest{ - name: "String not equal", - property: "Name", - baseValue: String("Hupman's Hotdogs"), - newValue: String("Bryan's Bagels"), - isDiff: true, - deltaValue: String("Bryan's Bagels"), - }, - diffTest{ - name: "*String equal", - property: "Name", - baseValue: String("Hupman's Hotdogs"), - newValue: String("Hupman's Hotdogs"), - isDiff: false, - }, - diffTest{ - name: "*Float not equal", - property: "YearEstablished", - baseValue: Float(2018), - newValue: Float(1993), - isDiff: true, - deltaValue: Float(1993), - }, - diffTest{ - name: "*Float equal", - property: "YearEstablished", - baseValue: Float(2018), - newValue: Float(2018), - isDiff: false, - }, - diffTest{ - name: "*Bool not equal", - property: "SuppressAddress", - baseValue: Bool(true), - newValue: Bool(false), - isDiff: true, - deltaValue: Bool(false), - }, - diffTest{ - name: "*Bool equal", - property: "SuppressAddress", - baseValue: Bool(true), - newValue: Bool(true), - isDiff: false, - }, - diffTest{ - name: "Address Equal", + name: "Address: base is empty struct, new is nil (nis is empty) (?)", property: "Address", - baseValue: &Address{ - Line1: String("7900 Westpark"), - }, - newValue: &Address{ - Line1: String("7900 Westpark"), - }, - isDiff: false, + + baseValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, }, diffTest{ - name: "Address Not Equal", - property: "Address", - baseValue: &Address{ - Line1: String("7900 Westpark"), - }, - newValue: &Address{ - Line1: String("7900 Westpark Dr"), - }, - isDiff: true, - deltaValue: &Address{ - Line1: String("7900 Westpark Dr"), - }, + name: "Address: base is empty struct, new is nil (nil is empty) (?)", + property: "Address", + baseValue: &Address{}, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, }, + // diffTest{ + // name: "Address: base is nil (nil is empty), new is empty struct (?)", + // property: "Address", + // baseValue: nil, + // baseNilIsEmpty: true, + // newValue: &Address{}, + // isDiff: false, + // }, diffTest{ - name: "Address Not Equal (New Value Non-Empty String)", - property: "Address", - baseValue: &Address{ - Line1: String(""), - }, - newValue: &Address{ - Line1: String("7900 Westpark Dr"), - }, - isDiff: true, - deltaValue: &Address{ - Line1: String("7900 Westpark Dr"), - }, + name: "Address: base is nil (nil is empty), new is empty struct (?)", + property: "Address", + baseValue: nil, + baseNilIsEmpty: true, + newValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + isDiff: false, }, diffTest{ - name: "Address Non Equal (New Value Empty String)", - property: "Address", - baseValue: &Address{ - Line1: String("7900 Westpark Dr"), - }, - newValue: &Address{ - Line1: String(""), - }, - isDiff: true, - deltaValue: &Address{ - Line1: String(""), - }, - }, - diffTest{ - name: "Address Equal (New Value nil)", - property: "Address", - baseValue: &Address{ - Line1: String("7900 Westpark Dr"), - }, - newValue: nil, - isDiff: false, + name: "Address: base is nil, new is nil (nil is empty) (?)", + property: "Address", + baseValue: nil, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, }, + // // TODO: add L, M, N + // + // diffTest{ + // name: "Address: base line1 is empty", + // property: "Address", + // baseValue: &Address{ + // Line1: String(""), + // }, + // newValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // isDiff: true, + // deltaValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // }, + // diffTest{ + // name: "Address: new line1 is empty", + // property: "Address", + // baseValue: &Address{ + // Line1: String("7900 Westpark Dr"), + // }, + // newValue: &Address{ + // Line1: String(""), + // }, + // isDiff: true, + // deltaValue: &Address{ + // Line1: String(""), + // }, + // }, } + + log.Println("&Address{}, true", isZeroValue(reflect.ValueOf(&Address{}), true)) + log.Println("&Address{}, false", isZeroValue(reflect.ValueOf(&Address{}), false)) + log.Println("Address{}, true", isZeroValue(reflect.ValueOf(Address{}), true)) + log.Println("Address{}, false", isZeroValue(reflect.ValueOf(Address{}), false)) + for _, test := range tests { t.Run(test.name, func(t *testing.T) { - //a := new(CustomLocationEntity).SetName("Name A") var ( - a = new(CustomLocationEntity) - b = new(CustomLocationEntity) + baseEntity = new(CustomLocationEntity) + newEntity = new(CustomLocationEntity) ) if test.property != "" { - a.SetValOnProperty(test.baseValue, test.property) - b.SetValOnProperty(test.newValue, test.property) + baseEntity.SetValOnProperty(test.baseValue, test.property) + newEntity.SetValOnProperty(test.newValue, test.property) + } + if test.baseNilIsEmpty { + setNilIsEmpty(baseEntity) } - delta, isDiff := Diff(a, b) + if test.newNilIsEmpty { + setNilIsEmpty(newEntity) + } + delta, isDiff := Diff(baseEntity, newEntity) if isDiff != test.isDiff { t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) } else if test.isDiff == false && delta != nil { t.Errorf("Expected isDiff: %t. Got delta: %v", test.isDiff, delta) } else if isDiff { expectedDelta := new(CustomLocationEntity) - expectedDelta.SetValOnProperty(&EntityMeta{Id: String("")}, "Meta") if test.property != "" && test.deltaValue != nil { expectedDelta.SetValOnProperty(test.deltaValue, test.property) } @@ -197,100 +670,9 @@ func TestEntityDiff(t *testing.T) { } }) } - - // tests := []struct { - // EntityA Entity - // EntityB Entity - // isDiff bool - // }{ - // { - // EntityA: &LocationEntity{ - // BaseEntity: BaseEntity{ - // Meta: &EntityMeta{EntityType: ENTITYTYPE_LOCATION}, - // }, - // }, - // EntityB: &LocationEntity{ - // BaseEntity: BaseEntity{ - // Meta: &EntityMeta{EntityType: ENTITYTYPE_LOCATION}, - // }, - // }, - // isDiff: false, - // }, - // { - // EntityA: &LocationEntity{ - // BaseEntity: BaseEntity{ - // Meta: &EntityMeta{EntityType: ENTITYTYPE_LOCATION}, - // }, - // }, - // EntityB: &LocationEntity{ - // BaseEntity: BaseEntity{ - // Meta: &EntityMeta{EntityType: ENTITYTYPE_EVENT}, - // }, - // }, - // isDiff: true, - // }, - // { - // EntityA: &LocationEntity{ - // Name: String("Consulting"), - // }, - // EntityB: &LocationEntity{ - // Name: String("Yext Consulting"), - // }, - // isDiff: true, - // }, - // { - // EntityA: &LocationEntity{ - // Address: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // EntityB: &LocationEntity{ - // Address: &Address{ - // Line1: String("7900 Westpark St"), - // }, - // }, - // isDiff: true, - // }, - // { - // EntityA: &LocationEntity{ - // Address: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // EntityB: &LocationEntity{ - // Address: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // isDiff: false, - // }, - // { - // EntityA: &LocationEntity{ - // Address: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // EntityB: &LocationEntity{}, - // isDiff: false, - // }, - // { - // EntityA: &LocationEntity{}, - // EntityB: &LocationEntity{ - // Address: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // isDiff: true, - // }, - // } - // for _, test := range tests { - // _, isDiff := Diff(test.EntityA, test.EntityB) - // if isDiff != test.isDiff { - // t.Errorf("Expected diff to be %t was %t", test.isDiff, isDiff) - // } - // } } -//SetValOnProperty("Name", "") -//https://blog.golang.org/subtests -// subtests +// test comparable to make sure it gets used +// UnorderedStrings +// text list +// hours diff --git a/entity_service.go b/entity_service.go index 8e4517b..a12f5e0 100644 --- a/entity_service.go +++ b/entity_service.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/url" + "reflect" ) const entityPath = "entities" @@ -104,6 +105,7 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { return "", err } for _, entity := range typedEntities { + setNilIsEmpty(entity) entities = append(entities, entity) } return resp.PageToken, err @@ -184,12 +186,29 @@ func (e *EntityService) Get(id string) (Entity, *Response, error) { return nil, r, err } - // TODO: nil is emtpy - //v.nilIsEmpty = true + setNilIsEmpty(entity) return entity, r, nil } +func setNilIsEmpty(i interface{}) { + m := reflect.ValueOf(i).MethodByName("SetNilIsEmpty") + if m.IsValid() { + m.Call([]reflect.Value{reflect.ValueOf(true)}) + } +} + +func getNilIsEmpty(i interface{}) bool { + m := reflect.ValueOf(i).MethodByName("GetNilIsEmpty") + if m.IsValid() { + values := m.Call([]reflect.Value{}) + if len(values) == 1 { + return values[0].Interface().(bool) + } + } + return false +} + // TODO: Currently an error with API. Need to test this func (e *EntityService) Create(y Entity) (*Response, error) { // TODO: custom field validation diff --git a/entity_service_test.go b/entity_service_test.go new file mode 100644 index 0000000..ed6a2c1 --- /dev/null +++ b/entity_service_test.go @@ -0,0 +1,47 @@ +package yext + +import "testing" + +func TestSetNilIsEmpty(t *testing.T) { + type randomStruct struct{} + tests := []struct { + i interface{} + before bool + after bool + }{ + { + i: &BaseEntity{}, + before: false, + after: true, + }, + { + i: &BaseEntity{ + nilIsEmpty: true, + }, + before: true, + after: true, + }, + { + i: &LocationEntity{}, + before: false, + after: true, + }, + { + i: &randomStruct{}, + before: false, + after: false, + }, + } + + for _, test := range tests { + before := getNilIsEmpty(test.i) + if before != test.before { + t.Errorf("Before set nil is empty: Expected %t, got %t", test.before, before) + } + setNilIsEmpty(test.i) + after := getNilIsEmpty(test.i) + if after != test.after { + t.Errorf("After set nil is empty: Expected %t, got %t", test.after, after) + } + } +} diff --git a/entity_test.go b/entity_test.go index e0d117a..8d4b4bd 100644 --- a/entity_test.go +++ b/entity_test.go @@ -6,21 +6,26 @@ import ( "testing" ) -// Note to self: fields with json tag HAVE to be exported -// See: https://stackoverflow.com/questions/11126793/json-and-dealing-with-unexported-fields +type CustomCustomFieldType struct { + Name *string + Date *float64 + Person *bool +} + type CustomLocationEntity struct { 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"` - CFTextList *[]string `json:"cf_TextList,omitempty"` - CFGallery []*Photo `json:"cf_Gallery,omitempty"` - CFPhoto *Photo `json:"cf_Photo,omitempty"` - CFVideos []*Video `json:"cf_Videos,omitempty"` - CFVideo *Video `json:"cf_Video,omitempty"` - CFDate *Date `json:"cf_Date,omitempty"` - CFSingleOption *string `json:"cf_SingleOtpion,omitempty"` - CFMultiOption *[]string `json:"cf_MultiOption,omitempty"` + CFHours *Hours `json:"cf_Hours,omitempty"` + CFUrl *string `json:"cf_Url,omitempty"` + CFDailyTimes *DailyTimes `json:"cf_DailyTimes,omitempty"` + CFTextList *[]string `json:"cf_TextList,omitempty"` + CFGallery []*Photo `json:"cf_Gallery,omitempty"` + CFPhoto *Photo `json:"cf_Photo,omitempty"` + CFVideos []*Video `json:"cf_Videos,omitempty"` + CFVideo *Video `json:"cf_Video,omitempty"` + CFDate *Date `json:"cf_Date,omitempty"` + CFSingleOption *string `json:"cf_SingleOtpion,omitempty"` + CFMultiOption *[]UnorderedStrings `json:"cf_MultiOption,omitempty"` + CFCustomType *CustomCustomFieldType `json:"cf_CustomCustomFieldType,omitempty"` } func entityToJSONString(entity Entity) (error, string) { @@ -114,91 +119,119 @@ var sampleEntityJSON = `{ "addressHidden": false, "description": "This is my description", "hours": { - "monday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "tuesday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "wednesday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "thursday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "friday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "sunday": [ - { - "start": "00:00", - "end": "23:59" - } - ], + "monday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "tuesday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "wednesday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "thursday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "friday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "saturday": { + "isClosed": true + }, + "sunday": { + "openIntervals": [ + { + "start": "00:00", + "end": "23:59" + } + ] + }, "holidayHours": [ { "date": "2018-12-25", + "isClosed": true, "isRegularHours": false } ] }, "name": "Yext Consulting", "cf_Hours": { - "monday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "tuesday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "wednesday": [ - { - "start": "09:00", - "end": "17:00" - } - ], - "thursday": [ + "monday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "tuesday": { + "openIntervals": [ { "start": "09:00", "end": "17:00" } - ], - "friday": [ + ] + }, + "wednesday": { + "openIntervals": [ { "start": "09:00", - "end": "14:00" - }, - { - "start": "15:00", "end": "17:00" } - ], - "saturday": [ - { - "start": "00:00", - "end": "23:59" - } - ], + ] + }, + "thursday": { + "openIntervals": [ + { + "start": "09:00", + "end": "17:00" + } + ] + }, + "friday": { + "openIntervals": [ + { + "start": "09:00", + "end": "14:00" + }, + { + "start": "15:00", + "end": "17:00" + } + ] + }, + "saturday": { + "openIntervals": [ + { + "start": "00:00", + "end": "23:59" + } + ] + }, "holidayHours": [ { "date": "2018-10-13", diff --git a/location.go b/location.go index 8d32ee5..a8f3d8d 100644 --- a/location.go +++ b/location.go @@ -10,9 +10,19 @@ import ( "fmt" ) +type EmbeddedStruct struct { + MyName string +} + +type EmbeddedStruct2 struct { + MyName2 string +} + // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { + // EmbeddedStruct + // EmbeddedStruct2 // Admin Id *string `json:"id,omitempty"` AccountId *string `json:"accountId,omitempty"` diff --git a/location_diff.go b/location_diff.go index e65450f..78543d0 100644 --- a/location_diff.go +++ b/location_diff.go @@ -43,6 +43,10 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { valB = bV.Field(i) ) + // log.Println(nameA) + // log.Println(valB.CanSet()) + // log.Println(valB.Kind()) + if !valB.CanSet() { continue } diff --git a/location_diff_test.go b/location_diff_test.go index 80791ca..220e8ca 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -630,7 +630,7 @@ type floatTest struct { var floatTests = []floatTest{ {Float(1234.0), Float(1234.0), false, false, nil}, {Float(1234.0), nil, false, false, nil}, - {Float(0), nil, false, false, nil}, + {Float(1234), nil, false, false, nil}, {nil, nil, false, false, nil}, {Float(0), Float(0), false, false, nil}, {Float(0), Float(9876.0), true, false, Float(9876.0)}, diff --git a/location_entity.go b/location_entity.go index e81c94c..5722b17 100644 --- a/location_entity.go +++ b/location_entity.go @@ -17,16 +17,15 @@ type LocationEntity struct { BaseEntity // Admin - FolderId *string `json:"folderId,omitempty"` - LabelIds *UnorderedStrings `json:"labelIds,omitempty"` - CategoryIds *[]string `json:"categoryIds,omitempty"` - Closed *LocationClosed `json:"closed,omitempty"` - Keywords *[]string `json:"keywords,omitempty"` - Language *string `json:"language,omitempty"` - CustomFields map[string]interface{} `json:"customFields,omitempty"` + FolderId *string `json:"folderId,omitempty"` + LabelIds *UnorderedStrings `json:"labelIds,omitempty"` + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed *LocationClosed `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + Language *string `json:"language,omitempty"` - hydrated bool - nilIsEmpty bool + // hydrated bool + // nilIsEmpty bool // Address Fields Name *string `json:"name,omitempty"` @@ -174,22 +173,32 @@ type Coordinate struct { } type Hours struct { - Monday []*Times `json:"monday,omitempty"` - Tuesday []*Times `json:"tuesday,omitempty"` - Wednesday []*Times `json:"wednesday,omitempty"` - Thursday []*Times `json:"thursday,omitempty"` - Friday []*Times `json:"friday,omitempty"` - Saturday []*Times `json:"saturday,omitempty"` - Sunday []*Times `json:"sunday,omitempty"` + Monday *DayHours `json:"monday,omitempty"` + Tuesday *DayHours `json:"tuesday,omitempty"` + Wednesday *DayHours `json:"wednesday,omitempty"` + Thursday *DayHours `json:"thursday,omitempty"` + Friday *DayHours `json:"friday,omitempty"` + Saturday *DayHours `json:"saturday,omitempty"` + Sunday *DayHours `json:"sunday,omitempty"` HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` } -// TODO: *Times will become *OpenIntervals after Techops change -type Times struct { +type DayHours struct { + OpenIntervals []*Interval `json:"openIntervals,omitempty"` + IsClosed *bool `json:"isClosed,omitempty"` +} + +type Interval struct { Start string `json:"start,omitempty"` End string `json:"end,omitempty"` } +type HolidayHours struct { + Date string `json:"date"` + IsClosed *bool `json:"isClosed,omitempty"` + OpenIntervals []*Interval `json:"openIntervals"` +} + func (y LocationEntity) GetId() string { if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { return *y.BaseEntity.Meta.Id @@ -658,10 +667,3 @@ func (y LocationEntity) IsClosed() bool { } return false } - -// HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager. -// For details see -type HolidayHours struct { - Date string `json:"date"` - Hours []*Times `json:"hours"` -} diff --git a/role_diff_test.go b/role_diff_test.go index 6dadf22..3540d8b 100644 --- a/role_diff_test.go +++ b/role_diff_test.go @@ -1,69 +1,61 @@ package yext_test -import ( - "fmt" - "reflect" - "testing" - - "github.com/yext/yext-go" -) - -func TestRole_Diff(t *testing.T) { - tests := []struct { - name string - roleA yext.Role - roleB yext.Role - wantDelta yext.Role - wantDiff bool - }{ - { - name: "Identical Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - roleB: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - wantDelta: yext.Role{}, - wantDiff: false, - }, - { - name: "Different 'Id' params in Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - roleB: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role"), - }, - wantDelta: yext.Role{ - Id: yext.String("4"), - }, - wantDiff: true, - }, - { - name: "Different 'Name' params in Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), - }, - roleB: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role Two"), - }, - wantDelta: yext.Role{ - Name: yext.String("Example Role Two"), - }, - wantDiff: true, - }, - } - - for _, test := range tests { - if gotDelta, gotDiff := test.roleA.Diff(test.roleB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { - t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) - } - } -} +// func TestRole_Diff(t *testing.T) { +// tests := []struct { +// name string +// roleA yext.Role +// roleB yext.Role +// wantDelta yext.Role +// wantDiff bool +// }{ +// { +// name: "Identical Roles", +// roleA: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// roleB: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// wantDelta: yext.Role{}, +// wantDiff: false, +// }, +// { +// name: "Different 'Id' params in Roles", +// roleA: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// roleB: yext.Role{ +// Id: yext.String("4"), +// Name: yext.String("Example Role"), +// }, +// wantDelta: yext.Role{ +// Id: yext.String("4"), +// }, +// wantDiff: true, +// }, +// { +// name: "Different 'Name' params in Roles", +// roleA: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role"), +// }, +// roleB: yext.Role{ +// Id: yext.String("3"), +// Name: yext.String("Example Role Two"), +// }, +// wantDelta: yext.Role{ +// Name: yext.String("Example Role Two"), +// }, +// wantDiff: true, +// }, +// } +// +// for _, test := range tests { +// if gotDelta, gotDiff := test.roleA.Diff(test.roleB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { +// t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) +// } +// } +// } From ecd438a31e8ba87bd4ac43ee16f1c74b7c51667a Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 17 Oct 2018 01:31:27 -0400 Subject: [PATCH 012/285] wip --- entity_diff.go | 92 ++-- entity_diff_test.go | 1188 +++++++++++++++++++++++------------------ location_diff.go | 2 +- location_diff_test.go | 135 +++++ 4 files changed, 846 insertions(+), 571 deletions(-) diff --git a/entity_diff.go b/entity_diff.go index e6dacec..54e5978 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -26,10 +26,13 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int ) if aV.Kind() == reflect.Ptr { - aV = aV.Elem() + aV = indirect(aV) } if bV.Kind() == reflect.Ptr { - bV = bV.Elem() + if bV.IsNil() { + return delta, isDiff + } + bV = indirect(bV) } var ( @@ -49,74 +52,40 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int continue } - log.Println("valA", valA) - log.Println("valB", valB) + if valB.Kind() == reflect.Ptr && valB.IsNil() { + continue + } + if !valB.CanSet() { + continue + } + + // First, use recursion to handle a field that is a struct or a pointer to a struct // If Kind() == struct, this is likely an embedded struct if valA.Kind() == reflect.Struct { - log.Println("is struct") d, diff := diff(valA.Addr().Interface(), valB.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d).Elem()) } continue - } else if valA.Kind() == reflect.Ptr { - // log.Println("is pointer") - // if valA.IsNil() { // implies valB is non-nil - // log.Println("I am nil") - // //valBIndirect := valB.Elem() - // log.Println("Nil is empty b", nilIsEmptyB) - // log.Println("Nil is empty a", nilIsEmptyA) - // log.Println("is zero a", isZeroValue(valA, nilIsEmptyA)) - // log.Println("is zero b", isZeroValue(valB.Elem(), nilIsEmptyB)) - // if isZeroValue(valA, nilIsEmptyA) && isZeroValue(valB, nilIsEmptyB) { - // continue - // } - // isDiff = true - // reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) - // continue - log.Println("isZeroA", isZeroValue(valA, nilIsEmptyA)) - log.Println("isZeroB", isZeroValue(valB, nilIsEmptyB)) - if !valB.IsNil() && valB.CanSet() { - log.Println("val B is not nil") - valAIndirect := valA.Elem() - valBIndirect := valB.Elem() - // log.Println("valAInd", valAIndirect) - // log.Println("valBInd", valBIndirect) - // log.Println("kind:", valAIndirect.Kind()) - if valAIndirect.Kind() == reflect.Struct { - // If base is &Address{Line1:"abc"} and new is &Address{}, we want &Address for the diff - // if isZeroValue(valBIndirect, getNilIsEmpty(valBIndirect)) { - // log.Println("val b is zero") - // if !(isZeroValue(valAIndirect, getNilIsEmpty(valAIndirect))) { - // log.Println("val a is not zero") - // isDiff = true - // reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) - // } - // continue - // } - reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) - d, diff := diff(valAIndirect.Addr().Interface(), valBIndirect.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) - if diff { - isDiff = true - reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d)) - } - continue + // If it's a pointer to a struct we need to handle it in a special way: + } else if valA.Kind() == reflect.Ptr && indirectKind(valA) == reflect.Struct { + // Handle case where new is &Address{} and base is &Address{"Line1"} + if isZeroValue(valB, nilIsEmptyB) && !isZeroValue(valA, nilIsEmptyA) { + isDiff = true + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + } else { + d, diff := diff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) + if diff { + isDiff = true + reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d)) } } - } - - // before we want to recur we want to make sure neither is zoer - - if valB.Kind() == reflect.Ptr && valB.IsNil() { - continue - } - if !valB.CanSet() { continue } - if isZeroValue(valA, getNilIsEmpty(a)) && isZeroValue(valB, getNilIsEmpty(b)) { + if isZeroValue(valA, nilIsEmptyA) && isZeroValue(valB, nilIsEmptyB) { continue } @@ -138,6 +107,17 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int return delta, isDiff } +func indirect(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + return v +} + +func indirectKind(v reflect.Value) reflect.Kind { + return indirect(v).Kind() +} + // Diff(a, b): a is base, b is new func Diff(a Entity, b Entity) (Entity, bool) { if a.GetEntityType() != b.GetEntityType() { diff --git a/entity_diff_test.go b/entity_diff_test.go index 9d57311..3d2a6d7 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -92,551 +92,613 @@ func TestEntityDiff(t *testing.T) { // }, // isDiff: false, // }, - // // String tests - // diffTest{ - // name: "*String: equal (A)", - // property: "Name", - // baseValue: String("Hupman's Hotdogs"), - // newValue: String("Hupman's Hotdogs"), - // isDiff: false, - // }, - // diffTest{ - // name: "*String: not equal (B)", - // property: "Name", - // baseValue: String("Hupman's Hotdogs"), - // newValue: String("Bryan's Bagels"), - // isDiff: true, - // deltaValue: String("Bryan's Bagels"), - // }, - // diffTest{ - // name: "*String: base is empty string, new is not (C)", - // property: "Name", - // baseValue: String(""), - // newValue: String("Bryan's Bagels"), - // isDiff: true, - // deltaValue: String("Bryan's Bagels"), - // }, - // diffTest{ - // name: "*String: new is empty string, base is not (D)", - // property: "Name", - // baseValue: String("Hupman's Hotdogs"), - // newValue: String(""), - // isDiff: true, - // deltaValue: String(""), - // }, - // diffTest{ - // name: "*String: both are empty (E)", - // property: "Name", - // baseValue: String(""), - // newValue: String(""), - // isDiff: false, - // }, - // diffTest{ - // name: "*String: both are nil (F)", - // property: "Name", - // baseValue: nil, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*String: base is not nil, new is nil (G)", - // property: "Name", - // baseValue: String("Bryan's Bagels"), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*String: base is nil, new is not nil (H)", - // property: "Name", - // baseValue: nil, - // newValue: String("Bryan's Bagels"), - // isDiff: true, - // deltaValue: String("Bryan's Bagels"), - // }, - // diffTest{ - // name: "*String: base is nil, new is empty string (I)", - // property: "Name", - // baseValue: nil, - // newValue: String(""), - // isDiff: true, - // deltaValue: String(""), - // }, - // diffTest{ - // name: "*String: base is empty string, new is nil (J)", - // property: "Name", - // baseValue: String(""), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*String: base is nil (with nil is empty), new is empty string (K)", - // property: "Name", - // baseValue: nil, - // newValue: String(""), - // baseNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*String: base is nil (with nil is empty), new is empty string (with nil is empty) (L)", - // property: "Name", - // baseValue: nil, - // newValue: String(""), - // baseNilIsEmpty: true, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*String: base is empty string, new is nil (with nil is empty) (M)", - // property: "Name", - // baseValue: String(""), - // newValue: nil, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*String: base is empty string (with nil is empty), new is nil (with nil is empty) (N)", - // property: "Name", - // baseValue: String(""), - // baseNilIsEmpty: true, - // newValue: nil, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // - // //Float tests - // diffTest{ - // name: "*Float: equal (A)", - // property: "YearEstablished", - // baseValue: Float(2018), - // newValue: Float(2018), - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: not equal (B)", - // property: "YearEstablished", - // baseValue: Float(2018), - // newValue: Float(2006), - // isDiff: true, - // deltaValue: Float(2006), - // }, - // diffTest{ - // name: "*Float: base is 0, new is not 0 (C)", - // property: "YearEstablished", - // baseValue: Float(0), - // newValue: Float(2006), - // isDiff: true, - // deltaValue: Float(2006), - // }, - // diffTest{ - // name: "*Float: base is not 0, new is 0 (D)", - // property: "YearEstablished", - // baseValue: Float(2006), - // newValue: Float(0), - // isDiff: true, - // deltaValue: Float(0), - // }, - // diffTest{ - // name: "*Float: both are 0 (E)", - // property: "YearEstablished", - // baseValue: Float(2018), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: both are nil (F)", - // property: "YearEstablished", - // baseValue: nil, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: base is not 0, new is nil (G)", - // property: "YearEstablished", - // baseValue: Float(1993), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: base is nil, new is not 0 (H)", - // property: "YearEstablished", - // baseValue: nil, - // newValue: Float(1993), - // isDiff: true, - // deltaValue: Float(1993), - // }, - // diffTest{ - // name: "*Float: base is nil, new is 0 (I)", - // property: "YearEstablished", - // baseValue: nil, - // newValue: Float(0), - // isDiff: true, - // deltaValue: Float(0), - // }, - // diffTest{ - // name: "*Float: base is 0, new is nil (J)", - // property: "YearEstablished", - // baseValue: Float(0), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: base is nil (nil is empty), new is 0 (K)", - // property: "YearEstablished", - // baseValue: nil, - // newValue: Float(0), - // baseNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: base is nil (nil is empty), new is 0 (nil is empty) (L)", - // property: "YearEstablished", - // baseValue: nil, - // newValue: Float(0), - // baseNilIsEmpty: true, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: base is 0, new is nil (nil is empty) (M)", - // property: "YearEstablished", - // baseValue: Float(0), - // newValue: nil, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*Float: base is 0 (nil is empty), new is nil (nil is empty) (N)", - // property: "YearEstablished", - // baseValue: Float(0), - // baseNilIsEmpty: true, - // newValue: nil, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // - // // Bool tests - // diffTest{ - // name: "*Bool: both true (A)", - // property: "SuppressAddress", - // baseValue: Bool(true), - // newValue: Bool(true), - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: both false (A/E)", - // property: "SuppressAddress", - // baseValue: Bool(false), - // newValue: Bool(false), - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: not equal, base true, new false (B/D)", - // property: "SuppressAddress", - // baseValue: Bool(true), - // newValue: Bool(false), - // isDiff: true, - // deltaValue: Bool(false), - // }, - // diffTest{ - // name: "*Bool: not equal, base is false, new is true (B/C)", - // property: "SuppressAddress", - // baseValue: Bool(false), - // newValue: Bool(true), - // isDiff: true, - // deltaValue: Bool(true), - // }, - // diffTest{ - // name: "*Bool: both are nil (F)", - // property: "SuppressAddress", - // baseValue: nil, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: base is non-zero, new is nil (G)", - // property: "SuppressAddress", - // baseValue: Bool(true), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: base is nil, new is non-zero value (H)", - // property: "SuppressAddress", - // baseValue: nil, - // newValue: Bool(true), - // isDiff: true, - // deltaValue: Bool(true), - // }, - // diffTest{ - // name: "*Bool: base is nil, new is zero value (I)", - // property: "SuppressAddress", - // baseValue: nil, - // newValue: Bool(false), - // isDiff: true, - // deltaValue: Bool(false), - // }, - // diffTest{ - // name: "*Bool: base is zero value, new is nil (J)", - // property: "SuppressAddress", - // baseValue: Bool(false), - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: base is nil (nil is empty), new is zero value (K)", - // property: "SuppressAddress", - // baseValue: nil, - // newValue: Bool(false), - // baseNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", - // property: "SuppressAddress", - // baseValue: nil, - // newValue: Bool(false), - // baseNilIsEmpty: true, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: base is zero value, new is nil (nil is empty) (L)", - // property: "SuppressAddress", - // baseValue: Bool(false), - // newValue: nil, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // diffTest{ - // name: "*Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", - // property: "SuppressAddress", - // baseValue: Bool(false), - // newValue: nil, - // baseNilIsEmpty: true, - // newNilIsEmpty: true, - // isDiff: false, - // }, - // - // Struct Diffs - // diffTest{ - // name: "Address: equal (A)", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark"), - // }, - // newValue: &Address{ - // Line1: String("7900 Westpark"), - // }, - // isDiff: false, - // }, - // diffTest{ - // name: "Address: line 1 and line 2 equal (A)", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // Line2: String("Suite 500"), - // }, - // newValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // Line2: String("Suite 500"), - // }, - // isDiff: false, - // }, - // diffTest{ - // name: "Address: not equal (B)", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark"), - // }, - // newValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // isDiff: true, - // deltaValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // diffTest{ - // name: "Address: line 1 equal, line 2 not equal (B)", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // Line2: String("Suite 500"), - // }, - // newValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // Line2: String("Suite 700"), - // }, - // isDiff: true, - // deltaValue: &Address{ - // Line2: String("Suite 700"), - // }, - // }, - // diffTest{ - // name: "Address: base is empty struct, new is not empty (C)", - // property: "Address", - // baseValue: &Address{}, - // newValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // isDiff: true, - // deltaValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // diffTest{ - // name: "Address: base is not empty struct, new is empty struct (D)", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // newValue: &Address{}, - // isDiff: true, - // deltaValue: &Address{}, - // }, - // diffTest{ - // name: "List: base is not empty struct, new is empty struct (D)", - // property: "CFTextList", - // baseValue: &[]string{"a", "b"}, - // newValue: &[]string{}, - // isDiff: true, - // deltaValue: &[]string{}, - // }, - // diffTest{ - // name: "Address: both are empty struct (E)", - // property: "Address", - // baseValue: &Address{}, - // newValue: &Address{}, - // isDiff: false, - // }, - // diffTest{ - // name: "Address: both are nil (F)", - // property: "Address", - // baseValue: nil, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "Address: base is non-empty struct, new is nil (G)", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "Address: base is nil, new is non-empty struct (H)", - // property: "Address", - // baseValue: nil, - // newValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // isDiff: true, - // deltaValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // }, - // diffTest{ - // name: "Address: base is nil, new is empty struct (I)", - // property: "Address", - // baseValue: nil, - // newValue: &Address{}, - // isDiff: true, - // deltaValue: &Address{}, - // }, - // diffTest{ - // name: "Address: base is empty struct, new is nil (J)", - // property: "Address", - // baseValue: &Address{}, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "Address: base is nil (nil is empty), new is empty struct (K)", - // property: "Address", - // baseValue: nil, - // newValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, // NOT &Address{} - // baseNilIsEmpty: true, - // isDiff: false, - // }, + // String tests diffTest{ - name: "Address: base is empty struct, new is nil (nis is empty) (?)", - property: "Address", + name: "*String: equal (A)", + property: "Name", + baseValue: String("Hupman's Hotdogs"), + newValue: String("Hupman's Hotdogs"), + isDiff: false, + }, + diffTest{ + name: "*String: not equal (B)", + property: "Name", + baseValue: String("Hupman's Hotdogs"), + newValue: String("Bryan's Bagels"), + isDiff: true, + deltaValue: String("Bryan's Bagels"), + }, + diffTest{ + name: "*String: base is empty string, new is not (C)", + property: "Name", + baseValue: String(""), + newValue: String("Bryan's Bagels"), + isDiff: true, + deltaValue: String("Bryan's Bagels"), + }, + diffTest{ + name: "*String: new is empty string, base is not (D)", + property: "Name", + baseValue: String("Hupman's Hotdogs"), + newValue: String(""), + isDiff: true, + deltaValue: String(""), + }, + diffTest{ + name: "*String: both are empty (E)", + property: "Name", + baseValue: String(""), + newValue: String(""), + isDiff: false, + }, + diffTest{ + name: "*String: both are nil (F)", + property: "Name", + baseValue: nil, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*String: base is not nil, new is nil (G)", + property: "Name", + baseValue: String("Bryan's Bagels"), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*String: base is nil, new is not nil (H)", + property: "Name", + baseValue: nil, + newValue: String("Bryan's Bagels"), + isDiff: true, + deltaValue: String("Bryan's Bagels"), + }, + diffTest{ + name: "*String: base is nil, new is empty string (I)", + property: "Name", + baseValue: nil, + newValue: String(""), + isDiff: true, + deltaValue: String(""), + }, + diffTest{ + name: "*String: base is empty string, new is nil (J)", + property: "Name", + baseValue: String(""), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*String: base is nil (with nil is empty), new is empty string (K)", + property: "Name", + baseValue: nil, + newValue: String(""), + baseNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*String: base is nil (with nil is empty), new is empty string (with nil is empty) (L)", + property: "Name", + baseValue: nil, + newValue: String(""), + baseNilIsEmpty: true, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*String: base is empty string, new is nil (with nil is empty) (M)", + property: "Name", + baseValue: String(""), + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*String: base is empty string (with nil is empty), new is nil (with nil is empty) (N)", + property: "Name", + baseValue: String(""), + baseNilIsEmpty: true, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, - baseValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + //Float tests + diffTest{ + name: "*Float: equal (A)", + property: "YearEstablished", + baseValue: Float(2018), + newValue: Float(2018), + isDiff: false, + }, + diffTest{ + name: "*Float: not equal (B)", + property: "YearEstablished", + baseValue: Float(2018), + newValue: Float(2006), + isDiff: true, + deltaValue: Float(2006), + }, + diffTest{ + name: "*Float: base is 0, new is not 0 (C)", + property: "YearEstablished", + baseValue: Float(0), + newValue: Float(2006), + isDiff: true, + deltaValue: Float(2006), + }, + diffTest{ + name: "*Float: base is not 0, new is 0 (D)", + property: "YearEstablished", + baseValue: Float(2006), + newValue: Float(0), + isDiff: true, + deltaValue: Float(0), + }, + diffTest{ + name: "*Float: both are 0 (E)", + property: "YearEstablished", + baseValue: Float(2018), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Float: both are nil (F)", + property: "YearEstablished", + baseValue: nil, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Float: base is not 0, new is nil (G)", + property: "YearEstablished", + baseValue: Float(1993), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Float: base is nil, new is not 0 (H)", + property: "YearEstablished", + baseValue: nil, + newValue: Float(1993), + isDiff: true, + deltaValue: Float(1993), + }, + diffTest{ + name: "*Float: base is nil, new is 0 (I)", + property: "YearEstablished", + baseValue: nil, + newValue: Float(0), + isDiff: true, + deltaValue: Float(0), + }, + diffTest{ + name: "*Float: base is 0, new is nil (J)", + property: "YearEstablished", + baseValue: Float(0), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Float: base is nil (nil is empty), new is 0 (K)", + property: "YearEstablished", + baseValue: nil, + newValue: Float(0), + baseNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*Float: base is nil (nil is empty), new is 0 (nil is empty) (L)", + property: "YearEstablished", + baseValue: nil, + newValue: Float(0), + baseNilIsEmpty: true, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*Float: base is 0, new is nil (nil is empty) (M)", + property: "YearEstablished", + baseValue: Float(0), newValue: nil, newNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "Address: base is empty struct, new is nil (nil is empty) (?)", - property: "Address", - baseValue: &Address{}, + name: "*Float: base is 0 (nil is empty), new is nil (nil is empty) (N)", + property: "YearEstablished", + baseValue: Float(0), + baseNilIsEmpty: true, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + + // Bool tests + diffTest{ + name: "*Bool: both true (A)", + property: "SuppressAddress", + baseValue: Bool(true), + newValue: Bool(true), + isDiff: false, + }, + diffTest{ + name: "*Bool: both false (A/E)", + property: "SuppressAddress", + baseValue: Bool(false), + newValue: Bool(false), + isDiff: false, + }, + diffTest{ + name: "*Bool: not equal, base true, new false (B/D)", + property: "SuppressAddress", + baseValue: Bool(true), + newValue: Bool(false), + isDiff: true, + deltaValue: Bool(false), + }, + diffTest{ + name: "*Bool: not equal, base is false, new is true (B/C)", + property: "SuppressAddress", + baseValue: Bool(false), + newValue: Bool(true), + isDiff: true, + deltaValue: Bool(true), + }, + diffTest{ + name: "*Bool: both are nil (F)", + property: "SuppressAddress", + baseValue: nil, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Bool: base is non-zero, new is nil (G)", + property: "SuppressAddress", + baseValue: Bool(true), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Bool: base is nil, new is non-zero value (H)", + property: "SuppressAddress", + baseValue: nil, + newValue: Bool(true), + isDiff: true, + deltaValue: Bool(true), + }, + diffTest{ + name: "*Bool: base is nil, new is zero value (I)", + property: "SuppressAddress", + baseValue: nil, + newValue: Bool(false), + isDiff: true, + deltaValue: Bool(false), + }, + diffTest{ + name: "*Bool: base is zero value, new is nil (J)", + property: "SuppressAddress", + baseValue: Bool(false), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "*Bool: base is nil (nil is empty), new is zero value (K)", + property: "SuppressAddress", + baseValue: nil, + newValue: Bool(false), + baseNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", + property: "SuppressAddress", + baseValue: nil, + newValue: Bool(false), + baseNilIsEmpty: true, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "*Bool: base is zero value, new is nil (nil is empty) (L)", + property: "SuppressAddress", + baseValue: Bool(false), newValue: nil, newNilIsEmpty: true, isDiff: false, }, - // diffTest{ - // name: "Address: base is nil (nil is empty), new is empty struct (?)", - // property: "Address", - // baseValue: nil, - // baseNilIsEmpty: true, - // newValue: &Address{}, - // isDiff: false, - // }, diffTest{ - name: "Address: base is nil (nil is empty), new is empty struct (?)", + name: "*Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", + property: "SuppressAddress", + baseValue: Bool(false), + newValue: nil, + baseNilIsEmpty: true, + newNilIsEmpty: true, + isDiff: false, + }, + + // Struct Diffs + diffTest{ + name: "Address: equal (A)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark"), + }, + newValue: &Address{ + Line1: String("7900 Westpark"), + }, + isDiff: false, + }, + diffTest{ + name: "Address: line 1 and line 2 equal (A)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + Line2: String("Suite 500"), + }, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + Line2: String("Suite 500"), + }, + isDiff: false, + }, + diffTest{ + name: "Address: not equal (B)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark"), + }, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + isDiff: true, + deltaValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + }, + diffTest{ + name: "Address: line 1 equal, line 2 not equal (B)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + Line2: String("Suite 500"), + }, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + Line2: String("Suite 700"), + }, + isDiff: true, + deltaValue: &Address{ + Line2: String("Suite 700"), + }, + }, + diffTest{ + name: "Address: base has line 1, new has line 2 (B)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + newValue: &Address{ + Line2: String("Suite 700"), + }, + isDiff: true, + deltaValue: &Address{ + Line2: String("Suite 700"), + }, + }, + diffTest{ + name: "Address: base is empty struct, new is not empty (C)", + property: "Address", + baseValue: &Address{}, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + isDiff: true, + deltaValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + }, + // THIS IS THE DIFFICULT TEST + diffTest{ + name: "Address: base is not empty struct, new is empty struct (D)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + newValue: &Address{}, + isDiff: true, + deltaValue: &Address{}, + }, + diffTest{ + name: "List: base is not empty struct, new is empty struct (D)", + property: "CFTextList", + baseValue: &[]string{"a", "b"}, + newValue: &[]string{}, + isDiff: true, + deltaValue: &[]string{}, + }, + diffTest{ + name: "Address: both are empty struct (E)", + property: "Address", + baseValue: &Address{}, + newValue: &Address{}, + isDiff: false, + }, + diffTest{ + name: "Address: both are nil (F)", + property: "Address", + baseValue: nil, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "Address: base is non-empty struct, new is nil (G)", + property: "Address", + baseValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "Address: base is nil, new is non-empty struct (H)", + property: "Address", + baseValue: nil, + newValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + isDiff: true, + deltaValue: &Address{ + Line1: String("7900 Westpark Dr"), + }, + }, + diffTest{ + name: "Address: base is nil, new is empty struct (I)", + property: "Address", + baseValue: nil, + newValue: &Address{}, + isDiff: true, + deltaValue: &Address{}, + }, + diffTest{ + name: "Address: base is empty struct, new is nil (J)", + property: "Address", + baseValue: &Address{}, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "Address: base is nil (nil is empty), new is struct with zero values (K)", + property: "Address", + baseValue: nil, + newValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + baseNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "Address: base is nil (nil is empty), new is struct with zero value (K-2)", + property: "Address", + baseValue: nil, + newValue: &Address{Line1: String("")}, + baseNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "Address: base is nil (nil is empty), new is empty struct (K-3)", + property: "Address", + baseValue: nil, + newValue: &Address{}, + baseNilIsEmpty: true, + isDiff: false, + }, + + diffTest{ + name: "Address: base is nil (nil is empty), new is struct with zero values (nil is empty) (L)", property: "Address", baseValue: nil, baseNilIsEmpty: true, newValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + newNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "Address: base is nil, new is nil (nil is empty) (?)", + name: "Address: base is nil (nil is empty), new is struct with zero value (nil is empty) (L-2)", + property: "Address", + baseValue: nil, + baseNilIsEmpty: true, + newValue: &Address{Line1: String("")}, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "Address: base is nil (nil is empty), new is empty struct (nil is empty) (L-3)", + property: "Address", + baseValue: nil, + baseNilIsEmpty: true, + newValue: &Address{}, + newNilIsEmpty: true, + isDiff: false, + }, + + diffTest{ + name: "Address: base is struct with zero values, new is nil (nil is empty) (M)", + property: "Address", + baseValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "Address: base is struct with zero value, new is nil (nil is empty) (M-2)", property: "Address", - baseValue: nil, + baseValue: &Address{Line1: String("")}, newValue: nil, newNilIsEmpty: true, isDiff: false, }, - // // TODO: add L, M, N + diffTest{ + name: "Address: base empty struct, new nis nil (nil is empty) (M-3)", + property: "Address", + baseValue: &Address{}, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + + diffTest{ + name: "Address: base is struct with zero values (nil is empty), new is nil (nil is empty) (N)", + property: "Address", + baseValue: &Address{Line1: String(""), Line2: String(""), City: String(""), Sublocality: String(""), Region: String(""), PostalCode: String("")}, + baseNilIsEmpty: true, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "Address: base is struct with zero value (nil is empty), new is nil (nil is empty) (N-2)", + property: "Address", + baseValue: &Address{Line1: String("")}, + baseNilIsEmpty: true, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "Address: base empty struct (nil is empty), new nis nil (nil is empty) (N-3)", + property: "Address", + baseValue: &Address{}, + baseNilIsEmpty: true, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, // // diffTest{ - // name: "Address: base line1 is empty", - // property: "Address", - // baseValue: &Address{ - // Line1: String(""), - // }, - // newValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // isDiff: true, - // deltaValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, + // name: "LocationEntity", + // property: "LocationEntity", + // baseValue: LocationEntity{}, + // newValue: LocationEntity{}, + // isDiff: false, // }, // diffTest{ - // name: "Address: new line1 is empty", - // property: "Address", - // baseValue: &Address{ - // Line1: String("7900 Westpark Dr"), - // }, - // newValue: &Address{ - // Line1: String(""), - // }, - // isDiff: true, - // deltaValue: &Address{ - // Line1: String(""), - // }, + // name: "LocationEntity", + // property: "LocationEntity", + // baseValue: LocationEntity{}, + // newValue: nil, + // isDiff: false, + // }, + // diffTest{ + // name: "LocationEntity", + // property: "LocationEntity", + // baseValue: nil, + // newValue: LocationEntity{}, + // isDiff: true, // }, } - log.Println("&Address{}, true", isZeroValue(reflect.ValueOf(&Address{}), true)) - log.Println("&Address{}, false", isZeroValue(reflect.ValueOf(&Address{}), false)) - log.Println("Address{}, true", isZeroValue(reflect.ValueOf(Address{}), true)) - log.Println("Address{}, false", isZeroValue(reflect.ValueOf(Address{}), false)) + // l := CustomLocationEntity{ + // LocationEntity: nil, + // } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -672,6 +734,104 @@ func TestEntityDiff(t *testing.T) { } } +func TestEntityDiffComplex(t *testing.T) { + custom1 := &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line1: String("7900 Westpark"), + }, + }, + } + custom2 := &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line1: String("7900 Westpark"), + Line2: String("Suite T200"), + }, + }, + } + custom3 := &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line2: String("Suite T200"), + }, + }, + } + custom4 := &CustomLocationEntity{} + + tests := []struct { + name string + base *CustomLocationEntity + new *CustomLocationEntity + isDiff bool + delta *CustomLocationEntity + }{ + { + name: "equal", + base: custom1, + new: custom1, + isDiff: false, + }, + { + name: "not equal", + base: custom1, + new: custom2, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line2: String("Suite T200"), + }, + }, + }, + }, + { + name: "not equal (2)", + base: custom1, + new: custom3, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line2: String("Suite T200"), + }, + }, + }, + }, + { + name: "empty struct", + base: custom4, + new: custom3, + isDiff: true, + delta: custom3, + }, + { + name: "empty struct (new is empty struct)", + base: custom3, + new: custom4, + isDiff: true, + delta: custom4, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + log.Println() + delta, isDiff := Diff(test.base, test.new) + if isDiff != test.isDiff { + t.Log(delta) + t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) + } else if test.isDiff == false && delta != nil { + t.Errorf("Expected isDiff: %t. Got delta: %v", test.isDiff, delta) + } else if isDiff { + if !reflect.DeepEqual(delta, test.delta) { + t.Errorf("Expected delta: %v. Got: %v", test.delta, delta) + } + } + }) + } +} + // test comparable to make sure it gets used // UnorderedStrings // text list diff --git a/location_diff.go b/location_diff.go index 78543d0..a370372 100644 --- a/location_diff.go +++ b/location_diff.go @@ -152,7 +152,7 @@ func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { return isZeroValue(v.Elem(), interpretNilAsZeroValue) case reflect.Struct: for i, n := 0, v.NumField(); i < n; i++ { - if !isZeroValue(v.Field(i), interpretNilAsZeroValue) { + if !isZeroValue(v.Field(i), true) { return false } } diff --git a/location_diff_test.go b/location_diff_test.go index 220e8ca..052f71e 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -1500,3 +1500,138 @@ func TestHoursAreEquivalentDiff(t *testing.T) { } } } + +func TestIsZeroValue(t *testing.T) { + tests := []struct { + name string + i interface{} + nilIsEmpty bool + want bool + }{ + { + name: "Non-Empty String", + i: "CTG", + want: false, + }, + { + name: "Non-Empty String (nil is empty)", + i: "CTG", + nilIsEmpty: true, + want: false, + }, + { + name: "Empty String", + i: "", + want: true, + }, + { + name: "Empty String (nil is empty)", + i: "", + nilIsEmpty: true, + want: true, + }, + { + name: "Empty String", + i: "", + want: true, + }, + { + name: "Nil *string", + i: (*string)(nil), + want: false, + }, + { + name: "Nil *string (nil is empty)", + i: (*string)(nil), + nilIsEmpty: true, + want: true, + }, + { + name: "Empty *string", + i: String(""), + want: true, + }, + { + name: "Empty *string (nil is empty)", + i: String(""), + nilIsEmpty: true, + want: true, + }, + { + name: "nil ptr to *Address struct ", + i: (*Address)(nil), + want: false, + }, + { + name: "nil ptr to *Address struct (nil is empty)", + i: (*Address)(nil), + nilIsEmpty: true, + want: true, + }, + { + name: "empty *Address struct", + i: &Address{}, + want: true, + }, + { + name: "empty *Address struct (nil is empty)", + i: &Address{}, + nilIsEmpty: true, + want: true, + }, + { + name: "non-empty *Address struct", + i: &Address{ + Line1: String("7900 Westpark"), + }, + want: false, + }, + { + name: "non-empty *Address struct with empty values", + i: &Address{ + Line1: String(""), + }, + want: true, + }, + { + name: "*Address struct with empty values (nil is empty)", + i: &Address{ + Line1: String(""), + }, + nilIsEmpty: true, + want: true, + }, + { + name: "*Address struct with zero values", + i: &Address{ + Line1: String(""), + Line2: String(""), + City: String(""), + Region: String(""), + Sublocality: String(""), + PostalCode: String(""), + }, + want: true, + }, + { + name: "struct with zero values (*Address)", + i: &Address{ + Line1: String(""), + Line2: String(""), + City: String(""), + Region: String(""), + Sublocality: String(""), + PostalCode: String(""), + }, + nilIsEmpty: true, + want: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if isZeroValue := isZeroValue(reflect.ValueOf(test.i), test.nilIsEmpty); test.want != isZeroValue { + t.Errorf(`Expected IsZeroValue: %t\nGot:%t`, test.want, isZeroValue) + } + }) + } +} From 2d1aab8e6bc7ad070ba1586aa0a710808b807363 Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 17 Oct 2018 15:58:35 -0400 Subject: [PATCH 013/285] diff is ready for review --- entity_diff.go | 39 +++--- entity_diff_test.go | 284 ++++++++++++++++++++++++++++++-------------- entity_test.go | 29 ++--- 3 files changed, 225 insertions(+), 127 deletions(-) diff --git a/entity_diff.go b/entity_diff.go index 54e5978..74e8b35 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -1,7 +1,6 @@ package yext import ( - "log" "reflect" ) @@ -46,13 +45,12 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int valA = aV.Field(i) valB = bV.Field(i) ) - log.Println(nameA) if nameA == "nilIsEmpty" { continue } - if valB.Kind() == reflect.Ptr && valB.IsNil() { + if isNil(valB) { continue } @@ -60,6 +58,20 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int continue } + aI, bI := valA.Interface(), valB.Interface() + // Comparable does not handle the nil is empty case: + // So if valA is nil, don't call comparable (valB checked for nil above) + if !isNil(valA) { + comparableA, aOk := aI.(Comparable) + comparableB, bOk := bI.(Comparable) + if aOk && bOk { + if !comparableA.Equal(comparableB) { + return b, true + } + return nil, false + } + } + // First, use recursion to handle a field that is a struct or a pointer to a struct // If Kind() == struct, this is likely an embedded struct if valA.Kind() == reflect.Struct { @@ -70,7 +82,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int } continue // If it's a pointer to a struct we need to handle it in a special way: - } else if valA.Kind() == reflect.Ptr && indirectKind(valA) == reflect.Struct { + } else if valA.Kind() == reflect.Ptr && indirect(valA).Kind() == reflect.Struct { // Handle case where new is &Address{} and base is &Address{"Line1"} if isZeroValue(valB, nilIsEmptyB) && !isZeroValue(valA, nilIsEmptyA) { isDiff = true @@ -89,17 +101,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int continue } - aI, bI := valA.Interface(), valB.Interface() - - comparableA, aOk := aI.(Comparable) - comparableB, bOk := bI.(Comparable) - - if aOk && bOk { - if !comparableA.Equal(comparableB) { - reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) - isDiff = true - } - } else if !reflect.DeepEqual(aI, bI) { + if !reflect.DeepEqual(aI, bI) { reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) isDiff = true } @@ -114,8 +116,11 @@ func indirect(v reflect.Value) reflect.Value { return v } -func indirectKind(v reflect.Value) reflect.Kind { - return indirect(v).Kind() +func isNil(v reflect.Value) bool { + if v.Kind() == reflect.Ptr { + return v.IsNil() + } + return false } // Diff(a, b): a is base, b is new diff --git a/entity_diff_test.go b/entity_diff_test.go index 3d2a6d7..0dba826 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1,16 +1,14 @@ package yext import ( - "log" "reflect" "testing" ) type diffTest struct { - name string - property string - isDiff bool - + name string + property string + isDiff bool baseValue interface{} newValue interface{} baseNilIsEmpty bool @@ -30,6 +28,7 @@ func (c *CustomLocationEntity) SetValOnProperty(val interface{}, property string } func TestEntityDiff(t *testing.T) { + // To ensure that we test all combinations, tests follow this pattern: (more or less): // BASIC EQUALITY: // A) equal // B) not equal @@ -53,46 +52,7 @@ func TestEntityDiff(t *testing.T) { // N) base is zero value (nil is empty), new is nil (nil is empty) tests := []diffTest{ - // Meta - // diffTest{ - // name: "Base Entity: not equal", - // property: "BaseEntity", - // baseValue: BaseEntity{ - // Meta: &EntityMeta{ - // Id: String("CTG"), - // CategoryIds: Strings([]string{"123"}), - // }, - // }, - // newValue: BaseEntity{ - // Meta: &EntityMeta{ - // Id: String("CTG"), - // CategoryIds: Strings([]string{"123", "456"}), - // }, - // }, - // isDiff: true, - // deltaValue: BaseEntity{ - // Meta: &EntityMeta{ - // Id: String("CTG"), - // CategoryIds: Strings([]string{"123", "456"}), - // }, - // }, - // }, - // diffTest{ - // name: "Base Entity: equal", - // property: "BaseEntity", - // baseValue: BaseEntity{ - // Meta: &EntityMeta{ - // CategoryIds: Strings([]string{"123", "456"}), - // }, - // }, - // newValue: BaseEntity{ - // Meta: &EntityMeta{ - // CategoryIds: Strings([]string{"123", "456"}), - // }, - // }, - // isDiff: false, - // }, - // String tests + // *String tests diffTest{ name: "*String: equal (A)", property: "Name", @@ -202,8 +162,7 @@ func TestEntityDiff(t *testing.T) { newNilIsEmpty: true, isDiff: false, }, - - //Float tests + // *Float tests diffTest{ name: "*Float: equal (A)", property: "YearEstablished", @@ -416,8 +375,7 @@ func TestEntityDiff(t *testing.T) { newNilIsEmpty: true, isDiff: false, }, - - // Struct Diffs + // Struct tests (Address) diffTest{ name: "Address: equal (A)", property: "Address", @@ -498,7 +456,6 @@ func TestEntityDiff(t *testing.T) { Line1: String("7900 Westpark Dr"), }, }, - // THIS IS THE DIFFICULT TEST diffTest{ name: "Address: base is not empty struct, new is empty struct (D)", property: "Address", @@ -509,14 +466,6 @@ func TestEntityDiff(t *testing.T) { isDiff: true, deltaValue: &Address{}, }, - diffTest{ - name: "List: base is not empty struct, new is empty struct (D)", - property: "CFTextList", - baseValue: &[]string{"a", "b"}, - newValue: &[]string{}, - isDiff: true, - deltaValue: &[]string{}, - }, diffTest{ name: "Address: both are empty struct (E)", property: "Address", @@ -672,34 +621,191 @@ func TestEntityDiff(t *testing.T) { newNilIsEmpty: true, isDiff: false, }, - // - // diffTest{ - // name: "LocationEntity", - // property: "LocationEntity", - // baseValue: LocationEntity{}, - // newValue: LocationEntity{}, - // isDiff: false, - // }, - // diffTest{ - // name: "LocationEntity", - // property: "LocationEntity", - // baseValue: LocationEntity{}, - // newValue: nil, - // isDiff: false, - // }, - // diffTest{ - // name: "LocationEntity", - // property: "LocationEntity", - // baseValue: nil, - // newValue: LocationEntity{}, - // isDiff: true, - // }, + diffTest{ + name: "List: base is not empty struct, new is empty struct (D)", + property: "CFTextList", + baseValue: &[]string{"a", "b"}, + newValue: &[]string{}, + isDiff: true, + deltaValue: &[]string{}, + }, + // Comparable tests (Unordered Strings) + diffTest{ + name: "UnorderedStrings: equal (ordered) (A)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{"a", "b"}), + newValue: ToUnorderedStrings([]string{"a", "b"}), + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: equal (unordered) (A)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{"a", "b"}), + newValue: ToUnorderedStrings([]string{"b", "a"}), + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: not equal (B)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{"a", "b"}), + newValue: ToUnorderedStrings([]string{"c", "b"}), + isDiff: true, + deltaValue: ToUnorderedStrings([]string{"c", "b"}), + }, + diffTest{ + name: "UnorderedStrings: base is empty, new is non-empty (C)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{}), + newValue: ToUnorderedStrings([]string{"a", "b"}), + isDiff: true, + deltaValue: ToUnorderedStrings([]string{"a", "b"}), + }, + diffTest{ + name: "UnorderedStrings: base is non-empty, new is empty (D)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{"a", "b"}), + newValue: ToUnorderedStrings([]string{}), + isDiff: true, + deltaValue: ToUnorderedStrings([]string{}), + }, + diffTest{ + name: "UnorderedStrings: both are empty (E)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{}), + newValue: ToUnorderedStrings([]string{}), + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: both are nil (F)", + property: "CFMultiOption", + baseValue: nil, + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: base is non-zero, new is nil (G)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{"a", "b"}), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: base is nil, new is non-zero (H)", + property: "CFMultiOption", + baseValue: nil, + newValue: ToUnorderedStrings([]string{"a", "b"}), + isDiff: true, + deltaValue: ToUnorderedStrings([]string{"a", "b"}), + }, + diffTest{ + name: "UnorderedStrings: base is nil, new is zero (I)", + property: "CFMultiOption", + baseValue: nil, + newValue: ToUnorderedStrings([]string{}), + isDiff: true, + deltaValue: ToUnorderedStrings([]string{}), + }, + diffTest{ + name: "UnorderedStrings: base is zero, new is nil (J)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{}), + newValue: nil, + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: base is nil (nil is empty), new is zero (K)", + property: "CFMultiOption", + baseValue: nil, + baseNilIsEmpty: true, + newValue: ToUnorderedStrings([]string{}), + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: base is nil (nil is empty), new is zero (nil is empty) (L)", + property: "CFMultiOption", + baseValue: nil, + baseNilIsEmpty: true, + newValue: ToUnorderedStrings([]string{}), + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: base is zero value, new is nil (nil is empty) (L)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{}), + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "UnorderedStrings: base is zero value (nil is empty), new is nil (nil is empty) (L)", + property: "CFMultiOption", + baseValue: ToUnorderedStrings([]string{}), + baseNilIsEmpty: true, + newValue: nil, + newNilIsEmpty: true, + isDiff: false, + }, + // EmbeddedStruct tests + diffTest{ + name: "Base Entity: different Ids", + property: "BaseEntity", + baseValue: BaseEntity{ + Meta: &EntityMeta{ + Id: String("CTG"), + }, + }, + newValue: BaseEntity{ + Meta: &EntityMeta{ + Id: String("CTG2"), + }, + }, + isDiff: true, + deltaValue: BaseEntity{ + Meta: &EntityMeta{ + Id: String("CTG2"), + }, + }, + }, + diffTest{ + name: "Base Entity: not equal", + property: "BaseEntity", + baseValue: BaseEntity{ + Meta: &EntityMeta{ + Id: String("CTG"), + CategoryIds: Strings([]string{"123"}), + }, + }, + newValue: BaseEntity{ + Meta: &EntityMeta{ + Id: String("CTG"), + CategoryIds: Strings([]string{"123", "456"}), + }, + }, + isDiff: true, + deltaValue: BaseEntity{ + Meta: &EntityMeta{ + CategoryIds: Strings([]string{"123", "456"}), + }, + }, + }, + diffTest{ + name: "Base Entity: equal", + property: "BaseEntity", + baseValue: BaseEntity{ + Meta: &EntityMeta{ + CategoryIds: Strings([]string{"123", "456"}), + }, + }, + newValue: BaseEntity{ + Meta: &EntityMeta{ + CategoryIds: Strings([]string{"123", "456"}), + }, + }, + isDiff: false, + }, } - // l := CustomLocationEntity{ - // LocationEntity: nil, - // } - for _, test := range tests { t.Run(test.name, func(t *testing.T) { var ( @@ -805,18 +911,17 @@ func TestEntityDiffComplex(t *testing.T) { isDiff: true, delta: custom3, }, + // Though the test below might look incorrect this is how the location.Diff() works { name: "empty struct (new is empty struct)", base: custom3, new: custom4, - isDiff: true, - delta: custom4, + isDiff: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - log.Println() delta, isDiff := Diff(test.base, test.new) if isDiff != test.isDiff { t.Log(delta) @@ -831,8 +936,3 @@ func TestEntityDiffComplex(t *testing.T) { }) } } - -// test comparable to make sure it gets used -// UnorderedStrings -// text list -// hours diff --git a/entity_test.go b/entity_test.go index 8d4b4bd..7cd97d4 100644 --- a/entity_test.go +++ b/entity_test.go @@ -6,26 +6,19 @@ import ( "testing" ) -type CustomCustomFieldType struct { - Name *string - Date *float64 - Person *bool -} - type CustomLocationEntity struct { LocationEntity - CFHours *Hours `json:"cf_Hours,omitempty"` - CFUrl *string `json:"cf_Url,omitempty"` - CFDailyTimes *DailyTimes `json:"cf_DailyTimes,omitempty"` - CFTextList *[]string `json:"cf_TextList,omitempty"` - CFGallery []*Photo `json:"cf_Gallery,omitempty"` - CFPhoto *Photo `json:"cf_Photo,omitempty"` - CFVideos []*Video `json:"cf_Videos,omitempty"` - CFVideo *Video `json:"cf_Video,omitempty"` - CFDate *Date `json:"cf_Date,omitempty"` - CFSingleOption *string `json:"cf_SingleOtpion,omitempty"` - CFMultiOption *[]UnorderedStrings `json:"cf_MultiOption,omitempty"` - CFCustomType *CustomCustomFieldType `json:"cf_CustomCustomFieldType,omitempty"` + CFHours *Hours `json:"cf_Hours,omitempty"` + CFUrl *string `json:"cf_Url,omitempty"` + CFDailyTimes *DailyTimes `json:"cf_DailyTimes,omitempty"` + CFTextList *[]string `json:"cf_TextList,omitempty"` + CFGallery []*Photo `json:"cf_Gallery,omitempty"` + CFPhoto *Photo `json:"cf_Photo,omitempty"` + CFVideos []*Video `json:"cf_Videos,omitempty"` + CFVideo *Video `json:"cf_Video,omitempty"` + CFDate *Date `json:"cf_Date,omitempty"` + CFSingleOption *string `json:"cf_SingleOtpion,omitempty"` + CFMultiOption *UnorderedStrings `json:"cf_MultiOption,omitempty"` } func entityToJSONString(entity Entity) (error, string) { From 44901a1611221f7009f67f30c8b846ef97f089ec Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 17 Oct 2018 16:42:39 -0400 Subject: [PATCH 014/285] clean up --- acl_diff_test.go | 852 ++++++++++++++++++------------------ cmd/main.go | 137 ------ customfield.go | 6 +- customfield_service.go | 2 +- customfield_service_test.go | 71 +-- diff/main.go | 10 - entity_service.go | 47 +- entity_test.go | 4 +- location.go | 10 - location_diff.go | 4 - location_diff_test.go | 2 +- role_diff_test.go | 126 +++--- 12 files changed, 535 insertions(+), 736 deletions(-) delete mode 100644 cmd/main.go delete mode 100644 diff/main.go diff --git a/acl_diff_test.go b/acl_diff_test.go index a6fcb7b..835fdbc 100644 --- a/acl_diff_test.go +++ b/acl_diff_test.go @@ -1,424 +1,432 @@ package yext_test -// func TestACL_Diff(t *testing.T) { -// tests := []struct { -// name string -// aclA yext.ACL -// aclB yext.ACL -// wantDelta *yext.ACL -// wantDiff bool -// }{ -// { -// name: "Identical ACLs", -// aclA: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// aclB: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// wantDelta: nil, -// wantDiff: false, -// }, -// { -// name: "Different Roles in ACL", -// aclA: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// aclB: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// wantDelta: &yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// }, -// wantDiff: true, -// }, -// { -// name: "Different 'On' params in ACL", -// aclA: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// aclB: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// wantDelta: &yext.ACL{ -// On: "123456", -// }, -// wantDiff: true, -// }, -// { -// name: "Different 'AccessOn' params in ACL", -// aclA: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// aclB: yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// wantDelta: &yext.ACL{ -// AccessOn: yext.ACCESS_LOCATION, -// }, -// wantDiff: true, -// }, -// } -// -// for _, test := range tests { -// if gotDelta, gotDiff := test.aclA.Diff(test.aclB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { -// t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) -// } -// } -// } -// -// func TestACLList_Diff(t *testing.T) { -// tests := []struct { -// name string -// aclListA yext.ACLList -// aclListB yext.ACLList -// wantDelta yext.ACLList -// wantDiff bool -// }{ -// { -// name: "Identical ACLLists", -// aclListA: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// aclListB: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDelta: nil, -// wantDiff: false, -// }, -// { -// name: "Identical ACLs in ACLLists", -// aclListA: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// }, -// aclListB: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// }, -// wantDelta: nil, -// wantDiff: false, -// }, -// { -// name: "Different Length in ACLLists", -// aclListA: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// aclListB: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("5"), -// Name: yext.String("Example Role Three"), -// }, -// On: "1234567", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDelta: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("5"), -// Name: yext.String("Example Role Three"), -// }, -// On: "1234567", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDiff: true, -// }, -// { -// name: "Different Items in ACLLists", -// aclListA: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// aclListB: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("33"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("44"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDelta: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("33"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("44"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDiff: true, -// }, -// { -// name: "Some Identical and Some Different Items in ACLLists", -// aclListA: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// aclListB: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("5"), -// Name: yext.String("Example Role Three"), -// }, -// On: "1234567", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDelta: yext.ACLList{ -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// On: "12345", -// AccessOn: yext.ACCESS_FOLDER, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role Two"), -// }, -// On: "123456", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// yext.ACL{ -// Role: yext.Role{ -// Id: yext.String("5"), -// Name: yext.String("Example Role Three"), -// }, -// On: "1234567", -// AccessOn: yext.ACCESS_LOCATION, -// }, -// }, -// wantDiff: true, -// }, -// } -// -// for _, test := range tests { -// if gotDelta, gotDiff := test.aclListA.Diff(test.aclListB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { -// t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) -// } -// } -// } +import ( + "fmt" + "reflect" + "testing" + + yext "gopkg.in/yext/yext-go.v2" +) + +func TestACL_Diff(t *testing.T) { + tests := []struct { + name string + aclA yext.ACL + aclB yext.ACL + wantDelta *yext.ACL + wantDiff bool + }{ + { + name: "Identical ACLs", + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + wantDelta: nil, + wantDiff: false, + }, + { + name: "Different Roles in ACL", + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + wantDelta: &yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + }, + wantDiff: true, + }, + { + name: "Different 'On' params in ACL", + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "123456", + AccessOn: yext.ACCESS_FOLDER, + }, + wantDelta: &yext.ACL{ + On: "123456", + }, + wantDiff: true, + }, + { + name: "Different 'AccessOn' params in ACL", + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_LOCATION, + }, + wantDelta: &yext.ACL{ + AccessOn: yext.ACCESS_LOCATION, + }, + wantDiff: true, + }, + } + + for _, test := range tests { + if gotDelta, gotDiff := test.aclA.Diff(test.aclB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { + t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) + } + } +} + +func TestACLList_Diff(t *testing.T) { + tests := []struct { + name string + aclListA yext.ACLList + aclListB yext.ACLList + wantDelta yext.ACLList + wantDiff bool + }{ + { + name: "Identical ACLLists", + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDelta: nil, + wantDiff: false, + }, + { + name: "Identical ACLs in ACLLists", + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + }, + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + }, + wantDelta: nil, + wantDiff: false, + }, + { + name: "Different Length in ACLLists", + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), + }, + On: "1234567", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDelta: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), + }, + On: "1234567", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDiff: true, + }, + { + name: "Different Items in ACLLists", + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("33"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("44"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDelta: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("33"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("44"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDiff: true, + }, + { + name: "Some Identical and Some Different Items in ACLLists", + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), + }, + On: "1234567", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDelta: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + On: "12345", + AccessOn: yext.ACCESS_FOLDER, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), + }, + On: "123456", + AccessOn: yext.ACCESS_LOCATION, + }, + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), + }, + On: "1234567", + AccessOn: yext.ACCESS_LOCATION, + }, + }, + wantDiff: true, + }, + } + + for _, test := range tests { + if gotDelta, gotDiff := test.aclListA.Diff(test.aclListB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { + t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) + } + } +} diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index ceaa3dd..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,137 +0,0 @@ -package main - -import ( - "log" - - yext "gopkg.in/yext/yext-go.v2" -) - -type MorganLocation struct { - yext.LocationEntity - CMultiText *string `json:"c_multiText"` -} - -type HealthcareProfessional struct { - yext.Location -} - -type PersonEntity interface { - MyType() string -} - -type Teacher struct { - Person - Blah string -} - -func (t *Teacher) MyType() string { - return "Teacher" -} - -type Person struct { - Name string -} - -func (p *Person) MyType() string { - return "Person" -} - -func whatAmI(it interface{}) { - - if _, ok := it.(*Teacher); ok { - log.Println("is a teacher") - } - if _, ok := it.(*Person); ok { - log.Println("is a person") - } -} - -func main() { - // teacher := &Teacher{ - // Person: Person{ - // Name: "Catherine", - // }, - // Blah: "Blah", - // } - // whatAmI(teacher) - client := yext.NewClient(yext.NewDefaultConfig().WithApiKey("e929153c956b051cea51ec289bfd2383")) - client.EntityService.RegisterEntity(yext.ENTITYTYPE_LOCATION, &MorganLocation{}) - client.EntityService.RegisterEntity("HEALTHCARE_PROFESSIONAL", &HealthcareProfessional{}) - client.EntityService.RegisterEntity("HEALTHCARE_FACILITY", &HealthcareProfessional{}) - client.EntityService.RegisterEntity("ATM", &HealthcareProfessional{}) - // entities, err := client.EntityService.ListAll(nil) - // if err != nil { - // log.Fatal(err) - // } - // - // log.Printf("ListAll: Got %d entities", len(entities)) - // for _, entity := range entities { - // switch entity.(type) { - // case *MorganLocation: - // log.Println("I am a Morgan Location") - // morganEntity := entity.(*MorganLocation) - // log.Printf("PROFILE description: %s", GetString(morganEntity.Description)) - // log.Printf("CUSTOM multi: %s", GetString(morganEntity.CMultiText)) - // } - // } - - entity, _, err := client.EntityService.Get("CTG") - if err != nil { - log.Fatal(err) - } - morganEntity := entity.(*MorganLocation) - log.Printf("Get: Got %s", morganEntity.GetName()) - - // morganEntity.Name = yext.String(morganEntity.GetName() + "- 1") - // update := &MorganLocation{ - // Location: yext.Location{ - // // EntityMeta: yext.EntityMeta{ - // // Id: yext.String("CTG"), - // // }, - // Name: yext.String("CTG"), - // }, - // } - // _, err = client.EntityService.Edit(update) - // if err != nil { - // log.Fatal(err) - // } - - // log.Printf("Edit: Edited %s", morganEntity.GetName()) - - // morganEntity = &MorganLocation{ - // Location: yext.Location{ - // Name: yext.String("Yext Consulting"), - // EntityMeta: &yext.EntityMeta{ - // //Id: yext.String("CTG2"), - // EntityType: yext.ENTITYTYPE_LOCATION, - // CategoryIds: &[]string{"2374", "708"}, - // }, - // MainPhone: yext.String("8888888888"), - // Address: &yext.Address{ - // Line1: yext.String("7900 Westpark"), - // City: yext.String("McLean"), - // Region: yext.String("VA"), - // PostalCode: yext.String("22102"), - // }, - - // FeaturedMessage: &yext.FeaturedMessage{ - // Url: yext.String("www.yext.com"), - // Description: yext.String("Yext Consulting"), - // }, - // }, - // } - // - // _, err = client.EntityService.Create(morganEntity) - // if err != nil { - // log.Fatalf("Create error: %s", err) - // } - // log.Printf("Create: Created %s", morganEntity.GetEntityId()) - -} - -func GetString(s *string) string { - if s == nil { - return "" - } - return *s -} diff --git a/customfield.go b/customfield.go index f6abf3c..cf8d1ae 100644 --- a/customfield.go +++ b/customfield.go @@ -245,9 +245,9 @@ func (h HoursCustom) CustomFieldTag() string { return CUSTOMFIELDTYPE_HOURS } -func (h Hours) 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 { diff --git a/customfield_service.go b/customfield_service.go index ff7cb33..686ba3a 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -493,7 +493,7 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Hours Field %v", v, err) } - var cf Hours + var cf HoursCustom err = json.Unmarshal(asJSON, &cf) if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Hours Field %v", v, err) diff --git a/customfield_service_test.go b/customfield_service_test.go index 9e1e2d6..467daf8 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -189,68 +189,17 @@ var ( Url: "http://www.youtube.com/watch?v=M80FTIcEgZM", Description: "An example caption for a video", }}, - // 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: &DayHours{ - OpenIntervals: []*Interval{ - &Interval{Start: "9:00", End: "17:00"}, + 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: "", }, - }, - Tuesday: &DayHours{ - OpenIntervals: []*Interval{ - &Interval{Start: "12:00", End: "15:00"}, - &Interval{Start: "5:00", End: "11:00"}, - }, - }, - Wednesday: &DayHours{ - OpenIntervals: []*Interval{ - &Interval{Start: "9:00", End: "17:00"}, - }, - }, - Thursday: &DayHours{ - OpenIntervals: []*Interval{ - &Interval{Start: "0:00", End: "23:59"}, - }, - }, - Friday: &DayHours{ - OpenIntervals: []*Interval{ - &Interval{Start: "9:00", End: "17:00"}, - }, - }, - Saturday: &DayHours{ - OpenIntervals: []*Interval{ - &Interval{Start: "9:00", End: "17:00"}, - }, - }, - Sunday: &DayHours{ - IsClosed: Bool(true), - }, - HolidayHours: &[]HolidayHours{ - HolidayHours{ - Date: "2016-05-30", - }, - HolidayHours{ - Date: "2016-05-31", - OpenIntervals: []*Interval{ - &Interval{ - Start: "9:00", - End: "17:00", - }, - }, + LocationHolidayHours{ + Date: "2016-05-31", + Hours: "9:00:17:00", }, }, }}, diff --git a/diff/main.go b/diff/main.go deleted file mode 100644 index 297ae78..0000000 --- a/diff/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import yext "gopkg.in/yext/yext-go.v2" - -func main() { - locA := &yext.Location{} - locB := &yext.Location{} - - locA.Diff(locB) -} diff --git a/entity_service.go b/entity_service.go index a12f5e0..fb9ca77 100644 --- a/entity_service.go +++ b/entity_service.go @@ -7,7 +7,10 @@ import ( "reflect" ) -const entityPath = "entities" +const ( + entityPath = "entities" + EntityListMaxLimit = 50 +) type EntityService struct { client *Client @@ -21,9 +24,10 @@ type EntityListOptions struct { } type EntityListResponse struct { - Count int `json:"count"` - Entities []interface{} `json:"entities"` - PageToken string `json:"pageToken"` + Count int `json:"count"` + Entities []interface{} `json:"entities"` + typedEntites []Entity + PageToken string `json:"pageToken"` } func (e *EntityService) RegisterDefaultEntities() { @@ -92,7 +96,7 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { if opts == nil { opts = &EntityListOptions{} } - opts.ListOptions = ListOptions{Limit: LocationListMaxLimit} // TODO: should this be EntityListMaxLimit + opts.ListOptions = ListOptions{Limit: EntityListMaxLimit} var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { opts.ListOptions = *listOptions resp, _, err := e.List(opts) @@ -100,14 +104,7 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { return "", err } - typedEntities, err := e.toEntityTypes(resp.Entities) - if err != nil { - return "", err - } - for _, entity := range typedEntities { - setNilIsEmpty(entity) - entities = append(entities, entity) - } + entities = append(entities, resp.typedEntites...) return resp.PageToken, err } @@ -144,12 +141,18 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res return nil, r, err } - // TODO: nil is empty - // for _, l := range v.Entities { - // l.nilIsEmpty = true - // } + typedEntities, err := e.toEntityTypes(v.Entities) + if err != nil { + return nil, r, err + } + entities := []Entity{} + for _, entity := range typedEntities { + setNilIsEmpty(entity) + entities = append(entities, entity) + } + v.typedEntites = entities - return v, nil, nil + return v, r, nil } func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error) { @@ -211,10 +214,6 @@ func getNilIsEmpty(i interface{}) bool { // TODO: Currently an error with API. Need to test this func (e *EntityService) Create(y Entity) (*Response, error) { - // TODO: custom field validation - // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { - // return nil, err - // } var requrl = entityPath u, err := url.Parse(requrl) if err != nil { @@ -234,10 +233,6 @@ func (e *EntityService) Create(y Entity) (*Response, error) { // TODO: There is an outstanding techops QA issue to allow the Id in the request but we may have to remove other things like account func (e *EntityService) Edit(y Entity) (*Response, error) { - // TODO: custom field validation - // if err := validateCustomFieldsKeys(y.CustomFields); err != nil { - // return nil, err - // } r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, y.GetEntityId()), y, nil) if err != nil { return r, err diff --git a/entity_test.go b/entity_test.go index 7cd97d4..59392bd 100644 --- a/entity_test.go +++ b/entity_test.go @@ -38,7 +38,7 @@ func TestEntityJSONSerialization(t *testing.T) { tests := []test{ {&CustomLocationEntity{}, `{}`}, - {&CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: nil}}}, `{"address":{}}`}, // TODO: verify this is correct + {&CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: nil}}}, `{"address":{}}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: String("")}}}, `{"address":{"city":""}}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Languages: nil}}, `{}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Languages: nil}}, `{}`}, @@ -70,7 +70,7 @@ func TestEntityJSONDeserialization(t *testing.T) { tests := []test{ {`{}`, &CustomLocationEntity{}}, {`{"emails": []}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{})}}}, - {`{"emails": ["mhupman@yext.com", "bmcginnis@yext.com"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{"mhupman@yext.com", "bmcginnis@yext.com"})}}}, + {`{"emails": ["bob@email.com", "sue@email.com"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{"bob@email.com", "sue@email.com"})}}}, {`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CFUrl: String("www.yext.com")}}, {`{"cf_TextList": ["a", "b", "c"]}`, &CustomLocationEntity{CFTextList: Strings([]string{"a", "b", "c"})}}, } diff --git a/location.go b/location.go index a8f3d8d..8d32ee5 100644 --- a/location.go +++ b/location.go @@ -10,19 +10,9 @@ import ( "fmt" ) -type EmbeddedStruct struct { - MyName string -} - -type EmbeddedStruct2 struct { - MyName2 string -} - // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { - // EmbeddedStruct - // EmbeddedStruct2 // Admin Id *string `json:"id,omitempty"` AccountId *string `json:"accountId,omitempty"` diff --git a/location_diff.go b/location_diff.go index a370372..8b33bc6 100644 --- a/location_diff.go +++ b/location_diff.go @@ -43,10 +43,6 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { valB = bV.Field(i) ) - // log.Println(nameA) - // log.Println(valB.CanSet()) - // log.Println(valB.Kind()) - if !valB.CanSet() { continue } diff --git a/location_diff_test.go b/location_diff_test.go index 052f71e..c63c6ac 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -630,7 +630,7 @@ type floatTest struct { var floatTests = []floatTest{ {Float(1234.0), Float(1234.0), false, false, nil}, {Float(1234.0), nil, false, false, nil}, - {Float(1234), nil, false, false, nil}, + {Float(0), nil, false, false, nil}, {nil, nil, false, false, nil}, {Float(0), Float(0), false, false, nil}, {Float(0), Float(9876.0), true, false, Float(9876.0)}, diff --git a/role_diff_test.go b/role_diff_test.go index 3540d8b..bd66e24 100644 --- a/role_diff_test.go +++ b/role_diff_test.go @@ -1,61 +1,69 @@ package yext_test -// func TestRole_Diff(t *testing.T) { -// tests := []struct { -// name string -// roleA yext.Role -// roleB yext.Role -// wantDelta yext.Role -// wantDiff bool -// }{ -// { -// name: "Identical Roles", -// roleA: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// roleB: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// wantDelta: yext.Role{}, -// wantDiff: false, -// }, -// { -// name: "Different 'Id' params in Roles", -// roleA: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// roleB: yext.Role{ -// Id: yext.String("4"), -// Name: yext.String("Example Role"), -// }, -// wantDelta: yext.Role{ -// Id: yext.String("4"), -// }, -// wantDiff: true, -// }, -// { -// name: "Different 'Name' params in Roles", -// roleA: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role"), -// }, -// roleB: yext.Role{ -// Id: yext.String("3"), -// Name: yext.String("Example Role Two"), -// }, -// wantDelta: yext.Role{ -// Name: yext.String("Example Role Two"), -// }, -// wantDiff: true, -// }, -// } -// -// for _, test := range tests { -// if gotDelta, gotDiff := test.roleA.Diff(test.roleB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { -// t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) -// } -// } -// } +import ( + "fmt" + "reflect" + "testing" + + yext "gopkg.in/yext/yext-go.v2" +) + +func TestRole_Diff(t *testing.T) { + tests := []struct { + name string + roleA yext.Role + roleB yext.Role + wantDelta yext.Role + wantDiff bool + }{ + { + name: "Identical Roles", + roleA: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + roleB: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + wantDelta: yext.Role{}, + wantDiff: false, + }, + { + name: "Different 'Id' params in Roles", + roleA: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + roleB: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role"), + }, + wantDelta: yext.Role{ + Id: yext.String("4"), + }, + wantDiff: true, + }, + { + name: "Different 'Name' params in Roles", + roleA: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), + }, + roleB: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role Two"), + }, + wantDelta: yext.Role{ + Name: yext.String("Example Role Two"), + }, + wantDiff: true, + }, + } + + for _, test := range tests { + if gotDelta, gotDiff := test.roleA.Diff(test.roleB); !reflect.DeepEqual(test.wantDelta, gotDelta) || test.wantDiff != gotDiff { + t.Error(fmt.Sprintf("test '%s' failed, got diff: %t, wanted diff: %t, got delta: %+v, wanted delta: %+v", test.name, test.wantDiff, gotDiff, test.wantDelta, gotDelta)) + } + } +} From b0ca1b8933d26645189e58a3fcc282b918a9bc12 Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 17 Oct 2018 16:53:24 -0400 Subject: [PATCH 015/285] clean up 2 --- acl_diff_test.go | 446 ++++++++++++++++++------------------ customfield.go | 11 +- customfield_service.go | 2 +- customfield_service_test.go | 97 +------- role_diff_test.go | 56 +++-- 5 files changed, 259 insertions(+), 353 deletions(-) diff --git a/acl_diff_test.go b/acl_diff_test.go index 835fdbc..3f2f068 100644 --- a/acl_diff_test.go +++ b/acl_diff_test.go @@ -1,111 +1,109 @@ -package yext_test +package yext import ( "fmt" "reflect" "testing" - - yext "gopkg.in/yext/yext-go.v2" ) func TestACL_Diff(t *testing.T) { tests := []struct { name string - aclA yext.ACL - aclB yext.ACL - wantDelta *yext.ACL + aclA ACL + aclB ACL + wantDelta *ACL wantDiff bool }{ { name: "Identical ACLs", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclB: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, wantDelta: nil, wantDiff: false, }, { name: "Different Roles in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + aclB: ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - wantDelta: &yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + wantDelta: &ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, }, wantDiff: true, }, { name: "Different 'On' params in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclB: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "123456", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - wantDelta: &yext.ACL{ + wantDelta: &ACL{ On: "123456", }, wantDiff: true, }, { name: "Different 'AccessOn' params in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclB: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - wantDelta: &yext.ACL{ - AccessOn: yext.ACCESS_LOCATION, + wantDelta: &ACL{ + AccessOn: ACCESS_LOCATION, }, wantDiff: true, }, @@ -121,47 +119,47 @@ func TestACL_Diff(t *testing.T) { func TestACLList_Diff(t *testing.T) { tests := []struct { name string - aclListA yext.ACLList - aclListB yext.ACLList - wantDelta yext.ACLList + aclListA ACLList + aclListB ACLList + wantDelta ACLList wantDiff bool }{ { name: "Identical ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDelta: nil, @@ -169,40 +167,40 @@ func TestACLList_Diff(t *testing.T) { }, { name: "Identical ACLs in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, }, wantDelta: nil, @@ -210,214 +208,214 @@ func TestACLList_Diff(t *testing.T) { }, { name: "Different Length in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + wantDelta: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDiff: true, }, { name: "Different Items in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("33"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("33"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("44"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("44"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("33"), - Name: yext.String("Example Role"), + wantDelta: ACLList{ + ACL{ + Role: Role{ + Id: String("33"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("44"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("44"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDiff: true, }, { name: "Some Identical and Some Different Items in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + wantDelta: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDiff: true, diff --git a/customfield.go b/customfield.go index cf8d1ae..c4dc299 100644 --- a/customfield.go +++ b/customfield.go @@ -245,11 +245,8 @@ 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 +// DailyTimesCustom is the DailyTimes custom field format used by locations API +// Entities API uses the new DailyTimes struct type DailyTimesCustom struct { DailyTimes string `json:"dailyTimes,omitempty"` } @@ -267,7 +264,3 @@ type DailyTimes struct { Friday string `json:"friday,omitempty"` Saturday string `json:"saturday,omitempty"` } - -func (d DailyTimes) CustomFieldTag() string { - return CUSTOMFIELDTYPE_DAILYTIMES -} diff --git a/customfield_service.go b/customfield_service.go index 686ba3a..e09999f 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -504,7 +504,7 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for DailyT imes Field %v", v, err) } - var cf DailyTimes + var cf DailyTimesCustom err = json.Unmarshal(asJSON, &cf) if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Daily Times Field %v", v, err) diff --git a/customfield_service_test.go b/customfield_service_test.go index 467daf8..a06da68 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -57,80 +57,8 @@ var ( "clickthroughUrl": "https://yext.com/event", "url": "https://mktgcdn.com/awesome.jpg", } - // 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{}{ - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", - }, - }, - }, - "tuesday": map[string]interface{}{ - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "12:00", - "end": "15:00", - }, - { - "start": "5:00", - "end": "11:00", - }, - }, - }, - "wednesday": map[string]interface{}{ - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", - }, - }, - }, - "thursday": map[string]interface{}{ - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "0:00", - "end": "23:59", - }, - }, - }, - "friday": map[string]interface{}{ - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", - }, - }, - }, - "saturday": map[string]interface{}{ - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", - }, - }, - }, - "sunday": map[string]interface{}{ - "isClosed": true, - }, - "holidayHours": []interface{}{ - map[string]interface{}{ - "date": "2016-05-30", - }, - map[string]interface{}{ - "date": "2016-05-31", - "openIntervals": []map[string]interface{}{ - map[string]interface{}{ - "start": "9:00", - "end": "17:00", - }, - }, - }, - }, - } - hoursRawForLocation = map[string]interface{}{ + // hoursRaw is in the format used by HoursCustom for location-service + hoursRaw = 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{}{ @@ -148,14 +76,9 @@ var ( "description": "An example caption for a video", "url": "http://www.youtube.com/watch?v=M80FTIcEgZM", } + // dailyTimesRaw is in the format used by DailyTimesCustom for location-service dailyTimesRaw = map[string]interface{}{ - "monday": "4:00", - "tuesday": "5:00", - "wednesday": "6:00", - "thursday": "7:00", - "friday": "8:00", - "saturday": "9:00", - "sunday": "10:00", + "dailyTimes": "1:10:00;2:4:00;3:5:00;4:6:00;5:7:00;6:8:00;7:9:00", } parseTests = []customFieldParseTest{ customFieldParseTest{"BOOLEAN", false, YesNo(false)}, @@ -189,7 +112,7 @@ var ( Url: "http://www.youtube.com/watch?v=M80FTIcEgZM", Description: "An example caption for a video", }}, - customFieldParseTest{"HOURS", hoursRawForLocation, HoursCustom{ + customFieldParseTest{"HOURS", hoursRaw, 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{ @@ -203,14 +126,8 @@ var ( }, }, }}, - customFieldParseTest{"DAILY_TIMES", dailyTimesRaw, DailyTimes{ - Monday: "4:00", - Tuesday: "5:00", - Wednesday: "6:00", - Thursday: "7:00", - Friday: "8:00", - Saturday: "9:00", - Sunday: "10:00", + customFieldParseTest{"DAILY_TIMES", dailyTimesRaw, DailyTimesCustom{ + DailyTimes: "1:10:00;2:4:00;3:5:00;4:6:00;5:7:00;6:8:00;7:9:00", }}, customFieldParseTest{"LOCATION_LIST", []string{"a", "b", "c"}, LocationList([]string{"a", "b", "c"})}, } diff --git a/role_diff_test.go b/role_diff_test.go index bd66e24..bea904f 100644 --- a/role_diff_test.go +++ b/role_diff_test.go @@ -1,61 +1,59 @@ -package yext_test +package yext import ( "fmt" "reflect" "testing" - - yext "gopkg.in/yext/yext-go.v2" ) func TestRole_Diff(t *testing.T) { tests := []struct { name string - roleA yext.Role - roleB yext.Role - wantDelta yext.Role + roleA Role + roleB Role + wantDelta Role wantDiff bool }{ { name: "Identical Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleA: Role{ + Id: String("3"), + Name: String("Example Role"), }, - roleB: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleB: Role{ + Id: String("3"), + Name: String("Example Role"), }, - wantDelta: yext.Role{}, + wantDelta: Role{}, wantDiff: false, }, { name: "Different 'Id' params in Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleA: Role{ + Id: String("3"), + Name: String("Example Role"), }, - roleB: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role"), + roleB: Role{ + Id: String("4"), + Name: String("Example Role"), }, - wantDelta: yext.Role{ - Id: yext.String("4"), + wantDelta: Role{ + Id: String("4"), }, wantDiff: true, }, { name: "Different 'Name' params in Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleA: Role{ + Id: String("3"), + Name: String("Example Role"), }, - roleB: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role Two"), + roleB: Role{ + Id: String("3"), + Name: String("Example Role Two"), }, - wantDelta: yext.Role{ - Name: yext.String("Example Role Two"), + wantDelta: Role{ + Name: String("Example Role Two"), }, wantDiff: true, }, From bb7e2ef627b93dbdd3ba4f622c92f6d0240f60c4 Mon Sep 17 00:00:00 2001 From: cdworak Date: Wed, 17 Oct 2018 17:02:02 -0400 Subject: [PATCH 016/285] revert acl_diff and role_diff --- acl_diff_test.go | 446 +++++++++++++++++++++++----------------------- role_diff_test.go | 56 +++--- 2 files changed, 253 insertions(+), 249 deletions(-) diff --git a/acl_diff_test.go b/acl_diff_test.go index 3f2f068..5d28975 100644 --- a/acl_diff_test.go +++ b/acl_diff_test.go @@ -1,109 +1,111 @@ -package yext +package yext_test import ( "fmt" "reflect" "testing" + + "github.com/yext/yext-go" ) func TestACL_Diff(t *testing.T) { tests := []struct { name string - aclA ACL - aclB ACL - wantDelta *ACL + aclA yext.ACL + aclB yext.ACL + wantDelta *yext.ACL wantDiff bool }{ { name: "Identical ACLs", - aclA: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - aclB: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, wantDelta: nil, wantDiff: false, }, { name: "Different Roles in ACL", - aclA: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - aclB: ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - wantDelta: &ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + wantDelta: &yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, }, wantDiff: true, }, { name: "Different 'On' params in ACL", - aclA: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - aclB: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "123456", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - wantDelta: &ACL{ + wantDelta: &yext.ACL{ On: "123456", }, wantDiff: true, }, { name: "Different 'AccessOn' params in ACL", - aclA: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclA: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - aclB: ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclB: yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, - wantDelta: &ACL{ - AccessOn: ACCESS_LOCATION, + wantDelta: &yext.ACL{ + AccessOn: yext.ACCESS_LOCATION, }, wantDiff: true, }, @@ -119,47 +121,47 @@ func TestACL_Diff(t *testing.T) { func TestACLList_Diff(t *testing.T) { tests := []struct { name string - aclListA ACLList - aclListB ACLList - wantDelta ACLList + aclListA yext.ACLList + aclListB yext.ACLList + wantDelta yext.ACLList wantDiff bool }{ { name: "Identical ACLLists", - aclListA: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - aclListB: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, wantDelta: nil, @@ -167,40 +169,40 @@ func TestACLList_Diff(t *testing.T) { }, { name: "Identical ACLs in ACLLists", - aclListA: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, }, - aclListB: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, }, wantDelta: nil, @@ -208,214 +210,214 @@ func TestACLList_Diff(t *testing.T) { }, { name: "Different Length in ACLLists", - aclListA: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - aclListB: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, - ACL{ - Role: Role{ - Id: String("5"), - Name: String("Example Role Three"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), }, On: "1234567", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - wantDelta: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + wantDelta: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, - ACL{ - Role: Role{ - Id: String("5"), - Name: String("Example Role Three"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), }, On: "1234567", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, wantDiff: true, }, { name: "Different Items in ACLLists", - aclListA: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - aclListB: ACLList{ - ACL{ - Role: Role{ - Id: String("33"), - Name: String("Example Role"), + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("33"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("44"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("44"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - wantDelta: ACLList{ - ACL{ - Role: Role{ - Id: String("33"), - Name: String("Example Role"), + wantDelta: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("33"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("44"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("44"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, wantDiff: true, }, { name: "Some Identical and Some Different Items in ACLLists", - aclListA: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListA: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - aclListB: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + aclListB: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, - ACL{ - Role: Role{ - Id: String("5"), - Name: String("Example Role Three"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), }, On: "1234567", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, - wantDelta: ACLList{ - ACL{ - Role: Role{ - Id: String("3"), - Name: String("Example Role"), + wantDelta: yext.ACLList{ + yext.ACL{ + Role: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, On: "12345", - AccessOn: ACCESS_FOLDER, + AccessOn: yext.ACCESS_FOLDER, }, - ACL{ - Role: Role{ - Id: String("4"), - Name: String("Example Role Two"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role Two"), }, On: "123456", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, - ACL{ - Role: Role{ - Id: String("5"), - Name: String("Example Role Three"), + yext.ACL{ + Role: yext.Role{ + Id: yext.String("5"), + Name: yext.String("Example Role Three"), }, On: "1234567", - AccessOn: ACCESS_LOCATION, + AccessOn: yext.ACCESS_LOCATION, }, }, wantDiff: true, diff --git a/role_diff_test.go b/role_diff_test.go index bea904f..6dadf22 100644 --- a/role_diff_test.go +++ b/role_diff_test.go @@ -1,59 +1,61 @@ -package yext +package yext_test import ( "fmt" "reflect" "testing" + + "github.com/yext/yext-go" ) func TestRole_Diff(t *testing.T) { tests := []struct { name string - roleA Role - roleB Role - wantDelta Role + roleA yext.Role + roleB yext.Role + wantDelta yext.Role wantDiff bool }{ { name: "Identical Roles", - roleA: Role{ - Id: String("3"), - Name: String("Example Role"), + roleA: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, - roleB: Role{ - Id: String("3"), - Name: String("Example Role"), + roleB: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, - wantDelta: Role{}, + wantDelta: yext.Role{}, wantDiff: false, }, { name: "Different 'Id' params in Roles", - roleA: Role{ - Id: String("3"), - Name: String("Example Role"), + roleA: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, - roleB: Role{ - Id: String("4"), - Name: String("Example Role"), + roleB: yext.Role{ + Id: yext.String("4"), + Name: yext.String("Example Role"), }, - wantDelta: Role{ - Id: String("4"), + wantDelta: yext.Role{ + Id: yext.String("4"), }, wantDiff: true, }, { name: "Different 'Name' params in Roles", - roleA: Role{ - Id: String("3"), - Name: String("Example Role"), + roleA: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role"), }, - roleB: Role{ - Id: String("3"), - Name: String("Example Role Two"), + roleB: yext.Role{ + Id: yext.String("3"), + Name: yext.String("Example Role Two"), }, - wantDelta: Role{ - Name: String("Example Role Two"), + wantDelta: yext.Role{ + Name: yext.String("Example Role Two"), }, wantDiff: true, }, From 27e7d23e49979f6569ae7709986eb0474f4a6ff9 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Wed, 17 Oct 2018 17:28:05 -0400 Subject: [PATCH 017/285] CFT Assets: modify asset data model, add asset type registry to asset service (#63) * CFT Assets: modify asset data model, add asset type registry to asset service * CFT Assets: fix value nesting for simple types * CFT Assets: Field corrections, type assert text asset values as string, cleanup * Entities: Registered LocationEntity by default, added new hours handling, other cleanup * Entities: Move Location specific metadata back into LocationEntity * Assets: Add GetId() function * Assets: Restore old asset service endpoints * CFT Asset Service: Add to client * Update entity fields, update Go versions in TravisCI config --- .travis.yml | 3 +- asset.go | 9 --- asset_service.go | 4 -- cftasset.go | 94 ++++++++++++++++++++++++++++ cftasset_service.go | 148 ++++++++++++++++++++++++++++++++++++++++++++ client.go | 3 + entity_service.go | 2 +- location_entity.go | 41 ++++++------ 8 files changed, 269 insertions(+), 35 deletions(-) create mode 100644 cftasset.go create mode 100644 cftasset_service.go diff --git a/.travis.yml b/.travis.yml index cf0d44e..62f9f95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: go go: - - 1.6 - 1.7 - 1.8 - 1.9 + - 1.10 + - 1.11 - tip diff --git a/asset.go b/asset.go index 20415a9..44a062d 100644 --- a/asset.go +++ b/asset.go @@ -1,20 +1,11 @@ package yext -type AssetType string - const ( AssetTypeText AssetType = "AssetTypeText" AssetTypePhoto AssetType = "AssetTypePhoto" AssetTypeVideo AssetType = "AssetTypeVideo" ) -type LabelOperator string - -const ( - LabelOperatorAnd LabelOperator = "AND" - LabelOperatorOr LabelOperator = "OR" -) - type Asset struct { Id string `json:"id"` Name string `json:"name"` diff --git a/asset_service.go b/asset_service.go index bb0ee8c..63b3734 100644 --- a/asset_service.go +++ b/asset_service.go @@ -2,10 +2,6 @@ package yext import "fmt" -const assetsPath = "assets" - -var AssetListMaxLimit = 1000 - type AssetService struct { client *Client } diff --git a/cftasset.go b/cftasset.go new file mode 100644 index 0000000..1f3f1cf --- /dev/null +++ b/cftasset.go @@ -0,0 +1,94 @@ +package yext + +type MappingType string + +const ( + MappingTypeNone MappingType = "NO_ENTITIES" + MappingTypeAll MappingType = "ALL_ENTITIES" + MappingTypeFolder MappingType = "FOLDER" + MappingTypeEntities MappingType = "ENTITIES" +) + +type UsageType string + +const ( + UsageTypeProfileFields UsageType = "PROFILE_FIELDS" + UsageTypeReviewResponse UsageType = "REVIEW_RESPONSE" + UsageTypeSocialPosting UsageType = "SOCIAL_POSTING" + UsageTypeAllFields UsageType = "ALL_PROFILE_FIELDS" +) + +type LabelOperator string + +const ( + LabelOperatorAnd LabelOperator = "AND" + LabelOperatorOr LabelOperator = "OR" +) + +type AssetType string + +const ( + ASSETTYPE_TEXT AssetType = "text" + ASSETTYPE_IMAGE AssetType = "image" + ASSETTYPE_VIDEO AssetType = "video" + ASSETTYPE_COMPLEXIMAGE AssetType = "complexImage" + ASSETTYPE_COMPLEXVIDEO AssetType = "complexVideo" +) + +type CFTAsset struct { + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type AssetType `json:"type,omitempty"` + Description *string `json:"description,omitempty"` + ForEntities *ForEntities `json:"forEntities,omitempty"` + Usage *[]AssetUsage `json:"usage,omitempty"` + Locale *string `json:"locale,omitempty"` + Labels *UnorderedStrings `json:"labels,omitempty"` + Owner *uint64 `json:"owner,omitempty"` + Value interface{} `json:"value,omitempty"` +} + +type ForEntities struct { + MappingType MappingType `json:"mappingType,omitempty"` + FolderId *string `json:"folderId,omitempty"` + EntityIds *[]string `json:"entityIds,omitempty"` + LabelIds *UnorderedStrings `json:"labelIds,omitempty"` + LabelOperator LabelOperator `json:"labelOperator,omitempty"` +} + +type AssetUsage struct { + Type UsageType `json:"type,omitempty"` + FieldNames *[]string `json:"fieldNames,omitempty"` +} + +type TextValue string + +type ImageValue struct { + Url string `json:"url,omitempty"` + AlternateText string `json:"alternateText,omitempty"` + Height uint64 `json:height,omitempty` + Width uint64 `json:width,omitempty` +} + +type ComplexImageValue struct { + Image *ImageValue `json:"image,omitempty"` + Description string `json:"description,omitempty"` + Details string `json:"details,omitempty"` + ClickthroughURL string `json:"clickthroughUrl,omitempty"` +} + +type VideoValue struct { + Url string `json:"url,omitempty"` +} + +type ComplexVideoValue struct { + Video *VideoValue `json:"video,omitempty"` + Description string `json:"description,omitempty"` +} + +func (a *CFTAsset) GetId() string { + if a.Id == nil { + return "" + } + return *a.Id +} diff --git a/cftasset_service.go b/cftasset_service.go new file mode 100644 index 0000000..a0ef864 --- /dev/null +++ b/cftasset_service.go @@ -0,0 +1,148 @@ +package yext + +import ( + "encoding/json" + "fmt" +) + +const assetsPath = "assets" + +var AssetListMaxLimit = 1000 + +type CFTAssetService struct { + client *Client + registry Registry +} + +type CFTAssetListResponse struct { + Count int `json:"count"` + Assets []*CFTAsset `json:"assets"` +} + +func (a *CFTAssetService) RegisterDefaultAssetValues() { + a.registry = make(Registry) + // ASSETTYPE_TEXT doesn't need to register because it's just a string + a.RegisterAssetValue(ASSETTYPE_IMAGE, &ImageValue{}) + a.RegisterAssetValue(ASSETTYPE_VIDEO, &VideoValue{}) + a.RegisterAssetValue(ASSETTYPE_COMPLEXIMAGE, &ComplexImageValue{}) + a.RegisterAssetValue(ASSETTYPE_COMPLEXVIDEO, &ComplexVideoValue{}) +} + +func (a *CFTAssetService) RegisterAssetValue(t AssetType, assetValue interface{}) { + a.registry.Register(string(t), assetValue) +} + +func (a *CFTAssetService) CreateAssetValue(t AssetType) (interface{}, error) { + return a.registry.Create(string(t)) +} + +func (a *CFTAssetService) toAssetsWithValues(assets []*CFTAsset) error { + for _, asset := range assets { + if err := a.toAssetWithValue(asset); err != nil { + return err + } + } + return nil +} + +func (a *CFTAssetService) toAssetWithValue(asset *CFTAsset) error { + if asset.Type == ASSETTYPE_TEXT { + asset.Value = asset.Value.(string) + return nil + } + var assetValueValsByKey = asset.Value.(map[string]interface{}) + assetValueObj, err := a.CreateAssetValue(asset.Type) + if err != nil { + return err + } + + // Convert into struct of Asset Value Type + assetValueJSON, err := json.Marshal(assetValueValsByKey) + if err != nil { + return fmt.Errorf("Marshaling asset value to JSON: %s", err) + } + + err = json.Unmarshal(assetValueJSON, &assetValueObj) + if err != nil { + return fmt.Errorf("Unmarshaling asset value to JSON: %s", err) + } + asset.Value = assetValueObj + return nil +} + +func (a *CFTAssetService) ListAll() ([]*CFTAsset, error) { + var assets []*CFTAsset + var lr listRetriever = func(opts *ListOptions) (int, int, error) { + alr, _, err := a.List(opts) + if err != nil { + return 0, 0, err + } + + if err = a.toAssetsWithValues(alr.Assets); err != nil { + return 0, 0, err + } + assets = append(assets, alr.Assets...) + return len(alr.Assets), alr.Count, err + } + + if err := listHelper(lr, &ListOptions{Limit: AssetListMaxLimit}); err != nil { + return nil, err + } else { + return assets, nil + } +} + +func (a *CFTAssetService) List(opts *ListOptions) (*CFTAssetListResponse, *Response, error) { + requrl, err := addListOptions(assetsPath, opts) + if err != nil { + return nil, nil, err + } + var v CFTAssetListResponse + r, err := a.client.DoRequest("GET", requrl, &v) + if err != nil { + return nil, r, err + } + + return &v, r, nil +} + +func (a *CFTAssetService) Create(asset *CFTAsset) (*Response, error) { + r, err := a.client.DoRequestJSON("POST", fmt.Sprintf("%s", assetsPath), asset, nil) + if err != nil { + return r, err + } + + return r, nil +} + +func (a *CFTAssetService) Get(assetId string) (*CFTAsset, *Response, error) { + var v *CFTAsset + r, err := a.client.DoRequest("GET", fmt.Sprintf("%s/%s", assetsPath, assetId), &v) + if err != nil { + return nil, r, err + } + + if err := a.toAssetWithValue(v); err != nil { + return nil, r, err + } + + return v, r, nil +} + +func (a *CFTAssetService) Update(assetId string, asset *CFTAsset) (*Response, error) { + r, err := a.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", assetsPath, assetId), asset, nil) + if err != nil { + return r, err + } + + return r, nil +} + +func (a *CFTAssetService) Delete(assetId string) (*Response, error) { + r, err := a.client.DoRequest("DELETE", fmt.Sprintf("%s/%s", assetsPath, assetId), nil) + if err != nil { + return r, err + } + + return r, nil +} diff --git a/client.go b/client.go index 46b2610..0bb7343 100644 --- a/client.go +++ b/client.go @@ -35,6 +35,7 @@ type Client struct { ReviewService *ReviewService LanguageProfileService *LanguageProfileService AssetService *AssetService + CFTAssetService *CFTAssetService AnalyticsService *AnalyticsService EntityService *EntityService } @@ -51,6 +52,8 @@ func NewClient(config *Config) *Client { c.ReviewService = &ReviewService{client: c} c.LanguageProfileService = &LanguageProfileService{client: c} c.AssetService = &AssetService{client: c} + c.CFTAssetService = &CFTAssetService{client: c} + c.CFTAssetService.RegisterDefaultAssetValues() c.AnalyticsService = &AnalyticsService{client: c} c.EntityService = &EntityService{client: c} c.EntityService.RegisterDefaultEntities() diff --git a/entity_service.go b/entity_service.go index fb9ca77..4000bb5 100644 --- a/entity_service.go +++ b/entity_service.go @@ -32,7 +32,7 @@ type EntityListResponse struct { func (e *EntityService) RegisterDefaultEntities() { e.registry = make(Registry) - e.RegisterEntity(ENTITYTYPE_LOCATION, &Location{}) + e.RegisterEntity(ENTITYTYPE_LOCATION, &LocationEntity{}) e.RegisterEntity(ENTITYTYPE_EVENT, &Event{}) } diff --git a/location_entity.go b/location_entity.go index 5722b17..a80e425 100644 --- a/location_entity.go +++ b/location_entity.go @@ -193,12 +193,6 @@ type Interval struct { End string `json:"end,omitempty"` } -type HolidayHours struct { - Date string `json:"date"` - IsClosed *bool `json:"isClosed,omitempty"` - OpenIntervals []*Interval `json:"openIntervals"` -} - func (y LocationEntity) GetId() string { if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { return *y.BaseEntity.Meta.Id @@ -521,13 +515,6 @@ func (y LocationEntity) GetMenuListIds() (v []string) { return v } -func (y LocationEntity) GetFolderId() string { - if y.FolderId != nil { - return *y.FolderId - } - return "" -} - func (y LocationEntity) GetReviewBalancingURL() string { if y.ReviewBalancingURL != nil { return *y.ReviewBalancingURL @@ -603,6 +590,20 @@ func (y LocationEntity) GetLanguages() (v []string) { return v } +func (y LocationEntity) GetFolderId() string { + if y.FolderId != nil { + return *y.FolderId + } + return "" +} + +func (y LocationEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + func (y LocationEntity) GetLabelIds() (v UnorderedStrings) { if y.LabelIds != nil { v = *y.LabelIds @@ -619,13 +620,6 @@ func (y LocationEntity) SetLabelIdsWithUnorderedStrings(v UnorderedStrings) { y.LabelIds = &v } -func (y LocationEntity) GetCategoryIds() (v []string) { - if y.CategoryIds != nil { - v = *y.CategoryIds - } - return v -} - func (y LocationEntity) GetPaymentOptions() (v []string) { if y.PaymentOptions != nil { v = *y.PaymentOptions @@ -667,3 +661,10 @@ func (y LocationEntity) IsClosed() bool { } return false } + +// HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager. +// For details see +type HolidayHours struct { + Date string `json:"date"` + Hours []*Interval `json:"hours"` +} From 014851d5ae92ad25b628f1cb752549f1a12e81cb Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Wed, 17 Oct 2018 18:05:11 -0400 Subject: [PATCH 018/285] CFT Assets: Fix ImageValue struct tags --- cftasset.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cftasset.go b/cftasset.go index 1f3f1cf..e9277e1 100644 --- a/cftasset.go +++ b/cftasset.go @@ -66,8 +66,8 @@ type TextValue string type ImageValue struct { Url string `json:"url,omitempty"` AlternateText string `json:"alternateText,omitempty"` - Height uint64 `json:height,omitempty` - Width uint64 `json:width,omitempty` + Height uint64 `json:"height,omitempty"` + Width uint64 `json:"width,omitempty"` } type ComplexImageValue struct { From 7796080b1b9063509dfb21430983dc60351f7c7b Mon Sep 17 00:00:00 2001 From: cdworak Date: Thu, 18 Oct 2018 09:11:15 -0400 Subject: [PATCH 019/285] fix travis build --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62f9f95..9b23d3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.7 - - 1.8 - - 1.9 - - 1.10 - - 1.11 - - tip + - "1.7" + - "1.8" + - "1.9" + - "1.10" + - "1.11" + - "tip" From 763008918cfe4d5ada6f89b6ba1e60c1affd2510 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Thu, 18 Oct 2018 10:24:02 -0400 Subject: [PATCH 020/285] CFT Assets: Add Asset Diff() function --- cftasset.go | 4 ++++ cftasset_diff.go | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 cftasset_diff.go diff --git a/cftasset.go b/cftasset.go index e9277e1..7428499 100644 --- a/cftasset.go +++ b/cftasset.go @@ -92,3 +92,7 @@ func (a *CFTAsset) GetId() string { } return *a.Id } + +func (a *CFTAsset) GetAssetType() AssetType { + return a.Type +} diff --git a/cftasset_diff.go b/cftasset_diff.go new file mode 100644 index 0000000..0a6df23 --- /dev/null +++ b/cftasset_diff.go @@ -0,0 +1,13 @@ +package yext + +func (a *CFTAsset) Diff(b *CFTAsset) (*CFTAsset, bool) { + if a.GetAssetType() != b.GetAssetType() { + return nil, true + } + + delta, isDiff := diff(a, b, true, true) + if !isDiff { + return nil, isDiff + } + return delta.(*CFTAsset), isDiff +} From 5ef978514abe2e7bc83e8d1071a730f260a9514e Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Thu, 18 Oct 2018 13:49:18 -0400 Subject: [PATCH 021/285] CFT Assets: Asset Diff() fixes --- cftasset.go | 62 ++++++++++++++++++++++++++++++++++++++++++--- cftasset_diff.go | 8 ++++++ cftasset_service.go | 2 +- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/cftasset.go b/cftasset.go index 7428499..c547a1a 100644 --- a/cftasset.go +++ b/cftasset.go @@ -1,5 +1,7 @@ package yext +import "fmt" + type MappingType string const ( @@ -41,7 +43,7 @@ type CFTAsset struct { Type AssetType `json:"type,omitempty"` Description *string `json:"description,omitempty"` ForEntities *ForEntities `json:"forEntities,omitempty"` - Usage *[]AssetUsage `json:"usage,omitempty"` + Usage *AssetUsageList `json:"usage,omitempty"` Locale *string `json:"locale,omitempty"` Labels *UnorderedStrings `json:"labels,omitempty"` Owner *uint64 `json:"owner,omitempty"` @@ -51,14 +53,66 @@ type CFTAsset struct { type ForEntities struct { MappingType MappingType `json:"mappingType,omitempty"` FolderId *string `json:"folderId,omitempty"` - EntityIds *[]string `json:"entityIds,omitempty"` + EntityIds *UnorderedStrings `json:"entityIds,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` LabelOperator LabelOperator `json:"labelOperator,omitempty"` } type AssetUsage struct { - Type UsageType `json:"type,omitempty"` - FieldNames *[]string `json:"fieldNames,omitempty"` + Type UsageType `json:"type,omitempty"` + FieldNames *UnorderedStrings `json:"fieldNames,omitempty"` +} + +func (a *AssetUsage) Equal(b Comparable) bool { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + bAssetUsage := *b.(*AssetUsage) + if a.Type != bAssetUsage.Type || !a.FieldNames.Equal(bAssetUsage.FieldNames) { + return false + } + + return true +} + +type AssetUsageList []AssetUsage + +func (a *AssetUsageList) Equal(b Comparable) bool { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + if a == nil || b == nil { + return false + } + + var ( + u = []AssetUsage(*a) + s = []AssetUsage(*b.(*AssetUsageList)) + ) + if len(u) != len(s) { + return false + } + + for i := 0; i < len(u); i++ { + var found bool + for j := 0; j < len(s); j++ { + if u[i].Equal(&s[j]) { + found = true + } + } + if !found { + return false + } + } + return true } type TextValue string diff --git a/cftasset_diff.go b/cftasset_diff.go index 0a6df23..e8b9c23 100644 --- a/cftasset_diff.go +++ b/cftasset_diff.go @@ -1,6 +1,14 @@ package yext func (a *CFTAsset) Diff(b *CFTAsset) (*CFTAsset, bool) { + if a == nil && b != nil { + return b, true + } else if b == nil && a != nil { + return a, true + } else if a == nil && b == nil { + return nil, false + } + if a.GetAssetType() != b.GetAssetType() { return nil, true } diff --git a/cftasset_service.go b/cftasset_service.go index a0ef864..932bec9 100644 --- a/cftasset_service.go +++ b/cftasset_service.go @@ -47,7 +47,7 @@ func (a *CFTAssetService) toAssetsWithValues(assets []*CFTAsset) error { func (a *CFTAssetService) toAssetWithValue(asset *CFTAsset) error { if asset.Type == ASSETTYPE_TEXT { - asset.Value = asset.Value.(string) + asset.Value = TextValue(asset.Value.(string)) return nil } var assetValueValsByKey = asset.Value.(map[string]interface{}) From 4829e4014c6f2e849e5794bf81d1edca2e886bc3 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Thu, 18 Oct 2018 14:02:07 -0400 Subject: [PATCH 022/285] LocationEntity Diff: Add uint64 Zero value handling for Image height/width fields --- location_diff.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/location_diff.go b/location_diff.go index 8b33bc6..c1af503 100644 --- a/location_diff.go +++ b/location_diff.go @@ -139,6 +139,8 @@ func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { return v.Bool() == false case reflect.String: return v.String() == "" + case reflect.Uint64: + return v.Uint() == 0 case reflect.Float64: return v.Float() == 0.0 case reflect.Ptr, reflect.Interface: From 29b7de71e8f29b1fe0484f85fba5e5c657e90c59 Mon Sep 17 00:00:00 2001 From: cdworak Date: Fri, 19 Oct 2018 09:38:51 -0400 Subject: [PATCH 023/285] Entity Profile Service: Get --- client.go | 4 ++- entity.go | 22 +++++++++++++++ entity_profile.go | 5 ++++ entity_profile_service.go | 35 +++++++++++++++++++++++ entity_registry.go | 58 +++++++++++++++++++++++++++++++++++++++ entity_service.go | 55 ++++--------------------------------- event.go | 2 +- location_entity.go | 2 +- registry.go | 2 +- 9 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 entity_profile.go create mode 100644 entity_profile_service.go create mode 100644 entity_registry.go diff --git a/client.go b/client.go index 0bb7343..2ecfd4d 100644 --- a/client.go +++ b/client.go @@ -38,6 +38,7 @@ type Client struct { CFTAssetService *CFTAssetService AnalyticsService *AnalyticsService EntityService *EntityService + EntityProfileService *EntityProfileService } func NewClient(config *Config) *Client { @@ -57,6 +58,8 @@ func NewClient(config *Config) *Client { c.AnalyticsService = &AnalyticsService{client: c} c.EntityService = &EntityService{client: c} c.EntityService.RegisterDefaultEntities() + c.EntityProfileService = &EntityProfileService{client: c} + c.EntityProfileService.RegisterDefaultEntities() return c } @@ -341,7 +344,6 @@ func addListOptions(requrl string, opts *ListOptions) (string, error) { q.Add("limit", strconv.Itoa(opts.Limit)) } if opts.PageToken != "" { - // TODO: Outstanding techops issue because this seems to have changed to page_token (similar to api_key) q.Add("pageToken", opts.PageToken) } else if opts.Offset != 0 { q.Add("offset", strconv.Itoa(opts.Offset)) diff --git a/entity.go b/entity.go index da62d78..9acf61f 100644 --- a/entity.go +++ b/entity.go @@ -44,3 +44,25 @@ func (b *BaseEntity) GetNilIsEmpty() bool { func (b *BaseEntity) SetNilIsEmpty(val bool) { b.nilIsEmpty = val } + +type RawEntity map[string]interface{} + +func (r *RawEntity) GetEntityId() string { + if m, ok := (*r)["meta"]; ok { + meta := m.(map[string]interface{}) + if id, ok := meta["id"]; ok { + return id.(string) + } + } + return "" +} + +func (r *RawEntity) GetEntityType() EntityType { + if m, ok := (*r)["meta"]; ok { + meta := m.(map[string]interface{}) + if t, ok := meta["entityType"]; ok { + return EntityType(t.(string)) + } + } + return EntityType("") +} diff --git a/entity_profile.go b/entity_profile.go new file mode 100644 index 0000000..0b19e56 --- /dev/null +++ b/entity_profile.go @@ -0,0 +1,5 @@ +package yext + +type EntityProfile struct { + Entity +} diff --git a/entity_profile_service.go b/entity_profile_service.go new file mode 100644 index 0000000..295c176 --- /dev/null +++ b/entity_profile_service.go @@ -0,0 +1,35 @@ +package yext + +import "fmt" + +const entityProfilesPath = "entityprofiles" + +type EntityProfileService struct { + client *Client + registry Registry +} + +func (e *EntityProfileService) RegisterDefaultEntities() { + e.registry = defaultEntityRegistry() +} + +func (e *EntityProfileService) RegisterEntity(t EntityType, entity interface{}) { + e.registry.Register(string(t), entity) +} + +func (e *EntityProfileService) Get(id string, languageCode string) (*EntityProfile, *Response, error) { + var v map[string]interface{} + r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), &v) + if err != nil { + return nil, r, err + } + + entity, err := toEntityType(v, e.registry) + if err != nil { + return nil, r, err + } + + setNilIsEmpty(entity) + + return &EntityProfile{Entity: entity}, r, nil +} diff --git a/entity_registry.go b/entity_registry.go new file mode 100644 index 0000000..21031b6 --- /dev/null +++ b/entity_registry.go @@ -0,0 +1,58 @@ +package yext + +import ( + "encoding/json" + "fmt" +) + +func defaultEntityRegistry() Registry { + registry := make(Registry) + registry.Register(string(ENTITYTYPE_LOCATION), &Location{}) + registry.Register(string(ENTITYTYPE_EVENT), &Event{}) + return registry +} + +func toEntityTypes(entities []interface{}, registry Registry) ([]Entity, error) { + var types = []Entity{} + for _, entityInterface := range entities { + entity, err := toEntityType(entityInterface, registry) + if err != nil { + return nil, err + } + types = append(types, entity) + } + return types, nil +} + +func toEntityType(entity interface{}, registry Registry) (Entity, error) { + // Determine Entity Type + var entityValsByKey = entity.(map[string]interface{}) + meta, ok := entityValsByKey["meta"] + if !ok { + return nil, fmt.Errorf("Unable to find meta attribute in %v\nFor Entity: %v", entityValsByKey, entity) + } + + var metaByKey = meta.(map[string]interface{}) + entityType, ok := metaByKey["entityType"] + if !ok { + return nil, fmt.Errorf("Unable to find entityType attribute in %v\nFor Entity: %v", metaByKey, entity) + } + + entityObj, err := registry.Create(entityType.(string)) + if err != nil { + // Unable to create an instace of entityType, use RawEntity instead + entityObj = &RawEntity{} + } + + // Convert into struct of Entity Type + entityJSON, err := json.Marshal(entityValsByKey) + if err != nil { + return nil, fmt.Errorf("Marshaling entity to JSON: %s\nFor Entity: %v", err, entity) + } + + err = json.Unmarshal(entityJSON, &entityObj) + if err != nil { + return nil, fmt.Errorf("Unmarshaling entity JSON: %s\nFor Entity: %v", err, entity) + } + return entityObj.(Entity), nil +} diff --git a/entity_service.go b/entity_service.go index 4000bb5..5274343 100644 --- a/entity_service.go +++ b/entity_service.go @@ -1,7 +1,6 @@ package yext import ( - "encoding/json" "fmt" "net/url" "reflect" @@ -31,9 +30,7 @@ type EntityListResponse struct { } func (e *EntityService) RegisterDefaultEntities() { - e.registry = make(Registry) - e.RegisterEntity(ENTITYTYPE_LOCATION, &LocationEntity{}) - e.RegisterEntity(ENTITYTYPE_EVENT, &Event{}) + e.registry = defaultEntityRegistry() } func (e *EntityService) RegisterEntity(t EntityType, entity interface{}) { @@ -45,51 +42,13 @@ func (e *EntityService) CreateEntity(t EntityType) (interface{}, error) { } 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 - } - types = append(types, entity) - } - return types, nil + return toEntityTypes(entities, e.registry) } func (e *EntityService) toEntityType(entity interface{}) (Entity, error) { - // Determine Entity Type - var entityValsByKey = entity.(map[string]interface{}) - meta, ok := entityValsByKey["meta"] - if !ok { - return nil, fmt.Errorf("Unable to find meta attribute in %v", entityValsByKey) - } - - var metaByKey = meta.(map[string]interface{}) - entityType, ok := metaByKey["entityType"] - if !ok { - return nil, fmt.Errorf("Unable to find entityType attribute in %v", metaByKey) - } - - // 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("Marshaling entity to JSON: %s", err) - } - - err = json.Unmarshal(entityJSON, &entityObj) - if err != nil { - return nil, fmt.Errorf("Unmarshaling entity JSON: %s", err) - } - return entityObj.(Entity), nil + return toEntityType(entity, e.registry) } -// TODO: Paging is not working here. Waiting on techops // TODO: Add List for SearchID (similar to location-service). Follow up with Techops to see if SearchID is implemented func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { var entities []Entity @@ -105,14 +64,13 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { } entities = append(entities, resp.typedEntites...) - return resp.PageToken, err + return resp.PageToken, nil } if err := tokenListHelper(lg, &opts.ListOptions); err != nil { return nil, err - } else { - return entities, nil } + return entities, nil } func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Response, error) { @@ -151,7 +109,6 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res entities = append(entities, entity) } v.typedEntites = entities - return v, r, nil } @@ -212,7 +169,6 @@ func getNilIsEmpty(i interface{}) bool { return false } -// TODO: Currently an error with API. Need to test this func (e *EntityService) Create(y Entity) (*Response, error) { var requrl = entityPath u, err := url.Parse(requrl) @@ -231,7 +187,6 @@ func (e *EntityService) Create(y Entity) (*Response, error) { return r, nil } -// TODO: There is an outstanding techops QA issue to allow the Id in the request but we may have to remove other things like account func (e *EntityService) Edit(y Entity) (*Response, error) { r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, y.GetEntityId()), y, nil) if err != nil { diff --git a/event.go b/event.go index 5759dd6..62a6fc0 100644 --- a/event.go +++ b/event.go @@ -4,7 +4,7 @@ import ( "encoding/json" ) -const ENTITYTYPE_EVENT EntityType = "EVENT" +const ENTITYTYPE_EVENT EntityType = "event" type EventEntity struct { BaseEntity diff --git a/location_entity.go b/location_entity.go index a80e425..9786d91 100644 --- a/location_entity.go +++ b/location_entity.go @@ -9,7 +9,7 @@ import ( "encoding/json" ) -const ENTITYTYPE_LOCATION EntityType = "LOCATION" +const ENTITYTYPE_LOCATION EntityType = "location" // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm diff --git a/registry.go b/registry.go index 4386172..c273f80 100644 --- a/registry.go +++ b/registry.go @@ -27,7 +27,7 @@ func (r Registry) Register(key string, val interface{}) { func (r Registry) Create(key string) (interface{}, error) { val, ok := r[key] if !ok { - return nil, fmt.Errorf("Unable to find key %s in registry. Known types: %s", key, strings.Join(r.Keys(), ", ")) + return nil, fmt.Errorf("Unable to find key %s in registry. Known keys: %s", key, strings.Join(r.Keys(), ",")) } return reflect.New(reflect.TypeOf(val)).Interface(), nil } From 49d86e0f220c2da2284950171798805cf1345fa5 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Fri, 19 Oct 2018 19:11:38 -0400 Subject: [PATCH 024/285] Location Entity: Change Closed field to bool to match API response --- cftasset.go | 90 +++++++++++++++++++++++----------------------- location_entity.go | 4 +-- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/cftasset.go b/cftasset.go index c547a1a..6b284f9 100644 --- a/cftasset.go +++ b/cftasset.go @@ -43,7 +43,7 @@ type CFTAsset struct { Type AssetType `json:"type,omitempty"` Description *string `json:"description,omitempty"` ForEntities *ForEntities `json:"forEntities,omitempty"` - Usage *AssetUsageList `json:"usage,omitempty"` + Usage *AssetUsageList `json:"usage,omitempty"` Locale *string `json:"locale,omitempty"` Labels *UnorderedStrings `json:"labels,omitempty"` Owner *uint64 `json:"owner,omitempty"` @@ -64,55 +64,55 @@ type AssetUsage struct { } func (a *AssetUsage) Equal(b Comparable) bool { - defer func() { - if r := recover(); r != nil { - fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) - panic(r) - } - }() - - bAssetUsage := *b.(*AssetUsage) - if a.Type != bAssetUsage.Type || !a.FieldNames.Equal(bAssetUsage.FieldNames) { - return false - } - - return true + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + bAssetUsage := *b.(*AssetUsage) + if a.Type != bAssetUsage.Type || !a.FieldNames.Equal(bAssetUsage.FieldNames) { + return false + } + + return true } type AssetUsageList []AssetUsage func (a *AssetUsageList) Equal(b Comparable) bool { - defer func() { - if r := recover(); r != nil { - fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) - panic(r) - } - }() - - if a == nil || b == nil { - return false - } - - var ( - u = []AssetUsage(*a) - s = []AssetUsage(*b.(*AssetUsageList)) - ) - if len(u) != len(s) { - return false - } - - for i := 0; i < len(u); i++ { - var found bool - for j := 0; j < len(s); j++ { - if u[i].Equal(&s[j]) { - found = true - } - } - if !found { - return false - } - } - return true + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + if a == nil || b == nil { + return false + } + + var ( + u = []AssetUsage(*a) + s = []AssetUsage(*b.(*AssetUsageList)) + ) + if len(u) != len(s) { + return false + } + + for i := 0; i < len(u); i++ { + var found bool + for j := 0; j < len(s); j++ { + if u[i].Equal(&s[j]) { + found = true + } + } + if !found { + return false + } + } + return true } type TextValue string diff --git a/location_entity.go b/location_entity.go index 9786d91..e876419 100644 --- a/location_entity.go +++ b/location_entity.go @@ -20,7 +20,7 @@ type LocationEntity struct { FolderId *string `json:"folderId,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` CategoryIds *[]string `json:"categoryIds,omitempty"` - Closed *LocationClosed `json:"closed,omitempty"` + Closed *bool `json:"closed,omitempty"` Keywords *[]string `json:"keywords,omitempty"` Language *string `json:"language,omitempty"` @@ -656,7 +656,7 @@ func (y LocationEntity) GetHolidayHours() []HolidayHours { } func (y LocationEntity) IsClosed() bool { - if y.Closed != nil && y.Closed.IsClosed { + if y.Closed != nil && *y.Closed { return true } return false From 8dda0986c77c32695a5a72b85f39b4ff8dfe911d Mon Sep 17 00:00:00 2001 From: cdworak Date: Fri, 26 Oct 2018 13:44:02 -0400 Subject: [PATCH 025/285] entity-profile-service --- entity_profile_service.go | 49 +++++++++++++++++- location_entity.go | 106 +------------------------------------- 2 files changed, 50 insertions(+), 105 deletions(-) diff --git a/entity_profile_service.go b/entity_profile_service.go index 295c176..287bb1d 100644 --- a/entity_profile_service.go +++ b/entity_profile_service.go @@ -1,6 +1,9 @@ package yext -import "fmt" +import ( + "fmt" + "log" +) const entityProfilesPath = "entityprofiles" @@ -9,6 +12,10 @@ type EntityProfileService struct { registry Registry } +type EntityProfileListResponse struct { + Profiles []*EntityProfile `json:"profiles"` +} + func (e *EntityProfileService) RegisterDefaultEntities() { e.registry = defaultEntityRegistry() } @@ -33,3 +40,43 @@ func (e *EntityProfileService) Get(id string, languageCode string) (*EntityProfi return &EntityProfile{Entity: entity}, r, nil } + +func (e *EntityProfileService) List(id string) ([]*EntityProfile, *Response, error) { + var v EntityProfileListResponse + r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityProfilesPath, id), &v) + if err != nil { + return nil, r, err + } + + log.Println(len(v.Profiles)) + + // entity, err := toEntityType(v, e.registry) + // if err != nil { + // return nil, r, err + // } + // + // setNilIsEmpty(entity) + + return nil, r, nil +} + +func (e *EntityProfileService) Upsert(entity Entity, languageCode string) (*Response, error) { + id := entity.GetEntityId() + if id == "" { + return nil, fmt.Errorf("entity profile service upsert: profile object had no id") + } + + r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), entity, nil) + if err != nil { + return r, err + } + return r, nil +} + +func (e *EntityProfileService) Delete(id string, languageCode string) (*Response, error) { + r, err := e.client.DoRequest("DELETE", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), nil) + if err != nil { + return r, err + } + return r, nil +} diff --git a/location_entity.go b/location_entity.go index e876419..acf8817 100644 --- a/location_entity.go +++ b/location_entity.go @@ -24,9 +24,6 @@ type LocationEntity struct { Keywords *[]string `json:"keywords,omitempty"` Language *string `json:"language,omitempty"` - // hydrated bool - // nilIsEmpty bool - // Address Fields Name *string `json:"name,omitempty"` Address *Address `json:"address,omitempty"` @@ -45,20 +42,6 @@ type LocationEntity struct { IsPhoneTracked *bool `json:"isPhoneTracked,omitempty"` Emails *[]string `json:"emails,omitempty"` - // HealthCare fields - FirstName *string `json:"firstName,omitempty"` - MiddleName *string `json:"middleName,omitempty"` - LastName *string `json:"lastName,omitempty"` - Gender *string `json:"gender,omitempty"` - Headshot *LocationPhoto `json:"headshot,omitempty"` - AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` - AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` - ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` - InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` - NPI *string `json:"npi,omitempty"` - OfficeName *string `json:"officeName,omitempty"` - Degrees *[]string `json:"degrees,omitempty"` - // Location Info Description *string `json:"description,omitempty"` Hours *Hours `json:"hours,omitempty"` @@ -130,21 +113,6 @@ type LocationEntity struct { // Reviews ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` - - /** TODO(bmcginnis) add the following fields: - - ServiceArea struct { - Places *[]string `json:"places,omitempty"` - Radius *int `json:"radius,omitempty"` - Unit *string `json:"unit,omitempty"` - } `json:"serviceArea,omitempty"` - - EducationList []struct { - InstitutionName *string `json:"institutionName,omitempty"` - Type *string `json:"type,omitempty"` - YearCompleted *string `json:"yearCompleted,omitempty"` - } `json:"educationList,omitempty"` - */ } type Address struct { @@ -207,69 +175,6 @@ func (y LocationEntity) GetName() string { return "" } -func (y LocationEntity) GetFirstName() string { - if y.FirstName != nil { - return *y.FirstName - } - return "" -} - -func (y LocationEntity) GetMiddleName() string { - if y.MiddleName != nil { - return *y.MiddleName - } - return "" -} - -func (y LocationEntity) GetLastName() string { - if y.LastName != nil { - return *y.LastName - } - return "" -} - -func (y LocationEntity) GetGender() string { - if y.Gender != nil { - return *y.Gender - } - return "" -} - -func (y LocationEntity) GetAcceptingNewPatients() bool { - if y.AcceptingNewPatients != nil { - return *y.AcceptingNewPatients - } - return false -} - -func (y LocationEntity) GetCertifications() []string { - if y.Certifications != nil { - return *y.Certifications - } - return nil -} - -func (y LocationEntity) GetNPI() string { - if y.NPI != nil { - return *y.NPI - } - return "" -} - -func (y LocationEntity) GetOfficeName() string { - if y.OfficeName != nil { - return *y.OfficeName - } - return "" -} - -func (y LocationEntity) GetDegrees() []string { - if y.Degrees != nil { - return *y.Degrees - } - return nil -} - func (y LocationEntity) GetAccountId() string { if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { return *y.BaseEntity.Meta.AccountId @@ -634,13 +539,6 @@ func (y LocationEntity) GetVideoUrls() (v []string) { return v } -func (y LocationEntity) GetAdmittingHospitals() (v []string) { - if y.AdmittingHospitals != nil { - v = *y.AdmittingHospitals - } - return v -} - func (y LocationEntity) GetGoogleAttributes() GoogleAttributes { if y.GoogleAttributes != nil { return *y.GoogleAttributes @@ -656,8 +554,8 @@ func (y LocationEntity) GetHolidayHours() []HolidayHours { } func (y LocationEntity) IsClosed() bool { - if y.Closed != nil && *y.Closed { - return true + if y.Closed != nil { + return *y.Closed } return false } From 3211cde58bfd9c280cfea1d0a1373d791cbe31ab Mon Sep 17 00:00:00 2001 From: cdworak Date: Fri, 26 Oct 2018 13:40:11 -0400 Subject: [PATCH 026/285] entity-profile-service --- entity_profile_service.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/entity_profile_service.go b/entity_profile_service.go index 287bb1d..f9484eb 100644 --- a/entity_profile_service.go +++ b/entity_profile_service.go @@ -2,7 +2,6 @@ package yext import ( "fmt" - "log" ) const entityProfilesPath = "entityprofiles" @@ -13,7 +12,7 @@ type EntityProfileService struct { } type EntityProfileListResponse struct { - Profiles []*EntityProfile `json:"profiles"` + Profiles []interface{} `json:"profiles"` } func (e *EntityProfileService) RegisterDefaultEntities() { @@ -35,29 +34,30 @@ func (e *EntityProfileService) Get(id string, languageCode string) (*EntityProfi if err != nil { return nil, r, err } - setNilIsEmpty(entity) return &EntityProfile{Entity: entity}, r, nil } func (e *EntityProfileService) List(id string) ([]*EntityProfile, *Response, error) { - var v EntityProfileListResponse + var ( + v EntityProfileListResponse + profiles = []*EntityProfile{} + ) r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityProfilesPath, id), &v) if err != nil { return nil, r, err } - log.Println(len(v.Profiles)) - - // entity, err := toEntityType(v, e.registry) - // if err != nil { - // return nil, r, err - // } - // - // setNilIsEmpty(entity) - - return nil, r, nil + typedProfiles, err := toEntityTypes(v.Profiles, e.registry) + if err != nil { + return nil, r, err + } + for _, profile := range typedProfiles { + setNilIsEmpty(profile) + profiles = append(profiles, &EntityProfile{Entity: profile}) + } + return profiles, r, nil } func (e *EntityProfileService) Upsert(entity Entity, languageCode string) (*Response, error) { From 7d46c71983e24f7804c87233c563ec6f8be34488 Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Tue, 30 Oct 2018 11:28:46 -0400 Subject: [PATCH 027/285] Update location.go to include ISORegionCode (#66) R=cdworak,usaha --- location.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/location.go b/location.go index 8d32ee5..4785c7b 100644 --- a/location.go +++ b/location.go @@ -39,6 +39,7 @@ type Location struct { Zip *string `json:"zip,omitempty"` CountryCode *string `json:"countryCode,omitempty"` SuppressAddress *bool `json:"suppressAddress,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -272,6 +273,13 @@ func (y Location) GetSuppressAddress() bool { return false } +func (y Location) GetISORegionCode() string { + if y.ISORegionCode != nil { + return *y.ISORegionCode + } + return "" +} + func (y Location) GetDisplayAddress() string { if y.DisplayAddress != nil { return *y.DisplayAddress From 2299d770760914e9b523a85b9e22355d6c3e80c1 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Mon, 19 Nov 2018 10:59:42 -0500 Subject: [PATCH 028/285] Types: Add Getters for easy type dereferencing --- type.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/type.go b/type.go index dfabcd5..ff2c01b 100644 --- a/type.go +++ b/type.go @@ -6,28 +6,48 @@ func Bool(v bool) *bool { return p } +func GetBool(v *bool) bool { + return *v +} + func String(v string) *string { p := new(string) *p = v return p } +func GetString(v *string) string { + return *v +} + func Float(v float64) *float64 { p := new(float64) *p = v return p } +func GetFloat(v *float64) float64 { + return *v +} + func Int(v int) *int { p := new(int) *p = v return p } +func GetInt(v *int) int { + return *v +} + func Strings(v []string) *[]string { return &v } +func GetStrings(v *[]string) []string { + return *v +} + func ToUnorderedStrings(v []string) *UnorderedStrings { u := UnorderedStrings(v) return &u From fbbcc2f54b2ac0ca4b83fd9612a78582f8c83c09 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Mon, 19 Nov 2018 11:36:39 -0500 Subject: [PATCH 029/285] Types: Add Getter handling to return default primitive values for nil fields --- type.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/type.go b/type.go index ff2c01b..cc4ef2b 100644 --- a/type.go +++ b/type.go @@ -7,6 +7,9 @@ func Bool(v bool) *bool { } func GetBool(v *bool) bool { + if v == nil { + return false + } return *v } @@ -17,6 +20,9 @@ func String(v string) *string { } func GetString(v *string) string { + if v == nil { + return "" + } return *v } @@ -27,6 +33,9 @@ func Float(v float64) *float64 { } func GetFloat(v *float64) float64 { + if v == nil { + return 0 + } return *v } @@ -37,6 +46,9 @@ func Int(v int) *int { } func GetInt(v *int) int { + if v == nil { + return 0 + } return *v } @@ -45,6 +57,9 @@ func Strings(v []string) *[]string { } func GetStrings(v *[]string) []string { + if v == nil { + return []string{} + } return *v } From 799c8956b0d290a34e4c609380bcc6ee2afd86e1 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Tue, 20 Nov 2018 09:44:21 -0500 Subject: [PATCH 030/285] CTF Assets: Remove ImageValue height/width fields as they aren't settable through API --- cftasset.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cftasset.go b/cftasset.go index 6b284f9..38588e6 100644 --- a/cftasset.go +++ b/cftasset.go @@ -120,8 +120,6 @@ type TextValue string type ImageValue struct { Url string `json:"url,omitempty"` AlternateText string `json:"alternateText,omitempty"` - Height uint64 `json:"height,omitempty"` - Width uint64 `json:"width,omitempty"` } type ComplexImageValue struct { From adc969c8d6e0b6ef4c8b2fabd7f2ddc3410cda8f Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Mon, 3 Dec 2018 17:44:35 -0500 Subject: [PATCH 031/285] Approval Groups: Add endpoints and request functions --- approvalgroups.go | 36 ++++++++++++++++++ approvalgroups_service.go | 79 +++++++++++++++++++++++++++++++++++++++ client.go | 2 + 3 files changed, 117 insertions(+) create mode 100644 approvalgroups.go create mode 100644 approvalgroups_service.go diff --git a/approvalgroups.go b/approvalgroups.go new file mode 100644 index 0000000..206b73d --- /dev/null +++ b/approvalgroups.go @@ -0,0 +1,36 @@ +package yext + +type ApprovalGroup struct { + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` + Users *UnorderedStrings `json:"users,omitempty"` +} + +func (a *ApprovalGroup) GetId() string { + if a.Id == nil { + return "" + } + return *a.Id +} + +func (a *ApprovalGroup) GetName() string { + if a.Name == nil { + return "" + } + return *a.Name +} + +func (a *ApprovalGroup) GetIsDefault() bool { + if a.IsDefault == nil { + return false + } + return *a.IsDefault +} + +func (a *ApprovalGroup) GetUsers() (v UnorderedStrings) { + if a.Users != nil { + v = *a.Users + } + return v +} diff --git a/approvalgroups_service.go b/approvalgroups_service.go new file mode 100644 index 0000000..a6e0835 --- /dev/null +++ b/approvalgroups_service.go @@ -0,0 +1,79 @@ +package yext + +import ( + "fmt" +) + +const approvalGroupsPath = "approvalgroups" + +type ApprovalGroupsService struct { + client *Client +} + +type ApprovalGroupsListReponse struct { + Count int `json:"count"` + ApprovalGroups []*ApprovalGroup `json:"approvalGroups"` + NextPageToken string `json:"nextPageToken"` +} + +func (a *ApprovalGroupsService) ListAll() ([]*ApprovalGroup, error) { + var approvalGroups []*ApprovalGroup + + var al tokenListRetriever = func(opts *ListOptions) (string, error) { + alr, _, err := a.List(opts) + if err != nil { + return "", err + } + approvalGroups = append(approvalGroups, alr.ApprovalGroups...) + return alr.NextPageToken, err + } + + if err := tokenListHelper(al, nil); err != nil { + return nil, err + } else { + return approvalGroups, nil + } +} + +func (a *ApprovalGroupsService) List(opts *ListOptions) (*ApprovalGroupsListReponse, *Response, error) { + requrl, err := addListOptions(approvalGroupsPath, opts) + if err != nil { + return nil, nil, err + } + + v := &ApprovalGroupsListReponse{} + r, err := a.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + return v, r, nil +} + +func (a *ApprovalGroup) pathToApprovalGroup() string { + return pathToApprovalGroupId(a.GetId()) +} + +func pathToApprovalGroupId(id string) string { + return fmt.Sprintf("%s/%s", approvalGroupsPath, id) +} + +func (a *ApprovalGroupsService) Get(id string) (*ApprovalGroup, *Response, error) { + var v = &ApprovalGroup{} + r, err := a.client.DoRequest("GET", pathToApprovalGroupId(id), v) + if err != nil { + return nil, r, err + } + return v, r, nil +} + +func (a *ApprovalGroupsService) Edit(y *ApprovalGroup) (*Response, error) { + return a.client.DoRequestJSON("PUT", y.pathToApprovalGroup(), y, nil) +} + +func (a *ApprovalGroupsService) Create(y *ApprovalGroup) (*Response, error) { + return a.client.DoRequestJSON("POST", y.pathToApprovalGroup(), y, nil) +} + +func (a *ApprovalGroupsService) Delete(y *ApprovalGroup) (*Response, error) { + return a.client.DoRequest("DELETE", y.pathToApprovalGroup(), nil) +} diff --git a/client.go b/client.go index 2ecfd4d..9487127 100644 --- a/client.go +++ b/client.go @@ -32,6 +32,7 @@ type Client struct { FolderService *FolderService CategoryService *CategoryService UserService *UserService + ApprovalGroupsService *ApprovalGroupsService ReviewService *ReviewService LanguageProfileService *LanguageProfileService AssetService *AssetService @@ -50,6 +51,7 @@ func NewClient(config *Config) *Client { c.FolderService = &FolderService{client: c} c.CategoryService = &CategoryService{client: c} c.UserService = &UserService{client: c} + c.ApprovalGroupsService = &ApprovalGroupsService{client: c} c.ReviewService = &ReviewService{client: c} c.LanguageProfileService = &LanguageProfileService{client: c} c.AssetService = &AssetService{client: c} From 2cd0752fd68a22eab4111471cf14ca8ff69d6dc9 Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Mon, 10 Dec 2018 13:14:04 -0500 Subject: [PATCH 032/285] Location Entity: Restructure Location entity to match current meta JSON response --- entity.go | 2 +- location_entity.go | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/entity.go b/entity.go index 9acf61f..18330b3 100644 --- a/entity.go +++ b/entity.go @@ -12,7 +12,7 @@ type EntityMeta struct { AccountId *string `json:"accountId,omitempty"` EntityType EntityType `json:"entityType,omitempty"` FolderId *string `json:"folderId,omitempty"` - LabelIds *UnorderedStrings `json:"labelIds,omitempty"` + Labels *UnorderedStrings `json:"labels,omitempty"` CategoryIds *[]string `json:"categoryIds,omitempty"` Language *string `json:"language,omitempty"` CountryCode *string `json:"countryCode,omitempty"` diff --git a/location_entity.go b/location_entity.go index acf8817..5603848 100644 --- a/location_entity.go +++ b/location_entity.go @@ -17,12 +17,9 @@ type LocationEntity struct { BaseEntity // Admin - FolderId *string `json:"folderId,omitempty"` - LabelIds *UnorderedStrings `json:"labelIds,omitempty"` - CategoryIds *[]string `json:"categoryIds,omitempty"` - Closed *bool `json:"closed,omitempty"` - Keywords *[]string `json:"keywords,omitempty"` - Language *string `json:"language,omitempty"` + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed *bool `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` // Address Fields Name *string `json:"name,omitempty"` @@ -447,8 +444,8 @@ func (y LocationEntity) GetKeywords() (v []string) { } func (y LocationEntity) GetLanguage() (v string) { - if y.Language != nil { - v = *y.Language + if y.BaseEntity.Meta.Language != nil { + v = *y.BaseEntity.Meta.Language } return v } @@ -496,8 +493,8 @@ func (y LocationEntity) GetLanguages() (v []string) { } func (y LocationEntity) GetFolderId() string { - if y.FolderId != nil { - return *y.FolderId + if y.BaseEntity.Meta.FolderId != nil { + return *y.BaseEntity.Meta.FolderId } return "" } @@ -509,20 +506,20 @@ func (y LocationEntity) GetCategoryIds() (v []string) { return v } -func (y LocationEntity) GetLabelIds() (v UnorderedStrings) { - if y.LabelIds != nil { - v = *y.LabelIds +func (y LocationEntity) GetLabels() (v UnorderedStrings) { + if y.BaseEntity.Meta.Labels != nil { + v = *y.BaseEntity.Meta.Labels } return v } -func (y LocationEntity) SetLabelIds(v []string) { +func (y LocationEntity) SetLabels(v []string) { l := UnorderedStrings(v) - y.SetLabelIdsWithUnorderedStrings(l) + y.SetLabelsWithUnorderedStrings(l) } -func (y LocationEntity) SetLabelIdsWithUnorderedStrings(v UnorderedStrings) { - y.LabelIds = &v +func (y LocationEntity) SetLabelsWithUnorderedStrings(v UnorderedStrings) { + y.BaseEntity.Meta.Labels = &v } func (y LocationEntity) GetPaymentOptions() (v []string) { From 841461cfb6178185c2793c4ad570e0291c96209a Mon Sep 17 00:00:00 2001 From: Upal Saha Date: Mon, 10 Dec 2018 14:21:15 -0500 Subject: [PATCH 033/285] Base Entity: Move Entity Meta getters from Location Entity and into Base Entity --- entity.go | 30 ++++++++++++++++++++++++++++++ location_entity.go | 30 ------------------------------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/entity.go b/entity.go index 18330b3..0f7ebe6 100644 --- a/entity.go +++ b/entity.go @@ -37,6 +37,36 @@ func (b *BaseEntity) GetEntityType() EntityType { return "" } +func (b *BaseEntity) GetFolderId() string { + if b.Meta.FolderId != nil { + return *b.Meta.FolderId + } + return "" +} + +func (b *BaseEntity) GetCategoryIds() (v []string) { + if b.Meta.CategoryIds != nil { + v = *b.Meta.CategoryIds + } + return v +} + +func (b *BaseEntity) GetLabels() (v UnorderedStrings) { + if b.Meta.Labels != nil { + v = *b.Meta.Labels + } + return v +} + +func (b *BaseEntity) SetLabels(v []string) { + l := UnorderedStrings(v) + b.SetLabelsWithUnorderedStrings(l) +} + +func (b *BaseEntity) SetLabelsWithUnorderedStrings(v UnorderedStrings) { + b.Meta.Labels = &v +} + func (b *BaseEntity) GetNilIsEmpty() bool { return b.nilIsEmpty } diff --git a/location_entity.go b/location_entity.go index 5603848..36ba630 100644 --- a/location_entity.go +++ b/location_entity.go @@ -492,36 +492,6 @@ func (y LocationEntity) GetLanguages() (v []string) { return v } -func (y LocationEntity) GetFolderId() string { - if y.BaseEntity.Meta.FolderId != nil { - return *y.BaseEntity.Meta.FolderId - } - return "" -} - -func (y LocationEntity) GetCategoryIds() (v []string) { - if y.CategoryIds != nil { - v = *y.CategoryIds - } - return v -} - -func (y LocationEntity) GetLabels() (v UnorderedStrings) { - if y.BaseEntity.Meta.Labels != nil { - v = *y.BaseEntity.Meta.Labels - } - return v -} - -func (y LocationEntity) SetLabels(v []string) { - l := UnorderedStrings(v) - y.SetLabelsWithUnorderedStrings(l) -} - -func (y LocationEntity) SetLabelsWithUnorderedStrings(v UnorderedStrings) { - y.BaseEntity.Meta.Labels = &v -} - func (y LocationEntity) GetPaymentOptions() (v []string) { if y.PaymentOptions != nil { v = *y.PaymentOptions From e516b17e92896ba886098a849f634b7211b00f21 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 17 Dec 2018 14:58:49 -0500 Subject: [PATCH 034/285] Id swap * location edit: specify which location to update; diff: allow id comparison * location-service: Add new EdithWithId frunction --- location_diff.go | 2 +- location_diff_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++ location_service.go | 6 +++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/location_diff.go b/location_diff.go index c1af503..a0e7a36 100644 --- a/location_diff.go +++ b/location_diff.go @@ -47,7 +47,7 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { continue } - if valB.IsNil() || nameA == "Id" { + if valB.IsNil() { continue } diff --git a/location_diff_test.go b/location_diff_test.go index c63c6ac..d6ee51f 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -1202,6 +1202,73 @@ func TestComplexIdentical(t *testing.T) { } } +func TestDiffIds(t *testing.T) { + tests := []struct { + BaseLoc *Location + NewLoc *Location + IsDiff bool + Delta *Location + }{ + { + BaseLoc: &Location{ + Id: String("1"), + }, + NewLoc: &Location{ + Id: String("2"), + }, + IsDiff: true, + Delta: &Location{ + Id: String("2"), + }, + }, + { + BaseLoc: &Location{ + Id: String("1"), + }, + NewLoc: &Location{ + Id: String("1"), + }, + IsDiff: false, + }, + { + BaseLoc: &Location{ + Id: String("1"), + }, + NewLoc: &Location{ + Id: String("2"), + Name: String("New Name"), + }, + IsDiff: true, + Delta: &Location{ + Id: String("2"), + Name: String("New Name"), + }, + }, + { + BaseLoc: &Location{ + Id: String("1"), + }, + NewLoc: &Location{ + Id: String("1"), + Name: String("New Name"), + }, + IsDiff: true, + Delta: &Location{ + Id: String("1"), // Historically, even though there is no diff in the Id, we always want the Id set + Name: String("New Name"), + }, + }, + } + for _, test := range tests { + delta, isDiff := test.BaseLoc.Diff(test.NewLoc) + if isDiff != test.IsDiff { + t.Errorf("Expected %t but was %t, delta was:\n%v\n", test.IsDiff, isDiff, delta) + } else if test.IsDiff && !reflect.DeepEqual(test.Delta, delta) { + t.Errorf("Expected %v for delta, delta was \n%v\n", test.Delta, delta) + } + } +} + func TestUnmarshal(t *testing.T) { var one, two = new(Location), new(Location) err := json.Unmarshal([]byte(jsonData), one) diff --git a/location_service.go b/location_service.go index 709ed36..29e22c6 100644 --- a/location_service.go +++ b/location_service.go @@ -135,10 +135,14 @@ func addGetOptions(requrl string, opts *LocationListOptions) (string, error) { } func (l *LocationService) Edit(y *Location) (*Response, error) { + return l.EditWithId(y.GetId(), y) +} + +func (l *LocationService) EditWithId(id string, y *Location) (*Response, error) { if err := validateLocationCustomFieldsKeys(y.CustomFields); err != nil { return nil, err } - r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", locationsPath, y.GetId()), y, nil) + r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", locationsPath, id), y, nil) if err != nil { return r, err } From 7c9167c4947b0143313c506c9f21d5037983164f Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 2 Jan 2019 17:13:40 -0500 Subject: [PATCH 035/285] customfield-service: Add MustSetPhoto and MustUnsetPhoto --- customfield_service.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/customfield_service.go b/customfield_service.go index e09999f..779213c 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -793,6 +793,14 @@ func (c *CustomFieldManager) UnsetPhoto(name string, loc *Location) error { return c.SetPhoto(name, UnsetPhotoValue, loc) } +func (c *CustomFieldManager) MustSetPhoto(name string, v *Photo, loc *Location) { + Must(c.SetPhoto(name, v, loc)) +} + +func (c *CustomFieldManager) MustUnsetPhoto(name string, loc *Location) { + Must(c.SetPhoto(name, UnsetPhotoValue, loc)) +} + func GetSingleOptionPointer(option SingleOption) *SingleOption { return &option } From e2b54ed80723f9844b2f8444a78c21940b376c9b Mon Sep 17 00:00:00 2001 From: Ben Haines Date: Sun, 13 Jan 2019 16:42:50 -0500 Subject: [PATCH 036/285] Add support for more location fields (#73) --- location.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/location.go b/location.go index 4785c7b..619f47d 100644 --- a/location.go +++ b/location.go @@ -210,6 +210,13 @@ func (y Location) GetGender() string { return "" } +func (y Location) GetHeadshot() (v LocationPhoto) { + if y.Headshot != nil { + v = *y.Headshot + } + return v +} + func (y Location) GetAcceptingNewPatients() bool { if y.AcceptingNewPatients != nil { return *y.AcceptingNewPatients @@ -434,6 +441,20 @@ func (y Location) GetTwitterHandle() string { return "" } +func (y Location) GetFacebookCoverPhoto() (v LocationPhoto) { + if y.FacebookCoverPhoto != nil { + v = *y.FacebookCoverPhoto + } + return v +} + +func (y Location) GetFacebookProfilePicture() (v LocationPhoto) { + if y.FacebookProfilePicture != nil { + v = *y.FacebookProfilePicture + } + return v +} + func (y Location) GetFacebookPageUrl() string { if y.FacebookPageUrl != nil { return *y.FacebookPageUrl @@ -586,6 +607,13 @@ func (y Location) GetLanguages() (v []string) { return v } +func (y Location) GetLogo() (v LocationPhoto) { + if y.Logo != nil { + v = *y.Logo + } + return v +} + func (y Location) GetLabelIds() (v UnorderedStrings) { if y.LabelIds != nil { v = *y.LabelIds @@ -616,6 +644,13 @@ func (y Location) GetPaymentOptions() (v []string) { return v } +func (y Location) GetPhotos() (v []LocationPhoto) { + if y.Photos != nil { + v = *y.Photos + } + return v +} + func (y Location) GetVideoUrls() (v []string) { if y.VideoUrls != nil { v = *y.VideoUrls @@ -630,6 +665,20 @@ func (y Location) GetAdmittingHospitals() (v []string) { return v } +func (y Location) GetConditionsTreated() (v []string) { + if y.ConditionsTreated != nil { + v = *y.ConditionsTreated + } + return v +} + +func (y Location) GetInsuranceAccepted() (v []string) { + if y.InsuranceAccepted != nil { + v = *y.InsuranceAccepted + } + return v +} + func (y Location) GetGoogleAttributes() GoogleAttributes { if y.GoogleAttributes != nil { return *y.GoogleAttributes From cd3c6921950a439ed21cab0368e73e7fcff69513 Mon Sep 17 00:00:00 2001 From: cdworak Date: Thu, 17 Jan 2019 16:25:23 -0500 Subject: [PATCH 037/285] Add EntityAvailability to customfield --- customfield.go | 1 + 1 file changed, 1 insertion(+) diff --git a/customfield.go b/customfield.go index c4dc299..a7ee63d 100644 --- a/customfield.go +++ b/customfield.go @@ -44,6 +44,7 @@ type CustomField struct { Group string `json:"group"` Description string `json:"description"` AlternateLanguageBehaviour string `json:"alternateLanguageBehavior"` + EntityAvailability []string `json:"entityAvailability"` } func (c CustomField) GetId() string { From 6c20ed7b9d54ff25e9275945de7d33850707d69f Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Fri, 18 Jan 2019 15:05:57 -0500 Subject: [PATCH 038/285] Added educationList to location.go R=cdworak,bharvey (#71) --- location.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 133 insertions(+), 11 deletions(-) diff --git a/location.go b/location.go index 619f47d..f334c09 100644 --- a/location.go +++ b/location.go @@ -145,19 +145,14 @@ type Location struct { ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` + EducationList *EducationList `json:"educationList,omitempty"` /** TODO(bmcginnis) add the following fields: - ServiceArea struct { - Places *[]string `json:"places,omitempty"` - Radius *int `json:"radius,omitempty"` - Unit *string `json:"unit,omitempty"` - } `json:"serviceArea,omitempty"` - - EducationList []struct { - InstitutionName *string `json:"institutionName,omitempty"` - Type *string `json:"type,omitempty"` - YearCompleted *string `json:"yearCompleted,omitempty"` - } `json:"educationList,omitempty"` + ServiceArea struct { + Places *[]string `json:"places,omitempty"` + Radius *int `json:"radius,omitempty"` + Unit *string `json:"unit,omitempty"` + } `json:"serviceArea,omitempty"` */ } @@ -686,6 +681,13 @@ func (y Location) GetGoogleAttributes() GoogleAttributes { return nil } +func (y Location) GetEducationList() EducationList { + if y.EducationList != nil { + return *y.EducationList + } + return nil +} + func (y Location) GetHolidayHours() []LocationHolidayHours { if y.HolidayHours != nil { return *y.HolidayHours @@ -700,6 +702,100 @@ func (y Location) IsClosed() bool { return false } +//Education is an entry in EducationList which represents a location's (person's) education history +type Education struct { + InstitutionName string `json:"institutionName,omitempty"` + Type string `json:"type,omitempty"` + YearCompleted string `json:"yearCompleted,omitempty"` +} + +func (e Education) String() string { + return fmt.Sprintf("Institution Name: '%v', Type: '%v', Year Completed: '%v'", e.InstitutionName, e.Type, e.YearCompleted) +} + +// Equal compares Education +func (a *Education) Equal(b Comparable) bool { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + if a == nil || b == nil { + return false + } + + var ( + u = Education(*a) + s = Education(*b.(*Education)) + ) + if u.InstitutionName != s.InstitutionName { + return false + } + + if u.Type != s.Type { + return false + } + + if u.YearCompleted != s.YearCompleted { + return false + } + + return true +} + +type EducationList []*Education + +func (e EducationList) String() string { + var ret string + + for i, education := range e { + if i == 0 { + ret = education.String() + continue + } + ret = fmt.Sprintf("%s; %s", ret, education.String()) + } + + return ret +} + +// Equal compares EducationList +func (a *EducationList) Equal(b Comparable) bool { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + if a == nil || b == nil { + return false + } + + var ( + u = []*Education(*a) + s = []*Education(*b.(*EducationList)) + ) + if len(u) != len(s) { + return false + } + + for i := 0; i < len(u); i++ { + var found bool + for j := 0; j < len(s); j++ { + if u[i].Equal(s[j]) { + found = true + } + } + if !found { + return false + } + } + return true +} + // LocationPhoto represents a photo associated with a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Photo type LocationPhoto struct { @@ -709,6 +805,10 @@ type LocationPhoto struct { ClickThroughUrl string `json:"clickthroughUrl,omitempty"` } +func (l LocationPhoto) String() string { + return fmt.Sprintf("Url: '%v', Description: '%v', AlternateText: '%v', ClickThroughUrl: '%v'", l.Url, l.Description, l.AlternateText, l.ClickThroughUrl) +} + func (l Photo) String() string { return fmt.Sprintf("Url: '%v', Description: '%v'", l.Url, l.Description) } @@ -731,6 +831,10 @@ type LocationHolidayHours struct { Hours string `json:"hours"` } +func (l LocationHolidayHours) String() string { + return fmt.Sprintf("Date: '%v', Hours: '%v'", l.Date, l.Hours) +} + // UnorderedStrings masks []string properties for which Order doesn't matter, such as LabelIds type UnorderedStrings []string @@ -774,6 +878,10 @@ type GoogleAttribute struct { OptionIds *[]string `json:"optionIds,omitempty"` } +func (g GoogleAttribute) String() string { + return fmt.Sprintf("Id: '%v', OptionIds: '%v'", g.Id, g.OptionIds) +} + // Equal compares GoogleAttribute func (a *GoogleAttribute) Equal(b Comparable) bool { defer func() { @@ -818,6 +926,20 @@ func (a *GoogleAttribute) Equal(b Comparable) bool { type GoogleAttributes []*GoogleAttribute +func (g GoogleAttributes) String() string { + var ret string + + for i, googleAttr := range g { + if i == 0 { + ret = googleAttr.String() + continue + } + ret = fmt.Sprintf("%s; %s", ret, googleAttr.String()) + } + + return ret +} + // Equal compares GoogleAttributes func (a *GoogleAttributes) Equal(b Comparable) bool { defer func() { From 2702f1fdea30c052ea3977fd641f0c4abd173b47 Mon Sep 17 00:00:00 2001 From: Byron Harvey Date: Fri, 18 Jan 2019 15:35:48 -0500 Subject: [PATCH 039/285] Hard-encode all requests to the yext api for # characters --- client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client.go b/client.go index 9487127..e026d17 100644 --- a/client.go +++ b/client.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" ) @@ -107,6 +108,7 @@ func (c *Client) NewRootRequestJSON(method string, path string, obj interface{}) } func (c *Client) NewRequestBody(method string, fullPath string, data []byte) (*http.Request, error) { + fullPath = strings.Replace(fullPath, "#", "%23", -1) req, err := http.NewRequest(method, fullPath, bytes.NewBuffer(data)) if err != nil { return nil, err From 67b467914a22433730851e15d29c361123132909 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 25 Jan 2019 11:38:35 -0500 Subject: [PATCH 040/285] Adding support for entities --- client.go | 34 ++--- customfield.go | 12 +- customfield_service.go | 8 +- customfield_service_test.go | 8 +- entity.go | 35 +++++ entity_diff.go | 10 +- entity_diff_test.go | 4 +- entity_profile.go | 5 - entity_profile_service.go | 82 ----------- entity_service.go | 16 ++- entity_test.go | 2 +- hours.go | 54 +++++-- hours_test.go | 4 +- language_profile.go | 2 +- language_profile_service.go | 92 +++++------- location.go | 4 - location_diff_test.go | 8 +- location_entity.go | 206 ++++++++++++++++++++++++--- location_language_profile.go | 5 + location_language_profile_service.go | 104 ++++++++++++++ 20 files changed, 466 insertions(+), 229 deletions(-) delete mode 100644 entity_profile.go delete mode 100644 entity_profile_service.go create mode 100644 location_language_profile.go create mode 100644 location_language_profile_service.go diff --git a/client.go b/client.go index e026d17..a186ac4 100644 --- a/client.go +++ b/client.go @@ -27,20 +27,20 @@ type ListOptions struct { type Client struct { Config *Config - LocationService *LocationService - ListService *ListService - CustomFieldService *CustomFieldService - FolderService *FolderService - CategoryService *CategoryService - UserService *UserService - ApprovalGroupsService *ApprovalGroupsService - ReviewService *ReviewService - LanguageProfileService *LanguageProfileService - AssetService *AssetService - CFTAssetService *CFTAssetService - AnalyticsService *AnalyticsService - EntityService *EntityService - EntityProfileService *EntityProfileService + LocationService *LocationService + ListService *ListService + CustomFieldService *CustomFieldService + FolderService *FolderService + CategoryService *CategoryService + UserService *UserService + ApprovalGroupsService *ApprovalGroupsService + ReviewService *ReviewService + LocationLanguageProfileService *LocationLanguageProfileService + AssetService *AssetService + CFTAssetService *CFTAssetService + AnalyticsService *AnalyticsService + EntityService *EntityService + LanguageProfileService *LanguageProfileService } func NewClient(config *Config) *Client { @@ -54,15 +54,15 @@ func NewClient(config *Config) *Client { c.UserService = &UserService{client: c} c.ApprovalGroupsService = &ApprovalGroupsService{client: c} c.ReviewService = &ReviewService{client: c} - c.LanguageProfileService = &LanguageProfileService{client: c} + c.LocationLanguageProfileService = &LocationLanguageProfileService{client: c} c.AssetService = &AssetService{client: c} c.CFTAssetService = &CFTAssetService{client: c} c.CFTAssetService.RegisterDefaultAssetValues() c.AnalyticsService = &AnalyticsService{client: c} c.EntityService = &EntityService{client: c} c.EntityService.RegisterDefaultEntities() - c.EntityProfileService = &EntityProfileService{client: c} - c.EntityProfileService.RegisterDefaultEntities() + c.LanguageProfileService = &LanguageProfileService{client: c} + c.LanguageProfileService.RegisterDefaultEntities() return c } diff --git a/customfield.go b/customfield.go index a7ee63d..175c38f 100644 --- a/customfield.go +++ b/customfield.go @@ -26,7 +26,7 @@ const ( ) var ( - UnsetPhotoValue = (*Photo)(nil) + UnsetPhotoValue = (*CustomLocationPhoto)(nil) ) type CustomFieldOption struct { @@ -206,18 +206,18 @@ func (m LocationList) Equal(c Comparable) bool { return (&a).Equal(&b) } -type Photo struct { +type CustomLocationPhoto struct { Url string `json:"url,omitempty"` Description string `json:"description,omitempty"` Details string `json:"details,omitempty"` ClickThroughURL string `json:"clickthroughUrl,omitempty"` } -func (p *Photo) CustomFieldTag() string { +func (p *CustomLocationPhoto) CustomFieldTag() string { return CUSTOMFIELDTYPE_PHOTO } -type Gallery []*Photo +type Gallery []*CustomLocationPhoto func (g *Gallery) CustomFieldTag() string { return CUSTOMFIELDTYPE_GALLERY @@ -236,13 +236,13 @@ func (v *VideoGallery) CustomFieldTag() string { // 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 { +type CustomLocationHours struct { AdditionalText string `json:"additionalHoursText,omitempty"` Hours string `json:"hours,omitempty"` HolidayHours []LocationHolidayHours `json:"holidayHours,omitempty"` } -func (h HoursCustom) CustomFieldTag() string { +func (h CustomLocationHours) CustomFieldTag() string { return CUSTOMFIELDTYPE_HOURS } diff --git a/customfield_service.go b/customfield_service.go index 779213c..4c35533 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -460,7 +460,7 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Photo Field %v", v, err) } - var cfp *Photo + var cfp *CustomLocationPhoto err = json.Unmarshal(asJSON, &cfp) if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Photo Field %v", v, err) @@ -493,7 +493,7 @@ func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[st if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Hours Field %v", v, err) } - var cf HoursCustom + var cf CustomLocationHours err = json.Unmarshal(asJSON, &cf) if err != nil { return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Hours Field %v", v, err) @@ -784,7 +784,7 @@ func (c *CustomFieldManager) MustSetString(name string, value string, loc *Locat Must(c.SetString(name, value, loc)) } -func (c *CustomFieldManager) SetPhoto(name string, v *Photo, loc *Location) error { +func (c *CustomFieldManager) SetPhoto(name string, v *CustomLocationPhoto, loc *Location) error { _, err := c.Set(name, v, loc) return err } @@ -793,7 +793,7 @@ func (c *CustomFieldManager) UnsetPhoto(name string, loc *Location) error { return c.SetPhoto(name, UnsetPhotoValue, loc) } -func (c *CustomFieldManager) MustSetPhoto(name string, v *Photo, loc *Location) { +func (c *CustomFieldManager) MustSetPhoto(name string, v *CustomLocationPhoto, loc *Location) { Must(c.SetPhoto(name, v, loc)) } diff --git a/customfield_service_test.go b/customfield_service_test.go index a06da68..b0edc2a 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -93,15 +93,15 @@ var ( customFieldParseTest{"TEXT_LIST", []interface{}{"a", "b", "c"}, TextList([]string{"a", "b", "c"})}, customFieldParseTest{"MULTI_OPTION", []string{"a", "b", "c"}, MultiOption([]string{"a", "b", "c"})}, customFieldParseTest{"MULTI_OPTION", []interface{}{"a", "b", "c"}, MultiOption([]string{"a", "b", "c"})}, - customFieldParseTest{"PHOTO", customPhotoRaw, &Photo{ + customFieldParseTest{"PHOTO", customPhotoRaw, &CustomLocationPhoto{ Url: "https://mktgcdn.com/awesome.jpg", Description: "This is a picture of an awesome event", Details: "A great picture", ClickThroughURL: "https://yext.com/event", }}, - customFieldParseTest{"PHOTO", nil, (*Photo)(nil)}, + customFieldParseTest{"PHOTO", nil, (*CustomLocationPhoto)(nil)}, customFieldParseTest{"GALLERY", []interface{}{customPhotoRaw}, Gallery{ - &Photo{ + &CustomLocationPhoto{ Url: "https://mktgcdn.com/awesome.jpg", Description: "This is a picture of an awesome event", Details: "A great picture", @@ -112,7 +112,7 @@ var ( Url: "http://www.youtube.com/watch?v=M80FTIcEgZM", Description: "An example caption for a video", }}, - customFieldParseTest{"HOURS", hoursRaw, HoursCustom{ + customFieldParseTest{"HOURS", hoursRaw, CustomLocationHours{ 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{ diff --git a/entity.go b/entity.go index 0f7ebe6..b9627c9 100644 --- a/entity.go +++ b/entity.go @@ -1,5 +1,7 @@ package yext +import "encoding/json" + type EntityType string type Entity interface { @@ -96,3 +98,36 @@ func (r *RawEntity) GetEntityType() EntityType { } return EntityType("") } + +func (r *RawEntity) GetLanguage() string { + if m, ok := (*r)["meta"]; ok { + meta := m.(map[string]interface{}) + if l, ok := meta["language"]; ok { + return l.(string) + } + } + return "" +} + +func (r *RawEntity) GetAccountId() string { + if m, ok := (*r)["meta"]; ok { + meta := m.(map[string]interface{}) + if a, ok := meta["account"]; ok { + return a.(string) + } + } + return "" +} + +func ConvertToRawEntity(e Entity) (*RawEntity, error) { + var raw RawEntity + m, err := json.Marshal(e) + if err != nil { + return nil, err + } + err = json.Unmarshal(m, &raw) + if err != nil { + return nil, err + } + return &raw, nil +} diff --git a/entity_diff.go b/entity_diff.go index 74e8b35..fcd3403 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -1,6 +1,7 @@ package yext import ( + "fmt" "reflect" ) @@ -124,14 +125,15 @@ func isNil(v reflect.Value) bool { } // Diff(a, b): a is base, b is new -func Diff(a Entity, b Entity) (Entity, bool) { +func Diff(a Entity, b Entity) (Entity, bool, error) { + // TODO: should the below return an error? If not should return an empty b object with entity type set? if a.GetEntityType() != b.GetEntityType() { - return nil, true + return nil, true, fmt.Errorf("Entity Types do not match: '%s', '%s'", a.GetEntityType(), b.GetEntityType()) } delta, isDiff := diff(a, b, getNilIsEmpty(a), getNilIsEmpty(b)) if !isDiff { - return nil, isDiff + return nil, isDiff, nil } - return delta.(Entity), isDiff + return delta.(Entity), isDiff, nil } diff --git a/entity_diff_test.go b/entity_diff_test.go index 0dba826..9eadac5 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -822,7 +822,7 @@ func TestEntityDiff(t *testing.T) { if test.newNilIsEmpty { setNilIsEmpty(newEntity) } - delta, isDiff := Diff(baseEntity, newEntity) + delta, isDiff, _ := Diff(baseEntity, newEntity) if isDiff != test.isDiff { t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) } else if test.isDiff == false && delta != nil { @@ -922,7 +922,7 @@ func TestEntityDiffComplex(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - delta, isDiff := Diff(test.base, test.new) + delta, isDiff, _ := Diff(test.base, test.new) if isDiff != test.isDiff { t.Log(delta) t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) diff --git a/entity_profile.go b/entity_profile.go deleted file mode 100644 index 0b19e56..0000000 --- a/entity_profile.go +++ /dev/null @@ -1,5 +0,0 @@ -package yext - -type EntityProfile struct { - Entity -} diff --git a/entity_profile_service.go b/entity_profile_service.go deleted file mode 100644 index f9484eb..0000000 --- a/entity_profile_service.go +++ /dev/null @@ -1,82 +0,0 @@ -package yext - -import ( - "fmt" -) - -const entityProfilesPath = "entityprofiles" - -type EntityProfileService struct { - client *Client - registry Registry -} - -type EntityProfileListResponse struct { - Profiles []interface{} `json:"profiles"` -} - -func (e *EntityProfileService) RegisterDefaultEntities() { - e.registry = defaultEntityRegistry() -} - -func (e *EntityProfileService) RegisterEntity(t EntityType, entity interface{}) { - e.registry.Register(string(t), entity) -} - -func (e *EntityProfileService) Get(id string, languageCode string) (*EntityProfile, *Response, error) { - var v map[string]interface{} - r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), &v) - if err != nil { - return nil, r, err - } - - entity, err := toEntityType(v, e.registry) - if err != nil { - return nil, r, err - } - setNilIsEmpty(entity) - - return &EntityProfile{Entity: entity}, r, nil -} - -func (e *EntityProfileService) List(id string) ([]*EntityProfile, *Response, error) { - var ( - v EntityProfileListResponse - profiles = []*EntityProfile{} - ) - r, err := e.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityProfilesPath, id), &v) - if err != nil { - return nil, r, err - } - - typedProfiles, err := toEntityTypes(v.Profiles, e.registry) - if err != nil { - return nil, r, err - } - for _, profile := range typedProfiles { - setNilIsEmpty(profile) - profiles = append(profiles, &EntityProfile{Entity: profile}) - } - return profiles, r, nil -} - -func (e *EntityProfileService) Upsert(entity Entity, languageCode string) (*Response, error) { - id := entity.GetEntityId() - if id == "" { - return nil, fmt.Errorf("entity profile service upsert: profile object had no id") - } - - r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), entity, nil) - if err != nil { - return r, err - } - return r, nil -} - -func (e *EntityProfileService) Delete(id string, languageCode string) (*Response, error) { - r, err := e.client.DoRequest("DELETE", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), nil) - if err != nil { - return r, err - } - return r, nil -} diff --git a/entity_service.go b/entity_service.go index 5274343..d4cc5b5 100644 --- a/entity_service.go +++ b/entity_service.go @@ -41,11 +41,11 @@ func (e *EntityService) CreateEntity(t EntityType) (interface{}, error) { return e.registry.Create(string(t)) } -func (e *EntityService) toEntityTypes(entities []interface{}) ([]Entity, error) { +func (e *EntityService) ToEntityTypes(entities []interface{}) ([]Entity, error) { return toEntityTypes(entities, e.registry) } -func (e *EntityService) toEntityType(entity interface{}) (Entity, error) { +func (e *EntityService) ToEntityType(entity interface{}) (Entity, error) { return toEntityType(entity, e.registry) } @@ -99,7 +99,7 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res return nil, r, err } - typedEntities, err := e.toEntityTypes(v.Entities) + typedEntities, err := e.ToEntityTypes(v.Entities) if err != nil { return nil, r, err } @@ -141,7 +141,7 @@ func (e *EntityService) Get(id string) (Entity, *Response, error) { return nil, r, err } - entity, err := e.toEntityType(v) + entity, err := e.ToEntityType(v) if err != nil { return nil, r, err } @@ -187,11 +187,15 @@ func (e *EntityService) Create(y Entity) (*Response, error) { return r, nil } -func (e *EntityService) Edit(y Entity) (*Response, error) { - r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, y.GetEntityId()), y, nil) +func (e *EntityService) EditWithId(id string, y Entity) (*Response, error) { + r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, id), y, nil) if err != nil { return r, err } return r, nil } + +func (e *EntityService) Edit(y Entity) (*Response, error) { + return e.EditWithId(y.GetEntityId(), y) +} diff --git a/entity_test.go b/entity_test.go index 59392bd..1cc7161 100644 --- a/entity_test.go +++ b/entity_test.go @@ -95,7 +95,7 @@ func TestEntitySampleJSONResponseDeserialization(t *testing.T) { if err != nil { t.Errorf("Unable to unmarshal sample entity JSON. Err: %s", err) } - if _, err := entityService.toEntityType(mapOfStringToInterface); err != nil { + if _, err := entityService.ToEntityType(mapOfStringToInterface); err != nil { t.Errorf("Unable to convert JSON to entity type. Err: %s", err) } } diff --git a/hours.go b/hours.go index 4e436df..025c5ad 100644 --- a/hours.go +++ b/hours.go @@ -49,6 +49,7 @@ func (w Weekday) ToString() string { return "Unknown" } +// TODO: Rename to LocationHoursHelper type HoursHelper struct { Sunday []string Monday []string @@ -59,6 +60,7 @@ type HoursHelper struct { Saturday []string } +// Format used from LocationService func HoursHelperFromString(str string) (*HoursHelper, error) { var ( hoursHelper = &HoursHelper{} @@ -160,23 +162,23 @@ func (h *HoursHelper) GetHours(weekday Weekday) []string { return nil } -func (h *HoursHelper) Serialize() string { +func (h *HoursHelper) StringSerialize() string { if h.HoursAreAllUnspecified() { return "" } var days = []string{ - h.SerializeDay(Sunday), - h.SerializeDay(Monday), - h.SerializeDay(Tuesday), - h.SerializeDay(Wednesday), - h.SerializeDay(Thursday), - h.SerializeDay(Friday), - h.SerializeDay(Saturday), + h.StringSerializeDay(Sunday), + h.StringSerializeDay(Monday), + h.StringSerializeDay(Tuesday), + h.StringSerializeDay(Wednesday), + h.StringSerializeDay(Thursday), + h.StringSerializeDay(Friday), + h.StringSerializeDay(Saturday), } return strings.Join(days, ",") } -func (h *HoursHelper) SerializeDay(weekday Weekday) string { +func (h *HoursHelper) StringSerializeDay(weekday Weekday) string { if h.HoursAreAllUnspecified() { return "" } @@ -190,6 +192,40 @@ func (h *HoursHelper) SerializeDay(weekday Weekday) string { return strings.Join(hoursStrings, ",") } +func (h *HoursHelper) StructSerialize() *Hours { + if h.HoursAreAllUnspecified() { + return nil + } + hours := &Hours{} + hours.Sunday = h.StructSerializeDay(Sunday) + hours.Monday = h.StructSerializeDay(Monday) + hours.Tuesday = h.StructSerializeDay(Tuesday) + hours.Wednesday = h.StructSerializeDay(Wednesday) + hours.Thursday = h.StructSerializeDay(Thursday) + hours.Friday = h.StructSerializeDay(Friday) + hours.Saturday = h.StructSerializeDay(Saturday) + return hours + +} + +func (h *HoursHelper) StructSerializeDay(weekday Weekday) *DayHours { + if h.HoursAreUnspecified(weekday) { + return nil + } + + if h.HoursAreClosed(weekday) { + return &DayHours{ + IsClosed: Bool(true), + } + } + var d = &DayHours{} + for _, interval := range h.GetHours(weekday) { + parts := strings.Split(interval, ":") + d.SetHours(fmt.Sprintf("%s:%s", parts[0], parts[1]), fmt.Sprintf("%s:%s", parts[2], parts[3])) + } + return d +} + func (h *HoursHelper) ToStringSlice() ([]string, error) { var hoursStringSlice = make([][]string, 7) for i := range hoursStringSlice { diff --git a/hours_test.go b/hours_test.go index 9bebb41..73b39fa 100644 --- a/hours_test.go +++ b/hours_test.go @@ -137,8 +137,10 @@ func TestSerialize(t *testing.T) { } for i, test := range tests { - if got := test.Have.Serialize(); got != test.Want { + if got := test.Have.StringSerialize(); got != test.Want { t.Errorf("Test Serialize %d.\nWanted: %s\nGot: %s", i+1, test.Want, got) } } } + +// TODO: Add struct serialize diff --git a/language_profile.go b/language_profile.go index adc4ec5..cc03977 100644 --- a/language_profile.go +++ b/language_profile.go @@ -1,5 +1,5 @@ package yext type LanguageProfile struct { - Location + Entity } diff --git a/language_profile_service.go b/language_profile_service.go index d2d5665..58b9ecd 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -1,104 +1,82 @@ package yext import ( - "encoding/json" "fmt" ) -const profilesPath = "profiles" +const entityProfilesPath = "entityprofiles" type LanguageProfileService struct { - client *Client - CustomFields []*CustomField + client *Client + registry Registry } type LanguageProfileListResponse struct { - LanguageProfiles []*LanguageProfile `json:"languageProfiles"` + Profiles []interface{} `json:"profiles"` } -func (l *LanguageProfileListResponse) ResponseAsLocations() []*Location { - languageProfilesAsLocs := []*Location{} - for _, lp := range l.LanguageProfiles { - languageProfilesAsLocs = append(languageProfilesAsLocs, &lp.Location) - } - return languageProfilesAsLocs +func (l *LanguageProfileService) RegisterDefaultEntities() { + l.registry = defaultEntityRegistry() +} + +func (l *LanguageProfileService) RegisterEntity(t EntityType, entity interface{}) { + l.registry.Register(string(t), entity) } -func (l *LanguageProfileService) GetAll(id string) (*LanguageProfileListResponse, *Response, error) { - var v LanguageProfileListResponse - r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", locationsPath, id, profilesPath), &v) +func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageProfile, *Response, error) { + var v map[string]interface{} + r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), &v) if err != nil { return nil, r, err } - if _, err := l.HydrateLocations(v.LanguageProfiles); err != nil { + entity, err := toEntityType(v, l.registry) + if err != nil { return nil, r, err } + setNilIsEmpty(entity) - return &v, r, nil + return &LanguageProfile{Entity: entity}, r, nil } -func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageProfile, *Response, error) { - var v LanguageProfile - r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s/%s", locationsPath, id, profilesPath, languageCode), &v) +func (l *LanguageProfileService) List(id string) ([]*LanguageProfile, *Response, error) { + var ( + v LanguageProfileListResponse + profiles = []*LanguageProfile{} + ) + r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityProfilesPath, id), &v) if err != nil { return nil, r, err } - if _, err := HydrateLocation(&v.Location, l.CustomFields); err != nil { + typedProfiles, err := toEntityTypes(v.Profiles, l.registry) + if err != nil { return nil, r, err } - - return &v, r, nil + for _, profile := range typedProfiles { + setNilIsEmpty(profile) + profiles = append(profiles, &LanguageProfile{Entity: profile}) + } + return profiles, r, nil } -func (l *LanguageProfileService) Upsert(y *LanguageProfile, languageCode string) (*Response, error) { - id := y.GetId() +func (l *LanguageProfileService) Upsert(entity Entity, languageCode string) (*Response, error) { + id := entity.GetEntityId() if id == "" { - return nil, fmt.Errorf("language profile service: upsert: profile object had no id") - } - asJSON, err := json.Marshal(y) - if err != nil { - return nil, err + return nil, fmt.Errorf("entity profile service upsert: profile object had no id") } - var asMap map[string]interface{} - err = json.Unmarshal(asJSON, &asMap) - if err != nil { - return nil, err - } - delete(asMap, "id") - 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) + r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), entity, nil) if err != nil { return r, err } - return r, nil } func (l *LanguageProfileService) Delete(id string, languageCode string) (*Response, error) { - r, err := l.client.DoRequest("DELETE", fmt.Sprintf("%s/%s/%s/%s", locationsPath, id, profilesPath, languageCode), nil) + r, err := l.client.DoRequest("DELETE", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), nil) if err != nil { return r, err } - return r, nil } - -func (l *LanguageProfileService) HydrateLocations(languageProfiles []*LanguageProfile) ([]*LanguageProfile, error) { - if l.CustomFields == nil { - return languageProfiles, nil - } - - for _, profile := range languageProfiles { - _, err := HydrateLocation(&profile.Location, l.CustomFields) - if err != nil { - return languageProfiles, err - } - } - - return languageProfiles, nil -} diff --git a/location.go b/location.go index f334c09..c50b99a 100644 --- a/location.go +++ b/location.go @@ -809,10 +809,6 @@ func (l LocationPhoto) String() string { return fmt.Sprintf("Url: '%v', Description: '%v', AlternateText: '%v', ClickThroughUrl: '%v'", l.Url, l.Description, l.AlternateText, l.ClickThroughUrl) } -func (l Photo) String() string { - return fmt.Sprintf("Url: '%v', Description: '%v'", l.Url, l.Description) -} - // LocationClosed represents the 'closed' state of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Closed type LocationClosed struct { diff --git a/location_diff_test.go b/location_diff_test.go index d6ee51f..e2ada9d 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -797,13 +797,13 @@ type customFieldsTest struct { var baseCustomFields = map[string]interface{}{ "62150": Gallery{ - &Photo{ + &CustomLocationPhoto{ ClickThroughURL: "https://locations.yext.com", Description: "This is the caption", Url: "http://a.mktgcdn.com/p-sandbox/gRcmaehu-FoJtL3Ld6vNjYHpbZxmPSYZ1cTEF_UU7eY/1247x885.png", }, }, - "62151": Photo{ + "62151": CustomLocationPhoto{ ClickThroughURL: "https://locations.yext.com", Description: "This is a caption on a single!", Url: "http://a.mktgcdn.com/p-sandbox/bSZ_mKhfFYGih6-ry5mtbwB_JbKu930kFxHOaQRwZC4/1552x909.png", @@ -1390,10 +1390,10 @@ func TestCustomFieldPointerComparison(t *testing.T) { a, b := *new(Location), new(Location) a.Id = String("blah") a.CustomFields = map[string]interface{}{ - "1": HoursCustom{Hours: "1:09:00:18:00"}, + "1": CustomLocationHours{Hours: "1:09:00:18:00"}, } b.CustomFields = map[string]interface{}{ - "1": &HoursCustom{Hours: "1:09:00:18:00"}, + "1": &CustomLocationHours{Hours: "1:09:00:18:00"}, } a.hydrated, b.hydrated = true, true diff --git a/location_entity.go b/location_entity.go index 36ba630..917dcc4 100644 --- a/location_entity.go +++ b/location_entity.go @@ -7,6 +7,8 @@ package yext import ( "encoding/json" + + yext "github.com/yext/yext-go" ) const ENTITYTYPE_LOCATION EntityType = "location" @@ -24,13 +26,11 @@ type LocationEntity struct { // Address Fields Name *string `json:"name,omitempty"` Address *Address `json:"address,omitempty"` - DisplayAddress *string `json:"displayAddress,omitempty"` - CountryCode *string `json:"countryCode,omitempty"` SuppressAddress *bool `json:"suppressAddress,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` - FaxPhone *string `json:"faxPhone,omitempty"` + Fax *string `json:"fax,omitempty"` LocalPhone *string `json:"localPhone,omitempty"` MobilePhone *string `json:"mobilePhone,omitempty"` MainPhone *string `json:"mainPhone,omitempty"` @@ -112,13 +112,28 @@ type LocationEntity struct { FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` } +// TODO: Rename LocationPhoto....profilePhoto? +// Or below could be complex photo vs simple photo +type Photo struct { + Image *Image `json:"image,omitempty"` + ClickthroughUrl *string `json:"clickthroughUrl,omitempty"` + Description *string `json:"description,omitempty"` + Details *string `json:"details,omitempty"` +} + +type Image struct { + Url *string `json:"url,omitempty"` + AlternateText *string `json:"alternateText,omitempty"` +} + type Address struct { - Line1 *string `json:"line1,omitempty"` - Line2 *string `json:"line2,omitempty"` - City *string `json:"city,omitempty"` - Region *string `json:"region,omitempty"` - Sublocality *string `json:"sublocality,omitempty"` - PostalCode *string `json:"postalCode,omitempty"` + Line1 *string `json:"line1,omitempty"` + Line2 *string `json:"line2,omitempty"` + City *string `json:"city,omitempty"` + Region *string `json:"region,omitempty"` + Sublocality *string `json:"sublocality,omitempty"` + PostalCode *string `json:"postalCode,omitempty"` + ExtraDescription *string `json:"extraDescription,omitempty"` } type FeaturedMessage struct { @@ -153,11 +168,165 @@ type DayHours struct { IsClosed *bool `json:"isClosed,omitempty"` } +func (d *DayHours) SetClosed() { + d.IsClosed = yext.Bool(true) + d.OpenIntervals = nil +} + +func (d *DayHours) AddHours(start string, end string) { + d.IsClosed = nil + if d.OpenIntervals == nil { + d.OpenIntervals = []*Interval{} + } + d.OpenIntervals = append(d.OpenIntervals, &Interval{ + Start: start, + End: end, + }) +} + +func (d *DayHours) SetHours(start string, end string) { + d.IsClosed = nil + d.OpenIntervals = []*Interval{ + &Interval{ + Start: start, + End: end, + }, + } +} + type Interval struct { Start string `json:"start,omitempty"` End string `json:"end,omitempty"` } +func NewInterval(start string, end string) *Interval { + return &Interval{Start: start, End: end} +} + +func (h *Hours) GetDayHours(w Weekday) *DayHours { + switch w { + case Sunday: + return h.Sunday + case Monday: + return h.Monday + case Tuesday: + return h.Tuesday + case Wednesday: + return h.Wednesday + case Thursday: + return h.Thursday + case Friday: + return h.Friday + case Saturday: + return h.Saturday + } + return nil +} + +func (h *Hours) SetClosedAllWeek() { + h = &Hours{ + Sunday: &DayHours{}, + Monday: &DayHours{}, + Tuesday: &DayHours{}, + Wednesday: &DayHours{}, + Thursday: &DayHours{}, + Friday: &DayHours{}, + Saturday: &DayHours{}, + } + h.Sunday.SetClosed() + h.Monday.SetClosed() + h.Tuesday.SetClosed() + h.Wednesday.SetClosed() + h.Thursday.SetClosed() + h.Friday.SetClosed() + h.Saturday.SetClosed() +} + +func (h *Hours) SetClosed(w Weekday) { + d := &DayHours{} + d.SetClosed() + switch w { + case Sunday: + h.Sunday = d + case Monday: + h.Monday = d + case Tuesday: + h.Tuesday = d + case Wednesday: + h.Wednesday = d + case Thursday: + h.Thursday = d + case Friday: + h.Friday = d + case Saturday: + h.Saturday = d + } +} + +func (h *Hours) SetUnspecified(w Weekday) { + switch w { + case Sunday: + h.Sunday = nil + case Monday: + h.Monday = nil + case Tuesday: + h.Tuesday = nil + case Wednesday: + h.Wednesday = nil + case Thursday: + h.Thursday = nil + case Friday: + h.Friday = nil + case Saturday: + h.Saturday = nil + } +} + +func (h *Hours) AddHours(w Weekday, start string, end string) { + d := h.GetDayHours(w) + if d == nil { + d = &DayHours{} + } + d.AddHours(start, end) + switch w { + case Sunday: + h.Sunday = d + case Monday: + h.Monday = d + case Tuesday: + h.Tuesday = d + case Wednesday: + h.Wednesday = d + case Thursday: + h.Thursday = d + case Friday: + h.Friday = d + case Saturday: + h.Saturday = d + } +} + +func (h *Hours) SetHours(w Weekday, start string, end string) { + d := &DayHours{} + d.AddHours(start, end) + switch w { + case Sunday: + h.Sunday = d + case Monday: + h.Monday = d + case Tuesday: + h.Tuesday = d + case Wednesday: + h.Wednesday = d + case Thursday: + h.Thursday = d + case Friday: + h.Friday = d + case Saturday: + h.Saturday = d + } +} + func (y LocationEntity) GetId() string { if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { return *y.BaseEntity.Meta.Id @@ -200,9 +369,9 @@ func (y LocationEntity) GetSuppressAddress() bool { return false } -func (y LocationEntity) GetDisplayAddress() string { - if y.DisplayAddress != nil { - return *y.DisplayAddress +func (y LocationEntity) GetExtraDescription() string { + if y.Address != nil && y.Address.ExtraDescription != nil { + return *y.Address.ExtraDescription } return "" } @@ -228,13 +397,6 @@ func (y LocationEntity) GetPostalCode() string { return "" } -func (y LocationEntity) GetCountryCode() string { - if y.CountryCode != nil { - return *y.CountryCode - } - return "" -} - func (y LocationEntity) GetMainPhone() string { if y.MainPhone != nil { return *y.MainPhone @@ -263,9 +425,9 @@ func (y LocationEntity) GetAlternatePhone() string { return "" } -func (y LocationEntity) GetFaxPhone() string { - if y.FaxPhone != nil { - return *y.FaxPhone +func (y LocationEntity) GetFax() string { + if y.Fax != nil { + return *y.Fax } return "" } diff --git a/location_language_profile.go b/location_language_profile.go new file mode 100644 index 0000000..10f0df6 --- /dev/null +++ b/location_language_profile.go @@ -0,0 +1,5 @@ +package yext + +type LocationLanguageProfile struct { + Location +} diff --git a/location_language_profile_service.go b/location_language_profile_service.go new file mode 100644 index 0000000..a052dbd --- /dev/null +++ b/location_language_profile_service.go @@ -0,0 +1,104 @@ +package yext + +import ( + "encoding/json" + "fmt" +) + +const profilesPath = "profiles" + +type LocationLanguageProfileService struct { + client *Client + CustomFields []*CustomField +} + +type LocationLanguageProfileListResponse struct { + LanguageProfiles []*LocationLanguageProfile `json:"languageProfiles"` +} + +func (l *LocationLanguageProfileListResponse) ResponseAsLocations() []*Location { + languageProfilesAsLocs := []*Location{} + for _, lp := range l.LanguageProfiles { + languageProfilesAsLocs = append(languageProfilesAsLocs, &lp.Location) + } + return languageProfilesAsLocs +} + +func (l *LocationLanguageProfileService) GetAll(id string) (*LocationLanguageProfileListResponse, *Response, error) { + var v LocationLanguageProfileListResponse + r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", locationsPath, id, profilesPath), &v) + if err != nil { + return nil, r, err + } + + if _, err := l.HydrateLocations(v.LanguageProfiles); err != nil { + return nil, r, err + } + + return &v, r, nil +} + +func (l *LocationLanguageProfileService) Get(id string, languageCode string) (*LocationLanguageProfile, *Response, error) { + var v LocationLanguageProfile + r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s/%s", locationsPath, id, profilesPath, languageCode), &v) + if err != nil { + return nil, r, err + } + + if _, err := HydrateLocation(&v.Location, l.CustomFields); err != nil { + return nil, r, err + } + + return &v, r, nil +} + +func (l *LocationLanguageProfileService) Upsert(y *LocationLanguageProfile, languageCode string) (*Response, error) { + id := y.GetId() + if id == "" { + return nil, fmt.Errorf("language profile service: upsert: profile object had no id") + } + asJSON, err := json.Marshal(y) + if err != nil { + return nil, err + } + var asMap map[string]interface{} + err = json.Unmarshal(asJSON, &asMap) + if err != nil { + return nil, err + } + delete(asMap, "id") + + 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) + if err != nil { + return r, err + } + + return r, nil +} + +func (l *LocationLanguageProfileService) Delete(id string, languageCode string) (*Response, error) { + r, err := l.client.DoRequest("DELETE", fmt.Sprintf("%s/%s/%s/%s", locationsPath, id, profilesPath, languageCode), nil) + if err != nil { + return r, err + } + + return r, nil +} + +func (l *LocationLanguageProfileService) HydrateLocations(languageProfiles []*LocationLanguageProfile) ([]*LocationLanguageProfile, error) { + if l.CustomFields == nil { + return languageProfiles, nil + } + + for _, profile := range languageProfiles { + _, err := HydrateLocation(&profile.Location, l.CustomFields) + if err != nil { + return languageProfiles, err + } + } + + return languageProfiles, nil +} From 66b0933a93a4ae2ff2aecbe362be7192b680e309 Mon Sep 17 00:00:00 2001 From: Taylor Birdsall Date: Mon, 28 Jan 2019 11:37:20 -0500 Subject: [PATCH 041/285] Review Service: Fixed some review invitation issues (#76) J=PC-40957 TEST=compile --- review.go | 13 +++++++------ review_service.go | 24 ++++++++++++------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/review.go b/review.go index 778430b..01e0ac1 100644 --- a/review.go +++ b/review.go @@ -1,12 +1,13 @@ package yext type Reviewer struct { - LocationId *string `json:"locationId"` - FirstName *string `json:"firstName"` - LastName *string `json:"lastName"` - Contact *string `json:"contact"` - Image *bool `json:"image"` - TemplateId *string `json:"templateId"` + LocationId *string `json:"locationId,omitempty"` + FirstName *string `json:"firstName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Contact *string `json:"contact,omitempty"` + Image *bool `json:"image,omitempty"` + TemplateId *string `json:"templateId,omitempty"` + LabelIds []*string `json:"labelIds,omitempty"` } type Review struct { diff --git a/review_service.go b/review_service.go index 1d16cd8..9c4d9d5 100644 --- a/review_service.go +++ b/review_service.go @@ -48,15 +48,15 @@ type ReviewListResponse struct { } type ReviewCreateInvitationResponse struct { - Id string `json:"id"` - LocationId string `json:"locationId"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Contact string `json:"contact"` - Image bool `json:"image"` - TemplateId string `json:"templateId"` - Status string `json:"status"` - Details string `json:"details"` + Id string `json:"id"` + LocationId string `json:"locationId"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Contact string `json:"contact"` + Image bool `json:"image"` + TemplateId int `json:"templateId"` + Status string `json:"status"` + Details string `json:"details"` } func (l *ReviewService) ListAllWithOptions(rlOpts *ReviewListOptions) ([]*Review, error) { @@ -191,9 +191,9 @@ func (l *ReviewService) Get(id int) (*Review, *Response, error) { return &v, r, nil } -func (l *ReviewService) CreateInvitation(jsonData []Reviewer) (*[]ReviewCreateInvitationResponse, *Response, error) { - v := &[]ReviewCreateInvitationResponse{} - r, err := l.client.DoRequestJSON("POST", reviewInvitePath, jsonData, v) +func (l *ReviewService) CreateInvitation(jsonData []*Reviewer) ([]*ReviewCreateInvitationResponse, *Response, error) { + var v []*ReviewCreateInvitationResponse + r, err := l.client.DoRequestJSON("POST", reviewInvitePath, jsonData, &v) if err != nil { return nil, r, err } From 975df06dddb28a93643865b08e26141c5aa5a18d Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Tue, 29 Jan 2019 10:07:26 -0500 Subject: [PATCH 042/285] Add event entity support R=cdworak,usaha,bharvey (#69) --- event.go | 405 ++++++++++++++++++++++++++++++++++++++++++++- location_entity.go | 27 ++- 2 files changed, 411 insertions(+), 21 deletions(-) diff --git a/event.go b/event.go index 62a6fc0..f1bd5d1 100644 --- a/event.go +++ b/event.go @@ -8,10 +8,407 @@ const ENTITYTYPE_EVENT EntityType = "event" type EventEntity struct { BaseEntity - Id *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - EntityType EntityType `json:"entityType,omitempty"` + + // Admin + CategoryIds *UnorderedStrings `json:"categoryIds,omitempty"` + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden *bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + + // Other Contact Info + MainPhone *string `json:"mainPhone,omitempty"` + AlternatePhone *string `json:"alternatePhone,omitempty"` + FaxPhone *string `json:"fax,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + + //Event Info + Description *string `json:"description,omitempty"` + TicketUrl *string `json:"ticketUrl,omitempty"` + Hours *Hours `json:"hours,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Logo *LocationPhoto `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Timezone *string `json:"timezone,omitempty"` + YearEstablished *float64 `json:"yearEstablished,omitempty"` + AgeRange *AgeRange `json:"ageRange,omitempty"` + Time *TimeRange `json:"time,omitempty"` + IsFreeEvent *bool `json:"isFreeEvent,omitempty"` + IsTicketedEvent *bool `json:"isTicketedEvent,omitempty"` + EventStatus *string `json:"eventStatus,omitempty"` + VenueName *string `json:"venueName,omitempty"` + TicketAvailability *string `json:"ticketAvailability,omitempty"` + TicketPriceRange *TicketPriceRange `json:"ticketPriceRange,omitempty"` + + //Lats & Lngs + DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` + RoutableCoordinate *Coordinate `json:"yextRoutableCoordinate,omitempty"` + DropoffCoordinate *Coordinate `json:"yextDropoffCoordinate,omitempty"` + WalkableCoordinate *Coordinate `json:"yextWalkableCoordinate,omitempty"` + PickupCoordinate *Coordinate `json:"yextPickupCoordinate,omitempty"` + + //Event Organizer Info + OrganizerEmail *string `json:"organizerEmail,omitempty"` + OrganizerName *string `json:"organizerName,omitempty"` + OrganizerPhone *string `json:"organizerPhone,omitempty"` + + //Urls + WebsiteUrl *Website `json:"websiteUrl,omitempty"` + FeaturedMessage *FeaturedMessage `json:"featuredMessage,omitempty"` + + //Social Media + FacebookCoverPhoto *LocationPhoto `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto *LocationPhoto `json:"facebookProfilePhoto,omitempty"` + FacebookStoreId *string `json:"facebookStoreId,omitempty"` + FacebookVanityUrl *string `json:"facebookVanityUrl,omitempty"` + + TwitterHandle *string `json:"twitterHandle,omitempty"` + + PhotoGallery *[]PhotoGalleryItem `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + + GoogleAttributes *GoogleAttributes `json:"googleAttributes,omitempty"` +} + +type AgeRange struct { + MinValue *int64 `json:"minValue,omitempty"` + MaxValue *int64 `json:"maxValue,omitempty"` +} + +type TimeRange struct { + Start *string `json:"start,omitempty"` + End *string `json:"end,omitempty"` +} + +type TicketPriceRange struct { + MinValue *string `json:"minValue,omitempty"` + MaxValue *string `json:"maxValue,omitempty"` + CurrencyCode *string `json:"currencyCode,omitempty"` +} + +type PhotoGalleryItem struct { + Image *Image `json:"image,omitempty"` + ClickthroughUrl *string `json:"clickthroughUrl,omitempty"` + Description *string `json:"description,omitempty"` + Details *string `json:"details,omitempty"` +} + +func (y EventEntity) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y EventEntity) GetName() string { + if y.Name != nil { + return *y.Name + } + return "" +} + +func (y EventEntity) GetCountryCode() string { + if y.Address != nil && y.Address.CountryCode != nil { + return *y.Address.CountryCode + } + return "" +} + +func (y EventEntity) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return *y.Address.Line1 + } + return "" +} + +func (y EventEntity) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return *y.Address.Line2 + } + return "" +} + +func (y EventEntity) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return *y.Address.City + } + return "" +} + +func (y EventEntity) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return *y.Address.Region + } + return "" +} + +func (y EventEntity) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return *y.Address.PostalCode + } + return "" +} + +func (y EventEntity) GetAddressHidden() bool { + if y.AddressHidden != nil { + return *y.AddressHidden + } + return false +} + +func (y EventEntity) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone + } + return "" +} + +func (y EventEntity) GetAlternatePhone() string { + if y.AlternatePhone != nil { + return *y.AlternatePhone + } + return "" +} + +func (y EventEntity) GetFaxPhone() string { + if y.FaxPhone != nil { + return *y.FaxPhone + } + return "" +} + +func (y EventEntity) GetTollFreePhone() string { + if y.TollFreePhone != nil { + return *y.TollFreePhone + } + return "" +} + +func (y EventEntity) GetOrganizerEmail() string { + if y.OrganizerEmail != nil { + return *y.OrganizerEmail + } + return "" +} + +func (y EventEntity) GetOrganizerName() string { + if y.OrganizerName != nil { + return *y.OrganizerName + } + return "" +} + +func (y EventEntity) GetOrganizerPhone() string { + if y.OrganizerPhone != nil { + return *y.OrganizerPhone + } + return "" +} + +func (y EventEntity) GetDescription() string { + if y.Description != nil { + return *y.Description + } + return "" +} + +func (y EventEntity) GetHolidayHours() []HolidayHours { + if y.Hours != nil && y.Hours.HolidayHours != nil { + return *y.Hours.HolidayHours + } + return nil +} + +func (y EventEntity) GetBrands() (v []string) { + if y.Brands != nil { + v = *y.Brands + } + return v +} + +func (y EventEntity) GetDisplayLat() float64 { + if y.DisplayCoordinate != nil && y.DisplayCoordinate.Latitude != nil { + return *y.DisplayCoordinate.Latitude + } + return 0 +} + +func (y EventEntity) GetDisplayLng() float64 { + if y.DisplayCoordinate != nil && y.DisplayCoordinate.Longitude != nil { + return *y.DisplayCoordinate.Longitude + } + return 0 +} + +func (y EventEntity) GetRoutableLat() float64 { + if y.RoutableCoordinate != nil && y.RoutableCoordinate.Latitude != nil { + return *y.RoutableCoordinate.Latitude + } + return 0 +} + +func (y EventEntity) GetRoutableLng() float64 { + if y.RoutableCoordinate != nil && y.RoutableCoordinate.Longitude != nil { + return *y.RoutableCoordinate.Longitude + } + return 0 +} + +func (y EventEntity) GetDropoffLat() float64 { + if y.DropoffCoordinate != nil && y.DropoffCoordinate.Latitude != nil { + return *y.DropoffCoordinate.Latitude + } + return 0 +} + +func (y EventEntity) GetDropoffLng() float64 { + if y.DropoffCoordinate != nil && y.DropoffCoordinate.Longitude != nil { + return *y.DropoffCoordinate.Longitude + } + return 0 +} + +func (y EventEntity) GetPickupLat() float64 { + if y.PickupCoordinate != nil && y.PickupCoordinate.Latitude != nil { + return *y.PickupCoordinate.Latitude + } + return 0 +} + +func (y EventEntity) GetPickupLng() float64 { + if y.PickupCoordinate != nil && y.PickupCoordinate.Longitude != nil { + return *y.PickupCoordinate.Longitude + } + return 0 +} + +func (y EventEntity) GetWalkableLat() float64 { + if y.WalkableCoordinate != nil && y.WalkableCoordinate.Latitude != nil { + return *y.WalkableCoordinate.Latitude + } + return 0 +} + +func (y EventEntity) GetWalkableLng() float64 { + if y.WalkableCoordinate != nil && y.WalkableCoordinate.Longitude != nil { + return *y.WalkableCoordinate.Longitude + } + return 0 +} + +func (y EventEntity) GetWebsiteUrl() string { + if y.WebsiteUrl != nil && y.WebsiteUrl.Url != nil { + return *y.WebsiteUrl.Url + } + return "" +} + +func (y EventEntity) GetDisplayWebsiteUrl() string { + if y.WebsiteUrl != nil && y.WebsiteUrl.DisplayUrl != nil { + return *y.WebsiteUrl.DisplayUrl + } + return "" +} + +func (y EventEntity) GetTicketUrl() string { + if y.TicketUrl != nil { + return *y.TicketUrl + } + return "" +} + +func (y EventEntity) GetFeaturedMessageDescription() string { + if y.FeaturedMessage != nil && y.FeaturedMessage.Description != nil { + return *y.FeaturedMessage.Description + } + return "" +} + +func (y EventEntity) GetFeaturedMessageUrl() string { + if y.FeaturedMessage != nil && y.FeaturedMessage.Url != nil { + return *y.FeaturedMessage.Url + } + return "" +} + +func (y EventEntity) GetFacebookPageUrl() string { + if y.FacebookPageUrl != nil { + return *y.FacebookPageUrl + } + return "" +} + +func (y EventEntity) GetGoogleAttributes() GoogleAttributes { + if y.GoogleAttributes != nil { + return *y.GoogleAttributes + } + return nil +} + +func (y EventEntity) GetPaymentOptions() (v []string) { + if y.PaymentOptions != nil { + v = *y.PaymentOptions + } + return v +} + +func (y EventEntity) GetTimezone() string { + if y.Timezone != nil { + return *y.Timezone + } + return "" +} + +func (y EventEntity) GetYearEstablished() float64 { + if y.YearEstablished != nil { + return *y.YearEstablished + } + return 0 +} + +func (y EventEntity) GetIsTicketedEvent() bool { + if y.IsTicketedEvent != nil { + return *y.IsTicketedEvent + } + return false +} + +func (y EventEntity) GetIsFreeEvent() bool { + if y.IsFreeEvent != nil { + return *y.IsFreeEvent + } + return false +} + +func (y EventEntity) GetEventStatus() string { + if y.EventStatus != nil { + return *y.EventStatus + } + return "" +} + +func (y EventEntity) GetVenueName() string { + if y.VenueName != nil { + return *y.VenueName + } + return "" +} + +func (y EventEntity) GetTicketAvailability() string { + if y.TicketAvailability != nil { + return *y.TicketAvailability + } + return "" +} + +func (b *EventEntity) GetCategoryIds() (v UnorderedStrings) { + if b.CategoryIds != nil { + v = *b.CategoryIds + } + return v } func (e *EventEntity) String() string { diff --git a/location_entity.go b/location_entity.go index 917dcc4..7ba4c8d 100644 --- a/location_entity.go +++ b/location_entity.go @@ -24,9 +24,9 @@ type LocationEntity struct { Keywords *[]string `json:"keywords,omitempty"` // Address Fields - Name *string `json:"name,omitempty"` - Address *Address `json:"address,omitempty"` - SuppressAddress *bool `json:"suppressAddress,omitempty"` + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden *bool `json:"addressHidden,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -36,7 +36,6 @@ type LocationEntity struct { MainPhone *string `json:"mainPhone,omitempty"` TollFreePhone *string `json:"tollFreePhone,omitempty"` TtyPhone *string `json:"ttyPhone,omitempty"` - IsPhoneTracked *bool `json:"isPhoneTracked,omitempty"` Emails *[]string `json:"emails,omitempty"` // Location Info @@ -90,9 +89,9 @@ type LocationEntity struct { UberTripBrandingUrl *string `json:"uberTripBrandingUrl,omitempty"` // Social Media - FacebookCoverPhoto *LocationPhoto `json:"facebookCoverPhoto,omitempty"` - FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` - FacebookProfilePicture *LocationPhoto `json:"facebookProfilePicture,omitempty"` + FacebookCoverPhoto *LocationPhoto `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto *LocationPhoto `json:"facebookProfilePhoto,omitempty"` GoogleCoverPhoto *LocationPhoto `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` @@ -133,6 +132,7 @@ type Address struct { Region *string `json:"region,omitempty"` Sublocality *string `json:"sublocality,omitempty"` PostalCode *string `json:"postalCode,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` ExtraDescription *string `json:"extraDescription,omitempty"` } @@ -362,9 +362,9 @@ func (y LocationEntity) GetLine2() string { return "" } -func (y LocationEntity) GetSuppressAddress() bool { - if y.SuppressAddress != nil { - return *y.SuppressAddress +func (y LocationEntity) GetAddressHidden() bool { + if y.AddressHidden != nil { + return *y.AddressHidden } return false } @@ -404,13 +404,6 @@ func (y LocationEntity) GetMainPhone() string { return "" } -func (y LocationEntity) GetIsPhoneTracked() bool { - if y.IsPhoneTracked != nil { - return *y.IsPhoneTracked - } - return false -} - func (y LocationEntity) GetLocalPhone() string { if y.LocalPhone != nil { return *y.LocalPhone From 278d7cae65ee8c71f5e4c19e7e703626e2f828b4 Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Tue, 29 Jan 2019 12:14:29 -0500 Subject: [PATCH 043/285] Updated entity code to accomodate varying Google Attributes fields (#77) --- event.go | 4 ++-- location_entity.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/event.go b/event.go index f1bd5d1..a75ea1f 100644 --- a/event.go +++ b/event.go @@ -70,7 +70,7 @@ type EventEntity struct { PhotoGallery *[]PhotoGalleryItem `json:"photoGallery,omitempty"` Videos *[]Video `json:"videos,omitempty"` - GoogleAttributes *GoogleAttributes `json:"googleAttributes,omitempty"` + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` } type AgeRange struct { @@ -341,7 +341,7 @@ func (y EventEntity) GetFacebookPageUrl() string { return "" } -func (y EventEntity) GetGoogleAttributes() GoogleAttributes { +func (y EventEntity) GetGoogleAttributes() map[string][]string { if y.GoogleAttributes != nil { return *y.GoogleAttributes } diff --git a/location_entity.go b/location_entity.go index 7ba4c8d..1090c7f 100644 --- a/location_entity.go +++ b/location_entity.go @@ -104,7 +104,7 @@ type LocationEntity struct { Photos *[]LocationPhoto `json:"photos,omitempty"` VideoUrls *[]string `json:"videoUrls,omitempty"` - GoogleAttributes *GoogleAttributes `json:"googleAttributes,omitempty"` + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` // Reviews ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` @@ -661,7 +661,7 @@ func (y LocationEntity) GetVideoUrls() (v []string) { return v } -func (y LocationEntity) GetGoogleAttributes() GoogleAttributes { +func (y LocationEntity) GetGoogleAttributes() map[string][]string { if y.GoogleAttributes != nil { return *y.GoogleAttributes } From d928a6b1a89f703deb5106224dc2179eee516d43 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 8 Feb 2019 11:07:04 -0500 Subject: [PATCH 044/285] CustomFieldService -> LocationCustomFieldService; double pointer support --- acl_diff_test.go | 446 ++++---- cftasset_service.go | 2 +- client.go | 2 + customfield.go | 236 +---- customfield_service.go | 705 ++----------- customfield_service_test.go | 423 +------- entity.go | 8 - entity_diff.go | 29 +- entity_diff_test.go | 978 ++++++++++++++++-- entity_registry.go | 30 +- entity_service.go | 88 +- entity_service_test.go | 99 +- entity_test.go | 105 +- entity_unmarshal.go | 40 + event.go => event_entity.go | 60 +- hours.go | 134 ++- hours_test.go | 417 +++++++- language_profile_service.go | 8 +- location.go | 73 +- location_customfield.go | 234 +++++ location_customfield_service.go | 797 ++++++++++++++ location_customfield_service_test.go | 504 +++++++++ ...ld_test.go => location_customfield_test.go | 0 location_diff.go | 3 +- location_diff_test.go | 197 ++-- location_entity.go | 534 ++++++---- registry.go | 2 +- role_diff_test.go | 56 +- type.go | 199 +++- 29 files changed, 4343 insertions(+), 2066 deletions(-) create mode 100644 entity_unmarshal.go rename event.go => event_entity.go (85%) create mode 100644 location_customfield.go create mode 100644 location_customfield_service.go create mode 100644 location_customfield_service_test.go rename customfield_test.go => location_customfield_test.go (100%) diff --git a/acl_diff_test.go b/acl_diff_test.go index 5d28975..3f2f068 100644 --- a/acl_diff_test.go +++ b/acl_diff_test.go @@ -1,111 +1,109 @@ -package yext_test +package yext import ( "fmt" "reflect" "testing" - - "github.com/yext/yext-go" ) func TestACL_Diff(t *testing.T) { tests := []struct { name string - aclA yext.ACL - aclB yext.ACL - wantDelta *yext.ACL + aclA ACL + aclB ACL + wantDelta *ACL wantDiff bool }{ { name: "Identical ACLs", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclB: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, wantDelta: nil, wantDiff: false, }, { name: "Different Roles in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + aclB: ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - wantDelta: &yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + wantDelta: &ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, }, wantDiff: true, }, { name: "Different 'On' params in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclB: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "123456", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - wantDelta: &yext.ACL{ + wantDelta: &ACL{ On: "123456", }, wantDiff: true, }, { name: "Different 'AccessOn' params in ACL", - aclA: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclA: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - aclB: yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclB: ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - wantDelta: &yext.ACL{ - AccessOn: yext.ACCESS_LOCATION, + wantDelta: &ACL{ + AccessOn: ACCESS_LOCATION, }, wantDiff: true, }, @@ -121,47 +119,47 @@ func TestACL_Diff(t *testing.T) { func TestACLList_Diff(t *testing.T) { tests := []struct { name string - aclListA yext.ACLList - aclListB yext.ACLList - wantDelta yext.ACLList + aclListA ACLList + aclListB ACLList + wantDelta ACLList wantDiff bool }{ { name: "Identical ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDelta: nil, @@ -169,40 +167,40 @@ func TestACLList_Diff(t *testing.T) { }, { name: "Identical ACLs in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, }, wantDelta: nil, @@ -210,214 +208,214 @@ func TestACLList_Diff(t *testing.T) { }, { name: "Different Length in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + wantDelta: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDiff: true, }, { name: "Different Items in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("33"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("33"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("44"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("44"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("33"), - Name: yext.String("Example Role"), + wantDelta: ACLList{ + ACL{ + Role: Role{ + Id: String("33"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("44"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("44"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDiff: true, }, { name: "Some Identical and Some Different Items in ACLLists", - aclListA: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListA: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - aclListB: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + aclListB: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, - wantDelta: yext.ACLList{ - yext.ACL{ - Role: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + wantDelta: ACLList{ + ACL{ + Role: Role{ + Id: String("3"), + Name: String("Example Role"), }, On: "12345", - AccessOn: yext.ACCESS_FOLDER, + AccessOn: ACCESS_FOLDER, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role Two"), + ACL{ + Role: Role{ + Id: String("4"), + Name: String("Example Role Two"), }, On: "123456", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, - yext.ACL{ - Role: yext.Role{ - Id: yext.String("5"), - Name: yext.String("Example Role Three"), + ACL{ + Role: Role{ + Id: String("5"), + Name: String("Example Role Three"), }, On: "1234567", - AccessOn: yext.ACCESS_LOCATION, + AccessOn: ACCESS_LOCATION, }, }, wantDiff: true, diff --git a/cftasset_service.go b/cftasset_service.go index 932bec9..608fe0e 100644 --- a/cftasset_service.go +++ b/cftasset_service.go @@ -33,7 +33,7 @@ func (a *CFTAssetService) RegisterAssetValue(t AssetType, assetValue interface{} } func (a *CFTAssetService) CreateAssetValue(t AssetType) (interface{}, error) { - return a.registry.Create(string(t)) + return a.registry.Initialize(string(t)) } func (a *CFTAssetService) toAssetsWithValues(assets []*CFTAsset) error { diff --git a/client.go b/client.go index a186ac4..1bfeb85 100644 --- a/client.go +++ b/client.go @@ -29,6 +29,7 @@ type Client struct { LocationService *LocationService ListService *ListService + LocationCustomFieldService *LocationCustomFieldService CustomFieldService *CustomFieldService FolderService *FolderService CategoryService *CategoryService @@ -48,6 +49,7 @@ func NewClient(config *Config) *Client { c.LocationService = &LocationService{client: c} c.ListService = &ListService{client: c} + c.LocationCustomFieldService = &LocationCustomFieldService{client: c} c.CustomFieldService = &CustomFieldService{client: c} c.FolderService = &FolderService{client: c} c.CategoryService = &CategoryService{client: c} diff --git a/customfield.go b/customfield.go index 175c38f..a65bc35 100644 --- a/customfield.go +++ b/customfield.go @@ -1,11 +1,5 @@ package yext -import ( - "fmt" -) - -type CustomFieldType string - const ( CUSTOMFIELDTYPE_YESNO = "BOOLEAN" CUSTOMFIELDTYPE_SINGLELINETEXT = "TEXT" @@ -21,30 +15,18 @@ const ( CUSTOMFIELDTYPE_VIDEO = "VIDEO" CUSTOMFIELDTYPE_HOURS = "HOURS" CUSTOMFIELDTYPE_DAILYTIMES = "DAILY_TIMES" - CUSTOMFIELDTYPE_LOCATIONLIST = "LOCATION_LIST" - // not sure what to do with "DAILYTIMES", omitting + CUSTOMFIELDTYPE_ENTITYLIST = "ENTITY_LIST" ) -var ( - UnsetPhotoValue = (*CustomLocationPhoto)(nil) -) - -type CustomFieldOption struct { - Key string `json:"key,omitempty"` - Value string `json:"value"` -} - -// CustomField is the representation of a Custom Field definition in Yext Location Manager. -// For details see https://www.yext.com/support/platform-api/#Administration_API/Custom_Fields.htm type CustomField struct { Id *string `json:"id,omitempty"` Type string `json:"type"` Name string `json:"name"` - Options []CustomFieldOption `json:"options,omitempty"` // Only present for multi-option custom fields + Options []CustomFieldOption `json:"options,omitempty"` // Only present for option custom fields Group string `json:"group"` Description string `json:"description"` AlternateLanguageBehaviour string `json:"alternateLanguageBehavior"` - EntityAvailability []string `json:"entityAvailability"` + EntityAvailability []EntityType `json:"entityAvailability"` } func (c CustomField) GetId() string { @@ -53,215 +35,3 @@ func (c CustomField) GetId() string { } return *c.Id } - -type CustomFieldValue interface { - CustomFieldTag() string -} - -type YesNo bool - -func (y YesNo) CustomFieldTag() string { - return CUSTOMFIELDTYPE_YESNO -} - -type SingleLineText string - -func (s SingleLineText) CustomFieldTag() string { - return CUSTOMFIELDTYPE_SINGLELINETEXT -} - -type MultiLineText string - -func (m MultiLineText) CustomFieldTag() string { - return CUSTOMFIELDTYPE_MULTILINETEXT -} - -type Url string - -func (u Url) CustomFieldTag() string { - return CUSTOMFIELDTYPE_URL -} - -type Date string - -func (d Date) CustomFieldTag() string { - return CUSTOMFIELDTYPE_DATE -} - -type Number string - -func (n Number) CustomFieldTag() string { - return CUSTOMFIELDTYPE_NUMBER -} - -type OptionField interface { - CustomFieldValue - SetOptionId(id string) - UnsetOptionId(id string) - IsOptionIdSet(id string) bool -} - -type SingleOption string - -func (s SingleOption) CustomFieldTag() string { - return CUSTOMFIELDTYPE_SINGLEOPTION -} - -func (s *SingleOption) SetOptionId(id string) { - *s = SingleOption(id) -} - -func (s *SingleOption) UnsetOptionId(id string) { - if string(*s) == id { - *s = SingleOption("") - } -} - -func (s *SingleOption) IsOptionIdSet(id string) bool { - return *s == SingleOption(id) -} - -type MultiOption UnorderedStrings - -func (m MultiOption) CustomFieldTag() string { - return CUSTOMFIELDTYPE_MULTIOPTION -} - -func (m MultiOption) Equal(c Comparable) bool { - var n MultiOption - switch v := c.(type) { - case MultiOption: - n = v - case *MultiOption: - n = *v - default: - panic(fmt.Errorf("%v is not a MultiOption is %T", c, c)) - } - if len(m) != len(n) { - return false - } - a := UnorderedStrings(m) - b := UnorderedStrings(n) - return (&a).Equal(&b) - -} - -func (m *MultiOption) SetOptionId(id string) { - if !m.IsOptionIdSet(id) { - *m = append(*m, id) - } -} - -func (m *MultiOption) UnsetOptionId(id string) { - if m.IsOptionIdSet(id) { - t := []string(*m) - indexOfTarget := -1 - for i := 0; i < len(*m); i++ { - if t[i] == id { - indexOfTarget = i - } - } - if indexOfTarget >= 0 { - *m = append(t[:indexOfTarget], t[indexOfTarget+1:]...) - } - } -} - -func (m *MultiOption) IsOptionIdSet(id string) bool { - for _, option := range *m { - if option == id { - return true - } - } - return false -} - -type TextList []string - -func (t TextList) CustomFieldTag() string { - return CUSTOMFIELDTYPE_TEXTLIST -} - -type LocationList UnorderedStrings - -func (l LocationList) CustomFieldTag() string { - return CUSTOMFIELDTYPE_LOCATIONLIST -} - -func (m LocationList) Equal(c Comparable) bool { - var n LocationList - switch v := c.(type) { - case LocationList: - n = v - case *LocationList: - n = *v - default: - panic(fmt.Errorf("%v is not a LocationList is %T", c, c)) - } - if len(m) != len(n) { - return false - } - a := UnorderedStrings(m) - b := UnorderedStrings(n) - return (&a).Equal(&b) -} - -type CustomLocationPhoto struct { - Url string `json:"url,omitempty"` - Description string `json:"description,omitempty"` - Details string `json:"details,omitempty"` - ClickThroughURL string `json:"clickthroughUrl,omitempty"` -} - -func (p *CustomLocationPhoto) CustomFieldTag() string { - return CUSTOMFIELDTYPE_PHOTO -} - -type Gallery []*CustomLocationPhoto - -func (g *Gallery) CustomFieldTag() string { - return CUSTOMFIELDTYPE_GALLERY -} - -type Video struct { - Description string `json:"description"` - Url string `json:"url"` -} - -type VideoGallery []Video - -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 CustomLocationHours struct { - AdditionalText string `json:"additionalHoursText,omitempty"` - Hours string `json:"hours,omitempty"` - HolidayHours []LocationHolidayHours `json:"holidayHours,omitempty"` -} - -func (h CustomLocationHours) CustomFieldTag() string { - return CUSTOMFIELDTYPE_HOURS -} - -// DailyTimesCustom is the DailyTimes custom field format used by locations API -// Entities API uses the new DailyTimes struct -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"` - Tuesday string `json:"tuesday,omitempty"` - Wednesday string `json:"wednesday,omitempty"` - Thursday string `json:"thursday,omitempty"` - Friday string `json:"friday,omitempty"` - Saturday string `json:"saturday,omitempty"` -} diff --git a/customfield_service.go b/customfield_service.go index 4c35533..be0d9c4 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -2,17 +2,53 @@ package yext import ( "encoding/json" - "errors" "fmt" - "strconv" ) const customFieldPath = "customfields" var CustomFieldListMaxLimit = 1000 -type CustomFieldManager struct { - CustomFields []*CustomField +type CustomFieldService struct { + CustomFieldManager *CustomFieldManager + client *Client +} + +type CustomFieldResponse struct { + Count int `json:"count"` + CustomFields []*CustomField `json:"customFields"` +} + +func (s *CustomFieldService) ListAll() ([]*CustomField, error) { + var customFields []*CustomField + var lr listRetriever = func(opts *ListOptions) (int, int, error) { + cfr, _, err := s.List(opts) + if err != nil { + return 0, 0, err + } + customFields = append(customFields, cfr.CustomFields...) + return len(cfr.CustomFields), cfr.Count, err + } + + if err := listHelper(lr, &ListOptions{Limit: CustomFieldListMaxLimit}); err != nil { + return nil, err + } + return customFields, nil + +} + +func (s *CustomFieldService) List(opts *ListOptions) (*CustomFieldResponse, *Response, error) { + requrl, err := addListOptions(customFieldPath, opts) + if err != nil { + return nil, nil, err + } + + v := &CustomFieldResponse{} + r, err := s.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + return v, r, nil } func (s *CustomFieldService) Create(cf *CustomField) (*Response, error) { @@ -44,181 +80,8 @@ func (s *CustomFieldService) Edit(cf *CustomField) (*Response, error) { return s.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", customFieldPath, cf.GetId()), asMap, nil) } -func (s *CustomFieldService) Delete(customFieldId string) (*Response, error) { - return s.client.DoRequest("DELETE", fmt.Sprintf("%s/%s", customFieldPath, customFieldId), nil) -} - -func (c *CustomFieldManager) Get(name string, loc *Location) (interface{}, error) { - if loc == nil || loc.CustomFields == nil { - return nil, nil - } - - var ( - field *CustomField - err error - ) - - if field, err = c.CustomField(name); err != nil { - return nil, err - } - - return loc.CustomFields[field.GetId()], nil -} - -func (c *CustomFieldManager) MustGet(name string, loc *Location) interface{} { - if ret, err := c.Get(name, loc); err != nil { - panic(err) - } else { - return ret - } -} - -func (c *CustomFieldManager) IsOptionSet(fieldName string, optionName string, loc *Location) (bool, error) { - var ( - field interface{} - err error - of OptionField - id string - ) - - if field, err = c.Get(fieldName, loc); err != nil { - return false, err - } - - if field == nil { - return false, nil - } - switch field.(type) { - case nil: - return false, nil - case MultiOption: - mo := field.(MultiOption) - of = &mo - case *MultiOption: - of = field.(*MultiOption) - case SingleOption: - so := field.(SingleOption) - of = &so - case *SingleOption: - of = field.(*SingleOption) - default: - return false, fmt.Errorf("'%s' is not an OptionField custom field, is %T", fieldName, field) - } - - if id, err = c.CustomFieldOptionId(fieldName, optionName); err != nil { - return false, err - } - - return of.IsOptionIdSet(id), nil -} - -func (c *CustomFieldManager) MustIsOptionSet(fieldName string, optionName string, loc *Location) bool { - if set, err := c.IsOptionSet(fieldName, optionName, loc); err != nil { - panic(err) - } else { - return set - } -} - -func (c *CustomFieldManager) SetOption(fieldName string, optionName string, loc *Location) (*Location, error) { - var ( - field interface{} - err error - of OptionField - ok bool - id string - ) - - if field, err = c.Get(fieldName, loc); err != nil { - return loc, err - } - - if field == nil { - var cf *CustomField - if cf, err = c.CustomField(fieldName); err != nil { - return loc, fmt.Errorf("problem getting '%s': %v", fieldName, err) - } - switch cf.Type { - case CUSTOMFIELDTYPE_MULTIOPTION: - of = new(MultiOption) - case CUSTOMFIELDTYPE_SINGLEOPTION: - of = new(SingleOption) - default: - return loc, fmt.Errorf("'%s' is not an OptionField is '%s'", cf.Name, cf.Type) - } - } else if of, ok = field.(OptionField); !ok { - return loc, fmt.Errorf("'%s': %v is not an OptionField custom field is %T", fieldName, field, field) - } - - if id, err = c.CustomFieldOptionId(fieldName, optionName); err != nil { - return loc, err - } - - of.SetOptionId(id) - return c.Set(fieldName, of, loc) -} - -func (c *CustomFieldManager) MustSetOption(fieldName string, optionName string, loc *Location) *Location { - if loc, err := c.SetOption(fieldName, optionName, loc); err != nil { - panic(err) - } else { - return loc - } -} - -func (c *CustomFieldManager) UnsetOption(fieldName string, optionName string, loc *Location) (*Location, error) { - var ( - field interface{} - err error - id string - ) - - if field, err = c.Get(fieldName, loc); err != nil { - return loc, err - } - - if field == nil { - return loc, fmt.Errorf("'%s' is not currently set", fieldName) - } - - option, ok := field.(OptionField) - if !ok { - return loc, fmt.Errorf("'%s' is not an OptionField custom field", fieldName) - } - - if id, err = c.CustomFieldOptionId(fieldName, optionName); err != nil { - return loc, err - } - - option.UnsetOptionId(id) - return c.Set(fieldName, option, loc) -} - -func (c *CustomFieldManager) MustUnsetOption(fieldName string, optionName string, loc *Location) *Location { - if loc, err := c.UnsetOption(fieldName, optionName, loc); err != nil { - panic(err) - } else { - return loc - } -} - -// TODO: Why does this return a location? -// TODO: Should we validate the the type we received matches the type of the field? Probably. -func (c *CustomFieldManager) Set(name string, value CustomFieldValue, loc *Location) (*Location, error) { - field, err := c.CustomField(name) - if err != nil { - return loc, err - } - loc.CustomFields[field.GetId()] = value - return loc, nil -} - -func (c *CustomFieldManager) MustSet(name string, value CustomFieldValue, loc *Location) *Location { - if loc, err := c.Set(name, value, loc); err != nil { - panic(err) - } else { - return loc - } +type CustomFieldManager struct { + CustomFields []*CustomField } func (c *CustomFieldManager) CustomField(name string) (*CustomField, error) { @@ -304,315 +167,44 @@ func (c *CustomFieldManager) MustCustomFieldOptionId(fieldName, optionName strin } } -type CustomFieldService struct { - CustomFieldManager *CustomFieldManager - client *Client -} - -type CustomFieldResponse struct { - Count int `json:"count"` - CustomFields []*CustomField `json:"customFields"` -} - -func (s *CustomFieldService) ListAll() ([]*CustomField, error) { - var customFields []*CustomField - var lr listRetriever = func(opts *ListOptions) (int, int, error) { - cfr, _, err := s.List(opts) - if err != nil { - return 0, 0, err - } - customFields = append(customFields, cfr.CustomFields...) - return len(cfr.CustomFields), cfr.Count, err - } - - if err := listHelper(lr, &ListOptions{Limit: CustomFieldListMaxLimit}); err != nil { - return nil, err - } else { - return customFields, nil - } +func (c *CustomFieldManager) MustSingleOptionId(fieldName, optionName string) **string { + id := c.MustCustomFieldOptionId(fieldName, optionName) + return NullableString(id) } -func (s *CustomFieldService) List(opts *ListOptions) (*CustomFieldResponse, *Response, error) { - requrl, err := addListOptions(customFieldPath, opts) - if err != nil { - return nil, nil, err - } - - v := &CustomFieldResponse{} - r, err := s.client.DoRequest("GET", requrl, v) - if err != nil { - return nil, r, err - } - return v, r, nil +func (c *CustomFieldManager) MustIsSingleOptionSet(fieldName, optionName string, setOptionId **string) bool { + id := c.MustCustomFieldOptionId(fieldName, optionName) + return GetNullableString(setOptionId) == id } -func (s *CustomFieldService) CacheCustomFields() ([]*CustomField, error) { - cfs, err := s.ListAll() - if err != nil { - return nil, err - } - - s.CustomFieldManager = &CustomFieldManager{CustomFields: cfs} - return s.CustomFieldManager.CustomFields, nil +func (c *CustomFieldManager) NullSingleOption() **string { + return NullString() } -func (s *CustomFieldService) MustCacheCustomFields() []*CustomField { - slice, err := s.CacheCustomFields() - if err != nil { - panic(err) +func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames ...string) *[]string { + var optionIds = []string{} + for _, optionName := range optionNames { + id := c.MustCustomFieldOptionId(fieldName, optionName) + optionIds = append(optionIds, id) } - return slice + return &optionIds } -func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[string]interface{}, error) { - typefor := func(id string) string { - for _, cf := range cfs { - if cf.GetId() == id { - return cf.Type - } - } - return "" +func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *[]string) bool { + if setOptionIds == nil { + return false } - - parsed := map[string]interface{}{} - - for k, v := range cfraw { - if _, ok := v.(CustomFieldValue); ok { - parsed[k] = v - continue - } - - var newval interface{} - - switch typefor(k) { - case CUSTOMFIELDTYPE_YESNO: - if typedVal, ok := v.(bool); ok { - newval = YesNo(typedVal) - } else if typedVal, ok := v.(string); ok { - b, err := strconv.ParseBool(typedVal) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as yes/no %v", v, err) - } - newval = YesNo(b) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as yes/no, expected bool got %T", v, v) - } - case CUSTOMFIELDTYPE_NUMBER: - if typedVal, ok := v.(string); ok { - newval = Number(typedVal) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as number, expected string got %T", v, v) - } - case CUSTOMFIELDTYPE_SINGLELINETEXT: - if typedVal, ok := v.(string); ok { - newval = SingleLineText(typedVal) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as single-line text, expected string got %T", v, v) - } - case CUSTOMFIELDTYPE_MULTILINETEXT: - if typedVal, ok := v.(string); ok { - newval = MultiLineText(typedVal) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as multi-line text, expected string got %T", v, v) - } - case CUSTOMFIELDTYPE_SINGLEOPTION: - if typedVal, ok := v.(string); ok { - newval = GetSingleOptionPointer(SingleOption(typedVal)) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as single-option field, expected string got %T", v, v) - } - case CUSTOMFIELDTYPE_URL: - if typedVal, ok := v.(string); ok { - newval = Url(typedVal) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as url field, expected string got %T", v, v) - } - case CUSTOMFIELDTYPE_DATE: - if typedVal, ok := v.(string); ok { - newval = Date(typedVal) - } else { - return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as date field, expected string got %T", v, v) - } - case CUSTOMFIELDTYPE_TEXTLIST: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Text List Field %v", v, err) - } - var cf TextList - err = json.Unmarshal(asJSON, &cf) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Text List Field %v", v, err) - } - newval = cf - case CUSTOMFIELDTYPE_MULTIOPTION: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Multi-Option Field %v", v, err) - } - var cf MultiOption - err = json.Unmarshal(asJSON, &cf) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Multi-Option Field %v", v, err) - } - newval = cf - case CUSTOMFIELDTYPE_PHOTO: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Photo Field %v", v, err) - } - var cfp *CustomLocationPhoto - err = json.Unmarshal(asJSON, &cfp) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Photo Field %v", v, err) - } - newval = cfp - case CUSTOMFIELDTYPE_GALLERY: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Photo Gallery Field %v", v, err) - } - var g Gallery - err = json.Unmarshal(asJSON, &g) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Photo Gallery Field %v", v, err) - } - newval = g - case CUSTOMFIELDTYPE_VIDEO: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Video Field %v", v, err) - } - var cf Video - err = json.Unmarshal(asJSON, &cf) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Video Field %v", v, err) - } - newval = cf - case CUSTOMFIELDTYPE_HOURS: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Hours Field %v", v, err) - } - var cf CustomLocationHours - err = json.Unmarshal(asJSON, &cf) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Hours Field %v", v, err) - } - newval = cf - case CUSTOMFIELDTYPE_DAILYTIMES: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for DailyT imes Field %v", v, err) - } - var cf DailyTimesCustom - err = json.Unmarshal(asJSON, &cf) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Daily Times Field %v", v, err) - } - newval = cf - case CUSTOMFIELDTYPE_LOCATIONLIST: - asJSON, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Location List Field %v", v, err) - } - var cf LocationList - err = json.Unmarshal(asJSON, &cf) - if err != nil { - return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Location List Field %v", v, err) - } - newval = cf - default: - newval = v - } - - parsed[k] = newval - } - - return parsed, nil -} - -// 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)) + optionId := c.MustCustomFieldOptionId(fieldName, optionName) + for _, v := range *setOptionIds { + if v == optionId { + return true } } - return nil + return false } -func (c *CustomFieldManager) GetBool(name string, loc *Location) (bool, error) { - value, err := c.Get(name, loc) - if err != nil { - return false, err - } - - if value == nil { - return false, nil - } - - switch t := value.(type) { - case YesNo: - return bool(value.(YesNo)), nil - case *YesNo: - return bool(*value.(*YesNo)), nil - default: - return false, fmt.Errorf("GetBool failure: Field '%v' is not of a YesNo type", t) - } -} - -func (c *CustomFieldManager) MustGetBool(name string, loc *Location) bool { - if ret, err := c.GetBool(name, loc); err != nil { - panic(err) - } else { - return ret - } -} - -// GetStringAliasCustomField returns the string value from a string type alias -// custom field. It will return an error if the field is not a string type. -func (c *CustomFieldManager) GetString(name string, loc *Location) (string, error) { - fv, err := c.Get(name, loc) - if err != nil { - return "", err - } - if fv == nil { - return "", nil - } - switch fv.(type) { - case SingleLineText: - return string(fv.(SingleLineText)), nil - case *SingleLineText: - return string(*fv.(*SingleLineText)), nil - case MultiLineText: - return string(fv.(MultiLineText)), nil - case *MultiLineText: - return string(*fv.(*MultiLineText)), nil - case Url: - return string(fv.(Url)), nil - case *Url: - return string(*fv.(*Url)), nil - case Date: - return string(fv.(Date)), nil - case *Date: - return string(*fv.(*Date)), nil - case Number: - return string(fv.(Number)), nil - case *Number: - return string(*fv.(*Number)), nil - case SingleOption: - if string(fv.(SingleOption)) == "" { - return "", nil - } - return c.CustomFieldOptionName(name, string(fv.(SingleOption))) - case *SingleOption: - if string(*fv.(*SingleOption)) == "" { - return "", nil - } - return c.CustomFieldOptionName(name, string(*fv.(*SingleOption))) - default: - return "", fmt.Errorf("%s is not a string custom field type, is %T", name, fv) - } +func (c *CustomFieldManager) NullMultiOption() *[]string { + return &[]string{} } func (c *CustomFieldManager) CustomFieldOptionName(cfName string, optionId string) (string, error) { @@ -636,171 +228,20 @@ func (c *CustomFieldManager) MustCustomFieldOptionName(fieldName, optionId strin } } -func (c *CustomFieldManager) MustGetString(name string, loc *Location) string { - if ret, err := c.GetString(name, loc); err != nil { - panic(err) - } else { - return ret - } -} - -// GetStringArrayAliasCustomField returns the string array value from a string array -// type alias custom field. It will return an error if the field is not a string -// array type. -func (c *CustomFieldManager) GetStringSlice(name string, loc *Location) ([]string, error) { - fv, err := c.Get(name, loc) +func (c *CustomFieldService) CacheCustomFields() ([]*CustomField, error) { + cfs, err := c.ListAll() if err != nil { return nil, err } - if fv == nil { - return nil, nil - } - switch fv.(type) { - case UnorderedStrings: - return []string(fv.(UnorderedStrings)), nil - case *UnorderedStrings: - return []string(*fv.(*UnorderedStrings)), nil - case LocationList: - return []string(fv.(LocationList)), nil - case *LocationList: - return []string(*fv.(*LocationList)), nil - case TextList: - return []string(fv.(TextList)), nil - case *TextList: - return []string(*fv.(*TextList)), nil - case MultiOption: - return c.CustomFieldOptionNames(name, []string(fv.(MultiOption))) - case *MultiOption: - return c.CustomFieldOptionNames(name, []string(*fv.(*MultiOption))) - default: - return nil, fmt.Errorf("%s is not a string array custom field type, is %T", name, fv) - } -} - -func (c *CustomFieldManager) CustomFieldOptionNames(cfName string, optionIds []string) ([]string, error) { - var optionNames = []string{} - for _, optionId := range optionIds { - optionName, err := c.CustomFieldOptionName(cfName, optionId) - if err != nil { - return nil, err - } - optionNames = append(optionNames, optionName) - } - return optionNames, nil -} - -func (c *CustomFieldManager) MustGetStringSlice(name string, loc *Location) []string { - if ret, err := c.GetStringSlice(name, loc); err != nil { - panic(err) - } else { - return ret - } -} - -func (c *CustomFieldManager) SetBool(name string, value bool, loc *Location) error { - field, err := c.CustomField(name) - if err != nil { - return err - } - - if field.Type != CUSTOMFIELDTYPE_YESNO { - return fmt.Errorf("SetBool failure: custom field '%v' is of type '%v' and not boolean", name, field.Type) - } - - loc.CustomFields[field.GetId()] = YesNo(value) - return nil -} -func (c *CustomFieldManager) MustSetBool(name string, value bool, loc *Location) { - if err := c.SetBool(name, value, loc); err != nil { - panic(err) - } else { - return - } + c.CustomFieldManager = &CustomFieldManager{CustomFields: cfs} + return c.CustomFieldManager.CustomFields, nil } -func (c *CustomFieldManager) SetStringSlice(name string, value []string, loc *Location) error { - field, err := c.CustomField(name) +func (c *CustomFieldService) MustCacheCustomFields() []*CustomField { + slice, err := c.CacheCustomFields() if err != nil { - return err - } - - switch field.Type { - case CUSTOMFIELDTYPE_MULTIOPTION: - for _, element := range value { - c.MustSetOption(name, element, loc) - } - return nil - case CUSTOMFIELDTYPE_TEXTLIST: - loc.CustomFields[field.GetId()] = TextList(value) - return nil - case CUSTOMFIELDTYPE_LOCATIONLIST: - loc.CustomFields[field.GetId()] = UnorderedStrings(value) - return nil - default: - return fmt.Errorf("SetStringSlice failure: custom field '%v' is of type '%v' and can not take a string slice", name, field.Type) - } -} - -func (c *CustomFieldManager) MustSetStringSlice(name string, value []string, loc *Location) { - if err := c.SetStringSlice(name, value, loc); err != nil { panic(err) - } else { - return - } -} - -func (c *CustomFieldManager) SetString(name string, value string, loc *Location) error { - field, err := c.CustomField(name) - if err != nil { - return err } - - switch field.Type { - case CUSTOMFIELDTYPE_SINGLEOPTION: - c.MustSetOption(name, value, loc) - return nil - case CUSTOMFIELDTYPE_SINGLELINETEXT: - loc.CustomFields[field.GetId()] = SingleLineText(value) - return nil - case CUSTOMFIELDTYPE_MULTILINETEXT: - loc.CustomFields[field.GetId()] = MultiLineText(value) - return nil - case CUSTOMFIELDTYPE_URL: - loc.CustomFields[field.GetId()] = Url(value) - return nil - case CUSTOMFIELDTYPE_DATE: - loc.CustomFields[field.GetId()] = Date(value) - return nil - case CUSTOMFIELDTYPE_NUMBER: - loc.CustomFields[field.GetId()] = Number(value) - return nil - default: - return fmt.Errorf("SetString failure: custom field '%v' is of type '%v' and can not take a string", name, field.Type) - } -} - -func (c *CustomFieldManager) MustSetString(name string, value string, loc *Location) { - Must(c.SetString(name, value, loc)) -} - -func (c *CustomFieldManager) SetPhoto(name string, v *CustomLocationPhoto, loc *Location) error { - _, err := c.Set(name, v, loc) - return err -} - -func (c *CustomFieldManager) UnsetPhoto(name string, loc *Location) error { - return c.SetPhoto(name, UnsetPhotoValue, loc) -} - -func (c *CustomFieldManager) MustSetPhoto(name string, v *CustomLocationPhoto, loc *Location) { - Must(c.SetPhoto(name, v, loc)) -} - -func (c *CustomFieldManager) MustUnsetPhoto(name string, loc *Location) { - Must(c.SetPhoto(name, UnsetPhotoValue, loc)) -} - -func GetSingleOptionPointer(option SingleOption) *SingleOption { - return &option + return slice } diff --git a/customfield_service_test.go b/customfield_service_test.go index b0edc2a..cbac033 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -3,136 +3,10 @@ package yext import ( "encoding/json" "net/http" - "reflect" "strconv" "testing" ) -func parseAs(cftype string, rawval interface{}) (interface{}, error) { - cfs := []*CustomField{ - &CustomField{ - Id: String("123"), - Type: cftype, - }, - } - - cfsraw := map[string]interface{}{ - "123": rawval, - } - - newcfs, err := ParseCustomFields(cfsraw, cfs) - return newcfs["123"], err -} - -type customFieldParseTest struct { - TypeName string - Raw interface{} - Expected interface{} -} - -func runParseTest(t *testing.T, index int, c customFieldParseTest) { - cf, err := parseAs(c.TypeName, c.Raw) - if err != nil { - t.Error(err) - return - } - var ( - cfType = reflect.TypeOf(cf).String() - expectType = reflect.TypeOf(c.Expected).String() - ) - - if cfType != expectType { - t.Errorf("test #%d (%s) failed:\nexpected type %s\ngot type %s", index, c.TypeName, expectType, cfType) - } - - if !reflect.DeepEqual(cf, c.Expected) { - t.Errorf("test #%d (%s) failed\nexpected value %v\ngot value %v", index, c.TypeName, c.Expected, cf) - } -} - -var ( - customPhotoRaw = map[string]interface{}{ - "details": "A great picture", - "description": "This is a picture of an awesome event", - "clickthroughUrl": "https://yext.com/event", - "url": "https://mktgcdn.com/awesome.jpg", - } - // hoursRaw is in the format used by HoursCustom for location-service - hoursRaw = 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", - } - // dailyTimesRaw is in the format used by DailyTimesCustom for location-service - dailyTimesRaw = map[string]interface{}{ - "dailyTimes": "1:10:00;2:4:00;3:5:00;4:6:00;5:7:00;6:8:00;7:9:00", - } - parseTests = []customFieldParseTest{ - customFieldParseTest{"BOOLEAN", false, YesNo(false)}, - customFieldParseTest{"BOOLEAN", "false", YesNo(false)}, - customFieldParseTest{"NUMBER", "12345", Number("12345")}, - customFieldParseTest{"TEXT", "foo", SingleLineText("foo")}, - customFieldParseTest{"MULTILINE_TEXT", "foo", MultiLineText("foo")}, - customFieldParseTest{"SINGLE_OPTION", "foo", GetSingleOptionPointer(SingleOption("foo"))}, - customFieldParseTest{"URL", "foo", Url("foo")}, - customFieldParseTest{"DATE", "foo", Date("foo")}, - customFieldParseTest{"TEXT_LIST", []string{"a", "b", "c"}, TextList([]string{"a", "b", "c"})}, - customFieldParseTest{"TEXT_LIST", []interface{}{"a", "b", "c"}, TextList([]string{"a", "b", "c"})}, - customFieldParseTest{"MULTI_OPTION", []string{"a", "b", "c"}, MultiOption([]string{"a", "b", "c"})}, - customFieldParseTest{"MULTI_OPTION", []interface{}{"a", "b", "c"}, MultiOption([]string{"a", "b", "c"})}, - customFieldParseTest{"PHOTO", customPhotoRaw, &CustomLocationPhoto{ - Url: "https://mktgcdn.com/awesome.jpg", - Description: "This is a picture of an awesome event", - Details: "A great picture", - ClickThroughURL: "https://yext.com/event", - }}, - customFieldParseTest{"PHOTO", nil, (*CustomLocationPhoto)(nil)}, - customFieldParseTest{"GALLERY", []interface{}{customPhotoRaw}, Gallery{ - &CustomLocationPhoto{ - Url: "https://mktgcdn.com/awesome.jpg", - Description: "This is a picture of an awesome event", - Details: "A great picture", - ClickThroughURL: "https://yext.com/event", - }, - }}, - customFieldParseTest{"VIDEO", videoRaw, Video{ - Url: "http://www.youtube.com/watch?v=M80FTIcEgZM", - Description: "An example caption for a video", - }}, - customFieldParseTest{"HOURS", hoursRaw, CustomLocationHours{ - 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{"DAILY_TIMES", dailyTimesRaw, DailyTimesCustom{ - DailyTimes: "1:10:00;2:4:00;3:5:00;4:6:00;5:7:00;6:8:00;7:9:00", - }}, - customFieldParseTest{"LOCATION_LIST", []string{"a", "b", "c"}, LocationList([]string{"a", "b", "c"})}, - } -) - func makeCustomFields(n int) []*CustomField { var cfs []*CustomField @@ -144,7 +18,7 @@ func makeCustomFields(n int) []*CustomField { return cfs } -func TestListAll(t *testing.T) { +func TestCustomFieldListAll(t *testing.T) { maxLimit := strconv.Itoa(CustomFieldListMaxLimit) type req struct { @@ -216,7 +90,7 @@ func TestListAll(t *testing.T) { } } -func TestListMismatchCount(t *testing.T) { +func TestCustomFieldListMismatchCount(t *testing.T) { setup() defer teardown() @@ -235,7 +109,7 @@ func TestListMismatchCount(t *testing.T) { } } -func TestMustCache(t *testing.T) { +func TestMustCacheCustomFields(t *testing.T) { setup() defer teardown() @@ -253,263 +127,64 @@ func TestMustCache(t *testing.T) { } } -func TestParsing(t *testing.T) { - for i, testData := range parseTests { - runParseTest(t, i, testData) - } -} - -func TestParseLeaveUnknownTypes(t *testing.T) { - type peanut []string - cf, err := parseAs("BLAH", peanut([]string{"a", "b", "c"})) - if err != nil { - t.Error(err) - } - - if _, ok := cf.(peanut); !ok { - t.Errorf("Expected type peanut, got type %T", cf) - } -} - -func TestGetString(t *testing.T) { - var cfManager = &CustomFieldManager{ - CustomFields: []*CustomField{ - &CustomField{ - Name: "Single Line Text", - Id: String("SingleLineText"), - Type: CUSTOMFIELDTYPE_SINGLELINETEXT, - }, - &CustomField{ - Name: "Multi Line Text", - Id: String("MultiLineText"), - Type: CUSTOMFIELDTYPE_MULTILINETEXT, - }, - &CustomField{ - Name: "Date", - Id: String("Date"), - Type: CUSTOMFIELDTYPE_DATE, - }, - &CustomField{ - Name: "Number", - Id: String("Number"), - Type: CUSTOMFIELDTYPE_NUMBER, - }, - &CustomField{ - Name: "Single Option", - Id: String("SingleOption"), - Type: CUSTOMFIELDTYPE_SINGLEOPTION, - Options: []CustomFieldOption{ - CustomFieldOption{ - Key: "SingleOptionOneKey", - Value: "Single Option One Value", - }, +var cfm = &CustomFieldManager{ + CustomFields: []*CustomField{ + &CustomField{ + Name: "My Favorite Colors", + Type: CUSTOMFIELDTYPE_MULTIOPTION, + Id: String("c_myFavoriteColors"), + Options: []CustomFieldOption{ + CustomFieldOption{ + Key: "c_blue", + Value: "Blue", + }, + CustomFieldOption{ + Key: "c_red", + Value: "Red", }, }, - &CustomField{ - Name: "Url", - Id: String("Url"), - Type: CUSTOMFIELDTYPE_URL, + }, + &CustomField{ + Name: "My Favorite Food", + Type: CUSTOMFIELDTYPE_MULTIOPTION, + Id: String("c_myFavoriteFood"), + Options: []CustomFieldOption{ + CustomFieldOption{ + Key: "c_cheese", + Value: "Cheese", + }, + CustomFieldOption{ + Key: "c_olives", + Value: "Olives", + }, }, }, - } + }, +} - loc := &Location{ - CustomFields: map[string]interface{}{ - cfManager.MustCustomFieldId("Single Line Text"): SingleLineText("Single Line Text Value"), - cfManager.MustCustomFieldId("Multi Line Text"): MultiLineText("Multi Line Text Value"), - cfManager.MustCustomFieldId("Date"): Date("04/16/2018"), - cfManager.MustCustomFieldId("Number"): Number("2"), - cfManager.MustCustomFieldId("Single Option"): GetSingleOptionPointer(SingleOption(cfManager.MustCustomFieldOptionId("Single Option", "Single Option One Value"))), - cfManager.MustCustomFieldId("Url"): Url("www.url.com"), - }, +func TestMustIsMultiOptionSet(t *testing.T) { + if !cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{"c_red"}) { + t.Error("TestMustIsMultiOptionSet: red is set but got false") } - - blankLoc := &Location{ - CustomFields: map[string]interface{}{ - cfManager.MustCustomFieldId("Single Line Text"): SingleLineText(""), - cfManager.MustCustomFieldId("Multi Line Text"): MultiLineText(""), - cfManager.MustCustomFieldId("Date"): Date(""), - cfManager.MustCustomFieldId("Number"): Number(""), - cfManager.MustCustomFieldId("Single Option"): GetSingleOptionPointer(SingleOption("")), - cfManager.MustCustomFieldId("Url"): Url(""), - }, + if cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{"c_blue"}) { + t.Error("TestMustIsMultiOptionSet: blue is not set but got true") } - - tests := []struct { - CFName string - Loc *Location - Want string - }{ - { - CFName: "Single Line Text", - Loc: loc, - Want: "Single Line Text Value", - }, - { - CFName: "Multi Line Text", - Loc: loc, - Want: "Multi Line Text Value", - }, - { - CFName: "Single Option", - Loc: loc, - Want: "Single Option One Value", - }, - { - CFName: "Date", - Loc: loc, - Want: "04/16/2018", - }, - { - CFName: "Number", - Loc: loc, - Want: "2", - }, - { - CFName: "Url", - Loc: loc, - Want: "www.url.com", - }, - { - CFName: "Single Line Text", - Loc: blankLoc, - Want: "", - }, - { - CFName: "Multi Line Text", - Loc: blankLoc, - Want: "", - }, - { - CFName: "Single Option", - Loc: blankLoc, - Want: "", - }, - { - CFName: "Date", - Loc: blankLoc, - Want: "", - }, - { - CFName: "Number", - Loc: blankLoc, - Want: "", - }, - { - CFName: "Url", - Loc: blankLoc, - Want: "", - }, + if !cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{"c_blue", "c_red"}) { + t.Error("TestMustIsMultiOptionSet: red is set but got false") } - - for _, test := range tests { - if got, err := cfManager.GetString(test.CFName, test.Loc); err != nil { - t.Errorf("Get String: got err for custom field %s: %s", test.CFName, err) - } else if got != test.Want { - t.Errorf("Get String: got '%s', wanted '%s' for custom field %s", got, test.Want, test.CFName) - } + if cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{}) { + t.Error("TestMustIsMultiOptionSet: red is not set but got true") } } -func TestGetStringSlice(t *testing.T) { - var cfManager = &CustomFieldManager{ - CustomFields: []*CustomField{ - &CustomField{ - Name: "Text List", - Id: String("TextList"), - Type: CUSTOMFIELDTYPE_TEXTLIST, - }, - &CustomField{ - Name: "Location List", - Id: String("LocationList"), - Type: CUSTOMFIELDTYPE_LOCATIONLIST, - }, - &CustomField{ - Name: "Multi Option", - Id: String("MultiOption"), - Type: CUSTOMFIELDTYPE_MULTIOPTION, - Options: []CustomFieldOption{ - CustomFieldOption{ - Key: "MultiOptionOneKey", - Value: "Multi Option One Value", - }, - CustomFieldOption{ - Key: "MultiOptionTwoKey", - Value: "Multi Option Two Value", - }, - CustomFieldOption{ - Key: "MultiOptionThreeKey", - Value: "Multi Option Three Value", - }, - }, - }, - }, - } - - loc := &Location{ - CustomFields: map[string]interface{}{ - cfManager.MustCustomFieldId("Text List"): TextList([]string{"A", "B", "C"}), - cfManager.MustCustomFieldId("Location List"): LocationList(UnorderedStrings([]string{"1", "2", "3"})), - cfManager.MustCustomFieldId("Multi Option"): MultiOption(UnorderedStrings([]string{"MultiOptionOneKey", "MultiOptionTwoKey", "MultiOptionThreeKey"})), - }, - } - - blankLoc := &Location{ - CustomFields: map[string]interface{}{ - cfManager.MustCustomFieldId("Text List"): TextList([]string{}), - cfManager.MustCustomFieldId("Location List"): LocationList(UnorderedStrings([]string{})), - cfManager.MustCustomFieldId("Multi Option"): MultiOption(UnorderedStrings([]string{})), - }, +func TestMustIsSingleOptionSet(t *testing.T) { + if !cfm.MustIsSingleOptionSet("My Favorite Food", "Cheese", NullableString("c_cheese")) { + t.Error("TestMustIsSingleOptionSet: cheese is set but got false") } - - tests := []struct { - CFName string - Loc *Location - Want []string - }{ - { - CFName: "Text List", - Loc: loc, - Want: []string{"A", "B", "C"}, - }, - { - CFName: "Location List", - Loc: loc, - Want: []string{"1", "2", "3"}, - }, - { - CFName: "Multi Option", - Loc: loc, - Want: []string{"Multi Option One Value", "Multi Option Two Value", "Multi Option Three Value"}, - }, - { - CFName: "Text List", - Loc: blankLoc, - Want: []string{}, - }, - { - CFName: "Location List", - Loc: blankLoc, - Want: []string{}, - }, - { - CFName: "Multi Option", - Loc: blankLoc, - Want: []string{}, - }, + if cfm.MustIsSingleOptionSet("My Favorite Food", "Olives", NullableString("c_cheese")) { + t.Error("TestMustIsSingleOptionSet: olives is not set but got true") } - - for _, test := range tests { - if got, err := cfManager.GetStringSlice(test.CFName, test.Loc); err != nil { - t.Errorf("Get String Slice: got err for custom field %s: %s", test.CFName, err) - } else if got == nil && test.Want != nil || got != nil && test.Want == nil { - t.Errorf("Get String Slice: got %v, wanted %v for custom field %s", got, test.Want, test.CFName) - } else { - for i, _ := range got { - if got[i] != test.Want[i] { - t.Errorf("Get String Slice: got %v, wanted %v for custom field %s", got, test.Want, test.CFName) - } - } - } + if cfm.MustIsSingleOptionSet("My Favorite Food", "Cheese", NullString()) { + t.Error("TestMustIsSingleOptionSet: cheese is not set but got true") } } diff --git a/entity.go b/entity.go index b9627c9..c1f7ffa 100644 --- a/entity.go +++ b/entity.go @@ -15,7 +15,6 @@ type EntityMeta struct { EntityType EntityType `json:"entityType,omitempty"` FolderId *string `json:"folderId,omitempty"` Labels *UnorderedStrings `json:"labels,omitempty"` - CategoryIds *[]string `json:"categoryIds,omitempty"` Language *string `json:"language,omitempty"` CountryCode *string `json:"countryCode,omitempty"` } @@ -46,13 +45,6 @@ func (b *BaseEntity) GetFolderId() string { return "" } -func (b *BaseEntity) GetCategoryIds() (v []string) { - if b.Meta.CategoryIds != nil { - v = *b.Meta.CategoryIds - } - return v -} - func (b *BaseEntity) GetLabels() (v UnorderedStrings) { if b.Meta.Labels != nil { v = *b.Meta.Labels diff --git a/entity_diff.go b/entity_diff.go index fcd3403..6069955 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -10,12 +10,26 @@ func instanceOf(val interface{}) interface{} { isPtr = reflect.ValueOf(val).Kind() == reflect.Ptr tmp interface{} ) + if isPtr { - tmp = reflect.ValueOf(val).Elem().Interface() - } else { - tmp = val + var ( + ptr = reflect.New(reflect.TypeOf(val).Elem()).Interface() + numPointers = 0 + ) + for reflect.ValueOf(val).Kind() == reflect.Ptr { + val = reflect.ValueOf(val).Elem().Interface() + numPointers++ + } + + tmp = reflect.New(reflect.TypeOf(val)).Interface() + if numPointers == 1 { + return tmp + } + // This will only work for ** pointers, no *** pointers + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(tmp)) + return ptr } - return reflect.New(reflect.TypeOf(tmp)).Interface() + return reflect.New(reflect.TypeOf(val)).Interface() } func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (interface{}, bool) { @@ -87,12 +101,12 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int // Handle case where new is &Address{} and base is &Address{"Line1"} if isZeroValue(valB, nilIsEmptyB) && !isZeroValue(valA, nilIsEmptyA) { isDiff = true - reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) + indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } else { d, diff := diff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true - reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d)) + indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) } } continue @@ -103,8 +117,8 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int } if !reflect.DeepEqual(aI, bI) { - reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(valB) isDiff = true + indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } } return delta, isDiff @@ -126,7 +140,6 @@ func isNil(v reflect.Value) bool { // Diff(a, b): a is base, b is new func Diff(a Entity, b Entity) (Entity, bool, error) { - // TODO: should the below return an error? If not should return an empty b object with entity type set? if a.GetEntityType() != b.GetEntityType() { return nil, true, fmt.Errorf("Entity Types do not match: '%s', '%s'", a.GetEntityType(), b.GetEntityType()) } diff --git a/entity_diff_test.go b/entity_diff_test.go index 9eadac5..941ae5e 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -52,7 +52,7 @@ func TestEntityDiff(t *testing.T) { // N) base is zero value (nil is empty), new is nil (nil is empty) tests := []diffTest{ - // *String tests + // **String tests diffTest{ name: "*String: equal (A)", property: "Name", @@ -162,214 +162,214 @@ func TestEntityDiff(t *testing.T) { newNilIsEmpty: true, isDiff: false, }, - // *Float tests + // **Float tests diffTest{ - name: "*Float: equal (A)", + name: "**Float: equal (A)", property: "YearEstablished", - baseValue: Float(2018), - newValue: Float(2018), + baseValue: NullableFloat(2018), + newValue: NullableFloat(2018), isDiff: false, }, diffTest{ - name: "*Float: not equal (B)", + name: "**Float: not equal (B)", property: "YearEstablished", - baseValue: Float(2018), - newValue: Float(2006), + baseValue: NullableFloat(2018), + newValue: NullableFloat(2006), isDiff: true, - deltaValue: Float(2006), + deltaValue: NullableFloat(2006), }, diffTest{ - name: "*Float: base is 0, new is not 0 (C)", + name: "**Float: base is 0, new is not 0 (C)", property: "YearEstablished", - baseValue: Float(0), - newValue: Float(2006), + baseValue: NullableFloat(0), + newValue: NullableFloat(2006), isDiff: true, - deltaValue: Float(2006), + deltaValue: NullableFloat(2006), }, diffTest{ - name: "*Float: base is not 0, new is 0 (D)", + name: "**Float: base is not 0, new is 0 (D)", property: "YearEstablished", - baseValue: Float(2006), - newValue: Float(0), + baseValue: NullableFloat(2006), + newValue: NullableFloat(0), isDiff: true, - deltaValue: Float(0), + deltaValue: NullableFloat(0), }, diffTest{ - name: "*Float: both are 0 (E)", + name: "**Float: both are 0 (E)", property: "YearEstablished", - baseValue: Float(2018), + baseValue: NullableFloat(2018), newValue: nil, isDiff: false, }, diffTest{ - name: "*Float: both are nil (F)", + name: "**Float: both are nil (F)", property: "YearEstablished", baseValue: nil, newValue: nil, isDiff: false, }, diffTest{ - name: "*Float: base is not 0, new is nil (G)", + name: "**Float: base is not 0, new is nil (G)", property: "YearEstablished", - baseValue: Float(1993), + baseValue: NullableFloat(1993), newValue: nil, isDiff: false, }, diffTest{ - name: "*Float: base is nil, new is not 0 (H)", + name: "**Float: base is nil, new is not 0 (H)", property: "YearEstablished", baseValue: nil, - newValue: Float(1993), + newValue: NullableFloat(1993), isDiff: true, - deltaValue: Float(1993), + deltaValue: NullableFloat(1993), }, diffTest{ - name: "*Float: base is nil, new is 0 (I)", + name: "**Float: base is nil, new is 0 (I)", property: "YearEstablished", baseValue: nil, - newValue: Float(0), + newValue: NullableFloat(0), isDiff: true, - deltaValue: Float(0), + deltaValue: NullableFloat(0), }, diffTest{ - name: "*Float: base is 0, new is nil (J)", + name: "**Float: base is 0, new is nil (J)", property: "YearEstablished", - baseValue: Float(0), + baseValue: NullableFloat(0), newValue: nil, isDiff: false, }, diffTest{ - name: "*Float: base is nil (nil is empty), new is 0 (K)", + name: "**Float: base is nil (nil is empty), new is 0 (K)", property: "YearEstablished", baseValue: nil, - newValue: Float(0), + newValue: NullableFloat(0), baseNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "*Float: base is nil (nil is empty), new is 0 (nil is empty) (L)", + name: "**Float: base is nil (nil is empty), new is 0 (nil is empty) (L)", property: "YearEstablished", baseValue: nil, - newValue: Float(0), + newValue: NullableFloat(0), baseNilIsEmpty: true, newNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "*Float: base is 0, new is nil (nil is empty) (M)", + name: "**Float: base is 0, new is nil (nil is empty) (M)", property: "YearEstablished", - baseValue: Float(0), + baseValue: NullableFloat(0), newValue: nil, newNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "*Float: base is 0 (nil is empty), new is nil (nil is empty) (N)", + name: "**Float: base is 0 (nil is empty), new is nil (nil is empty) (N)", property: "YearEstablished", - baseValue: Float(0), + baseValue: NullableFloat(0), baseNilIsEmpty: true, newValue: nil, newNilIsEmpty: true, isDiff: false, }, - // Bool tests + // **Bool tests diffTest{ - name: "*Bool: both true (A)", - property: "SuppressAddress", - baseValue: Bool(true), - newValue: Bool(true), + name: "**Bool: both true (A)", + property: "Closed", + baseValue: NullableBool(true), + newValue: NullableBool(true), isDiff: false, }, diffTest{ - name: "*Bool: both false (A/E)", - property: "SuppressAddress", - baseValue: Bool(false), - newValue: Bool(false), + name: "**Bool: both false (A/E)", + property: "Closed", + baseValue: NullableBool(false), + newValue: NullableBool(false), isDiff: false, }, diffTest{ - name: "*Bool: not equal, base true, new false (B/D)", - property: "SuppressAddress", - baseValue: Bool(true), - newValue: Bool(false), + name: "**Bool: not equal, base true, new false (B/D)", + property: "Closed", + baseValue: NullableBool(true), + newValue: NullableBool(false), isDiff: true, - deltaValue: Bool(false), + deltaValue: NullableBool(false), }, diffTest{ - name: "*Bool: not equal, base is false, new is true (B/C)", - property: "SuppressAddress", - baseValue: Bool(false), - newValue: Bool(true), + name: "**Bool: not equal, base is false, new is true (B/C)", + property: "Closed", + baseValue: NullableBool(false), + newValue: NullableBool(true), isDiff: true, - deltaValue: Bool(true), + deltaValue: NullableBool(true), }, diffTest{ - name: "*Bool: both are nil (F)", - property: "SuppressAddress", + name: "**Bool: both are nil (F)", + property: "Closed", baseValue: nil, newValue: nil, isDiff: false, }, diffTest{ - name: "*Bool: base is non-zero, new is nil (G)", - property: "SuppressAddress", - baseValue: Bool(true), + name: "**Bool: base is non-zero, new is nil (G)", + property: "Closed", + baseValue: NullableBool(true), newValue: nil, isDiff: false, }, diffTest{ - name: "*Bool: base is nil, new is non-zero value (H)", - property: "SuppressAddress", + name: "**Bool: base is nil, new is non-zero value (H)", + property: "Closed", baseValue: nil, - newValue: Bool(true), + newValue: NullableBool(true), isDiff: true, - deltaValue: Bool(true), + deltaValue: NullableBool(true), }, diffTest{ - name: "*Bool: base is nil, new is zero value (I)", - property: "SuppressAddress", + name: "**Bool: base is nil, new is zero value (I)", + property: "Closed", baseValue: nil, - newValue: Bool(false), + newValue: NullableBool(false), isDiff: true, - deltaValue: Bool(false), + deltaValue: NullableBool(false), }, diffTest{ - name: "*Bool: base is zero value, new is nil (J)", - property: "SuppressAddress", - baseValue: Bool(false), + name: "**Bool: base is zero value, new is nil (J)", + property: "Closed", + baseValue: NullableBool(false), newValue: nil, isDiff: false, }, diffTest{ - name: "*Bool: base is nil (nil is empty), new is zero value (K)", - property: "SuppressAddress", + name: "**Bool: base is nil (nil is empty), new is zero value (K)", + property: "Closed", baseValue: nil, - newValue: Bool(false), + newValue: NullableBool(false), baseNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "*Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", - property: "SuppressAddress", + name: "**Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", + property: "Closed", baseValue: nil, - newValue: Bool(false), + newValue: NullableBool(false), baseNilIsEmpty: true, newNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "*Bool: base is zero value, new is nil (nil is empty) (L)", - property: "SuppressAddress", - baseValue: Bool(false), + name: "**Bool: base is zero value, new is nil (nil is empty) (L)", + property: "Closed", + baseValue: NullableBool(false), newValue: nil, newNilIsEmpty: true, isDiff: false, }, diffTest{ - name: "*Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", - property: "SuppressAddress", - baseValue: Bool(false), + name: "**Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", + property: "Closed", + baseValue: NullableBool(false), newValue: nil, baseNilIsEmpty: true, newNilIsEmpty: true, @@ -629,6 +629,14 @@ func TestEntityDiff(t *testing.T) { isDiff: true, deltaValue: &[]string{}, }, + diffTest{ + name: "List: not equal (B)", + property: "CFTextList", + baseValue: &[]string{"a", "b"}, + newValue: &[]string{"b", "c"}, + isDiff: true, + deltaValue: &[]string{"b", "c"}, + }, // Comparable tests (Unordered Strings) diffTest{ name: "UnorderedStrings: equal (ordered) (A)", @@ -772,20 +780,20 @@ func TestEntityDiff(t *testing.T) { property: "BaseEntity", baseValue: BaseEntity{ Meta: &EntityMeta{ - Id: String("CTG"), - CategoryIds: Strings([]string{"123"}), + Id: String("CTG"), + FolderId: String("123"), }, }, newValue: BaseEntity{ Meta: &EntityMeta{ - Id: String("CTG"), - CategoryIds: Strings([]string{"123", "456"}), + Id: String("CTG"), + FolderId: String("456"), }, }, isDiff: true, deltaValue: BaseEntity{ Meta: &EntityMeta{ - CategoryIds: Strings([]string{"123", "456"}), + FolderId: String("456"), }, }, }, @@ -794,12 +802,12 @@ func TestEntityDiff(t *testing.T) { property: "BaseEntity", baseValue: BaseEntity{ Meta: &EntityMeta{ - CategoryIds: Strings([]string{"123", "456"}), + FolderId: String("123"), }, }, newValue: BaseEntity{ Meta: &EntityMeta{ - CategoryIds: Strings([]string{"123", "456"}), + FolderId: String("123"), }, }, isDiff: false, @@ -918,6 +926,736 @@ func TestEntityDiffComplex(t *testing.T) { new: custom4, isDiff: false, }, + { + name: "Address, partial diff", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line1: String("7900 Westpark"), + City: String("McLean"), + }, + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line1: String("7900 Westpark"), + City: String(""), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + City: String(""), + }, + }, + }, + }, + { + name: "CFGallery", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFGallery: &[]Photo{ + Photo{ + Description: String("Description 1"), + }, + Photo{ + Description: String("Description 2"), + }, + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFGallery: &[]Photo{ + Photo{ + Description: String("New Description 1"), + }, + Photo{ + Description: String("Description 2"), + }, + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFGallery: &[]Photo{ + Photo{ + Description: String("New Description 1"), + }, + Photo{ + Description: String("Description 2"), + }, + }, + }, + }, + }, + { + name: "CFHours", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-22"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-22"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + }, + { + name: "CFHours", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(false), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + }, + { + name: "CFHours", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "16:30", + }, + }, + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + }, + { + name: "CFHours", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "16:30", + }, + }, + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "17:00", + }, + }, + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "17:00", + }, + }, + }, + }, + }), + }, + }, + }, + { + name: "CFHours", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-21"), + IsClosed: NullableBool(true), + }, + HolidayHours{ + Date: String("2019-01-22"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-23"), + IsClosed: NullableBool(true), + }, + HolidayHours{ + Date: String("2019-01-24"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFHours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("2019-01-23"), + IsClosed: NullableBool(true), + }, + HolidayHours{ + Date: String("2019-01-24"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + }, + { + name: "Hours Closed Change", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(false), + }), + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(false), + }), + }), + }, + }, + }, + { + name: "Holiday Hours Date Change", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + HolidayHours{ + Date: String("01-23-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-22-2019"), + IsClosed: NullableBool(true), + }, + HolidayHours{ + Date: String("01-23-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-22-2019"), + IsClosed: NullableBool(true), + }, + HolidayHours{ + Date: String("01-23-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + }, + { + name: "Hours Change", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "20:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "20:00", + }, + }, + }), + }), + }, + }, + }, + { + name: "Hours Change (**DayHours) is nil -> null", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + Sunday: NullDayHours(), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + Sunday: NullDayHours(), + }), + }, + }, + }, + { + name: "Hours Change null -> value", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + Sunday: NullDayHours(), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + }), + }, + }, + }, + { + name: "Hours No Change (showing nil is not the same as **DayHours(nil) )", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: nil, + Tuesday: nil, + Wednesday: nil, + Thursday: nil, + Friday: nil, + Saturday: nil, + Sunday: nil, + HolidayHours: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + }), + }, + }, + isDiff: false, + }, + + { + name: "Name", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Name: String("CTG"), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Name: String("CTG2"), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Name: String("CTG2"), + }, + }, + }, } for _, test := range tests { @@ -925,14 +1663,64 @@ func TestEntityDiffComplex(t *testing.T) { delta, isDiff, _ := Diff(test.base, test.new) if isDiff != test.isDiff { t.Log(delta) - t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) + t.Errorf("Expected isDiff: %t.\nGot: %t", test.isDiff, isDiff) } else if test.isDiff == false && delta != nil { - t.Errorf("Expected isDiff: %t. Got delta: %v", test.isDiff, delta) + t.Errorf("Expected isDiff: %t.\nGot delta: %v", test.isDiff, delta) } else if isDiff { if !reflect.DeepEqual(delta, test.delta) { - t.Errorf("Expected delta: %v. Got: %v", test.delta, delta) + t.Errorf("Expected delta: %v.\nGot: %v", test.delta, delta) } } }) } } + +func TestInstanceOf(t *testing.T) { + var ( + b = String("apple") + i = instanceOf(b) + ) + if _, ok := i.(*string); !ok { + t.Error("Expected *string") + } + + var ( + h = &[]HolidayHours{ + HolidayHours{ + IsClosed: NullableBool(true), + }, + } + j = instanceOf(h) + ) + if _, ok := j.(*[]HolidayHours); !ok { + t.Error("Expected *[]HolidayHours") + } + + var ( + hours = NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + }) + k = instanceOf(hours) + ) + if iHours, ok := k.(**Hours); !ok { + t.Error("Expected **Hours") + } else if *iHours == nil { + t.Errorf("*Hours is nil") + } else if GetHours(iHours) == nil { + t.Error("**Hours instance is nil") + } + + var ( + address = &Address{ + Line1: String("7900 Westpark"), + } + l = instanceOf(address) + ) + if iAddress, ok := l.(*Address); !ok { + t.Error("Expected *Address") + } else if iAddress == nil { + t.Error("*Address instance is nil") + } +} diff --git a/entity_registry.go b/entity_registry.go index 21031b6..89dd990 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -5,17 +5,34 @@ import ( "fmt" ) -func defaultEntityRegistry() Registry { +type EntityRegistry Registry + +func defaultEntityRegistry() *EntityRegistry { registry := make(Registry) registry.Register(string(ENTITYTYPE_LOCATION), &Location{}) registry.Register(string(ENTITYTYPE_EVENT), &Event{}) - return registry + entityRegistry := EntityRegistry(registry) + return &entityRegistry +} + +func (r *EntityRegistry) RegisterEntity(t EntityType, entity interface{}) { + registry := Registry(*r) + registry.Register(string(t), entity) +} + +func (r *EntityRegistry) InitializeEntity(t EntityType) (Entity, error) { + registry := Registry(*r) + i, err := registry.Initialize(string(t)) + if err != nil { + return nil, err + } + return i.(Entity), nil } -func toEntityTypes(entities []interface{}, registry Registry) ([]Entity, error) { +func (r *EntityRegistry) ToEntityTypes(entities []interface{}) ([]Entity, error) { var types = []Entity{} for _, entityInterface := range entities { - entity, err := toEntityType(entityInterface, registry) + entity, err := r.ToEntityType(entityInterface) if err != nil { return nil, err } @@ -24,7 +41,7 @@ func toEntityTypes(entities []interface{}, registry Registry) ([]Entity, error) return types, nil } -func toEntityType(entity interface{}, registry Registry) (Entity, error) { +func (r *EntityRegistry) ToEntityType(entity interface{}) (Entity, error) { // Determine Entity Type var entityValsByKey = entity.(map[string]interface{}) meta, ok := entityValsByKey["meta"] @@ -38,7 +55,8 @@ func toEntityType(entity interface{}, registry Registry) (Entity, error) { return nil, fmt.Errorf("Unable to find entityType attribute in %v\nFor Entity: %v", metaByKey, entity) } - entityObj, err := registry.Create(entityType.(string)) + var registry = Registry(*r) + entityObj, err := registry.Initialize(entityType.(string)) if err != nil { // Unable to create an instace of entityType, use RawEntity instead entityObj = &RawEntity{} diff --git a/entity_service.go b/entity_service.go index d4cc5b5..daba28b 100644 --- a/entity_service.go +++ b/entity_service.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" "reflect" + "strings" ) const ( @@ -13,13 +14,20 @@ const ( type EntityService struct { client *Client - registry Registry + Registry *EntityRegistry } type EntityListOptions struct { ListOptions SearchID string ResolvePlaceholders bool + EntityTypes []string +} + +// Used for Create and Edit +type EntityServiceOptions struct { + TemplateId string `json:"templateId,omitempty"` + TemplateFields []string `json:"templateFields,omitempty"` } type EntityListResponse struct { @@ -30,23 +38,23 @@ type EntityListResponse struct { } func (e *EntityService) RegisterDefaultEntities() { - e.registry = defaultEntityRegistry() + e.Registry = defaultEntityRegistry() } func (e *EntityService) RegisterEntity(t EntityType, entity interface{}) { - e.registry.Register(string(t), entity) + e.Registry.RegisterEntity(t, entity) } -func (e *EntityService) CreateEntity(t EntityType) (interface{}, error) { - return e.registry.Create(string(t)) +func (e *EntityService) InitializeEntity(t EntityType) (Entity, error) { + return e.Registry.InitializeEntity(t) } func (e *EntityService) ToEntityTypes(entities []interface{}) ([]Entity, error) { - return toEntityTypes(entities, e.registry) + return e.Registry.ToEntityTypes(entities) } func (e *EntityService) ToEntityType(entity interface{}) (Entity, error) { - return toEntityType(entity, e.registry) + return e.Registry.ToEntityType(entity) } // TODO: Add List for SearchID (similar to location-service). Follow up with Techops to see if SearchID is implemented @@ -112,6 +120,28 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res return v, r, nil } +func addEntityServiceOptions(requrl string, opts *EntityServiceOptions) (string, error) { + u, err := url.Parse(requrl) + if err != nil { + return "", err + } + + if opts == nil { + return requrl, nil + } + + q := u.Query() + if opts.TemplateId != "" { + q.Add("templateId", opts.TemplateId) + } + if len(opts.TemplateFields) > 0 { + q.Add("templateFields", strings.Join(opts.TemplateFields, ",")) + } + u.RawQuery = q.Encode() + + return u.String(), nil +} + func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error) { if opts == nil { return requrl, nil @@ -129,6 +159,9 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if opts.ResolvePlaceholders { q.Add("resolvePlaceholders", "true") } + if len(opts.EntityTypes) > 0 { + q.Add("entityTypes", strings.Join(opts.EntityTypes, ",")) + } u.RawQuery = q.Encode() return u.String(), nil @@ -187,8 +220,39 @@ func (e *EntityService) Create(y Entity) (*Response, error) { return r, nil } -func (e *EntityService) EditWithId(id string, y Entity) (*Response, error) { - r, err := e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, id), y, nil) +func (e *EntityService) CreateWithOptions(y Entity, opts *EntityServiceOptions) (*Response, error) { + var ( + requrl = entityPath + err error + ) + + requrl, err = addEntityServiceOptions(requrl, opts) + if err != nil { + return nil, err + } + + u, err := url.Parse(requrl) + if err != nil { + return nil, err + } + q := u.Query() + q.Add("entityType", string(y.GetEntityType())) + u.RawQuery = q.Encode() + return e.client.DoRequestJSON("POST", u.String(), y, nil) +} + +func (e *EntityService) EditWithOptions(y Entity, id string, opts *EntityServiceOptions) (*Response, error) { + var ( + requrl = fmt.Sprintf("%s/%s", entityPath, id) + err error + ) + + requrl, err = addEntityServiceOptions(requrl, opts) + if err != nil { + return nil, err + } + + r, err := e.client.DoRequestJSON("PUT", requrl, y, nil) if err != nil { return r, err } @@ -196,6 +260,10 @@ func (e *EntityService) EditWithId(id string, y Entity) (*Response, error) { return r, nil } +func (e *EntityService) EditWithId(y Entity, id string) (*Response, error) { + return e.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", entityPath, id), y, nil) +} + func (e *EntityService) Edit(y Entity) (*Response, error) { - return e.EditWithId(y.GetEntityId(), y) + return e.EditWithId(y, y.GetEntityId()) } diff --git a/entity_service_test.go b/entity_service_test.go index ed6a2c1..264471b 100644 --- a/entity_service_test.go +++ b/entity_service_test.go @@ -1,6 +1,9 @@ package yext -import "testing" +import ( + "net/http" + "testing" +) func TestSetNilIsEmpty(t *testing.T) { type randomStruct struct{} @@ -45,3 +48,97 @@ func TestSetNilIsEmpty(t *testing.T) { } } } + +func TestEntityListOptions(t *testing.T) { + tests := []struct { + opts *EntityListOptions + limit string + token string + searchID string + entityTypes string + resolvePlaceholders bool + }{ + { + opts: nil, + limit: "", + token: "", + searchID: "", + }, + { + // The values are technically 0,0, but that doesn't make any sense in the context of a list request + opts: &EntityListOptions{ListOptions: ListOptions{}}, + limit: "", + token: "", + searchID: "", + }, + { + opts: &EntityListOptions{ListOptions: ListOptions{Limit: 10}}, + limit: "10", + token: "", + searchID: "", + }, + { + opts: &EntityListOptions{EntityTypes: []string{"location"}}, + limit: "", + token: "", + searchID: "", + entityTypes: "location", + }, + { + opts: &EntityListOptions{EntityTypes: []string{"location,event"}}, + limit: "", + token: "", + searchID: "", + entityTypes: "location,event", + }, + { + opts: &EntityListOptions{ListOptions: ListOptions{PageToken: "qwerty1234"}}, + limit: "", + token: "qwerty1234", + searchID: "", + }, + { + opts: &EntityListOptions{ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, + limit: "42", + token: "asdfgh4321", + searchID: "", + }, + { + opts: &EntityListOptions{SearchID: "1234", ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, + limit: "42", + token: "asdfgh4321", + searchID: "1234", + }, + { + opts: &EntityListOptions{ResolvePlaceholders: true, ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, + limit: "42", + token: "asdfgh4321", + resolvePlaceholders: true, + }, + } + + for _, test := range tests { + setup() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if v := r.URL.Query().Get("limit"); v != test.limit { + t.Errorf("Wanted limit %s, got %s", test.limit, v) + } + if v := r.URL.Query().Get("pageToken"); v != test.token { + t.Errorf("Wanted token %s, got %s", test.token, v) + } + if v := r.URL.Query().Get("searchId"); v != test.searchID { + t.Errorf("Wanted searchId %s, got %s", test.searchID, v) + } + if v := r.URL.Query().Get("entityTypes"); v != test.entityTypes { + t.Errorf("Wanted entityTypes %s, got %s", test.entityTypes, v) + } + v := r.URL.Query().Get("resolvePlaceholders") + if v == "true" && !test.resolvePlaceholders || v == "" && test.resolvePlaceholders || v == "false" && test.resolvePlaceholders { + t.Errorf("Wanted resolvePlaceholders %t, got %s", test.resolvePlaceholders, v) + } + }) + + client.EntityService.List(test.opts) + teardown() + } +} diff --git a/entity_test.go b/entity_test.go index 1cc7161..302da03 100644 --- a/entity_test.go +++ b/entity_test.go @@ -6,19 +6,52 @@ import ( "testing" ) -type CustomLocationEntity struct { - LocationEntity - CFHours *Hours `json:"cf_Hours,omitempty"` +type CustomEntity struct { + CFHours **Hours `json:"cf_Hours,omitempty"` CFUrl *string `json:"cf_Url,omitempty"` - CFDailyTimes *DailyTimes `json:"cf_DailyTimes,omitempty"` + CFDailyTimes **DailyTimes `json:"cf_DailyTimes,omitempty"` CFTextList *[]string `json:"cf_TextList,omitempty"` - CFGallery []*Photo `json:"cf_Gallery,omitempty"` - CFPhoto *Photo `json:"cf_Photo,omitempty"` - CFVideos []*Video `json:"cf_Videos,omitempty"` - CFVideo *Video `json:"cf_Video,omitempty"` - CFDate *Date `json:"cf_Date,omitempty"` - CFSingleOption *string `json:"cf_SingleOtpion,omitempty"` + CFGallery *[]Photo `json:"cf_Gallery,omitempty"` + CFPhoto **Photo `json:"cf_Photo,omitempty"` + CFVideos *[]Video `json:"cf_Videos,omitempty"` + CFVideo **Video `json:"cf_Video,omitempty"` + CFDate **Date `json:"cf_Date,omitempty"` + CFSingleOption **string `json:"cf_SingleOption,omitempty"` CFMultiOption *UnorderedStrings `json:"cf_MultiOption,omitempty"` + CFYesNo **bool `json:"cf_YesNo,omitempty"` +} + +type CustomLocationEntity struct { + LocationEntity + CustomEntity +} + +func (y *CustomLocationEntity) UnmarshalJSON(bytes []byte) error { + if err := json.Unmarshal(bytes, &y.LocationEntity); err != nil { + return err + } + if err := json.Unmarshal(bytes, &y.CustomEntity); err != nil { + return err + } + return nil +} + +func (y CustomLocationEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (c *CustomEntity) UnmarshalJSON(data []byte) error { + type Alias CustomEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(c), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(c, data) } func entityToJSONString(entity Entity) (error, string) { @@ -41,15 +74,26 @@ func TestEntityJSONSerialization(t *testing.T) { {&CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: nil}}}, `{"address":{}}`}, {&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":[]}`}, - {&CustomLocationEntity{CFTextList: nil}, `{}`}, + {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullHours()}}, `{"hours":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("")}}, `{"cf_Url":""}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: nil}}, `{}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: &[]string{}}}, `{"cf_TextList":[]}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: nil}}, `{}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFSingleOption: NullString()}}, `{"cf_SingleOption":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFMultiOption: ToUnorderedStrings([]string{})}}, `{"cf_MultiOption":[]}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFDate: NullDate()}}, `{"cf_Date":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFVideo: NullVideo()}}, `{"cf_Video":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFPhoto: NullPhoto()}}, `{"cf_Photo":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFGallery: &[]Photo{}}}, `{"cf_Gallery":[]}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFVideos: &[]Video{}}}, `{"cf_Videos":[]}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFDailyTimes: NullDailyTimes()}}, `{"cf_DailyTimes":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFHours: NullHours()}}, `{"cf_Hours":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFYesNo: NullBool()}}, `{"cf_YesNo":null}`}, + {&CustomLocationEntity{LocationEntity: LocationEntity{Name: String("Hello")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}, `{"name":"Hello","cf_YesNo":null}`}, + {&CustomLocationEntity{LocationEntity: LocationEntity{Name: String("")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}, `{"name":"","cf_YesNo":null}`}, } for _, test := range tests { @@ -71,8 +115,27 @@ func TestEntityJSONDeserialization(t *testing.T) { {`{}`, &CustomLocationEntity{}}, {`{"emails": []}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{})}}}, {`{"emails": ["bob@email.com", "sue@email.com"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{"bob@email.com", "sue@email.com"})}}}, - {`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CFUrl: String("www.yext.com")}}, - {`{"cf_TextList": ["a", "b", "c"]}`, &CustomLocationEntity{CFTextList: Strings([]string{"a", "b", "c"})}}, + {`{"emails": ["bob@email.com", "sue@email.com"], "cf_Url": "www.yext.com"}`, &CustomLocationEntity{LocationEntity: LocationEntity{Emails: Strings([]string{"bob@email.com", "sue@email.com"})}, CustomEntity: CustomEntity{CFUrl: String("www.yext.com")}}}, + {`{"cf_TextList": ["a", "b", "c"]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: Strings([]string{"a", "b", "c"})}}}, + {`{"address":{"city":""}}`, &CustomLocationEntity{LocationEntity: LocationEntity{Address: &Address{City: String("")}}}}, + {`{"languages":[]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{}}}}, + {`{"languages":["English"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{"English"}}}}, + {`{"hours":null}`, &CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullHours()}}}, + {`{"cf_Url":""}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("")}}}, + {`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("www.yext.com")}}}, + {`{"cf_TextList":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: &[]string{}}}}, + {`{"cf_SingleOption":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFSingleOption: NullString()}}}, + {`{"cf_MultiOption":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFMultiOption: ToUnorderedStrings([]string{})}}}, + {`{"cf_Date":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFDate: NullDate()}}}, + {`{"cf_Video":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFVideo: NullVideo()}}}, + {`{"cf_Photo":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFPhoto: NullPhoto()}}}, + {`{"cf_Gallery":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFGallery: &[]Photo{}}}}, + {`{"cf_Videos":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFVideos: &[]Video{}}}}, + {`{"cf_DailyTimes":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFDailyTimes: NullDailyTimes()}}}, + {`{"cf_Hours":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFHours: NullHours()}}}, + {`{"cf_YesNo":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFYesNo: NullBool()}}}, + {`{"name":"Hello","cf_YesNo":null}`, &CustomLocationEntity{LocationEntity: LocationEntity{Name: String("Hello")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}}, + {`{"name":"","cf_YesNo":null}`, &CustomLocationEntity{LocationEntity: LocationEntity{Name: String("")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}}, } for _, test := range tests { @@ -87,9 +150,9 @@ func TestEntityJSONDeserialization(t *testing.T) { func TestEntitySampleJSONResponseDeserialization(t *testing.T) { entityService := EntityService{ - registry: make(Registry), + Registry: &EntityRegistry{}, } - entityService.RegisterEntity("LOCATION", &CustomLocationEntity{}) + entityService.RegisterEntity("location", &CustomLocationEntity{}) mapOfStringToInterface := make(map[string]interface{}) err := json.Unmarshal([]byte(sampleEntityJSON), &mapOfStringToInterface) if err != nil { @@ -354,6 +417,6 @@ var sampleEntityJSON = `{ "folderId": "0", "language": "en", "countryCode": "US", - "entityType": "LOCATION" + "entityType": "location" } }` diff --git a/entity_unmarshal.go b/entity_unmarshal.go new file mode 100644 index 0000000..3efc732 --- /dev/null +++ b/entity_unmarshal.go @@ -0,0 +1,40 @@ +package yext + +import ( + "encoding/json" + "reflect" + "strings" +) + +func UnmarshalEntityJSON(i interface{}, data []byte) error { + var jsonTagToKey = map[string]string{} + val := reflect.ValueOf(i).Elem() + for i := 0; i < val.Type().NumField(); i++ { + field := val.Type().Field(i) + tag := strings.Replace(field.Tag.Get("json"), ",omitempty", "", -1) + jsonTagToKey[tag] = field.Name + } + + var m map[string]interface{} + err := json.Unmarshal(data, &m) + if err != nil { + return err + } + + for tag, val := range m { + if _, ok := jsonTagToKey[tag]; ok && val == nil { + v := reflect.ValueOf(i).Elem().FieldByName(jsonTagToKey[tag]) + + // Check if double pointer + if v.Type().Kind() == reflect.Ptr { + t := v.Type() + for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + t = t.Elem() + } + typedNil := reflect.New(t) + reflect.ValueOf(i).Elem().FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) + } + } + } + return nil +} diff --git a/event.go b/event_entity.go similarity index 85% rename from event.go rename to event_entity.go index a75ea1f..9c70728 100644 --- a/event.go +++ b/event_entity.go @@ -43,11 +43,11 @@ type EventEntity struct { TicketPriceRange *TicketPriceRange `json:"ticketPriceRange,omitempty"` //Lats & Lngs - DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` - RoutableCoordinate *Coordinate `json:"yextRoutableCoordinate,omitempty"` - DropoffCoordinate *Coordinate `json:"yextDropoffCoordinate,omitempty"` - WalkableCoordinate *Coordinate `json:"yextWalkableCoordinate,omitempty"` - PickupCoordinate *Coordinate `json:"yextPickupCoordinate,omitempty"` + DisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"yextRoutableCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"yextDropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"yextWalkableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"yextPickupCoordinate,omitempty"` //Event Organizer Info OrganizerEmail *string `json:"organizerEmail,omitempty"` @@ -230,71 +230,81 @@ func (y EventEntity) GetBrands() (v []string) { } func (y EventEntity) GetDisplayLat() float64 { - if y.DisplayCoordinate != nil && y.DisplayCoordinate.Latitude != nil { - return *y.DisplayCoordinate.Latitude + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y EventEntity) GetDisplayLng() float64 { - if y.DisplayCoordinate != nil && y.DisplayCoordinate.Longitude != nil { - return *y.DisplayCoordinate.Longitude + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } func (y EventEntity) GetRoutableLat() float64 { - if y.RoutableCoordinate != nil && y.RoutableCoordinate.Latitude != nil { - return *y.RoutableCoordinate.Latitude + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y EventEntity) GetRoutableLng() float64 { - if y.RoutableCoordinate != nil && y.RoutableCoordinate.Longitude != nil { - return *y.RoutableCoordinate.Longitude + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } func (y EventEntity) GetDropoffLat() float64 { - if y.DropoffCoordinate != nil && y.DropoffCoordinate.Latitude != nil { - return *y.DropoffCoordinate.Latitude + c := GetCoordinate(y.DropoffCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y EventEntity) GetDropoffLng() float64 { - if y.DropoffCoordinate != nil && y.DropoffCoordinate.Longitude != nil { - return *y.DropoffCoordinate.Longitude + c := GetCoordinate(y.DropoffCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } func (y EventEntity) GetPickupLat() float64 { - if y.PickupCoordinate != nil && y.PickupCoordinate.Latitude != nil { - return *y.PickupCoordinate.Latitude + c := GetCoordinate(y.PickupCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y EventEntity) GetPickupLng() float64 { - if y.PickupCoordinate != nil && y.PickupCoordinate.Longitude != nil { - return *y.PickupCoordinate.Longitude + c := GetCoordinate(y.PickupCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } func (y EventEntity) GetWalkableLat() float64 { - if y.WalkableCoordinate != nil && y.WalkableCoordinate.Latitude != nil { - return *y.WalkableCoordinate.Latitude + c := GetCoordinate(y.WalkableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y EventEntity) GetWalkableLng() float64 { - if y.WalkableCoordinate != nil && y.WalkableCoordinate.Longitude != nil { - return *y.WalkableCoordinate.Longitude + c := GetCoordinate(y.WalkableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } diff --git a/hours.go b/hours.go index 025c5ad..004143a 100644 --- a/hours.go +++ b/hours.go @@ -11,6 +11,7 @@ const ( HoursClosedAllWeek = "1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed" HoursOpen24Hours = "00:00:00:00" HoursClosed = "closed" + hoursLen = 11 // XX:XX:XX:XX format ) type Weekday int @@ -49,8 +50,7 @@ func (w Weekday) ToString() string { return "Unknown" } -// TODO: Rename to LocationHoursHelper -type HoursHelper struct { +type LocationHoursHelper struct { Sunday []string Monday []string Tuesday []string @@ -60,10 +60,10 @@ type HoursHelper struct { Saturday []string } -// Format used from LocationService -func HoursHelperFromString(str string) (*HoursHelper, error) { +// String Format used from LocationService +func LocationHoursHelperFromString(str string) (*LocationHoursHelper, error) { var ( - hoursHelper = &HoursHelper{} + hoursHelper = &LocationHoursHelper{} hoursForDays = strings.Split(str, ",") ) if len(str) == 0 { @@ -84,15 +84,15 @@ func HoursHelperFromString(str string) (*HoursHelper, error) { return hoursHelper, nil } -func MustHoursHelperFromString(str string) *HoursHelper { - hoursHelper, err := HoursHelperFromString(str) +func MustLocationHoursHelperFromString(str string) *LocationHoursHelper { + hoursHelper, err := LocationHoursHelperFromString(str) if err != nil { panic(err) } return hoursHelper } -func (h *HoursHelper) SetHours(weekday Weekday, hours []string) { +func (h *LocationHoursHelper) SetHours(weekday Weekday, hours []string) { switch weekday { case Sunday: h.Sunday = hours @@ -111,7 +111,7 @@ func (h *HoursHelper) SetHours(weekday Weekday, hours []string) { } } -func (h *HoursHelper) AppendHours(weekday Weekday, hours string) { +func (h *LocationHoursHelper) AppendHours(weekday Weekday, hours string) { switch weekday { case Sunday: h.Sunday = append(h.Sunday, hours) @@ -130,19 +130,19 @@ func (h *HoursHelper) AppendHours(weekday Weekday, hours string) { } } -func (h *HoursHelper) SetClosed(weekday Weekday) { +func (h *LocationHoursHelper) SetClosed(weekday Weekday) { h.SetHours(weekday, []string{HoursClosed}) } -func (h *HoursHelper) SetUnspecified(weekday Weekday) { +func (h *LocationHoursHelper) SetUnspecified(weekday Weekday) { h.SetHours(weekday, nil) } -func (h *HoursHelper) SetOpen24Hours(weekday Weekday) { +func (h *LocationHoursHelper) SetOpen24Hours(weekday Weekday) { h.SetHours(weekday, []string{HoursOpen24Hours}) } -func (h *HoursHelper) GetHours(weekday Weekday) []string { +func (h *LocationHoursHelper) GetHours(weekday Weekday) []string { switch weekday { case Sunday: return h.Sunday @@ -162,7 +162,7 @@ func (h *HoursHelper) GetHours(weekday Weekday) []string { return nil } -func (h *HoursHelper) StringSerialize() string { +func (h *LocationHoursHelper) StringSerialize() string { if h.HoursAreAllUnspecified() { return "" } @@ -178,55 +178,61 @@ func (h *HoursHelper) StringSerialize() string { return strings.Join(days, ",") } -func (h *HoursHelper) StringSerializeDay(weekday Weekday) string { +func (h *LocationHoursHelper) StringSerializeDay(weekday Weekday) string { if h.HoursAreAllUnspecified() { return "" } var hoursStrings = []string{} - if h.GetHours(weekday) == nil || len(h.GetHours(weekday)) == 0 { + if h.GetHours(weekday) == nil || len(h.GetHours(weekday)) == 0 || h.HoursAreClosed(weekday) { return fmt.Sprintf("%d:%s", weekday, HoursClosed) } for _, hours := range h.GetHours(weekday) { + if len(hours) != hoursLen { + hours = "0" + hours + } hoursStrings = append(hoursStrings, fmt.Sprintf("%d:%s", weekday, hours)) } return strings.Join(hoursStrings, ",") } -func (h *HoursHelper) StructSerialize() *Hours { +func (h *LocationHoursHelper) StructSerialize() **Hours { if h.HoursAreAllUnspecified() { - return nil + return NullHours() } hours := &Hours{} - hours.Sunday = h.StructSerializeDay(Sunday) - hours.Monday = h.StructSerializeDay(Monday) - hours.Tuesday = h.StructSerializeDay(Tuesday) - hours.Wednesday = h.StructSerializeDay(Wednesday) - hours.Thursday = h.StructSerializeDay(Thursday) - hours.Friday = h.StructSerializeDay(Friday) - hours.Saturday = h.StructSerializeDay(Saturday) - return hours + hours.Sunday = NullableDayHours(h.StructSerializeDay(Sunday)) + hours.Monday = NullableDayHours(h.StructSerializeDay(Monday)) + hours.Tuesday = NullableDayHours(h.StructSerializeDay(Tuesday)) + hours.Wednesday = NullableDayHours(h.StructSerializeDay(Wednesday)) + hours.Thursday = NullableDayHours(h.StructSerializeDay(Thursday)) + hours.Friday = NullableDayHours(h.StructSerializeDay(Friday)) + hours.Saturday = NullableDayHours(h.StructSerializeDay(Saturday)) + return NullableHours(hours) } -func (h *HoursHelper) StructSerializeDay(weekday Weekday) *DayHours { - if h.HoursAreUnspecified(weekday) { - return nil - } - - if h.HoursAreClosed(weekday) { +func (h *LocationHoursHelper) StructSerializeDay(weekday Weekday) *DayHours { + if h.HoursAreUnspecified(weekday) || h.HoursAreClosed(weekday) || len(h.GetHours(weekday)) == 0 { return &DayHours{ - IsClosed: Bool(true), + IsClosed: NullableBool(true), } } var d = &DayHours{} + intervals := []Interval{} for _, interval := range h.GetHours(weekday) { parts := strings.Split(interval, ":") - d.SetHours(fmt.Sprintf("%s:%s", parts[0], parts[1]), fmt.Sprintf("%s:%s", parts[2], parts[3])) + intervals = append(intervals, + Interval{ + Start: fmt.Sprintf("%s:%s", parts[0], parts[1]), + End: fmt.Sprintf("%s:%s", parts[2], parts[3]), + }, + ) } + d.OpenIntervals = &intervals return d } -func (h *HoursHelper) ToStringSlice() ([]string, error) { +func (h *LocationHoursHelper) ToStringSlice() ([]string, error) { var hoursStringSlice = make([][]string, 7) for i := range hoursStringSlice { weekday := Weekday(i + 1) @@ -241,10 +247,10 @@ func (h *HoursHelper) ToStringSlice() ([]string, error) { if err != nil { return nil, err } - if open, err = ConvertBetweenFormats(open, "15:04", "3:04pm"); err != nil { + if open, err = ConvertBetweenFormats(open, "15:04", "03:04pm"); err != nil { return nil, err } - if close, err = ConvertBetweenFormats(close, "15:04", "3:04pm"); err != nil { + if close, err = ConvertBetweenFormats(close, "15:04", "03:04pm"); err != nil { return nil, err } hoursStringSlice[i] = append(hoursStringSlice[i], fmt.Sprintf("%s - %s", open, close)) @@ -258,7 +264,7 @@ func (h *HoursHelper) ToStringSlice() ([]string, error) { return hoursSlice, nil } -func (h *HoursHelper) MustToStringSlice() []string { +func (h *LocationHoursHelper) MustToStringSlice() []string { hoursStringSlice, err := h.ToStringSlice() if err != nil { panic(err) @@ -278,7 +284,14 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { if err != nil { return -1, "", fmt.Errorf("Error parsing weekday hours from string; unable to convert index to num: %s", err) } - return Weekday(weekdayInt), strings.Join(hoursParts[1:], ":"), nil + if hoursParts[1] == HoursClosed { + return Weekday(weekdayInt), HoursClosed, nil + } + hours := strings.Join(hoursParts[1:], ":") + if len(hours) != hoursLen { + hours = "0" + hours + } + return Weekday(weekdayInt), hours, nil } func ParseOpenAndCloseHoursFromString(hours string) (string, string, error) { @@ -293,7 +306,7 @@ func ParseOpenAndCloseHoursFromString(hours string) (string, string, error) { return "", "", fmt.Errorf("Error parsing open and close hours from string %s: Unexpected format", hours) } -func (h HoursHelper) ToMap() map[Weekday][]string { +func (h LocationHoursHelper) ToMap() map[Weekday][]string { return map[Weekday][]string{ Sunday: h.Sunday, Monday: h.Monday, @@ -305,7 +318,7 @@ func (h HoursHelper) ToMap() map[Weekday][]string { } } -func (h *HoursHelper) HoursAreAllUnspecified() bool { +func (h *LocationHoursHelper) HoursAreAllUnspecified() bool { for _, hours := range h.ToMap() { if hours != nil { return false @@ -314,16 +327,16 @@ func (h *HoursHelper) HoursAreAllUnspecified() bool { return true } -func (h *HoursHelper) HoursAreUnspecified(weekday Weekday) bool { +func (h *LocationHoursHelper) HoursAreUnspecified(weekday Weekday) bool { return h.GetHours(weekday) == nil } -func (h *HoursHelper) HoursAreClosed(weekday Weekday) bool { +func (h *LocationHoursHelper) HoursAreClosed(weekday Weekday) bool { var hours = h.GetHours(weekday) return hours != nil && len(hours) == 1 && hours[0] == HoursClosed } -func (h *HoursHelper) HoursAreOpen24Hours(weekday Weekday) bool { +func (h *LocationHoursHelper) HoursAreOpen24Hours(weekday Weekday) bool { var hours = h.GetHours(weekday) return hours != nil && len(hours) == 1 && hours[0] == HoursOpen24Hours } @@ -347,3 +360,36 @@ func ConvertBetweenFormats(hours string, convertFromFormat string, convertToForm } return t.Format(convertToFormat), nil } + +func LocationHolidayHoursToHolidayHours(l *LocationHolidayHours) (*HolidayHours, error) { + if l == nil { + return nil, nil + } + var h = &HolidayHours{ + Date: String(l.Date), + } + if l.Hours == "" { + h.IsClosed = NullableBool(true) + } else { + intervalsList := []Interval{} + intervals := strings.Split(l.Hours, ",") + for _, i := range intervals { + open, close, err := ParseOpenAndCloseHoursFromString(i) + if err != nil { + return nil, err + } + if len(open) != 5 { + open = "0" + open + } + if len(close) != 5 { + close = "0" + close + } + intervalsList = append(intervalsList, Interval{ + Start: open, + End: close, + }) + } + h.OpenIntervals = &intervalsList + } + return h, nil +} diff --git a/hours_test.go b/hours_test.go index 73b39fa..0034a15 100644 --- a/hours_test.go +++ b/hours_test.go @@ -8,11 +8,23 @@ import ( func TestHoursHelperFromString(t *testing.T) { var tests = []struct { Have string - Want *HoursHelper + Want *LocationHoursHelper }{ { Have: "1:09:00:20:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", - Want: &HoursHelper{ + Want: &LocationHoursHelper{ + Sunday: []string{"09:00:20:00"}, + Monday: []string{"closed"}, + Tuesday: []string{"00:00:00:00"}, + Wednesday: []string{"07:00:22:00"}, + Thursday: []string{"09:00:19:00"}, + Friday: []string{"09:00:21:00"}, + Saturday: []string{"08:00:20:00"}, + }, + }, + { + Have: "1:9:00:20:00,2:closed,3:0:00:00:00,4:7:00:22:00,5:9:00:19:00,6:9:00:21:00,7:8:00:20:00", + Want: &LocationHoursHelper{ Sunday: []string{"09:00:20:00"}, Monday: []string{"closed"}, Tuesday: []string{"00:00:00:00"}, @@ -24,7 +36,7 @@ func TestHoursHelperFromString(t *testing.T) { }, { Have: "1:09:00:20:00,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", - Want: &HoursHelper{ + Want: &LocationHoursHelper{ Sunday: []string{"09:00:20:00"}, Monday: []string{"closed"}, Tuesday: []string{"00:00:00:00"}, @@ -36,7 +48,7 @@ func TestHoursHelperFromString(t *testing.T) { }, { Have: "1:09:00:11:00,1:13:00:16:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", - Want: &HoursHelper{ + Want: &LocationHoursHelper{ Sunday: []string{"09:00:11:00", "13:00:16:00"}, Monday: []string{"closed"}, Tuesday: []string{"00:00:00:00"}, @@ -48,7 +60,7 @@ func TestHoursHelperFromString(t *testing.T) { }, { Have: "", - Want: &HoursHelper{ + Want: &LocationHoursHelper{ Sunday: nil, Monday: nil, Tuesday: nil, @@ -61,7 +73,7 @@ func TestHoursHelperFromString(t *testing.T) { } for i, test := range tests { - if got, err := HoursHelperFromString(test.Have); err != nil { + if got, err := LocationHoursHelperFromString(test.Have); err != nil { t.Errorf("Test HoursHelperFromString %d\nGot error: %s", i+1, err) } else if !reflect.DeepEqual(got, test.Want) { t.Errorf("Test HoursHelperFromString %d\nWanted: %v\nGot: %v", i+1, *test.Want, *got) @@ -69,13 +81,13 @@ func TestHoursHelperFromString(t *testing.T) { } } -func TestSerialize(t *testing.T) { +func TestStringSerialize(t *testing.T) { var tests = []struct { - Have *HoursHelper + Have *LocationHoursHelper Want string }{ { - Have: &HoursHelper{ + Have: &LocationHoursHelper{ Sunday: []string{"09:00:20:00"}, Monday: []string{"closed"}, Tuesday: []string{"00:00:00:00"}, @@ -87,7 +99,19 @@ func TestSerialize(t *testing.T) { Want: "1:09:00:20:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", }, { - Have: &HoursHelper{ + Have: &LocationHoursHelper{ + Sunday: []string{"9:00:20:00"}, + Monday: []string{"closed"}, + Tuesday: []string{"00:00:00:00"}, + Wednesday: []string{"7:00:22:00"}, + Thursday: []string{"9:00:19:00"}, + Friday: []string{"9:00:21:00"}, + Saturday: []string{"8:00:20:00"}, + }, + Want: "1:09:00:20:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", + }, + { + Have: &LocationHoursHelper{ Sunday: []string{"09:00:20:00"}, Monday: []string{}, Tuesday: []string{"00:00:00:00"}, @@ -99,7 +123,7 @@ func TestSerialize(t *testing.T) { Want: "1:09:00:20:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", }, { - Have: &HoursHelper{ + Have: &LocationHoursHelper{ Sunday: []string{"09:00:20:00"}, Monday: nil, Tuesday: []string{"00:00:00:00"}, @@ -111,19 +135,19 @@ func TestSerialize(t *testing.T) { Want: "1:09:00:20:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", }, { - Have: &HoursHelper{ + Have: &LocationHoursHelper{ Sunday: []string{"09:00:11:00", "13:00:16:00"}, Monday: []string{"closed"}, Tuesday: []string{"00:00:00:00"}, Wednesday: []string{"07:00:22:00"}, Thursday: []string{"09:00:19:00"}, Friday: []string{"09:00:21:00"}, - Saturday: []string{"08:00:20:00"}, + Saturday: []string{"10:00:00:00"}, }, - Want: "1:09:00:11:00,1:13:00:16:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:08:00:20:00", + Want: "1:09:00:11:00,1:13:00:16:00,2:closed,3:00:00:00:00,4:07:00:22:00,5:09:00:19:00,6:09:00:21:00,7:10:00:00:00", }, { - Have: &HoursHelper{ + Have: &LocationHoursHelper{ Sunday: nil, Monday: nil, Tuesday: nil, @@ -143,4 +167,365 @@ func TestSerialize(t *testing.T) { } } -// TODO: Add struct serialize +func TestStructSerialize(t *testing.T) { + var tests = []struct { + Have *LocationHoursHelper + Want **Hours + }{ + { + Have: &LocationHoursHelper{ + Sunday: []string{"09:00:20:00"}, + Monday: []string{"closed"}, + Tuesday: []string{"00:00:00:00"}, + Wednesday: []string{"07:00:22:00"}, + Thursday: []string{"09:00:19:00"}, + Friday: []string{"09:00:21:00"}, + Saturday: []string{"08:00:20:00"}, + }, + Want: NullableHours(&Hours{ + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "20:00", + }, + }, + }), + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "00:00", + End: "00:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "07:00", + End: "22:00", + }, + }, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "19:00", + }, + }, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "21:00", + }, + }, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "20:00", + }, + }, + }), + }), + }, + { + Have: &LocationHoursHelper{ + Sunday: []string{"09:00:20:00"}, + Monday: []string{}, + Tuesday: []string{"00:00:00:00"}, + Wednesday: []string{"07:00:22:00"}, + Thursday: []string{"09:00:19:00"}, + Friday: []string{"09:00:21:00"}, + Saturday: []string{"08:00:20:00"}, + }, + Want: NullableHours(&Hours{ + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "20:00", + }, + }, + }), + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "00:00", + End: "00:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "07:00", + End: "22:00", + }, + }, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "19:00", + }, + }, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "21:00", + }, + }, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "20:00", + }, + }, + }), + }), + }, + { + Have: &LocationHoursHelper{ + Sunday: []string{"09:00:20:00"}, + Monday: nil, + Tuesday: []string{"00:00:00:00"}, + Wednesday: []string{"07:00:22:00"}, + Thursday: []string{"09:00:19:00"}, + Friday: []string{"09:00:21:00"}, + Saturday: []string{"08:00:20:00"}, + }, + Want: NullableHours(&Hours{ + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "20:00", + }, + }, + }), + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "00:00", + End: "00:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "07:00", + End: "22:00", + }, + }, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "19:00", + }, + }, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "21:00", + }, + }, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "20:00", + }, + }, + }), + }), + }, + { + Have: &LocationHoursHelper{ + Sunday: []string{"09:00:11:00", "13:00:16:00"}, + Monday: []string{"closed"}, + Tuesday: []string{"00:00:00:00"}, + Wednesday: []string{"07:00:22:00"}, + Thursday: []string{"09:00:19:00"}, + Friday: []string{"09:00:21:00"}, + Saturday: []string{"08:00:20:00"}, + }, + Want: NullableHours(&Hours{ + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "11:00", + }, + Interval{ + Start: "13:00", + End: "16:00", + }, + }, + }), + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "00:00", + End: "00:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "07:00", + End: "22:00", + }, + }, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "19:00", + }, + }, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "21:00", + }, + }, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "20:00", + }, + }, + }), + }), + }, + { + Have: &LocationHoursHelper{ + Sunday: nil, + Monday: nil, + Tuesday: nil, + Wednesday: nil, + Thursday: nil, + Friday: nil, + Saturday: nil, + }, + Want: NullHours(), + }, + } + + for i, test := range tests { + if got := test.Have.StructSerialize(); !reflect.DeepEqual(GetHours(got), GetHours(test.Want)) { + //if want != nil { + t.Errorf("Test StructSerialize %d.\nWanted: %v\nGot: %v", i+1, GetHours(test.Want), GetHours(got)) + //} + } + } +} + +func TestHolidayHoursConvert(t *testing.T) { + + var tests = []struct { + Have *LocationHolidayHours + Want *HolidayHours + }{ + { + Have: &LocationHolidayHours{ + Date: "2018-12-25", + Hours: "8:00:16:00", + }, + Want: &HolidayHours{ + Date: String("2018-12-25"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "16:00", + }, + }, + }, + }, + { + Have: &LocationHolidayHours{ + Date: "2018-12-25", + Hours: "08:00:16:00", + }, + Want: &HolidayHours{ + Date: String("2018-12-25"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "16:00", + }, + }, + }, + }, + { + Have: &LocationHolidayHours{ + Date: "2018-12-25", + Hours: "9:00:15:00,17:00:19:00", + }, + Want: &HolidayHours{ + Date: String("2018-12-25"), + OpenIntervals: &[]Interval{ + Interval{ + Start: "09:00", + End: "15:00", + }, + Interval{ + Start: "17:00", + End: "19:00", + }, + }, + }, + }, + { + Have: &LocationHolidayHours{ + Date: "2018-12-25", + Hours: "", + }, + Want: &HolidayHours{ + Date: String("2018-12-25"), + IsClosed: NullableBool(true), + }, + }, + } + + for i, test := range tests { + if got, _ := LocationHolidayHoursToHolidayHours(test.Have); !reflect.DeepEqual(got, test.Want) { + t.Errorf("Test LocattionHolidayHoursToHolidayHours %d.\nWanted: %v\nGot: %v", i+1, *test.Want, *got) + } + } + +} diff --git a/language_profile_service.go b/language_profile_service.go index 58b9ecd..3ca58d8 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -8,7 +8,7 @@ const entityProfilesPath = "entityprofiles" type LanguageProfileService struct { client *Client - registry Registry + registry *EntityRegistry } type LanguageProfileListResponse struct { @@ -20,7 +20,7 @@ func (l *LanguageProfileService) RegisterDefaultEntities() { } func (l *LanguageProfileService) RegisterEntity(t EntityType, entity interface{}) { - l.registry.Register(string(t), entity) + l.registry.RegisterEntity(t, entity) } func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageProfile, *Response, error) { @@ -30,7 +30,7 @@ func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageP return nil, r, err } - entity, err := toEntityType(v, l.registry) + entity, err := l.registry.ToEntityType(v) if err != nil { return nil, r, err } @@ -49,7 +49,7 @@ func (l *LanguageProfileService) List(id string) ([]*LanguageProfile, *Response, return nil, r, err } - typedProfiles, err := toEntityTypes(v.Profiles, l.registry) + typedProfiles, err := l.registry.ToEntityTypes(v.Profiles) if err != nil { return nil, r, err } diff --git a/location.go b/location.go index c50b99a..c7b8298 100644 --- a/location.go +++ b/location.go @@ -139,7 +139,7 @@ type Location struct { Photos *[]LocationPhoto `json:"photos,omitempty"` VideoUrls *[]string `json:"videoUrls,omitempty"` - GoogleAttributes *GoogleAttributes `json:"googleAttributes,omitempty"` + GoogleAttributes *LocationGoogleAttributes `json:"googleAttributes,omitempty"` // Reviews ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` @@ -674,7 +674,7 @@ func (y Location) GetInsuranceAccepted() (v []string) { return v } -func (y Location) GetGoogleAttributes() GoogleAttributes { +func (y Location) GetGoogleAttributes() LocationGoogleAttributes { if y.GoogleAttributes != nil { return *y.GoogleAttributes } @@ -696,8 +696,8 @@ func (y Location) GetHolidayHours() []LocationHolidayHours { } func (y Location) IsClosed() bool { - if y.Closed != nil && y.Closed.IsClosed { - return true + if y.Closed != nil && y.Closed.IsClosed != nil { + return *y.Closed.IsClosed } return false } @@ -812,7 +812,7 @@ func (l LocationPhoto) String() string { // LocationClosed represents the 'closed' state of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm#Closed type LocationClosed struct { - IsClosed bool `json:"isClosed"` + IsClosed *bool `json:"isClosed"` ClosedDate string `json:"closedDate,omitempty"` } @@ -831,55 +831,17 @@ func (l LocationHolidayHours) String() string { return fmt.Sprintf("Date: '%v', Hours: '%v'", l.Date, l.Hours) } -// UnorderedStrings masks []string properties for which Order doesn't matter, such as LabelIds -type UnorderedStrings []string - -// Equal compares UnorderedStrings -func (a *UnorderedStrings) Equal(b Comparable) bool { - defer func() { - if r := recover(); r != nil { - fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) - panic(r) - } - }() - - if a == nil || b == nil { - return false - } - - var ( - u = []string(*a) - s = []string(*b.(*UnorderedStrings)) - ) - if len(u) != len(s) { - return false - } - - for i := 0; i < len(u); i++ { - var found bool - for j := 0; j < len(s); j++ { - if u[i] == s[j] { - found = true - } - } - if !found { - return false - } - } - return true -} - -type GoogleAttribute struct { +type LocationGoogleAttribute struct { Id *string `json:"id,omitempty"` OptionIds *[]string `json:"optionIds,omitempty"` } -func (g GoogleAttribute) String() string { +func (g LocationGoogleAttribute) String() string { return fmt.Sprintf("Id: '%v', OptionIds: '%v'", g.Id, g.OptionIds) } // Equal compares GoogleAttribute -func (a *GoogleAttribute) Equal(b Comparable) bool { +func (a *LocationGoogleAttribute) Equal(b Comparable) bool { defer func() { if r := recover(); r != nil { fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) @@ -892,8 +854,8 @@ func (a *GoogleAttribute) Equal(b Comparable) bool { } var ( - u = GoogleAttribute(*a) - s = GoogleAttribute(*b.(*GoogleAttribute)) + u = LocationGoogleAttribute(*a) + s = LocationGoogleAttribute(*b.(*LocationGoogleAttribute)) ) if *u.Id != *s.Id { return false @@ -920,9 +882,9 @@ func (a *GoogleAttribute) Equal(b Comparable) bool { return true } -type GoogleAttributes []*GoogleAttribute +type LocationGoogleAttributes []*LocationGoogleAttribute -func (g GoogleAttributes) String() string { +func (g LocationGoogleAttributes) String() string { var ret string for i, googleAttr := range g { @@ -937,7 +899,7 @@ func (g GoogleAttributes) String() string { } // Equal compares GoogleAttributes -func (a *GoogleAttributes) Equal(b Comparable) bool { +func (a *LocationGoogleAttributes) Equal(b Comparable) bool { defer func() { if r := recover(); r != nil { fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) @@ -950,8 +912,8 @@ func (a *GoogleAttributes) Equal(b Comparable) bool { } var ( - u = []*GoogleAttribute(*a) - s = []*GoogleAttribute(*b.(*GoogleAttributes)) + u = []*LocationGoogleAttribute(*a) + s = []*LocationGoogleAttribute(*b.(*LocationGoogleAttributes)) ) if len(u) != len(s) { return false @@ -970,3 +932,8 @@ func (a *GoogleAttributes) Equal(b Comparable) bool { } return true } + +func ToLocationGoogleAttributes(v []*LocationGoogleAttribute) *LocationGoogleAttributes { + u := LocationGoogleAttributes(v) + return &u +} diff --git a/location_customfield.go b/location_customfield.go new file mode 100644 index 0000000..6dd50ab --- /dev/null +++ b/location_customfield.go @@ -0,0 +1,234 @@ +package yext + +// Custom field service to be used with legacy location service + +import ( + "fmt" +) + +type CustomFieldType string + +const ( + CUSTOMFIELDTYPE_LOCATIONLIST = "LOCATION_LIST" +) + +var ( + UnsetPhotoValue = (*CustomLocationPhoto)(nil) +) + +type CustomFieldOption struct { + Key string `json:"key,omitempty"` + Value string `json:"value"` +} + +type CustomFieldValue interface { + CustomFieldTag() string +} + +type YesNo bool + +func (y YesNo) CustomFieldTag() string { + return CUSTOMFIELDTYPE_YESNO +} + +type SingleLineText string + +func (s SingleLineText) CustomFieldTag() string { + return CUSTOMFIELDTYPE_SINGLELINETEXT +} + +type MultiLineText string + +func (m MultiLineText) CustomFieldTag() string { + return CUSTOMFIELDTYPE_MULTILINETEXT +} + +type Url string + +func (u Url) CustomFieldTag() string { + return CUSTOMFIELDTYPE_URL +} + +type Date string + +func (d Date) CustomFieldTag() string { + return CUSTOMFIELDTYPE_DATE +} + +type Number string + +func (n Number) CustomFieldTag() string { + return CUSTOMFIELDTYPE_NUMBER +} + +type OptionField interface { + CustomFieldValue + SetOptionId(id string) + UnsetOptionId(id string) + IsOptionIdSet(id string) bool +} + +type SingleOption string + +func (s SingleOption) CustomFieldTag() string { + return CUSTOMFIELDTYPE_SINGLEOPTION +} + +func (s *SingleOption) SetOptionId(id string) { + *s = SingleOption(id) +} + +func (s *SingleOption) UnsetOptionId(id string) { + if string(*s) == id { + *s = SingleOption("") + } +} + +func (s *SingleOption) IsOptionIdSet(id string) bool { + return *s == SingleOption(id) +} + +type MultiOption UnorderedStrings + +func (m MultiOption) CustomFieldTag() string { + return CUSTOMFIELDTYPE_MULTIOPTION +} + +func (m MultiOption) Equal(c Comparable) bool { + var n MultiOption + switch v := c.(type) { + case MultiOption: + n = v + case *MultiOption: + n = *v + default: + panic(fmt.Errorf("%v is not a MultiOption is %T", c, c)) + } + if len(m) != len(n) { + return false + } + a := UnorderedStrings(m) + b := UnorderedStrings(n) + return (&a).Equal(&b) + +} + +func (m *MultiOption) SetOptionId(id string) { + if !m.IsOptionIdSet(id) { + *m = append(*m, id) + } +} + +func (m *MultiOption) UnsetOptionId(id string) { + if m.IsOptionIdSet(id) { + t := []string(*m) + indexOfTarget := -1 + for i := 0; i < len(*m); i++ { + if t[i] == id { + indexOfTarget = i + } + } + if indexOfTarget >= 0 { + *m = append(t[:indexOfTarget], t[indexOfTarget+1:]...) + } + } +} + +func (m *MultiOption) IsOptionIdSet(id string) bool { + for _, option := range *m { + if option == id { + return true + } + } + return false +} + +type TextList []string + +func (t TextList) CustomFieldTag() string { + return CUSTOMFIELDTYPE_TEXTLIST +} + +type LocationList UnorderedStrings + +func (l LocationList) CustomFieldTag() string { + return CUSTOMFIELDTYPE_LOCATIONLIST +} + +func (m LocationList) Equal(c Comparable) bool { + var n LocationList + switch v := c.(type) { + case LocationList: + n = v + case *LocationList: + n = *v + default: + panic(fmt.Errorf("%v is not a LocationList is %T", c, c)) + } + if len(m) != len(n) { + return false + } + a := UnorderedStrings(m) + b := UnorderedStrings(n) + return (&a).Equal(&b) +} + +type CustomLocationPhoto struct { + Url string `json:"url,omitempty"` + Description string `json:"description,omitempty"` + Details string `json:"details,omitempty"` + ClickThroughURL string `json:"clickthroughUrl,omitempty"` +} + +func (p *CustomLocationPhoto) CustomFieldTag() string { + return CUSTOMFIELDTYPE_PHOTO +} + +type CustomLocationGallery []*CustomLocationPhoto + +func (g *CustomLocationGallery) CustomFieldTag() string { + return CUSTOMFIELDTYPE_GALLERY +} + +type CustomLocationVideo struct { + Description string `json:"description"` + Url string `json:"url"` +} + +type VideoGallery []CustomLocationVideo + +func (v *VideoGallery) CustomFieldTag() string { + return CUSTOMFIELDTYPE_VIDEO +} + +// CustomLocationHours 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 CustomLocationHours struct { + AdditionalText string `json:"additionalHoursText,omitempty"` + Hours string `json:"hours,omitempty"` + HolidayHours []LocationHolidayHours `json:"holidayHours,omitempty"` +} + +func (h CustomLocationHours) CustomFieldTag() string { + return CUSTOMFIELDTYPE_HOURS +} + +// CustomLocationDailyTimes is the DailyTimes custom field format used by locations API +// Entities API uses the new DailyTimes struct +type CustomLocationDailyTimes struct { + DailyTimes string `json:"dailyTimes,omitempty"` +} + +func (d CustomLocationDailyTimes) CustomFieldTag() string { + return CUSTOMFIELDTYPE_DAILYTIMES +} + +type DailyTimes struct { + Sunday string `json:"sunday,omitempty"` + Monday string `json:"monday,omitempty"` + Tuesday string `json:"tuesday,omitempty"` + Wednesday string `json:"wednesday,omitempty"` + Thursday string `json:"thursday,omitempty"` + Friday string `json:"friday,omitempty"` + Saturday string `json:"saturday,omitempty"` +} diff --git a/location_customfield_service.go b/location_customfield_service.go new file mode 100644 index 0000000..3c15a60 --- /dev/null +++ b/location_customfield_service.go @@ -0,0 +1,797 @@ +package yext + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" +) + +type LocationCustomFieldService struct { + CustomFieldManager *LocationCustomFieldManager + client *Client +} + +func (s *LocationCustomFieldService) ListAll() ([]*CustomField, error) { + var customFields []*CustomField + var lr listRetriever = func(opts *ListOptions) (int, int, error) { + cfr, _, err := s.List(opts) + if err != nil { + return 0, 0, err + } + customFields = append(customFields, cfr.CustomFields...) + return len(cfr.CustomFields), cfr.Count, err + } + + if err := listHelper(lr, &ListOptions{Limit: CustomFieldListMaxLimit}); err != nil { + return nil, err + } else { + return customFields, nil + } +} + +func (s *LocationCustomFieldService) List(opts *ListOptions) (*CustomFieldResponse, *Response, error) { + requrl, err := addListOptions(customFieldPath, opts) + if err != nil { + return nil, nil, err + } + + v := &CustomFieldResponse{} + r, err := s.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + return v, r, nil +} + +func (s *LocationCustomFieldService) Create(cf *CustomField) (*Response, error) { + asJSON, err := json.Marshal(cf) + if err != nil { + return nil, err + } + var asMap map[string]interface{} + err = json.Unmarshal(asJSON, &asMap) + if err != nil { + return nil, err + } + delete(asMap, "id") + return s.client.DoRequestJSON("POST", customFieldPath, asMap, nil) +} + +func (s *LocationCustomFieldService) Edit(cf *CustomField) (*Response, error) { + asJSON, err := json.Marshal(cf) + if err != nil { + return nil, err + } + var asMap map[string]interface{} + err = json.Unmarshal(asJSON, &asMap) + if err != nil { + return nil, err + } + delete(asMap, "id") + delete(asMap, "type") + return s.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", customFieldPath, cf.GetId()), asMap, nil) +} + +func (s *LocationCustomFieldService) Delete(customFieldId string) (*Response, error) { + return s.client.DoRequest("DELETE", fmt.Sprintf("%s/%s", customFieldPath, customFieldId), nil) +} + +type LocationCustomFieldManager struct { + CustomFields []*CustomField +} + +func (c *LocationCustomFieldManager) Get(name string, loc *Location) (interface{}, error) { + if loc == nil || loc.CustomFields == nil { + return nil, nil + } + + var ( + field *CustomField + err error + ) + + if field, err = c.CustomField(name); err != nil { + return nil, err + } + + return loc.CustomFields[field.GetId()], nil +} + +func (c *LocationCustomFieldManager) MustGet(name string, loc *Location) interface{} { + if ret, err := c.Get(name, loc); err != nil { + panic(err) + } else { + return ret + } +} + +func (c *LocationCustomFieldManager) IsOptionSet(fieldName string, optionName string, loc *Location) (bool, error) { + var ( + field interface{} + err error + of OptionField + id string + ) + + if field, err = c.Get(fieldName, loc); err != nil { + return false, err + } + + if field == nil { + return false, nil + } + switch field.(type) { + case nil: + return false, nil + case MultiOption: + mo := field.(MultiOption) + of = &mo + case *MultiOption: + of = field.(*MultiOption) + case SingleOption: + so := field.(SingleOption) + of = &so + case *SingleOption: + of = field.(*SingleOption) + default: + return false, fmt.Errorf("'%s' is not an OptionField custom field, is %T", fieldName, field) + } + + if id, err = c.CustomFieldOptionId(fieldName, optionName); err != nil { + return false, err + } + + return of.IsOptionIdSet(id), nil +} + +func (c *LocationCustomFieldManager) MustIsOptionSet(fieldName string, optionName string, loc *Location) bool { + if set, err := c.IsOptionSet(fieldName, optionName, loc); err != nil { + panic(err) + } else { + return set + } +} + +func (c *LocationCustomFieldManager) SetOption(fieldName string, optionName string, loc *Location) (*Location, error) { + var ( + field interface{} + err error + of OptionField + ok bool + id string + ) + + if field, err = c.Get(fieldName, loc); err != nil { + return loc, err + } + + if field == nil { + var cf *CustomField + if cf, err = c.CustomField(fieldName); err != nil { + return loc, fmt.Errorf("problem getting '%s': %v", fieldName, err) + } + switch cf.Type { + case CUSTOMFIELDTYPE_MULTIOPTION: + of = new(MultiOption) + case CUSTOMFIELDTYPE_SINGLEOPTION: + of = new(SingleOption) + default: + return loc, fmt.Errorf("'%s' is not an OptionField is '%s'", cf.Name, cf.Type) + } + } else if of, ok = field.(OptionField); !ok { + return loc, fmt.Errorf("'%s': %v is not an OptionField custom field is %T", fieldName, field, field) + } + + if id, err = c.CustomFieldOptionId(fieldName, optionName); err != nil { + return loc, err + } + + of.SetOptionId(id) + return c.Set(fieldName, of, loc) +} + +func (c *LocationCustomFieldManager) MustSetOption(fieldName string, optionName string, loc *Location) *Location { + if loc, err := c.SetOption(fieldName, optionName, loc); err != nil { + panic(err) + } else { + return loc + } +} + +func (c *LocationCustomFieldManager) UnsetOption(fieldName string, optionName string, loc *Location) (*Location, error) { + var ( + field interface{} + err error + id string + ) + + if field, err = c.Get(fieldName, loc); err != nil { + return loc, err + } + + if field == nil { + return loc, fmt.Errorf("'%s' is not currently set", fieldName) + } + + option, ok := field.(OptionField) + if !ok { + return loc, fmt.Errorf("'%s' is not an OptionField custom field", fieldName) + } + + if id, err = c.CustomFieldOptionId(fieldName, optionName); err != nil { + return loc, err + } + + option.UnsetOptionId(id) + return c.Set(fieldName, option, loc) +} + +func (c *LocationCustomFieldManager) MustUnsetOption(fieldName string, optionName string, loc *Location) *Location { + if loc, err := c.UnsetOption(fieldName, optionName, loc); err != nil { + panic(err) + } else { + return loc + } +} + +// TODO: Why does this return a location? +// TODO: Should we validate the the type we received matches the type of the field? Probably. +func (c *LocationCustomFieldManager) Set(name string, value CustomFieldValue, loc *Location) (*Location, error) { + field, err := c.CustomField(name) + if err != nil { + return loc, err + } + loc.CustomFields[field.GetId()] = value + return loc, nil +} + +func (c *LocationCustomFieldManager) MustSet(name string, value CustomFieldValue, loc *Location) *Location { + if loc, err := c.Set(name, value, loc); err != nil { + panic(err) + } else { + return loc + } +} + +func (c *LocationCustomFieldManager) CustomField(name string) (*CustomField, error) { + names := []string{} + for _, cf := range c.CustomFields { + if name == cf.Name { + return cf, nil + } + names = append(names, cf.Name) + } + + return nil, fmt.Errorf("Unable to find custom field with name %s, available fields: %v", name, names) +} + +func (c *LocationCustomFieldManager) MustCustomField(name string) *CustomField { + if cf, err := c.CustomField(name); err != nil { + panic(err) + } else { + return cf + } +} + +func (c *LocationCustomFieldManager) CustomFieldId(name string) (string, error) { + if cf, err := c.CustomField(name); err != nil { + return "", err + } else { + return cf.GetId(), nil + } +} + +func (c *LocationCustomFieldManager) MustCustomFieldId(name string) string { + if id, err := c.CustomFieldId(name); err != nil { + panic(err) + } else { + return id + } +} + +func (c *LocationCustomFieldManager) CustomFieldName(id string) (string, error) { + ids := []string{} + for _, cf := range c.CustomFields { + if id == cf.GetId() { + return cf.Name, nil + } + ids = append(ids, cf.GetId()) + } + + return "", fmt.Errorf("Unable to find custom field with Id %s, available Ids: %v", id, ids) +} + +func (c *LocationCustomFieldManager) MustCustomFieldName(id string) string { + if name, err := c.CustomFieldName(id); err != nil { + panic(err) + } else { + return name + } +} + +func (c *LocationCustomFieldManager) CustomFieldOptionId(fieldName, optionName string) (string, error) { + cf, err := c.CustomField(fieldName) + if err != nil { + return "", err + } + + if cf.Options == nil { + return "", fmt.Errorf("Custom field %s doesn't have any options", fieldName) + } + + for _, option := range cf.Options { + if option.Value == optionName { + return option.Key, nil + } + } + + return "", fmt.Errorf("Unable to find custom field option with name %s", optionName) +} + +func (c *LocationCustomFieldManager) MustCustomFieldOptionId(fieldName, optionName string) string { + if id, err := c.CustomFieldOptionId(fieldName, optionName); err != nil { + panic(err) + } else { + return id + } +} + +func (c *LocationCustomFieldService) CacheCustomFields() ([]*CustomField, error) { + cfs, err := c.ListAll() + if err != nil { + return nil, err + } + + c.CustomFieldManager = &LocationCustomFieldManager{CustomFields: cfs} + return c.CustomFieldManager.CustomFields, nil +} + +func (c *LocationCustomFieldService) MustCacheCustomFields() []*CustomField { + slice, err := c.CacheCustomFields() + if err != nil { + panic(err) + } + return slice +} + +func ParseCustomFields(cfraw map[string]interface{}, cfs []*CustomField) (map[string]interface{}, error) { + typefor := func(id string) string { + for _, cf := range cfs { + if cf.GetId() == id { + return cf.Type + } + } + return "" + } + + parsed := map[string]interface{}{} + + for k, v := range cfraw { + if _, ok := v.(CustomFieldValue); ok { + parsed[k] = v + continue + } + + var newval interface{} + + switch typefor(k) { + case CUSTOMFIELDTYPE_YESNO: + if typedVal, ok := v.(bool); ok { + newval = YesNo(typedVal) + } else if typedVal, ok := v.(string); ok { + b, err := strconv.ParseBool(typedVal) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as yes/no %v", v, err) + } + newval = YesNo(b) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as yes/no, expected bool got %T", v, v) + } + case CUSTOMFIELDTYPE_NUMBER: + if typedVal, ok := v.(string); ok { + newval = Number(typedVal) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as number, expected string got %T", v, v) + } + case CUSTOMFIELDTYPE_SINGLELINETEXT: + if typedVal, ok := v.(string); ok { + newval = SingleLineText(typedVal) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as single-line text, expected string got %T", v, v) + } + case CUSTOMFIELDTYPE_MULTILINETEXT: + if typedVal, ok := v.(string); ok { + newval = MultiLineText(typedVal) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as multi-line text, expected string got %T", v, v) + } + case CUSTOMFIELDTYPE_SINGLEOPTION: + if typedVal, ok := v.(string); ok { + newval = GetSingleOptionPointer(SingleOption(typedVal)) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as single-option field, expected string got %T", v, v) + } + case CUSTOMFIELDTYPE_URL: + if typedVal, ok := v.(string); ok { + newval = Url(typedVal) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as url field, expected string got %T", v, v) + } + case CUSTOMFIELDTYPE_DATE: + if typedVal, ok := v.(string); ok { + newval = Date(typedVal) + } else { + return nil, fmt.Errorf("parse custom fields failure: could not parse '%v' as date field, expected string got %T", v, v) + } + case CUSTOMFIELDTYPE_TEXTLIST: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Text List Field %v", v, err) + } + var cf TextList + err = json.Unmarshal(asJSON, &cf) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Text List Field %v", v, err) + } + newval = cf + case CUSTOMFIELDTYPE_MULTIOPTION: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Multi-Option Field %v", v, err) + } + var cf MultiOption + err = json.Unmarshal(asJSON, &cf) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Multi-Option Field %v", v, err) + } + newval = cf + case CUSTOMFIELDTYPE_PHOTO: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Photo Field %v", v, err) + } + var cfp *CustomLocationPhoto + err = json.Unmarshal(asJSON, &cfp) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Photo Field %v", v, err) + } + newval = cfp + case CUSTOMFIELDTYPE_GALLERY: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Photo Gallery Field %v", v, err) + } + var g CustomLocationGallery + err = json.Unmarshal(asJSON, &g) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Photo Gallery Field %v", v, err) + } + newval = g + case CUSTOMFIELDTYPE_VIDEO: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Video Field %v", v, err) + } + var cf CustomLocationVideo + err = json.Unmarshal(asJSON, &cf) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Custom Location Video Field %v", v, err) + } + newval = cf + case CUSTOMFIELDTYPE_HOURS: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Hours Field %v", v, err) + } + var cf CustomLocationHours + err = json.Unmarshal(asJSON, &cf) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Hours Field %v", v, err) + } + newval = cf + case CUSTOMFIELDTYPE_DAILYTIMES: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for DailyT imes Field %v", v, err) + } + var cf CustomLocationDailyTimes + err = json.Unmarshal(asJSON, &cf) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Daily Times Field %v", v, err) + } + newval = cf + case CUSTOMFIELDTYPE_LOCATIONLIST: + asJSON, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not re-marshal '%v' as json for Location List Field %v", v, err) + } + var cf LocationList + err = json.Unmarshal(asJSON, &cf) + if err != nil { + return nil, fmt.Errorf("parse custom fields failure: could not unmarshal '%v' into Location List Field %v", v, err) + } + newval = cf + default: + newval = v + } + + parsed[k] = newval + } + + return parsed, nil +} + +// 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)) + } + } + return nil +} + +func (c *LocationCustomFieldManager) GetBool(name string, loc *Location) (bool, error) { + value, err := c.Get(name, loc) + if err != nil { + return false, err + } + + if value == nil { + return false, nil + } + + switch t := value.(type) { + case YesNo: + return bool(value.(YesNo)), nil + case *YesNo: + return bool(*value.(*YesNo)), nil + default: + return false, fmt.Errorf("GetBool failure: Field '%v' is not of a YesNo type", t) + } +} + +func (c *LocationCustomFieldManager) MustGetBool(name string, loc *Location) bool { + if ret, err := c.GetBool(name, loc); err != nil { + panic(err) + } else { + return ret + } +} + +// GetStringAliasCustomField returns the string value from a string type alias +// custom field. It will return an error if the field is not a string type. +func (c *LocationCustomFieldManager) GetString(name string, loc *Location) (string, error) { + fv, err := c.Get(name, loc) + if err != nil { + return "", err + } + if fv == nil { + return "", nil + } + switch fv.(type) { + case SingleLineText: + return string(fv.(SingleLineText)), nil + case *SingleLineText: + return string(*fv.(*SingleLineText)), nil + case MultiLineText: + return string(fv.(MultiLineText)), nil + case *MultiLineText: + return string(*fv.(*MultiLineText)), nil + case Url: + return string(fv.(Url)), nil + case *Url: + return string(*fv.(*Url)), nil + case Date: + return string(fv.(Date)), nil + case *Date: + return string(*fv.(*Date)), nil + case Number: + return string(fv.(Number)), nil + case *Number: + return string(*fv.(*Number)), nil + case SingleOption: + if string(fv.(SingleOption)) == "" { + return "", nil + } + return c.CustomFieldOptionName(name, string(fv.(SingleOption))) + case *SingleOption: + if string(*fv.(*SingleOption)) == "" { + return "", nil + } + return c.CustomFieldOptionName(name, string(*fv.(*SingleOption))) + default: + return "", fmt.Errorf("%s is not a string custom field type, is %T", name, fv) + } +} + +func (c *LocationCustomFieldManager) CustomFieldOptionName(cfName string, optionId string) (string, error) { + cf, err := c.CustomField(cfName) + if err != nil { + return "", err + } + for _, option := range cf.Options { + if option.Key == optionId { + return option.Value, nil + } + } + return "", fmt.Errorf("Unable to find option for key %s for custom field %s", optionId, cfName) +} + +func (c *LocationCustomFieldManager) MustCustomFieldOptionName(fieldName, optionId string) string { + if id, err := c.CustomFieldOptionName(fieldName, optionId); err != nil { + panic(err) + } else { + return id + } +} + +func (c *LocationCustomFieldManager) MustGetString(name string, loc *Location) string { + if ret, err := c.GetString(name, loc); err != nil { + panic(err) + } else { + return ret + } +} + +// GetStringArrayAliasCustomField returns the string array value from a string array +// type alias custom field. It will return an error if the field is not a string +// array type. +func (c *LocationCustomFieldManager) GetStringSlice(name string, loc *Location) ([]string, error) { + fv, err := c.Get(name, loc) + if err != nil { + return nil, err + } + if fv == nil { + return nil, nil + } + switch fv.(type) { + case UnorderedStrings: + return []string(fv.(UnorderedStrings)), nil + case *UnorderedStrings: + return []string(*fv.(*UnorderedStrings)), nil + case LocationList: + return []string(fv.(LocationList)), nil + case *LocationList: + return []string(*fv.(*LocationList)), nil + case TextList: + return []string(fv.(TextList)), nil + case *TextList: + return []string(*fv.(*TextList)), nil + case MultiOption: + return c.CustomFieldOptionNames(name, []string(fv.(MultiOption))) + case *MultiOption: + return c.CustomFieldOptionNames(name, []string(*fv.(*MultiOption))) + default: + return nil, fmt.Errorf("%s is not a string array custom field type, is %T", name, fv) + } +} + +func (c *LocationCustomFieldManager) CustomFieldOptionNames(cfName string, optionIds []string) ([]string, error) { + var optionNames = []string{} + for _, optionId := range optionIds { + optionName, err := c.CustomFieldOptionName(cfName, optionId) + if err != nil { + return nil, err + } + optionNames = append(optionNames, optionName) + } + return optionNames, nil +} + +func (c *LocationCustomFieldManager) MustGetStringSlice(name string, loc *Location) []string { + if ret, err := c.GetStringSlice(name, loc); err != nil { + panic(err) + } else { + return ret + } +} + +func (c *LocationCustomFieldManager) SetBool(name string, value bool, loc *Location) error { + field, err := c.CustomField(name) + if err != nil { + return err + } + + if field.Type != CUSTOMFIELDTYPE_YESNO { + return fmt.Errorf("SetBool failure: custom field '%v' is of type '%v' and not boolean", name, field.Type) + } + + loc.CustomFields[field.GetId()] = YesNo(value) + return nil +} + +func (c *LocationCustomFieldManager) MustSetBool(name string, value bool, loc *Location) { + if err := c.SetBool(name, value, loc); err != nil { + panic(err) + } else { + return + } +} + +func (c *LocationCustomFieldManager) SetStringSlice(name string, value []string, loc *Location) error { + field, err := c.CustomField(name) + if err != nil { + return err + } + + switch field.Type { + case CUSTOMFIELDTYPE_MULTIOPTION: + for _, element := range value { + c.MustSetOption(name, element, loc) + } + return nil + case CUSTOMFIELDTYPE_TEXTLIST: + loc.CustomFields[field.GetId()] = TextList(value) + return nil + case CUSTOMFIELDTYPE_LOCATIONLIST: + loc.CustomFields[field.GetId()] = UnorderedStrings(value) + return nil + default: + return fmt.Errorf("SetStringSlice failure: custom field '%v' is of type '%v' and can not take a string slice", name, field.Type) + } +} + +func (c *LocationCustomFieldManager) MustSetStringSlice(name string, value []string, loc *Location) { + if err := c.SetStringSlice(name, value, loc); err != nil { + panic(err) + } else { + return + } +} + +func (c *LocationCustomFieldManager) SetString(name string, value string, loc *Location) error { + field, err := c.CustomField(name) + if err != nil { + return err + } + + switch field.Type { + case CUSTOMFIELDTYPE_SINGLEOPTION: + c.MustSetOption(name, value, loc) + return nil + case CUSTOMFIELDTYPE_SINGLELINETEXT: + loc.CustomFields[field.GetId()] = SingleLineText(value) + return nil + case CUSTOMFIELDTYPE_MULTILINETEXT: + loc.CustomFields[field.GetId()] = MultiLineText(value) + return nil + case CUSTOMFIELDTYPE_URL: + loc.CustomFields[field.GetId()] = Url(value) + return nil + case CUSTOMFIELDTYPE_DATE: + loc.CustomFields[field.GetId()] = Date(value) + return nil + case CUSTOMFIELDTYPE_NUMBER: + loc.CustomFields[field.GetId()] = Number(value) + return nil + default: + return fmt.Errorf("SetString failure: custom field '%v' is of type '%v' and can not take a string", name, field.Type) + } +} + +func (c *LocationCustomFieldManager) MustSetString(name string, value string, loc *Location) { + Must(c.SetString(name, value, loc)) +} + +func (c *LocationCustomFieldManager) SetPhoto(name string, v *CustomLocationPhoto, loc *Location) error { + _, err := c.Set(name, v, loc) + return err +} + +func (c *LocationCustomFieldManager) UnsetPhoto(name string, loc *Location) error { + return c.SetPhoto(name, UnsetPhotoValue, loc) +} + +func (c *LocationCustomFieldManager) MustSetPhoto(name string, v *CustomLocationPhoto, loc *Location) { + Must(c.SetPhoto(name, v, loc)) +} + +func (c *LocationCustomFieldManager) MustUnsetPhoto(name string, loc *Location) { + Must(c.SetPhoto(name, UnsetPhotoValue, loc)) +} + +func GetSingleOptionPointer(option SingleOption) *SingleOption { + return &option +} diff --git a/location_customfield_service_test.go b/location_customfield_service_test.go new file mode 100644 index 0000000..cbf419c --- /dev/null +++ b/location_customfield_service_test.go @@ -0,0 +1,504 @@ +package yext + +import ( + "encoding/json" + "net/http" + "reflect" + "strconv" + "testing" +) + +func parseAs(cftype string, rawval interface{}) (interface{}, error) { + cfs := []*CustomField{ + &CustomField{ + Id: String("123"), + Type: cftype, + }, + } + + cfsraw := map[string]interface{}{ + "123": rawval, + } + + newcfs, err := ParseCustomFields(cfsraw, cfs) + return newcfs["123"], err +} + +type customFieldParseTest struct { + TypeName string + Raw interface{} + Expected interface{} +} + +func runParseTest(t *testing.T, index int, c customFieldParseTest) { + cf, err := parseAs(c.TypeName, c.Raw) + if err != nil { + t.Error(err) + return + } + var ( + cfType = reflect.TypeOf(cf).String() + expectType = reflect.TypeOf(c.Expected).String() + ) + + if cfType != expectType { + t.Errorf("test #%d (%s) failed:\nexpected type %s\ngot type %s", index, c.TypeName, expectType, cfType) + } + + if !reflect.DeepEqual(cf, c.Expected) { + t.Errorf("test #%d (%s) failed\nexpected value %v\ngot value %v", index, c.TypeName, c.Expected, cf) + } +} + +var ( + customPhotoRaw = map[string]interface{}{ + "details": "A great picture", + "description": "This is a picture of an awesome event", + "clickthroughUrl": "https://yext.com/event", + "url": "https://mktgcdn.com/awesome.jpg", + } + // hoursRaw is in the format used by HoursCustom for location-service + hoursRaw = 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", + } + // dailyTimesRaw is in the format used by DailyTimesCustom for location-service + dailyTimesRaw = map[string]interface{}{ + "dailyTimes": "1:10:00;2:4:00;3:5:00;4:6:00;5:7:00;6:8:00;7:9:00", + } + parseTests = []customFieldParseTest{ + customFieldParseTest{"BOOLEAN", false, YesNo(false)}, + customFieldParseTest{"BOOLEAN", "false", YesNo(false)}, + customFieldParseTest{"NUMBER", "12345", Number("12345")}, + customFieldParseTest{"TEXT", "foo", SingleLineText("foo")}, + customFieldParseTest{"MULTILINE_TEXT", "foo", MultiLineText("foo")}, + customFieldParseTest{"SINGLE_OPTION", "foo", GetSingleOptionPointer(SingleOption("foo"))}, + customFieldParseTest{"URL", "foo", Url("foo")}, + customFieldParseTest{"DATE", "foo", Date("foo")}, + customFieldParseTest{"TEXT_LIST", []string{"a", "b", "c"}, TextList([]string{"a", "b", "c"})}, + customFieldParseTest{"TEXT_LIST", []interface{}{"a", "b", "c"}, TextList([]string{"a", "b", "c"})}, + customFieldParseTest{"MULTI_OPTION", []string{"a", "b", "c"}, MultiOption([]string{"a", "b", "c"})}, + customFieldParseTest{"MULTI_OPTION", []interface{}{"a", "b", "c"}, MultiOption([]string{"a", "b", "c"})}, + customFieldParseTest{"PHOTO", customPhotoRaw, &CustomLocationPhoto{ + Url: "https://mktgcdn.com/awesome.jpg", + Description: "This is a picture of an awesome event", + Details: "A great picture", + ClickThroughURL: "https://yext.com/event", + }}, + customFieldParseTest{"PHOTO", nil, (*CustomLocationPhoto)(nil)}, + customFieldParseTest{"GALLERY", []interface{}{customPhotoRaw}, CustomLocationGallery{ + &CustomLocationPhoto{ + Url: "https://mktgcdn.com/awesome.jpg", + Description: "This is a picture of an awesome event", + Details: "A great picture", + ClickThroughURL: "https://yext.com/event", + }, + }}, + customFieldParseTest{"VIDEO", videoRaw, CustomLocationVideo{ + Url: "http://www.youtube.com/watch?v=M80FTIcEgZM", + Description: "An example caption for a video", + }}, + customFieldParseTest{"HOURS", hoursRaw, CustomLocationHours{ + 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{"DAILY_TIMES", dailyTimesRaw, CustomLocationDailyTimes{ + DailyTimes: "1:10:00;2:4:00;3:5:00;4:6:00;5:7:00;6:8:00;7:9:00", + }}, + customFieldParseTest{"LOCATION_LIST", []string{"a", "b", "c"}, LocationList([]string{"a", "b", "c"})}, + } +) + +func TestListAll(t *testing.T) { + maxLimit := strconv.Itoa(CustomFieldListMaxLimit) + + type req struct { + limit string + offset string + } + + tests := []struct { + count int + reqs []req + }{ + { + count: 0, + reqs: []req{{limit: maxLimit, offset: ""}}, + }, + { + count: 1000, + reqs: []req{{limit: maxLimit, offset: ""}}, + }, + { + count: 1001, + reqs: []req{{limit: maxLimit, offset: ""}, {limit: maxLimit, offset: "1000"}}, + }, + { + count: 2000, + reqs: []req{{limit: maxLimit, offset: ""}, {limit: maxLimit, offset: "1000"}}, + }, + } + + for _, test := range tests { + setup() + reqs := 0 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + reqs++ + if reqs > len(test.reqs) { + t.Errorf("Too many requests sent to custom field list - got %d, expected %d", reqs, len(test.reqs)) + } + + expectedreq := test.reqs[reqs-1] + + if v := r.URL.Query().Get("limit"); v != expectedreq.limit { + t.Errorf("Wanted limit %s, got %s", expectedreq.limit, v) + } + + if v := r.URL.Query().Get("offset"); v != expectedreq.offset { + t.Errorf("Wanted offset %s, got %s", expectedreq.offset, v) + } + + cfs := []*CustomField{} + remaining := test.count - ((reqs - 1) * CustomFieldListMaxLimit) + if remaining > 0 { + if remaining > CustomFieldListMaxLimit { + remaining = CustomFieldListMaxLimit + } + cfs = makeCustomFields(remaining) + } + + v := &mockResponse{Response: &CustomFieldResponse{Count: test.count, CustomFields: cfs}} + data, _ := json.Marshal(v) + w.Write(data) + }) + + client.LocationCustomFieldService.ListAll() + if reqs < len(test.reqs) { + t.Errorf("Too few requests sent to custom field list - got %d, expected %d", reqs, len(test.reqs)) + } + + teardown() + } +} + +func TestListMismatchCount(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + v := &mockResponse{Response: &CustomFieldResponse{Count: 25, CustomFields: makeCustomFields(24)}} + data, _ := json.Marshal(v) + w.Write(data) + }) + + rlr, err := client.LocationCustomFieldService.ListAll() + if rlr != nil { + t.Error("Expected response to be nil when receiving mismatched count and number of custom fields") + } + if err == nil { + t.Error("Expected error to be present when receiving mismatched count and number of custom fields") + } +} + +func TestMustCache(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + v := &mockResponse{Response: &CustomFieldResponse{Count: 24, CustomFields: makeCustomFields(24)}} + data, _ := json.Marshal(v) + w.Write(data) + }) + m := client.LocationCustomFieldService.MustCacheCustomFields() + n := makeCustomFields(24) + for i := 0; i < 24; i++ { + if m[i].GetId() != n[i].GetId() { + t.Error("Must Cache Custom fields should return the same custom field slice as makeCustomFields") + } + } +} + +func TestParsing(t *testing.T) { + for i, testData := range parseTests { + runParseTest(t, i, testData) + } +} + +func TestParseLeaveUnknownTypes(t *testing.T) { + type peanut []string + cf, err := parseAs("BLAH", peanut([]string{"a", "b", "c"})) + if err != nil { + t.Error(err) + } + + if _, ok := cf.(peanut); !ok { + t.Errorf("Expected type peanut, got type %T", cf) + } +} + +func TestGetString(t *testing.T) { + var cfManager = &LocationCustomFieldManager{ + CustomFields: []*CustomField{ + &CustomField{ + Name: "Single Line Text", + Id: String("SingleLineText"), + Type: CUSTOMFIELDTYPE_SINGLELINETEXT, + }, + &CustomField{ + Name: "Multi Line Text", + Id: String("MultiLineText"), + Type: CUSTOMFIELDTYPE_MULTILINETEXT, + }, + &CustomField{ + Name: "Date", + Id: String("Date"), + Type: CUSTOMFIELDTYPE_DATE, + }, + &CustomField{ + Name: "Number", + Id: String("Number"), + Type: CUSTOMFIELDTYPE_NUMBER, + }, + &CustomField{ + Name: "Single Option", + Id: String("SingleOption"), + Type: CUSTOMFIELDTYPE_SINGLEOPTION, + Options: []CustomFieldOption{ + CustomFieldOption{ + Key: "SingleOptionOneKey", + Value: "Single Option One Value", + }, + }, + }, + &CustomField{ + Name: "Url", + Id: String("Url"), + Type: CUSTOMFIELDTYPE_URL, + }, + }, + } + + loc := &Location{ + CustomFields: map[string]interface{}{ + cfManager.MustCustomFieldId("Single Line Text"): SingleLineText("Single Line Text Value"), + cfManager.MustCustomFieldId("Multi Line Text"): MultiLineText("Multi Line Text Value"), + cfManager.MustCustomFieldId("Date"): Date("04/16/2018"), + cfManager.MustCustomFieldId("Number"): Number("2"), + cfManager.MustCustomFieldId("Single Option"): GetSingleOptionPointer(SingleOption(cfManager.MustCustomFieldOptionId("Single Option", "Single Option One Value"))), + cfManager.MustCustomFieldId("Url"): Url("www.url.com"), + }, + } + + blankLoc := &Location{ + CustomFields: map[string]interface{}{ + cfManager.MustCustomFieldId("Single Line Text"): SingleLineText(""), + cfManager.MustCustomFieldId("Multi Line Text"): MultiLineText(""), + cfManager.MustCustomFieldId("Date"): Date(""), + cfManager.MustCustomFieldId("Number"): Number(""), + cfManager.MustCustomFieldId("Single Option"): GetSingleOptionPointer(SingleOption("")), + cfManager.MustCustomFieldId("Url"): Url(""), + }, + } + + tests := []struct { + CFName string + Loc *Location + Want string + }{ + { + CFName: "Single Line Text", + Loc: loc, + Want: "Single Line Text Value", + }, + { + CFName: "Multi Line Text", + Loc: loc, + Want: "Multi Line Text Value", + }, + { + CFName: "Single Option", + Loc: loc, + Want: "Single Option One Value", + }, + { + CFName: "Date", + Loc: loc, + Want: "04/16/2018", + }, + { + CFName: "Number", + Loc: loc, + Want: "2", + }, + { + CFName: "Url", + Loc: loc, + Want: "www.url.com", + }, + { + CFName: "Single Line Text", + Loc: blankLoc, + Want: "", + }, + { + CFName: "Multi Line Text", + Loc: blankLoc, + Want: "", + }, + { + CFName: "Single Option", + Loc: blankLoc, + Want: "", + }, + { + CFName: "Date", + Loc: blankLoc, + Want: "", + }, + { + CFName: "Number", + Loc: blankLoc, + Want: "", + }, + { + CFName: "Url", + Loc: blankLoc, + Want: "", + }, + } + + for _, test := range tests { + if got, err := cfManager.GetString(test.CFName, test.Loc); err != nil { + t.Errorf("Get String: got err for custom field %s: %s", test.CFName, err) + } else if got != test.Want { + t.Errorf("Get String: got '%s', wanted '%s' for custom field %s", got, test.Want, test.CFName) + } + } +} + +func TestGetStringSlice(t *testing.T) { + var cfManager = &LocationCustomFieldManager{ + CustomFields: []*CustomField{ + &CustomField{ + Name: "Text List", + Id: String("TextList"), + Type: CUSTOMFIELDTYPE_TEXTLIST, + }, + &CustomField{ + Name: "Location List", + Id: String("LocationList"), + Type: CUSTOMFIELDTYPE_LOCATIONLIST, + }, + &CustomField{ + Name: "Multi Option", + Id: String("MultiOption"), + Type: CUSTOMFIELDTYPE_MULTIOPTION, + Options: []CustomFieldOption{ + CustomFieldOption{ + Key: "MultiOptionOneKey", + Value: "Multi Option One Value", + }, + CustomFieldOption{ + Key: "MultiOptionTwoKey", + Value: "Multi Option Two Value", + }, + CustomFieldOption{ + Key: "MultiOptionThreeKey", + Value: "Multi Option Three Value", + }, + }, + }, + }, + } + + loc := &Location{ + CustomFields: map[string]interface{}{ + cfManager.MustCustomFieldId("Text List"): TextList([]string{"A", "B", "C"}), + cfManager.MustCustomFieldId("Location List"): LocationList(UnorderedStrings([]string{"1", "2", "3"})), + cfManager.MustCustomFieldId("Multi Option"): MultiOption(UnorderedStrings([]string{"MultiOptionOneKey", "MultiOptionTwoKey", "MultiOptionThreeKey"})), + }, + } + + blankLoc := &Location{ + CustomFields: map[string]interface{}{ + cfManager.MustCustomFieldId("Text List"): TextList([]string{}), + cfManager.MustCustomFieldId("Location List"): LocationList(UnorderedStrings([]string{})), + cfManager.MustCustomFieldId("Multi Option"): MultiOption(UnorderedStrings([]string{})), + }, + } + + tests := []struct { + CFName string + Loc *Location + Want []string + }{ + { + CFName: "Text List", + Loc: loc, + Want: []string{"A", "B", "C"}, + }, + { + CFName: "Location List", + Loc: loc, + Want: []string{"1", "2", "3"}, + }, + { + CFName: "Multi Option", + Loc: loc, + Want: []string{"Multi Option One Value", "Multi Option Two Value", "Multi Option Three Value"}, + }, + { + CFName: "Text List", + Loc: blankLoc, + Want: []string{}, + }, + { + CFName: "Location List", + Loc: blankLoc, + Want: []string{}, + }, + { + CFName: "Multi Option", + Loc: blankLoc, + Want: []string{}, + }, + } + + for _, test := range tests { + if got, err := cfManager.GetStringSlice(test.CFName, test.Loc); err != nil { + t.Errorf("Get String Slice: got err for custom field %s: %s", test.CFName, err) + } else if got == nil && test.Want != nil || got != nil && test.Want == nil { + t.Errorf("Get String Slice: got %v, wanted %v for custom field %s", got, test.Want, test.CFName) + } else { + for i, _ := range got { + if got[i] != test.Want[i] { + t.Errorf("Get String Slice: got %v, wanted %v for custom field %s", got, test.Want, test.CFName) + } + } + } + } +} diff --git a/customfield_test.go b/location_customfield_test.go similarity index 100% rename from customfield_test.go rename to location_customfield_test.go diff --git a/location_diff.go b/location_diff.go index a0e7a36..dfff1f8 100644 --- a/location_diff.go +++ b/location_diff.go @@ -122,6 +122,7 @@ func getUnderlyingValue(v interface{}) interface{} { return nil } rv = rv.Elem() + return getUnderlyingValue(rv.Interface()) } return rv.Interface() @@ -147,7 +148,7 @@ func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { if v.IsNil() && !interpretNilAsZeroValue { return false } - return isZeroValue(v.Elem(), interpretNilAsZeroValue) + return isZeroValue(v.Elem(), true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) case reflect.Struct: for i, n := 0, v.NumField(); i < n; i++ { if !isZeroValue(v.Field(i), true) { diff --git a/location_diff_test.go b/location_diff_test.go index e2ada9d..c40df9e 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -194,7 +194,7 @@ var baseLocation Location = Location{ Photos: &[]LocationPhoto{examplePhoto, examplePhoto, examplePhoto}, ProductListIds: &[]string{"1234", "5678"}, Closed: &LocationClosed{ - IsClosed: false, + IsClosed: Bool(false), }, CustomFields: map[string]interface{}{ "1234": "ding", @@ -252,7 +252,7 @@ func TestDiffIdentical(t *testing.T) { Photos: &[]LocationPhoto{examplePhoto, examplePhoto, examplePhoto}, ProductListIds: &[]string{"1234", "5678"}, Closed: &LocationClosed{ - IsClosed: false, + IsClosed: Bool(false), }, CustomFields: map[string]interface{}{ "1234": "ding", @@ -384,162 +384,162 @@ func TestBoolDiffs(t *testing.T) { } type googleAttributesTest struct { - baseValue *GoogleAttributes - newValue *GoogleAttributes + baseValue *LocationGoogleAttributes + newValue *LocationGoogleAttributes isDiff bool nilIsEmpty bool - expectedFieldValue []*GoogleAttribute + expectedFieldValue []*LocationGoogleAttribute } var googleAttributesTests = []googleAttributesTest{ {nil, nil, false, false, nil}, { baseValue: nil, - newValue: &GoogleAttributes{}, + newValue: &LocationGoogleAttributes{}, isDiff: true, nilIsEmpty: false, - expectedFieldValue: GoogleAttributes{}, + expectedFieldValue: LocationGoogleAttributes{}, }, { baseValue: nil, - newValue: &GoogleAttributes{}, + newValue: &LocationGoogleAttributes{}, isDiff: false, nilIsEmpty: true, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{}, + baseValue: &LocationGoogleAttributes{}, newValue: nil, isDiff: false, nilIsEmpty: true, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{}, + baseValue: &LocationGoogleAttributes{}, newValue: nil, isDiff: false, nilIsEmpty: false, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{&GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}}, - newValue: &GoogleAttributes{&GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}}, + baseValue: &LocationGoogleAttributes{&LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}}, + newValue: &LocationGoogleAttributes{&LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}}, isDiff: false, nilIsEmpty: false, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{&GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}}, - newValue: &GoogleAttributes{&GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"false"})}}, + baseValue: &LocationGoogleAttributes{&LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}}, + newValue: &LocationGoogleAttributes{&LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"false"})}}, isDiff: true, nilIsEmpty: false, - expectedFieldValue: []*GoogleAttribute{&GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"false"})}}, + expectedFieldValue: []*LocationGoogleAttribute{&LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"false"})}}, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, }, isDiff: false, nilIsEmpty: false, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{OptionIds: Strings([]string{"true"}), Id: String("has_delivery")}, - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{OptionIds: Strings([]string{"true"}), Id: String("has_delivery")}, + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, }, isDiff: false, nilIsEmpty: false, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"false"})}, - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"false"})}, + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, }, isDiff: true, nilIsEmpty: false, - expectedFieldValue: GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + expectedFieldValue: LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"true"})}, + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, }, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, }, isDiff: true, nilIsEmpty: false, - expectedFieldValue: GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, - &GoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, + expectedFieldValue: LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + &LocationGoogleAttribute{Id: String("has_delivery"), OptionIds: Strings([]string{"true"})}, }, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: nil}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: nil}, }, isDiff: true, nilIsEmpty: false, - expectedFieldValue: GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + expectedFieldValue: LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: nil}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: nil}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, isDiff: true, nilIsEmpty: false, - expectedFieldValue: GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + expectedFieldValue: LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: nil}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: nil}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: nil}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: nil}, }, isDiff: false, nilIsEmpty: false, expectedFieldValue: nil, }, { - baseValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + baseValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, - newValue: &GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{})}, + newValue: &LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{})}, }, isDiff: true, nilIsEmpty: false, - expectedFieldValue: GoogleAttributes{ - &GoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, + expectedFieldValue: LocationGoogleAttributes{ + &LocationGoogleAttribute{Id: String("has_catering"), OptionIds: Strings([]string{"false"})}, }, }, } @@ -796,7 +796,7 @@ type customFieldsTest struct { } var baseCustomFields = map[string]interface{}{ - "62150": Gallery{ + "62150": CustomLocationGallery{ &CustomLocationPhoto{ ClickThroughURL: "https://locations.yext.com", Description: "This is the caption", @@ -1079,23 +1079,23 @@ var closedTests = []closedTest{ { nil, &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, true, false, &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, }, { &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, false, @@ -1104,7 +1104,7 @@ var closedTests = []closedTest{ }, { &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, nil, @@ -1114,33 +1114,33 @@ var closedTests = []closedTest{ }, { &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, &LocationClosed{ - IsClosed: false, + IsClosed: Bool(false), ClosedDate: "1/1/2001", }, true, false, &LocationClosed{ - IsClosed: false, + IsClosed: Bool(false), ClosedDate: "1/1/2001", }, }, { &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2001", }, &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2002", }, true, false, &LocationClosed{ - IsClosed: true, + IsClosed: Bool(true), ClosedDate: "1/1/2002", }, }, @@ -1173,10 +1173,10 @@ func TestClosedDiffs(t *testing.T) { t.Errorf("%v\ndelta was nil but expected %v\n", data.formatErrorBase(i), formatClosed(data.expectedFieldValue)) } else if d != nil && data.expectedFieldValue == nil { t.Errorf("%v\ndelta was not nil but expected nil\n diff:%v\n", data.formatErrorBase(i), d) - } else if d.Closed.IsClosed != data.expectedFieldValue.IsClosed { - t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) + } else if GetBool(d.Closed.IsClosed) != GetBool(data.expectedFieldValue.IsClosed) { + t.Errorf("%v\ndiff was %v\n", data.formatErrorBase(i), d) } else if d.Closed.ClosedDate != data.expectedFieldValue.ClosedDate { - t.Errorf("%v\ndiff was%v\n", data.formatErrorBase(i), d) + t.Errorf("%v\ndiff was %v\n", data.formatErrorBase(i), d) } } } @@ -1363,7 +1363,7 @@ func TestLocationNils(t *testing.T) { b.Name = String("") b.Emails = &[]string{} b.Headshot = &LocationPhoto{} - b.GoogleAttributes = &GoogleAttributes{} + b.GoogleAttributes = &LocationGoogleAttributes{} a.nilIsEmpty, b.nilIsEmpty = true, true d, isDiff := a.Diff(b) @@ -1693,6 +1693,39 @@ func TestIsZeroValue(t *testing.T) { nilIsEmpty: true, want: true, }, + { + name: "list", + i: &[]HolidayHours{ + HolidayHours{ + Date: String("01-21-2019"), + IsClosed: NullableBool(true), + }, + }, + nilIsEmpty: true, + want: false, + }, + { + name: "**struct", + i: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + nilIsEmpty: true, + want: false, + }, + { + name: "**struct", + i: NullableDayHours(&DayHours{ + IsClosed: NullableBool(false), + }), + nilIsEmpty: true, + want: true, + }, + { + name: "**struct", + i: NullDayHours(), + nilIsEmpty: false, + want: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/location_entity.go b/location_entity.go index 1090c7f..cd52a6f 100644 --- a/location_entity.go +++ b/location_entity.go @@ -7,8 +7,6 @@ package yext import ( "encoding/json" - - yext "github.com/yext/yext-go" ) const ENTITYTYPE_LOCATION EntityType = "location" @@ -20,13 +18,14 @@ type LocationEntity struct { // Admin CategoryIds *[]string `json:"categoryIds,omitempty"` - Closed *bool `json:"closed,omitempty"` + Closed **bool `json:"closed,omitempty"` Keywords *[]string `json:"keywords,omitempty"` // Address Fields Name *string `json:"name,omitempty"` Address *Address `json:"address,omitempty"` - AddressHidden *bool `json:"addressHidden,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -39,80 +38,154 @@ type LocationEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours *Hours `json:"hours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - YearEstablished *float64 `json:"yearEstablished,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Certifications *[]string `json:"certifications,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Products *[]string `json:"products,omitempty"` - Services *[]string `json:"services,omitempty"` - Specialties *[]string `json:"specialties,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo *LocationPhoto `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + Specialties *[]string `json:"specialties,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Image `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs - DisplayCoordinate *Coordinate `json:"yextDisplayCoordinate,omitempty"` - // TODO: Update below - DropoffLat *float64 `json:"dropoffLat,omitempty"` - DropoffLng *float64 `json:"dropoffLng,omitempty"` - WalkableLat *float64 `json:"walkableLat,omitempty"` - WalkableLng *float64 `json:"walkableLng,omitempty"` - RoutableCoordinate *Coordinate `json:"yextRoutableCoordinate,omitempty"` - PickupLat *float64 `json:"pickupLat,omitempty"` - PickupLng *float64 `json:"pickupLng,omitempty"` - - // ECLS - BioListIds *[]string `json:"bioListIds,omitempty"` - BioListsLabel *string `json:"bioListsLabel,omitempty"` - EventListIds *[]string `json:"eventListIds,omitempty"` - EventListsLabel *string `json:"eventListsLabel,omitempty"` - MenuListsLabel *string `json:"menusLabel,omitempty"` - MenuListIds *[]string `json:"menuIds,omitempty"` - ProductListIds *[]string `json:"productListIds,omitempty"` - ProductListsLabel *string `json:"productListsLabel,omitempty"` + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + + // Lists + Bios **Lists `json:"bios,omitempty"` + Calendars **Lists `json:"calendars,omitempty"` + Menus **Lists `json:"menus,omitempty"` + ProductLists **Lists `json:"productLists,omitempty"` // Urls - MenuUrl *Website `json:"menuUrl,omitempty"` - OrderUrl *Website `json:"orderUrl,omitempty"` - ReservationUrl *Website `json:"reservationUrl,omitempty"` - WebsiteUrl *Website `json:"websiteUrl,omitempty"` - FeaturedMessage *FeaturedMessage `json:"featuredMessage,omitempty"` + MenuUrl **Website `json:"menuUrl,omitempty"` + OrderUrl **Website `json:"orderUrl,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` // Uber - UberClientId *string `json:"uberClientId,omitempty"` - UberLinkText *string `json:"uberLinkText,omitempty"` - UberLinkType *string `json:"uberLinkType,omitempty"` - UberTripBrandingText *string `json:"uberTripBrandingText,omitempty"` - UberTripBrandingUrl *string `json:"uberTripBrandingUrl,omitempty"` + //UberClientId *string `json:"uberClientId,omitempty"` + UberLink **UberLink `json:"uberLink,omitempty"` + UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` // Social Media - FacebookCoverPhoto *LocationPhoto `json:"facebookCoverPhoto,omitempty"` - FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` - FacebookProfilePhoto *LocationPhoto `json:"facebookProfilePhoto,omitempty"` + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto *LocationPhoto `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto *LocationPhoto `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` - Photos *[]LocationPhoto `json:"photos,omitempty"` - VideoUrls *[]string `json:"videoUrls,omitempty"` + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` // Reviews - ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` + //ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` } -// TODO: Rename LocationPhoto....profilePhoto? -// Or below could be complex photo vs simple photo +func (l *LocationEntity) UnmarshalJSON(data []byte) error { + type Alias LocationEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(l), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(l, data) +} + +type Video struct { + VideoUrl VideoUrl `json:"video,omitempty"` + Description string `json:"description,omitempty"` +} + +type VideoUrl struct { + Url string `json:"url,omitempty"` +} + +type UberLink struct { + Text *string `json:"text,omitempty"` + Presentation *string `json:"presentation,omitempty"` +} + +func NullableUberLink(u *UberLink) **UberLink { + return &u +} + +func GetUberLink(u **UberLink) *UberLink { + if u == nil { + return nil + } + return *u +} + +func NullUberLink() **UberLink { + u := &UberLink{} + return &u +} + +type UberTripBranding struct { + Text *string `json:"text,omitempty"` + Url *string `json:"url,omitempty"` + Description *string `json:"description,omitempty"` +} + +func NullableUberTripBranding(u *UberTripBranding) **UberTripBranding { + return &u +} + +func GetUberTripBranding(u **UberTripBranding) *UberTripBranding { + if u == nil { + return nil + } + return *u +} + +func NullUberTripBranding() **UberTripBranding { + u := &UberTripBranding{} + return &u +} + +type Lists struct { + Label *string `json:"label,omitempty"` + Ids *[]string `json:"ids,omitempty"` +} + +func NullableLists(l *Lists) **Lists { + return &l +} + +func GetLists(l **Lists) *Lists { + if l == nil { + return nil + } + return *l +} + +func NullLists() **Lists { + l := &Lists{} + return &l +} + type Photo struct { Image *Image `json:"image,omitempty"` ClickthroughUrl *string `json:"clickthroughUrl,omitempty"` @@ -125,6 +198,22 @@ type Image struct { AlternateText *string `json:"alternateText,omitempty"` } +func NullableImage(i *Image) **Image { + return &i +} + +func GetImage(i **Image) *Image { + if i == nil { + return nil + } + return *i +} + +func NullImage() **Image { + i := &Image{} + return &i +} + type Address struct { Line1 *string `json:"line1,omitempty"` Line2 *string `json:"line2,omitempty"` @@ -141,53 +230,124 @@ type FeaturedMessage struct { Url *string `json:"url,omitempty"` } +func NullableFeaturedMessage(f *FeaturedMessage) **FeaturedMessage { + return &f +} + +func GetFeaturedMessage(f **FeaturedMessage) *FeaturedMessage { + if f == nil { + return nil + } + return *f +} + +func NullFeaturedMessage() **FeaturedMessage { + f := &FeaturedMessage{} + return &f +} + type Website struct { DisplayUrl *string `json:"displayUrl,omitempty"` Url *string `json:"url,omitempty"` - PreferDisplayUrl *bool `json:"preferDisplayUrl,omitempty"` + PreferDisplayUrl **bool `json:"preferDisplayUrl,omitempty"` +} + +func NullableWebsite(w *Website) **Website { + return &w +} + +func GetWebsite(w **Website) *Website { + if w == nil { + return nil + } + return *w +} + +func NullWebsite() **Website { + w := &Website{} + return &w } type Coordinate struct { - Latitude *float64 `json:"latitude,omitempty"` - Longitude *float64 `json:"longitude,omitempty"` + Latitude **float64 `json:"latitude,omitempty"` + Longitude **float64 `json:"longitude,omitempty"` +} + +func NullableCoordinate(c *Coordinate) **Coordinate { + return &c +} + +func GetCoordinate(c **Coordinate) *Coordinate { + if c == nil { + return nil + } + return *c +} + +func NullCoordinate() **Coordinate { + c := &Coordinate{} + return &c } type Hours struct { - Monday *DayHours `json:"monday,omitempty"` - Tuesday *DayHours `json:"tuesday,omitempty"` - Wednesday *DayHours `json:"wednesday,omitempty"` - Thursday *DayHours `json:"thursday,omitempty"` - Friday *DayHours `json:"friday,omitempty"` - Saturday *DayHours `json:"saturday,omitempty"` - Sunday *DayHours `json:"sunday,omitempty"` + Monday **DayHours `json:"monday,omitempty"` + Tuesday **DayHours `json:"tuesday,omitempty"` + Wednesday **DayHours `json:"wednesday,omitempty"` + Thursday **DayHours `json:"thursday,omitempty"` + Friday **DayHours `json:"friday,omitempty"` + Saturday **DayHours `json:"saturday,omitempty"` + Sunday **DayHours `json:"sunday,omitempty"` HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` } +func (h Hours) String() string { + b, _ := json.Marshal(h) + return string(b) +} + type DayHours struct { - OpenIntervals []*Interval `json:"openIntervals,omitempty"` - IsClosed *bool `json:"isClosed,omitempty"` + OpenIntervals *[]Interval `json:"openIntervals,omitempty"` + IsClosed **bool `json:"isClosed,omitempty"` +} + +func NullableDayHours(d *DayHours) **DayHours { + return &d +} + +func NullDayHours() **DayHours { + var v *DayHours + return &v +} + +func GetDayHours(d **DayHours) *DayHours { + if d == nil { + return nil + } + return *d } func (d *DayHours) SetClosed() { - d.IsClosed = yext.Bool(true) + d.IsClosed = NullableBool(true) d.OpenIntervals = nil } func (d *DayHours) AddHours(start string, end string) { + intervals := []Interval{} d.IsClosed = nil - if d.OpenIntervals == nil { - d.OpenIntervals = []*Interval{} + if d.OpenIntervals != nil { + intervals = *d.OpenIntervals } - d.OpenIntervals = append(d.OpenIntervals, &Interval{ + intervals = append(intervals, Interval{ Start: start, End: end, }) + d.OpenIntervals = &intervals } func (d *DayHours) SetHours(start string, end string) { d.IsClosed = nil - d.OpenIntervals = []*Interval{ - &Interval{ + d.OpenIntervals = &[]Interval{ + Interval{ Start: start, End: end, }, @@ -206,40 +366,31 @@ func NewInterval(start string, end string) *Interval { func (h *Hours) GetDayHours(w Weekday) *DayHours { switch w { case Sunday: - return h.Sunday + return GetDayHours(h.Sunday) case Monday: - return h.Monday + return GetDayHours(h.Monday) case Tuesday: - return h.Tuesday + return GetDayHours(h.Tuesday) case Wednesday: - return h.Wednesday + return GetDayHours(h.Wednesday) case Thursday: - return h.Thursday + return GetDayHours(h.Thursday) case Friday: - return h.Friday + return GetDayHours(h.Friday) case Saturday: - return h.Saturday + return GetDayHours(h.Saturday) } return nil } func (h *Hours) SetClosedAllWeek() { - h = &Hours{ - Sunday: &DayHours{}, - Monday: &DayHours{}, - Tuesday: &DayHours{}, - Wednesday: &DayHours{}, - Thursday: &DayHours{}, - Friday: &DayHours{}, - Saturday: &DayHours{}, - } - h.Sunday.SetClosed() - h.Monday.SetClosed() - h.Tuesday.SetClosed() - h.Wednesday.SetClosed() - h.Thursday.SetClosed() - h.Friday.SetClosed() - h.Saturday.SetClosed() + h.SetClosed(Sunday) + h.SetClosed(Monday) + h.SetClosed(Tuesday) + h.SetClosed(Wednesday) + h.SetClosed(Thursday) + h.SetClosed(Friday) + h.SetClosed(Saturday) } func (h *Hours) SetClosed(w Weekday) { @@ -247,19 +398,19 @@ func (h *Hours) SetClosed(w Weekday) { d.SetClosed() switch w { case Sunday: - h.Sunday = d + h.Sunday = NullableDayHours(d) case Monday: - h.Monday = d + h.Monday = NullableDayHours(d) case Tuesday: - h.Tuesday = d + h.Tuesday = NullableDayHours(d) case Wednesday: - h.Wednesday = d + h.Wednesday = NullableDayHours(d) case Thursday: - h.Thursday = d + h.Thursday = NullableDayHours(d) case Friday: - h.Friday = d + h.Friday = NullableDayHours(d) case Saturday: - h.Saturday = d + h.Saturday = NullableDayHours(d) } } @@ -290,19 +441,19 @@ func (h *Hours) AddHours(w Weekday, start string, end string) { d.AddHours(start, end) switch w { case Sunday: - h.Sunday = d + h.Sunday = NullableDayHours(d) case Monday: - h.Monday = d + h.Monday = NullableDayHours(d) case Tuesday: - h.Tuesday = d + h.Tuesday = NullableDayHours(d) case Wednesday: - h.Wednesday = d + h.Wednesday = NullableDayHours(d) case Thursday: - h.Thursday = d + h.Thursday = NullableDayHours(d) case Friday: - h.Friday = d + h.Friday = NullableDayHours(d) case Saturday: - h.Saturday = d + h.Saturday = NullableDayHours(d) } } @@ -311,19 +462,19 @@ func (h *Hours) SetHours(w Weekday, start string, end string) { d.AddHours(start, end) switch w { case Sunday: - h.Sunday = d + h.Sunday = NullableDayHours(d) case Monday: - h.Monday = d + h.Monday = NullableDayHours(d) case Tuesday: - h.Tuesday = d + h.Tuesday = NullableDayHours(d) case Wednesday: - h.Wednesday = d + h.Wednesday = NullableDayHours(d) case Thursday: - h.Thursday = d + h.Thursday = NullableDayHours(d) case Friday: - h.Friday = d + h.Friday = NullableDayHours(d) case Saturday: - h.Saturday = d + h.Saturday = NullableDayHours(d) } } @@ -336,7 +487,7 @@ func (y LocationEntity) GetId() string { func (y LocationEntity) GetName() string { if y.Name != nil { - return *y.Name + return GetString(y.Name) } return "" } @@ -350,49 +501,46 @@ func (y LocationEntity) GetAccountId() string { func (y LocationEntity) GetLine1() string { if y.Address != nil && y.Address.Line1 != nil { - return *y.Address.Line1 + return GetString(y.Address.Line1) } return "" } func (y LocationEntity) GetLine2() string { if y.Address != nil && y.Address.Line2 != nil { - return *y.Address.Line2 + return GetString(y.Address.Line2) } return "" } func (y LocationEntity) GetAddressHidden() bool { - if y.AddressHidden != nil { - return *y.AddressHidden - } - return false + return GetNullableBool(y.AddressHidden) } func (y LocationEntity) GetExtraDescription() string { if y.Address != nil && y.Address.ExtraDescription != nil { - return *y.Address.ExtraDescription + return GetString(y.Address.ExtraDescription) } return "" } func (y LocationEntity) GetCity() string { if y.Address != nil && y.Address.City != nil { - return *y.Address.City + return GetString(y.Address.City) } return "" } func (y LocationEntity) GetRegion() string { if y.Address != nil && y.Address.Region != nil { - return *y.Address.Region + return GetString(y.Address.Region) } return "" } func (y LocationEntity) GetPostalCode() string { if y.Address != nil && y.Address.PostalCode != nil { - return *y.Address.PostalCode + return GetString(y.Address.PostalCode) } return "" } @@ -446,41 +594,50 @@ func (y LocationEntity) GetTtyPhone() string { return "" } -func (y LocationEntity) GetFeaturedMessageDescription() string { - if y.FeaturedMessage != nil && y.FeaturedMessage.Description != nil { - return *y.FeaturedMessage.Description +func (y LocationEntity) GetFeaturedMessage() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Description) } return "" } func (y LocationEntity) GetFeaturedMessageUrl() string { - if y.FeaturedMessage != nil && y.FeaturedMessage.Url != nil { - return *y.FeaturedMessage.Url + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Url) } return "" } func (y LocationEntity) GetWebsiteUrl() string { - if y.WebsiteUrl != nil && y.WebsiteUrl.Url != nil { - return *y.WebsiteUrl.Url + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.Url) } return "" } func (y LocationEntity) GetDisplayWebsiteUrl() string { - if y.WebsiteUrl != nil && y.WebsiteUrl.DisplayUrl != nil { - return *y.WebsiteUrl.DisplayUrl + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) } return "" } func (y LocationEntity) GetReservationUrl() string { - if y.ReservationUrl != nil && y.ReservationUrl.Url != nil { - return *y.ReservationUrl.Url + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.Url) } return "" } +func (y LocationEntity) GetHours() *Hours { + return GetHours(y.Hours) +} + func (y LocationEntity) GetAdditionalHoursText() string { if y.AdditionalHoursText != nil { return *y.AdditionalHoursText @@ -511,73 +668,65 @@ func (y LocationEntity) GetFacebookPageUrl() string { func (y LocationEntity) GetYearEstablished() float64 { if y.YearEstablished != nil { - return *y.YearEstablished + return GetNullableFloat(y.YearEstablished) } return 0 } func (y LocationEntity) GetDisplayLat() float64 { - if y.DisplayCoordinate != nil && y.DisplayCoordinate.Latitude != nil { - return *y.DisplayCoordinate.Latitude + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y LocationEntity) GetDisplayLng() float64 { - if y.DisplayCoordinate != nil && y.DisplayCoordinate.Longitude != nil { - return *y.DisplayCoordinate.Longitude + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } func (y LocationEntity) GetRoutableLat() float64 { - if y.RoutableCoordinate != nil && y.RoutableCoordinate.Latitude != nil { - return *y.RoutableCoordinate.Latitude + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) } return 0 } func (y LocationEntity) GetRoutableLng() float64 { - if y.RoutableCoordinate != nil && y.RoutableCoordinate.Longitude != nil { - return *y.RoutableCoordinate.Longitude + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) } return 0 } -func (y LocationEntity) GetBioListIds() (v []string) { - if y.BioListIds != nil { - v = *y.BioListIds - } - return v +func (y LocationEntity) GetBios() (v *Lists) { + return GetLists(y.Bios) } -func (y LocationEntity) GetEventListIds() (v []string) { - if y.EventListIds != nil { - v = *y.EventListIds - } - return v +func (y LocationEntity) GetCalendars() (v *Lists) { + return GetLists(y.Calendars) } -func (y LocationEntity) GetProductListIds() (v []string) { - if y.ProductListIds != nil { - v = *y.ProductListIds - } - return v +func (y LocationEntity) GetProductLists() (v *Lists) { + return GetLists(y.ProductLists) } -func (y LocationEntity) GetMenuListIds() (v []string) { - if y.MenuListIds != nil { - v = *y.MenuListIds - } - return v +func (y LocationEntity) GetMenus() (v *Lists) { + return GetLists(y.Menus) } -func (y LocationEntity) GetReviewBalancingURL() string { - if y.ReviewBalancingURL != nil { - return *y.ReviewBalancingURL - } - return "" -} +// func (y LocationEntity) GetReviewBalancingURL() string { +// if y.ReviewBalancingURL != nil { +// return *y.ReviewBalancingURL +// } +// return "" +// } func (y LocationEntity) GetFirstPartyReviewPage() string { if y.FirstPartyReviewPage != nil { @@ -654,9 +803,9 @@ func (y LocationEntity) GetPaymentOptions() (v []string) { return v } -func (y LocationEntity) GetVideoUrls() (v []string) { - if y.VideoUrls != nil { - v = *y.VideoUrls +func (y LocationEntity) GetVideos() (v []Video) { + if y.Videos != nil { + v = *y.Videos } return v } @@ -669,22 +818,31 @@ func (y LocationEntity) GetGoogleAttributes() map[string][]string { } func (y LocationEntity) GetHolidayHours() []HolidayHours { - if y.Hours != nil && y.Hours.HolidayHours != nil { - return *y.Hours.HolidayHours + h := GetHours(y.Hours) + if h != nil { + return *h.HolidayHours } return nil } func (y LocationEntity) IsClosed() bool { - if y.Closed != nil { - return *y.Closed - } - return false + return GetNullableBool(y.Closed) } // HolidayHours represents individual exceptions to a Location's regular hours in Yext Location Manager. // For details see type HolidayHours struct { - Date string `json:"date"` - Hours []*Interval `json:"hours"` + Date *string `json:"date"` + OpenIntervals *[]Interval `json:"openIntervals,omitempty"` + IsClosed **bool `json:"isClosed,omitempty"` + IsRegularHours **bool `json:"isRegularHours,omitempty"` +} + +func (y HolidayHours) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func ToHolidayHours(y []HolidayHours) *[]HolidayHours { + return &y } diff --git a/registry.go b/registry.go index c273f80..081649e 100644 --- a/registry.go +++ b/registry.go @@ -24,7 +24,7 @@ func (r Registry) Register(key string, val interface{}) { r[key] = newVal } -func (r Registry) Create(key string) (interface{}, error) { +func (r Registry) Initialize(key string) (interface{}, error) { val, ok := r[key] if !ok { return nil, fmt.Errorf("Unable to find key %s in registry. Known keys: %s", key, strings.Join(r.Keys(), ",")) diff --git a/role_diff_test.go b/role_diff_test.go index 6dadf22..bea904f 100644 --- a/role_diff_test.go +++ b/role_diff_test.go @@ -1,61 +1,59 @@ -package yext_test +package yext import ( "fmt" "reflect" "testing" - - "github.com/yext/yext-go" ) func TestRole_Diff(t *testing.T) { tests := []struct { name string - roleA yext.Role - roleB yext.Role - wantDelta yext.Role + roleA Role + roleB Role + wantDelta Role wantDiff bool }{ { name: "Identical Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleA: Role{ + Id: String("3"), + Name: String("Example Role"), }, - roleB: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleB: Role{ + Id: String("3"), + Name: String("Example Role"), }, - wantDelta: yext.Role{}, + wantDelta: Role{}, wantDiff: false, }, { name: "Different 'Id' params in Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleA: Role{ + Id: String("3"), + Name: String("Example Role"), }, - roleB: yext.Role{ - Id: yext.String("4"), - Name: yext.String("Example Role"), + roleB: Role{ + Id: String("4"), + Name: String("Example Role"), }, - wantDelta: yext.Role{ - Id: yext.String("4"), + wantDelta: Role{ + Id: String("4"), }, wantDiff: true, }, { name: "Different 'Name' params in Roles", - roleA: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role"), + roleA: Role{ + Id: String("3"), + Name: String("Example Role"), }, - roleB: yext.Role{ - Id: yext.String("3"), - Name: yext.String("Example Role Two"), + roleB: Role{ + Id: String("3"), + Name: String("Example Role Two"), }, - wantDelta: yext.Role{ - Name: yext.String("Example Role Two"), + wantDelta: Role{ + Name: String("Example Role Two"), }, wantDiff: true, }, diff --git a/type.go b/type.go index cc4ef2b..3ebe0c0 100644 --- a/type.go +++ b/type.go @@ -1,5 +1,19 @@ package yext +import "fmt" + +func NullableBool(v bool) **bool { + p := &v + return &p +} + +func GetNullableBool(v **bool) bool { + if v == nil || *v == nil { + return false + } + return **v +} + func Bool(v bool) *bool { p := new(bool) *p = v @@ -13,6 +27,11 @@ func GetBool(v *bool) bool { return *v } +func NullBool() **bool { + var v *bool + return &v +} + func String(v string) *string { p := new(string) *p = v @@ -26,6 +45,34 @@ func GetString(v *string) string { return *v } +func NullableString(v string) **string { + y := &v + return &y +} + +func GetNullableString(v **string) string { + if v == nil || *v == nil { + return "" + } + return **v +} + +func NullString() **string { + var v *string + return &v +} + +func Strings(v []string) *[]string { + return &v +} + +func GetStrings(v *[]string) []string { + if v == nil { + return []string{} + } + return *v +} + func Float(v float64) *float64 { p := new(float64) *p = v @@ -39,6 +86,23 @@ func GetFloat(v *float64) float64 { return *v } +func NullableFloat(v float64) **float64 { + p := &v + return &p +} + +func GetNullableFloat(v **float64) float64 { + if v == nil || *v == nil { + return 0 + } + return **v +} + +func NullFloat() **float64 { + var v *float64 + return &v +} + func Int(v int) *int { p := new(int) *p = v @@ -52,27 +116,142 @@ func GetInt(v *int) int { return *v } -func Strings(v []string) *[]string { +func NullableInt(v int) **int { + p := &v + return &p +} + +func GetNullableInt(v **int) int { + if v == nil || *v == nil { + return 0 + } + return **v +} + +func NullInt() **int { + var v *int return &v } -func GetStrings(v *[]string) []string { +func NullableDate(v *Date) **Date { + return &v +} + +func GetDate(v **Date) *Date { if v == nil { - return []string{} + return nil } return *v } -func ToUnorderedStrings(v []string) *UnorderedStrings { - u := UnorderedStrings(v) - return &u +func NullDate() **Date { + var v *Date + return &v } -func ToGoogleAttributes(v []*GoogleAttribute) *GoogleAttributes { - u := GoogleAttributes(v) - return &u +func NullableVideo(v *Video) **Video { + return &v +} + +func GetVideo(v **Video) *Video { + if v == nil { + return nil + } + return *v +} + +func NullVideo() **Video { + var v *Video + return &v +} + +func NullablePhoto(v *Photo) **Photo { + return &v +} + +func GetPhoto(v **Photo) *Photo { + if v == nil { + return nil + } + return *v +} + +func NullPhoto() **Photo { + var v *Photo + return &v +} + +func NullableDailyTimes(v *DailyTimes) **DailyTimes { + return &v +} + +func GetDailyTimes(v **DailyTimes) *DailyTimes { + if v == nil { + return nil + } + return *v } -func ToHolidayHours(v []HolidayHours) *[]HolidayHours { +func NullDailyTimes() **DailyTimes { + var v *DailyTimes return &v } + +func NullableHours(v *Hours) **Hours { + return &v +} + +func GetHours(v **Hours) *Hours { + if v == nil { + return nil + } + return *v +} + +func NullHours() **Hours { + var v *Hours + return &v +} + +// UnorderedStrings masks []string properties for which Order doesn't matter, such as LabelIds +type UnorderedStrings []string + +// Equal compares UnorderedStrings +func (a *UnorderedStrings) Equal(b Comparable) bool { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + if a == nil || b == nil { + return false + } + + var ( + u = []string(*a) + s = []string(*b.(*UnorderedStrings)) + ) + if len(u) != len(s) { + return false + } + + for i := 0; i < len(u); i++ { + var found bool + for j := 0; j < len(s); j++ { + if u[i] == s[j] { + found = true + } + } + if !found { + return false + } + } + return true +} + +func ToUnorderedStrings(v []string) *UnorderedStrings { + u := UnorderedStrings(v) + return &u +} From eecffe1a5272acb48976b4afe58e5b24d4d07b1f Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 8 Feb 2019 17:11:59 -0500 Subject: [PATCH 045/285] **Date to **string --- entity_test.go | 9 ++++++--- type.go | 16 ---------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/entity_test.go b/entity_test.go index 302da03..1a0b1a9 100644 --- a/entity_test.go +++ b/entity_test.go @@ -15,10 +15,11 @@ type CustomEntity struct { CFPhoto **Photo `json:"cf_Photo,omitempty"` CFVideos *[]Video `json:"cf_Videos,omitempty"` CFVideo **Video `json:"cf_Video,omitempty"` - CFDate **Date `json:"cf_Date,omitempty"` + CFDate *string `json:"cf_Date,omitempty"` CFSingleOption **string `json:"cf_SingleOption,omitempty"` CFMultiOption *UnorderedStrings `json:"cf_MultiOption,omitempty"` CFYesNo **bool `json:"cf_YesNo,omitempty"` + CFText *string `json:"cf_Text,omitempty"` } type CustomLocationEntity struct { @@ -84,7 +85,7 @@ func TestEntityJSONSerialization(t *testing.T) { {&CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: nil}}, `{}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFSingleOption: NullString()}}, `{"cf_SingleOption":null}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFMultiOption: ToUnorderedStrings([]string{})}}, `{"cf_MultiOption":[]}`}, - {&CustomLocationEntity{CustomEntity: CustomEntity{CFDate: NullDate()}}, `{"cf_Date":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFDate: String("")}}, `{"cf_Date":""}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFVideo: NullVideo()}}, `{"cf_Video":null}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFPhoto: NullPhoto()}}, `{"cf_Photo":null}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFGallery: &[]Photo{}}}, `{"cf_Gallery":[]}`}, @@ -94,6 +95,7 @@ func TestEntityJSONSerialization(t *testing.T) { {&CustomLocationEntity{CustomEntity: CustomEntity{CFYesNo: NullBool()}}, `{"cf_YesNo":null}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Name: String("Hello")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}, `{"name":"Hello","cf_YesNo":null}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Name: String("")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}, `{"name":"","cf_YesNo":null}`}, + {&CustomLocationEntity{CustomEntity: CustomEntity{CFText: String("")}}, `{"cf_Text":""}`}, } for _, test := range tests { @@ -126,7 +128,7 @@ func TestEntityJSONDeserialization(t *testing.T) { {`{"cf_TextList":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: &[]string{}}}}, {`{"cf_SingleOption":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFSingleOption: NullString()}}}, {`{"cf_MultiOption":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFMultiOption: ToUnorderedStrings([]string{})}}}, - {`{"cf_Date":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFDate: NullDate()}}}, + {`{"cf_Date":""}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFDate: String("")}}}, {`{"cf_Video":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFVideo: NullVideo()}}}, {`{"cf_Photo":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFPhoto: NullPhoto()}}}, {`{"cf_Gallery":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFGallery: &[]Photo{}}}}, @@ -136,6 +138,7 @@ func TestEntityJSONDeserialization(t *testing.T) { {`{"cf_YesNo":null}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFYesNo: NullBool()}}}, {`{"name":"Hello","cf_YesNo":null}`, &CustomLocationEntity{LocationEntity: LocationEntity{Name: String("Hello")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}}, {`{"name":"","cf_YesNo":null}`, &CustomLocationEntity{LocationEntity: LocationEntity{Name: String("")}, CustomEntity: CustomEntity{CFYesNo: NullBool()}}}, + {`{"cf_Text":""}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFText: String("")}}}, } for _, test := range tests { diff --git a/type.go b/type.go index 3ebe0c0..5ce0b20 100644 --- a/type.go +++ b/type.go @@ -133,22 +133,6 @@ func NullInt() **int { return &v } -func NullableDate(v *Date) **Date { - return &v -} - -func GetDate(v **Date) *Date { - if v == nil { - return nil - } - return *v -} - -func NullDate() **Date { - var v *Date - return &v -} - func NullableVideo(v *Video) **Video { return &v } From d97d1ab68b920b895bc37e09b9d2577037d4882c Mon Sep 17 00:00:00 2001 From: czou Date: Fri, 8 Feb 2019 11:45:38 -0500 Subject: [PATCH 046/285] Add LocationType variables --- location.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/location.go b/location.go index c7b8298..119481b 100644 --- a/location.go +++ b/location.go @@ -10,6 +10,13 @@ import ( "fmt" ) +type LocationType *string + +var LOCATIONTYPE_LOCATION LocationType = String("LOCATION") +var LOCATIONTYPE_HEALTHCARE_PROFESSIONAL LocationType = String("HEALTHCARE PROFESSIONAL") +var LOCATIONTYPE_HEALTHCARE_FACILITY LocationType = String("HEALTHCARE FACILITY") +var LOCATIONTYPE_RESTAURANT LocationType = String("RESTAURANT") + // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type Location struct { From 02e0161bbbb49e49ae339c8036d812e423ce1de0 Mon Sep 17 00:00:00 2001 From: bharvey Date: Wed, 13 Feb 2019 10:30:30 -0500 Subject: [PATCH 047/285] added cf endpoint location entitytype --- location_entity.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/location_entity.go b/location_entity.go index cd52a6f..baa3237 100644 --- a/location_entity.go +++ b/location_entity.go @@ -11,6 +11,11 @@ import ( const ENTITYTYPE_LOCATION EntityType = "location" +// Entity types for custom fields must be MACRO_CASED +// TODO: wait for completion of this item: https://yexttest.atlassian.net/browse/AO-3660 (ETC End of February 2019) +// After item is copmleted up the vparam and (hopefully) delete this line and remove all references to it. +const ENTITYTYPE_LOCATION_CF EntityType = "LOCATION" + // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type LocationEntity struct { From 026f0fb2f77d33fcc8ca827d53f764445f67d8f7 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 13 Feb 2019 13:15:07 -0500 Subject: [PATCH 048/285] hours: Strip leading 0's --- hours.go | 9 ++++++--- hours_test.go | 12 ++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/hours.go b/hours.go index 004143a..a6078c4 100644 --- a/hours.go +++ b/hours.go @@ -287,10 +287,13 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { if hoursParts[1] == HoursClosed { return Weekday(weekdayInt), HoursClosed, nil } - hours := strings.Join(hoursParts[1:], ":") - if len(hours) != hoursLen { - hours = "0" + hours + // Pad hours with leading 0s + for i := 1; i < len(hoursParts); i += 2 { + if len(hoursParts[i])+len(hoursParts[i+1]) != 4 { + hoursParts[i] = "0" + hoursParts[i] + } } + hours := strings.Join(hoursParts[1:], ":") return Weekday(weekdayInt), hours, nil } diff --git a/hours_test.go b/hours_test.go index 0034a15..fa3fc6b 100644 --- a/hours_test.go +++ b/hours_test.go @@ -58,6 +58,18 @@ func TestHoursHelperFromString(t *testing.T) { Saturday: []string{"08:00:20:00"}, }, }, + { + Have: "2:9:00:18:00,3:9:00:18:00,4:9:00:18:00,5:9:00:18:00,6:9:00:18:00,7:10:00:2:00", + Want: &LocationHoursHelper{ + Sunday: []string{"closed"}, + Monday: []string{"09:00:18:00"}, + Tuesday: []string{"09:00:18:00"}, + Wednesday: []string{"09:00:18:00"}, + Thursday: []string{"09:00:18:00"}, + Friday: []string{"09:00:18:00"}, + Saturday: []string{"10:00:02:00"}, + }, + }, { Have: "", Want: &LocationHoursHelper{ From 3d51ed9768a1ca3d61d5df01ba26f8bd10e34e3d Mon Sep 17 00:00:00 2001 From: bharvey Date: Wed, 13 Feb 2019 13:41:24 -0500 Subject: [PATCH 049/285] add external first party reviews create endpoint --- review.go | 28 +++++++++++++++++++++------- review_service.go | 32 +++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/review.go b/review.go index 01e0ac1..c72fd09 100644 --- a/review.go +++ b/review.go @@ -1,13 +1,13 @@ package yext type Reviewer struct { - LocationId *string `json:"locationId,omitempty"` - FirstName *string `json:"firstName,omitempty"` - LastName *string `json:"lastName,omitempty"` - Contact *string `json:"contact,omitempty"` - Image *bool `json:"image,omitempty"` - TemplateId *string `json:"templateId,omitempty"` - LabelIds []*string `json:"labelIds,omitempty"` + LocationId *string `json:"locationId,omitempty"` + FirstName *string `json:"firstName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Contact *string `json:"contact,omitempty"` + Image *bool `json:"image,omitempty"` + TemplateId *string `json:"templateId,omitempty"` + LabelIds []*string `json:"labelIds,omitempty"` } type Review struct { @@ -29,6 +29,20 @@ type Review struct { ReviewLabels *[]ReviewLabel `json:"reviewLabels"` } +type ReviewCreate struct { + LocationId *string `json:"locationId"` + AccountId *string `json:"accountId"` + Rating *float64 `json:"rating"` + Content *string `json:"content"` + AuthorName *string `json:"authorName"` + AuthorEmail *string `json:"authorEmail,omitempty"` + Status *string `json:"status,omitempty"` + FlagStatus *string `json:"flagStatus,omitempty"` + ReviewLanguage *string `json:"reviewLanguage,omitempty"` + TransationId *string `json:"transactionId,omitempty"` + Date *string `json:"date,omitempty"` +} + type Comment struct { Id *int `json:"id"` ParentId *int `json:"parentId"` diff --git a/review_service.go b/review_service.go index 9c4d9d5..aed1157 100644 --- a/review_service.go +++ b/review_service.go @@ -48,15 +48,19 @@ type ReviewListResponse struct { } type ReviewCreateInvitationResponse struct { - Id string `json:"id"` - LocationId string `json:"locationId"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Contact string `json:"contact"` - Image bool `json:"image"` - TemplateId int `json:"templateId"` - Status string `json:"status"` - Details string `json:"details"` + Id string `json:"id"` + LocationId string `json:"locationId"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Contact string `json:"contact"` + Image bool `json:"image"` + TemplateId int `json:"templateId"` + Status string `json:"status"` + Details string `json:"details"` +} + +type ReviewCreateReviewResponse struct { + Id string `json:"id"` } func (l *ReviewService) ListAllWithOptions(rlOpts *ReviewListOptions) ([]*Review, error) { @@ -200,3 +204,13 @@ func (l *ReviewService) CreateInvitation(jsonData []*Reviewer) ([]*ReviewCreateI return v, r, nil } + +func (l *ReviewService) CreateReview(jsonData []*ReviewCreate) ([]*ReviewCreateReviewResponse, *Response, error) { + var v []*ReviewCreateReviewResponse + r, err := l.client.DoRequestJSON("POST", reviewsPath, jsonData, &v) + if err != nil { + return nil, r, err + } + + return v, r, nil +} From a58dc3f0c83fed28616ede4c38a161470cbf9ce9 Mon Sep 17 00:00:00 2001 From: Alexis Grow <39104038+alexisgrow@users.noreply.github.com> Date: Wed, 20 Feb 2019 12:52:42 -0500 Subject: [PATCH 050/285] Add lastLoginDate to Users struct PC-43487 (#84) --- user.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/user.go b/user.go index de02827..652be53 100644 --- a/user.go +++ b/user.go @@ -14,6 +14,7 @@ type User struct { Password *string `json:"password,omitempty"` SSO *bool `json:"sso,omitempty"` ACLs []ACL `json:"acl,omitempty"` + LastLoginDate *string `json:"lastLoginDate,omitempty"` } func (u *User) GetId() string { @@ -71,6 +72,13 @@ func (u *User) GetSSO() bool { return *u.SSO } +func (u *User) GetLastLoginDate() string { + if u.LastLoginDate == nil { + return "" + } + return *u.LastLoginDate +} + func (u *User) String() string { b, _ := json.Marshal(u) return string(b) From 976bb5694a18e69dc98e505b156d6062b0b7b7b6 Mon Sep 17 00:00:00 2001 From: Byron Harvey Date: Wed, 20 Feb 2019 17:16:34 -0500 Subject: [PATCH 051/285] update reviews service to handle response --- review_service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/review_service.go b/review_service.go index aed1157..e4e7f7c 100644 --- a/review_service.go +++ b/review_service.go @@ -205,8 +205,8 @@ func (l *ReviewService) CreateInvitation(jsonData []*Reviewer) ([]*ReviewCreateI return v, r, nil } -func (l *ReviewService) CreateReview(jsonData []*ReviewCreate) ([]*ReviewCreateReviewResponse, *Response, error) { - var v []*ReviewCreateReviewResponse +func (l *ReviewService) CreateReview(jsonData *ReviewCreate) (*ReviewCreateReviewResponse, *Response, error) { + var v *ReviewCreateReviewResponse r, err := l.client.DoRequestJSON("POST", reviewsPath, jsonData, &v) if err != nil { return nil, r, err From 5916716cfb3fe611e9661421a5d65fd00e5012e7 Mon Sep 17 00:00:00 2001 From: Cindy Zou Date: Fri, 22 Feb 2019 10:31:02 -0500 Subject: [PATCH 052/285] Add GetCountryCode() to LocationEntity --- location_entity.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/location_entity.go b/location_entity.go index baa3237..49815f9 100644 --- a/location_entity.go +++ b/location_entity.go @@ -543,6 +543,13 @@ func (y LocationEntity) GetRegion() string { return "" } +func (y LocationEntity) GetCountryCode() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.CountryCode != nil { + return GetString(y.BaseEntity.Meta.CountryCode) + } + return "" +} + func (y LocationEntity) GetPostalCode() string { if y.Address != nil && y.Address.PostalCode != nil { return GetString(y.Address.PostalCode) From 2da3d766210a174da778bedeeed6b56b3b425f36 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 22 Feb 2019 12:23:39 -0500 Subject: [PATCH 053/285] customfield_service helpers, add restuarant entity --- customfield_service.go | 10 +- customfield_service_test.go | 8 +- entity.go | 16 ++ entity_registry.go | 5 +- location_entity.go | 17 +- restaurant_entity.go | 431 ++++++++++++++++++++++++++++++++++++ 6 files changed, 467 insertions(+), 20 deletions(-) create mode 100644 restaurant_entity.go diff --git a/customfield_service.go b/customfield_service.go index be0d9c4..3d35e6f 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -181,16 +181,16 @@ func (c *CustomFieldManager) NullSingleOption() **string { return NullString() } -func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames ...string) *[]string { +func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames ...string) *UnorderedStrings { var optionIds = []string{} for _, optionName := range optionNames { id := c.MustCustomFieldOptionId(fieldName, optionName) optionIds = append(optionIds, id) } - return &optionIds + return ToUnorderedStrings(optionIds) } -func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *[]string) bool { +func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *UnorderedStrings) bool { if setOptionIds == nil { return false } @@ -203,8 +203,8 @@ func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName s return false } -func (c *CustomFieldManager) NullMultiOption() *[]string { - return &[]string{} +func (c *CustomFieldManager) NullMultiOption() *UnorderedStrings { + return ToUnorderedStrings([]string{}) } func (c *CustomFieldManager) CustomFieldOptionName(cfName string, optionId string) (string, error) { diff --git a/customfield_service_test.go b/customfield_service_test.go index cbac033..ee2e229 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -163,16 +163,16 @@ var cfm = &CustomFieldManager{ } func TestMustIsMultiOptionSet(t *testing.T) { - if !cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{"c_red"}) { + if !cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", ToUnorderedStrings([]string{"c_red"})) { t.Error("TestMustIsMultiOptionSet: red is set but got false") } - if cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{"c_blue"}) { + if cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", ToUnorderedStrings([]string{"c_blue"})) { t.Error("TestMustIsMultiOptionSet: blue is not set but got true") } - if !cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{"c_blue", "c_red"}) { + if !cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", ToUnorderedStrings([]string{"c_blue", "c_red"})) { t.Error("TestMustIsMultiOptionSet: red is set but got false") } - if cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", &[]string{}) { + if cfm.MustIsMultiOptionSet("My Favorite Colors", "Red", ToUnorderedStrings([]string{})) { t.Error("TestMustIsMultiOptionSet: red is not set but got true") } } diff --git a/entity.go b/entity.go index c1f7ffa..459d390 100644 --- a/entity.go +++ b/entity.go @@ -39,13 +39,29 @@ func (b *BaseEntity) GetEntityType() EntityType { } func (b *BaseEntity) GetFolderId() string { + if b == nil || b.Meta == nil { + return "" + } if b.Meta.FolderId != nil { return *b.Meta.FolderId } return "" } +func (b *BaseEntity) GetCountryCode() string { + if b == nil || b.Meta == nil { + return "" + } + if b.Meta.CountryCode != nil { + return *b.Meta.CountryCode + } + return "" +} + func (b *BaseEntity) GetLabels() (v UnorderedStrings) { + if b == nil || b.Meta == nil { + return nil + } if b.Meta.Labels != nil { v = *b.Meta.Labels } diff --git a/entity_registry.go b/entity_registry.go index 89dd990..157a3ac 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -9,8 +9,9 @@ type EntityRegistry Registry func defaultEntityRegistry() *EntityRegistry { registry := make(Registry) - registry.Register(string(ENTITYTYPE_LOCATION), &Location{}) - registry.Register(string(ENTITYTYPE_EVENT), &Event{}) + registry.Register(string(ENTITYTYPE_LOCATION), &LocationEntity{}) + registry.Register(string(ENTITYTYPE_EVENT), &EventEntity{}) + registry.Register(string(ENTITYTYPE_RESTAURANT), &RestaurantEntity{}) entityRegistry := EntityRegistry(registry) return &entityRegistry } diff --git a/location_entity.go b/location_entity.go index 49815f9..9c25c3f 100644 --- a/location_entity.go +++ b/location_entity.go @@ -78,7 +78,6 @@ type LocationEntity struct { FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` // Uber - //UberClientId *string `json:"uberClientId,omitempty"` UberLink **UberLink `json:"uberLink,omitempty"` UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` @@ -101,7 +100,7 @@ type LocationEntity struct { GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` // Reviews - //ReviewBalancingURL *string `json:"reviewBalancingURL,omitempty"` + ReviewGenerationUrl *string `json:"reviewGenerationUrl,omitempty"` FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` } @@ -733,12 +732,12 @@ func (y LocationEntity) GetMenus() (v *Lists) { return GetLists(y.Menus) } -// func (y LocationEntity) GetReviewBalancingURL() string { -// if y.ReviewBalancingURL != nil { -// return *y.ReviewBalancingURL -// } -// return "" -// } +func (y LocationEntity) GetReviewGenerationUrl() string { + if y.ReviewGenerationUrl != nil { + return *y.ReviewGenerationUrl + } + return "" +} func (y LocationEntity) GetFirstPartyReviewPage() string { if y.FirstPartyReviewPage != nil { @@ -831,7 +830,7 @@ func (y LocationEntity) GetGoogleAttributes() map[string][]string { func (y LocationEntity) GetHolidayHours() []HolidayHours { h := GetHours(y.Hours) - if h != nil { + if h != nil && h.HolidayHours != nil { return *h.HolidayHours } return nil diff --git a/restaurant_entity.go b/restaurant_entity.go new file mode 100644 index 0000000..e14c267 --- /dev/null +++ b/restaurant_entity.go @@ -0,0 +1,431 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_RESTAURANT EntityType = "restaurant" + +type RestaurantEntity struct { + BaseEntity + + // Admin + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed **bool `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + Fax *string `json:"fax,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + Emails *[]string `json:"emails,omitempty"` + + // Location Info + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Image `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + + // Lats & Lngs + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + + // Lists + Bios **Lists `json:"bios,omitempty"` + Calendars **Lists `json:"calendars,omitempty"` + Menus **Lists `json:"menus,omitempty"` + ProductLists **Lists `json:"productLists,omitempty"` + + // Urls + MenuUrl **Website `json:"menuUrl,omitempty"` + OrderUrl **Website `json:"orderUrl,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + + // Uber + UberLink **UberLink `json:"uberLink,omitempty"` + UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` + + // Social Media + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` + + // Reviews + ReviewGenerationUrl *string `json:"reviewGenerationUrl,omitempty"` + FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` +} + +func (r *RestaurantEntity) UnmarshalJSON(data []byte) error { + type Alias RestaurantEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(r), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(r, data) +} + +func (r RestaurantEntity) GetId() string { + if r.BaseEntity.Meta != nil && r.BaseEntity.Meta.Id != nil { + return *r.BaseEntity.Meta.Id + } + return "" +} + +func (r RestaurantEntity) GetName() string { + if r.Name != nil { + return GetString(r.Name) + } + return "" +} + +func (r RestaurantEntity) GetAccountId() string { + if r.BaseEntity.Meta != nil && r.BaseEntity.Meta.AccountId != nil { + return *r.BaseEntity.Meta.AccountId + } + return "" +} + +func (r RestaurantEntity) GetLine1() string { + if r.Address != nil && r.Address.Line1 != nil { + return GetString(r.Address.Line1) + } + return "" +} + +func (r RestaurantEntity) GetLine2() string { + if r.Address != nil && r.Address.Line2 != nil { + return GetString(r.Address.Line2) + } + return "" +} + +func (r RestaurantEntity) GetAddressHidden() bool { + return GetNullableBool(r.AddressHidden) +} + +func (r RestaurantEntity) GetExtraDescription() string { + if r.Address != nil && r.Address.ExtraDescription != nil { + return GetString(r.Address.ExtraDescription) + } + return "" +} + +func (r RestaurantEntity) GetCity() string { + if r.Address != nil && r.Address.City != nil { + return GetString(r.Address.City) + } + return "" +} + +func (r RestaurantEntity) GetRegion() string { + if r.Address != nil && r.Address.Region != nil { + return GetString(r.Address.Region) + } + return "" +} + +func (r RestaurantEntity) GetPostalCode() string { + if r.Address != nil && r.Address.PostalCode != nil { + return GetString(r.Address.PostalCode) + } + return "" +} + +func (r RestaurantEntity) GetMainPhone() string { + if r.MainPhone != nil { + return *r.MainPhone + } + return "" +} + +func (r RestaurantEntity) GetLocalPhone() string { + if r.LocalPhone != nil { + return *r.LocalPhone + } + return "" +} + +func (r RestaurantEntity) GetAlternatePhone() string { + if r.AlternatePhone != nil { + return *r.AlternatePhone + } + return "" +} + +func (r RestaurantEntity) GetFax() string { + if r.Fax != nil { + return *r.Fax + } + return "" +} + +func (r RestaurantEntity) GetMobilePhone() string { + if r.MobilePhone != nil { + return *r.MobilePhone + } + return "" +} + +func (r RestaurantEntity) GetTollFreePhone() string { + if r.TollFreePhone != nil { + return *r.TollFreePhone + } + return "" +} + +func (r RestaurantEntity) GetTtyPhone() string { + if r.TtyPhone != nil { + return *r.TtyPhone + } + return "" +} + +func (r RestaurantEntity) GetFeaturedMessage() string { + f := GetFeaturedMessage(r.FeaturedMessage) + if f != nil { + return GetString(f.Description) + } + return "" +} + +func (r RestaurantEntity) GetFeaturedMessageUrl() string { + f := GetFeaturedMessage(r.FeaturedMessage) + if f != nil { + return GetString(f.Url) + } + return "" +} + +func (r RestaurantEntity) GetWebsiteUrl() string { + w := GetWebsite(r.WebsiteUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (r RestaurantEntity) GetDisplayWebsiteUrl() string { + w := GetWebsite(r.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + +func (r RestaurantEntity) GetReservationUrl() string { + w := GetWebsite(r.ReservationUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (r RestaurantEntity) GetHours() *Hours { + return GetHours(r.Hours) +} + +func (r RestaurantEntity) GetAdditionalHoursText() string { + if r.AdditionalHoursText != nil { + return *r.AdditionalHoursText + } + return "" +} + +func (r RestaurantEntity) GetDescription() string { + if r.Description != nil { + return *r.Description + } + return "" +} + +func (r RestaurantEntity) GetTwitterHandle() string { + if r.TwitterHandle != nil { + return *r.TwitterHandle + } + return "" +} + +func (r RestaurantEntity) GetFacebookPageUrl() string { + if r.FacebookPageUrl != nil { + return *r.FacebookPageUrl + } + return "" +} + +func (r RestaurantEntity) GetYearEstablished() float64 { + if r.YearEstablished != nil { + return GetNullableFloat(r.YearEstablished) + } + return 0 +} + +func (r RestaurantEntity) GetDisplayLat() float64 { + c := GetCoordinate(r.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (r RestaurantEntity) GetDisplayLng() float64 { + c := GetCoordinate(r.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (r RestaurantEntity) GetRoutableLat() float64 { + c := GetCoordinate(r.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (r RestaurantEntity) GetRoutableLng() float64 { + c := GetCoordinate(r.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (r RestaurantEntity) GetBios() (v *Lists) { + return GetLists(r.Bios) +} + +func (r RestaurantEntity) GetCalendars() (v *Lists) { + return GetLists(r.Calendars) +} + +func (r RestaurantEntity) GetProductLists() (v *Lists) { + return GetLists(r.ProductLists) +} + +func (r RestaurantEntity) GetMenus() (v *Lists) { + return GetLists(r.Menus) +} + +func (r RestaurantEntity) GetReviewGenerationUrl() string { + if r.ReviewGenerationUrl != nil { + return *r.ReviewGenerationUrl + } + return "" +} + +func (r RestaurantEntity) GetFirstPartyReviewPage() string { + if r.FirstPartyReviewPage != nil { + return *r.FirstPartyReviewPage + } + return "" +} + +func (r RestaurantEntity) String() string { + b, _ := json.Marshal(r) + return string(b) +} + +func (r RestaurantEntity) GetKeywords() (v []string) { + if r.Keywords != nil { + v = *r.Keywords + } + return v +} + +func (r RestaurantEntity) GetLanguage() (v string) { + if r.BaseEntity.Meta.Language != nil { + v = *r.BaseEntity.Meta.Language + } + return v +} + +func (r RestaurantEntity) GetEmails() (v []string) { + if r.Emails != nil { + v = *r.Emails + } + return v +} + +func (r RestaurantEntity) GetServices() (v []string) { + if r.Services != nil { + v = *r.Services + } + return v +} + +func (r RestaurantEntity) GetLanguages() (v []string) { + if r.Languages != nil { + v = *r.Languages + } + return v +} + +func (r RestaurantEntity) GetPaymentOptions() (v []string) { + if r.PaymentOptions != nil { + v = *r.PaymentOptions + } + return v +} + +func (r RestaurantEntity) GetVideos() (v []Video) { + if r.Videos != nil { + v = *r.Videos + } + return v +} + +func (r RestaurantEntity) GetGoogleAttributes() map[string][]string { + if r.GoogleAttributes != nil { + return *r.GoogleAttributes + } + return nil +} + +func (r RestaurantEntity) GetHolidayHours() []HolidayHours { + h := GetHours(r.Hours) + if h != nil && h.HolidayHours != nil { + return *h.HolidayHours + } + return nil +} + +func (r RestaurantEntity) IsClosed() bool { + return GetNullableBool(r.Closed) +} From 27a272a4d91b14348a53c9d6b18a97231eda5e58 Mon Sep 17 00:00:00 2001 From: czou Date: Fri, 22 Feb 2019 12:56:54 -0500 Subject: [PATCH 054/285] Update variables for healthcare professional and healthcare facility --- location.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/location.go b/location.go index 119481b..56ca7b7 100644 --- a/location.go +++ b/location.go @@ -13,8 +13,8 @@ import ( type LocationType *string var LOCATIONTYPE_LOCATION LocationType = String("LOCATION") -var LOCATIONTYPE_HEALTHCARE_PROFESSIONAL LocationType = String("HEALTHCARE PROFESSIONAL") -var LOCATIONTYPE_HEALTHCARE_FACILITY LocationType = String("HEALTHCARE FACILITY") +var LOCATIONTYPE_HEALTHCARE_PROFESSIONAL LocationType = String("HEALTHCARE_PROFESSIONAL") +var LOCATIONTYPE_HEALTHCARE_FACILITY LocationType = String("HEALTHCARE_FACILITY") var LOCATIONTYPE_RESTAURANT LocationType = String("RESTAURANT") // Location is the representation of a Location in Yext Location Manager. From 41976c627fe12370fdcfff06d83c244d1b579b80 Mon Sep 17 00:00:00 2001 From: Ben Haines Date: Sat, 23 Feb 2019 17:15:39 -0500 Subject: [PATCH 055/285] Return Hours reference from SetClosedAllWeek (#89) * Add NewHoursClosedAllWeek static method --- location_entity.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/location_entity.go b/location_entity.go index 9c25c3f..81597e4 100644 --- a/location_entity.go +++ b/location_entity.go @@ -309,6 +309,12 @@ func (h Hours) String() string { return string(b) } +func NewHoursClosedAllWeek() *Hours { + h := &Hours{} + h.SetClosedAllWeek() + return h +} + type DayHours struct { OpenIntervals *[]Interval `json:"openIntervals,omitempty"` IsClosed **bool `json:"isClosed,omitempty"` From 350d8e8a8e7b4140bd6c27b395439379b13eb500 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 25 Feb 2019 10:37:14 -0500 Subject: [PATCH 056/285] add healthcare_professional entity --- event_entity.go => entity_event.go | 0 entity_healthcare_professional.go | 513 +++++++++++++++++++ location_entity.go => entity_location.go | 0 restaurant_entity.go => entity_restaurant.go | 0 location.go | 16 +- type.go | 8 + 6 files changed, 529 insertions(+), 8 deletions(-) rename event_entity.go => entity_event.go (100%) create mode 100644 entity_healthcare_professional.go rename location_entity.go => entity_location.go (100%) rename restaurant_entity.go => entity_restaurant.go (100%) diff --git a/event_entity.go b/entity_event.go similarity index 100% rename from event_entity.go rename to entity_event.go diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go new file mode 100644 index 0000000..863e143 --- /dev/null +++ b/entity_healthcare_professional.go @@ -0,0 +1,513 @@ +package yext + +// TODO +// * Need better custom field accessors and helpers +// * The API will accept some things and return them in a different format - this makes diff'ing difficult: +// ** Phone: Send in 540-444-4444, get back 5404444444 + +import ( + "encoding/json" + "fmt" +) + +const ENTITYTYPE_HEALTHCAREPROFESSIONAL EntityType = "healthcareProfessional" + +const ENTITYTYPE_HEALTHCAREPROFESSIONAL_CF EntityType = "HEALTHCARE_PROFESSIONAL" + +type HealthcareProfessionalEntity struct { + BaseEntity + + // Admin + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed **bool `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + Fax *string `json:"fax,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + Emails *[]string `json:"emails,omitempty"` + + // Location Info + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Services *[]string `json:"services,omitempty"` + Specialties *[]string `json:"specialties,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Image `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + + // Healthcare + FirstName *string `json:"firstName,omitempty"` + MiddleName *string `json:"middleName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Gender *string `json:"gender,omitempty"` + Headshot *LocationPhoto `json:"headshot,omitempty"` + AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` + AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` + ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` + InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` + NPI *string `json:"npi,omitempty"` + OfficeName *string `json:"officeName,omitempty"` + Degrees *[]string `json:"degrees,omitempty"` + EducationList *[]Education `json:"educationList,omitempty"` + + // Lats & Lngs + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + + // Lists + Bios **Lists `json:"bios,omitempty"` + Calendars **Lists `json:"calendars,omitempty"` + Menus **Lists `json:"menus,omitempty"` + ProductLists **Lists `json:"productLists,omitempty"` + + // Urls + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + + // Uber + UberLink **UberLink `json:"uberLink,omitempty"` + UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` + + // Social Media + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` + + // Reviews + ReviewGenerationUrl *string `json:"reviewGenerationUrl,omitempty"` + FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` +} + +type Education struct { + InstitutionName string `json:"institutionName,omitempty"` + Type string `json:"type,omitempty"` + YearCompleted int `json:"yearCompleted,omitempty"` +} + +func (e Education) String() string { + return fmt.Sprintf("Institution Name: '%v', Type: '%v', Year Completed: '%v'", e.InstitutionName, e.Type, e.YearCompleted) +} + +// Equal compares Education +func (a *Education) Equal(b Comparable) bool { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) + panic(r) + } + }() + + if a == nil || b == nil { + return false + } + + var ( + u = Education(*a) + s = Education(*b.(*Education)) + ) + if u.InstitutionName != s.InstitutionName { + return false + } + + if u.Type != s.Type { + return false + } + + if u.YearCompleted != s.YearCompleted { + return false + } + + return true +} + +func (h *HealthcareProfessionalEntity) UnmarshalJSON(data []byte) error { + type Alias HealthcareProfessionalEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(h), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(h, data) +} + +func (y HealthcareProfessionalEntity) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y HealthcareProfessionalEntity) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetAccountId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { + return *y.BaseEntity.Meta.AccountId + } + return "" +} + +func (y HealthcareProfessionalEntity) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return GetString(y.Address.Line1) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return GetString(y.Address.Line2) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetAddressHidden() bool { + return GetNullableBool(y.AddressHidden) +} + +func (y HealthcareProfessionalEntity) GetExtraDescription() string { + if y.Address != nil && y.Address.ExtraDescription != nil { + return GetString(y.Address.ExtraDescription) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return GetString(y.Address.City) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return GetString(y.Address.Region) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetCountryCode() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.CountryCode != nil { + return GetString(y.BaseEntity.Meta.CountryCode) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return GetString(y.Address.PostalCode) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone + } + return "" +} + +func (y HealthcareProfessionalEntity) GetLocalPhone() string { + if y.LocalPhone != nil { + return *y.LocalPhone + } + return "" +} + +func (y HealthcareProfessionalEntity) GetAlternatePhone() string { + if y.AlternatePhone != nil { + return *y.AlternatePhone + } + return "" +} + +func (y HealthcareProfessionalEntity) GetFax() string { + if y.Fax != nil { + return *y.Fax + } + return "" +} + +func (y HealthcareProfessionalEntity) GetMobilePhone() string { + if y.MobilePhone != nil { + return *y.MobilePhone + } + return "" +} + +func (y HealthcareProfessionalEntity) GetTollFreePhone() string { + if y.TollFreePhone != nil { + return *y.TollFreePhone + } + return "" +} + +func (y HealthcareProfessionalEntity) GetTtyPhone() string { + if y.TtyPhone != nil { + return *y.TtyPhone + } + return "" +} + +func (y HealthcareProfessionalEntity) GetFeaturedMessage() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Description) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetFeaturedMessageUrl() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Url) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetDisplayWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetHours() *Hours { + return GetHours(y.Hours) +} + +func (y HealthcareProfessionalEntity) GetAdditionalHoursText() string { + if y.AdditionalHoursText != nil { + return *y.AdditionalHoursText + } + return "" +} + +func (y HealthcareProfessionalEntity) GetDescription() string { + if y.Description != nil { + return *y.Description + } + return "" +} + +func (y HealthcareProfessionalEntity) GetTwitterHandle() string { + if y.TwitterHandle != nil { + return *y.TwitterHandle + } + return "" +} + +func (y HealthcareProfessionalEntity) GetFacebookPageUrl() string { + if y.FacebookPageUrl != nil { + return *y.FacebookPageUrl + } + return "" +} + +func (y HealthcareProfessionalEntity) GetYearEstablished() float64 { + if y.YearEstablished != nil { + return GetNullableFloat(y.YearEstablished) + } + return 0 +} + +func (y HealthcareProfessionalEntity) GetDisplayLat() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareProfessionalEntity) GetDisplayLng() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HealthcareProfessionalEntity) GetRoutableLat() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareProfessionalEntity) GetRoutableLng() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HealthcareProfessionalEntity) GetBios() (v *Lists) { + return GetLists(y.Bios) +} + +func (y HealthcareProfessionalEntity) GetCalendars() (v *Lists) { + return GetLists(y.Calendars) +} + +func (y HealthcareProfessionalEntity) GetProductLists() (v *Lists) { + return GetLists(y.ProductLists) +} + +func (y HealthcareProfessionalEntity) GetReviewGenerationUrl() string { + if y.ReviewGenerationUrl != nil { + return *y.ReviewGenerationUrl + } + return "" +} + +func (y HealthcareProfessionalEntity) GetFirstPartyReviewPage() string { + if y.FirstPartyReviewPage != nil { + return *y.FirstPartyReviewPage + } + return "" +} + +func (y HealthcareProfessionalEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (y HealthcareProfessionalEntity) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (y HealthcareProfessionalEntity) GetLanguage() (v string) { + if y.BaseEntity.Meta.Language != nil { + v = *y.BaseEntity.Meta.Language + } + return v +} + +func (y HealthcareProfessionalEntity) GetAssociations() (v []string) { + if y.Associations != nil { + v = *y.Associations + } + return v +} + +func (y HealthcareProfessionalEntity) GetEmails() (v []string) { + if y.Emails != nil { + v = *y.Emails + } + return v +} + +func (y HealthcareProfessionalEntity) GetSpecialties() (v []string) { + if y.Specialties != nil { + v = *y.Specialties + } + return v +} + +func (y HealthcareProfessionalEntity) GetServices() (v []string) { + if y.Services != nil { + v = *y.Services + } + return v +} + +func (y HealthcareProfessionalEntity) GetBrands() (v []string) { + if y.Brands != nil { + v = *y.Brands + } + return v +} + +func (y HealthcareProfessionalEntity) GetLanguages() (v []string) { + if y.Languages != nil { + v = *y.Languages + } + return v +} + +func (y HealthcareProfessionalEntity) GetPaymentOptions() (v []string) { + if y.PaymentOptions != nil { + v = *y.PaymentOptions + } + return v +} + +func (y HealthcareProfessionalEntity) GetVideos() (v []Video) { + if y.Videos != nil { + v = *y.Videos + } + return v +} + +func (y HealthcareProfessionalEntity) GetGoogleAttributes() map[string][]string { + if y.GoogleAttributes != nil { + return *y.GoogleAttributes + } + return nil +} + +func (y HealthcareProfessionalEntity) GetHolidayHours() []HolidayHours { + h := GetHours(y.Hours) + if h != nil && h.HolidayHours != nil { + return *h.HolidayHours + } + return nil +} + +func (y HealthcareProfessionalEntity) IsClosed() bool { + return GetNullableBool(y.Closed) +} diff --git a/location_entity.go b/entity_location.go similarity index 100% rename from location_entity.go rename to entity_location.go diff --git a/restaurant_entity.go b/entity_restaurant.go similarity index 100% rename from restaurant_entity.go rename to entity_restaurant.go diff --git a/location.go b/location.go index 56ca7b7..c7b031b 100644 --- a/location.go +++ b/location.go @@ -710,18 +710,18 @@ func (y Location) IsClosed() bool { } //Education is an entry in EducationList which represents a location's (person's) education history -type Education struct { +type LocationEducation struct { InstitutionName string `json:"institutionName,omitempty"` Type string `json:"type,omitempty"` YearCompleted string `json:"yearCompleted,omitempty"` } -func (e Education) String() string { +func (e LocationEducation) String() string { return fmt.Sprintf("Institution Name: '%v', Type: '%v', Year Completed: '%v'", e.InstitutionName, e.Type, e.YearCompleted) } // Equal compares Education -func (a *Education) Equal(b Comparable) bool { +func (a *LocationEducation) Equal(b Comparable) bool { defer func() { if r := recover(); r != nil { fmt.Printf("Value of A: %+v, Value of B:%+v, Type Of A: %T, Type Of B: %T\n", a, b, a, b) @@ -734,8 +734,8 @@ func (a *Education) Equal(b Comparable) bool { } var ( - u = Education(*a) - s = Education(*b.(*Education)) + u = LocationEducation(*a) + s = LocationEducation(*b.(*LocationEducation)) ) if u.InstitutionName != s.InstitutionName { return false @@ -752,7 +752,7 @@ func (a *Education) Equal(b Comparable) bool { return true } -type EducationList []*Education +type EducationList []*LocationEducation func (e EducationList) String() string { var ret string @@ -782,8 +782,8 @@ func (a *EducationList) Equal(b Comparable) bool { } var ( - u = []*Education(*a) - s = []*Education(*b.(*EducationList)) + u = []*LocationEducation(*a) + s = []*LocationEducation(*b.(*EducationList)) ) if len(u) != len(s) { return false diff --git a/type.go b/type.go index 5ce0b20..9ffd21d 100644 --- a/type.go +++ b/type.go @@ -236,6 +236,14 @@ func (a *UnorderedStrings) Equal(b Comparable) bool { } func ToUnorderedStrings(v []string) *UnorderedStrings { + if v == nil { + return nil + } + u := UnorderedStrings(v) + return &u +} + +func NullableUnorderedStrings(v []string) *UnorderedStrings { u := UnorderedStrings(v) return &u } From 497c648cc6287d70a0085d21cd0ad8cdffa4a212 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 25 Feb 2019 11:29:42 -0500 Subject: [PATCH 057/285] unorderedstrings: if both nil, not diff --- location_diff_test.go | 9 +++++++++ type.go | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/location_diff_test.go b/location_diff_test.go index c40df9e..079cc20 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -1343,6 +1343,15 @@ func TestLabels(t *testing.T) { }, WantDiff: true, }, + Scenario{ + A: &Location{ + LabelIds: nil, + }, + B: &Location{ + LabelIds: nil, + }, + WantDiff: false, + }, } ) diff --git a/type.go b/type.go index 9ffd21d..d9f9a4b 100644 --- a/type.go +++ b/type.go @@ -209,7 +209,9 @@ func (a *UnorderedStrings) Equal(b Comparable) bool { } }() - if a == nil || b == nil { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { return false } From b84ec9eb6018fab84d59c3f29aa592499e67347d Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 25 Feb 2019 15:18:29 -0500 Subject: [PATCH 058/285] entity_diff: prevent diff from returning too early --- cftasset_diff_test.go | 137 ++++++++++++++++++++++++++++++++++++++++++ entity_diff.go | 5 +- entity_diff_test.go | 57 ++++++++++++++++++ 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 cftasset_diff_test.go diff --git a/cftasset_diff_test.go b/cftasset_diff_test.go new file mode 100644 index 0000000..80dad1d --- /dev/null +++ b/cftasset_diff_test.go @@ -0,0 +1,137 @@ +package yext + +import ( + "testing" +) + +func TestAssetDiff(t *testing.T) { + tests := []struct { + A *CFTAsset + B *CFTAsset + IsDiff bool + }{ + { + A: &CFTAsset{ + Id: String("400122"), + ForEntities: &ForEntities{ + MappingType: MappingTypeEntities, + EntityIds: ToUnorderedStrings([]string{ + "DK738", + "AG995", + "BP579", + "AP730", + "57GLL", + "0000646579", + "AH350", + }), + }, + }, + B: &CFTAsset{ + Id: String("400122"), + ForEntities: &ForEntities{ + MappingType: MappingTypeEntities, + EntityIds: ToUnorderedStrings([]string{ + "599YM", + "26FMJ", + "0000679221", + }), + }, + }, + IsDiff: true, + }, + { + A: &CFTAsset{ + Id: String("122400"), + Name: String("122400"), + Type: ASSETTYPE_TEXT, + Locale: String("en"), + ForEntities: &ForEntities{ + MappingType: MappingTypeEntities, + EntityIds: ToUnorderedStrings([]string{ + "DK738", + "AG995", + "BP579", + "AP730", + "57GLL", + "0000646579", + "AH350", + }), + }, + Value: TextValue("122400"), + }, + B: &CFTAsset{ + Id: String("122400"), + Name: String("122400"), + Type: ASSETTYPE_TEXT, + Locale: String("en"), + ForEntities: &ForEntities{ + MappingType: MappingTypeEntities, + EntityIds: ToUnorderedStrings([]string{ + "599YM", + "26FMJ", + "0000679221", + }), + }, + Value: TextValue("122400"), + }, + IsDiff: true, + }, + // Shouldn't be detecting diff but for some reason equal struct subfield Usage is throwing diff() off + { + A: &CFTAsset{ + Id: String("122400"), + Name: String("122400"), + Type: ASSETTYPE_TEXT, + Locale: String("en"), + ForEntities: &ForEntities{ + MappingType: MappingTypeEntities, + EntityIds: ToUnorderedStrings([]string{ + "DK738", + "AG995", + "BP579", + "AP730", + "57GLL", + "0000646579", + "AH350", + }), + }, + Usage: &AssetUsageList{ + { + Type: UsageTypeProfileFields, + FieldNames: ToUnorderedStrings([]string{"c_contactFormJPN"}), + }, + }, + Value: TextValue("122400"), + }, + B: &CFTAsset{ + Id: String("122400"), + Name: String("122400"), + Type: ASSETTYPE_TEXT, + Locale: String("en"), + ForEntities: &ForEntities{ + MappingType: MappingTypeEntities, + EntityIds: ToUnorderedStrings([]string{ + "599YM", + "26FMJ", + "0000679221", + }), + }, + Usage: &AssetUsageList{ + { + Type: UsageTypeProfileFields, + FieldNames: ToUnorderedStrings([]string{"c_contactFormJPN"}), + }, + }, + Value: TextValue("122400"), + }, + IsDiff: true, + }, + } + + for _, test := range tests { + diff, isDiff := test.A.Diff(test.B) + if isDiff != test.IsDiff { + t.Errorf("Expected isDiff: %t. Got: %t\nDelta: %+v", test.IsDiff, isDiff, diff) + } + } +} diff --git a/entity_diff.go b/entity_diff.go index 6069955..a0687ea 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -81,9 +81,10 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int comparableB, bOk := bI.(Comparable) if aOk && bOk { if !comparableA.Equal(comparableB) { - return b, true + isDiff = true + indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } - return nil, false + continue } } diff --git a/entity_diff_test.go b/entity_diff_test.go index 941ae5e..532f072 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1656,6 +1656,63 @@ func TestEntityDiffComplex(t *testing.T) { }, }, }, + { + name: "Meta", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + CountryCode: String("US"), + Labels: ToUnorderedStrings([]string{"label1"}), + }, + }, + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + CountryCode: String("US"), + Labels: ToUnorderedStrings([]string{"label1", "label2"}), + }, + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: ToUnorderedStrings([]string{"label1", "label2"}), + }, + }, + }, + }, + }, + { + name: "Meta", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + CountryCode: String("US"), + Labels: ToUnorderedStrings([]string{"label1"}), + }, + }, + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + CountryCode: String("US"), + Labels: ToUnorderedStrings([]string{"label1"}), + }, + }, + }, + }, + isDiff: false, + }, } for _, test := range tests { From 318c71ce750c042082a4f51bf2e211bbb2b8cdc3 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 25 Feb 2019 21:22:09 -0500 Subject: [PATCH 059/285] Add LocationType Event --- location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/location.go b/location.go index c7b031b..49468d4 100644 --- a/location.go +++ b/location.go @@ -16,6 +16,7 @@ var LOCATIONTYPE_LOCATION LocationType = String("LOCATION") var LOCATIONTYPE_HEALTHCARE_PROFESSIONAL LocationType = String("HEALTHCARE_PROFESSIONAL") var LOCATIONTYPE_HEALTHCARE_FACILITY LocationType = String("HEALTHCARE_FACILITY") var LOCATIONTYPE_RESTAURANT LocationType = String("RESTAURANT") +var LOCATIONTYPE_EVENT LocationType = String("EVENT") // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm From d45e0aaab191115564727ff60eb99c54653abe6f Mon Sep 17 00:00:00 2001 From: Ben Haines Date: Wed, 27 Feb 2019 14:18:56 -0500 Subject: [PATCH 060/285] Update searchID param to searchIDs (#96) --- entity_service.go | 6 ++-- entity_service_test.go | 64 +++++++++++++++++++++++------------------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/entity_service.go b/entity_service.go index daba28b..c9293ca 100644 --- a/entity_service.go +++ b/entity_service.go @@ -19,7 +19,7 @@ type EntityService struct { type EntityListOptions struct { ListOptions - SearchID string + SearchIDs []string ResolvePlaceholders bool EntityTypes []string } @@ -153,8 +153,8 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error } q := u.Query() - if opts.SearchID != "" { - q.Add("searchId", opts.SearchID) + if len(opts.SearchIDs) > 0 { + q.Add("searchIds", strings.Join(opts.SearchIDs, ",")) } if opts.ResolvePlaceholders { q.Add("resolvePlaceholders", "true") diff --git a/entity_service_test.go b/entity_service_test.go index 264471b..1885cd1 100644 --- a/entity_service_test.go +++ b/entity_service_test.go @@ -54,60 +54,66 @@ func TestEntityListOptions(t *testing.T) { opts *EntityListOptions limit string token string - searchID string + searchIDs string entityTypes string resolvePlaceholders bool }{ { - opts: nil, - limit: "", - token: "", - searchID: "", + opts: nil, + limit: "", + token: "", + searchIDs: "", }, { // The values are technically 0,0, but that doesn't make any sense in the context of a list request - opts: &EntityListOptions{ListOptions: ListOptions{}}, - limit: "", - token: "", - searchID: "", + opts: &EntityListOptions{ListOptions: ListOptions{}}, + limit: "", + token: "", + searchIDs: "", }, { - opts: &EntityListOptions{ListOptions: ListOptions{Limit: 10}}, - limit: "10", - token: "", - searchID: "", + opts: &EntityListOptions{ListOptions: ListOptions{Limit: 10}}, + limit: "10", + token: "", + searchIDs: "", }, { opts: &EntityListOptions{EntityTypes: []string{"location"}}, limit: "", token: "", - searchID: "", + searchIDs: "", entityTypes: "location", }, { opts: &EntityListOptions{EntityTypes: []string{"location,event"}}, limit: "", token: "", - searchID: "", + searchIDs: "", entityTypes: "location,event", }, { - opts: &EntityListOptions{ListOptions: ListOptions{PageToken: "qwerty1234"}}, - limit: "", - token: "qwerty1234", - searchID: "", + opts: &EntityListOptions{ListOptions: ListOptions{PageToken: "qwerty1234"}}, + limit: "", + token: "qwerty1234", + searchIDs: "", }, { - opts: &EntityListOptions{ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, - limit: "42", - token: "asdfgh4321", - searchID: "", + opts: &EntityListOptions{ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, + limit: "42", + token: "asdfgh4321", + searchIDs: "", }, { - opts: &EntityListOptions{SearchID: "1234", ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, - limit: "42", - token: "asdfgh4321", - searchID: "1234", + opts: &EntityListOptions{SearchIDs: []string{"1234"}}, + limit: "", + token: "", + searchIDs: "1234", + }, + { + opts: &EntityListOptions{SearchIDs: []string{"1234", "5678"}, ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, + limit: "42", + token: "asdfgh4321", + searchIDs: "1234,5678", }, { opts: &EntityListOptions{ResolvePlaceholders: true, ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, @@ -126,8 +132,8 @@ func TestEntityListOptions(t *testing.T) { if v := r.URL.Query().Get("pageToken"); v != test.token { t.Errorf("Wanted token %s, got %s", test.token, v) } - if v := r.URL.Query().Get("searchId"); v != test.searchID { - t.Errorf("Wanted searchId %s, got %s", test.searchID, v) + if v := r.URL.Query().Get("searchIds"); v != test.searchIDs { + t.Errorf("Wanted searchId %s, got %s", test.searchIDs, v) } if v := r.URL.Query().Get("entityTypes"); v != test.entityTypes { t.Errorf("Wanted entityTypes %s, got %s", test.entityTypes, v) From 9360a79ccd7796d82c104c52dc4492fc8cb40b5f Mon Sep 17 00:00:00 2001 From: Byron Harvey Date: Mon, 25 Feb 2019 13:05:17 -0500 Subject: [PATCH 061/285] Create review diff, update pointers, and add getter to entities pr fixes --- entity_location.go | 7 ++++ review.go | 86 +++++++++++++++++++++++++++++++++++++++++++++- review_diff.go | 37 ++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 review_diff.go diff --git a/entity_location.go b/entity_location.go index 81597e4..ab81e07 100644 --- a/entity_location.go +++ b/entity_location.go @@ -495,6 +495,13 @@ func (y LocationEntity) GetId() string { return "" } +func (y LocationEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + func (y LocationEntity) GetName() string { if y.Name != nil { return GetString(y.Name) diff --git a/review.go b/review.go index c72fd09..c6d33da 100644 --- a/review.go +++ b/review.go @@ -1,5 +1,7 @@ package yext +import "encoding/json" + type Reviewer struct { LocationId *string `json:"locationId,omitempty"` FirstName *string `json:"firstName,omitempty"` @@ -39,7 +41,7 @@ type ReviewCreate struct { Status *string `json:"status,omitempty"` FlagStatus *string `json:"flagStatus,omitempty"` ReviewLanguage *string `json:"reviewLanguage,omitempty"` - TransationId *string `json:"transactionId,omitempty"` + TransactionId *string `json:"transactionId,omitempty"` Date *string `json:"date,omitempty"` } @@ -240,3 +242,85 @@ func (y ReviewLabel) GetName() string { } return "" } + +func (y ReviewCreate) GetLocationId() string { + if y.LocationId != nil { + return *y.LocationId + } + return "" +} + +func (y ReviewCreate) GetAccountId() string { + if y.AccountId != nil { + return *y.AccountId + } + return "" +} + +func (y ReviewCreate) GetRating() float64 { + if y.Rating != nil { + return *y.Rating + } + return 0 +} + +func (y ReviewCreate) GetContent() string { + if y.Content != nil { + return *y.Content + } + return "" +} + +func (y ReviewCreate) GetAuthorName() string { + if y.AuthorName != nil { + return *y.AuthorName + } + return "" +} + +func (y ReviewCreate) GetAuthorEmail() string { + if y.AuthorEmail != nil { + return *y.AuthorEmail + } + return "" +} + +func (y ReviewCreate) GetStatus() string { + if y.Status != nil { + return *y.Status + } + return "" +} + +func (y ReviewCreate) GetFlagStatus() string { + if y.FlagStatus != nil { + return *y.FlagStatus + } + return "" +} + +func (y ReviewCreate) GetReviewLanguage() string { + if y.ReviewLanguage != nil { + return *y.ReviewLanguage + } + return "" +} + +func (y ReviewCreate) GetTransactionId() string { + if y.TransactionId != nil { + return *y.TransactionId + } + return "" +} + +func (y ReviewCreate) GetDate() string { + if y.Date != nil { + return *y.Date + } + return "" +} + +func (y ReviewCreate) String() string { + b, _ := json.Marshal(y) + return string(b) +} diff --git a/review_diff.go b/review_diff.go new file mode 100644 index 0000000..e0bc62e --- /dev/null +++ b/review_diff.go @@ -0,0 +1,37 @@ +package yext + +import ( + "reflect" +) + +func (a *ReviewCreate) Diff(b *ReviewCreate) (d *ReviewCreate, diff bool) { + d = &ReviewCreate{LocationId: String(a.GetLocationId())} + + var ( + aValue, bValue = reflect.ValueOf(a).Elem(), reflect.ValueOf(b).Elem() + aType = reflect.TypeOf(*a) + numA = aValue.NumField() + ) + + for i := 0; i < numA; i++ { + var ( + nameA = aType.Field(i).Name + valA = aValue.Field(i) + valB = bValue.Field(i) + ) + + if valB.IsNil() { + continue + } + + if !reflect.DeepEqual(valA.Interface(), valB.Interface()) { + diff = true + reflect.ValueOf(d).Elem().FieldByName(nameA).Set(valB) + } + } + if !diff { + // ensure that d remains nil if nothing has changed + d = nil + } + return d, diff +} From 281c05b7bbfa5fa5dfa488a570dcf6b8fca24177 Mon Sep 17 00:00:00 2001 From: Alexis Grow <39104038+alexisgrow@users.noreply.github.com> Date: Thu, 28 Feb 2019 14:00:46 -0500 Subject: [PATCH 062/285] CustomFieldService: stop adding duplicates for multioption select ids --- customfield_service.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/customfield_service.go b/customfield_service.go index 3d35e6f..8edd9fa 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -182,12 +182,23 @@ func (c *CustomFieldManager) NullSingleOption() **string { } func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames ...string) *UnorderedStrings { - var optionIds = []string{} - for _, optionName := range optionNames { - id := c.MustCustomFieldOptionId(fieldName, optionName) - optionIds = append(optionIds, id) - } - return ToUnorderedStrings(optionIds) + var optionIds = []string{} + for _, optionName := range optionNames { + id := c.MustCustomFieldOptionId(fieldName, optionName) + + shouldAddOptionId := true + for _, optionId := range optionIds { + if id == optionId { // Don't add duplicate option ids + shouldAddOptionId = false + break + } + } + + if shouldAddOptionId { + optionIds = append(optionIds, id) + } + } + return ToUnorderedStrings(optionIds) } func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *UnorderedStrings) bool { From 2a4c54c8792d56c82b5b365782153fdf618fefa3 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 4 Mar 2019 14:46:41 -0500 Subject: [PATCH 063/285] modify healthcare professional entity --- entity_diff_test.go | 50 +++++++++++++++++++++++++++++++ entity_healthcare_professional.go | 26 ++++++++-------- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/entity_diff_test.go b/entity_diff_test.go index 532f072..bfbc11c 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1656,6 +1656,20 @@ func TestEntityDiffComplex(t *testing.T) { }, }, }, + { + name: "Phone", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + MainPhone: String("7032985819"), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + MainPhone: String("7032985819"), + }, + }, + isDiff: false, + }, { name: "Meta", base: &CustomLocationEntity{ @@ -1713,6 +1727,42 @@ func TestEntityDiffComplex(t *testing.T) { }, isDiff: false, }, + { + name: "PhotoGallery", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + PhotoGallery: &[]Photo{ + Photo{ + Image: &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg?context=bWFzdGVyfHJvb3R8MTA3NDM1fGltYWdlL2pwZWd8aGI2L2gzNi84Nzk2NTg2NTQxMDg2LmpwZ3w5MmI1ZTAzZmZhYjc4ODBiNTk2NWMxZGIyZWE5Yjc3N2RhMGE4MjNjZjM2YzU0MjQ0NmE2NWQ5ODZmODcxZGEy"), + }, + }, + Photo{ + Image: &Image{ + Url: String("https://www.napaonline.com/medias/autocare-hero.jpg?context=bWFzdGVyfHJvb3R8OTU0ODN8aW1hZ2UvanBlZ3xoZGMvaDg2Lzg3OTY1ODgxNzk0ODYuanBnfDRkZGIyMTVmZjNiYTI2NGYyNTc0ODdjMDZjM2ZkZTU3YWVlYjBhYWM5MjkwMWM1MjRiMjIyYmM2NWVkODI3MzI"), + }, + }, + }, + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + PhotoGallery: &[]Photo{ + Photo{ + Image: &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg?context=bWFzdGVyfHJvb3R8MTA3NDM1fGltYWdlL2pwZWd8aGI2L2gzNi84Nzk2NTg2NTQxMDg2LmpwZ3w5MmI1ZTAzZmZhYjc4ODBiNTk2NWMxZGIyZWE5Yjc3N2RhMGE4MjNjZjM2YzU0MjQ0NmE2NWQ5ODZmODcxZGEy"), + }, + }, + Photo{ + Image: &Image{ + Url: String("https://www.napaonline.com/medias/autocare-hero.jpg?context=bWFzdGVyfHJvb3R8OTU0ODN8aW1hZ2UvanBlZ3xoZGMvaDg2Lzg3OTY1ODgxNzk0ODYuanBnfDRkZGIyMTVmZjNiYTI2NGYyNTc0ODdjMDZjM2ZkZTU3YWVlYjBhYWM5MjkwMWM1MjRiMjIyYmM2NWVkODI3MzI"), + }, + }, + }, + }, + }, + isDiff: false, + }, } for _, test := range tests { diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 863e143..0924613 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -53,19 +53,19 @@ type HealthcareProfessionalEntity struct { PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Healthcare - FirstName *string `json:"firstName,omitempty"` - MiddleName *string `json:"middleName,omitempty"` - LastName *string `json:"lastName,omitempty"` - Gender *string `json:"gender,omitempty"` - Headshot *LocationPhoto `json:"headshot,omitempty"` - AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` - AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` - ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` - InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` - NPI *string `json:"npi,omitempty"` - OfficeName *string `json:"officeName,omitempty"` - Degrees *[]string `json:"degrees,omitempty"` - EducationList *[]Education `json:"educationList,omitempty"` + FirstName *string `json:"firstName,omitempty"` + MiddleName *string `json:"middleName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Gender *string `json:"gender,omitempty"` + Headshot **Image `json:"headshot,omitempty"` + AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` + AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` + ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` + InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` + NPI *string `json:"npi,omitempty"` + OfficeName *string `json:"officeName,omitempty"` + Degrees *[]string `json:"degrees,omitempty"` + EducationList *[]Education `json:"educationList,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From 23cf225ab9a2b0e181705c033af4e5efcffb927d Mon Sep 17 00:00:00 2001 From: czou Date: Wed, 6 Mar 2019 13:30:04 -0500 Subject: [PATCH 064/285] Update hours helper struct serialize to return NullRegularHours() --- hours.go | 2 +- hours_test.go | 2 +- type.go | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/hours.go b/hours.go index a6078c4..05e2534 100644 --- a/hours.go +++ b/hours.go @@ -197,7 +197,7 @@ func (h *LocationHoursHelper) StringSerializeDay(weekday Weekday) string { func (h *LocationHoursHelper) StructSerialize() **Hours { if h.HoursAreAllUnspecified() { - return NullHours() + return NullRegularHours() } hours := &Hours{} hours.Sunday = NullableDayHours(h.StructSerializeDay(Sunday)) diff --git a/hours_test.go b/hours_test.go index fa3fc6b..08cc836 100644 --- a/hours_test.go +++ b/hours_test.go @@ -454,7 +454,7 @@ func TestStructSerialize(t *testing.T) { Friday: nil, Saturday: nil, }, - Want: NullHours(), + Want: NullRegularHours(), }, } diff --git a/type.go b/type.go index d9f9a4b..269b29d 100644 --- a/type.go +++ b/type.go @@ -197,6 +197,20 @@ func NullHours() **Hours { return &v } +func NullRegularHours() **Hours { + var v *Hours + v = &Hours{ + Sunday: NullDayHours(), + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + } + return NullableHours(v) +} + // UnorderedStrings masks []string properties for which Order doesn't matter, such as LabelIds type UnorderedStrings []string From 55f27caaed65bf40a479b49e1bfa7f7f1de84758 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 8 Mar 2019 11:08:27 -0500 Subject: [PATCH 065/285] export helper functions (#103) --- entity.go | 6 ++++-- entity_diff.go | 31 +++++++++++++++++++++---------- entity_diff_test.go | 8 ++++---- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/entity.go b/entity.go index 459d390..6cbc272 100644 --- a/entity.go +++ b/entity.go @@ -1,6 +1,8 @@ package yext -import "encoding/json" +import ( + "encoding/json" +) type EntityType string @@ -120,7 +122,7 @@ func (r *RawEntity) GetLanguage() string { func (r *RawEntity) GetAccountId() string { if m, ok := (*r)["meta"]; ok { meta := m.(map[string]interface{}) - if a, ok := meta["account"]; ok { + if a, ok := meta["accountId"]; ok { return a.(string) } } diff --git a/entity_diff.go b/entity_diff.go index a0687ea..50af7f6 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -5,7 +5,7 @@ import ( "reflect" ) -func instanceOf(val interface{}) interface{} { +func InstanceOf(val interface{}) interface{} { var ( isPtr = reflect.ValueOf(val).Kind() == reflect.Ptr tmp interface{} @@ -36,17 +36,17 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int var ( aV, bV = reflect.ValueOf(a), reflect.ValueOf(b) isDiff = false - delta = instanceOf(a) + delta = InstanceOf(a) ) if aV.Kind() == reflect.Ptr { - aV = indirect(aV) + aV = Indirect(aV) } if bV.Kind() == reflect.Ptr { if bV.IsNil() { return delta, isDiff } - bV = indirect(bV) + bV = Indirect(bV) } var ( @@ -82,7 +82,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int if aOk && bOk { if !comparableA.Equal(comparableB) { isDiff = true - indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) + Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } continue } @@ -98,16 +98,16 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int } continue // If it's a pointer to a struct we need to handle it in a special way: - } else if valA.Kind() == reflect.Ptr && indirect(valA).Kind() == reflect.Struct { + } else if valA.Kind() == reflect.Ptr && Indirect(valA).Kind() == reflect.Struct { // Handle case where new is &Address{} and base is &Address{"Line1"} if isZeroValue(valB, nilIsEmptyB) && !isZeroValue(valA, nilIsEmptyA) { isDiff = true - indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) + Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } else { d, diff := diff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true - indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) + Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) } } continue @@ -119,13 +119,13 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int if !reflect.DeepEqual(aI, bI) { isDiff = true - indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) + Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } } return delta, isDiff } -func indirect(v reflect.Value) reflect.Value { +func Indirect(v reflect.Value) reflect.Value { for v.Kind() == reflect.Ptr { v = v.Elem() } @@ -139,6 +139,17 @@ func isNil(v reflect.Value) bool { return false } +func GetUnderlyingType(v reflect.Value) reflect.Kind { + if v.Type().Kind() == reflect.Ptr { + t := v.Type() + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Kind() + } + return v.Kind() +} + // Diff(a, b): a is base, b is new func Diff(a Entity, b Entity) (Entity, bool, error) { if a.GetEntityType() != b.GetEntityType() { diff --git a/entity_diff_test.go b/entity_diff_test.go index bfbc11c..428aafa 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1785,7 +1785,7 @@ func TestEntityDiffComplex(t *testing.T) { func TestInstanceOf(t *testing.T) { var ( b = String("apple") - i = instanceOf(b) + i = InstanceOf(b) ) if _, ok := i.(*string); !ok { t.Error("Expected *string") @@ -1797,7 +1797,7 @@ func TestInstanceOf(t *testing.T) { IsClosed: NullableBool(true), }, } - j = instanceOf(h) + j = InstanceOf(h) ) if _, ok := j.(*[]HolidayHours); !ok { t.Error("Expected *[]HolidayHours") @@ -1809,7 +1809,7 @@ func TestInstanceOf(t *testing.T) { IsClosed: NullableBool(true), }), }) - k = instanceOf(hours) + k = InstanceOf(hours) ) if iHours, ok := k.(**Hours); !ok { t.Error("Expected **Hours") @@ -1823,7 +1823,7 @@ func TestInstanceOf(t *testing.T) { address = &Address{ Line1: String("7900 Westpark"), } - l = instanceOf(address) + l = InstanceOf(address) ) if iAddress, ok := l.(*Address); !ok { t.Error("Expected *Address") From 23f3b9c6980ff1a4a6bb8c9f866ff7d76d2a81d9 Mon Sep 17 00:00:00 2001 From: Alexis Grow <39104038+alexisgrow@users.noreply.github.com> Date: Tue, 12 Mar 2019 19:21:00 -0400 Subject: [PATCH 066/285] add entity ACL function to users_service (#100) --- user_service.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/user_service.go b/user_service.go index 6331de1..0d23d46 100644 --- a/user_service.go +++ b/user_service.go @@ -119,3 +119,12 @@ func (u *UserService) NewLocationACL(l *Location, r Role) ACL { AccessOn: ACCESS_LOCATION, } } + +func (u *UserService) NewEntityACL(e Entity, r Role) ACL { + return ACL{ + Role: r, + On: e.GetEntityId(), + AccountId: u.client.Config.AccountId, + AccessOn: ACCESS_LOCATION, + } +} From dd356f00a7adf592dc12c39122b112917d42144c Mon Sep 17 00:00:00 2001 From: Ben Haines Date: Wed, 13 Mar 2019 12:35:03 -0400 Subject: [PATCH 067/285] Replace LanguageProfile with Entity (#104) --- language_profile.go | 5 ----- language_profile_service.go | 10 +++++----- 2 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 language_profile.go diff --git a/language_profile.go b/language_profile.go deleted file mode 100644 index cc03977..0000000 --- a/language_profile.go +++ /dev/null @@ -1,5 +0,0 @@ -package yext - -type LanguageProfile struct { - Entity -} diff --git a/language_profile_service.go b/language_profile_service.go index 3ca58d8..fac8d5d 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -23,7 +23,7 @@ func (l *LanguageProfileService) RegisterEntity(t EntityType, entity interface{} l.registry.RegisterEntity(t, entity) } -func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageProfile, *Response, error) { +func (l *LanguageProfileService) Get(id string, languageCode string) (*Entity, *Response, error) { var v map[string]interface{} r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), &v) if err != nil { @@ -36,13 +36,13 @@ func (l *LanguageProfileService) Get(id string, languageCode string) (*LanguageP } setNilIsEmpty(entity) - return &LanguageProfile{Entity: entity}, r, nil + return &entity, r, nil } -func (l *LanguageProfileService) List(id string) ([]*LanguageProfile, *Response, error) { +func (l *LanguageProfileService) List(id string) ([]*Entity, *Response, error) { var ( v LanguageProfileListResponse - profiles = []*LanguageProfile{} + profiles = []*Entity{} ) r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityProfilesPath, id), &v) if err != nil { @@ -55,7 +55,7 @@ func (l *LanguageProfileService) List(id string) ([]*LanguageProfile, *Response, } for _, profile := range typedProfiles { setNilIsEmpty(profile) - profiles = append(profiles, &LanguageProfile{Entity: profile}) + profiles = append(profiles, &profile) } return profiles, r, nil } From 526070bf27825e6a34fadc4e23ea1c80d5604ae6 Mon Sep 17 00:00:00 2001 From: Byron Date: Wed, 13 Mar 2019 17:47:24 -0400 Subject: [PATCH 068/285] Add delete operation to entity service (#105) * Add delete operation to entity service * pr fix * update request to use caps * rename function --- entity_service.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/entity_service.go b/entity_service.go index c9293ca..d57fe9f 100644 --- a/entity_service.go +++ b/entity_service.go @@ -267,3 +267,8 @@ func (e *EntityService) EditWithId(y Entity, id string) (*Response, error) { func (e *EntityService) Edit(y Entity) (*Response, error) { return e.EditWithId(y, y.GetEntityId()) } + +// Delete sends a request to the Knowledge Entities API to delete an entity with a given id +func (e *EntityService) Delete(id string) (*Response, error) { + return e.client.DoRequestJSON("DELETE", fmt.Sprintf("%s/%s", entityPath, id), nil, nil) +} From 601c633c9b6d3c3e9c0791955597a7ddb347ea6e Mon Sep 17 00:00:00 2001 From: Cindy Zou Date: Tue, 26 Feb 2019 20:20:01 -0500 Subject: [PATCH 069/285] Add methods to DayHours; null check *Hours in GetDayHours --- entity_location.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/entity_location.go b/entity_location.go index ab81e07..d8f2d98 100644 --- a/entity_location.go +++ b/entity_location.go @@ -341,6 +341,13 @@ func (d *DayHours) SetClosed() { d.OpenIntervals = nil } +func (d *DayHours) GetIntervals() []Interval { + if d != nil && d.OpenIntervals != nil { + return *d.OpenIntervals + } + return nil +} + func (d *DayHours) AddHours(start string, end string) { intervals := []Interval{} d.IsClosed = nil @@ -374,6 +381,9 @@ func NewInterval(start string, end string) *Interval { } func (h *Hours) GetDayHours(w Weekday) *DayHours { + if h == nil { + return nil + } switch w { case Sunday: return GetDayHours(h.Sunday) From abd27975fe207ee14ef9770d622860372d1e715c Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 15 Mar 2019 14:32:49 -0400 Subject: [PATCH 070/285] language profile upsert: add id param --- entity_diff_test.go | 28 ++++++++++++++++++++++++++++ entity_test.go | 1 + language_profile_service.go | 7 +------ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/entity_diff_test.go b/entity_diff_test.go index 428aafa..7eae160 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1763,6 +1763,34 @@ func TestEntityDiffComplex(t *testing.T) { }, isDiff: false, }, + { + name: "MultiLine", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFMultiLine: String(""), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFMultiLine: String(""), + }, + }, + isDiff: false, + }, + { + name: "MultiLine", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFMultiLine: String("Bachelors of Science from Florida International University Doctor of Optometry from Nova Southeastern University"), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFMultiLine: String("Bachelors of Science from Florida International University Doctor of Optometry from Nova Southeastern University"), + }, + }, + isDiff: false, + }, } for _, test := range tests { diff --git a/entity_test.go b/entity_test.go index 1a0b1a9..d8fc5d2 100644 --- a/entity_test.go +++ b/entity_test.go @@ -20,6 +20,7 @@ type CustomEntity struct { CFMultiOption *UnorderedStrings `json:"cf_MultiOption,omitempty"` CFYesNo **bool `json:"cf_YesNo,omitempty"` CFText *string `json:"cf_Text,omitempty"` + CFMultiLine *string `json:"cf_MultiLineText,omitempty"` } type CustomLocationEntity struct { diff --git a/language_profile_service.go b/language_profile_service.go index fac8d5d..b282e75 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -60,12 +60,7 @@ func (l *LanguageProfileService) List(id string) ([]*Entity, *Response, error) { return profiles, r, nil } -func (l *LanguageProfileService) Upsert(entity Entity, languageCode string) (*Response, error) { - id := entity.GetEntityId() - if id == "" { - return nil, fmt.Errorf("entity profile service upsert: profile object had no id") - } - +func (l *LanguageProfileService) Upsert(entity Entity, id string, languageCode string) (*Response, error) { r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), entity, nil) if err != nil { return r, err From 3f00a534550b9c82a90988a2c9fc94a6fa08b8f3 Mon Sep 17 00:00:00 2001 From: Ben Haines Date: Mon, 18 Mar 2019 17:35:33 -0400 Subject: [PATCH 071/285] add support for LanguageProfile:ListAll (#107) * add support for LanguageProfile:ListAll --- language_profile_service.go | 91 +++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/language_profile_service.go b/language_profile_service.go index b282e75..c4bff9f 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -4,7 +4,10 @@ import ( "fmt" ) -const entityProfilesPath = "entityprofiles" +const ( + entityProfilesPath = "entityprofiles" + LanguageProfileListMaxLimit = 50 +) type LanguageProfileService struct { client *Client @@ -15,6 +18,15 @@ type LanguageProfileListResponse struct { Profiles []interface{} `json:"profiles"` } +type LanguageProfileListAllResponse struct { + Count int `json:"count"` + PageToken string `json:"pageToken"` + ProfileLists []struct { + Profiles []interface{} `json:"profiles"` + } `json:"profileLists"` + typedProfiles []Entity +} + func (l *LanguageProfileService) RegisterDefaultEntities() { l.registry = defaultEntityRegistry() } @@ -39,10 +51,10 @@ func (l *LanguageProfileService) Get(id string, languageCode string) (*Entity, * return &entity, r, nil } -func (l *LanguageProfileService) List(id string) ([]*Entity, *Response, error) { +func (l *LanguageProfileService) List(id string) ([]Entity, *Response, error) { var ( v LanguageProfileListResponse - profiles = []*Entity{} + profiles = []Entity{} ) r, err := l.client.DoRequest("GET", fmt.Sprintf("%s/%s", entityProfilesPath, id), &v) if err != nil { @@ -55,11 +67,82 @@ func (l *LanguageProfileService) List(id string) ([]*Entity, *Response, error) { } for _, profile := range typedProfiles { setNilIsEmpty(profile) - profiles = append(profiles, &profile) + profiles = append(profiles, profile) } return profiles, r, nil } +func (l *LanguageProfileService) ListAll(opts *EntityListOptions) ([]Entity, error) { + var entities []Entity + if opts == nil { + opts = &EntityListOptions{} + } + opts.ListOptions = ListOptions{Limit: EntityListMaxLimit} + var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { + opts.ListOptions = *listOptions + resp, _, err := l.listAllHelper(opts) + if err != nil { + return "", err + } + + for _, entity := range resp.typedProfiles { + entities = append(entities, entity) + } + return resp.PageToken, nil + } + + if err := tokenListHelper(lg, &opts.ListOptions); err != nil { + return nil, err + } + return entities, nil +} + +func (l *LanguageProfileService) listAllHelper(opts *EntityListOptions) (*LanguageProfileListAllResponse, *Response, error) { + var ( + requrl = entityProfilesPath + err error + ) + + if opts != nil { + requrl, err = addEntityListOptions(requrl, opts) + if err != nil { + return nil, nil, err + } + } + + if opts != nil { + requrl, err = addListOptions(requrl, &opts.ListOptions) + if err != nil { + return nil, nil, err + } + } + + v := &LanguageProfileListAllResponse{} + r, err := l.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + + entityInterfaces := []interface{}{} + for _, list := range v.ProfileLists { + for _, entity := range list.Profiles { + entityInterfaces = append(entityInterfaces, entity) + } + } + + typedEntities, err := l.registry.ToEntityTypes(entityInterfaces) + if err != nil { + return nil, r, err + } + + for _, entity := range typedEntities { + setNilIsEmpty(entity) + } + + v.typedProfiles = typedEntities + return v, r, nil +} + func (l *LanguageProfileService) Upsert(entity Entity, id string, languageCode string) (*Response, error) { r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), entity, nil) if err != nil { From e9714aedccd155b904ae35dae2fd74e4af642fcf Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Tue, 19 Mar 2019 10:26:58 -0400 Subject: [PATCH 072/285] yext-go changes for MBE ETL Implementation (#108) --- entity_diff.go | 10 +++++----- entity_location.go | 38 ++++++++++++++++++++++++++++++++++++++ entity_service.go | 5 +++++ location_diff.go | 10 +++++----- location_diff_test.go | 2 +- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/entity_diff.go b/entity_diff.go index 50af7f6..b7bbb61 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -65,7 +65,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int continue } - if isNil(valB) { + if IsNil(valB) { continue } @@ -76,7 +76,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int aI, bI := valA.Interface(), valB.Interface() // Comparable does not handle the nil is empty case: // So if valA is nil, don't call comparable (valB checked for nil above) - if !isNil(valA) { + if !IsNil(valA) { comparableA, aOk := aI.(Comparable) comparableB, bOk := bI.(Comparable) if aOk && bOk { @@ -100,7 +100,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int // If it's a pointer to a struct we need to handle it in a special way: } else if valA.Kind() == reflect.Ptr && Indirect(valA).Kind() == reflect.Struct { // Handle case where new is &Address{} and base is &Address{"Line1"} - if isZeroValue(valB, nilIsEmptyB) && !isZeroValue(valA, nilIsEmptyA) { + if IsZeroValue(valB, nilIsEmptyB) && !IsZeroValue(valA, nilIsEmptyA) { isDiff = true Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } else { @@ -113,7 +113,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int continue } - if isZeroValue(valA, nilIsEmptyA) && isZeroValue(valB, nilIsEmptyB) { + if IsZeroValue(valA, nilIsEmptyA) && IsZeroValue(valB, nilIsEmptyB) { continue } @@ -132,7 +132,7 @@ func Indirect(v reflect.Value) reflect.Value { return v } -func isNil(v reflect.Value) bool { +func IsNil(v reflect.Value) bool { if v.Kind() == reflect.Ptr { return v.IsNil() } diff --git a/entity_location.go b/entity_location.go index d8f2d98..b40e923 100644 --- a/entity_location.go +++ b/entity_location.go @@ -64,6 +64,12 @@ type LocationEntity struct { RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + YextDisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` + YextDropoffCoordinate **Coordinate `json:"yextDropoffCoordinate,omitempty"` + YextWalkableCoordinate **Coordinate `json:"yextWalkableCoordinate,omitempty"` + YextRoutableCoordinate **Coordinate `json:"yextRoutableCoordinate,omitempty"` + YextPickupCoordinate **Coordinate `json:"yextPickupCoordinate,omitempty"` + // Lists Bios **Lists `json:"bios,omitempty"` Calendars **Lists `json:"calendars,omitempty"` @@ -739,6 +745,38 @@ func (y LocationEntity) GetRoutableLng() float64 { return 0 } +func (y LocationEntity) GetYextDisplayLat() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y LocationEntity) GetYextDisplayLng() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y LocationEntity) GetYextRoutableLat() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y LocationEntity) GetYextRoutableLng() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + func (y LocationEntity) GetBios() (v *Lists) { return GetLists(y.Bios) } diff --git a/entity_service.go b/entity_service.go index d57fe9f..747d554 100644 --- a/entity_service.go +++ b/entity_service.go @@ -57,6 +57,11 @@ func (e *EntityService) ToEntityType(entity interface{}) (Entity, error) { return e.Registry.ToEntityType(entity) } +func (e *EntityService) WithClient(c *Client) *EntityService { + e.client = c + return e +} + // TODO: Add List for SearchID (similar to location-service). Follow up with Techops to see if SearchID is implemented func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { var entities []Entity diff --git a/location_diff.go b/location_diff.go index dfff1f8..da6ccdb 100644 --- a/location_diff.go +++ b/location_diff.go @@ -57,7 +57,7 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { } } - if isZeroValue(valA, y.nilIsEmpty) && isZeroValue(valB, b.nilIsEmpty) { + if IsZeroValue(valA, y.nilIsEmpty) && IsZeroValue(valB, b.nilIsEmpty) { continue } @@ -88,7 +88,7 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { diff = true d.CustomFields[field] = value } - } else if !(isZeroValue(reflect.ValueOf(value), b.nilIsEmpty) && y.nilIsEmpty) { + } else if !(IsZeroValue(reflect.ValueOf(value), b.nilIsEmpty) && y.nilIsEmpty) { d.CustomFields[field] = value diff = true } @@ -128,7 +128,7 @@ func getUnderlyingValue(v interface{}) interface{} { return rv.Interface() } -func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { +func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { if !v.IsValid() { return true } @@ -148,10 +148,10 @@ func isZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { if v.IsNil() && !interpretNilAsZeroValue { return false } - return isZeroValue(v.Elem(), true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) + return IsZeroValue(v.Elem(), true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) case reflect.Struct: for i, n := 0, v.NumField(); i < n; i++ { - if !isZeroValue(v.Field(i), true) { + if !IsZeroValue(v.Field(i), true) { return false } } diff --git a/location_diff_test.go b/location_diff_test.go index 079cc20..96bf69a 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -1738,7 +1738,7 @@ func TestIsZeroValue(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if isZeroValue := isZeroValue(reflect.ValueOf(test.i), test.nilIsEmpty); test.want != isZeroValue { + if isZeroValue := IsZeroValue(reflect.ValueOf(test.i), test.nilIsEmpty); test.want != isZeroValue { t.Errorf(`Expected IsZeroValue: %t\nGot:%t`, test.want, isZeroValue) } }) From 02b446fbd9f00ec4031731792dd6d4b102ef8ffe Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Tue, 19 Mar 2019 12:54:24 -0400 Subject: [PATCH 073/285] Update generic diff function name (#109) --- cftasset_diff.go | 2 +- entity_diff.go | 8 ++++---- entity_service.go | 2 +- entity_service_test.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cftasset_diff.go b/cftasset_diff.go index e8b9c23..4ad4273 100644 --- a/cftasset_diff.go +++ b/cftasset_diff.go @@ -13,7 +13,7 @@ func (a *CFTAsset) Diff(b *CFTAsset) (*CFTAsset, bool) { return nil, true } - delta, isDiff := diff(a, b, true, true) + delta, isDiff := GenericDiff(a, b, true, true) if !isDiff { return nil, isDiff } diff --git a/entity_diff.go b/entity_diff.go index b7bbb61..8008392 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -32,7 +32,7 @@ func InstanceOf(val interface{}) interface{} { return reflect.New(reflect.TypeOf(val)).Interface() } -func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (interface{}, bool) { +func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (interface{}, bool) { var ( aV, bV = reflect.ValueOf(a), reflect.ValueOf(b) isDiff = false @@ -91,7 +91,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int // First, use recursion to handle a field that is a struct or a pointer to a struct // If Kind() == struct, this is likely an embedded struct if valA.Kind() == reflect.Struct { - d, diff := diff(valA.Addr().Interface(), valB.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) + d, diff := GenericDiff(valA.Addr().Interface(), valB.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d).Elem()) @@ -104,7 +104,7 @@ func diff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (int isDiff = true Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) } else { - d, diff := diff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) + d, diff := GenericDiff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) if diff { isDiff = true Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) @@ -156,7 +156,7 @@ func Diff(a Entity, b Entity) (Entity, bool, error) { return nil, true, fmt.Errorf("Entity Types do not match: '%s', '%s'", a.GetEntityType(), b.GetEntityType()) } - delta, isDiff := diff(a, b, getNilIsEmpty(a), getNilIsEmpty(b)) + delta, isDiff := GenericDiff(a, b, GetNilIsEmpty(a), GetNilIsEmpty(b)) if !isDiff { return nil, isDiff, nil } diff --git a/entity_service.go b/entity_service.go index 747d554..1bb7f3f 100644 --- a/entity_service.go +++ b/entity_service.go @@ -196,7 +196,7 @@ func setNilIsEmpty(i interface{}) { } } -func getNilIsEmpty(i interface{}) bool { +func GetNilIsEmpty(i interface{}) bool { m := reflect.ValueOf(i).MethodByName("GetNilIsEmpty") if m.IsValid() { values := m.Call([]reflect.Value{}) diff --git a/entity_service_test.go b/entity_service_test.go index 1885cd1..76578ef 100644 --- a/entity_service_test.go +++ b/entity_service_test.go @@ -37,12 +37,12 @@ func TestSetNilIsEmpty(t *testing.T) { } for _, test := range tests { - before := getNilIsEmpty(test.i) + before := GetNilIsEmpty(test.i) if before != test.before { t.Errorf("Before set nil is empty: Expected %t, got %t", test.before, before) } setNilIsEmpty(test.i) - after := getNilIsEmpty(test.i) + after := GetNilIsEmpty(test.i) if after != test.after { t.Errorf("After set nil is empty: Expected %t, got %t", test.after, after) } From fd8d030374e0c4d86bee23efe01f6524c7285de1 Mon Sep 17 00:00:00 2001 From: Cindy Zou Date: Tue, 19 Mar 2019 17:04:09 -0400 Subject: [PATCH 074/285] Add methods to get the names of set multi-option and single-option values --- customfield_service.go | 95 ++++++++++++++++++++++++++++++------- customfield_service_test.go | 53 +++++++++++++++++++++ 2 files changed, 131 insertions(+), 17 deletions(-) diff --git a/customfield_service.go b/customfield_service.go index 8edd9fa..b3770cd 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -182,23 +182,23 @@ func (c *CustomFieldManager) NullSingleOption() **string { } func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames ...string) *UnorderedStrings { - var optionIds = []string{} - for _, optionName := range optionNames { - id := c.MustCustomFieldOptionId(fieldName, optionName) - - shouldAddOptionId := true - for _, optionId := range optionIds { - if id == optionId { // Don't add duplicate option ids - shouldAddOptionId = false - break - } - } - - if shouldAddOptionId { - optionIds = append(optionIds, id) - } - } - return ToUnorderedStrings(optionIds) + var optionIds = []string{} + for _, optionName := range optionNames { + id := c.MustCustomFieldOptionId(fieldName, optionName) + + shouldAddOptionId := true + for _, optionId := range optionIds { + if id == optionId { // Don't add duplicate option ids + shouldAddOptionId = false + break + } + } + + if shouldAddOptionId { + optionIds = append(optionIds, id) + } + } + return ToUnorderedStrings(optionIds) } func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *UnorderedStrings) bool { @@ -218,6 +218,67 @@ func (c *CustomFieldManager) NullMultiOption() *UnorderedStrings { return ToUnorderedStrings([]string{}) } +func (c *CustomFieldManager) GetMultiOptionNames(fieldName string, optionIds *UnorderedStrings) ([]string, error) { + optionNames := []string{} + for _, optionId := range *optionIds { + optionName, err := c.CustomFieldOptionName(fieldName, optionId) + if err != nil { + return nil, err + } + optionNames = append(optionNames, optionName) + } + return optionNames, nil +} + +// MustGetMultiOptionNames returns a slice containing the names of the options that are set +// Example Use: +// Custom field name: "Available Breeds" +// Custom field option ID to name map: { +// "2421": "Labrador Retriever", +// "2422": "Chihuahua", +// "2423": "Boston Terrier" +// } +// Custom field options that are set: "2421" and "2422" +// cfmanager.MustGetMultiOptionNames("Available Breeds", loc.AvailableBreeds) returns ["Labrador Retriever", "Chihuahua"] +func (c *CustomFieldManager) MustGetMultiOptionNames(fieldName string, options *UnorderedStrings) []string { + if ids, err := c.GetMultiOptionNames(fieldName, options); err != nil { + panic(err) + } else { + return ids + } +} + +func (c *CustomFieldManager) GetSingleOptionName(fieldName string, optionId **string) (string, error) { + if GetNullableString(optionId) == "" { + return "", nil + } + + optionName, err := c.CustomFieldOptionName(fieldName, GetNullableString(optionId)) + if err != nil { + return "", err + } + return optionName, nil +} + +// MustGetSingleOptionName returns the name of a set option. It returns an empty string if the single +// option is unset (i.e. if its value is a NullString) +// Example Use: +// Custom field name: "Dog Height" +// Custom field option ID to name map: { +// "1423": "Tall", +// "1424": "Short", +// } +// Custom field option that is set: "1424" +// cfmanager.MustGetSingleOptionName("Dog Height", loc.DogHeight) returns "Short" +// +func (c *CustomFieldManager) MustGetSingleOptionName(fieldName string, optionId **string) string { + if id, err := c.GetSingleOptionName(fieldName, optionId); err != nil { + panic(err) + } else { + return id + } +} + func (c *CustomFieldManager) CustomFieldOptionName(cfName string, optionId string) (string, error) { cf, err := c.CustomField(cfName) if err != nil { diff --git a/customfield_service_test.go b/customfield_service_test.go index ee2e229..b083249 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -3,6 +3,7 @@ package yext import ( "encoding/json" "net/http" + "reflect" "strconv" "testing" ) @@ -142,6 +143,10 @@ var cfm = &CustomFieldManager{ Key: "c_red", Value: "Red", }, + CustomFieldOption{ + Key: "c_green", + Value: "Green", + }, }, }, &CustomField{ @@ -157,6 +162,10 @@ var cfm = &CustomFieldManager{ Key: "c_olives", Value: "Olives", }, + CustomFieldOption{ + Key: "c_chickenFingers", + Value: "Chicken Fingers", + }, }, }, }, @@ -188,3 +197,47 @@ func TestMustIsSingleOptionSet(t *testing.T) { t.Error("TestMustIsSingleOptionSet: cheese is not set but got true") } } + +func TestMustGetMultiOptionNames(t *testing.T) { + tests := []struct { + got []string + want []string + }{ + { + got: cfm.MustGetMultiOptionNames("My Favorite Food", ToUnorderedStrings([]string{"c_cheese", "c_chickenFingers"})), + want: []string{"Cheese", "Chicken Fingers"}, + }, + { + got: cfm.MustGetMultiOptionNames("My Favorite Colors", ToUnorderedStrings([]string{})), + want: []string{}, + }, + } + + for _, test := range tests { + if !reflect.DeepEqual(test.got, test.want) { + t.Errorf("TestMustGetMultiOptionNames: wanted %v, got %v", test.want, test.got) + } + } +} + +func TestMustGetSingleOptionName(t *testing.T) { + tests := []struct { + got string + want string + }{ + { + got: cfm.MustGetSingleOptionName("My Favorite Food", NullableString("c_chickenFingers")), + want: "Chicken Fingers", + }, + { + got: cfm.MustGetSingleOptionName("My Favorite Colors", NullString()), + want: "", + }, + } + + for _, test := range tests { + if test.got != test.want { + t.Errorf("TestMustGetSingleOptionName: wanted %s, got %s", test.want, test.got) + } + } +} From 562345534a397e14602cf9e9a7024349d72c741d Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 21 Mar 2019 13:50:15 -0400 Subject: [PATCH 075/285] Set field on generic entity (#111) --- customfield_service.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/customfield_service.go b/customfield_service.go index b3770cd..3e5bf2c 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -3,6 +3,8 @@ package yext import ( "encoding/json" "fmt" + "reflect" + "strings" ) const customFieldPath = "customfields" @@ -317,3 +319,32 @@ func (c *CustomFieldService) MustCacheCustomFields() []*CustomField { } return slice } + +func (c *CustomFieldManager) SetCustomField(i interface{}, fieldName string, valToSet reflect.Value) interface{} { + cfId := c.MustCustomFieldId(fieldName) + return SetFieldByJSONTag(i, cfId, valToSet) +} + +func SetFieldByJSONTag(i interface{}, fieldTag string, valToSet reflect.Value) interface{} { + var ( + v = Indirect(reflect.ValueOf(i)) + t = v.Type() + num = v.NumField() + ) + + for n := 0; n < num; n++ { + var ( + field = t.Field(n) + name = field.Name + tag = strings.Replace(field.Tag.Get("json"), ",omitempty", "", -1) + val = v.Field(n) + ) + if tag == fieldTag { + Indirect(reflect.ValueOf(i)).FieldByName(name).Set(valToSet) + return Indirect(reflect.ValueOf(i)).Interface() + } else { + Indirect(reflect.ValueOf(i)).FieldByName(name).Set(val) + } + } + return Indirect(reflect.ValueOf(i)).Interface() +} From 68b5b34dead2944b47014c2fbeac372f820cb1d9 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 27 Mar 2019 10:28:26 -0400 Subject: [PATCH 076/285] entity_unmarshal: handle null day hours (and other null values in objects) --- entity_test.go | 2 ++ entity_unmarshal.go | 44 +++++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/entity_test.go b/entity_test.go index d8fc5d2..ddb8ac9 100644 --- a/entity_test.go +++ b/entity_test.go @@ -80,6 +80,7 @@ func TestEntityJSONSerialization(t *testing.T) { {&CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{"English"}}}, `{"languages":["English"]}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: nil}}, `{}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullHours()}}, `{"hours":null}`}, + {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullableHours(&Hours{Monday: NullDayHours(), Tuesday: NullDayHours(), Wednesday: NullDayHours(), Thursday: NullDayHours(), Friday: NullDayHours(), Saturday: NullDayHours(), Sunday: NullDayHours()})}}, `{"hours":{"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":null,"saturday":null,"sunday":null}}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("")}}, `{"cf_Url":""}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: nil}}, `{}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: &[]string{}}}, `{"cf_TextList":[]}`}, @@ -124,6 +125,7 @@ func TestEntityJSONDeserialization(t *testing.T) { {`{"languages":[]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{}}}}, {`{"languages":["English"]}`, &CustomLocationEntity{LocationEntity: LocationEntity{Languages: &[]string{"English"}}}}, {`{"hours":null}`, &CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullHours()}}}, + {`{"hours":{"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":null,"saturday":null,"sunday":null}}`, &CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullableHours(&Hours{Monday: NullDayHours(), Tuesday: NullDayHours(), Wednesday: NullDayHours(), Thursday: NullDayHours(), Friday: NullDayHours(), Saturday: NullDayHours(), Sunday: NullDayHours()})}}}, {`{"cf_Url":""}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("")}}}, {`{"cf_Url": "www.yext.com"}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("www.yext.com")}}}, {`{"cf_TextList":[]}`, &CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: &[]string{}}}}, diff --git a/entity_unmarshal.go b/entity_unmarshal.go index 3efc732..c5b7ef8 100644 --- a/entity_unmarshal.go +++ b/entity_unmarshal.go @@ -6,35 +6,45 @@ import ( "strings" ) -func UnmarshalEntityJSON(i interface{}, data []byte) error { +func unmarshal(i interface{}, m map[string]interface{}) interface{} { var jsonTagToKey = map[string]string{} - val := reflect.ValueOf(i).Elem() + val := Indirect(reflect.ValueOf(i)) for i := 0; i < val.Type().NumField(); i++ { field := val.Type().Field(i) tag := strings.Replace(field.Tag.Get("json"), ",omitempty", "", -1) jsonTagToKey[tag] = field.Name } - var m map[string]interface{} - err := json.Unmarshal(data, &m) - if err != nil { - return err - } - for tag, val := range m { - if _, ok := jsonTagToKey[tag]; ok && val == nil { - v := reflect.ValueOf(i).Elem().FieldByName(jsonTagToKey[tag]) + if _, ok := jsonTagToKey[tag]; ok { + if val == nil { + v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) - // Check if double pointer - if v.Type().Kind() == reflect.Ptr { - t := v.Type() - for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { - t = t.Elem() + // Check if double pointer + if v.Type().Kind() == reflect.Ptr { + t := v.Type() + for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + t = t.Elem() + } + typedNil := reflect.New(t) + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) } - typedNil := reflect.New(t) - reflect.ValueOf(i).Elem().FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) + } else if vMap, ok := val.(map[string]interface{}); ok { + v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) + r := unmarshal(v.Interface(), vMap) + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(r)) } } } + return i +} + +func UnmarshalEntityJSON(i interface{}, data []byte) error { + var m map[string]interface{} + err := json.Unmarshal(data, &m) + if err != nil { + return err + } + i = unmarshal(i, m) return nil } From 13881f0e0709202cbe10d30c031e100c496b8bec Mon Sep 17 00:00:00 2001 From: czou Date: Thu, 28 Mar 2019 09:17:35 -0400 Subject: [PATCH 077/285] entity_service: Add filters functionality --- entity_service.go | 4 ++++ entity_service_test.go | 22 ++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/entity_service.go b/entity_service.go index 1bb7f3f..31f79c9 100644 --- a/entity_service.go +++ b/entity_service.go @@ -22,6 +22,7 @@ type EntityListOptions struct { SearchIDs []string ResolvePlaceholders bool EntityTypes []string + Filter string } // Used for Create and Edit @@ -167,6 +168,9 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if len(opts.EntityTypes) > 0 { q.Add("entityTypes", strings.Join(opts.EntityTypes, ",")) } + if len(opts.Filter) > 0 { + q.Add("filter", opts.Filter) + } u.RawQuery = q.Encode() return u.String(), nil diff --git a/entity_service_test.go b/entity_service_test.go index 76578ef..1f577f0 100644 --- a/entity_service_test.go +++ b/entity_service_test.go @@ -57,12 +57,14 @@ func TestEntityListOptions(t *testing.T) { searchIDs string entityTypes string resolvePlaceholders bool + filter string }{ { opts: nil, limit: "", token: "", searchIDs: "", + filter: "", }, { // The values are technically 0,0, but that doesn't make any sense in the context of a list request @@ -70,12 +72,14 @@ func TestEntityListOptions(t *testing.T) { limit: "", token: "", searchIDs: "", + filter: "", }, { opts: &EntityListOptions{ListOptions: ListOptions{Limit: 10}}, limit: "10", token: "", searchIDs: "", + filter: "", }, { opts: &EntityListOptions{EntityTypes: []string{"location"}}, @@ -83,6 +87,7 @@ func TestEntityListOptions(t *testing.T) { token: "", searchIDs: "", entityTypes: "location", + filter: "", }, { opts: &EntityListOptions{EntityTypes: []string{"location,event"}}, @@ -90,36 +95,42 @@ func TestEntityListOptions(t *testing.T) { token: "", searchIDs: "", entityTypes: "location,event", + filter: "", }, { opts: &EntityListOptions{ListOptions: ListOptions{PageToken: "qwerty1234"}}, limit: "", token: "qwerty1234", searchIDs: "", + filter: "", }, { opts: &EntityListOptions{ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, limit: "42", token: "asdfgh4321", searchIDs: "", + filter: "", }, { opts: &EntityListOptions{SearchIDs: []string{"1234"}}, limit: "", token: "", searchIDs: "1234", + filter: "", }, { opts: &EntityListOptions{SearchIDs: []string{"1234", "5678"}, ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, limit: "42", token: "asdfgh4321", searchIDs: "1234,5678", + filter: "", }, { - opts: &EntityListOptions{ResolvePlaceholders: true, ListOptions: ListOptions{Limit: 42, PageToken: "asdfgh4321"}}, - limit: "42", - token: "asdfgh4321", - resolvePlaceholders: true, + opts: &EntityListOptions{Filter: `{"c_emergencyWatch":{"$eq":true}}`}, + limit: "", + token: "", + searchIDs: "", + filter: `{"c_emergencyWatch":{"$eq":true}}`, }, } @@ -142,6 +153,9 @@ func TestEntityListOptions(t *testing.T) { if v == "true" && !test.resolvePlaceholders || v == "" && test.resolvePlaceholders || v == "false" && test.resolvePlaceholders { t.Errorf("Wanted resolvePlaceholders %t, got %s", test.resolvePlaceholders, v) } + if v := r.URL.Query().Get("filter"); v != test.filter { + t.Errorf("Wanted filter %s, got %s", test.filter, v) + } }) client.EntityService.List(test.opts) From 1d8549c22bb82b53453e5703a29e07b85a288453 Mon Sep 17 00:00:00 2001 From: czou Date: Wed, 27 Mar 2019 23:51:56 -0400 Subject: [PATCH 078/285] entity_unmarshal: Check if type is struct before calling NumField() --- entity_test.go | 4 ++++ entity_unmarshal.go | 44 +++++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/entity_test.go b/entity_test.go index ddb8ac9..a08e726 100644 --- a/entity_test.go +++ b/entity_test.go @@ -413,6 +413,10 @@ var sampleEntityJSON = `{ "latitude": 38.9243983751914, "longitude": -77.2178385786886 }, + "googleAttributes": { + "wi_fi": ["free_wi_fi"], + "welcomes_dogs": ["true"] + }, "meta": { "accountId": "3549951188342570541", "uid": "b3JxON", diff --git a/entity_unmarshal.go b/entity_unmarshal.go index c5b7ef8..05dad9c 100644 --- a/entity_unmarshal.go +++ b/entity_unmarshal.go @@ -7,32 +7,34 @@ import ( ) func unmarshal(i interface{}, m map[string]interface{}) interface{} { - var jsonTagToKey = map[string]string{} val := Indirect(reflect.ValueOf(i)) - for i := 0; i < val.Type().NumField(); i++ { - field := val.Type().Field(i) - tag := strings.Replace(field.Tag.Get("json"), ",omitempty", "", -1) - jsonTagToKey[tag] = field.Name - } + if val.Kind() == reflect.Struct { + var jsonTagToKey = map[string]string{} + for i := 0; i < val.Type().NumField(); i++ { + field := val.Type().Field(i) + tag := strings.Replace(field.Tag.Get("json"), ",omitempty", "", -1) + jsonTagToKey[tag] = field.Name + } - for tag, val := range m { - if _, ok := jsonTagToKey[tag]; ok { - if val == nil { - v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) + for tag, val := range m { + if _, ok := jsonTagToKey[tag]; ok { + if val == nil { + v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) - // Check if double pointer - if v.Type().Kind() == reflect.Ptr { - t := v.Type() - for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { - t = t.Elem() + // Check if double pointer + if v.Type().Kind() == reflect.Ptr { + t := v.Type() + for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + t = t.Elem() + } + typedNil := reflect.New(t) + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) } - typedNil := reflect.New(t) - Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) + } else if vMap, ok := val.(map[string]interface{}); ok { + v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) + r := unmarshal(v.Interface(), vMap) + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(r)) } - } else if vMap, ok := val.(map[string]interface{}); ok { - v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) - r := unmarshal(v.Interface(), vMap) - Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(r)) } } } From 4dd13fa0eddcb4cee3f6f2e696f14c824fb43495 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 28 Mar 2019 16:32:47 -0400 Subject: [PATCH 079/285] entity: update json tag of specialties to specialities to match Yext API bug (#115) --- entity_healthcare_professional.go | 9 +++++---- entity_location.go | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 0924613..8fbd740 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -47,10 +47,11 @@ type HealthcareProfessionalEntity struct { Certifications *[]string `json:"certifications,omitempty"` Brands *[]string `json:"brands,omitempty"` Services *[]string `json:"services,omitempty"` - Specialties *[]string `json:"specialties,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Image `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` + // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API + Specialties *[]string `json:"specialities,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Image `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Healthcare FirstName *string `json:"firstName,omitempty"` diff --git a/entity_location.go b/entity_location.go index b40e923..d1de663 100644 --- a/entity_location.go +++ b/entity_location.go @@ -52,10 +52,11 @@ type LocationEntity struct { Brands *[]string `json:"brands,omitempty"` Products *[]string `json:"products,omitempty"` Services *[]string `json:"services,omitempty"` - Specialties *[]string `json:"specialties,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Image `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` + // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API + Specialties *[]string `json:"specialities,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Image `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From 6094a59335b300178d3f2ce1e46412d31056a9e3 Mon Sep 17 00:00:00 2001 From: Taylor Birdsall Date: Tue, 2 Apr 2019 11:54:26 -0400 Subject: [PATCH 080/285] Lat long (#116) * location: added yextDisplayLat and yextDisplayLng * location: update spacing --- location.go | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/location.go b/location.go index 49468d4..dffb4a1 100644 --- a/location.go +++ b/location.go @@ -91,16 +91,20 @@ type Location struct { PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs - DisplayLat *float64 `json:"displayLat,omitempty"` - DisplayLng *float64 `json:"displayLng,omitempty"` - DropoffLat *float64 `json:"dropoffLat,omitempty"` - DropoffLng *float64 `json:"dropoffLng,omitempty"` - WalkableLat *float64 `json:"walkableLat,omitempty"` - WalkableLng *float64 `json:"walkableLng,omitempty"` - RoutableLat *float64 `json:"routableLat,omitempty"` - RoutableLng *float64 `json:"routableLng,omitempty"` - PickupLat *float64 `json:"pickupLat,omitempty"` - PickupLng *float64 `json:"pickupLng,omitempty"` + DisplayLat *float64 `json:"displayLat,omitempty"` + DisplayLng *float64 `json:"displayLng,omitempty"` + DropoffLat *float64 `json:"dropoffLat,omitempty"` + DropoffLng *float64 `json:"dropoffLng,omitempty"` + WalkableLat *float64 `json:"walkableLat,omitempty"` + WalkableLng *float64 `json:"walkableLng,omitempty"` + RoutableLat *float64 `json:"routableLat,omitempty"` + RoutableLng *float64 `json:"routableLng,omitempty"` + PickupLat *float64 `json:"pickupLat,omitempty"` + PickupLng *float64 `json:"pickupLng,omitempty"` + + // Yext Lat & Lngs - NOTE: DO NOT SET THESE, they are auto-generated by Yext + YextDisplayLat *float64 `json:"yextDisplayLat,omitempty"` + YextDisplayLng *float64 `json:"yextDisplayLng,omitempty"` // ECLS BioListIds *[]string `json:"bioListIds,omitempty"` @@ -486,6 +490,20 @@ func (y Location) GetDisplayLng() float64 { return 0 } +func (y Location) GetYextDisplayLat() float64 { + if y.YextDisplayLat != nil { + return *y.YextDisplayLat + } + return 0 +} + +func (y Location) GetYextDisplayLng() float64 { + if y.YextDisplayLng != nil { + return *y.YextDisplayLng + } + return 0 +} + func (y Location) GetRoutableLat() float64 { if y.RoutableLat != nil { return *y.RoutableLat From 15195c94d8f3d459c74b6e1cf2f5d90e1c0986d9 Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Thu, 11 Apr 2019 10:47:03 -0400 Subject: [PATCH 081/285] Degrees Fix (#117) --- entity_healthcare_professional.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 8fbd740..a522c92 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -54,19 +54,19 @@ type HealthcareProfessionalEntity struct { PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Healthcare - FirstName *string `json:"firstName,omitempty"` - MiddleName *string `json:"middleName,omitempty"` - LastName *string `json:"lastName,omitempty"` - Gender *string `json:"gender,omitempty"` - Headshot **Image `json:"headshot,omitempty"` - AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` - AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` - ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` - InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` - NPI *string `json:"npi,omitempty"` - OfficeName *string `json:"officeName,omitempty"` - Degrees *[]string `json:"degrees,omitempty"` - EducationList *[]Education `json:"educationList,omitempty"` + FirstName *string `json:"firstName,omitempty"` + MiddleName *string `json:"middleName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Gender *string `json:"gender,omitempty"` + Headshot **Image `json:"headshot,omitempty"` + AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` + AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` + ConditionsTreated *[]string `json:"conditionsTreated,omitempty"` + InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` + NPI *string `json:"npi,omitempty"` + OfficeName *string `json:"officeName,omitempty"` + Degrees *UnorderedStrings `json:"degrees,omitempty"` + EducationList *[]Education `json:"educationList,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From d9b8b688ae913ac2df95c2a279d1132cab3fe486 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 11 Apr 2019 14:54:06 -0400 Subject: [PATCH 082/285] customfield_service: handle null option value (#118) --- customfield_service.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/customfield_service.go b/customfield_service.go index 3e5bf2c..88760b5 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -170,6 +170,9 @@ func (c *CustomFieldManager) MustCustomFieldOptionId(fieldName, optionName strin } func (c *CustomFieldManager) MustSingleOptionId(fieldName, optionName string) **string { + if optionName == "" { + return c.NullSingleOption() + } id := c.MustCustomFieldOptionId(fieldName, optionName) return NullableString(id) } @@ -184,6 +187,9 @@ func (c *CustomFieldManager) NullSingleOption() **string { } func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames ...string) *UnorderedStrings { + if len(optionNames) == 0 { + return c.NullMultiOption() + } var optionIds = []string{} for _, optionName := range optionNames { id := c.MustCustomFieldOptionId(fieldName, optionName) From 2956ec3a680909b87788ea68bfffa49f277fc032 Mon Sep 17 00:00:00 2001 From: Cindy Zou Date: Fri, 12 Apr 2019 11:19:45 -0400 Subject: [PATCH 083/285] Change type of logos from **Image to **Photo --- entity_healthcare_professional.go | 2 +- entity_location.go | 2 +- entity_restaurant.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index a522c92..a52a09c 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -50,7 +50,7 @@ type HealthcareProfessionalEntity struct { // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API Specialties *[]string `json:"specialities,omitempty"` Languages *[]string `json:"languages,omitempty"` - Logo **Image `json:"logo,omitempty"` + Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Healthcare diff --git a/entity_location.go b/entity_location.go index d1de663..5b04251 100644 --- a/entity_location.go +++ b/entity_location.go @@ -55,7 +55,7 @@ type LocationEntity struct { // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API Specialties *[]string `json:"specialities,omitempty"` Languages *[]string `json:"languages,omitempty"` - Logo **Image `json:"logo,omitempty"` + Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs diff --git a/entity_restaurant.go b/entity_restaurant.go index e14c267..eeb5c90 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -37,7 +37,7 @@ type RestaurantEntity struct { YearEstablished **float64 `json:"yearEstablished,omitempty"` Services *[]string `json:"services,omitempty"` Languages *[]string `json:"languages,omitempty"` - Logo **Image `json:"logo,omitempty"` + Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs From 133538f7d0cf6735ab0ed9c216047051a8767422 Mon Sep 17 00:00:00 2001 From: Ben Haines Date: Mon, 15 Apr 2019 12:10:18 -0400 Subject: [PATCH 084/285] Add support for fields param (#119) --- entity_service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/entity_service.go b/entity_service.go index 31f79c9..46bdf06 100644 --- a/entity_service.go +++ b/entity_service.go @@ -22,6 +22,7 @@ type EntityListOptions struct { SearchIDs []string ResolvePlaceholders bool EntityTypes []string + Fields []string Filter string } @@ -162,6 +163,9 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if len(opts.SearchIDs) > 0 { q.Add("searchIds", strings.Join(opts.SearchIDs, ",")) } + if len(opts.Fields) > 0 { + q.Add("fields", strings.Join(opts.Fields, ",")) + } if opts.ResolvePlaceholders { q.Add("resolvePlaceholders", "true") } From 77ed347ddd5cf5a1a6cbba23a82acffee599207a Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 18 Apr 2019 17:01:24 -0400 Subject: [PATCH 085/285] entity_diff: handle double pointer to nil (aka null) --- entity_diff.go | 29 +++-- entity_diff_test.go | 304 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 320 insertions(+), 13 deletions(-) diff --git a/entity_diff.go b/entity_diff.go index 8008392..07ffe38 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -69,6 +69,13 @@ func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB boo continue } + // if valB **(nil) aka null and valA is not + if IsUnderlyingNil(valB) && !IsUnderlyingNil(valA) { + isDiff = true + Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) + continue + } + if !valB.CanSet() { continue } @@ -99,16 +106,10 @@ func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB boo continue // If it's a pointer to a struct we need to handle it in a special way: } else if valA.Kind() == reflect.Ptr && Indirect(valA).Kind() == reflect.Struct { - // Handle case where new is &Address{} and base is &Address{"Line1"} - if IsZeroValue(valB, nilIsEmptyB) && !IsZeroValue(valA, nilIsEmptyA) { + d, diff := GenericDiff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) + if diff { isDiff = true - Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(valB) - } else { - d, diff := GenericDiff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) - if diff { - isDiff = true - Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) - } + Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) } continue } @@ -139,6 +140,16 @@ func IsNil(v reflect.Value) bool { return false } +func IsUnderlyingNil(v reflect.Value) bool { + if v.Kind() == reflect.Ptr { + if v.Elem().Kind() == reflect.Ptr { + return IsUnderlyingNil(v.Elem()) + } + return v.IsNil() + } + return false +} + func GetUnderlyingType(v reflect.Value) reflect.Kind { if v.Type().Kind() == reflect.Ptr { t := v.Type() diff --git a/entity_diff_test.go b/entity_diff_test.go index 7eae160..b74e4bd 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -462,9 +462,8 @@ func TestEntityDiff(t *testing.T) { baseValue: &Address{ Line1: String("7900 Westpark Dr"), }, - newValue: &Address{}, - isDiff: true, - deltaValue: &Address{}, + newValue: &Address{}, // This will make no change when sent to API, no not a diff + isDiff: false, }, diffTest{ name: "Address: both are empty struct (E)", @@ -953,6 +952,64 @@ func TestEntityDiffComplex(t *testing.T) { }, }, }, + { + name: "Address, partial diff", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line1: String(""), + }, + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + Line1: String(""), + City: String("McLean"), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Address: &Address{ + City: String("McLean"), + }, + }, + }, + }, + { + name: "Address, partial diff", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + EntityType: ENTITYTYPE_LOCATION, + }, + }, + Address: &Address{ + Line1: String(""), + Line2: String(""), + City: String(""), + Region: String("VA"), + PostalCode: String("20182"), + }, + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + EntityType: ENTITYTYPE_LOCATION, + }, + }, + Address: &Address{ + Line1: String(""), + }, + }, + }, + isDiff: false, + }, { name: "CFGallery", base: &CustomLocationEntity{ @@ -1575,6 +1632,109 @@ func TestEntityDiffComplex(t *testing.T) { }, }, }, + { + name: "Hours Change unspecified -> null hours", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Thursday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Sunday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullHours(), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullHours(), + }, + }, + }, + { + name: "Hours Change unspecified -> null hours", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + Sunday: NullDayHours(), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullHours(), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullHours(), + }, + }, + }, + { + name: "Hours Change unspecified -> null hours", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + Sunday: NullDayHours(), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullDayHours(), + Tuesday: NullDayHours(), + Wednesday: NullDayHours(), + Thursday: NullDayHours(), + Friday: NullDayHours(), + Saturday: NullDayHours(), + Sunday: NullDayHours(), + }), + }, + }, + isDiff: false, + }, { name: "Hours No Change (showing nil is not the same as **DayHours(nil) )", base: &CustomLocationEntity{ @@ -1636,7 +1796,143 @@ func TestEntityDiffComplex(t *testing.T) { }, isDiff: false, }, - + { + name: "Hours Closed to Open", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Friday: NullableDayHours(&DayHours{ + IsClosed: NullableBool(true), + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + }), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + Hours: NullableHours(&Hours{ + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{ + Start: "08:00", + End: "19:00", + }, + }, + }), + }), + }, + }, + }, { name: "Name", base: &CustomLocationEntity{ From 5b1c20aae3ada73f2035f4a4043d0d0229800b9f Mon Sep 17 00:00:00 2001 From: creotutar Date: Thu, 25 Apr 2019 18:26:31 -0400 Subject: [PATCH 086/285] Add Get NPI for Healthcare Professional Entity --- entity_healthcare_professional.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index a52a09c..e8064eb 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -509,6 +509,13 @@ func (y HealthcareProfessionalEntity) GetHolidayHours() []HolidayHours { return nil } +func (y HealthcareProfessionalEntity) GetNPI() string { + if y.NPI != nil { + return *y.NPI + } + return "" +} + func (y HealthcareProfessionalEntity) IsClosed() bool { return GetNullableBool(y.Closed) } From 7e96640595ee2719e397639472428b5845989be3 Mon Sep 17 00:00:00 2001 From: Alexis Grow <39104038+alexisgrow@users.noreply.github.com> Date: Mon, 29 Apr 2019 10:55:24 -0400 Subject: [PATCH 087/285] unordered strings equal: Update null checking for interface (#122) --- type.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/type.go b/type.go index 269b29d..bd51cfb 100644 --- a/type.go +++ b/type.go @@ -223,9 +223,9 @@ func (a *UnorderedStrings) Equal(b Comparable) bool { } }() - if a == nil && b == nil { + if a == nil && (b.(*UnorderedStrings) == nil) { return true - } else if a == nil || b == nil { + } else if a == nil || (b.(*UnorderedStrings) == nil) { return false } From 569a37b4aeaa9d79f4e272701e67a826612259bc Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 29 Apr 2019 14:35:00 -0400 Subject: [PATCH 088/285] Hours: allow for Closed or closed (#124) --- hours.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hours.go b/hours.go index 05e2534..ea07d88 100644 --- a/hours.go +++ b/hours.go @@ -284,7 +284,7 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { if err != nil { return -1, "", fmt.Errorf("Error parsing weekday hours from string; unable to convert index to num: %s", err) } - if hoursParts[1] == HoursClosed { + if strings.ToLower(hoursParts[1]) == HoursClosed { return Weekday(weekdayInt), HoursClosed, nil } // Pad hours with leading 0s From 547bf13f494cf7f538177575b92d87ccba3c4372 Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Tue, 30 Apr 2019 10:40:42 -0400 Subject: [PATCH 089/285] Add geomodifier to location entity (#127) --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 5b04251..a21e136 100644 --- a/entity_location.go +++ b/entity_location.go @@ -31,6 +31,7 @@ type LocationEntity struct { Address *Address `json:"address,omitempty"` AddressHidden **bool `json:"addressHidden,omitempty"` ISORegionCode *string `json:"isoRegionCode,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` From c72be6ba0233071a66d064175ee14d8df22b7eba Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 13 May 2019 16:17:19 -0400 Subject: [PATCH 090/285] SetLabelsWithUnorderedStrings: handle nil J=PC-43989 --- entity.go | 4 ++- entity_test.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/entity.go b/entity.go index 6cbc272..f9250de 100644 --- a/entity.go +++ b/entity.go @@ -76,7 +76,9 @@ func (b *BaseEntity) SetLabels(v []string) { } func (b *BaseEntity) SetLabelsWithUnorderedStrings(v UnorderedStrings) { - b.Meta.Labels = &v + if v != nil { + b.Meta.Labels = &v + } } func (b *BaseEntity) GetNilIsEmpty() bool { diff --git a/entity_test.go b/entity_test.go index a08e726..1c131f9 100644 --- a/entity_test.go +++ b/entity_test.go @@ -154,6 +154,91 @@ func TestEntityJSONDeserialization(t *testing.T) { } } +func TestSetLabels(t *testing.T) { + var tests = []struct { + Destination *LocationEntity + Source *LocationEntity + Want *LocationEntity + }{ + { + Destination: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: nil, + }, + }, + }, + Source: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: nil, + }, + }, + }, + Want: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: nil, + }, + }, + }, + }, + } + for _, test := range tests { + test.Destination.SetLabelsWithUnorderedStrings(test.Source.GetLabels()) + if _, isDiff, _ := Diff(test.Want, test.Destination); isDiff { + t.Errorf("SetLabelsWithUnorderedStrings: Wanted %v Got %v", test.Want, test.Destination) + } + } + + var labelTests = []struct { + Entity *LocationEntity + Labels []string + Want *LocationEntity + }{ + { + Entity: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: nil, + }, + }, + }, + Labels: []string{}, + Want: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: ToUnorderedStrings([]string{}), + }, + }, + }, + }, + { + Entity: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: nil, + }, + }, + }, + Labels: nil, + Want: &LocationEntity{ + BaseEntity: BaseEntity{ + Meta: &EntityMeta{ + Labels: nil, + }, + }, + }, + }, + } + for _, test := range labelTests { + test.Entity.SetLabels(test.Labels) + if _, isDiff, _ := Diff(test.Want, test.Entity); isDiff { + t.Errorf("SetLabels: Wanted %v Got %v", test.Want, test.Entity) + } + } +} + func TestEntitySampleJSONResponseDeserialization(t *testing.T) { entityService := EntityService{ Registry: &EntityRegistry{}, From e9a8a52433631d68a185dfec7a6168d26ff60b7d Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 30 May 2019 12:39:36 -0400 Subject: [PATCH 091/285] Register healthcare professional entity and add Get functions --- entity_healthcare_professional.go | 14 ++++++++++++++ entity_registry.go | 1 + 2 files changed, 15 insertions(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index e8064eb..a1d56e9 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -516,6 +516,20 @@ func (y HealthcareProfessionalEntity) GetNPI() string { return "" } +func (y HealthcareProfessionalEntity) GetAdmittingHospitals() (v []string) { + if y.AdmittingHospitals != nil { + v = *y.AdmittingHospitals + } + return v +} + func (y HealthcareProfessionalEntity) IsClosed() bool { return GetNullableBool(y.Closed) } + +func (y HealthcareProfessionalEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} diff --git a/entity_registry.go b/entity_registry.go index 157a3ac..09eb9bd 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -12,6 +12,7 @@ func defaultEntityRegistry() *EntityRegistry { registry.Register(string(ENTITYTYPE_LOCATION), &LocationEntity{}) registry.Register(string(ENTITYTYPE_EVENT), &EventEntity{}) registry.Register(string(ENTITYTYPE_RESTAURANT), &RestaurantEntity{}) + registry.Register(string(ENTITYTYPE_HEALTHCAREPROFESSIONAL), &HealthcareProfessionalEntity{}) entityRegistry := EntityRegistry(registry) return &entityRegistry } From 23afbb11a60f900f030263f2294ced80306be4da Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 5 Jun 2019 10:22:30 -0400 Subject: [PATCH 092/285] get and set custom field values by field name --- customfield_service.go | 46 +++++++++++++++++++++++------ customfield_service_test.go | 58 +++++++++++++++++++++++++++++++++++++ entity_test.go | 15 ++++++++++ 3 files changed, 110 insertions(+), 9 deletions(-) diff --git a/customfield_service.go b/customfield_service.go index 88760b5..a746e6c 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -326,16 +326,19 @@ func (c *CustomFieldService) MustCacheCustomFields() []*CustomField { return slice } -func (c *CustomFieldManager) SetCustomField(i interface{}, fieldName string, valToSet reflect.Value) interface{} { +// SetCustomFieldValue sets the value of a given custom field (fieldName) +// A pointer to the custom entity (&CustomEntity) should be passed in as the customEntity interface{} +func (c *CustomFieldManager) SetCustomFieldValue(customEntity interface{}, fieldName string, valToSet interface{}) { cfId := c.MustCustomFieldId(fieldName) - return SetFieldByJSONTag(i, cfId, valToSet) + SetFieldByJSONTag(customEntity, cfId, valToSet) } -func SetFieldByJSONTag(i interface{}, fieldTag string, valToSet reflect.Value) interface{} { +func SetFieldByJSONTag(i interface{}, fieldTag string, valToSet interface{}) { var ( - v = Indirect(reflect.ValueOf(i)) - t = v.Type() - num = v.NumField() + valueOfValToSet = reflect.ValueOf(valToSet) + v = Indirect(reflect.ValueOf(i)) + t = v.Type() + num = v.NumField() ) for n := 0; n < num; n++ { @@ -346,11 +349,36 @@ func SetFieldByJSONTag(i interface{}, fieldTag string, valToSet reflect.Value) i val = v.Field(n) ) if tag == fieldTag { - Indirect(reflect.ValueOf(i)).FieldByName(name).Set(valToSet) - return Indirect(reflect.ValueOf(i)).Interface() + Indirect(reflect.ValueOf(i)).FieldByName(name).Set(valueOfValToSet) } else { Indirect(reflect.ValueOf(i)).FieldByName(name).Set(val) } } - return Indirect(reflect.ValueOf(i)).Interface() +} + +// GetCustomFieldValue gets the value of a given custom field (fieldName) +// A pointer to the custom entity (&CustomEntity) should be passed in as the customEntity interface{} +func (c *CustomFieldManager) GetCustomFieldValue(customEntity interface{}, fieldName string) interface{} { + cfId := c.MustCustomFieldId(fieldName) + return GetFieldByJSONTag(customEntity, cfId) +} + +func GetFieldByJSONTag(i interface{}, fieldTag string) interface{} { + var ( + v = Indirect(reflect.ValueOf(i)) + t = v.Type() + num = v.NumField() + ) + + for n := 0; n < num; n++ { + var ( + field = t.Field(n) + name = field.Name + tag = strings.Replace(field.Tag.Get("json"), ",omitempty", "", -1) + ) + if tag == fieldTag { + return Indirect(reflect.ValueOf(i)).FieldByName(name).Interface() + } + } + return nil } diff --git a/customfield_service_test.go b/customfield_service_test.go index b083249..e73d6ed 100644 --- a/customfield_service_test.go +++ b/customfield_service_test.go @@ -241,3 +241,61 @@ func TestMustGetSingleOptionName(t *testing.T) { } } } + +func TestSetCustomField(t *testing.T) { + tests := []struct { + FieldName string + Value interface{} + Entity *CustomLocationEntity + Want *CustomLocationEntity + }{ + { + FieldName: "CF Text", + Value: String("New Text"), + Entity: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFUrl: String("www.yext.com"), + }, + }, + Want: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFUrl: String("www.yext.com"), + CFText: String("New Text"), + }, + }, + }, + } + + for _, test := range tests { + CustomLocationEntityCFManager.SetCustomFieldValue(&test.Entity.CustomEntity, test.FieldName, test.Value) + if delta, diff, _ := Diff(test.Entity, test.Want); diff { + t.Errorf("TestSetCustomField:\nWanted: %v\nGot: %v\nDelta: %v", test.Want, test.Entity, delta) + } + } +} + +func TestGetCustomField(t *testing.T) { + tests := []struct { + FieldName string + Entity *CustomLocationEntity + Want interface{} + }{ + { + FieldName: "CF Text", + Entity: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFUrl: String("www.yext.com"), + CFText: String("New Text"), + }, + }, + Want: String("New Text"), + }, + } + + for _, test := range tests { + got := CustomLocationEntityCFManager.GetCustomFieldValue(&test.Entity.CustomEntity, test.FieldName) + if !reflect.DeepEqual(got, test.Want) { + t.Errorf("TestGetCustomField:\nWanted: %v\nGot: %v", test.Want, got) + } + } +} diff --git a/entity_test.go b/entity_test.go index 1c131f9..293fa64 100644 --- a/entity_test.go +++ b/entity_test.go @@ -6,6 +6,21 @@ import ( "testing" ) +var CustomLocationEntityCFManager = &CustomFieldManager{ + CustomFields: []*CustomField{ + &CustomField{ + Name: "CF Url", + Type: CUSTOMFIELDTYPE_URL, + Id: String("cf_Url"), + }, + &CustomField{ + Name: "CF Text", + Type: CUSTOMFIELDTYPE_SINGLELINETEXT, + Id: String("cf_Text"), + }, + }, +} + type CustomEntity struct { CFHours **Hours `json:"cf_Hours,omitempty"` CFUrl *string `json:"cf_Url,omitempty"` From 864fce55783281aafe890b997e7f44068856fed3 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 10 Jun 2019 13:03:41 -0400 Subject: [PATCH 093/285] updating gender to **string --- entity_healthcare_professional.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index a1d56e9..987d47b 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -10,9 +10,13 @@ import ( "fmt" ) -const ENTITYTYPE_HEALTHCAREPROFESSIONAL EntityType = "healthcareProfessional" - -const ENTITYTYPE_HEALTHCAREPROFESSIONAL_CF EntityType = "HEALTHCARE_PROFESSIONAL" +const ( + ENTITYTYPE_HEALTHCAREPROFESSIONAL EntityType = "healthcareProfessional" + ENTITYTYPE_HEALTHCAREPROFESSIONAL_CF EntityType = "HEALTHCARE_PROFESSIONAL" + GENDER_MALE = "MALE" + GENDER_FEMALE = "FEMALE" + GENDER_UNSPECIFIED = "UNSPECIFIED" +) type HealthcareProfessionalEntity struct { BaseEntity @@ -57,7 +61,7 @@ type HealthcareProfessionalEntity struct { FirstName *string `json:"firstName,omitempty"` MiddleName *string `json:"middleName,omitempty"` LastName *string `json:"lastName,omitempty"` - Gender *string `json:"gender,omitempty"` + Gender **string `json:"gender,omitempty"` Headshot **Image `json:"headshot,omitempty"` AcceptingNewPatients *bool `json:"acceptingNewPatients,omitempty"` AdmittingHospitals *[]string `json:"admittingHospitals,omitempty"` From af2d590190e477848445a150a36342aab5f591d0 Mon Sep 17 00:00:00 2001 From: Byron Harvey Date: Mon, 1 Jul 2019 18:13:13 -0400 Subject: [PATCH 094/285] Comment labels --- entity.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/entity.go b/entity.go index f9250de..a5baa60 100644 --- a/entity.go +++ b/entity.go @@ -60,6 +60,10 @@ func (b *BaseEntity) GetCountryCode() string { return "" } +// GetLabels returns a list of labels. +// Labels are stored in the system by ID, not by name +// Given a label "Example Label" with ID "123" +// This function will return a list containing ["123"] func (b *BaseEntity) GetLabels() (v UnorderedStrings) { if b == nil || b.Meta == nil { return nil @@ -70,6 +74,11 @@ func (b *BaseEntity) GetLabels() (v UnorderedStrings) { return v } +// SetLabels takes a list of strings. +// Labels are stored in the system by ID, not by name +// This means that passing the name of the label will not work, labels must be passed by ID +// Given a label "Example Label" with ID "123" +// You must pass a list containing "123" for the Label "Example Label" to get set in the platform func (b *BaseEntity) SetLabels(v []string) { l := UnorderedStrings(v) b.SetLabelsWithUnorderedStrings(l) From 27e6419370cdddedac57b4910c475f5a936ce350 Mon Sep 17 00:00:00 2001 From: czou Date: Mon, 8 Jul 2019 22:21:24 -0400 Subject: [PATCH 095/285] Add and register ATM entity --- entity_atm.go | 331 +++++++++++++++++++++++++++++++++++++++++++++ entity_registry.go | 1 + 2 files changed, 332 insertions(+) create mode 100644 entity_atm.go diff --git a/entity_atm.go b/entity_atm.go new file mode 100644 index 0000000..4b9e254 --- /dev/null +++ b/entity_atm.go @@ -0,0 +1,331 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_ATM EntityType = "atm" + +// ATM is the representation of an ATM in Yext's Knowledge Graph. +type ATMEntity struct { + BaseEntity + + // Admin + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed **bool `json:"closed,omitempty"` + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + Fax *string `json:"fax,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + + // Location Info + Hours **Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Logo **Photo `json:"logo,omitempty"` + + // Lats & Lngs + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + + YextDisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` + YextDropoffCoordinate **Coordinate `json:"yextDropoffCoordinate,omitempty"` + YextWalkableCoordinate **Coordinate `json:"yextWalkableCoordinate,omitempty"` + YextRoutableCoordinate **Coordinate `json:"yextRoutableCoordinate,omitempty"` + YextPickupCoordinate **Coordinate `json:"yextPickupCoordinate,omitempty"` + + // Urls + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + + // Social Media + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` +} + +func (l *ATMEntity) UnmarshalJSON(data []byte) error { + type Alias ATMEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(l), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(l, data) +} + +func (y ATMEntity) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y ATMEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + +func (y ATMEntity) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y ATMEntity) GetAccountId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { + return *y.BaseEntity.Meta.AccountId + } + return "" +} + +func (y ATMEntity) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return GetString(y.Address.Line1) + } + return "" +} + +func (y ATMEntity) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return GetString(y.Address.Line2) + } + return "" +} + +func (y ATMEntity) GetAddressHidden() bool { + return GetNullableBool(y.AddressHidden) +} + +func (y ATMEntity) GetExtraDescription() string { + if y.Address != nil && y.Address.ExtraDescription != nil { + return GetString(y.Address.ExtraDescription) + } + return "" +} + +func (y ATMEntity) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return GetString(y.Address.City) + } + return "" +} + +func (y ATMEntity) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return GetString(y.Address.Region) + } + return "" +} + +func (y ATMEntity) GetCountryCode() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.CountryCode != nil { + return GetString(y.BaseEntity.Meta.CountryCode) + } + return "" +} + +func (y ATMEntity) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return GetString(y.Address.PostalCode) + } + return "" +} + +func (y ATMEntity) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone + } + return "" +} + +func (y ATMEntity) GetLocalPhone() string { + if y.LocalPhone != nil { + return *y.LocalPhone + } + return "" +} + +func (y ATMEntity) GetAlternatePhone() string { + if y.AlternatePhone != nil { + return *y.AlternatePhone + } + return "" +} + +func (y ATMEntity) GetFax() string { + if y.Fax != nil { + return *y.Fax + } + return "" +} + +func (y ATMEntity) GetMobilePhone() string { + if y.MobilePhone != nil { + return *y.MobilePhone + } + return "" +} + +func (y ATMEntity) GetTollFreePhone() string { + if y.TollFreePhone != nil { + return *y.TollFreePhone + } + return "" +} + +func (y ATMEntity) GetTtyPhone() string { + if y.TtyPhone != nil { + return *y.TtyPhone + } + return "" +} + +func (y ATMEntity) GetFeaturedMessage() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Description) + } + return "" +} + +func (y ATMEntity) GetFeaturedMessageUrl() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Url) + } + return "" +} + +func (y ATMEntity) GetWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y ATMEntity) GetDisplayWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + +func (y ATMEntity) GetHours() *Hours { + return GetHours(y.Hours) +} + +func (y ATMEntity) GetAdditionalHoursText() string { + if y.AdditionalHoursText != nil { + return *y.AdditionalHoursText + } + return "" +} + +func (y ATMEntity) GetFacebookPageUrl() string { + if y.FacebookPageUrl != nil { + return *y.FacebookPageUrl + } + return "" +} + +func (y ATMEntity) GetDisplayLat() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y ATMEntity) GetDisplayLng() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y ATMEntity) GetRoutableLat() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y ATMEntity) GetRoutableLng() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y ATMEntity) GetYextDisplayLat() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y ATMEntity) GetYextDisplayLng() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y ATMEntity) GetYextRoutableLat() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y ATMEntity) GetYextRoutableLng() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y ATMEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (y ATMEntity) GetHolidayHours() []HolidayHours { + h := GetHours(y.Hours) + if h != nil && h.HolidayHours != nil { + return *h.HolidayHours + } + return nil +} + +func (y ATMEntity) IsClosed() bool { + return GetNullableBool(y.Closed) +} diff --git a/entity_registry.go b/entity_registry.go index 09eb9bd..34ab532 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -13,6 +13,7 @@ func defaultEntityRegistry() *EntityRegistry { registry.Register(string(ENTITYTYPE_EVENT), &EventEntity{}) registry.Register(string(ENTITYTYPE_RESTAURANT), &RestaurantEntity{}) registry.Register(string(ENTITYTYPE_HEALTHCAREPROFESSIONAL), &HealthcareProfessionalEntity{}) + registry.Register(string(ENTITYTYPE_ATM), &ATMEntity{}) entityRegistry := EntityRegistry(registry) return &entityRegistry } From 9c8a2c57e4fb354169019cc75d8f8924b4956ec8 Mon Sep 17 00:00:00 2001 From: czou Date: Sun, 14 Jul 2019 22:15:41 -0400 Subject: [PATCH 096/285] Remove references to upper-snake-case entity types Done following Product update that has made entity types returned from the CF endpoint camel case for consistency with the entities API --- entity_healthcare_professional.go | 9 ++++----- entity_location.go | 5 ----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 987d47b..6cf6f53 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -11,11 +11,10 @@ import ( ) const ( - ENTITYTYPE_HEALTHCAREPROFESSIONAL EntityType = "healthcareProfessional" - ENTITYTYPE_HEALTHCAREPROFESSIONAL_CF EntityType = "HEALTHCARE_PROFESSIONAL" - GENDER_MALE = "MALE" - GENDER_FEMALE = "FEMALE" - GENDER_UNSPECIFIED = "UNSPECIFIED" + ENTITYTYPE_HEALTHCAREPROFESSIONAL EntityType = "healthcareProfessional" + GENDER_MALE = "MALE" + GENDER_FEMALE = "FEMALE" + GENDER_UNSPECIFIED = "UNSPECIFIED" ) type HealthcareProfessionalEntity struct { diff --git a/entity_location.go b/entity_location.go index a21e136..289c680 100644 --- a/entity_location.go +++ b/entity_location.go @@ -11,11 +11,6 @@ import ( const ENTITYTYPE_LOCATION EntityType = "location" -// Entity types for custom fields must be MACRO_CASED -// TODO: wait for completion of this item: https://yexttest.atlassian.net/browse/AO-3660 (ETC End of February 2019) -// After item is copmleted up the vparam and (hopefully) delete this line and remove all references to it. -const ENTITYTYPE_LOCATION_CF EntityType = "LOCATION" - // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type LocationEntity struct { From 266822e2eb3c7d0287be8f8eb449c2243ca4d84a Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Wed, 17 Jul 2019 12:38:02 -0400 Subject: [PATCH 097/285] add healthcare facility entity (#136) * add healthcare facility entity * remove todo --- entity_healthcare_facility.go | 513 ++++++++++++++++++++++++++++++++++ entity_registry.go | 1 + 2 files changed, 514 insertions(+) create mode 100644 entity_healthcare_facility.go diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go new file mode 100644 index 0000000..7fa4583 --- /dev/null +++ b/entity_healthcare_facility.go @@ -0,0 +1,513 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_HEALTHCAREFACILITY EntityType = "healthcareFacility" + +// Location is the representation of a Location in Yext Location Manager. +// For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm +type HealthcareFacilityEntity struct { + BaseEntity + + // Admin + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed **bool `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + Fax *string `json:"fax,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + Emails *[]string `json:"emails,omitempty"` + + // Location Info + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API + Specialties *[]string `json:"specialities,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + + // Lats & Lngs + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + + YextDisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` + YextDropoffCoordinate **Coordinate `json:"yextDropoffCoordinate,omitempty"` + YextWalkableCoordinate **Coordinate `json:"yextWalkableCoordinate,omitempty"` + YextRoutableCoordinate **Coordinate `json:"yextRoutableCoordinate,omitempty"` + YextPickupCoordinate **Coordinate `json:"yextPickupCoordinate,omitempty"` + + // Lists + Bios **Lists `json:"bios,omitempty"` + Calendars **Lists `json:"calendars,omitempty"` + Menus **Lists `json:"menus,omitempty"` + ProductLists **Lists `json:"productLists,omitempty"` + + // Urls + MenuUrl **Website `json:"menuUrl,omitempty"` + OrderUrl **Website `json:"orderUrl,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + + // Uber + UberLink **UberLink `json:"uberLink,omitempty"` + UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` + + // Social Media + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` + + // Reviews + ReviewGenerationUrl *string `json:"reviewGenerationUrl,omitempty"` + FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` +} + +func (l *HealthcareFacilityEntity) UnmarshalJSON(data []byte) error { + type Alias HealthcareFacilityEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(l), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(l, data) +} + +func (y HealthcareFacilityEntity) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y HealthcareFacilityEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + +func (y HealthcareFacilityEntity) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y HealthcareFacilityEntity) GetAccountId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { + return *y.BaseEntity.Meta.AccountId + } + return "" +} + +func (y HealthcareFacilityEntity) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return GetString(y.Address.Line1) + } + return "" +} + +func (y HealthcareFacilityEntity) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return GetString(y.Address.Line2) + } + return "" +} + +func (y HealthcareFacilityEntity) GetAddressHidden() bool { + return GetNullableBool(y.AddressHidden) +} + +func (y HealthcareFacilityEntity) GetExtraDescription() string { + if y.Address != nil && y.Address.ExtraDescription != nil { + return GetString(y.Address.ExtraDescription) + } + return "" +} + +func (y HealthcareFacilityEntity) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return GetString(y.Address.City) + } + return "" +} + +func (y HealthcareFacilityEntity) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return GetString(y.Address.Region) + } + return "" +} + +func (y HealthcareFacilityEntity) GetCountryCode() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.CountryCode != nil { + return GetString(y.BaseEntity.Meta.CountryCode) + } + return "" +} + +func (y HealthcareFacilityEntity) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return GetString(y.Address.PostalCode) + } + return "" +} + +func (y HealthcareFacilityEntity) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone + } + return "" +} + +func (y HealthcareFacilityEntity) GetLocalPhone() string { + if y.LocalPhone != nil { + return *y.LocalPhone + } + return "" +} + +func (y HealthcareFacilityEntity) GetAlternatePhone() string { + if y.AlternatePhone != nil { + return *y.AlternatePhone + } + return "" +} + +func (y HealthcareFacilityEntity) GetFax() string { + if y.Fax != nil { + return *y.Fax + } + return "" +} + +func (y HealthcareFacilityEntity) GetMobilePhone() string { + if y.MobilePhone != nil { + return *y.MobilePhone + } + return "" +} + +func (y HealthcareFacilityEntity) GetTollFreePhone() string { + if y.TollFreePhone != nil { + return *y.TollFreePhone + } + return "" +} + +func (y HealthcareFacilityEntity) GetTtyPhone() string { + if y.TtyPhone != nil { + return *y.TtyPhone + } + return "" +} + +func (y HealthcareFacilityEntity) GetFeaturedMessage() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Description) + } + return "" +} + +func (y HealthcareFacilityEntity) GetFeaturedMessageUrl() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Url) + } + return "" +} + +func (y HealthcareFacilityEntity) GetWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y HealthcareFacilityEntity) GetDisplayWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + +func (y HealthcareFacilityEntity) GetReservationUrl() string { + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y HealthcareFacilityEntity) GetHours() *Hours { + return GetHours(y.Hours) +} + +func (y HealthcareFacilityEntity) GetAdditionalHoursText() string { + if y.AdditionalHoursText != nil { + return *y.AdditionalHoursText + } + return "" +} + +func (y HealthcareFacilityEntity) GetDescription() string { + if y.Description != nil { + return *y.Description + } + return "" +} + +func (y HealthcareFacilityEntity) GetTwitterHandle() string { + if y.TwitterHandle != nil { + return *y.TwitterHandle + } + return "" +} + +func (y HealthcareFacilityEntity) GetFacebookPageUrl() string { + if y.FacebookPageUrl != nil { + return *y.FacebookPageUrl + } + return "" +} + +func (y HealthcareFacilityEntity) GetYearEstablished() float64 { + if y.YearEstablished != nil { + return GetNullableFloat(y.YearEstablished) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetDisplayLat() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetDisplayLng() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetRoutableLat() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetRoutableLng() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetYextDisplayLat() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetYextDisplayLng() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetYextRoutableLat() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetYextRoutableLng() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HealthcareFacilityEntity) GetBios() (v *Lists) { + return GetLists(y.Bios) +} + +func (y HealthcareFacilityEntity) GetCalendars() (v *Lists) { + return GetLists(y.Calendars) +} + +func (y HealthcareFacilityEntity) GetProductLists() (v *Lists) { + return GetLists(y.ProductLists) +} + +func (y HealthcareFacilityEntity) GetMenus() (v *Lists) { + return GetLists(y.Menus) +} + +func (y HealthcareFacilityEntity) GetReviewGenerationUrl() string { + if y.ReviewGenerationUrl != nil { + return *y.ReviewGenerationUrl + } + return "" +} + +func (y HealthcareFacilityEntity) GetFirstPartyReviewPage() string { + if y.FirstPartyReviewPage != nil { + return *y.FirstPartyReviewPage + } + return "" +} + +func (y HealthcareFacilityEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (y HealthcareFacilityEntity) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (y HealthcareFacilityEntity) GetLanguage() (v string) { + if y.BaseEntity.Meta.Language != nil { + v = *y.BaseEntity.Meta.Language + } + return v +} + +func (y HealthcareFacilityEntity) GetAssociations() (v []string) { + if y.Associations != nil { + v = *y.Associations + } + return v +} + +func (y HealthcareFacilityEntity) GetEmails() (v []string) { + if y.Emails != nil { + v = *y.Emails + } + return v +} + +func (y HealthcareFacilityEntity) GetSpecialties() (v []string) { + if y.Specialties != nil { + v = *y.Specialties + } + return v +} + +func (y HealthcareFacilityEntity) GetServices() (v []string) { + if y.Services != nil { + v = *y.Services + } + return v +} + +func (y HealthcareFacilityEntity) GetBrands() (v []string) { + if y.Brands != nil { + v = *y.Brands + } + return v +} + +func (y HealthcareFacilityEntity) GetLanguages() (v []string) { + if y.Languages != nil { + v = *y.Languages + } + return v +} + +func (y HealthcareFacilityEntity) GetPaymentOptions() (v []string) { + if y.PaymentOptions != nil { + v = *y.PaymentOptions + } + return v +} + +func (y HealthcareFacilityEntity) GetVideos() (v []Video) { + if y.Videos != nil { + v = *y.Videos + } + return v +} + +func (y HealthcareFacilityEntity) GetGoogleAttributes() map[string][]string { + if y.GoogleAttributes != nil { + return *y.GoogleAttributes + } + return nil +} + +func (y HealthcareFacilityEntity) GetHolidayHours() []HolidayHours { + h := GetHours(y.Hours) + if h != nil && h.HolidayHours != nil { + return *h.HolidayHours + } + return nil +} + +func (y HealthcareFacilityEntity) IsClosed() bool { + return GetNullableBool(y.Closed) +} diff --git a/entity_registry.go b/entity_registry.go index 34ab532..9b95cdc 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -13,6 +13,7 @@ func defaultEntityRegistry() *EntityRegistry { registry.Register(string(ENTITYTYPE_EVENT), &EventEntity{}) registry.Register(string(ENTITYTYPE_RESTAURANT), &RestaurantEntity{}) registry.Register(string(ENTITYTYPE_HEALTHCAREPROFESSIONAL), &HealthcareProfessionalEntity{}) + registry.Register(string(ENTITYTYPE_HEALTHCAREFACILITY), &HealthcareFacilityEntity{}) registry.Register(string(ENTITYTYPE_ATM), &ATMEntity{}) entityRegistry := EntityRegistry(registry) return &entityRegistry From 4b51b45c809d29b1606b25eb07250dcd7b3857a3 Mon Sep 17 00:00:00 2001 From: Isaac Nemzer Date: Fri, 2 Aug 2019 10:08:40 -0400 Subject: [PATCH 098/285] Added custom field type tests to entity differ (#140) * Added custom field type tests to entity differ * Fix bug with null types --- entity_diff_test.go | 300 ++++++++++++++++++++++++++++++++++++++++++++ entity_location.go | 14 +-- entity_test.go | 8 ++ 3 files changed, 315 insertions(+), 7 deletions(-) diff --git a/entity_diff_test.go b/entity_diff_test.go index b74e4bd..61bdbbb 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -1282,6 +1282,69 @@ func TestEntityDiffComplex(t *testing.T) { }, }, }, + { + name: "CFImage", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFImage: NullableImage(&Image{ + Url: String("www.yext.com"), + }), + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFImage: NullImage(), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFImage: NullImage(), + }, + }, + }, + { + name: "New FeaturedMessage is Null", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + FeaturedMessage: NullableFeaturedMessage(&FeaturedMessage{ + Url: String("Featured Message"), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + FeaturedMessage: NullFeaturedMessage(), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + FeaturedMessage: NullFeaturedMessage(), + }, + }, + }, + { + name: "New Website is Null", + base: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + WebsiteUrl: NullableWebsite(&Website{ + Url: String("Website"), + }), + }, + }, + new: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + WebsiteUrl: NullWebsite(), + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + LocationEntity: LocationEntity{ + WebsiteUrl: NullWebsite(), + }, + }, + }, { name: "Hours Closed Change", base: &CustomLocationEntity{ @@ -2087,6 +2150,243 @@ func TestEntityDiffComplex(t *testing.T) { }, isDiff: false, }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{}, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Initial custom field type text"), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Initial custom field type text"), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Text to clear"), + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String(""), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String(""), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Old custom field type text"), + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("New custom field type text"), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("New custom field type text"), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{}, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("New image"), + }, + ), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("New image"), + }, + ), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("Initial image to update"), + }, + ), + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/autocare-hero.jpg"), + AlternateText: String("Updated image"), + }, + ), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/autocare-hero.jpg"), + AlternateText: String("Updated image"), + }, + ), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("Initial image to clear"), + }, + ), + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullImage(), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Image: NullImage(), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Initial custom field type text to update"), + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("Initial image"), + }, + ), + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Updated custom field type text"), + }, + }, + }, + isDiff: true, + delta: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Updated custom field type text"), + }, + }, + }, + }, + { + name: "CFT", + base: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Initial custom field type text"), + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("Initial image"), + }, + ), + }, + }, + }, + new: &CustomLocationEntity{ + CustomEntity: CustomEntity{ + CFType: &CFT{ + Text: String("Initial custom field type text"), + Image: NullableImage( + &Image{ + Url: String("https://www.napaonline.com/medias/about-hero.jpg"), + AlternateText: String("Initial image"), + }, + ), + }, + }, + }, + isDiff: false, + }, } for _, test := range tests { diff --git a/entity_location.go b/entity_location.go index 289c680..20afd76 100644 --- a/entity_location.go +++ b/entity_location.go @@ -146,7 +146,7 @@ func GetUberLink(u **UberLink) *UberLink { } func NullUberLink() **UberLink { - u := &UberLink{} + var u *UberLink return &u } @@ -168,7 +168,7 @@ func GetUberTripBranding(u **UberTripBranding) *UberTripBranding { } func NullUberTripBranding() **UberTripBranding { - u := &UberTripBranding{} + var u *UberTripBranding return &u } @@ -189,7 +189,7 @@ func GetLists(l **Lists) *Lists { } func NullLists() **Lists { - l := &Lists{} + var l *Lists return &l } @@ -217,7 +217,7 @@ func GetImage(i **Image) *Image { } func NullImage() **Image { - i := &Image{} + var i *Image return &i } @@ -249,7 +249,7 @@ func GetFeaturedMessage(f **FeaturedMessage) *FeaturedMessage { } func NullFeaturedMessage() **FeaturedMessage { - f := &FeaturedMessage{} + var f *FeaturedMessage return &f } @@ -271,7 +271,7 @@ func GetWebsite(w **Website) *Website { } func NullWebsite() **Website { - w := &Website{} + var w *Website return &w } @@ -292,7 +292,7 @@ func GetCoordinate(c **Coordinate) *Coordinate { } func NullCoordinate() **Coordinate { - c := &Coordinate{} + var c *Coordinate return &c } diff --git a/entity_test.go b/entity_test.go index 293fa64..74910a9 100644 --- a/entity_test.go +++ b/entity_test.go @@ -21,6 +21,12 @@ var CustomLocationEntityCFManager = &CustomFieldManager{ }, } +type CFT struct { + Text *string `json:"cft_text,omitempty"` + Image **Image `json:"cft_image,omitempty"` + Bool **bool `json:"cft_bool,omitempty"` +} + type CustomEntity struct { CFHours **Hours `json:"cf_Hours,omitempty"` CFUrl *string `json:"cf_Url,omitempty"` @@ -36,6 +42,8 @@ type CustomEntity struct { CFYesNo **bool `json:"cf_YesNo,omitempty"` CFText *string `json:"cf_Text,omitempty"` CFMultiLine *string `json:"cf_MultiLineText,omitempty"` + CFImage **Image `json:"cf_Image,omitempty"` + CFType *CFT `json:"cf_Type,omitempty"` } type CustomLocationEntity struct { From aa98311676907a57ee90bc20a8a0dc62a5f9382d Mon Sep 17 00:00:00 2001 From: Isaac Nemzer Date: Wed, 7 Aug 2019 11:27:33 -0400 Subject: [PATCH 099/285] Add getters for VideoUrl (#141) --- type.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/type.go b/type.go index bd51cfb..8796777 100644 --- a/type.go +++ b/type.go @@ -149,6 +149,22 @@ func NullVideo() **Video { return &v } +func NullableVideoUrl(v *VideoUrl) **VideoUrl { + return &v +} + +func GetVideoUrl(v **VideoUrl) *VideoUrl { + if v == nil { + return nil + } + return *v +} + +func NullVideoUrl() **VideoUrl { + var v *VideoUrl + return &v +} + func NullablePhoto(v *Photo) **Photo { return &v } From 24f0309313ed4850cf4f8c5ba3bdc852b1f91829 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Tue, 27 Aug 2019 15:53:34 -0400 Subject: [PATCH 100/285] Diff() for RawEntity --- entity.go | 6 ++++- entity_diff.go | 47 +++++++++++++++++++++++++++++++++ entity_diff_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/entity.go b/entity.go index a5baa60..60b4209 100644 --- a/entity.go +++ b/entity.go @@ -114,7 +114,11 @@ func (r *RawEntity) GetEntityType() EntityType { if m, ok := (*r)["meta"]; ok { meta := m.(map[string]interface{}) if t, ok := meta["entityType"]; ok { - return EntityType(t.(string)) + if s, isString := t.(string); isString { + return EntityType(s) + } else if _, isEntityType := t.(EntityType); isEntityType { + return t.(EntityType) + } } } return EntityType("") diff --git a/entity_diff.go b/entity_diff.go index 07ffe38..28d3d11 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -167,9 +167,56 @@ func Diff(a Entity, b Entity) (Entity, bool, error) { return nil, true, fmt.Errorf("Entity Types do not match: '%s', '%s'", a.GetEntityType(), b.GetEntityType()) } + // TODO (cdworak): GenericDiff cannot handle map (RawEntity is map) + rawA, okA := a.(*RawEntity) + rawB, okB := b.(*RawEntity) + if okA && okB { + delta, isDiff := RawEntityDiff(*rawA, *rawB, GetNilIsEmpty(a), GetNilIsEmpty(b)) + if !isDiff { + return nil, isDiff, nil + } + rawDelta := RawEntity(delta) + return &rawDelta, isDiff, nil + } + delta, isDiff := GenericDiff(a, b, GetNilIsEmpty(a), GetNilIsEmpty(b)) if !isDiff { return nil, isDiff, nil } return delta.(Entity), isDiff, nil } + +func RawEntityDiff(a map[string]interface{}, b map[string]interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (map[string]interface{}, bool) { + var ( + aAsMap = a + bAsMap = b + delta = map[string]interface{}{} + isDiff = false + ) + + for key, bVal := range bAsMap { + if key == "nilIsEmpty" { + continue + } + if aVal, ok := aAsMap[key]; ok { + _, aIsMap := aVal.(map[string]interface{}) + _, bIsMap := bVal.(map[string]interface{}) + if aIsMap && bIsMap { + subFieldsDelta, subFieldsAreDiff := RawEntityDiff(aVal.(map[string]interface{}), bVal.(map[string]interface{}), nilIsEmptyA, nilIsEmptyB) + if subFieldsAreDiff { + delta[key] = subFieldsDelta + isDiff = true + } + } else { + if !reflect.DeepEqual(aVal, bVal) { + delta[key] = bVal + isDiff = true + } + } + } + } + if isDiff { + return delta, true + } + return nil, false +} diff --git a/entity_diff_test.go b/entity_diff_test.go index 61bdbbb..9611183 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -2406,6 +2406,70 @@ func TestEntityDiffComplex(t *testing.T) { } } +func TestRawEntityDiff(t *testing.T) { + tests := []struct { + name string + base *RawEntity + new *RawEntity + isDiff bool + delta *RawEntity + }{ + { + name: "equal", + base: &RawEntity{ + "name": "yext", + }, + new: &RawEntity{ + "name": "yext", + }, + isDiff: false, + }, + { + name: "not equal", + base: &RawEntity{ + "name": String("yext"), + }, + new: &RawEntity{ + "name": "new yext", + }, + isDiff: true, + delta: &RawEntity{ + "name": "new yext", + }, + }, + { + name: "field within field equal", + base: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + }, + }, + new: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + }, + }, + isDiff: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + delta, isDiff, _ := Diff(test.base, test.new) + if isDiff != test.isDiff { + t.Log(delta) + t.Errorf("Expected isDiff: %t.\nGot: %t", test.isDiff, isDiff) + } else if test.isDiff == false && delta != nil { + t.Errorf("Expected isDiff: %t.\nGot delta: %v", test.isDiff, delta) + } else if isDiff { + if !reflect.DeepEqual(delta, test.delta) { + t.Errorf("Expected delta: %v.\nGot: %v", test.delta, delta) + } + } + }) + } +} + func TestInstanceOf(t *testing.T) { var ( b = String("apple") From 59974c035b3cb533c5874c0842073528fbfef331 Mon Sep 17 00:00:00 2001 From: czou Date: Wed, 28 Aug 2019 16:13:38 -0400 Subject: [PATCH 101/285] Add constant for Yext hours format --- hours.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hours.go b/hours.go index ea07d88..a24e9a5 100644 --- a/hours.go +++ b/hours.go @@ -11,6 +11,7 @@ const ( HoursClosedAllWeek = "1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed" HoursOpen24Hours = "00:00:00:00" HoursClosed = "closed" + HoursFormat = "15:04" hoursLen = 11 // XX:XX:XX:XX format ) From 9df5ae73eb89a0b523d3745e62b53640f03dcd59 Mon Sep 17 00:00:00 2001 From: Cindy Zou Date: Mon, 9 Sep 2019 13:43:07 -0400 Subject: [PATCH 102/285] Add and register Hotel entity (#138) --- entity_hotel.go | 547 +++++++++++++++++++++++++++++++++++++++++++++ entity_registry.go | 1 + location_diff.go | 2 + type.go | 29 +++ 4 files changed, 579 insertions(+) create mode 100644 entity_hotel.go diff --git a/entity_hotel.go b/entity_hotel.go new file mode 100644 index 0000000..9d74989 --- /dev/null +++ b/entity_hotel.go @@ -0,0 +1,547 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_HOTEL EntityType = "hotel" + +type HotelEntity struct { + BaseEntity + + // Admin + CategoryIds *[]string `json:"categoryIds,omitempty"` + Closed **bool `json:"closed,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + Fax *string `json:"fax,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + Emails *[]string `json:"emails,omitempty"` + + // Location Info + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field + CheckOutTime *string `json:"checkOutTime,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + + // Social Media + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + + // Media + Logo **Photo `json:"logo,omitempty"` + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + + // Lists + Bios **Lists `json:"bios,omitempty"` + Menus **Lists `json:"menus,omitempty"` + ProductLists **Lists `json:"productLists,omitempty"` + + // URLs + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + MenuUrl **Website `json:"menuUrl,omitempty"` + OrderUrl **Website `json:"orderUrl,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + + // Lats & Lngs + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + + YextDisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` + YextDropoffCoordinate **Coordinate `json:"yextDropoffCoordinate,omitempty"` + YextWalkableCoordinate **Coordinate `json:"yextWalkableCoordinate,omitempty"` + YextRoutableCoordinate **Coordinate `json:"yextRoutableCoordinate,omitempty"` + YextPickupCoordinate **Coordinate `json:"yextPickupCoordinate,omitempty"` + + // Property Info + YearEstablished **float64 `json:"yearEstablished,omitempty"` + YearLastRenovated **int `json:"yearLastRenovated,omitempty"` + RoomCount **int `json:"roomCount,omitempty"` + FloorCount **int `json:"floorCount,omitempty"` + BeachFrontProperty **Ternary `json:"beachFrontProperty,omitempty"` + + // Services + ClassificationRating **int `json:"classificationRating,omitempty"` + FrontDesk **string `json:"frontDesk,omitempty"` + Laundry **string `json:"laundry,omitempty"` + Housekeeping **string `json:"housekeeping,omitempty"` + Parking **string `json:"parking,omitempty"` + SelfParking **string `json:"selfParking,omitempty"` + ValetParking **string `json:"valetParking,omitempty"` + Concierge **Ternary `json:"concierge,omitempty"` + Elevator **Ternary `json:"elevator,omitempty"` + BaggageStorage **Ternary `json:"baggageStorage,omitempty"` + SocialHour **Ternary `json:"socialHour,omitempty"` + WakeUpCalls **Ternary `json:"wakeUpCalls,omitempty"` + ConvenienceStore **Ternary `json:"convenienceStore,omitempty"` + GiftShop **Ternary `json:"giftShop,omitempty"` + CurrencyExchange **Ternary `json:"currencyExchange,omitempty"` + TurndownService **Ternary `json:"turndownService,omitempty"` + ElectricChargingStation **Ternary `json:"electricChargingStation,omitempty"` + + // Policies + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + AllInclusive **string `json:"allInclusive,omitempty"` + PetsAllowed **string `json:"petsAllowed,omitempty"` + KidsStayFree **Ternary `json:"kidsStayFree,omitempty"` + SmokeFreeProperty **Ternary `json:"smokeFreeProperty,omitempty"` + CatsAllowed **Ternary `json:"catsAllowed,omitempty"` + DogsAllowed **Ternary `json:"dogsAllowed,omitempty"` + + // Food and Drink + RoomService **string `json:"roomService,omitempty"` + RestaurantCount **int `json:"restaurantCount,omitempty"` + Breakfast **string `json:"breakfast,omitempty"` + TableService **Ternary `json:"tableService,omitempty"` + Bar **Ternary `json:"bar,omitempty"` + VendingMachine **Ternary `json:"vendingMachine,omitempty"` + BuffetOptions *UnorderedStrings `json:"buffetOptions,omitempty"` + + // Pools + IndoorPoolCount **int `json:"indoorPoolCount,omitempty"` + OutdoorPoolCount **int `json:"outdoorPoolCount,omitempty"` + HotTub **Ternary `json:"hotTub,omitempty"` + WaterSlide **Ternary `json:"waterslide,omitempty"` + LazyRiver **Ternary `json:"lazyRiver,omitempty"` + AdultPool **Ternary `json:"adultPool,omitempty"` + WadingPool **Ternary `json:"wadingPool,omitempty"` + WavePool **Ternary `json:"wavePool,omitempty"` + ThermalPool **Ternary `json:"thermalPool,omitempty"` + WaterPark **Ternary `json:"waterPark,omitempty"` + LifeGuard **Ternary `json:"lifeguard,omitempty"` + + // Wellness + FitnessCenter **string `json:"fitnessCenter,omitempty"` + EllipticalMachine **Ternary `json:"ellipticalMachine,omitempty"` + Treadmill **Ternary `json:"treadmill,omitempty"` + WeightMachine **Ternary `json:"weightMachine,omitempty"` + FreeWeights **Ternary `json:"freeWeights,omitempty"` + Spa **Ternary `json:"spa,omitempty"` + Salon **Ternary `json:"salon,omitempty"` + Sauna **Ternary `json:"sauna,omitempty"` + Massage **Ternary `json:"massage,omitempty"` + DoctorOnCall **Ternary `json:"doctorOnCall,omitempty"` + + // Activities + Bicycles **string `json:"bicycles,omitempty"` + WaterCraft **string `json:"watercraft,omitempty"` + GameRoom **Ternary `json:"gameRoom,omitempty"` + Nightclub **Ternary `json:"nightclub,omitempty"` + Casino **Ternary `json:"casino,omitempty"` + BoutiqueStores **Ternary `json:"boutiqueStores,omitempty"` + Tennis **Ternary `json:"tennis,omitempty"` + Golf **Ternary `json:"golf,omitempty"` + HorsebackRiding **Ternary `json:"horsebackRiding,omitempty"` + Snorkeling **Ternary `json:"snorkeling,omitempty"` + Scuba **Ternary `json:"scuba,omitempty"` + WaterSkiing **Ternary `json:"waterSkiing,omitempty"` + BeachAccess **Ternary `json:"beachAccess,omitempty"` + PrivateBeach **Ternary `json:"privateBeach,omitempty"` + + // Transportation + AirportShuttle **string `json:"airportShuttle,omitempty"` + PrivateCarService **string `json:"privateCarService,omitempty"` + AirportTransfer **Ternary `json:"airportTransfer,omitempty"` + LocalShuttle **Ternary `json:"localShuttle,omitempty"` + CarRental **Ternary `json:"carRental,omitempty"` + + // Families + BabySittingOffered **Ternary `json:"babysittingOffered,omitempty"` + KidFriendly **Ternary `json:"kidFriendly,omitempty"` + KidsClub **Ternary `json:"kidsClub,omitempty"` + + // Connectivity + WiFiAvailable **string `json:"wifiAvailable,omitempty"` + WiFiDetails *UnorderedStrings `json:"wifiDetails,omitempty"` + + // Business + MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` + BusinessCenter **Ternary `json:"businessCenter,omitempty"` + + // Accessibility + WheelchairAccessible **Ternary `json:"wheelchairAccessible,omitempty"` + AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` +} + +const ( + // Single-option IDs + OptionNotApplicable = "NOT_APPLICABLE" + OptionPetsWelcomeForFree = "PETS_WELCOME_FOR_FREE" + OptionPetsWelcome = "PETS_WELCOME" + OptionFrontDeskAvailable24Hours = "FRONT_DESK_AVAILABLE_24_HOURS" + OptionFrontDeskAvailable = "FRONT_DESK_AVAILABLE" + OptionHousekeepingAvailableDaily = "HOUSEKEEPING_AVAILABLE_DAILY" + OptionHousekeepingAvailable = "HOUSEKEEPING_AVAILABLE" + OptionFullServiceLaundry = "FULL_SERVICE" + OptionSelfServiceLaundry = "SELF_SERVICE" + Option24HourRoomService = "ROOM_SERVICE_AVAILABLE_24_HOURS" + OptionRoomServiceAvailable = "ROOM_SERVICE_AVAILABLE" + OptionBicyclesAvailable = "BICYCLES_AVAILABLE" + OptionBicyclesAvailableForFree = "BICYCLES_AVAILABLE_FOR_FREE" + OptionFitnessCenterAvailable = "FITNESS_CENTER_AVAILABLE" + OptionFitnessCenterAvailableForFree = "FITNESS_CENTER_AVAILABLE_FOR_FREE" + OptionParkingAvailableForFree = "PARKING_AVAILABLE_FOR_FREE" + OptionParkingAvailable = "PARKING_AVAILABLE" + OptionValetParkingAvailableForFree = "VALET_PARKING_AVAILABLE_FOR_FREE" + OptionValetParkingAvailable = "VALET_PARKING_AVAILABLE" + OptionAirportShuttleAvailableForFree = "AIRPORT_SHUTTLE_AVAILABLE_FOR_FREE" + OptionAirportShuttleAvailable = "AIRPORT_SHUTTLE_AVAILABLE" + + // Multi-option IDs + OptionWiFiInPublicAreas = "WIFI_IN_PUBLIC_AREAS" + OptionAccessibleParking = "ACCESSIBLE_PARKING" + OptionAccessibleElevator = "ACCESSIBLE_ELEVATOR" + OptionAccessiblePool = "ACCESSIBLE_POOL" +) + +func (h *HotelEntity) UnmarshalJSON(data []byte) error { + type Alias HotelEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(h), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(h, data) +} + +func (y HotelEntity) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y HotelEntity) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + +func (y HotelEntity) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y HotelEntity) GetAccountId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { + return *y.BaseEntity.Meta.AccountId + } + return "" +} + +func (y HotelEntity) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return GetString(y.Address.Line1) + } + return "" +} + +func (y HotelEntity) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return GetString(y.Address.Line2) + } + return "" +} + +func (y HotelEntity) GetAddressHidden() bool { + return GetNullableBool(y.AddressHidden) +} + +func (y HotelEntity) GetExtraDescription() string { + if y.Address != nil && y.Address.ExtraDescription != nil { + return GetString(y.Address.ExtraDescription) + } + return "" +} + +func (y HotelEntity) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return GetString(y.Address.City) + } + return "" +} + +func (y HotelEntity) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return GetString(y.Address.Region) + } + return "" +} + +func (y HotelEntity) GetCountryCode() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.CountryCode != nil { + return GetString(y.BaseEntity.Meta.CountryCode) + } + if y.Address != nil && y.Address.CountryCode != nil { + return GetString(y.Address.CountryCode) + } + return "" +} + +func (y HotelEntity) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return GetString(y.Address.PostalCode) + } + return "" +} + +func (y HotelEntity) GetFeaturedMessage() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Description) + } + return "" +} + +func (y HotelEntity) GetFeaturedMessageUrl() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Url) + } + return "" +} + +func (y HotelEntity) GetWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y HotelEntity) GetDisplayWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + +func (y HotelEntity) GetReservationUrl() string { + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y HotelEntity) GetHours() *Hours { + return GetHours(y.Hours) +} + +func (y HotelEntity) GetYearEstablished() float64 { + if y.YearEstablished != nil { + return GetNullableFloat(y.YearEstablished) + } + return 0 +} + +func (y HotelEntity) GetDisplayLat() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HotelEntity) GetDisplayLng() float64 { + c := GetCoordinate(y.DisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HotelEntity) GetRoutableLat() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HotelEntity) GetRoutableLng() float64 { + c := GetCoordinate(y.RoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HotelEntity) GetYextDisplayLat() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HotelEntity) GetYextDisplayLng() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HotelEntity) GetYextRoutableLat() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HotelEntity) GetYextRoutableLng() float64 { + c := GetCoordinate(y.YextRoutableCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + +func (y HotelEntity) GetBios() (v *Lists) { + return GetLists(y.Bios) +} + +func (y HotelEntity) GetProductLists() (v *Lists) { + return GetLists(y.ProductLists) +} + +func (y HotelEntity) GetMenus() (v *Lists) { + return GetLists(y.Menus) +} + +func (y HotelEntity) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (y HotelEntity) GetLanguage() (v string) { + if y.BaseEntity.Meta.Language != nil { + v = *y.BaseEntity.Meta.Language + } + return v +} + +func (y HotelEntity) GetAssociations() (v []string) { + if y.Associations != nil { + v = *y.Associations + } + return v +} + +func (y HotelEntity) GetEmails() (v []string) { + if y.Emails != nil { + v = *y.Emails + } + return v +} + +func (y HotelEntity) GetServices() (v []string) { + if y.Services != nil { + v = *y.Services + } + return v +} + +func (y HotelEntity) GetBrands() (v []string) { + if y.Brands != nil { + v = *y.Brands + } + return v +} + +func (y HotelEntity) GetLanguages() (v []string) { + if y.Languages != nil { + v = *y.Languages + } + return v +} + +func (y HotelEntity) GetPaymentOptions() (v []string) { + if y.PaymentOptions != nil { + v = *y.PaymentOptions + } + return v +} + +func (y HotelEntity) GetVideos() (v []Video) { + if y.Videos != nil { + v = *y.Videos + } + return v +} + +func (y HotelEntity) GetHolidayHours() []HolidayHours { + h := GetHours(y.Hours) + if h != nil && h.HolidayHours != nil { + return *h.HolidayHours + } + return nil +} + +func (y HotelEntity) GetBuffetOptions() []string { + if y.BuffetOptions != nil { + v := *y.BuffetOptions + return []string(v) + } + return []string{} +} + +func (y HotelEntity) GetWiFiDetails() []string { + if y.WiFiDetails != nil { + v := *y.WiFiDetails + return []string(v) + } + return []string{} +} + +func (y HotelEntity) GetAccessibilityDetails() []string { + if y.AccessibilityDetails != nil { + v := *y.AccessibilityDetails + return []string(v) + } + return []string{} +} + +func (y HotelEntity) IsClosed() bool { + return GetNullableBool(y.Closed) +} + +func (y HotelEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} diff --git a/entity_registry.go b/entity_registry.go index 9b95cdc..7c08c9e 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -15,6 +15,7 @@ func defaultEntityRegistry() *EntityRegistry { registry.Register(string(ENTITYTYPE_HEALTHCAREPROFESSIONAL), &HealthcareProfessionalEntity{}) registry.Register(string(ENTITYTYPE_HEALTHCAREFACILITY), &HealthcareFacilityEntity{}) registry.Register(string(ENTITYTYPE_ATM), &ATMEntity{}) + registry.Register(string(ENTITYTYPE_HOTEL), &HotelEntity{}) entityRegistry := EntityRegistry(registry) return &entityRegistry } diff --git a/location_diff.go b/location_diff.go index da6ccdb..f43a11b 100644 --- a/location_diff.go +++ b/location_diff.go @@ -140,6 +140,8 @@ func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { return v.Bool() == false case reflect.String: return v.String() == "" + case reflect.Int: + return v.Int() == 0 case reflect.Uint64: return v.Uint() == 0 case reflect.Float64: diff --git a/type.go b/type.go index 8796777..97a4412 100644 --- a/type.go +++ b/type.go @@ -279,3 +279,32 @@ func NullableUnorderedStrings(v []string) *UnorderedStrings { u := UnorderedStrings(v) return &u } + +// Ternary is used for single-option fields that could have one of three +// options (aside from being unset): "Yes", "No", and "Not Applicable" + +type Ternary string + +const ( + Yes Ternary = "YES" + No Ternary = "NO" + NotApplicable Ternary = "NOT_APPLICABLE" + Unset Ternary = "" +) + +func NullableTernary(v Ternary) **Ternary { + y := &v + return &y +} + +func GetNullableTernary(v **Ternary) Ternary { + if v == nil || *v == nil { + return Unset + } + return **v +} + +func NullTernary() **Ternary { + var v *Ternary + return &v +} From 1c073851cb5f1e5efe77eb156a0ac695877f158a Mon Sep 17 00:00:00 2001 From: Julian Brown <38870502+bjulian5@users.noreply.github.com> Date: Wed, 11 Sep 2019 13:42:38 -0400 Subject: [PATCH 103/285] J=PC-59368 Add image width, height and thumbnails fields for images (#146) --- entity_location.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/entity_location.go b/entity_location.go index 20afd76..4258d03 100644 --- a/entity_location.go +++ b/entity_location.go @@ -203,6 +203,15 @@ type Photo struct { type Image struct { Url *string `json:"url,omitempty"` AlternateText *string `json:"alternateText,omitempty"` + Width *int `json:"width,omitempty"` + Height *int `json:"height,omitempty"` + Thumbnails *[]Thumbnail `json:"thumnails,omitempty"` +} + +type Thumbnail struct { + Url *string `json:"url,omitempty"` + Width *int `json:"width,omitempty"` + Height *int `json:"height,omitempty"` } func NullableImage(i *Image) **Image { From 2e8b9a15462adb00a9500d3386db17b6166b0231 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Tue, 24 Sep 2019 15:29:06 -0400 Subject: [PATCH 104/285] Adding breakfast field values (#149) --- entity_hotel.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/entity_hotel.go b/entity_hotel.go index 9d74989..2aca376 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -210,6 +210,8 @@ const ( OptionValetParkingAvailable = "VALET_PARKING_AVAILABLE" OptionAirportShuttleAvailableForFree = "AIRPORT_SHUTTLE_AVAILABLE_FOR_FREE" OptionAirportShuttleAvailable = "AIRPORT_SHUTTLE_AVAILABLE" + OptionBreakfastAvailable = "BREAKFAST_AVAILABLE" + OptionBreakfastAvailableForFree = "BREAKFAST_AVAILABLE_FOR_FREE" // Multi-option IDs OptionWiFiInPublicAreas = "WIFI_IN_PUBLIC_AREAS" From 42749aab7871b6caa4a4979ac973b9a2abb0022a Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Wed, 25 Sep 2019 22:17:44 -0400 Subject: [PATCH 105/285] add google fields to hotel entity (#145) * add google fields to hotel entity * modify v2 loc converter to accept hotel * fix tabbing --- entity_hotel.go | 6 ++++++ location.go | 1 + 2 files changed, 7 insertions(+) diff --git a/entity_hotel.go b/entity_hotel.go index 2aca376..1c29cc8 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -47,6 +47,12 @@ type HotelEntity struct { InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` + // Media Logo **Photo `json:"logo,omitempty"` PhotoGallery *[]Photo `json:"photoGallery,omitempty"` diff --git a/location.go b/location.go index dffb4a1..59f8e72 100644 --- a/location.go +++ b/location.go @@ -17,6 +17,7 @@ var LOCATIONTYPE_HEALTHCARE_PROFESSIONAL LocationType = String("HEALTHCARE_PROFE var LOCATIONTYPE_HEALTHCARE_FACILITY LocationType = String("HEALTHCARE_FACILITY") var LOCATIONTYPE_RESTAURANT LocationType = String("RESTAURANT") var LOCATIONTYPE_EVENT LocationType = String("EVENT") +var LOCATIONTYPE_HOTEL LocationType = String("HOTEL") // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm From b3dbf63d4608e71555a39934127e350b5149a7f5 Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Wed, 25 Sep 2019 22:53:20 -0400 Subject: [PATCH 106/285] add GetMultiOptionIds for error handling (#151) --- customfield_service.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/customfield_service.go b/customfield_service.go index a746e6c..b13003f 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -5,6 +5,8 @@ import ( "fmt" "reflect" "strings" + + multierror "github.com/hashicorp/go-multierror" ) const customFieldPath = "customfields" @@ -209,6 +211,33 @@ func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames .. return ToUnorderedStrings(optionIds) } +func (c *CustomFieldManager) GetMultiOptionIds(fieldName string, optionNames ...string) (*UnorderedStrings, error) { + var errs *multierror.Error + if len(optionNames) == 0 { + return c.NullMultiOption(), nil + } + var optionIds = []string{} + for _, optionName := range optionNames { + id, err := c.CustomFieldOptionId(fieldName, optionName) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + shouldAddOptionId := true + for _, optionId := range optionIds { + if id == optionId { // Don't add duplicate option ids + shouldAddOptionId = false + break + } + } + + if shouldAddOptionId { + optionIds = append(optionIds, id) + } + } + return ToUnorderedStrings(optionIds), errs.ErrorOrNil() +} + func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *UnorderedStrings) bool { if setOptionIds == nil { return false From bec3ff353b68bc4af76807c212b57c612e8446a8 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Fri, 27 Sep 2019 13:49:46 -0400 Subject: [PATCH 107/285] Add wifi profile field values (#152) --- entity_hotel.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index 1c29cc8..7d4c380 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -47,11 +47,11 @@ type HotelEntity struct { InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` - GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` // Media Logo **Photo `json:"logo,omitempty"` @@ -218,6 +218,8 @@ const ( OptionAirportShuttleAvailable = "AIRPORT_SHUTTLE_AVAILABLE" OptionBreakfastAvailable = "BREAKFAST_AVAILABLE" OptionBreakfastAvailableForFree = "BREAKFAST_AVAILABLE_FOR_FREE" + OptionWiFiAvailable = "WIFI_AVAILABLE" + OptionWiFiAvailableForFree = "WIFI_AVAILABLE_FOR_FREE" // Multi-option IDs OptionWiFiInPublicAreas = "WIFI_IN_PUBLIC_AREAS" From 5ba2b25d70f43bcd8b92674f09b95514e369ed30 Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Wed, 2 Oct 2019 13:41:13 -0400 Subject: [PATCH 108/285] Added Insurances Accepted field to healthcare facility (#153) --- entity_healthcare_facility.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 7fa4583..1a5a2a8 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -44,10 +44,11 @@ type HealthcareFacilityEntity struct { Products *[]string `json:"products,omitempty"` Services *[]string `json:"services,omitempty"` // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API - Specialties *[]string `json:"specialities,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Specialties *[]string `json:"specialities,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From 6780be3f705fb3a62d2e92c447010858220144f0 Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Tue, 8 Oct 2019 10:31:34 -0400 Subject: [PATCH 109/285] return diff on nil fields (#154) --- entity_diff.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/entity_diff.go b/entity_diff.go index 28d3d11..037c7c4 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -213,7 +213,10 @@ func RawEntityDiff(a map[string]interface{}, b map[string]interface{}, nilIsEmpt isDiff = true } } - } + } else { + delta[key] = bVal + isDiff = true + } } if isDiff { return delta, true From d1d6359428d086e6562765f5e31820c1d01e3606 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Tue, 8 Oct 2019 10:34:52 -0400 Subject: [PATCH 110/285] reorder attributes of location entity --- entity_location.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/entity_location.go b/entity_location.go index 4258d03..7ea76ca 100644 --- a/entity_location.go +++ b/entity_location.go @@ -15,12 +15,6 @@ const ENTITYTYPE_LOCATION EntityType = "location" // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm type LocationEntity struct { BaseEntity - - // Admin - CategoryIds *[]string `json:"categoryIds,omitempty"` - Closed **bool `json:"closed,omitempty"` - Keywords *[]string `json:"keywords,omitempty"` - // Address Fields Name *string `json:"name,omitempty"` Address *Address `json:"address,omitempty"` @@ -39,8 +33,9 @@ type LocationEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` Hours **Hours `json:"hours,omitempty"` + Closed **bool `json:"closed,omitempty"` + Description *string `json:"description,omitempty"` AdditionalHoursText *string `json:"additionalHoursText,omitempty"` YearEstablished **float64 `json:"yearEstablished,omitempty"` Associations *[]string `json:"associations,omitempty"` @@ -49,11 +44,16 @@ type LocationEntity struct { Products *[]string `json:"products,omitempty"` Services *[]string `json:"services,omitempty"` // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API - Specialties *[]string `json:"specialities,omitempty"` + Specialties *[]string `json:"specialities,omitempty"` + Languages *[]string `json:"languages,omitempty"` Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` + // Admin + Keywords *[]string `json:"keywords,omitempty"` + CategoryIds *[]string `json:"categoryIds,omitempty"` + // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` From 41ac027abdd83cf486f0b78566f6bbff7c9187d2 Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Wed, 9 Oct 2019 15:22:07 -0400 Subject: [PATCH 111/285] Add performers field to event entity (#148) --- entity_event.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_event.go b/entity_event.go index 9c70728..45ff443 100644 --- a/entity_event.go +++ b/entity_event.go @@ -41,6 +41,7 @@ type EventEntity struct { VenueName *string `json:"venueName,omitempty"` TicketAvailability *string `json:"ticketAvailability,omitempty"` TicketPriceRange *TicketPriceRange `json:"ticketPriceRange,omitempty"` + Performers *[]string `json:"performers,omitempty"` //Lats & Lngs DisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` From ff88d9e1b80827d88f28bff14fc6791e117ca77a Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Mon, 14 Oct 2019 22:33:14 -0400 Subject: [PATCH 112/285] hours: unspecified all week (#157) --- entity_location.go | 67 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/entity_location.go b/entity_location.go index 7ea76ca..ecc097c 100644 --- a/entity_location.go +++ b/entity_location.go @@ -201,17 +201,17 @@ type Photo struct { } type Image struct { - Url *string `json:"url,omitempty"` - AlternateText *string `json:"alternateText,omitempty"` - Width *int `json:"width,omitempty"` - Height *int `json:"height,omitempty"` - Thumbnails *[]Thumbnail `json:"thumnails,omitempty"` + Url *string `json:"url,omitempty"` + AlternateText *string `json:"alternateText,omitempty"` + Width *int `json:"width,omitempty"` + Height *int `json:"height,omitempty"` + Thumbnails *[]Thumbnail `json:"thumnails,omitempty"` } type Thumbnail struct { - Url *string `json:"url,omitempty"` - Width *int `json:"width,omitempty"` - Height *int `json:"height,omitempty"` + Url *string `json:"url,omitempty"` + Width *int `json:"width,omitempty"` + Height *int `json:"height,omitempty"` } func NullableImage(i *Image) **Image { @@ -327,6 +327,12 @@ func NewHoursClosedAllWeek() *Hours { return h } +func NewHoursUnspecifiedAllWeek() *Hours { + h := &Hours{} + h.SetUnspecifiedAllWeek() + return h +} + type DayHours struct { OpenIntervals *[]Interval `json:"openIntervals,omitempty"` IsClosed **bool `json:"isClosed,omitempty"` @@ -446,22 +452,53 @@ func (h *Hours) SetClosed(w Weekday) { } } +func (h *Hours) SetUnspecifiedAllWeek() { + h.SetUnspecified(Sunday) + h.SetUnspecified(Monday) + h.SetUnspecified(Tuesday) + h.SetUnspecified(Wednesday) + h.SetUnspecified(Thursday) + h.SetUnspecified(Friday) + h.SetUnspecified(Saturday) +} + func (h *Hours) SetUnspecified(w Weekday) { switch w { case Sunday: - h.Sunday = nil + h.Sunday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) case Monday: - h.Monday = nil + h.Monday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) case Tuesday: - h.Tuesday = nil + h.Tuesday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) case Wednesday: - h.Wednesday = nil + h.Wednesday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) case Thursday: - h.Thursday = nil + h.Thursday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) case Friday: - h.Friday = nil + h.Friday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) case Saturday: - h.Saturday = nil + h.Saturday = NullableDayHours(&DayHours{ + OpenIntervals: nil, + IsClosed: nil, + }) } } From 96b9590bef56338864f5c396a3bfb45f4e8929a1 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Mon, 21 Oct 2019 13:04:09 -0400 Subject: [PATCH 113/285] Adding more hotel profile fields (#156) * Adding more hotel profile fields --- entity_hotel.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index 7d4c380..611eb2b 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -110,13 +110,15 @@ type HotelEntity struct { ElectricChargingStation **Ternary `json:"electricChargingStation,omitempty"` // Policies - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - AllInclusive **string `json:"allInclusive,omitempty"` - PetsAllowed **string `json:"petsAllowed,omitempty"` - KidsStayFree **Ternary `json:"kidsStayFree,omitempty"` - SmokeFreeProperty **Ternary `json:"smokeFreeProperty,omitempty"` - CatsAllowed **Ternary `json:"catsAllowed,omitempty"` - DogsAllowed **Ternary `json:"dogsAllowed,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + AllInclusive **string `json:"allInclusive,omitempty"` + PetsAllowed **string `json:"petsAllowed,omitempty"` + KidsStayFree **Ternary `json:"kidsStayFree,omitempty"` + MaxAgeOfKidsStayFree **int `json:"maxAgeOfKidsStayFree,omitempty"` + MaxNumberOfKidsStayFree **int `json:"maxNumberOfKidsStayFree,omitempty"` + SmokeFreeProperty **Ternary `json:"smokeFreeProperty,omitempty"` + CatsAllowed **Ternary `json:"catsAllowed,omitempty"` + DogsAllowed **Ternary `json:"dogsAllowed,omitempty"` // Food and Drink RoomService **string `json:"roomService,omitempty"` @@ -190,6 +192,7 @@ type HotelEntity struct { // Accessibility WheelchairAccessible **Ternary `json:"wheelchairAccessible,omitempty"` + MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` } @@ -212,6 +215,8 @@ const ( OptionFitnessCenterAvailableForFree = "FITNESS_CENTER_AVAILABLE_FOR_FREE" OptionParkingAvailableForFree = "PARKING_AVAILABLE_FOR_FREE" OptionParkingAvailable = "PARKING_AVAILABLE" + OptionSelfParkingAvailableForFree = "SELF_PARKING_AVAILABLE_FOR_FREE" + OptionSelfParkingAvailable = "SELF_PARKING_AVAILABLE" OptionValetParkingAvailableForFree = "VALET_PARKING_AVAILABLE_FOR_FREE" OptionValetParkingAvailable = "VALET_PARKING_AVAILABLE" OptionAirportShuttleAvailableForFree = "AIRPORT_SHUTTLE_AVAILABLE_FOR_FREE" @@ -220,12 +225,20 @@ const ( OptionBreakfastAvailableForFree = "BREAKFAST_AVAILABLE_FOR_FREE" OptionWiFiAvailable = "WIFI_AVAILABLE" OptionWiFiAvailableForFree = "WIFI_AVAILABLE_FOR_FREE" + OptionBuffetBreakfast = "BUFFET_BREAKFAST" + OptionBuffetDinner = "BUFFET_DINNER" + OptionBuffet = "BUFFET" + OptionPrivateCarService = "PRIVATE_CAR_SERVICE" + OptionPrivateCarServiceForFree = "PRIVATE_CAR_SERVICE_FOR_FREE" + OptionWatercraftRentals = "WATERCRAFT_RENTALS" + OptionWatercraftRentalsForFree = "WATERCRAFT_RENTALS_FOR_FREE" // Multi-option IDs - OptionWiFiInPublicAreas = "WIFI_IN_PUBLIC_AREAS" - OptionAccessibleParking = "ACCESSIBLE_PARKING" - OptionAccessibleElevator = "ACCESSIBLE_ELEVATOR" - OptionAccessiblePool = "ACCESSIBLE_POOL" + OptionWiFiInPublicAreas = "WIFI_IN_PUBLIC_AREAS" + OptionPublicInternetTerminal = "PUBLIC_INTERNET_TERMINAL" + OptionAccessibleParking = "ACCESSIBLE_PARKING" + OptionAccessibleElevator = "ACCESSIBLE_ELEVATOR" + OptionAccessiblePool = "ACCESSIBLE_POOL" ) func (h *HotelEntity) UnmarshalJSON(data []byte) error { From d9806fe64c2b0df458e47062de2de0e035e02215 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Tue, 22 Oct 2019 11:16:15 -0400 Subject: [PATCH 114/285] Changing to single pointer strings (#158) --- entity_hotel.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index 611eb2b..4667a30 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -91,13 +91,13 @@ type HotelEntity struct { BeachFrontProperty **Ternary `json:"beachFrontProperty,omitempty"` // Services - ClassificationRating **int `json:"classificationRating,omitempty"` - FrontDesk **string `json:"frontDesk,omitempty"` - Laundry **string `json:"laundry,omitempty"` - Housekeeping **string `json:"housekeeping,omitempty"` - Parking **string `json:"parking,omitempty"` - SelfParking **string `json:"selfParking,omitempty"` - ValetParking **string `json:"valetParking,omitempty"` + ClassificationRating *string `json:"classificationRating,omitempty"` + FrontDesk *string `json:"frontDesk,omitempty"` + Laundry *string `json:"laundry,omitempty"` + Housekeeping *string `json:"housekeeping,omitempty"` + Parking *string `json:"parking,omitempty"` + SelfParking *string `json:"selfParking,omitempty"` + ValetParking *string `json:"valetParking,omitempty"` Concierge **Ternary `json:"concierge,omitempty"` Elevator **Ternary `json:"elevator,omitempty"` BaggageStorage **Ternary `json:"baggageStorage,omitempty"` @@ -111,8 +111,8 @@ type HotelEntity struct { // Policies PaymentOptions *[]string `json:"paymentOptions,omitempty"` - AllInclusive **string `json:"allInclusive,omitempty"` - PetsAllowed **string `json:"petsAllowed,omitempty"` + AllInclusive *string `json:"allInclusive,omitempty"` + PetsAllowed *string `json:"petsAllowed,omitempty"` KidsStayFree **Ternary `json:"kidsStayFree,omitempty"` MaxAgeOfKidsStayFree **int `json:"maxAgeOfKidsStayFree,omitempty"` MaxNumberOfKidsStayFree **int `json:"maxNumberOfKidsStayFree,omitempty"` @@ -121,9 +121,9 @@ type HotelEntity struct { DogsAllowed **Ternary `json:"dogsAllowed,omitempty"` // Food and Drink - RoomService **string `json:"roomService,omitempty"` + RoomService *string `json:"roomService,omitempty"` RestaurantCount **int `json:"restaurantCount,omitempty"` - Breakfast **string `json:"breakfast,omitempty"` + Breakfast *string `json:"breakfast,omitempty"` TableService **Ternary `json:"tableService,omitempty"` Bar **Ternary `json:"bar,omitempty"` VendingMachine **Ternary `json:"vendingMachine,omitempty"` @@ -143,7 +143,7 @@ type HotelEntity struct { LifeGuard **Ternary `json:"lifeguard,omitempty"` // Wellness - FitnessCenter **string `json:"fitnessCenter,omitempty"` + FitnessCenter *string `json:"fitnessCenter,omitempty"` EllipticalMachine **Ternary `json:"ellipticalMachine,omitempty"` Treadmill **Ternary `json:"treadmill,omitempty"` WeightMachine **Ternary `json:"weightMachine,omitempty"` @@ -155,8 +155,8 @@ type HotelEntity struct { DoctorOnCall **Ternary `json:"doctorOnCall,omitempty"` // Activities - Bicycles **string `json:"bicycles,omitempty"` - WaterCraft **string `json:"watercraft,omitempty"` + Bicycles *string `json:"bicycles,omitempty"` + WaterCraft *string `json:"watercraft,omitempty"` GameRoom **Ternary `json:"gameRoom,omitempty"` Nightclub **Ternary `json:"nightclub,omitempty"` Casino **Ternary `json:"casino,omitempty"` @@ -171,8 +171,8 @@ type HotelEntity struct { PrivateBeach **Ternary `json:"privateBeach,omitempty"` // Transportation - AirportShuttle **string `json:"airportShuttle,omitempty"` - PrivateCarService **string `json:"privateCarService,omitempty"` + AirportShuttle *string `json:"airportShuttle,omitempty"` + PrivateCarService *string `json:"privateCarService,omitempty"` AirportTransfer **Ternary `json:"airportTransfer,omitempty"` LocalShuttle **Ternary `json:"localShuttle,omitempty"` CarRental **Ternary `json:"carRental,omitempty"` @@ -183,7 +183,7 @@ type HotelEntity struct { KidsClub **Ternary `json:"kidsClub,omitempty"` // Connectivity - WiFiAvailable **string `json:"wifiAvailable,omitempty"` + WiFiAvailable *string `json:"wifiAvailable,omitempty"` WiFiDetails *UnorderedStrings `json:"wifiDetails,omitempty"` // Business From 9c6067cc412d2ebd4123bab7941b350cf15e3602 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Thu, 31 Oct 2019 00:35:37 -0400 Subject: [PATCH 115/285] added reviewLanguage and transactionId to reviews struct --- review.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/review.go b/review.go index c6d33da..12bf901 100644 --- a/review.go +++ b/review.go @@ -29,6 +29,8 @@ type Review struct { LabelIds *[]int `json:"labelIds"` ExternalId *string `json:"externalId"` ReviewLabels *[]ReviewLabel `json:"reviewLabels"` + ReviewLanguage *string `json:"reviewLanguage"` + TransactionId *string `json:"transactionId"` } type ReviewCreate struct { @@ -166,6 +168,20 @@ func (y Review) GetReviewLabels() (v []ReviewLabel) { return v } +func (y Review) GetReviewLanguage() string { + if y.ReviewLanguage != nil { + return *y.ReviewLanguage + } + return "" +} + +func (y Review) GetTransactionId() string { + if y.TransactionId != nil { + return *y.TransactionId + } + return "" +} + func (y Review) GetComments() (v []Comment) { if y.Comments != nil { v = *y.Comments From 10afc2bbf719d96c4abe3b587ee648e989650404 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Thu, 31 Oct 2019 16:17:01 -0400 Subject: [PATCH 116/285] added more fields to reviews struct --- review.go | 70 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/review.go b/review.go index 12bf901..5b26078 100644 --- a/review.go +++ b/review.go @@ -15,6 +15,7 @@ type Reviewer struct { type Review struct { Id *int `json:"id"` LocationId *string `json:"locationId"` + AccountId *string `json:"accountId"` PublisherId *string `json:"publisherId"` Rating *float64 `json:"rating"` Title *string `json:"title"` @@ -23,13 +24,16 @@ type Review struct { AuthorEmail *string `json:"authorEmail"` URL *string `json:"url"` PublisherDate *int `json:"publisherDate"` - LastYextUpdateDate *int `json:"lastYextUpdateDate"` + LastYextUpdateTime *int `json:"lastYextUpdateTime"` Status *string `json:"status"` + FlagStatus *string `json:"flagStatus"` + ReviewLanguage *string `json:"reviewLanguage"` Comments *[]Comment `json:"comments"` LabelIds *[]int `json:"labelIds"` ExternalId *string `json:"externalId"` ReviewLabels *[]ReviewLabel `json:"reviewLabels"` - ReviewLanguage *string `json:"reviewLanguage"` + ReviewType *string `json:"reviewType"` + Recommendation *string `json:"recommendation"` TransactionId *string `json:"transactionId"` } @@ -77,6 +81,13 @@ func (y Review) GetLocationId() string { return "" } +func (y Review) GetAccountId() string { + if y.AccountId != nil { + return *y.AccountId + } + return "" +} + func (y Review) GetPublisherId() string { if y.PublisherId != nil { return *y.PublisherId @@ -133,9 +144,9 @@ func (y Review) GetPublisherDate() int { return 0 } -func (y Review) GetLastYextUpdateDate() int { - if y.LastYextUpdateDate != nil { - return *y.LastYextUpdateDate +func (y Review) GetLastYextUpdateTime() int { + if y.LastYextUpdateTime != nil { + return *y.LastYextUpdateTime } return 0 } @@ -147,6 +158,27 @@ func (y Review) GetStatus() string { return "" } +func (y Review) GetFlagStatus() string { + if y.FlagStatus != nil { + return *y.FlagStatus + } + return "" +} + +func (y Review) GetReviewLanguage() string { + if y.ReviewLanguage != nil { + return *y.ReviewLanguage + } + return "" +} + +func (y Review) GetComments() (v []Comment) { + if y.Comments != nil { + v = *y.Comments + } + return v +} + func (y Review) GetLabelIds() (v []int) { if y.LabelIds != nil { v = *y.LabelIds @@ -168,25 +200,25 @@ func (y Review) GetReviewLabels() (v []ReviewLabel) { return v } -func (y Review) GetReviewLanguage() string { - if y.ReviewLanguage != nil { - return *y.ReviewLanguage - } - return "" +func (y Review) GetReviewType() string { + if y.ReviewType != nil { + return *y.ReviewType + } + return "" } -func (y Review) GetTransactionId() string { - if y.TransactionId != nil { - return *y.TransactionId - } - return "" +func (y Review) GetRecommendation() string { + if y.Recommendation != nil { + return *y.Recommendation + } + return "" } -func (y Review) GetComments() (v []Comment) { - if y.Comments != nil { - v = *y.Comments +func (y Review) GetTransactionId() string { + if y.TransactionId != nil { + return *y.TransactionId } - return v + return "" } func (y Comment) GetId() int { From dde8202bee9257a7cdee5ea7b7572a699d881f47 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Mon, 11 Nov 2019 17:43:14 -0500 Subject: [PATCH 117/285] Fixing bicycle values (#161) --- entity_hotel.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index 4667a30..4e39719 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -209,8 +209,8 @@ const ( OptionSelfServiceLaundry = "SELF_SERVICE" Option24HourRoomService = "ROOM_SERVICE_AVAILABLE_24_HOURS" OptionRoomServiceAvailable = "ROOM_SERVICE_AVAILABLE" - OptionBicyclesAvailable = "BICYCLES_AVAILABLE" - OptionBicyclesAvailableForFree = "BICYCLES_AVAILABLE_FOR_FREE" + OptionBicycleRentals = "BICYCLE_RENTALS" + OptionBicycleRentalsForFree = "BICYCLE_RENTALS_FOR_FREE" OptionFitnessCenterAvailable = "FITNESS_CENTER_AVAILABLE" OptionFitnessCenterAvailableForFree = "FITNESS_CENTER_AVAILABLE_FOR_FREE" OptionParkingAvailableForFree = "PARKING_AVAILABLE_FOR_FREE" From 5d75f4d47c4f229235e975e6190cc176d6764287 Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Tue, 12 Nov 2019 11:45:06 -0500 Subject: [PATCH 118/285] Updates alongside account syncer implementation (#162) --- customfield_service.go | 63 +++++++++++++++++++++++++----------------- entity_location.go | 5 ++++ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/customfield_service.go b/customfield_service.go index b13003f..fa2b8f9 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -6,7 +6,7 @@ import ( "reflect" "strings" - multierror "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" ) const customFieldPath = "customfields" @@ -84,6 +84,19 @@ func (s *CustomFieldService) Edit(cf *CustomField) (*Response, error) { return s.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s", customFieldPath, cf.GetId()), asMap, nil) } +func (s *CustomFieldService) Delete(cf *CustomField) (*Response, error) { + asJSON, err := json.Marshal(cf) + if err != nil { + return nil, err + } + var asMap map[string]interface{} + err = json.Unmarshal(asJSON, &asMap) + if err != nil { + return nil, err + } + return s.client.DoRequestJSON("DELETE", fmt.Sprintf("%s/%s", customFieldPath, cf.GetId()), asMap, nil) +} + type CustomFieldManager struct { CustomFields []*CustomField } @@ -212,30 +225,30 @@ func (c *CustomFieldManager) MustMultiOptionIds(fieldName string, optionNames .. } func (c *CustomFieldManager) GetMultiOptionIds(fieldName string, optionNames ...string) (*UnorderedStrings, error) { - var errs *multierror.Error - if len(optionNames) == 0 { - return c.NullMultiOption(), nil - } - var optionIds = []string{} - for _, optionName := range optionNames { - id, err := c.CustomFieldOptionId(fieldName, optionName) - if err != nil { - errs = multierror.Append(errs, err) - continue - } - shouldAddOptionId := true - for _, optionId := range optionIds { - if id == optionId { // Don't add duplicate option ids - shouldAddOptionId = false - break - } - } - - if shouldAddOptionId { - optionIds = append(optionIds, id) - } - } - return ToUnorderedStrings(optionIds), errs.ErrorOrNil() + var errs *multierror.Error + if len(optionNames) == 0 { + return c.NullMultiOption(), nil + } + var optionIds = []string{} + for _, optionName := range optionNames { + id, err := c.CustomFieldOptionId(fieldName, optionName) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + shouldAddOptionId := true + for _, optionId := range optionIds { + if id == optionId { // Don't add duplicate option ids + shouldAddOptionId = false + break + } + } + + if shouldAddOptionId { + optionIds = append(optionIds, id) + } + } + return ToUnorderedStrings(optionIds), errs.ErrorOrNil() } func (c *CustomFieldManager) MustIsMultiOptionSet(fieldName string, optionName string, setOptionIds *UnorderedStrings) bool { diff --git a/entity_location.go b/entity_location.go index ecc097c..6a6a79f 100644 --- a/entity_location.go +++ b/entity_location.go @@ -268,6 +268,11 @@ type Website struct { PreferDisplayUrl **bool `json:"preferDisplayUrl,omitempty"` } +func (y Photo) String() string { + b, _ := json.Marshal(y) + return string(b) +} + func NullableWebsite(w *Website) **Website { return &w } From 5c9cd62547659f53230f72adbaa6df22b663efeb Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 13 Nov 2019 12:36:38 -0500 Subject: [PATCH 119/285] RawEntityClient (#163) --- client.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client.go b/client.go index 1bfeb85..9d73e8b 100644 --- a/client.go +++ b/client.go @@ -68,6 +68,14 @@ func NewClient(config *Config) *Client { return c } +// Default Client but with empty entity registries so that all entities are treated as Raw Entities +func NewRawEntityClient(config *Config) *Client { + c := NewClient(config) + c.EntityService.Registry = &EntityRegistry{} + c.LanguageProfileService.registry = &EntityRegistry{} + return c +} + // TODO: The account (e.g. /v2/account/me/locations) vs raw (e.g. /v2/categories) // URL distiction is present in the API. We currently have the NewXXX and DoXXX // helpers split as well, but that probably isn't necessary. We could have the From 6584331c29f6f30aa7b483f99f21ccc1091877a7 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 20 Nov 2019 11:34:07 -0500 Subject: [PATCH 120/285] customfield create: preserve id (#164) --- customfield_service.go | 1 - 1 file changed, 1 deletion(-) diff --git a/customfield_service.go b/customfield_service.go index fa2b8f9..114e2da 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -65,7 +65,6 @@ func (s *CustomFieldService) Create(cf *CustomField) (*Response, error) { if err != nil { return nil, err } - delete(asMap, "id") return s.client.DoRequestJSON("POST", customFieldPath, asMap, nil) } From fa6f110f2a7363bcb72268d94ffbf2965a011464 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Tue, 3 Dec 2019 13:08:03 -0500 Subject: [PATCH 121/285] Adding linked location field to events (#165) --- entity_event.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/entity_event.go b/entity_event.go index 45ff443..b73e241 100644 --- a/entity_event.go +++ b/entity_event.go @@ -13,10 +13,11 @@ type EventEntity struct { CategoryIds *UnorderedStrings `json:"categoryIds,omitempty"` // Address Fields - Name *string `json:"name,omitempty"` - Address *Address `json:"address,omitempty"` - AddressHidden *bool `json:"addressHidden,omitempty"` - ISORegionCode *string `json:"isoRegionCode,omitempty"` + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden *bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + LinkedLocation *string `json:"linkedLocation,omitempty"` // Other Contact Info MainPhone *string `json:"mainPhone,omitempty"` From bfa996197cc512ccfe191e5fc367200454bfa948 Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Thu, 5 Dec 2019 12:02:22 -0500 Subject: [PATCH 122/285] add GetUnorderedStrings helper (#167) --- type.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/type.go b/type.go index 97a4412..4e25d86 100644 --- a/type.go +++ b/type.go @@ -275,6 +275,13 @@ func ToUnorderedStrings(v []string) *UnorderedStrings { return &u } +func GetUnorderedStrings(v *UnorderedStrings) []string { + if v == nil { + return []string{} + } + return *v +} + func NullableUnorderedStrings(v []string) *UnorderedStrings { u := UnorderedStrings(v) return &u From 3939888b4eb723b6550b447ba46c69a5321e2200 Mon Sep 17 00:00:00 2001 From: etseff <35544133+etseff@users.noreply.github.com> Date: Tue, 10 Dec 2019 14:08:15 -0500 Subject: [PATCH 123/285] Add invitationId to Review struct (#169) --- review.go | 1 + 1 file changed, 1 insertion(+) diff --git a/review.go b/review.go index 5b26078..e48d51f 100644 --- a/review.go +++ b/review.go @@ -35,6 +35,7 @@ type Review struct { ReviewType *string `json:"reviewType"` Recommendation *string `json:"recommendation"` TransactionId *string `json:"transactionId"` + InvitationId *string `json:"invitationId"` } type ReviewCreate struct { From 7b08de6d252c8bb16cf2e91a04470800ad897ecc Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Mon, 16 Dec 2019 11:51:09 -0500 Subject: [PATCH 124/285] Improve error messaging for ETLs J=PC-65697 (#170) --- entity_registry.go | 1 + entity_unmarshal.go | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/entity_registry.go b/entity_registry.go index 7c08c9e..255ab9b 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -31,6 +31,7 @@ func (r *EntityRegistry) InitializeEntity(t EntityType) (Entity, error) { if err != nil { return nil, err } + return i.(Entity), nil } diff --git a/entity_unmarshal.go b/entity_unmarshal.go index 05dad9c..b0b09aa 100644 --- a/entity_unmarshal.go +++ b/entity_unmarshal.go @@ -2,6 +2,7 @@ package yext import ( "encoding/json" + "log" "reflect" "strings" ) @@ -28,6 +29,13 @@ func unmarshal(i interface{}, m map[string]interface{}) interface{} { t = t.Elem() } typedNil := reflect.New(t) + + defer func() { + if r := recover(); r != nil { + log.Fatalf("Error while unmarshaling field '%s': %s", jsonTagToKey[tag], r) + } + }() + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) } } else if vMap, ok := val.(map[string]interface{}); ok { From a529c709b6c275286d47fc53681e53b4103669e8 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Mon, 16 Dec 2019 16:15:35 -0500 Subject: [PATCH 125/285] Adding timezone field (#171) --- entity_location.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/entity_location.go b/entity_location.go index 6a6a79f..4973be3 100644 --- a/entity_location.go +++ b/entity_location.go @@ -105,6 +105,8 @@ type LocationEntity struct { // Reviews ReviewGenerationUrl *string `json:"reviewGenerationUrl,omitempty"` FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` + + TimeZoneUtcOffset *string `json:"timeZoneUtcOffset,omitempty"` } func (l *LocationEntity) UnmarshalJSON(data []byte) error { From 8caeee3c0774aedcd03b2556ac92ebeed07afdc4 Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Tue, 17 Dec 2019 15:03:41 -0500 Subject: [PATCH 126/285] add ReservationUrl to Healthcare Professional entity (#173) J=none TEST=manual tested in Heartland Dental ETL --- entity_healthcare_professional.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 6cf6f53..7642493 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -87,6 +87,7 @@ type HealthcareProfessionalEntity struct { // Urls WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -325,6 +326,22 @@ func (y HealthcareProfessionalEntity) GetDisplayWebsiteUrl() string { return "" } +func (y HealthcareProfessionalEntity) GetReservationUrl() string { + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y HealthcareProfessionalEntity) GetDisplayReservationUrl() string { + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + func (y HealthcareProfessionalEntity) GetHours() *Hours { return GetHours(y.Hours) } From 34f0219bad72e23a2c245577a2e381401b7f674f Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Fri, 20 Dec 2019 16:36:32 -0500 Subject: [PATCH 127/285] Create helper function for converting string slice to EntityType slice (#174) --- entity.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/entity.go b/entity.go index 60b4209..68b2963 100644 --- a/entity.go +++ b/entity.go @@ -156,3 +156,12 @@ func ConvertToRawEntity(e Entity) (*RawEntity, error) { } return &raw, nil } + +func ConvertStringsToEntityTypes(types []string) []EntityType { + entityTypes := []EntityType{} + for _, stringType := range types { + entityTypes = append(entityTypes, EntityType(stringType)) + } + + return entityTypes +} From 73a926bfe59e634599ada58ec8eed81e748af64e Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Tue, 7 Jan 2020 15:01:43 -0500 Subject: [PATCH 128/285] raw entity: get and set helpers --- client.go | 6 +-- entity.go | 105 +++++++++++++++++++++++++++++++++++------------- entity_hotel.go | 4 ++ entity_test.go | 93 ++++++++++++++++++++++++++++++++++++++++++ type.go | 7 ++++ 5 files changed, 182 insertions(+), 33 deletions(-) diff --git a/client.go b/client.go index 9d73e8b..3962ecb 100644 --- a/client.go +++ b/client.go @@ -208,10 +208,6 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { // Rehydrate the request body since it might have been drained by the previous attempt req.Body = ioutil.NopCloser(bytes.NewBuffer(originalRequestBody)) - if c.Config.Logger != nil { - c.Config.Logger.Log(fmt.Printf("%+v", req)) - } - resp, err := c.Config.HTTPClient.Do(req) if err != nil { resultError = err @@ -244,7 +240,7 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { hitRateLimit = true if rateLimitWait > 0 { if c.Config.Logger != nil { - c.Config.Logger.Log(fmt.Sprintf("rate limit hit, waiting for %d seconds", rateLimitWait)) + c.Config.Logger.Log(fmt.Sprintf("rate limit hit, waiting for %d minutes", rateLimitWait/60)) } c.Config.Clock.Sleep(time.Duration(rateLimitWait+1) * time.Second) } diff --git a/entity.go b/entity.go index 68b2963..bf42f5c 100644 --- a/entity.go +++ b/entity.go @@ -101,47 +101,41 @@ func (b *BaseEntity) SetNilIsEmpty(val bool) { type RawEntity map[string]interface{} func (r *RawEntity) GetEntityId() string { - if m, ok := (*r)["meta"]; ok { - meta := m.(map[string]interface{}) - if id, ok := meta["id"]; ok { - return id.(string) - } + v := r.GetValue([]string{"meta", "id"}) + if v == nil { + return "" } - return "" + return v.(string) } func (r *RawEntity) GetEntityType() EntityType { - if m, ok := (*r)["meta"]; ok { - meta := m.(map[string]interface{}) - if t, ok := meta["entityType"]; ok { - if s, isString := t.(string); isString { - return EntityType(s) - } else if _, isEntityType := t.(EntityType); isEntityType { - return t.(EntityType) - } - } + v := r.GetValue([]string{"meta", "entityType"}) + if v == nil { + return "" + } + if _, ok := v.(string); ok { + return EntityType(v.(string)) + } + if _, ok := v.(EntityType); ok { + return v.(EntityType) } - return EntityType("") + return "" } func (r *RawEntity) GetLanguage() string { - if m, ok := (*r)["meta"]; ok { - meta := m.(map[string]interface{}) - if l, ok := meta["language"]; ok { - return l.(string) - } + v := r.GetValue([]string{"meta", "language"}) + if v == nil { + return "" } - return "" + return v.(string) } func (r *RawEntity) GetAccountId() string { - if m, ok := (*r)["meta"]; ok { - meta := m.(map[string]interface{}) - if a, ok := meta["accountId"]; ok { - return a.(string) - } + v := r.GetValue([]string{"meta", "accountId"}) + if v == nil { + return "" } - return "" + return v.(string) } func ConvertToRawEntity(e Entity) (*RawEntity, error) { @@ -157,6 +151,61 @@ func ConvertToRawEntity(e Entity) (*RawEntity, error) { return &raw, nil } +func (r *RawEntity) GetValue(keys []string) interface{} { + if len(keys) == 0 { + return nil + } + m := (map[string]interface{})(*r) + return getValue(m, keys, 0) +} + +func getValue(m map[string]interface{}, keys []string, index int) interface{} { + for k, v := range m { + if k == keys[index] { + if index == len(keys)-1 { + return v + } else { + return getValue((v).(map[string]interface{}), keys, index+1) + } + } + } + return nil +} + +func (r *RawEntity) SetValue(keys []string, val interface{}) error { + if len(keys) == 0 { + return nil + } + m := (map[string]interface{})(*r) + v, err := setValue(m, keys, 0, val) + if err != nil { + return err + } + raw := RawEntity(v) + r = &raw + return nil +} + +func setValue(m map[string]interface{}, keys []string, index int, val interface{}) (map[string]interface{}, error) { + if m == nil { + return nil, nil + } + if index == len(keys)-1 { + m[keys[index]] = val + } else { + if _, ok := m[keys[index]]; !ok { + m[keys[index]] = map[string]interface{}{} + } + v, err := setValue(m[keys[index]].(map[string]interface{}), keys, index+1, val) + if err != nil { + return v, err + } + m[keys[index]] = v + } + + return m, nil +} + func ConvertStringsToEntityTypes(types []string) []EntityType { entityTypes := []EntityType{} for _, stringType := range types { diff --git a/entity_hotel.go b/entity_hotel.go index 4e39719..90a30d1 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -338,6 +338,10 @@ func (y HotelEntity) GetPostalCode() string { return "" } +func (y HotelEntity) GetMainPhone() string { + return GetString(y.MainPhone) +} + func (y HotelEntity) GetFeaturedMessage() string { f := GetFeaturedMessage(y.FeaturedMessage) if f != nil { diff --git a/entity_test.go b/entity_test.go index 74910a9..78bf429 100644 --- a/entity_test.go +++ b/entity_test.go @@ -538,3 +538,96 @@ var sampleEntityJSON = `{ "entityType": "location" } }` + +func TestGetValue(t *testing.T) { + var tests = []struct { + Raw *RawEntity + Keys []string + Want interface{} + }{ + { + Raw: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + }, + }, + Keys: []string{"meta", "entityType"}, + Want: "location", + }, + { + Raw: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + }, + }, + Keys: []string{"meta", "id"}, + Want: nil, + }, + } + + for _, test := range tests { + got := test.Raw.GetValue(test.Keys) + if got != test.Want { + t.Errorf("Got: %v, Wanted: %v", got, test.Want) + } + } +} + +func TestSetValue(t *testing.T) { + var tests = []struct { + Raw *RawEntity + Keys []string + Value interface{} + Want *RawEntity + }{ + { + Raw: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + }, + }, + Keys: []string{"meta", "entityType"}, + Value: "hotel", + Want: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "hotel", + }, + }, + }, + { + Raw: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + }, + }, + Keys: []string{"meta", "id"}, + Value: "1234", + Want: &RawEntity{ + "meta": map[string]interface{}{ + "entityType": "location", + "id": "1234", + }, + }, + }, + { + Raw: &RawEntity{}, + Keys: []string{"meta", "id"}, + Value: "1234", + Want: &RawEntity{ + "meta": map[string]interface{}{ + "id": "1234", + }, + }, + }, + } + + for _, test := range tests { + err := test.Raw.SetValue(test.Keys, test.Value) + if err != nil { + t.Errorf("Got err: %s", err) + } + if delta, isDiff := RawEntityDiff(*test.Raw, *test.Want, false, false); isDiff { + t.Errorf("Got: %v, Wanted: %v, Delta: %v", test.Raw, test.Want, delta) + } + } +} diff --git a/type.go b/type.go index 4e25d86..6cd5a79 100644 --- a/type.go +++ b/type.go @@ -315,3 +315,10 @@ func NullTernary() **Ternary { var v *Ternary return &v } + +func NullableTernaryFromBool(b bool) **Ternary { + if b { + return NullableTernary(Yes) + } + return NullableTernary(No) +} From 3c0a763c017285d232164b7d4170e864f702ef3c Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 8 Jan 2020 13:02:08 -0500 Subject: [PATCH 129/285] Remove WheelchairAccessbile (same as MobilityAccessible) --- entity_hotel.go | 1 - 1 file changed, 1 deletion(-) diff --git a/entity_hotel.go b/entity_hotel.go index 90a30d1..3f947fb 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -191,7 +191,6 @@ type HotelEntity struct { BusinessCenter **Ternary `json:"businessCenter,omitempty"` // Accessibility - WheelchairAccessible **Ternary `json:"wheelchairAccessible,omitempty"` MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` } From c393182246288fc9f0d059f41a47aa8094444a1a Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 8 Jan 2020 13:42:25 -0500 Subject: [PATCH 130/285] hotel: add all inclusive options --- entity_hotel.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/entity_hotel.go b/entity_hotel.go index 3f947fb..587d66a 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -231,6 +231,8 @@ const ( OptionPrivateCarServiceForFree = "PRIVATE_CAR_SERVICE_FOR_FREE" OptionWatercraftRentals = "WATERCRAFT_RENTALS" OptionWatercraftRentalsForFree = "WATERCRAFT_RENTALS_FOR_FREE" + OptionAllInclusiveRatesAvailable = "ALL_INCLUSIVE_RATES_AVAILABLE" + OptionAllInclusiveRatesOnly = "ALL_INCLUSIVE_RATES_ONLY" // Multi-option IDs OptionWiFiInPublicAreas = "WIFI_IN_PUBLIC_AREAS" From 320d33a704dc12587baae7dbeae4702bba414868 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Tue, 14 Jan 2020 13:52:35 -0500 Subject: [PATCH 131/285] Delete entity ID before upsert (#178) --- language_profile_service.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/language_profile_service.go b/language_profile_service.go index c4bff9f..0c8ba05 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -1,6 +1,7 @@ package yext import ( + "encoding/json" "fmt" ) @@ -144,7 +145,18 @@ func (l *LanguageProfileService) listAllHelper(opts *EntityListOptions) (*Langua } func (l *LanguageProfileService) Upsert(entity Entity, id string, languageCode string) (*Response, error) { - r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), entity, nil) + asJSON, err := json.Marshal(entity) + if err != nil { + return nil, err + } + var asMap map[string]interface{} + err = json.Unmarshal(asJSON, &asMap) + if err != nil { + return nil, err + } + delete(asMap["meta"].(map[string]interface{}), "id") + + r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), asMap, nil) if err != nil { return r, err } From 745ccf37afb21419fbdf92a828f8abe48f817725 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 16 Jan 2020 16:48:49 -0500 Subject: [PATCH 132/285] language profile service: handle nil meta --- language_profile_service.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/language_profile_service.go b/language_profile_service.go index 0c8ba05..f9bd48e 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -154,7 +154,9 @@ func (l *LanguageProfileService) Upsert(entity Entity, id string, languageCode s if err != nil { return nil, err } - delete(asMap["meta"].(map[string]interface{}), "id") + if _, ok := asMap["meta"]; ok { + delete(asMap["meta"].(map[string]interface{}), "id") + } r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), asMap, nil) if err != nil { From 825b6b442569d355fe6694d31f57d65b693c8a7b Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Wed, 22 Jan 2020 10:09:13 -0500 Subject: [PATCH 133/285] update yext menu fields (#180) --- list.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/list.go b/list.go index f94550e..7b67ded 100644 --- a/list.go +++ b/list.go @@ -230,9 +230,11 @@ type MenuListSection struct { type Menu struct { ListItem - Cost *Cost `json:"cost,omitempty"` - Photo *ListPhoto `json:"photo,omitempty"` - Calories *Calories `json:"calories,omitempty"` + Cost *Cost `json:"cost,omitempty"` + Photo *ListPhoto `json:"photo,omitempty"` + Calories *Calories `json:"calories,omitempty"` + Url *string `json:"url,omitempty"` + Allergens *[]string `json:"allergens,omitempty"` } type BioList struct { From 932decbd7a436d5dc2f1df2e533bb4316bd0e845 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Wed, 29 Jan 2020 11:36:57 -0500 Subject: [PATCH 134/285] hours: rename to location_hours to signify that is legacy format (#181) --- location_diff.go | 4 ++-- hours.go => location_hours.go | 26 ++++++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) rename hours.go => location_hours.go (92%) diff --git a/location_diff.go b/location_diff.go index f43a11b..5f06e50 100644 --- a/location_diff.go +++ b/location_diff.go @@ -164,8 +164,8 @@ func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { } var closedHoursEquivalents = map[string]struct{}{ - "": struct{}{}, - HoursClosedAllWeek: struct{}{}, + "": struct{}{}, + LocationHoursClosedAllWeek: struct{}{}, } func HoursAreEquivalent(a, b string) bool { diff --git a/hours.go b/location_hours.go similarity index 92% rename from hours.go rename to location_hours.go index a24e9a5..871ceee 100644 --- a/hours.go +++ b/location_hours.go @@ -7,12 +7,14 @@ import ( "time" ) +// Legacy hours format used for Yext v2 locations API + const ( - HoursClosedAllWeek = "1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed" - HoursOpen24Hours = "00:00:00:00" - HoursClosed = "closed" - HoursFormat = "15:04" - hoursLen = 11 // XX:XX:XX:XX format + LocationHoursClosedAllWeek = "1:closed,2:closed,3:closed,4:closed,5:closed,6:closed,7:closed" + LocationHoursOpen24Hours = "00:00:00:00" + LocationHoursClosed = "closed" + HoursFormat = "15:04" + hoursLen = 11 // XX:XX:XX:XX format ) type Weekday int @@ -132,7 +134,7 @@ func (h *LocationHoursHelper) AppendHours(weekday Weekday, hours string) { } func (h *LocationHoursHelper) SetClosed(weekday Weekday) { - h.SetHours(weekday, []string{HoursClosed}) + h.SetHours(weekday, []string{LocationHoursClosed}) } func (h *LocationHoursHelper) SetUnspecified(weekday Weekday) { @@ -140,7 +142,7 @@ func (h *LocationHoursHelper) SetUnspecified(weekday Weekday) { } func (h *LocationHoursHelper) SetOpen24Hours(weekday Weekday) { - h.SetHours(weekday, []string{HoursOpen24Hours}) + h.SetHours(weekday, []string{LocationHoursOpen24Hours}) } func (h *LocationHoursHelper) GetHours(weekday Weekday) []string { @@ -185,7 +187,7 @@ func (h *LocationHoursHelper) StringSerializeDay(weekday Weekday) string { } var hoursStrings = []string{} if h.GetHours(weekday) == nil || len(h.GetHours(weekday)) == 0 || h.HoursAreClosed(weekday) { - return fmt.Sprintf("%d:%s", weekday, HoursClosed) + return fmt.Sprintf("%d:%s", weekday, LocationHoursClosed) } for _, hours := range h.GetHours(weekday) { if len(hours) != hoursLen { @@ -285,8 +287,8 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { if err != nil { return -1, "", fmt.Errorf("Error parsing weekday hours from string; unable to convert index to num: %s", err) } - if strings.ToLower(hoursParts[1]) == HoursClosed { - return Weekday(weekdayInt), HoursClosed, nil + if strings.ToLower(hoursParts[1]) == LocationHoursClosed { + return Weekday(weekdayInt), LocationHoursClosed, nil } // Pad hours with leading 0s for i := 1; i < len(hoursParts); i += 2 { @@ -337,12 +339,12 @@ func (h *LocationHoursHelper) HoursAreUnspecified(weekday Weekday) bool { func (h *LocationHoursHelper) HoursAreClosed(weekday Weekday) bool { var hours = h.GetHours(weekday) - return hours != nil && len(hours) == 1 && hours[0] == HoursClosed + return hours != nil && len(hours) == 1 && hours[0] == LocationHoursClosed } func (h *LocationHoursHelper) HoursAreOpen24Hours(weekday Weekday) bool { var hours = h.GetHours(weekday) - return hours != nil && len(hours) == 1 && hours[0] == HoursOpen24Hours + return hours != nil && len(hours) == 1 && hours[0] == LocationHoursOpen24Hours } func ParseAndFormatHours(tFormat string, openHours string, closeHours string) (string, error) { From 41ba4050fa0a56ea405ae087d06c7f17824aa72e Mon Sep 17 00:00:00 2001 From: nyellowhair Date: Fri, 31 Jan 2020 16:26:32 -0500 Subject: [PATCH 135/285] changing calorie type to *int cases where calorie was intentionally set to 0 were being omitted --- list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/list.go b/list.go index 7b67ded..44f552d 100644 --- a/list.go +++ b/list.go @@ -161,7 +161,7 @@ type CostOptions struct { type Calories struct { Type string `json:"type,omitempty"` - Calorie int `json:"calorie,omitempty"` + Calorie *int `json:"calorie,omitempty"` RangeTo int `json:"rangeTo,omitempty"` } From 8dd92f70ffedfcc3b6c073d3ea25964d81c60a04 Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Mon, 10 Feb 2020 15:59:40 -0500 Subject: [PATCH 136/285] add job entity (#182) --- entity_job.go | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 entity_job.go diff --git a/entity_job.go b/entity_job.go new file mode 100644 index 0000000..d45a054 --- /dev/null +++ b/entity_job.go @@ -0,0 +1,92 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_JOB EntityType = "job" + +type JobEntity struct { + BaseEntity + + //Job Info + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Timezone *string `json:"timezone,omitempty"` + EmploymentType *string `json:"employmentType,omitempty"` + DatePosted *string `json:"datePosted,omitempty"` + ValidThrough *string `json:"validThrough,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + + // Urls + ApplicationURL *string `json:"applicationURL,omitempty"` + LandingPageURL *string `json:"landingPageUrl,omitempty"` +} + +func (j JobEntity) GetId() string { + if j.BaseEntity.Meta != nil && j.BaseEntity.Meta.Id != nil { + return *j.BaseEntity.Meta.Id + } + return "" +} + +func (j JobEntity) GetName() string { + if j.Name != nil { + return *j.Name + } + return "" +} + +func (j JobEntity) GetDescription() string { + if j.Description != nil { + return *j.Description + } + return "" +} + +func (j JobEntity) GetTimezone() string { + if j.Timezone != nil { + return *j.Timezone + } + return "" +} + +func (j JobEntity) GetEmploymentType() string { + if j.EmploymentType != nil { + return *j.EmploymentType + } + return "" +} + +func (j JobEntity) GetDatePosted() string { + if j.DatePosted != nil { + return *j.DatePosted + } + return "" +} + +func (j JobEntity) GetExpiryDate() string { + if j.ValidThrough != nil { + return *j.ValidThrough + } + return "" +} + +func (j JobEntity) GetApplicationURL() string { + if j.ApplicationURL != nil { + return *j.ApplicationURL + } + return "" +} + +func (j JobEntity) GetLandingPageUrl() string { + if j.LandingPageURL != nil { + return *j.LandingPageURL + } + return "" +} + +func (j *JobEntity) String() string { + b, _ := json.Marshal(j) + return string(b) +} From c4a2913da9379b35728191f8b85d950e97f9f081 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Wed, 19 Feb 2020 14:53:31 -0500 Subject: [PATCH 137/285] Adding Facebook cover & profile fields (#183) --- entity_hotel.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/entity_hotel.go b/entity_hotel.go index 587d66a..1d6cd5b 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -47,6 +47,9 @@ type HotelEntity struct { InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` From f418b8a1bca9ddc1b9c0373070ac9a90f5fc3124 Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Thu, 27 Feb 2020 12:57:34 -0500 Subject: [PATCH 138/285] Add Geomodifier to Restaurant Entity Definition (#184) --- entity_restaurant.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/entity_restaurant.go b/entity_restaurant.go index eeb5c90..b681e57 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -39,6 +39,7 @@ type RestaurantEntity struct { Languages *[]string `json:"languages,omitempty"` Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -404,6 +405,13 @@ func (r RestaurantEntity) GetPaymentOptions() (v []string) { return v } +func (r RestaurantEntity) GetGeomodifier() string { + if r.Geomodifier != nil { + return GetString(r.Geomodifier) + } + return "" +} + func (r RestaurantEntity) GetVideos() (v []Video) { if r.Videos != nil { v = *r.Videos From c2890bae460854feb8a8308feae44be6daeae365 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Thu, 5 Mar 2020 12:02:22 -0500 Subject: [PATCH 139/285] add facebookcta field and constants --- entity_location.go | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/entity_location.go b/entity_location.go index 4973be3..0fb2e0c 100644 --- a/entity_location.go +++ b/entity_location.go @@ -9,7 +9,21 @@ import ( "encoding/json" ) -const ENTITYTYPE_LOCATION EntityType = "location" +const ( + ENTITYTYPE_LOCATION EntityType = "location" + FACEBOOKCTA_TYPE_BOOKNOW = "BOOK_NOW" + FACEBOOKCTA_TYPE_CALLNOW = "CALL_NOW" + FACEBOOKCTA_TYPE_CONTACTUS = "CONTACT_US" + FACEBOOKCTA_TYPE_LEARNMORE = "LEARN_MORE" + FACEBOOKCTA_TYPE_OFF = "NONE" + FACEBOOKCTA_TYPE_PLAYGAME = "PLAY_GAME" + FACEBOOKCTA_TYPE_SENDEMAIL = "SEND_EMAIL" + FACEBOOKCTA_TYPE_SENDMESSAGE = "SEND_MESSAGE" + FACEBOOKCTA_TYPE_SHOPNOW = "SHOP_NOW" + FACEBOOKCTA_TYPE_SIGNUP = "SIGN_UP" + FACEBOOKCTA_TYPE_USEAPP = "USE_APP" + FACEBOOKCTA_TYPE_WATCHVIDEO = "WATCH_VIDEO" +) // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm @@ -85,9 +99,10 @@ type LocationEntity struct { UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` // Social Media - FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` - FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` - FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + FacebookCallToAction **FacebookCTA `json:"facebookCallToAction,omitempty"` + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` @@ -122,6 +137,20 @@ func (l *LocationEntity) UnmarshalJSON(data []byte) error { return UnmarshalEntityJSON(l, data) } +type FacebookCTA struct { + Type *string `json:"type,omitempty"` + Value *string `json:"value,omitempty"` +} + +func NullableFacebookCTA(f *FacebookCTA) **FacebookCTA { + return &f +} + +func NullFacebookCTA() **FacebookCTA { + var f *FacebookCTA + return &f +} + type Video struct { VideoUrl VideoUrl `json:"video,omitempty"` Description string `json:"description,omitempty"` From 1f224020d0cbe17b3702f9e8faf5b12c7a30e256 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Mon, 9 Mar 2020 14:59:22 -0400 Subject: [PATCH 140/285] -J=PC-63853 changes to entity_registry and unmarshal needed for hybrid history mode --- entity_registry.go | 11 +++++++++-- entity_unmarshal.go | 22 ++++++++++++---------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/entity_registry.go b/entity_registry.go index 255ab9b..28775cf 100644 --- a/entity_registry.go +++ b/entity_registry.go @@ -61,8 +61,15 @@ func (r *EntityRegistry) ToEntityType(entity interface{}) (Entity, error) { return nil, fmt.Errorf("Unable to find entityType attribute in %v\nFor Entity: %v", metaByKey, entity) } - var registry = Registry(*r) - entityObj, err := registry.Initialize(entityType.(string)) + return r.ToSpecifiedEntityType(entity, EntityType(entityType.(string))) +} + +func (r *EntityRegistry) ToSpecifiedEntityType(entity interface{}, entityType EntityType) (Entity, error) { + var ( + entityValsByKey = entity.(map[string]interface{}) + registry = Registry(*r) + ) + entityObj, err := registry.Initialize(string(entityType)) if err != nil { // Unable to create an instace of entityType, use RawEntity instead entityObj = &RawEntity{} diff --git a/entity_unmarshal.go b/entity_unmarshal.go index b0b09aa..57f3cb0 100644 --- a/entity_unmarshal.go +++ b/entity_unmarshal.go @@ -25,18 +25,20 @@ func unmarshal(i interface{}, m map[string]interface{}) interface{} { // Check if double pointer if v.Type().Kind() == reflect.Ptr { t := v.Type() - for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { - t = t.Elem() - } - typedNil := reflect.New(t) - - defer func() { - if r := recover(); r != nil { - log.Fatalf("Error while unmarshaling field '%s': %s", jsonTagToKey[tag], r) + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + t = t.Elem() } - }() + typedNil := reflect.New(t) - Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) + defer func() { + if r := recover(); r != nil { + log.Fatalf("Error while unmarshaling field '%s': %s", jsonTagToKey[tag], r) + } + }() + + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) + } } } else if vMap, ok := val.(map[string]interface{}); ok { v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) From 52495f83518dd0886bb1f9f4c935f838f28b4152 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Fri, 20 Mar 2020 12:29:05 -0500 Subject: [PATCH 141/285] fix unnecessary double ifs --- entity_unmarshal.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/entity_unmarshal.go b/entity_unmarshal.go index 57f3cb0..e8c4539 100644 --- a/entity_unmarshal.go +++ b/entity_unmarshal.go @@ -23,22 +23,20 @@ func unmarshal(i interface{}, m map[string]interface{}) interface{} { v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) // Check if double pointer - if v.Type().Kind() == reflect.Ptr { - t := v.Type() - if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { - for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { - t = t.Elem() - } - typedNil := reflect.New(t) + t := v.Type() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + for t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Ptr { + t = t.Elem() + } + typedNil := reflect.New(t) - defer func() { - if r := recover(); r != nil { - log.Fatalf("Error while unmarshaling field '%s': %s", jsonTagToKey[tag], r) - } - }() + defer func() { + if r := recover(); r != nil { + log.Fatalf("Error while unmarshaling field '%s': %s", jsonTagToKey[tag], r) + } + }() - Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) - } + Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]).Set(reflect.ValueOf(typedNil.Interface())) } } else if vMap, ok := val.(map[string]interface{}); ok { v := Indirect(reflect.ValueOf(i)).FieldByName(jsonTagToKey[tag]) From a4ebb0f09baa6d0c941bc82d35271d76d783992f Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Mon, 23 Mar 2020 12:40:03 -0400 Subject: [PATCH 142/285] prevent hours parsing panic and return error (#190) --- location_hours.go | 9 ++++----- hours_test.go => location_hours_test.go | 0 2 files changed, 4 insertions(+), 5 deletions(-) rename hours_test.go => location_hours_test.go (100%) diff --git a/location_hours.go b/location_hours.go index 871ceee..16862fb 100644 --- a/location_hours.go +++ b/location_hours.go @@ -280,7 +280,8 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { return -1, "", fmt.Errorf("Error parsing weekday and hours from string: string has 0 length") } hoursParts := strings.Split(str, ":") - if len(hoursParts) == 0 { + + if len(hoursParts) != 5 && len(hoursParts) != 2{ return -1, "", fmt.Errorf("Error parsing weekday and hours from string: string in unexpectd format") } weekdayInt, err := strconv.Atoi(hoursParts[0]) @@ -291,10 +292,8 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { return Weekday(weekdayInt), LocationHoursClosed, nil } // Pad hours with leading 0s - for i := 1; i < len(hoursParts); i += 2 { - if len(hoursParts[i])+len(hoursParts[i+1]) != 4 { - hoursParts[i] = "0" + hoursParts[i] - } + for i, _ := range hoursParts{ + hoursParts[i] = fmt.Sprintf("%02s",hoursParts[i]) } hours := strings.Join(hoursParts[1:], ":") return Weekday(weekdayInt), hours, nil diff --git a/hours_test.go b/location_hours_test.go similarity index 100% rename from hours_test.go rename to location_hours_test.go From a99fefe6dd280be4153d12e0c954d65f3ab89ea2 Mon Sep 17 00:00:00 2001 From: Tyler TerBush Date: Mon, 23 Mar 2020 15:28:53 -0400 Subject: [PATCH 143/285] analytics_service: Always return the response and error (#191) TEST=manual --- analytics_service.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/analytics_service.go b/analytics_service.go index c2e74b2..768e655 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -61,9 +61,5 @@ type AnalyticsReportResponse struct { func (a *AnalyticsService) Create(req *AnalyticsReportRequest) (*AnalyticsReportResponse, *Response, error) { arr := &AnalyticsReportResponse{} r, err := a.client.DoRequestJSON("POST", analyticsPath, req, arr) - if err != nil { - return nil, r, err - } - - return arr, r, nil + return arr, r, err } From 54fa1ab2a8ef174922ff0ce07b00668e63d0d189 Mon Sep 17 00:00:00 2001 From: Tyler Robinson Date: Thu, 26 Mar 2020 16:29:11 -0400 Subject: [PATCH 144/285] Add definition for FAQ entity type --- entity_faq.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 entity_faq.go diff --git a/entity_faq.go b/entity_faq.go new file mode 100644 index 0000000..e1f191e --- /dev/null +++ b/entity_faq.go @@ -0,0 +1,63 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_FAQ EntityType = "faq" + +// FAQ is the representation of a FAQ in Yext Location Manager. +type FAQEntity struct { + BaseEntity + + Name *string `json:"name,omitempty"` + Answer *string `json:"answer,omitempty"` + LandingPageUrl *string `json:"landingPageUrl,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` +} + +func (y FAQEntity) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y FAQEntity) GetAnswer() string { + if y.Answer != nil { + return GetString(y.Answer) + } + return "" +} + +func (y FAQEntity) GetLandingPageUrl() string { + if y.LandingPageUrl != nil { + return GetString(y.LandingPageUrl) + } + return "" +} + +func (y FAQEntity) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (l *FAQEntity) UnmarshalJSON(data []byte) error { + type Alias FAQEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(l), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(l, data) +} + +func (j *FAQEntity) String() string { + b, _ := json.Marshal(j) + return string(b) +} From f0ab5c5fc967dc8439f86e45e8700d3e0a5ce761 Mon Sep 17 00:00:00 2001 From: Parshwa Shah Date: Fri, 10 Apr 2020 18:34:28 -0400 Subject: [PATCH 145/285] add reopen date to hours (#192) --- entity_diff.go | 6 +++--- entity_location.go | 1 + location.go | 24 ++++++++++++------------ location_hours.go | 6 +++--- type.go | 8 ++++---- user.go | 18 +++++++++--------- user_service.go | 12 ++++++------ 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/entity_diff.go b/entity_diff.go index 037c7c4..ba08dbb 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -214,9 +214,9 @@ func RawEntityDiff(a map[string]interface{}, b map[string]interface{}, nilIsEmpt } } } else { - delta[key] = bVal - isDiff = true - } + delta[key] = bVal + isDiff = true + } } if isDiff { return delta, true diff --git a/entity_location.go b/entity_location.go index 0fb2e0c..d718999 100644 --- a/entity_location.go +++ b/entity_location.go @@ -350,6 +350,7 @@ type Hours struct { Saturday **DayHours `json:"saturday,omitempty"` Sunday **DayHours `json:"sunday,omitempty"` HolidayHours *[]HolidayHours `json:"holidayHours,omitempty"` + ReopenDate *string `json:"reopenDate,omitempty"` } func (h Hours) String() string { diff --git a/location.go b/location.go index 59f8e72..2aa2172 100644 --- a/location.go +++ b/location.go @@ -92,20 +92,20 @@ type Location struct { PaymentOptions *[]string `json:"paymentOptions,omitempty"` // Lats & Lngs - DisplayLat *float64 `json:"displayLat,omitempty"` - DisplayLng *float64 `json:"displayLng,omitempty"` - DropoffLat *float64 `json:"dropoffLat,omitempty"` - DropoffLng *float64 `json:"dropoffLng,omitempty"` - WalkableLat *float64 `json:"walkableLat,omitempty"` - WalkableLng *float64 `json:"walkableLng,omitempty"` - RoutableLat *float64 `json:"routableLat,omitempty"` - RoutableLng *float64 `json:"routableLng,omitempty"` - PickupLat *float64 `json:"pickupLat,omitempty"` - PickupLng *float64 `json:"pickupLng,omitempty"` + DisplayLat *float64 `json:"displayLat,omitempty"` + DisplayLng *float64 `json:"displayLng,omitempty"` + DropoffLat *float64 `json:"dropoffLat,omitempty"` + DropoffLng *float64 `json:"dropoffLng,omitempty"` + WalkableLat *float64 `json:"walkableLat,omitempty"` + WalkableLng *float64 `json:"walkableLng,omitempty"` + RoutableLat *float64 `json:"routableLat,omitempty"` + RoutableLng *float64 `json:"routableLng,omitempty"` + PickupLat *float64 `json:"pickupLat,omitempty"` + PickupLng *float64 `json:"pickupLng,omitempty"` // Yext Lat & Lngs - NOTE: DO NOT SET THESE, they are auto-generated by Yext - YextDisplayLat *float64 `json:"yextDisplayLat,omitempty"` - YextDisplayLng *float64 `json:"yextDisplayLng,omitempty"` + YextDisplayLat *float64 `json:"yextDisplayLat,omitempty"` + YextDisplayLng *float64 `json:"yextDisplayLng,omitempty"` // ECLS BioListIds *[]string `json:"bioListIds,omitempty"` diff --git a/location_hours.go b/location_hours.go index 16862fb..4ee4f31 100644 --- a/location_hours.go +++ b/location_hours.go @@ -281,7 +281,7 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { } hoursParts := strings.Split(str, ":") - if len(hoursParts) != 5 && len(hoursParts) != 2{ + if len(hoursParts) != 5 && len(hoursParts) != 2 { return -1, "", fmt.Errorf("Error parsing weekday and hours from string: string in unexpectd format") } weekdayInt, err := strconv.Atoi(hoursParts[0]) @@ -292,8 +292,8 @@ func parseWeekdayAndHoursFromString(str string) (Weekday, string, error) { return Weekday(weekdayInt), LocationHoursClosed, nil } // Pad hours with leading 0s - for i, _ := range hoursParts{ - hoursParts[i] = fmt.Sprintf("%02s",hoursParts[i]) + for i, _ := range hoursParts { + hoursParts[i] = fmt.Sprintf("%02s", hoursParts[i]) } hours := strings.Join(hoursParts[1:], ":") return Weekday(weekdayInt), hours, nil diff --git a/type.go b/type.go index 6cd5a79..d4da950 100644 --- a/type.go +++ b/type.go @@ -276,10 +276,10 @@ func ToUnorderedStrings(v []string) *UnorderedStrings { } func GetUnorderedStrings(v *UnorderedStrings) []string { - if v == nil { - return []string{} - } - return *v + if v == nil { + return []string{} + } + return *v } func NullableUnorderedStrings(v []string) *UnorderedStrings { diff --git a/user.go b/user.go index 652be53..dee3b16 100644 --- a/user.go +++ b/user.go @@ -5,15 +5,15 @@ import ( ) type User struct { - Id *string `json:"id,omitempty"` // req in post - FirstName *string `json:"firstName,omitempty"` // req in post - LastName *string `json:"lastName,omitempty"` // req in post - UserName *string `json:"username,omitempty"` - EmailAddress *string `json:"emailAddress,omitempty"` // req in post - PhoneNumber *string `json:"phoneNumber,omitempty"` - Password *string `json:"password,omitempty"` - SSO *bool `json:"sso,omitempty"` - ACLs []ACL `json:"acl,omitempty"` + Id *string `json:"id,omitempty"` // req in post + FirstName *string `json:"firstName,omitempty"` // req in post + LastName *string `json:"lastName,omitempty"` // req in post + UserName *string `json:"username,omitempty"` + EmailAddress *string `json:"emailAddress,omitempty"` // req in post + PhoneNumber *string `json:"phoneNumber,omitempty"` + Password *string `json:"password,omitempty"` + SSO *bool `json:"sso,omitempty"` + ACLs []ACL `json:"acl,omitempty"` LastLoginDate *string `json:"lastLoginDate,omitempty"` } diff --git a/user_service.go b/user_service.go index 0d23d46..670097d 100644 --- a/user_service.go +++ b/user_service.go @@ -121,10 +121,10 @@ func (u *UserService) NewLocationACL(l *Location, r Role) ACL { } func (u *UserService) NewEntityACL(e Entity, r Role) ACL { - return ACL{ - Role: r, - On: e.GetEntityId(), - AccountId: u.client.Config.AccountId, - AccessOn: ACCESS_LOCATION, - } + return ACL{ + Role: r, + On: e.GetEntityId(), + AccountId: u.client.Config.AccountId, + AccessOn: ACCESS_LOCATION, + } } From 4006991e6f713c40ce82f6933bb16bd8aa399477 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Fri, 17 Apr 2020 14:22:00 -0400 Subject: [PATCH 146/285] Add format param --- entity_service.go | 4 ++++ language_profile_service.go | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/entity_service.go b/entity_service.go index 46bdf06..c0a1a94 100644 --- a/entity_service.go +++ b/entity_service.go @@ -30,6 +30,7 @@ type EntityListOptions struct { type EntityServiceOptions struct { TemplateId string `json:"templateId,omitempty"` TemplateFields []string `json:"templateFields,omitempty"` + Format string `json:"format,omitempty"` } type EntityListResponse struct { @@ -141,6 +142,9 @@ func addEntityServiceOptions(requrl string, opts *EntityServiceOptions) (string, if opts.TemplateId != "" { q.Add("templateId", opts.TemplateId) } + if opts.Format != "" { + q.Add("format", opts.Format) + } if len(opts.TemplateFields) > 0 { q.Add("templateFields", strings.Join(opts.TemplateFields, ",")) } diff --git a/language_profile_service.go b/language_profile_service.go index f9bd48e..0b28986 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -145,6 +145,10 @@ func (l *LanguageProfileService) listAllHelper(opts *EntityListOptions) (*Langua } func (l *LanguageProfileService) Upsert(entity Entity, id string, languageCode string) (*Response, error) { + return l.UpsertWithOptions(entity, id, languageCode, nil) +} + +func (l *LanguageProfileService) UpsertWithOptions(entity Entity, id string, languageCode string, opts *EntityServiceOptions) (*Response, error) { asJSON, err := json.Marshal(entity) if err != nil { return nil, err @@ -158,7 +162,13 @@ func (l *LanguageProfileService) Upsert(entity Entity, id string, languageCode s delete(asMap["meta"].(map[string]interface{}), "id") } - r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode), asMap, nil) + requrl := fmt.Sprintf("%s/%s/%s", entityProfilesPath, id, languageCode) + requrl, err = addEntityServiceOptions(requrl, opts) + if err != nil { + return nil, err + } + + r, err := l.client.DoRequestJSON("PUT", requrl, asMap, nil) if err != nil { return r, err } From 4e22daa9dcab2f507be3c3a8fd8d53334c8ed287 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Wed, 6 May 2020 13:53:09 -0400 Subject: [PATCH 147/285] adding fields to analytics request (#194) --- analytics_data.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/analytics_data.go b/analytics_data.go index e58dc5b..25d0647 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -53,6 +53,10 @@ type AnalyticsData struct { IstShareOfIntelligentSearch *float64 `json:"Ist Share Of Intelligent Search"` LocationId *string `json:"location_id"` Month *string `json:"month"` + ResponseTime *int `json:"Response Time (Hours)"` + ResponseRate *int `json:"Response Rate"` + PartnerSite *string `json:"site"` + CumulativeRating *float64 `json:"Cumulative Rating"` } func (y AnalyticsData) GetProfileViews() int { From 7d01629692673abf708d6cd0a9c21dd07727df29 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Wed, 6 May 2020 17:06:42 -0400 Subject: [PATCH 148/285] Analytics fields (#195) * adding fields to analytics request * Adding EntityType to analytics filter --- analytics_service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/analytics_service.go b/analytics_service.go index 768e655..d890a4d 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -10,6 +10,7 @@ type AnalyticsFilters struct { StartDate *string `json:"startDate"` EndDate *string `json:"endDate"` LocationIds *[]string `json:"locationIds"` + EntityTypes *[]string `json:"entityType"` FolderId *int `json:"folderId"` Countries *[]string `json:"countries"` LocationLabels *[]string `json:"locationLabels"` From 61b3abfe7547bb6aade5d40a53a2f1b97cf2ab17 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Wed, 6 May 2020 17:08:41 -0500 Subject: [PATCH 149/285] add education type constants for healthcareprofessionals --- entity_healthcare_professional.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 7642493..d2d57ec 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -11,6 +11,10 @@ import ( ) const ( + EDUCATION_FELLOWSHIP = "FELLOWSHIP" + EDUCATION_INTERNSHIP = "INTERNSHIP" + EDUCATION_MEDICAL_SCHOOL = "MEDICAL_SCHOOL" + EDUCATION_RESIDENCY = "RESIDENCY" ENTITYTYPE_HEALTHCAREPROFESSIONAL EntityType = "healthcareProfessional" GENDER_MALE = "MALE" GENDER_FEMALE = "FEMALE" From 92ac743c9427eaa20b10f15947c986ff97835ecb Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Mon, 8 Jun 2020 10:43:36 -0400 Subject: [PATCH 150/285] customfields: adding validation field (#197) validation field allows for rich text formatting options to be unmarshalled by api request. Necessary for enhancing custom field create functionality in ETLs. J=PC-78670 TEST=manual --- customfield.go | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/customfield.go b/customfield.go index a65bc35..09b459b 100644 --- a/customfield.go +++ b/customfield.go @@ -16,17 +16,35 @@ const ( CUSTOMFIELDTYPE_HOURS = "HOURS" CUSTOMFIELDTYPE_DAILYTIMES = "DAILY_TIMES" CUSTOMFIELDTYPE_ENTITYLIST = "ENTITY_LIST" + CUSTOMFIELDTYPE_RICHTEXT = "RICH_TEXT" ) +type CustomFieldValidation struct { + MinCharLength int `json:"minCharLength,omitempty"` + MaxCharLength int `json:"maxCharLength,omitempty"` + MinItemCount int `json:"minItemCount,omitempty"` + MaxItemCount int `json:"maxItemCount,omitempty"` + MinValue int `json:"minValue,omitempty"` + MaxValue int `json:"maxValue,omitempty"` + MinDate string `json:"minDate,omitempty"` + MaxDate string `json:"maxDate,omitempty"` + AspectRatio string `json:"aspectRatio,omitempty"` + MinWidth int `json:"minWidth,omitempty"` + MinHeight int `json:"minHeight,omitempty"` + EntityTypes []string `json:"entityTypes,omitempty"` + RichTextFormats []string `json:"richTextFormats,omitempty"` +} + type CustomField struct { - Id *string `json:"id,omitempty"` - Type string `json:"type"` - Name string `json:"name"` - Options []CustomFieldOption `json:"options,omitempty"` // Only present for option custom fields - Group string `json:"group"` - Description string `json:"description"` - AlternateLanguageBehaviour string `json:"alternateLanguageBehavior"` - EntityAvailability []EntityType `json:"entityAvailability"` + Id *string `json:"id,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + Options []CustomFieldOption `json:"options,omitempty"` // Only present for option custom fields + Group string `json:"group"` + Description string `json:"description"` + AlternateLanguageBehaviour string `json:"alternateLanguageBehavior"` + EntityAvailability []EntityType `json:"entityAvailability"` + Validation *CustomFieldValidation `json:"validation,omitempty"` // Needed for rich text formatting } func (c CustomField) GetId() string { From 68baaf16baf745c8570cf28dfb53bbf201660eef Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Fri, 12 Jun 2020 14:19:02 -0400 Subject: [PATCH 151/285] customfield.go : update custom field validation min/max val to float64 (#199) --- customfield.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customfield.go b/customfield.go index 09b459b..9252b36 100644 --- a/customfield.go +++ b/customfield.go @@ -24,8 +24,8 @@ type CustomFieldValidation struct { MaxCharLength int `json:"maxCharLength,omitempty"` MinItemCount int `json:"minItemCount,omitempty"` MaxItemCount int `json:"maxItemCount,omitempty"` - MinValue int `json:"minValue,omitempty"` - MaxValue int `json:"maxValue,omitempty"` + MinValue float64 `json:"minValue,omitempty"` + MaxValue float64 `json:"maxValue,omitempty"` MinDate string `json:"minDate,omitempty"` MaxDate string `json:"maxDate,omitempty"` AspectRatio string `json:"aspectRatio,omitempty"` From 6477c9c08003ed12b2bf91935d9386cd3156aa03 Mon Sep 17 00:00:00 2001 From: npatel Date: Wed, 5 Aug 2020 16:18:59 -0400 Subject: [PATCH 152/285] feat(healthcare): add telehealth url field to providers and facilities J=PC-81848 --- entity_healthcare_facility.go | 9 +++++++++ entity_healthcare_professional.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 1a5a2a8..f94d76c 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -73,6 +73,7 @@ type HealthcareFacilityEntity struct { MenuUrl **Website `json:"menuUrl,omitempty"` OrderUrl **Website `json:"orderUrl,omitempty"` ReservationUrl **Website `json:"reservationUrl,omitempty"` + TelehealthUrl **Website `json:"telehealthUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` @@ -286,6 +287,14 @@ func (y HealthcareFacilityEntity) GetReservationUrl() string { return "" } +func (y HealthcareFacilityEntity) GetTelehealthUrl() string { + w := GetWebsite(y.TelehealthUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + func (y HealthcareFacilityEntity) GetHours() *Hours { return GetHours(y.Hours) } diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index d2d57ec..fddd3df 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -92,6 +92,7 @@ type HealthcareProfessionalEntity struct { WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` ReservationUrl **Website `json:"reservationUrl,omitempty"` + TelehealthUrl **Website `json:"telehealthUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -338,6 +339,14 @@ func (y HealthcareProfessionalEntity) GetReservationUrl() string { return "" } +func (y HealthcareProfessionalEntity) GetTelehealthUrl() string { + w := GetWebsite(y.TelehealthUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + func (y HealthcareProfessionalEntity) GetDisplayReservationUrl() string { w := GetWebsite(y.ReservationUrl) if w != nil { From 364d21ad9588ac15d569301652cd98e42db166b1 Mon Sep 17 00:00:00 2001 From: npatel Date: Wed, 5 Aug 2020 16:20:19 -0400 Subject: [PATCH 153/285] feat(hotel): add indoor and outdoor pool fields to hotel entities J=PC-81624 --- entity_hotel.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/entity_hotel.go b/entity_hotel.go index 1d6cd5b..afe8597 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -134,7 +134,9 @@ type HotelEntity struct { // Pools IndoorPoolCount **int `json:"indoorPoolCount,omitempty"` + IndoorPools **Ternary `json:"indoorPools,omitempty"` OutdoorPoolCount **int `json:"outdoorPoolCount,omitempty"` + OutdoorPools **Ternary `json:"outdoorPools,omitempty"` HotTub **Ternary `json:"hotTub,omitempty"` WaterSlide **Ternary `json:"waterslide,omitempty"` LazyRiver **Ternary `json:"lazyRiver,omitempty"` From 22f540b042ed727621c03f61a8ac1de8ec8ac561 Mon Sep 17 00:00:00 2001 From: npatel Date: Wed, 5 Aug 2020 16:37:41 -0400 Subject: [PATCH 154/285] feat(healthcare): add getter for display url for telehealth url field --- entity_healthcare_facility.go | 8 ++++++++ entity_healthcare_professional.go | 12 ++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index f94d76c..a73e3a4 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -295,6 +295,14 @@ func (y HealthcareFacilityEntity) GetTelehealthUrl() string { return "" } +func (y HealthcareFacilityEntity) GetDisplayTelehealthUrl() string { + w := GetWebsite(y.TelehealthUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + func (y HealthcareFacilityEntity) GetHours() *Hours { return GetHours(y.Hours) } diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index fddd3df..29b608d 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -339,6 +339,14 @@ func (y HealthcareProfessionalEntity) GetReservationUrl() string { return "" } +func (y HealthcareProfessionalEntity) GetDisplayReservationUrl() string { + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + func (y HealthcareProfessionalEntity) GetTelehealthUrl() string { w := GetWebsite(y.TelehealthUrl) if w != nil { @@ -347,8 +355,8 @@ func (y HealthcareProfessionalEntity) GetTelehealthUrl() string { return "" } -func (y HealthcareProfessionalEntity) GetDisplayReservationUrl() string { - w := GetWebsite(y.ReservationUrl) +func (y HealthcareProfessionalEntity) GetDisplayTelehealthUrl() string { + w := GetWebsite(y.TelehealthUrl) if w != nil { return GetString(w.DisplayUrl) } From 4aadec803e38d9f777a2304bee3d773f0006f921 Mon Sep 17 00:00:00 2001 From: npatel Date: Wed, 5 Aug 2020 19:21:00 -0400 Subject: [PATCH 155/285] fix(healthcare): fix telehealth type definition --- entity_healthcare_facility.go | 18 +----------------- entity_healthcare_professional.go | 18 +----------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index a73e3a4..0b0e03a 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -73,7 +73,7 @@ type HealthcareFacilityEntity struct { MenuUrl **Website `json:"menuUrl,omitempty"` OrderUrl **Website `json:"orderUrl,omitempty"` ReservationUrl **Website `json:"reservationUrl,omitempty"` - TelehealthUrl **Website `json:"telehealthUrl,omitempty"` + TelehealthUrl *string `json:"telehealthUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` @@ -287,22 +287,6 @@ func (y HealthcareFacilityEntity) GetReservationUrl() string { return "" } -func (y HealthcareFacilityEntity) GetTelehealthUrl() string { - w := GetWebsite(y.TelehealthUrl) - if w != nil { - return GetString(w.Url) - } - return "" -} - -func (y HealthcareFacilityEntity) GetDisplayTelehealthUrl() string { - w := GetWebsite(y.TelehealthUrl) - if w != nil { - return GetString(w.DisplayUrl) - } - return "" -} - func (y HealthcareFacilityEntity) GetHours() *Hours { return GetHours(y.Hours) } diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 29b608d..6056b23 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -92,7 +92,7 @@ type HealthcareProfessionalEntity struct { WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` ReservationUrl **Website `json:"reservationUrl,omitempty"` - TelehealthUrl **Website `json:"telehealthUrl,omitempty"` + TelehealthUrl *string `json:"telehealthUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -347,22 +347,6 @@ func (y HealthcareProfessionalEntity) GetDisplayReservationUrl() string { return "" } -func (y HealthcareProfessionalEntity) GetTelehealthUrl() string { - w := GetWebsite(y.TelehealthUrl) - if w != nil { - return GetString(w.Url) - } - return "" -} - -func (y HealthcareProfessionalEntity) GetDisplayTelehealthUrl() string { - w := GetWebsite(y.TelehealthUrl) - if w != nil { - return GetString(w.DisplayUrl) - } - return "" -} - func (y HealthcareProfessionalEntity) GetHours() *Hours { return GetHours(y.Hours) } From f0b7598f2c4215bbf164226d6a94ecd727c3d8ac Mon Sep 17 00:00:00 2001 From: Stephanie Harvey Date: Fri, 14 Aug 2020 14:55:10 -0400 Subject: [PATCH 156/285] entity location: add 'black owned business' and 'pickup and delivery service' profile field --- entity_location.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/entity_location.go b/entity_location.go index d718999..a95ab4d 100644 --- a/entity_location.go +++ b/entity_location.go @@ -30,11 +30,12 @@ const ( type LocationEntity struct { BaseEntity // Address Fields - Name *string `json:"name,omitempty"` - Address *Address `json:"address,omitempty"` - AddressHidden **bool `json:"addressHidden,omitempty"` - ISORegionCode *string `json:"isoRegionCode,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + BlackOwnedBusiness **bool `json:"blackOwnedBusiness,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -47,16 +48,17 @@ type LocationEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Hours **Hours `json:"hours,omitempty"` - Closed **bool `json:"closed,omitempty"` - Description *string `json:"description,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Certifications *[]string `json:"certifications,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Products *[]string `json:"products,omitempty"` - Services *[]string `json:"services,omitempty"` + Hours **Hours `json:"hours,omitempty"` + Closed **bool `json:"closed,omitempty"` + Description *string `json:"description,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + PickupAndDeliveryServices *UnorderedStrings `json:"pickupAndDeliveryServices,omitempty"` // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API Specialties *[]string `json:"specialities,omitempty"` From 77b9fc65d524d58677778dbe09d693c716ea052d Mon Sep 17 00:00:00 2001 From: npatel Date: Mon, 17 Aug 2020 15:47:09 -0400 Subject: [PATCH 157/285] feat(jobs): add Location profile field to job struct TEST=manual --- entity_job.go | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/entity_job.go b/entity_job.go index d45a054..2566409 100644 --- a/entity_job.go +++ b/entity_job.go @@ -10,19 +10,25 @@ type JobEntity struct { BaseEntity //Job Info - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Timezone *string `json:"timezone,omitempty"` - EmploymentType *string `json:"employmentType,omitempty"` - DatePosted *string `json:"datePosted,omitempty"` - ValidThrough *string `json:"validThrough,omitempty"` - Keywords *[]string `json:"keywords,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Timezone *string `json:"timezone,omitempty"` + EmploymentType *string `json:"employmentType,omitempty"` + DatePosted *string `json:"datePosted,omitempty"` + ValidThrough *string `json:"validThrough,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + Location *JobLocation `json:"location,omitempty"` // Urls ApplicationURL *string `json:"applicationURL,omitempty"` LandingPageURL *string `json:"landingPageUrl,omitempty"` } +type JobLocation struct { + ExistingLocation *string `json:"existingLocation,omitempty"` + ExternalLocation *string `json:"externalLocation,omitempty"` +} + func (j JobEntity) GetId() string { if j.BaseEntity.Meta != nil && j.BaseEntity.Meta.Id != nil { return *j.BaseEntity.Meta.Id @@ -86,6 +92,20 @@ func (j JobEntity) GetLandingPageUrl() string { return "" } +func (j JobEntity) GetExistingLocation() string { + if j.Location != nil && j.Location.ExistingLocation != nil { + return *j.Location.ExistingLocation + } + return "" +} + +func (j JobEntity) GetExternalLocation() string { + if j.Location != nil && j.Location.ExternalLocation != nil { + return *j.Location.ExternalLocation + } + return "" +} + func (j *JobEntity) String() string { b, _ := json.Marshal(j) return string(b) From 996299edebba3f66d1ef5af67b5d2f6a32840990 Mon Sep 17 00:00:00 2001 From: npatel Date: Tue, 18 Aug 2020 17:48:21 -0400 Subject: [PATCH 158/285] fix(jobs): fix api name for application url TEST=manual --- entity_job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entity_job.go b/entity_job.go index 2566409..6272469 100644 --- a/entity_job.go +++ b/entity_job.go @@ -20,7 +20,7 @@ type JobEntity struct { Location *JobLocation `json:"location,omitempty"` // Urls - ApplicationURL *string `json:"applicationURL,omitempty"` + ApplicationURL *string `json:"applicationUrl,omitempty"` LandingPageURL *string `json:"landingPageUrl,omitempty"` } From 8cc4799de82a7b43e83829f088c7b8a514129400 Mon Sep 17 00:00:00 2001 From: Will Enright Date: Tue, 22 Sep 2020 14:12:41 -0400 Subject: [PATCH 159/285] feat(entity): add service area places (#205) --- entity_location.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/entity_location.go b/entity_location.go index a95ab4d..25b5e58 100644 --- a/entity_location.go +++ b/entity_location.go @@ -30,12 +30,13 @@ const ( type LocationEntity struct { BaseEntity // Address Fields - Name *string `json:"name,omitempty"` - Address *Address `json:"address,omitempty"` - AddressHidden **bool `json:"addressHidden,omitempty"` - ISORegionCode *string `json:"isoRegionCode,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` - BlackOwnedBusiness **bool `json:"blackOwnedBusiness,omitempty"` + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + ServiceAreaPlaces *[]ServiceAreaPlace `json:"serviceAreaPlaces,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + BlackOwnedBusiness **bool `json:"blackOwnedBusiness,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -1000,3 +1001,8 @@ func (y HolidayHours) String() string { func ToHolidayHours(y []HolidayHours) *[]HolidayHours { return &y } + +type ServiceAreaPlace struct { + Name *string `json:"name"` + Type *string `json:"type"` +} From b0c69b52379f5573afbe7216c75d745fbb7d67ff Mon Sep 17 00:00:00 2001 From: Daniel Wu Date: Thu, 5 Nov 2020 17:21:07 -0500 Subject: [PATCH 160/285] feat(hotel): added pools to hotel entities (#206) J=PC-108774 --- entity_hotel.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_hotel.go b/entity_hotel.go index afe8597..83d0ad8 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -137,6 +137,7 @@ type HotelEntity struct { IndoorPools **Ternary `json:"indoorPools,omitempty"` OutdoorPoolCount **int `json:"outdoorPoolCount,omitempty"` OutdoorPools **Ternary `json:"outdoorPools,omitempty"` + Pools **Ternary `json:"pools,omitempty"` HotTub **Ternary `json:"hotTub,omitempty"` WaterSlide **Ternary `json:"waterslide,omitempty"` LazyRiver **Ternary `json:"lazyRiver,omitempty"` From eacd1b4040fbe0e0d411cc85640e030f921920b3 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Mon, 14 Dec 2020 11:02:19 -0700 Subject: [PATCH 161/285] adding 'accepting new patients' and 'covid 19 info url' (#207) --- entity_healthcare_facility.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 0b0e03a..1e5ee24 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -44,11 +44,12 @@ type HealthcareFacilityEntity struct { Products *[]string `json:"products,omitempty"` Services *[]string `json:"services,omitempty"` // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API - Specialties *[]string `json:"specialities,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` + Specialties *[]string `json:"specialities,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` + AcceptingNewPatients **bool `json:"acceptingNewPatients,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -70,12 +71,13 @@ type HealthcareFacilityEntity struct { ProductLists **Lists `json:"productLists,omitempty"` // Urls - MenuUrl **Website `json:"menuUrl,omitempty"` - OrderUrl **Website `json:"orderUrl,omitempty"` - ReservationUrl **Website `json:"reservationUrl,omitempty"` - TelehealthUrl *string `json:"telehealthUrl,omitempty"` - WebsiteUrl **Website `json:"websiteUrl,omitempty"` - FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + MenuUrl **Website `json:"menuUrl,omitempty"` + OrderUrl **Website `json:"orderUrl,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` + TelehealthUrl *string `json:"telehealthUrl,omitempty"` + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + COVID19InformationUrl *string `json:"covid19InformationUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -488,6 +490,10 @@ func (y HealthcareFacilityEntity) GetPaymentOptions() (v []string) { return v } +func (y HealthcareFacilityEntity) GetAcceptingNewPatients() bool { + return GetNullableBool(y.AcceptingNewPatients) +} + func (y HealthcareFacilityEntity) GetVideos() (v []Video) { if y.Videos != nil { v = *y.Videos From 4b36f35d6482f06e0fb04f3942ce81de7b7becb8 Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Mon, 21 Dec 2020 14:55:56 -0500 Subject: [PATCH 162/285] feat(entity): adds RemoveEntry for rawEntity (#208) * feat(entity): adds RemoveEntry for rawEntity J=PC-112865 TEST=manual * refactor(entity): removes print statements J=PC-112865 TEST=manual --- entity.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/entity.go b/entity.go index bf42f5c..96ae75f 100644 --- a/entity.go +++ b/entity.go @@ -206,6 +206,40 @@ func setValue(m map[string]interface{}, keys []string, index int, val interface{ return m, nil } +func (r *RawEntity) RemoveEntry(keys []string) error { + if len(keys) == 0 { + return nil + } + m := (map[string]interface{})(*r) + v, err := removeEntry(m, keys, 0) + if err != nil { + return err + } + raw := RawEntity(v) + r = &raw + return nil +} + +func removeEntry(m map[string]interface{}, keys []string, index int) (map[string]interface{}, error) { + if m == nil { + return nil, nil + } + if index == len(keys)-1 { + delete(m, keys[index]) + } else { + if _, ok := m[keys[index]]; !ok { + m[keys[index]] = map[string]interface{}{} + } + v, err := removeEntry(m[keys[index]].(map[string]interface{}), keys, index+1) + if err != nil { + return v, err + } + m[keys[index]] = v + } + + return m, nil +} + func ConvertStringsToEntityTypes(types []string) []EntityType { entityTypes := []EntityType{} for _, stringType := range types { From 00c254d5305813275db8090dfa3117ac316a24d7 Mon Sep 17 00:00:00 2001 From: ccabauatan Date: Fri, 26 Feb 2021 13:57:10 -0500 Subject: [PATCH 163/285] feat(entity-location): add SourceUrl as an exposed string for struct type Image for yext.Photo J=PC-121492 TEST=manual --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 25b5e58..2dffcfe 100644 --- a/entity_location.go +++ b/entity_location.go @@ -236,6 +236,7 @@ type Photo struct { type Image struct { Url *string `json:"url,omitempty"` + SourceUrl *string `json:"sourceUrl,omitempty"` AlternateText *string `json:"alternateText,omitempty"` Width *int `json:"width,omitempty"` Height *int `json:"height,omitempty"` From b0928c325e756cf9157a334c248a63a39fe1bde9 Mon Sep 17 00:00:00 2001 From: czou Date: Mon, 8 Mar 2021 13:29:37 -0500 Subject: [PATCH 164/285] feat: add drive-through hours profile field Add drive-through hours to location and ATM entities J=PC-122472 TEST=manual --- entity_atm.go | 1 + entity_location.go | 1 + 2 files changed, 2 insertions(+) diff --git a/entity_atm.go b/entity_atm.go index 4b9e254..30506ad 100644 --- a/entity_atm.go +++ b/entity_atm.go @@ -32,6 +32,7 @@ type ATMEntity struct { // Location Info Hours **Hours `json:"hours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` AdditionalHoursText *string `json:"additionalHoursText,omitempty"` Logo **Photo `json:"logo,omitempty"` diff --git a/entity_location.go b/entity_location.go index 2dffcfe..48bc69a 100644 --- a/entity_location.go +++ b/entity_location.go @@ -50,6 +50,7 @@ type LocationEntity struct { // Location Info Hours **Hours `json:"hours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` Closed **bool `json:"closed,omitempty"` Description *string `json:"description,omitempty"` AdditionalHoursText *string `json:"additionalHoursText,omitempty"` From 1a0eed2245b6708ba6f59cefc18e65270805450a Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Fri, 19 Mar 2021 11:15:45 -0400 Subject: [PATCH 165/285] entity_location: add cta yext type (#212) * entity_location: add cta yext type * *feat(entity_location): add nullableCTA to cta yext type --- entity_location.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/entity_location.go b/entity_location.go index 48bc69a..60821fb 100644 --- a/entity_location.go +++ b/entity_location.go @@ -1008,3 +1008,23 @@ type ServiceAreaPlace struct { Name *string `json:"name"` Type *string `json:"type"` } + +type CTA struct { + Text *string `json:"label,omitempty"` + URL *string `json:"link,omitempty"` + LinkType *string `json:"linkType,omitempty"` +} + +func (y CTA) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func ToCTA(c CTA) *CTA { + return &c +} + +func NullableCTA(c CTA) **CTA { + p := &c + return &p +} From 3e22583e8acd1af57ac91400a2065b05e63b62f7 Mon Sep 17 00:00:00 2001 From: tterbush Date: Fri, 2 Apr 2021 16:09:01 -0400 Subject: [PATCH 166/285] Update analytics review fields to match what was changed in this CR https://gerrit.yext.com/c/alpha/+/125658 TEST=manual --- analytics_data.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analytics_data.go b/analytics_data.go index 25d0647..0496701 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -24,7 +24,7 @@ type AnalyticsData struct { GooglePhoneCalls *int `json:"Google Phone Calls"` YelpCustomerActions *int `json:"Yelp Customer Actions"` AverageRating *float64 `json:"Average Rating"` - NewReviews *int `json:"New Reviews"` + NewReviews *int `json:"Reviews"` StorepagesSessions *int `json:"Storepages Sessions"` StorepagesPageviews *int `json:"Storepages Pageviews"` StorepagesDrivingdirections *int `json:"Storepages Drivingdirections"` @@ -56,7 +56,7 @@ type AnalyticsData struct { ResponseTime *int `json:"Response Time (Hours)"` ResponseRate *int `json:"Response Rate"` PartnerSite *string `json:"site"` - CumulativeRating *float64 `json:"Cumulative Rating"` + CumulativeRating *float64 `json:"Rolling Average Rating"` } func (y AnalyticsData) GetProfileViews() int { From c907e2d1f8ff61c6caf37dc10b86677a897d1ba3 Mon Sep 17 00:00:00 2001 From: npatel Date: Tue, 6 Apr 2021 17:34:48 -0400 Subject: [PATCH 167/285] feat(entity_service): add rich text format field TEST=manual --- entity_service.go | 27 +++++++++++++++++++++++++++ entity_service_test.go | 12 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/entity_service.go b/entity_service.go index c0a1a94..f51217f 100644 --- a/entity_service.go +++ b/entity_service.go @@ -12,6 +12,28 @@ const ( EntityListMaxLimit = 50 ) +type RichTextFormat int + +const ( + RichTextFormatDefault RichTextFormat = iota + RichTextFormatHTML + RichTextFormatMarkdown + RichTextFormatNone +) + +func (r RichTextFormat) ToString() string { + switch r { + case RichTextFormatHTML: + return "html" + case RichTextFormatMarkdown: + return "markdown" + case RichTextFormatNone: + return "none" + default: + return "" + } +} + type EntityService struct { client *Client Registry *EntityRegistry @@ -24,6 +46,7 @@ type EntityListOptions struct { EntityTypes []string Fields []string Filter string + Format RichTextFormat } // Used for Create and Edit @@ -179,6 +202,10 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if len(opts.Filter) > 0 { q.Add("filter", opts.Filter) } + if opts.Format != RichTextFormatDefault { + q.Add("format", opts.Format.ToString()) + } + u.RawQuery = q.Encode() return u.String(), nil diff --git a/entity_service_test.go b/entity_service_test.go index 1f577f0..b313cd0 100644 --- a/entity_service_test.go +++ b/entity_service_test.go @@ -58,6 +58,7 @@ func TestEntityListOptions(t *testing.T) { entityTypes string resolvePlaceholders bool filter string + format string }{ { opts: nil, @@ -132,6 +133,14 @@ func TestEntityListOptions(t *testing.T) { searchIDs: "", filter: `{"c_emergencyWatch":{"$eq":true}}`, }, + { + opts: &EntityListOptions{Format: RichTextFormatNone}, + limit: "", + token: "", + searchIDs: "", + filter: "", + format: "none", + }, } for _, test := range tests { @@ -156,6 +165,9 @@ func TestEntityListOptions(t *testing.T) { if v := r.URL.Query().Get("filter"); v != test.filter { t.Errorf("Wanted filter %s, got %s", test.filter, v) } + if v := r.URL.Query().Get("format"); v != test.format { + t.Errorf("Wanted format '%s', got '%s'", test.format, v) + } }) client.EntityService.List(test.opts) From 428ec1d9e8c9c69fdb00bfc54e614eb2d82db041 Mon Sep 17 00:00:00 2001 From: tterbush Date: Tue, 20 Apr 2021 22:01:41 -0400 Subject: [PATCH 168/285] Remove publisherSuggestionType, searchTerm, searchType, and foursquareCheckinTimeOfDay from AnalyticsFilters J=PC-126903 TEST=auto --- analytics_service.go | 74 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/analytics_service.go b/analytics_service.go index d890a4d..5613f1f 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -7,45 +7,41 @@ type AnalyticsService struct { } type AnalyticsFilters struct { - StartDate *string `json:"startDate"` - EndDate *string `json:"endDate"` - LocationIds *[]string `json:"locationIds"` - EntityTypes *[]string `json:"entityType"` - FolderId *int `json:"folderId"` - Countries *[]string `json:"countries"` - LocationLabels *[]string `json:"locationLabels"` - Platforms *[]string `json:"platforms"` - GoogleActionType *[]string `json:"googleActionType"` - CustomerActionType *[]string `json:"customerActionType"` - GoogleQueryType *[]string `json:"googleQueryType"` - Hours *[]int `json:"hours"` - Ratings *[]int `json:"ratings"` - FrequentWords *[]string `json:"frequentWords"` - Partners *[]int `json:"partners"` - ReviewLabels *[]int `json:"reviewLabels"` - PageTypes *[]string `json:"pageTypes"` - ListingsLiveType *string `json:"listingsLiveType"` - PublisherSuggestionType *[]string `json:"publisherSuggestionType"` - QueryTemplate *[]string `json:"queryTemplate"` - SearchEngine *[]string `json:"searchEngine"` - Keyword *[]string `json:"keyword"` - Competitor *[]string `json:"competitor"` - MatchPosition *[]string `json:"matchPosition"` - SearchResultType *[]string `json:"searchResultType"` - MatchType *[]string `json:"matchType"` - MinSearchFrequency *int `json:"minSearchFrequency"` - MaxSearchFrequency *int `json:"maxSearchFrequency"` - SearchTerm *string `json:"searchTerm"` - SearchType *string `json:"searchType"` - FoursquareCheckinType *string `json:"foursquareCheckinType"` - FoursquareCheckinAge *string `json:"foursquareCheckinAge"` - FoursquareCheckinGender *string `json:"foursquareCheckinGender"` - FoursquareCheckinTimeOfDay *string `json:"foursquareCheckinTimeOfDay"` - InstagramContentType *string `json:"instagramContentType"` - Age *[]string `json:"age"` - Gender *string `json:"gender"` - FacebookImpressionType *[]string `json:"facebookImpressionType"` - FacebookStoryType *[]string `json:"facebookStoryType"` + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + LocationIds *[]string `json:"locationIds"` + EntityTypes *[]string `json:"entityType"` + FolderId *int `json:"folderId"` + Countries *[]string `json:"countries"` + LocationLabels *[]string `json:"locationLabels"` + Platforms *[]string `json:"platforms"` + GoogleActionType *[]string `json:"googleActionType"` + CustomerActionType *[]string `json:"customerActionType"` + GoogleQueryType *[]string `json:"googleQueryType"` + Hours *[]int `json:"hours"` + Ratings *[]int `json:"ratings"` + FrequentWords *[]string `json:"frequentWords"` + Partners *[]int `json:"partners"` + ReviewLabels *[]int `json:"reviewLabels"` + PageTypes *[]string `json:"pageTypes"` + ListingsLiveType *string `json:"listingsLiveType"` + QueryTemplate *[]string `json:"queryTemplate"` + SearchEngine *[]string `json:"searchEngine"` + Keyword *[]string `json:"keyword"` + Competitor *[]string `json:"competitor"` + MatchPosition *[]string `json:"matchPosition"` + SearchResultType *[]string `json:"searchResultType"` + MatchType *[]string `json:"matchType"` + MinSearchFrequency *int `json:"minSearchFrequency"` + MaxSearchFrequency *int `json:"maxSearchFrequency"` + FoursquareCheckinType *string `json:"foursquareCheckinType"` + FoursquareCheckinAge *string `json:"foursquareCheckinAge"` + FoursquareCheckinGender *string `json:"foursquareCheckinGender"` + InstagramContentType *string `json:"instagramContentType"` + Age *[]string `json:"age"` + Gender *string `json:"gender"` + FacebookImpressionType *[]string `json:"facebookImpressionType"` + FacebookStoryType *[]string `json:"facebookStoryType"` } type AnalyticsReportRequest struct { From 5198a92d1b2dae99e0a045c65cf6b19208c8dde0 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Thu, 6 May 2021 15:59:45 -0400 Subject: [PATCH 169/285] adds accessHours profile field to locations --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 60821fb..0738a78 100644 --- a/entity_location.go +++ b/entity_location.go @@ -50,6 +50,7 @@ type LocationEntity struct { // Location Info Hours **Hours `json:"hours,omitempty"` + AccessHours **Hours `json:"accessHours,omitempty"` DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` Closed **bool `json:"closed,omitempty"` Description *string `json:"description,omitempty"` From 8ad70071626809de09c612e854a0efbfc7985aff Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Thu, 20 May 2021 12:31:14 -0400 Subject: [PATCH 170/285] adds funcs for getting all option names for option select fields --- customfield_service.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/customfield_service.go b/customfield_service.go index 114e2da..a0669fe 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -297,6 +297,40 @@ func (c *CustomFieldManager) MustGetMultiOptionNames(fieldName string, options * } } +func (c *CustomFieldManager) GetAllOptionNames(fieldName string) ([]string, error) { + var ( + cf, err = c.CustomField(fieldName) + optionNames = []string{} + ) + if err != nil { + return nil, err + } + + for _, option := range cf.Options { + optionNames = append(optionNames, option.Value) + } + + return optionNames, nil +} + +// MustGetAllOptionNames returns all the option names for a given Single or Multi Option Select field. +// It returns an empty slice if the field has no options +// Example Use: +// Custom field name: "Dog Height" +// Custom field option ID to name map: { +// "1423": "Tall", +// "1424": "Short", +// } +// cfmanager.MustGetAllOptionNames("Dog Height") returns []string{"Tall', "Short"} +// +func (c *CustomFieldManager) MustGetAllOptionNames(fieldName string) []string { + if names, err := c.GetAllOptionNames(fieldName); err != nil { + panic(err) + } else { + return names + } +} + func (c *CustomFieldManager) GetSingleOptionName(fieldName string, optionId **string) (string, error) { if GetNullableString(optionId) == "" { return "", nil From 564248b6c87511ccc4115ff5a940d49da2cfaded Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Wed, 19 May 2021 16:22:34 -0400 Subject: [PATCH 171/285] adds new func to determine if raw entity is zero value --- entity.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/entity.go b/entity.go index 96ae75f..7392eeb 100644 --- a/entity.go +++ b/entity.go @@ -151,6 +151,31 @@ func ConvertToRawEntity(e Entity) (*RawEntity, error) { return &raw, nil } +func (r *RawEntity) IsZeroValue() bool { + m := (map[string]interface{})(*r) + return rawEntityIsZeroValue(m) +} + +// Function that recursively checks each field on a RawEntity to determine if the entity overall is empty or not +// keys with nil values should still cause this func to return true +func rawEntityIsZeroValue(rawEntity map[string]interface{}) bool { + for _, v := range rawEntity { + if v != nil { + // if v is a map, it means we've got a nested field, so we need to recurse to check the subfields of that map too + if m, ok := v.(map[string]interface{}); ok { + return rawEntityIsZeroValue(m) + } else { + // if the value we're looking at isn't nil AND isn't a map (nested field), we can assume this field on + // the raw entity has some value, therefore the whole entity is not zero value + return false + } + } + } + + // if we never encounter a field with a value in the above, it's an empty rawEntity so we can return true + return true +} + func (r *RawEntity) GetValue(keys []string) interface{} { if len(keys) == 0 { return nil From 33c944467a11b05b14f76914c7942c5652c87841 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Wed, 19 May 2021 18:55:00 -0400 Subject: [PATCH 172/285] add tests and support for []interface{} --- entity.go | 65 ++++++++++++++------ entity_test.go | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 19 deletions(-) diff --git a/entity.go b/entity.go index 7392eeb..9ecc5c2 100644 --- a/entity.go +++ b/entity.go @@ -152,28 +152,55 @@ func ConvertToRawEntity(e Entity) (*RawEntity, error) { } func (r *RawEntity) IsZeroValue() bool { - m := (map[string]interface{})(*r) - return rawEntityIsZeroValue(m) + m := (map[string]interface{})(*r) + return rawEntityIsZeroValue(m) } // Function that recursively checks each field on a RawEntity to determine if the entity overall is empty or not -// keys with nil values should still cause this func to return true -func rawEntityIsZeroValue(rawEntity map[string]interface{}) bool { - for _, v := range rawEntity { - if v != nil { - // if v is a map, it means we've got a nested field, so we need to recurse to check the subfields of that map too - if m, ok := v.(map[string]interface{}); ok { - return rawEntityIsZeroValue(m) - } else { - // if the value we're looking at isn't nil AND isn't a map (nested field), we can assume this field on - // the raw entity has some value, therefore the whole entity is not zero value - return false - } - } - } - - // if we never encounter a field with a value in the above, it's an empty rawEntity so we can return true - return true +// keys with nil or slices with nil elements values should still cause this func to return true +func rawEntityIsZeroValue(rawEntity interface{}) bool { + // should only ever pass in maps or slices to this, so don't need to check other types + if m, ok := rawEntity.(map[string]interface{}); ok { + for _, v := range m { + if v != nil { + // if v is a map or a slice, it means we've got a nested field, + // so we need to recurse to check the subfields of it too + if mapSubfield, ok := v.(map[string]interface{}); ok { + return rawEntityIsZeroValue(mapSubfield) + } else if sliceSubfield, ok := v.([]interface{}); ok { + return rawEntityIsZeroValue(sliceSubfield) + } else { + // if the value we're looking at isn't nil AND isn't a map or slice (nested field), we can assume + // this field on the raw entity has some value, therefore the whole entity is not zero value + return false + } + } + } + } else if s, ok := rawEntity.([]interface{}); ok { + // if we're looking at a slice, need to check all elements in the slice. if any of them + // aren't zero, the toggle isZero + isZero := true + for _, e := range s { + if e != nil { + if mapSubfield, ok := e.(map[string]interface{}); ok { + isZero = rawEntityIsZeroValue(mapSubfield) + } else if sliceSubfield, ok := e.([]interface{}); ok { + isZero = rawEntityIsZeroValue(sliceSubfield) + } else { + isZero = false + } + } + // if this current element isn't zero, slice isn't zero, return false + // if this current elemtn IS zero, continue on in the slice + if isZero == false { + return isZero + } + } + return isZero + } + + // if we never encounter a field with a non-zero value in the above, it's an empty rawEntity so we can return true + return true } func (r *RawEntity) GetValue(keys []string) interface{} { diff --git a/entity_test.go b/entity_test.go index 78bf429..194cb27 100644 --- a/entity_test.go +++ b/entity_test.go @@ -539,6 +539,164 @@ var sampleEntityJSON = `{ } }` +func TestRawEntityIsZeroValue(t *testing.T) { + var tests = []struct { + Name string + Raw *RawEntity + Want bool + }{ + { + Name: "Zero - Empty entity", + Raw: &RawEntity{}, + Want: true, + }, + { + Name: "Zero - Empty single field", + Raw: &RawEntity{ + "name": nil, + }, + Want: true, + }, + { + Name: "Zero - Empty nested map field", + Raw: &RawEntity{ + "address": map[string]interface{}{ + "line1": nil, + }, + }, + Want: true, + }, + { + Name: "Zero - Empty nested slice subfield", + Raw: &RawEntity{ + "hours": map[string]interface{}{ + "monday": map[string]interface{}{ + "openIntervals": []interface{}{ + map[string]interface{}{ + "start": nil, + "end": nil, + }, + }, + }, + }, + }, + Want: true, + }, + { + Name: "Zero - double empty nested slice subfield", + Raw: &RawEntity{ + "providerData": []interface{}{ + []interface{}{ + map[string]interface{}{ + "name": nil, + }, + }, + map[string]interface{}{ + "name": nil, + "id": nil, + }, + }, + }, + Want: true, + }, + { + Name: "Not Zero - valid single field", + Raw: &RawEntity{ + "name": "Name", + }, + Want: false, + }, + { + Name: "Not Zero - valid single field (but it has zero value - empty string)", + Raw: &RawEntity{ + "name": "", + }, + Want: false, + }, + { + Name: "Not Zero - valid single field (but it has zero value - false)", + Raw: &RawEntity{ + "closed": false, + }, + Want: false, + }, + { + Name: "Not Zero - valid nested slice subfield", + Raw: &RawEntity{ + "hours": map[string]interface{}{ + "monday": map[string]interface{}{ + "openIntervals": []interface{}{ + map[string]interface{}{ + "start": "10:00", + "end": "18:00", + }, + }, + }, + }, + }, + Want: false, + }, + { + Name: "Not Zero - valid slice subfield, even with one invalid element", + Raw: &RawEntity{ + "providerData": []interface{}{ + map[string]interface{}{ + "name": nil, + "id": nil, + }, + map[string]interface{}{ + "name": "Name", + }, + }, + }, + Want: false, + }, + { + Name: "Not Zero - double nested slice subfield, even with one invalid element in each slice", + Raw: &RawEntity{ + "providerData": []interface{}{ + []interface{}{ + map[string]interface{}{ + "name": false, + }, + map[string]interface{}{ + "name": nil, + }, + }, + map[string]interface{}{ + "name": nil, + "id": nil, + }, + }, + }, + Want: false, + }, + { + Name: "Not Zero - double nested slice subfield, even with one invalid element", + Raw: &RawEntity{ + "providerData": []interface{}{ + []interface{}{ + map[string]interface{}{ + "name": nil, + }, + }, + map[string]interface{}{ + "name": "", + }, + }, + }, + Want: false, + }, + } + + for _, test := range tests { + got := test.Raw.IsZeroValue() + if got != test.Want { + t.Errorf("Got: %v, Wanted: %v", got, test.Want) + } + } +} + func TestGetValue(t *testing.T) { var tests = []struct { Raw *RawEntity From 4f9ae0fff01272faa9ea1b7ab8f554172d67e697 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Mon, 24 May 2021 15:17:45 -0400 Subject: [PATCH 173/285] fixes bug in rawEntityIsZeroValue --- entity.go | 8 ++++++-- entity_test.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/entity.go b/entity.go index 9ecc5c2..7eab02a 100644 --- a/entity.go +++ b/entity.go @@ -166,9 +166,13 @@ func rawEntityIsZeroValue(rawEntity interface{}) bool { // if v is a map or a slice, it means we've got a nested field, // so we need to recurse to check the subfields of it too if mapSubfield, ok := v.(map[string]interface{}); ok { - return rawEntityIsZeroValue(mapSubfield) + if !rawEntityIsZeroValue(mapSubfield) { + return false + } } else if sliceSubfield, ok := v.([]interface{}); ok { - return rawEntityIsZeroValue(sliceSubfield) + if !rawEntityIsZeroValue(sliceSubfield) { + return false + } } else { // if the value we're looking at isn't nil AND isn't a map or slice (nested field), we can assume // this field on the raw entity has some value, therefore the whole entity is not zero value diff --git a/entity_test.go b/entity_test.go index 194cb27..9e87b79 100644 --- a/entity_test.go +++ b/entity_test.go @@ -687,6 +687,26 @@ func TestRawEntityIsZeroValue(t *testing.T) { }, Want: false, }, + { + Name: "Disney ETL Test that was failing", + Raw: &RawEntity{ + "c_oSID": "80007922", + "c_propertyType": "land", + "c_storeName": "frontierland", + "description": "", + "hours": map[string]interface{}{}, + "meta": map[string]interface{}{ + "entityType": "location", + "id": "80007922;entityType=land", + "labels": []interface{}{ + "83533", + }, + }, + "name": "Frontierland", + "photoGallery": []interface{}{}, + }, + Want: false, + }, } for _, test := range tests { From 95af150f1a0f88408318ee082274a335a8ef4ace Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Tue, 25 May 2021 16:42:28 -0400 Subject: [PATCH 174/285] adds helpArticle entity type (#220) * adds helpArticle entity type * entity_helparticle: use better var names Co-authored-by: afitzgerald --- entity_helparticle.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 entity_helparticle.go diff --git a/entity_helparticle.go b/entity_helparticle.go new file mode 100644 index 0000000..c06108f --- /dev/null +++ b/entity_helparticle.go @@ -0,0 +1,52 @@ +package yext + +import "encoding/json" + +const ( + ENTITYTYPE_HELPARTICLE EntityType = "helpArticle" +) + +type HelpArticle struct { + BaseEntity + + // Admin + Keywords *[]string `json:"keywords,omitempty"` // LIST + + // Content + Name *string `json:"name,omitempty"` // STRING + Body *string `json:"body,omitempty"` // RICH_TEXT + ShortDescription *string `json:"shortDescription,omitempty"` // STRING + VoteCount *int `json:"voteCount,omitempty"` // STRING + VoteSum *int `json:"voteSum,omitempty"` // STRING + Promoted **bool `json:"promoted,omitempty"` // BOOl + + ExternalArticlePostDate *Date `json:"externalArticlePostDate,omitempty"` + ExternalArticleUpdateDate *Date `json:"externalArticleUpdateDate,omitempty"` + + // Knowledge Assistant + PrimaryConversationContact *string `json:"primaryConversationContact,omitempty"` + NudgeEnabled **bool `json:"nudgeEnabled,omitempty"` + + // Internal Use + Folder *string `json:"folder,omitempty"` + Timezone *string `json:"timezone,omitempty"` +} + +func (h *HelpArticle) String() string { + b, _ := json.Marshal(h) + return string(b) +} + +func (h HelpArticle) GetId() string { + if h.BaseEntity.Meta != nil && h.BaseEntity.Meta.Id != nil { + return *h.BaseEntity.Meta.Id + } + return "" +} + +func (h HelpArticle) GetName() string { + if h.Name != nil { + return *h.Name + } + return "" +} From 8ff78e5326e5de18afce393715865d3720978f42 Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Wed, 2 Jun 2021 14:36:52 -0400 Subject: [PATCH 175/285] location_customfield: add ToDate function for yext.Date (#221) * location_customfield: add ToDate function for yext.Date J=PC-127422 * type.go: add toDate function and remove from location customfield J=PC-127422 --- type.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/type.go b/type.go index d4da950..b7b0846 100644 --- a/type.go +++ b/type.go @@ -322,3 +322,8 @@ func NullableTernaryFromBool(b bool) **Ternary { } return NullableTernary(No) } + +func ToDate(s string) *Date { + d := Date(s) + return &d +} From 403fbb7bb65cc52c1624b52711102d1f3117c2f4 Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Wed, 16 Jun 2021 11:19:59 -0400 Subject: [PATCH 176/285] helparticle: add landing page URL field (#222) --- entity_helparticle.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/entity_helparticle.go b/entity_helparticle.go index c06108f..753f471 100644 --- a/entity_helparticle.go +++ b/entity_helparticle.go @@ -13,12 +13,13 @@ type HelpArticle struct { Keywords *[]string `json:"keywords,omitempty"` // LIST // Content - Name *string `json:"name,omitempty"` // STRING Body *string `json:"body,omitempty"` // RICH_TEXT + LandingPageUrl *string `json:"landingPageUrl,omitempty"` // STRING + Name *string `json:"name,omitempty"` // STRING + Promoted **bool `json:"promoted,omitempty"` // BOOl ShortDescription *string `json:"shortDescription,omitempty"` // STRING VoteCount *int `json:"voteCount,omitempty"` // STRING VoteSum *int `json:"voteSum,omitempty"` // STRING - Promoted **bool `json:"promoted,omitempty"` // BOOl ExternalArticlePostDate *Date `json:"externalArticlePostDate,omitempty"` ExternalArticleUpdateDate *Date `json:"externalArticleUpdateDate,omitempty"` @@ -50,3 +51,10 @@ func (h HelpArticle) GetName() string { } return "" } + +func (h HelpArticle) GetLandingPageUrl() string { + if h.LandingPageUrl != nil { + return GetString(h.LandingPageUrl) + } + return "" +} From 74b5acc8612e3556adaada2d08a556189558a62c Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Thu, 17 Jun 2021 17:24:09 -0400 Subject: [PATCH 177/285] Add Activity Logs and update some Analytics Report (#223) * Add Activity Logs Struct and Service J=PC-134868 * Update some Analytics Data API names J=PC-135005 --- activity_log.go | 52 +++++++++++++++++++++++++++++++++++++++++ activity_log_service.go | 49 ++++++++++++++++++++++++++++++++++++++ analytics_data.go | 12 +++++----- 3 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 activity_log.go create mode 100644 activity_log_service.go diff --git a/activity_log.go b/activity_log.go new file mode 100644 index 0000000..bbb326d --- /dev/null +++ b/activity_log.go @@ -0,0 +1,52 @@ +package yext + +type ActivityLog struct { + TimeStamp *int `json:"timestamp"` + LocationID *string `json:"locationId"` + Details *string `json:"details"` + Content *string `json:"content"` + Type *string `json:"type"` + Actor *string `json:"actor"` +} + +func (y ActivityLog) GetTimeStamp() int { + if y.TimeStamp != nil { + return *y.TimeStamp + } + return 0 +} + +func (y ActivityLog) GetLocationID() string { + if y.LocationID != nil { + return *y.LocationID + } + return "" +} + +func (y ActivityLog) GetDetails() string { + if y.Details != nil { + return *y.Details + } + return "" +} + +func (y ActivityLog) GetContent() string { + if y.Content != nil { + return *y.Content + } + return "" +} + +func (y ActivityLog) GetType() string { + if y.Type != nil { + return *y.Type + } + return "" +} + +func (y ActivityLog) GetActor() string { + if y.Actor != nil { + return *y.Actor + } + return "" +} diff --git a/activity_log_service.go b/activity_log_service.go new file mode 100644 index 0000000..a3d5c88 --- /dev/null +++ b/activity_log_service.go @@ -0,0 +1,49 @@ +package yext + +const ( + activityLogPath = "analytics/activity" + + // Use the following constants for Activity Filter + ACTIVITYTYPES_LOCATION_UPDATED = "LOCATION_UPDATED" + ACTIVITYTYPES_PUBLISHER_SUGGESTION_CREATED = "PUBLISHER_SUGGESTION_CREATED" + ACTIVITYTYPES_PUBLISHER_SUGGESTION_APPROVED = "PUBLISHER_SUGGESTION_APPROVED" + ACTIVITYTYPES_PUBLISHER_SUGGESTION_REJECTED = "PUBLISHER_SUGGESTION_REJECTED" + ACTIVITYTYPES_REVIEW_CREATED = "REVIEW_CREATED" + ACTIVITYTYPES_SOCIAL_POST_CREATED = "SOCIAL_POST_CREATED" + ACTIVITYTYPES_LISTING_LIVE = "LISTING_LIVE" + ACTIVITYTYPES_DUPLICATE_SUPPRESSED = "DUPLICATE_SUPPRESSED" + + ACTORS_YEXT_SYSTEM = "YEXT_SYSTEM" + ACTORS_SCHEDULED_CONTENT = "SCHEDULED_CONTENT" + ACTORS_API = "API" + ACTORS_PUBLISHER = "PUBLISHER" +) + +type ActivityLogService struct { + client *Client +} + +type ActivityLogResponse struct { + Data []*ActivityLog `json:"activities"` +} + +type ActivityFilter struct { + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + LocationIds *[]string `json:"locationIds"` + ActivityTypes *[]string `json:"activityTypes"` // See const above + Actors *[]string `json:"actors"` // See const above +} + +type ActivityLogRequest struct { + Limit int `json:"limit"` + Offset int `json:"offset"` + Filters *ActivityFilter `json:"filters"` +} + +// ActivityLogRequest can be nil, no filter +func (a *ActivityLogService) Create(req *ActivityLogRequest) (*ActivityLogResponse, *Response, error) { + arr := &ActivityLogResponse{} + r, err := a.client.DoRequestJSON("POST", activityLogPath, req, arr) + return arr, r, err +} diff --git a/analytics_data.go b/analytics_data.go index 0496701..5f76b62 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -19,18 +19,18 @@ type AnalyticsData struct { InstagramPosts *int `json:"Instagram Posts"` GoogleSearchQueries *int `json:"Google Search Queries"` GoogleSearchViews *int `json:"Google Search Views"` - GoogleMapViews *int `json:"Google Map Views"` + GoogleMapViews *int `json:"Google Maps Views"` GoogleCustomerActions *int `json:"Google Customer Actions"` GooglePhoneCalls *int `json:"Google Phone Calls"` YelpCustomerActions *int `json:"Yelp Customer Actions"` AverageRating *float64 `json:"Average Rating"` NewReviews *int `json:"Reviews"` StorepagesSessions *int `json:"Storepages Sessions"` - StorepagesPageviews *int `json:"Storepages Pageviews"` - StorepagesDrivingdirections *int `json:"Storepages Drivingdirections"` - StorepagesPhonecalls *int `json:"Storepages Phonecalls"` - StorepagesCalltoactionclicks *int `json:"Storepages Calltoactionclicks"` - StorepagesClickstowebsite *int `json:"Storepages Clickstowebsite"` + StorepagesPageviews *int `json:"Pages Views"` + StorepagesDrivingdirections *int `json:"Driving Directions"` + StorepagesPhonecalls *int `json:"Taps to Call"` + StorepagesCalltoactionclicks *int `json:"Call to Action Clicks"` + StorepagesClickstowebsite *int `json:"Clicks to Website"` StorepagesEventEventtype *int `json:"Storepages Event Eventtype"` ProfileUpdates *int `json:"Profile Updates"` PublisherSuggestions *int `json:"Publisher Suggestions"` From 4cee729c2750b8ef78c764c89e77d37e6da0dd0b Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Tue, 22 Jun 2021 13:00:33 -0400 Subject: [PATCH 178/285] adds DigitalGuestRoomKeys field to hotel entity struct (#224) --- entity_hotel.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/entity_hotel.go b/entity_hotel.go index 83d0ad8..e8c416d 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -199,6 +199,9 @@ type HotelEntity struct { // Accessibility MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` + + // Minimized Contact + DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` } const ( From 3285b960731ab4580044e575d17a4ca1ecd6c090 Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Thu, 24 Jun 2021 17:25:05 -0400 Subject: [PATCH 179/285] add activitylogservice to client.go (#225) --- client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client.go b/client.go index 3962ecb..e32e590 100644 --- a/client.go +++ b/client.go @@ -39,6 +39,7 @@ type Client struct { LocationLanguageProfileService *LocationLanguageProfileService AssetService *AssetService CFTAssetService *CFTAssetService + ActivityLogService *ActivityLogService AnalyticsService *AnalyticsService EntityService *EntityService LanguageProfileService *LanguageProfileService @@ -60,6 +61,7 @@ func NewClient(config *Config) *Client { c.AssetService = &AssetService{client: c} c.CFTAssetService = &CFTAssetService{client: c} c.CFTAssetService.RegisterDefaultAssetValues() + c.ActivityLogService = &ActivityLogService{client: c} c.AnalyticsService = &AnalyticsService{client: c} c.EntityService = &EntityService{client: c} c.EntityService.RegisterDefaultEntities() From 9adcff266040f3323dee57e48f4d0918dec1493a Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:19:32 -0400 Subject: [PATCH 180/285] update actordetails to activity log (#226) --- activity_log.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/activity_log.go b/activity_log.go index bbb326d..def0bcc 100644 --- a/activity_log.go +++ b/activity_log.go @@ -1,12 +1,18 @@ package yext type ActivityLog struct { - TimeStamp *int `json:"timestamp"` - LocationID *string `json:"locationId"` - Details *string `json:"details"` - Content *string `json:"content"` - Type *string `json:"type"` - Actor *string `json:"actor"` + TimeStamp *int `json:"timestamp"` + LocationID *string `json:"locationId"` + Details *string `json:"details"` + Content *string `json:"content"` + Type *string `json:"type"` + Actor *string `json:"actor"` + ActorDetails ActorDetail `json:"actorDetails"` // need to set version 20210728 +} + +type ActorDetail struct { + Name *string `json:"name"` + Email *string `json:"emails"` } func (y ActivityLog) GetTimeStamp() int { From 7739778bc720da65101f08bb61d379a9188d3e0a Mon Sep 17 00:00:00 2001 From: JamesJMH Date: Mon, 28 Jun 2021 14:27:02 -0400 Subject: [PATCH 181/285] Add google place id (#227) --- entity_healthcare_professional.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 6056b23..0336ab5 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -107,6 +107,7 @@ type HealthcareProfessionalEntity struct { GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` From 9a2ed94daef90c65df717e036e23c19534923782 Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Tue, 6 Jul 2021 16:12:07 -0400 Subject: [PATCH 182/285] update activity log and analytics data for att J=PC-134030 TEST=manual (#228) --- activity_log.go | 16 ++++++++++++++++ analytics_data.go | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/activity_log.go b/activity_log.go index def0bcc..d26fcc1 100644 --- a/activity_log.go +++ b/activity_log.go @@ -56,3 +56,19 @@ func (y ActivityLog) GetActor() string { } return "" } + +func (y ActivityLog) GetActorDetailsName() string { + if y.ActorDetails.Name != nil { + return *y.ActorDetails.Name + } + + return "" +} + +func (y ActivityLog) GetActorDetailsEmail() string { + if y.ActorDetails.Email != nil { + return *y.ActorDetails.Email + } + + return "" +} diff --git a/analytics_data.go b/analytics_data.go index 5f76b62..920a169 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -17,6 +17,7 @@ type AnalyticsData struct { FacebookPostImpressions *int `json:"Facebook Post Impressions"` FoursquareDailyCheckins *int `json:"Foursquare Daily Checkins"` InstagramPosts *int `json:"Instagram Posts"` + GoogleQueryType *string `json:"google_query_type"` GoogleSearchQueries *int `json:"Google Search Queries"` GoogleSearchViews *int `json:"Google Search Views"` GoogleMapViews *int `json:"Google Maps Views"` @@ -178,6 +179,13 @@ func (y AnalyticsData) GetGoogleSearchQueries() int { return 0 } +func (y AnalyticsData) GetGoogleQueryType() string { + if y.GoogleQueryType != nil { + return *y.GoogleQueryType + } + return "" +} + func (y AnalyticsData) GetGoogleSearchViews() int { if y.GoogleSearchViews != nil { return *y.GoogleSearchViews From 79597c6cce5f07eb5c376b55856528dac70a39ec Mon Sep 17 00:00:00 2001 From: Jesse Alloy Date: Wed, 7 Jul 2021 12:17:07 -0600 Subject: [PATCH 183/285] healthcare professional: implements setValidDegrees method - Checks degrees against hardcoded list of valid degrees supported by our platform - sets degrees on self, returns parse error if any are invalid J=PC-113595 TEST=manual --- entity_healthcare_professional.go | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 0336ab5..9cbb42c 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -8,6 +8,7 @@ package yext import ( "encoding/json" "fmt" + "strings" ) const ( @@ -559,3 +560,37 @@ func (y HealthcareProfessionalEntity) GetCategoryIds() (v []string) { } return v } + +// sets all degrees that are valid +// returns invalid degrees +// handle parseError for invalid degrees outside of function call +func (y *HealthcareProfessionalEntity) SetValidDegrees(degrees []string) []string { + var ( + invalidDegrees = []string{} + validDegrees = []string{} + validYextDegrees = []string{"ANP", "APN", "APRN", "ARNP", "CNM", "CNP", "CNS", "CPNP", "CRNA", "CRNP", "DC", + "DDS", "DMD", "DO", "DPM", "DVM", "FNP", "GNP", "LAC", "LPN", "MBA", "MBBS", "MD", + "MPH", "ND", "NP", "OD", "PA", "PAC", "PHARMD", "PHD", "PNP", "PSYD", "VMD", "WHNP"} + ) + + contains := func(list []string, item string) bool { + for _, l := range list { + if l == item { + return true + } + } + return false + } + + for _, degree := range degrees { + if contains(validYextDegrees, strings.ToUpper(degree)) { + validDegrees = append(validDegrees, degree) + } else { + invalidDegrees = append(invalidDegrees, degree) + } + } + + y.Degrees = ToUnorderedStrings(validDegrees) + + return invalidDegrees +} From dba56c6c86d657604c2e386ea659ee4f5a9c9237 Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:29:00 -0400 Subject: [PATCH 184/285] fix activity log email (#230) --- activity_log.go | 2 +- activity_log_service.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/activity_log.go b/activity_log.go index d26fcc1..d25876d 100644 --- a/activity_log.go +++ b/activity_log.go @@ -12,7 +12,7 @@ type ActivityLog struct { type ActorDetail struct { Name *string `json:"name"` - Email *string `json:"emails"` + Email *string `json:"email"` } func (y ActivityLog) GetTimeStamp() int { diff --git a/activity_log_service.go b/activity_log_service.go index a3d5c88..3d32b23 100644 --- a/activity_log_service.go +++ b/activity_log_service.go @@ -24,7 +24,8 @@ type ActivityLogService struct { } type ActivityLogResponse struct { - Data []*ActivityLog `json:"activities"` + Count *int `json:"count"` + Data []*ActivityLog `json:"activities"` } type ActivityFilter struct { From 2d3fba55d55a1d68da1537cbd780feb005033515 Mon Sep 17 00:00:00 2001 From: JamesJMH Date: Mon, 12 Jul 2021 12:21:06 -0400 Subject: [PATCH 185/285] Add googlePlaceId for healthcare professionals (#231) --- entity_healthcare_facility.go | 8 ++++++++ entity_healthcare_professional.go | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 1e5ee24..9a6c957 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -92,6 +92,7 @@ type HealthcareFacilityEntity struct { GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` @@ -422,6 +423,13 @@ func (y HealthcareFacilityEntity) GetFirstPartyReviewPage() string { return "" } +func (y HealthcareFacilityEntity) GetGooglePlaceId() string { + if y.GooglePlaceId != nil { + return *y.GooglePlaceId + } + return "" +} + func (y HealthcareFacilityEntity) String() string { b, _ := json.Marshal(y) return string(b) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 9cbb42c..0375b7c 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -594,3 +594,10 @@ func (y *HealthcareProfessionalEntity) SetValidDegrees(degrees []string) []strin return invalidDegrees } + +func (y HealthcareProfessionalEntity) GetGooglePlaceId() string { + if y.GooglePlaceId != nil { + return *y.GooglePlaceId + } + return "" +} From ffaee4a234cc6e4e2cbc66dbd6941ad68c17d8e4 Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Tue, 13 Jul 2021 15:50:38 -0400 Subject: [PATCH 186/285] fix page views api name and add cumulative rating get function (#233) --- analytics_data.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/analytics_data.go b/analytics_data.go index 920a169..a60d0d7 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -27,7 +27,7 @@ type AnalyticsData struct { AverageRating *float64 `json:"Average Rating"` NewReviews *int `json:"Reviews"` StorepagesSessions *int `json:"Storepages Sessions"` - StorepagesPageviews *int `json:"Pages Views"` + StorepagesPageviews *int `json:"Page Views"` StorepagesDrivingdirections *int `json:"Driving Directions"` StorepagesPhonecalls *int `json:"Taps to Call"` StorepagesCalltoactionclicks *int `json:"Call to Action Clicks"` @@ -228,6 +228,13 @@ func (y AnalyticsData) GetAverageRating() float64 { return -1 } +func (y AnalyticsData) GetCumulativeRating() float64 { + if y.CumulativeRating != nil { + return *y.CumulativeRating + } + return -1 +} + func (y AnalyticsData) GetNewReviews() int { if y.NewReviews != nil { return *y.NewReviews From 3be7855db0ba8515c44ff9524baadd511907448f Mon Sep 17 00:00:00 2001 From: JamesJMH Date: Thu, 15 Jul 2021 11:22:24 -0400 Subject: [PATCH 187/285] add npi for healthcare facilities (#234) --- entity_healthcare_facility.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 9a6c957..96f3adc 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -50,6 +50,7 @@ type HealthcareFacilityEntity struct { PaymentOptions *[]string `json:"paymentOptions,omitempty"` InsuranceAccepted *[]string `json:"insuranceAccepted,omitempty"` AcceptingNewPatients **bool `json:"acceptingNewPatients,omitempty"` + NPI *string `json:"npi,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -527,3 +528,10 @@ func (y HealthcareFacilityEntity) GetHolidayHours() []HolidayHours { func (y HealthcareFacilityEntity) IsClosed() bool { return GetNullableBool(y.Closed) } + +func (y HealthcareFacilityEntity) GetNPI() string { + if y.NPI != nil { + return GetString(y.NPI) + } + return "" +} From 64614739282529cfc4e0aa89d8e73e12ca566c52 Mon Sep 17 00:00:00 2001 From: Jesse Alloy Date: Tue, 24 Aug 2021 13:22:42 -0600 Subject: [PATCH 188/285] feat: adds profile field "Impressum" to location entity struct (#235) J=PC-130370 TEST=none --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 0738a78..8f683b0 100644 --- a/entity_location.go +++ b/entity_location.go @@ -37,6 +37,7 @@ type LocationEntity struct { ServiceAreaPlaces *[]ServiceAreaPlace `json:"serviceAreaPlaces,omitempty"` Geomodifier *string `json:"geomodifier,omitempty"` BlackOwnedBusiness **bool `json:"blackOwnedBusiness,omitempty"` + Impressum *string `json:"impressum,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` From a2fb31e144efd01136b08449e7d6a12004458c81 Mon Sep 17 00:00:00 2001 From: Jane Xu <52744177+zxu10@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:33:49 -0400 Subject: [PATCH 189/285] add competitor field to analytics data (#236) --- analytics_data.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/analytics_data.go b/analytics_data.go index a60d0d7..b68b550 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -58,6 +58,14 @@ type AnalyticsData struct { ResponseRate *int `json:"Response Rate"` PartnerSite *string `json:"site"` CumulativeRating *float64 `json:"Rolling Average Rating"` + Competitor *string `json:"competitor"` +} + +func (y AnalyticsData) GetCompetitor() string { + if y.Competitor != nil { + return *y.Competitor + } + return "" } func (y AnalyticsData) GetProfileViews() int { From 031738f37ce3cff979a93b61204599df05103163 Mon Sep 17 00:00:00 2001 From: Alex Liang Date: Tue, 31 Aug 2021 15:24:37 -0400 Subject: [PATCH 190/285] feat(entity): add new entity type "Financial Professional" J=PC-143030 TEST=manual --- entity_financial_professional.go | 422 +++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 entity_financial_professional.go diff --git a/entity_financial_professional.go b/entity_financial_professional.go new file mode 100644 index 0000000..a13ab68 --- /dev/null +++ b/entity_financial_professional.go @@ -0,0 +1,422 @@ +package yext + +import ( + "encoding/json" +) + +const ( + ENTITYTYPE_FINANCIALPROFESSIONAL EntityType = "financialProfessional" +) + +// FinancialProfessional is the representation of a FinancialProfessional in Yext Location Manager. +type FinancialProfessional struct { + BaseEntity + // Address Fields + Name *string `json:"name,omitempty"` + Address *Address `json:"address,omitempty"` + AddressHidden **bool `json:"addressHidden,omitempty"` + ISORegionCode *string `json:"isoRegionCode,omitempty"` + ServiceAreaPlaces *[]ServiceAreaPlace `json:"serviceAreaPlaces,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + Impressum *string `json:"impressum,omitempty"` + Neighborhood *string `json:"neighborhood,omitempty"` + + // Other Contact Info + AlternatePhone *string `json:"alternatePhone,omitempty"` + Fax *string `json:"fax,omitempty"` + LocalPhone *string `json:"localPhone,omitempty"` + MobilePhone *string `json:"mobilePhone,omitempty"` + MainPhone *string `json:"mainPhone,omitempty"` + TollFreePhone *string `json:"tollFreePhone,omitempty"` + TtyPhone *string `json:"ttyPhone,omitempty"` + Emails *[]string `json:"emails,omitempty"` + + // Location Info + NMLSNumber *string `json:"nmlsNumber,omitempty"` + TeamNumber *string `json:"teamNumber,omitempty"` + Hours **Hours `json:"hours,omitempty"` + OnlineServiceHours **Hours `json:"onlineServiceHours,omitempty"` + Closed **bool `json:"closed,omitempty"` + Description *string `json:"description,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Certifications *[]string `json:"certifications,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Products *[]string `json:"products,omitempty"` + Services *[]string `json:"services,omitempty"` + // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API + Specialties *[]string `json:"specialities,omitempty"` + + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + + // Admin + Keywords *[]string `json:"keywords,omitempty"` + CategoryIds *[]string `json:"categoryIds,omitempty"` + + // Urls + MenuUrl **Website `json:"menuUrl,omitempty"` + OrderUrl **Website `json:"orderUrl,omitempty"` + ReservationUrl **Website `json:"reservationUrl,omitempty"` + WebsiteUrl **Website `json:"websiteUrl,omitempty"` + LandingPageUrl **Website `json:"landingPageUrl,omitempty"` + IOSAppURL **Website `json:"iosAppUrl,omitempty"` + AndroidAppURL **Website `json:"androidAppUrl,omitempty"` + DisclosureLink **Website `json:"disclosureLink,omitempty"` + FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + + // Social Media + FacebookCallToAction **FacebookCTA `json:"facebookCallToAction,omitempty"` + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + FacebookOverrideCity *string `json:"facebookOverrideCity,omitempty"` + FacebookName *string `json:"facebookName,omitempty"` + FacebookDescriptor *string `json:"facebookDescriptor,omitempty"` + FacebookVanityURL *string `json:"facebookVanityUrl,omitempty"` + FacebookStoreID *string `json:"facebookStoreId,omitempty"` + FacebookAbout *string `json:"facebookAbout,omitempty"` + FacebookParentPageId *string `json:"facebookParentPageId,omitempty"` + + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleAccountID *string `json:"googleAccountId,omitempty"` + GooglePlaceID *string `json:"googlePlaceId,omitempty"` + GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` + GoogleShortName *string `json:"googleShortName,omitempty"` + + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + Headshot **Image `json:"headshot,omitempty"` + + GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` + + TimeZoneUtcOffset *string `json:"timeZoneUtcOffset,omitempty"` + Timezone *string `json:"timezone,omitempty"` + QuestionsAndAnswers **bool `json:"questionsAndAnswers,omitempty"` + DeliverListingsWithoutGeocode **bool `json:"deliverListingsWithoutGeocode,omitempty"` + HolidayHoursConversationEnabled **bool `json:"holidayHoursConversationEnabled,omitempty"` + ReviewResponseConversationEnabled **bool `json:"reviewResponseConversationEnabled,omitempty"` + NudgeEnabled **bool `json:"nudgeEnabled,omitempty"` + What3WordsAddress *string `json:"what3WordsAddress,omitempty"` + AppointmentOnly **bool `json:"appointmentOnly,omitempty"` + YearsOfExperience **int `json:"yearsOfExperience,omitempty"` + Interests *[]string `json:"interests,omitempty"` + Hobbies *[]string `json:"hobbies,omitempty"` + Awards *[]string `json:"awards,omitempty"` +} + +func (l *FinancialProfessional) UnmarshalJSON(data []byte) error { + type Alias FinancialProfessional + a := &struct { + *Alias + }{ + Alias: (*Alias)(l), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(l, data) +} + +func (y FinancialProfessional) GetId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.Id != nil { + return *y.BaseEntity.Meta.Id + } + return "" +} + +func (y FinancialProfessional) GetCategoryIds() (v []string) { + if y.CategoryIds != nil { + v = *y.CategoryIds + } + return v +} + +func (y FinancialProfessional) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y FinancialProfessional) GetAccountId() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.AccountId != nil { + return *y.BaseEntity.Meta.AccountId + } + return "" +} + +func (y FinancialProfessional) GetLine1() string { + if y.Address != nil && y.Address.Line1 != nil { + return GetString(y.Address.Line1) + } + return "" +} + +func (y FinancialProfessional) GetLine2() string { + if y.Address != nil && y.Address.Line2 != nil { + return GetString(y.Address.Line2) + } + return "" +} + +func (y FinancialProfessional) GetAddressHidden() bool { + return GetNullableBool(y.AddressHidden) +} + +func (y FinancialProfessional) GetExtraDescription() string { + if y.Address != nil && y.Address.ExtraDescription != nil { + return GetString(y.Address.ExtraDescription) + } + return "" +} + +func (y FinancialProfessional) GetCity() string { + if y.Address != nil && y.Address.City != nil { + return GetString(y.Address.City) + } + return "" +} + +func (y FinancialProfessional) GetRegion() string { + if y.Address != nil && y.Address.Region != nil { + return GetString(y.Address.Region) + } + return "" +} + +func (y FinancialProfessional) GetCountryCode() string { + if y.BaseEntity.Meta != nil && y.BaseEntity.Meta.CountryCode != nil { + return GetString(y.BaseEntity.Meta.CountryCode) + } + return "" +} + +func (y FinancialProfessional) GetPostalCode() string { + if y.Address != nil && y.Address.PostalCode != nil { + return GetString(y.Address.PostalCode) + } + return "" +} + +func (y FinancialProfessional) GetMainPhone() string { + if y.MainPhone != nil { + return *y.MainPhone + } + return "" +} + +func (y FinancialProfessional) GetLocalPhone() string { + if y.LocalPhone != nil { + return *y.LocalPhone + } + return "" +} + +func (y FinancialProfessional) GetAlternatePhone() string { + if y.AlternatePhone != nil { + return *y.AlternatePhone + } + return "" +} + +func (y FinancialProfessional) GetFax() string { + if y.Fax != nil { + return *y.Fax + } + return "" +} + +func (y FinancialProfessional) GetMobilePhone() string { + if y.MobilePhone != nil { + return *y.MobilePhone + } + return "" +} + +func (y FinancialProfessional) GetTollFreePhone() string { + if y.TollFreePhone != nil { + return *y.TollFreePhone + } + return "" +} + +func (y FinancialProfessional) GetTtyPhone() string { + if y.TtyPhone != nil { + return *y.TtyPhone + } + return "" +} + +func (y FinancialProfessional) GetFeaturedMessage() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Description) + } + return "" +} + +func (y FinancialProfessional) GetFeaturedMessageUrl() string { + f := GetFeaturedMessage(y.FeaturedMessage) + if f != nil { + return GetString(f.Url) + } + return "" +} + +func (y FinancialProfessional) GetWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y FinancialProfessional) GetDisplayWebsiteUrl() string { + w := GetWebsite(y.WebsiteUrl) + if w != nil { + return GetString(w.DisplayUrl) + } + return "" +} + +func (y FinancialProfessional) GetReservationUrl() string { + w := GetWebsite(y.ReservationUrl) + if w != nil { + return GetString(w.Url) + } + return "" +} + +func (y FinancialProfessional) GetHours() *Hours { + return GetHours(y.Hours) +} + +func (y FinancialProfessional) GetAdditionalHoursText() string { + if y.AdditionalHoursText != nil { + return *y.AdditionalHoursText + } + return "" +} + +func (y FinancialProfessional) GetDescription() string { + if y.Description != nil { + return *y.Description + } + return "" +} + +func (y FinancialProfessional) GetTwitterHandle() string { + if y.TwitterHandle != nil { + return *y.TwitterHandle + } + return "" +} + +func (y FinancialProfessional) GetFacebookPageUrl() string { + if y.FacebookPageUrl != nil { + return *y.FacebookPageUrl + } + return "" +} + +func (y FinancialProfessional) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (y FinancialProfessional) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (y FinancialProfessional) GetLanguage() (v string) { + if y.BaseEntity.Meta.Language != nil { + v = *y.BaseEntity.Meta.Language + } + return v +} + +func (y FinancialProfessional) GetAssociations() (v []string) { + if y.Associations != nil { + v = *y.Associations + } + return v +} + +func (y FinancialProfessional) GetEmails() (v []string) { + if y.Emails != nil { + v = *y.Emails + } + return v +} + +func (y FinancialProfessional) GetSpecialties() (v []string) { + if y.Specialties != nil { + v = *y.Specialties + } + return v +} + +func (y FinancialProfessional) GetServices() (v []string) { + if y.Services != nil { + v = *y.Services + } + return v +} + +func (y FinancialProfessional) GetBrands() (v []string) { + if y.Brands != nil { + v = *y.Brands + } + return v +} + +func (y FinancialProfessional) GetLanguages() (v []string) { + if y.Languages != nil { + v = *y.Languages + } + return v +} + +func (y FinancialProfessional) GetPaymentOptions() (v []string) { + if y.PaymentOptions != nil { + v = *y.PaymentOptions + } + return v +} + +func (y FinancialProfessional) GetVideos() (v []Video) { + if y.Videos != nil { + v = *y.Videos + } + return v +} + +func (y FinancialProfessional) GetGoogleAttributes() map[string][]string { + if y.GoogleAttributes != nil { + return *y.GoogleAttributes + } + return nil +} + +func (y FinancialProfessional) GetHolidayHours() []HolidayHours { + h := GetHours(y.Hours) + if h != nil && h.HolidayHours != nil { + return *h.HolidayHours + } + return nil +} + +func (y FinancialProfessional) IsClosed() bool { + return GetNullableBool(y.Closed) +} From d47f26daecd309da382ff3f0ba5e80ceb78e4de1 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Thu, 16 Sep 2021 03:51:45 -0400 Subject: [PATCH 191/285] adds covid fields to hotel entity type --- entity_hotel.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index e8c416d..af67981 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -200,8 +200,27 @@ type HotelEntity struct { MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` - // Minimized Contact - DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` + // Covid & Cleanliness Fields + DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` + CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` + GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` + CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` + EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` + EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` + EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` + HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` + PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` + ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` + PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` + PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` + LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` + PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` + CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` + SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` + MasksRequired **Ternary `json:"masksRequired,omitempty"` + IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` + DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` + SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` } const ( From a81681116b49f0925ffb7c046146244dd03cd3ba Mon Sep 17 00:00:00 2001 From: John Nguyen Date: Thu, 7 Oct 2021 16:53:42 -0400 Subject: [PATCH 192/285] Added delivery hours field to restaurant entity J=PC-146897 (#240) TEST=manual --- entity_restaurant.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_restaurant.go b/entity_restaurant.go index b681e57..339951a 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -34,6 +34,7 @@ type RestaurantEntity struct { Description *string `json:"description,omitempty"` Hours **Hours `json:"hours,omitempty"` AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` YearEstablished **float64 `json:"yearEstablished,omitempty"` Services *[]string `json:"services,omitempty"` Languages *[]string `json:"languages,omitempty"` From c6499518849ff8a6ba4cd58a48908219efcd1bdd Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Thu, 7 Oct 2021 16:54:14 -0400 Subject: [PATCH 193/285] adds googleMyBusinessLabels to healthcare professional and facility (#239) * adds googleMyBusinessLabels to healthcare professional and facility J=PC-146073 TEST=manual Ran test write api run with steward etl * use unordered strings --- entity_healthcare_facility.go | 11 ++++++----- entity_healthcare_professional.go | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 96f3adc..49bdbbb 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -89,11 +89,12 @@ type HealthcareFacilityEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` - GooglePlaceId *string `json:"googlePlaceId,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` + GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 0375b7c..4ad7a82 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -104,11 +104,12 @@ type HealthcareProfessionalEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` - GooglePlaceId *string `json:"googlePlaceId,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` + GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` From 4588a602e5b864e179062daba1d57e1ec60fe015 Mon Sep 17 00:00:00 2001 From: Jesse Alloy Date: Mon, 18 Oct 2021 17:17:48 -0600 Subject: [PATCH 194/285] feat(location_entity): adds timezone field to entity (#242) J=PC-27188 TEST=Manual --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 8f683b0..016c348 100644 --- a/entity_location.go +++ b/entity_location.go @@ -128,6 +128,7 @@ type LocationEntity struct { FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` TimeZoneUtcOffset *string `json:"timeZoneUtcOffset,omitempty"` + Timezone *string `json:"timezone,omitempty"` } func (l *LocationEntity) UnmarshalJSON(data []byte) error { From f697650c284dad5dbe28db95a58a66d5bc5f6606 Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Wed, 27 Oct 2021 11:42:17 -0400 Subject: [PATCH 195/285] add verbose response logging when we encounter a failure in our Do (#243) request logic Goal is to help debug unhelpful 'unexpected end of JSON input' logs J=CR-2054 TEST=auto --- client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index e32e590..0d0bcd9 100644 --- a/client.go +++ b/client.go @@ -276,7 +276,8 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { } if err != nil { - return resultResponse, err + errWithResponse := fmt.Errorf("request response: %v\n error: %s\n", resp, err.Error()) + return resultResponse, errWithResponse } else if len(resultResponse.Meta.Errors) > 0 { return resultResponse, resultResponse.Meta.Errors } else { From 9beb496398f8b5b70070035e2707721360f3f7ae Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Thu, 28 Oct 2021 17:10:41 -0400 Subject: [PATCH 196/285] entity_location: make GoogleWebsiteOverride a **string (#245) * entity_location: make GoogleWebsiteOverride a **string * Make GoogleWebsiteOverride a string across all entity types --- entity_hotel.go | 2 +- entity_location.go | 8 ++++---- entity_restaurant.go | 8 ++++---- location.go | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index af67981..ad20732 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -53,7 +53,7 @@ type HotelEntity struct { GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` GoogleAttributes *map[string][]string `json:"googleAttributes,omitempty"` // Media diff --git a/entity_location.go b/entity_location.go index 016c348..3d0bc43 100644 --- a/entity_location.go +++ b/entity_location.go @@ -110,10 +110,10 @@ type LocationEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` diff --git a/entity_restaurant.go b/entity_restaurant.go index 339951a..bbe9e64 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -71,10 +71,10 @@ type RestaurantEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` diff --git a/location.go b/location.go index 2aa2172..288fe91 100644 --- a/location.go +++ b/location.go @@ -144,7 +144,7 @@ type Location struct { GoogleCoverPhoto *LocationPhoto `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto *LocationPhoto `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` From b8774777f124a5b00dd93a4df319ccffde3575bd Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Fri, 29 Oct 2021 10:22:34 -0400 Subject: [PATCH 197/285] Googlewebsite fix (#247) * entity_location: make GoogleWebsiteOverride a **string * Make GoogleWebsiteOverride a string across all entity types * update GoogleWebsiteOverride to be **string for healthcare entity types * update GoogleWebsiteOverride to be **string for all entityy types --- entity_financial_professional.go | 2 +- entity_healthcare_facility.go | 2 +- entity_healthcare_professional.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/entity_financial_professional.go b/entity_financial_professional.go index a13ab68..a09171d 100644 --- a/entity_financial_professional.go +++ b/entity_financial_professional.go @@ -82,7 +82,7 @@ type FinancialProfessional struct { GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` GoogleAccountID *string `json:"googleAccountId,omitempty"` GooglePlaceID *string `json:"googlePlaceId,omitempty"` GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 49bdbbb..12216f1 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -92,7 +92,7 @@ type HealthcareFacilityEntity struct { GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` GooglePlaceId *string `json:"googlePlaceId,omitempty"` GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 4ad7a82..f1cdb48 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -107,7 +107,7 @@ type HealthcareProfessionalEntity struct { GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride *string `json:"googleWebsiteOverride,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` GooglePlaceId *string `json:"googlePlaceId,omitempty"` GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` From 30a0a61b6a7d31e94be864a04d0de055eed2b84e Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Fri, 29 Oct 2021 13:17:59 -0400 Subject: [PATCH 198/285] add request body to err message for debugging request failures (#246) J=CR-2054 TEST=auto --- client.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 0d0bcd9..0db51e0 100644 --- a/client.go +++ b/client.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "net/http/httputil" "net/url" "strconv" "strings" @@ -276,7 +277,11 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { } if err != nil { - errWithResponse := fmt.Errorf("request response: %v\n error: %s\n", resp, err.Error()) + b, ioError := httputil.DumpResponse(resp, true) + if ioError != nil { + return resultResponse, err + } + errWithResponse := fmt.Errorf("request response: %v\n error: %s\n", string(b), err.Error()) return resultResponse, errWithResponse } else if len(resultResponse.Meta.Errors) > 0 { return resultResponse, resultResponse.Meta.Errors From 8fc75edabd59b330558266eb08b80cf9b3f2cc44 Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Fri, 29 Oct 2021 13:20:23 -0400 Subject: [PATCH 199/285] fix, update formatted string to use string (#248) --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index 0db51e0..4b8407d 100644 --- a/client.go +++ b/client.go @@ -281,7 +281,7 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { if ioError != nil { return resultResponse, err } - errWithResponse := fmt.Errorf("request response: %v\n error: %s\n", string(b), err.Error()) + errWithResponse := fmt.Errorf("request response: %s\n error: %s\n", string(b), err.Error()) return resultResponse, errWithResponse } else if len(resultResponse.Meta.Errors) > 0 { return resultResponse, resultResponse.Meta.Errors From 9bf6c561daeb33e464a4d1643f49f2eb50ca2c75 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Fri, 29 Oct 2021 12:50:52 -0700 Subject: [PATCH 200/285] additions to valid degrees & job field (#244) --- entity_healthcare_professional.go | 4 ++-- entity_job.go | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index f1cdb48..db79360 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -570,8 +570,8 @@ func (y *HealthcareProfessionalEntity) SetValidDegrees(degrees []string) []strin invalidDegrees = []string{} validDegrees = []string{} validYextDegrees = []string{"ANP", "APN", "APRN", "ARNP", "CNM", "CNP", "CNS", "CPNP", "CRNA", "CRNP", "DC", - "DDS", "DMD", "DO", "DPM", "DVM", "FNP", "GNP", "LAC", "LPN", "MBA", "MBBS", "MD", - "MPH", "ND", "NP", "OD", "PA", "PAC", "PHARMD", "PHD", "PNP", "PSYD", "VMD", "WHNP"} + "DDS", "DMD", "DNP", "DO", "DPM", "DVM", "FNP", "GNP", "LAC", "LPN", "MBA", "MBBS", "MD", + "MPH", "ND", "NNP", "NP", "OD", "PA", "PAC", "PHARMD", "PHD", "PNP", "PSYD", "VMD", "WHNP"} ) contains := func(list []string, item string) bool { diff --git a/entity_job.go b/entity_job.go index 6272469..870eede 100644 --- a/entity_job.go +++ b/entity_job.go @@ -10,14 +10,15 @@ type JobEntity struct { BaseEntity //Job Info - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Timezone *string `json:"timezone,omitempty"` - EmploymentType *string `json:"employmentType,omitempty"` - DatePosted *string `json:"datePosted,omitempty"` - ValidThrough *string `json:"validThrough,omitempty"` - Keywords *[]string `json:"keywords,omitempty"` - Location *JobLocation `json:"location,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Timezone *string `json:"timezone,omitempty"` + EmploymentType *string `json:"employmentType,omitempty"` + DatePosted *string `json:"datePosted,omitempty"` + ValidThrough *string `json:"validThrough,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + Location *JobLocation `json:"location,omitempty"` + HiringOrganization *string `json:"hiringOrganization,omitemppty"` // Urls ApplicationURL *string `json:"applicationUrl,omitempty"` From 95cd99467a5c1bacf3b660f919b3623c87b575f7 Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Mon, 1 Nov 2021 13:52:51 -0400 Subject: [PATCH 201/285] print dump response error (#249) J=CR-2054 TEST=auto --- client.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 4b8407d..f853b4d 100644 --- a/client.go +++ b/client.go @@ -279,10 +279,9 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { if err != nil { b, ioError := httputil.DumpResponse(resp, true) if ioError != nil { - return resultResponse, err + return resultResponse, fmt.Errorf("io error: %s\n error: %s\n", ioError.Error(), err.Error()) } - errWithResponse := fmt.Errorf("request response: %s\n error: %s\n", string(b), err.Error()) - return resultResponse, errWithResponse + return resultResponse, fmt.Errorf("request response: %s\n error: %s\n", string(b), err.Error()) } else if len(resultResponse.Meta.Errors) > 0 { return resultResponse, resultResponse.Meta.Errors } else { From ea92ee0a3646a003b2b2dcc6e1df5b5b26317625 Mon Sep 17 00:00:00 2001 From: jpark Date: Tue, 2 Nov 2021 10:14:26 -0400 Subject: [PATCH 202/285] fix typo in hiringOrganization json --- entity_job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entity_job.go b/entity_job.go index 870eede..1d2e81a 100644 --- a/entity_job.go +++ b/entity_job.go @@ -18,7 +18,7 @@ type JobEntity struct { ValidThrough *string `json:"validThrough,omitempty"` Keywords *[]string `json:"keywords,omitempty"` Location *JobLocation `json:"location,omitempty"` - HiringOrganization *string `json:"hiringOrganization,omitemppty"` + HiringOrganization *string `json:"hiringOrganization,omitempty"` // Urls ApplicationURL *string `json:"applicationUrl,omitempty"` From de8e69b29c84c471bd7bc7b3c476b3bdea8f4833 Mon Sep 17 00:00:00 2001 From: Stephanie Harvey <31486703+stephh2@users.noreply.github.com> Date: Wed, 3 Nov 2021 16:46:56 -0400 Subject: [PATCH 203/285] Add analytics clicks event and update page views event (#252) --- analytics_data.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/analytics_data.go b/analytics_data.go index b68b550..c06e3aa 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -27,7 +27,7 @@ type AnalyticsData struct { AverageRating *float64 `json:"Average Rating"` NewReviews *int `json:"Reviews"` StorepagesSessions *int `json:"Storepages Sessions"` - StorepagesPageviews *int `json:"Page Views"` + StorepagesPageviews *int `json:"Pages Views"` StorepagesDrivingdirections *int `json:"Driving Directions"` StorepagesPhonecalls *int `json:"Taps to Call"` StorepagesCalltoactionclicks *int `json:"Call to Action Clicks"` @@ -59,6 +59,7 @@ type AnalyticsData struct { PartnerSite *string `json:"site"` CumulativeRating *float64 `json:"Rolling Average Rating"` Competitor *string `json:"competitor"` + Clicks *int `json:"CLICKS"` } func (y AnalyticsData) GetCompetitor() string { @@ -445,3 +446,10 @@ func (y AnalyticsData) GetMonth() string { } return "" } + +func (y AnalyticsData) GetClicks() int { + if y.Clicks != nil { + return *y.Clicks + } + return 0 +} From 75e59d1b4b5b4579b77842ecb1f10f8dead8a078 Mon Sep 17 00:00:00 2001 From: Stephanie Harvey <31486703+stephh2@users.noreply.github.com> Date: Thu, 4 Nov 2021 12:03:12 -0400 Subject: [PATCH 204/285] Add Entity ID metric to analytics data struct (#253) --- analytics_data.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/analytics_data.go b/analytics_data.go index c06e3aa..f312e70 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -53,6 +53,7 @@ type AnalyticsData struct { IstLocalPackShareOfSearch *float64 `json:"Ist Local Pack Share Of Search"` IstShareOfIntelligentSearch *float64 `json:"Ist Share Of Intelligent Search"` LocationId *string `json:"location_id"` + EntityId *string `json:"entity_id"` Month *string `json:"month"` ResponseTime *int `json:"Response Time (Hours)"` ResponseRate *int `json:"Response Rate"` @@ -440,6 +441,14 @@ func (y AnalyticsData) GetLocationId() string { return "" } +func (y AnalyticsData) GetEntityId() string { + if y.EntityId != nil { + return *y.EntityId + } + return "" +} + + func (y AnalyticsData) GetMonth() string { if y.Month != nil { return *y.Month From 14300f93b602ec58b9c3a446bbd9b28236acd109 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Tue, 9 Nov 2021 11:11:11 -0700 Subject: [PATCH 205/285] client.go error logging (#251) --- client.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index f853b4d..c754ecd 100644 --- a/client.go +++ b/client.go @@ -277,16 +277,23 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { } if err != nil { + errMsg := "error: " + err.Error() + if decodeError != nil { + errMsg = fmt.Sprintf("%s\nDecode() error: %s", errMsg, decodeError.Error()) + } b, ioError := httputil.DumpResponse(resp, true) if ioError != nil { - return resultResponse, fmt.Errorf("io error: %s\n error: %s\n", ioError.Error(), err.Error()) + errMsg = fmt.Sprintf("%s\nDumpResponse() error: %s", errMsg, ioError.Error()) + } else { + errMsg = fmt.Sprintf("%s\nResponse: %s", errMsg, string(b)) } - return resultResponse, fmt.Errorf("request response: %s\n error: %s\n", string(b), err.Error()) + return resultResponse, fmt.Errorf(errMsg) } else if len(resultResponse.Meta.Errors) > 0 { return resultResponse, resultResponse.Meta.Errors } else { return resultResponse, nil } + } return resultResponse, resultError } From 877ad628e9d0a800e7198cd92a2b16951e7bef81 Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Fri, 12 Nov 2021 11:46:28 -0500 Subject: [PATCH 206/285] price: add price object (#255) --- price.go | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 price.go diff --git a/price.go b/price.go new file mode 100644 index 0000000..be5b957 --- /dev/null +++ b/price.go @@ -0,0 +1,167 @@ +package yext + +type Price struct { + Value *string `json:"value,omitempty"` //TEXT + Currency **string `json:"currencyCode,omitempty"` //SINGLE_OPTION +} + +func NullablePrice(p Price) **Price { + pp := &p + return &pp +} + +func GetPrice(pp **Price) Price { + p := *pp + return *p +} + +var ValidCurrencyCodes = map[string]bool{ + "AED": true, + "AFN": true, + "ALL": true, + "AMD": true, + "ANG": true, + "AOA": true, + "ARS": true, + "AUD": true, + "AWG": true, + "AZN": true, + "BAM": true, + "BBD": true, + "BDT": true, + "BGN": true, + "BHD": true, + "BIF": true, + "BMD": true, + "BND": true, + "BOB": true, + "BRL": true, + "BSD": true, + "BTN": true, + "BWP": true, + "BYN": true, + "BZD": true, + "CAD": true, + "CHF": true, + "CLP": true, + "CNY": true, + "COP": true, + "CRC": true, + "CUP": true, + "CVE": true, + "CZK": true, + "DJF": true, + "DKK": true, + "DOP": true, + "DZD": true, + "EGP": true, + "ERN": true, + "ETB": true, + "EUR": true, + "FJD": true, + "FKP": true, + "GBP": true, + "GEL": true, + "GHS": true, + "GIP": true, + "GMD": true, + "GNF": true, + "GTQ": true, + "GYD": true, + "HKD": true, + "HRK": true, + "HTG": true, + "HUF": true, + "IDR": true, + "ILS": true, + "INR": true, + "IQD": true, + "IRR": true, + "ISK": true, + "JMD": true, + "JOD": true, + "JPY": true, + "KES": true, + "KGS": true, + "KHR": true, + "KMF": true, + "KRW": true, + "KWD": true, + "KYD": true, + "KZT": true, + "LAK": true, + "LBP": true, + "LKR": true, + "LSL": true, + "LYD": true, + "MAD": true, + "MDL": true, + "MGA": true, + "MKD": true, + "MMK": true, + "MNT": true, + "MOP": true, + "MRO": true, + "MUR": true, + "MVR": true, + "MWK": true, + "MXN": true, + "MYR": true, + "MZN": true, + "NAD": true, + "NGN": true, + "NIO": true, + "NOK": true, + "NPR": true, + "NZD": true, + "OMR": true, + "PAB": true, + "PEN": true, + "PGK": true, + "PHP": true, + "PKR": true, + "PLN": true, + "PYG": true, + "QAR": true, + "RON": true, + "RSD": true, + "RUB": true, + "RWF": true, + "SAR": true, + "SBD": true, + "SCR": true, + "SDG": true, + "SEK": true, + "SGD": true, + "SHP": true, + "SLL": true, + "SRD": true, + "SSP": true, + "STD": true, + "SYP": true, + "SZL": true, + "THB": true, + "TJS": true, + "TMT": true, + "TND": true, + "TOP": true, + "TRY": true, + "TTD": true, + "TWD": true, + "TZS": true, + "UAH": true, + "UGX": true, + "USD": true, + "UYU": true, + "UZS": true, + "VEF": true, + "VND": true, + "VUV": true, + "WST": true, + "XAF": true, + "XCD": true, + "XOF": true, + "XPF": true, + "ZAR": true, + "ZM": true, +} From 5a7e0050f31461b028191d06d6ca7d394ef6b29c Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Tue, 16 Nov 2021 13:14:14 -0500 Subject: [PATCH 207/285] retry network request on connection reset from server (#256) J=CR-2110 TEST=manual --- client.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index c754ecd..1950db0 100644 --- a/client.go +++ b/client.go @@ -3,6 +3,7 @@ package yext import ( "bytes" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -11,6 +12,7 @@ import ( "net/url" "strconv" "strings" + "syscall" "time" ) @@ -255,7 +257,12 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { } } - if resp.StatusCode >= 500 { + isConnectionReset := false + if decodeError != nil { + isConnectionReset = errors.Is(decodeError, syscall.ECONNRESET) + } + + if resp.StatusCode >= 500 || isConnectionReset { if decodeError != nil { resultError = decodeError resultResponse = nil From 70155e72c0770f48c3bab1c45572e5c6aed7ea6c Mon Sep 17 00:00:00 2001 From: jpark Date: Tue, 30 Nov 2021 15:33:56 -0500 Subject: [PATCH 208/285] feat(reviews): add reviews update endpoint --- review_service.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/review_service.go b/review_service.go index e4e7f7c..757d379 100644 --- a/review_service.go +++ b/review_service.go @@ -11,6 +11,19 @@ const reviewsPath = "reviews" const reviewInvitePath = "reviewinvites" +//Review update enums +const ( + ReviewStatusLive = "LIVE" + ReviewStatusQuarantined = "QUARANTINED" + ReviewStatusRemoved = "REMOVED" + + ReviewFlagStatusInappropriate = "INAPPROPRIATE" + ReviewFlagStatusSpam = "SPAM" + ReviewFlagStatusIrrelevant = "IRRELEVANT" + ReviewFlagStatusSensitive = "SENSITIVE" + ReviewFlagStatusNotFlagged = "NOT_FLAGGED" +) + var ( ReviewListMaxLimit = 50 ) @@ -47,6 +60,20 @@ type ReviewListResponse struct { NextPageToken string `json:"nextPageToken"` } +type ReviewUpdateOptions struct { + Rating float64 `json:"rating,omitempty"` + Content string `json:"content,omitempty"` + AuthorName string `json:"authorName,omitempty"` + AuthorEmail string `json:"authorEmail,omitempty"` + LocationId string `json:"locationId,omitempty"` + Status string `json:"status,omitempty"` + FlagStatus string `json:"flagStatus,omitempty"` +} + +type ReviewUpdateResponse struct { + Id string `json:"id"` +} + type ReviewCreateInvitationResponse struct { Id string `json:"id"` LocationId string `json:"locationId"` @@ -195,6 +222,16 @@ func (l *ReviewService) Get(id int) (*Review, *Response, error) { return &v, r, nil } +func (l *ReviewService) Update(id int, opts *ReviewUpdateOptions) (*ReviewUpdateResponse, *Response, error) { + var v ReviewUpdateResponse + r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%d", reviewsPath, id), opts, &v) + if err != nil { + return nil, r, err + } + + return &v, r, nil +} + func (l *ReviewService) CreateInvitation(jsonData []*Reviewer) ([]*ReviewCreateInvitationResponse, *Response, error) { var v []*ReviewCreateInvitationResponse r, err := l.client.DoRequestJSON("POST", reviewInvitePath, jsonData, &v) From 3c2b347ca6c5f26298f863ecc6c3c31900ee3b8e Mon Sep 17 00:00:00 2001 From: sharvey Date: Fri, 3 Dec 2021 12:34:29 -0500 Subject: [PATCH 209/285] Add label ids as review request param --- review_service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/review_service.go b/review_service.go index 757d379..51cf97a 100644 --- a/review_service.go +++ b/review_service.go @@ -38,6 +38,7 @@ type ReviewListOptions struct { FolderId string Countries []string LocationLabels []string + LabelIds []string PublisherIds []string ReviewContent string MinRating float64 @@ -145,6 +146,9 @@ func addReviewListOptions(requrl string, opts *ReviewListOptions) (string, error if opts.LocationLabels != nil { q.Add("locationLabels", strings.Join(opts.LocationLabels, ",")) } + if opts.LabelIds != nil { + q.Add("labelIds", strings.Join(opts.LabelIds, ",")) + } if opts.PublisherIds != nil { q.Add("publisherIds", strings.Join(opts.PublisherIds, ",")) } From 247a84f5ff16a478b8da80532be7facb6f97651a Mon Sep 17 00:00:00 2001 From: Daniel Wu Date: Tue, 7 Dec 2021 15:13:32 -0500 Subject: [PATCH 210/285] feat: add service for admin apis (#254) --- account_service.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++ client.go | 6 +++++ 2 files changed, 72 insertions(+) create mode 100644 account_service.go diff --git a/account_service.go b/account_service.go new file mode 100644 index 0000000..a7a9f85 --- /dev/null +++ b/account_service.go @@ -0,0 +1,66 @@ +package yext + +import "fmt" + +const createSubAccountPath = "createsubaccount" + +type AccountService struct { + client *Client +} + +type CreateSubAccountRequest struct { + Id string `json:"newSubAccountId"` + Name string `json:"newSubAccountName"` + CountryCode string `json:"countryCode"` +} + +type SubAccount struct { + AccountId *string `json:"accountId"` + LocationCount *int `json:"locationCount"` + SubAccountCount *int `json:"subAccountCount"` + AccountName *string `json:"accountName"` + ContactFirstName *string `json:"contactFirstName"` + ContactLastName *string `json:"contactLastName"` + ContactPhone *string `json:"contactPhone"` + ContactEmail *string `json:"contactEmail"` +} + +type ListResponse struct { + Count *int `json:"count"` + Accounts []*SubAccount `json:"accounts"` +} + +func (a *AccountService) CreateSubAccount(createSubAccountRequest *CreateSubAccountRequest) (*Response, error) { + r, err := a.client.DoRequestJSON("POST", fmt.Sprintf("%s", createSubAccountPath), createSubAccountRequest, nil) + if err != nil { + return r, err + } + + return r, nil +} + +func (a *AccountService) List(opts *ListOptions) (*ListResponse, *Response, error) { + arr := &ListResponse{} + requrl, err := addListOptions(ACCOUNTS_PATH, opts) + if err != nil { + return nil, nil, err + } + + r, err := a.client.DoRootRequest("GET", requrl, arr) + if err != nil { + return nil, r, err + } + + return arr, r, nil +} + +func (a *AccountService) Get(accountId string) (*SubAccount, *Response, error) { + v := &SubAccount{} + + r, err := a.client.DoRootRequest("GET", fmt.Sprintf("%s/%s", ACCOUNTS_PATH, accountId), v) + if err != nil { + return nil, r, err + } + + return v, r, nil +} diff --git a/client.go b/client.go index 1950db0..bb158ad 100644 --- a/client.go +++ b/client.go @@ -25,6 +25,7 @@ type ListOptions struct { Offset int DisableCountValidation bool PageToken string + Name string } type Client struct { @@ -46,6 +47,7 @@ type Client struct { AnalyticsService *AnalyticsService EntityService *EntityService LanguageProfileService *LanguageProfileService + AccountService *AccountService } func NewClient(config *Config) *Client { @@ -69,6 +71,7 @@ func NewClient(config *Config) *Client { c.EntityService = &EntityService{client: c} c.EntityService.RegisterDefaultEntities() c.LanguageProfileService = &LanguageProfileService{client: c} + c.AccountService = &AccountService{client: c} c.LanguageProfileService.RegisterDefaultEntities() return c } @@ -371,6 +374,9 @@ func addListOptions(requrl string, opts *ListOptions) (string, error) { } q := u.Query() + if opts.Name != "" { + q.Add("name", opts.Name) + } if opts.Limit != 0 { q.Add("limit", strconv.Itoa(opts.Limit)) } From f8b7e7c4efe25117b9513ec325b6f4da26fdc149 Mon Sep 17 00:00:00 2001 From: Nathan Yellowhair <37886474+Zavoky@users.noreply.github.com> Date: Wed, 8 Dec 2021 12:49:23 -0700 Subject: [PATCH 211/285] nil platform bool != zero value (#259) * nil platform bool != zero value when dealing with yes/no fields in the Yext platform, there is also an unspecified value (null). This change accounts for cases where we want to set that unspecified value to 'No' --- entity_diff_test.go | 7 +++++-- location_diff.go | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/entity_diff_test.go b/entity_diff_test.go index 9611183..d58b1bb 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -341,13 +341,15 @@ func TestEntityDiff(t *testing.T) { newValue: nil, isDiff: false, }, + // tests K & L differ from similar tests for other types, since nil isn't the zero value for booleans diffTest{ name: "**Bool: base is nil (nil is empty), new is zero value (K)", property: "Closed", baseValue: nil, newValue: NullableBool(false), baseNilIsEmpty: true, - isDiff: false, + isDiff: true, + deltaValue: NullableBool(false), }, diffTest{ name: "**Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", @@ -356,7 +358,8 @@ func TestEntityDiff(t *testing.T) { newValue: NullableBool(false), baseNilIsEmpty: true, newNilIsEmpty: true, - isDiff: false, + isDiff: true, + deltaValue: NullableBool(false), }, diffTest{ name: "**Bool: base is zero value, new is nil (nil is empty) (L)", diff --git a/location_diff.go b/location_diff.go index 5f06e50..df06749 100644 --- a/location_diff.go +++ b/location_diff.go @@ -147,7 +147,12 @@ func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { case reflect.Float64: return v.Float() == 0.0 case reflect.Ptr, reflect.Interface: - if v.IsNil() && !interpretNilAsZeroValue { + isNil := v.IsNil() + if isNil && v.Type() == reflect.TypeOf((**bool)(nil)) { + // nil booleans represent a non-zero value in the platform ("Unspecified" / unset) + return false + } + if isNil && !interpretNilAsZeroValue { return false } return IsZeroValue(v.Elem(), true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) From 2db3bf092ab0802c387bb2a4392d5809d2356e7a Mon Sep 17 00:00:00 2001 From: nyellowhair Date: Mon, 13 Dec 2021 22:51:45 -0700 Subject: [PATCH 212/285] Revert "nil platform bool != zero value (#259)" This reverts commit f8b7e7c4efe25117b9513ec325b6f4da26fdc149. Reverting until I can properly test the results --- entity_diff_test.go | 7 ++----- location_diff.go | 7 +------ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/entity_diff_test.go b/entity_diff_test.go index d58b1bb..9611183 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -341,15 +341,13 @@ func TestEntityDiff(t *testing.T) { newValue: nil, isDiff: false, }, - // tests K & L differ from similar tests for other types, since nil isn't the zero value for booleans diffTest{ name: "**Bool: base is nil (nil is empty), new is zero value (K)", property: "Closed", baseValue: nil, newValue: NullableBool(false), baseNilIsEmpty: true, - isDiff: true, - deltaValue: NullableBool(false), + isDiff: false, }, diffTest{ name: "**Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", @@ -358,8 +356,7 @@ func TestEntityDiff(t *testing.T) { newValue: NullableBool(false), baseNilIsEmpty: true, newNilIsEmpty: true, - isDiff: true, - deltaValue: NullableBool(false), + isDiff: false, }, diffTest{ name: "**Bool: base is zero value, new is nil (nil is empty) (L)", diff --git a/location_diff.go b/location_diff.go index df06749..5f06e50 100644 --- a/location_diff.go +++ b/location_diff.go @@ -147,12 +147,7 @@ func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { case reflect.Float64: return v.Float() == 0.0 case reflect.Ptr, reflect.Interface: - isNil := v.IsNil() - if isNil && v.Type() == reflect.TypeOf((**bool)(nil)) { - // nil booleans represent a non-zero value in the platform ("Unspecified" / unset) - return false - } - if isNil && !interpretNilAsZeroValue { + if v.IsNil() && !interpretNilAsZeroValue { return false } return IsZeroValue(v.Elem(), true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) From f0cc46446f92c2bf66e16a65f46ea0968bd58a1d Mon Sep 17 00:00:00 2001 From: Daniel Wu Date: Thu, 16 Dec 2021 12:09:43 -0500 Subject: [PATCH 213/285] feat: add services service for add requests (#260) https://hitchhikers.yext.com/docs/adminapis/addrequests/ --- client.go | 2 ++ services_service.go | 75 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 services_service.go diff --git a/client.go b/client.go index bb158ad..f340d1e 100644 --- a/client.go +++ b/client.go @@ -48,6 +48,7 @@ type Client struct { EntityService *EntityService LanguageProfileService *LanguageProfileService AccountService *AccountService + ServicesService *ServicesService } func NewClient(config *Config) *Client { @@ -72,6 +73,7 @@ func NewClient(config *Config) *Client { c.EntityService.RegisterDefaultEntities() c.LanguageProfileService = &LanguageProfileService{client: c} c.AccountService = &AccountService{client: c} + c.ServicesService = &ServicesService{client: c} c.LanguageProfileService.RegisterDefaultEntities() return c } diff --git a/services_service.go b/services_service.go new file mode 100644 index 0000000..09ebcd1 --- /dev/null +++ b/services_service.go @@ -0,0 +1,75 @@ +package yext + +const createExistingSubAccountPath = "existingsubaccountaddrequest" +const createExistingLocationPath = "existinglocationaddrequest" + +type ServicesService struct { + client *Client +} + +type ExistingSubAccountAddRequest struct { + SubAccountId string `json:"subAccountId"` + SkuAdditions []SkuAddition `json:"skuAdditions"` + AgreementId string `json:"agreementId"` +} + +type ExistingLocationAddRequest struct { + ExistingLocationId string `json:"existingLocationId"` + ExistingLocationAccountId string `json:"existingLocationAccountId"` + Skus []string `json:"skus"` + AgreementId string `json:"agreementId"` + ForceReview string `json:"forceReview"` +} + +type SkuAddition struct { + Sku string `json:"sku"` + Quantity string `json:"quantity"` +} + +type ExistingSubAccountAddResponse struct { + Id int `json:"id"` + SubAccountId string `json:"subAccountId"` + SkuAdditions []SkuAddition `json:"skuAdditions"` + AgreementId string `json:"agreementId"` + Status string `json:"status"` + DateSubmitted string `json:"dateSubmitted"` + StatusDetail string `json:"statusDetail"` +} + +type ExistingLocationAddResponse struct { + Id int `json:"id"` + LocationMode string `json:"locationMode"` + ExistingLocationId string `json:"existingLocationId"` + NewLocationId string `json:"newLocationId"` + NewLocationAccountId string `json:"newLocationAccountId"` + NewLocationAccountName string `json:"newLocationAccountName"` + NewAccountParentAccountId string `json:"newAccountParentAccountId"` + NewLocationData string `json:"newLocationData"` + NewEntityData string `json:"newEntityData"` + Skus []string `json:"skus"` + AgreementId string `json:"agreementId"` + Status string `json:"status"` + DateSubmitted string `json:"dateSubmitted"` + DateCompleted string `json:"dateCompleted"` + StatusDetail string `json:"statusDetail"` +} + +func (a *ServicesService) CreateAddRequestExistingSubAccount(existingSubAccountAddRequest *ExistingSubAccountAddRequest) (*ExistingSubAccountAddResponse, *Response, error) { + var v *ExistingSubAccountAddResponse + r, err := a.client.DoRequest("POST", createExistingSubAccountPath, &v) + if err != nil { + return v, r, err + } + + return v, r, nil +} + +func (a *ServicesService) CreateAddRequestExistingLocation(existingLocationAddRequest *ExistingLocationAddRequest) (*ExistingLocationAddResponse, *Response, error) { + var v *ExistingLocationAddResponse + r, err := a.client.DoRequest("POST", createExistingLocationPath, &v) + if err != nil { + return v, r, err + } + + return v, r, nil +} From ac7e78dce3f7dd0bac368d12c6d9e99663bcce0f Mon Sep 17 00:00:00 2001 From: jpark Date: Tue, 11 Jan 2022 16:28:44 -0500 Subject: [PATCH 214/285] feat: add FAQ fields to locationEntity --- entity_location.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/entity_location.go b/entity_location.go index 3d0bc43..9a33f42 100644 --- a/entity_location.go +++ b/entity_location.go @@ -66,6 +66,8 @@ type LocationEntity struct { // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API Specialties *[]string `json:"specialities,omitempty"` + FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` + Languages *[]string `json:"languages,omitempty"` Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` @@ -1031,3 +1033,22 @@ func NullableCTA(c CTA) **CTA { p := &c return &p } + +type FAQField struct { + Answer *string `json:"answer,omitempty"` + Question *string `json:"question,omitempty"` +} + +func (y FAQField) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func ToFAQ(c FAQField) *FAQField { + return &c +} + +func NullableFAQ(c FAQField) **FAQField { + p := &c + return &p +} From 577ac0651a120913dc8d60ff7b9e3e27bddfa643 Mon Sep 17 00:00:00 2001 From: Nathan <37886474+Zavoky@users.noreply.github.com> Date: Tue, 18 Jan 2022 12:28:49 -0700 Subject: [PATCH 215/285] add 'strip unsupported formats' (#262) --- entity_service.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/entity_service.go b/entity_service.go index f51217f..c9f8351 100644 --- a/entity_service.go +++ b/entity_service.go @@ -51,9 +51,10 @@ type EntityListOptions struct { // Used for Create and Edit type EntityServiceOptions struct { - TemplateId string `json:"templateId,omitempty"` - TemplateFields []string `json:"templateFields,omitempty"` - Format string `json:"format,omitempty"` + TemplateId string `json:"templateId,omitempty"` + TemplateFields []string `json:"templateFields,omitempty"` + Format string `json:"format,omitempty"` + StripUnsupportedFormats bool `json:"stripUnsupportedFormats,omitempty"` } type EntityListResponse struct { @@ -168,6 +169,9 @@ func addEntityServiceOptions(requrl string, opts *EntityServiceOptions) (string, if opts.Format != "" { q.Add("format", opts.Format) } + if opts.StripUnsupportedFormats { + q.Add("stripUnsupportedFormats", "true") + } if len(opts.TemplateFields) > 0 { q.Add("templateFields", strings.Join(opts.TemplateFields, ",")) } From 424d01c7a7c554fd6ab49dfa728a61e9fbc9c143 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 20 Jan 2022 14:31:00 -0500 Subject: [PATCH 216/285] review-service: add UpdateLabels, externalId (#263) * review-service:add review update * review: add externalId field --- review.go | 2 ++ review_service.go | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/review.go b/review.go index e48d51f..44d5422 100644 --- a/review.go +++ b/review.go @@ -15,6 +15,7 @@ type Reviewer struct { type Review struct { Id *int `json:"id"` LocationId *string `json:"locationId"` + ExternalId *string `json:"externalId"` // Must have v param >= 20220120 AccountId *string `json:"accountId"` PublisherId *string `json:"publisherId"` Rating *float64 `json:"rating"` @@ -40,6 +41,7 @@ type Review struct { type ReviewCreate struct { LocationId *string `json:"locationId"` + ExternalId *string `json:"externalId"` // Must have v param >= 20220120 AccountId *string `json:"accountId"` Rating *float64 `json:"rating"` Content *string `json:"content"` diff --git a/review_service.go b/review_service.go index 51cf97a..804dde3 100644 --- a/review_service.go +++ b/review_service.go @@ -25,7 +25,7 @@ const ( ) var ( - ReviewListMaxLimit = 50 + ReviewListMaxLimit = 100 ) type ReviewService struct { @@ -255,3 +255,17 @@ func (l *ReviewService) CreateReview(jsonData *ReviewCreate) (*ReviewCreateRevie return v, r, nil } + +type ReviewUpdateLabelOptions struct { + LabelIds *[]int `json:"labelIds,omitempty"` +} + +func (l *ReviewService) UpdateLabels(id int, opts *ReviewUpdateLabelOptions) (*ReviewUpdateResponse, *Response, error) { + var v ReviewUpdateResponse + r, err := l.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%d/labels", reviewsPath, id), opts, &v) + if err != nil { + return nil, r, err + } + + return &v, r, nil +} From 22522e061f391e3a0cea81814a834a327e3a7853 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 20 Jan 2022 14:54:24 -0500 Subject: [PATCH 217/285] review: removing duplicate external id (#264) --- review.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/review.go b/review.go index 44d5422..5dd8c0a 100644 --- a/review.go +++ b/review.go @@ -15,7 +15,6 @@ type Reviewer struct { type Review struct { Id *int `json:"id"` LocationId *string `json:"locationId"` - ExternalId *string `json:"externalId"` // Must have v param >= 20220120 AccountId *string `json:"accountId"` PublisherId *string `json:"publisherId"` Rating *float64 `json:"rating"` @@ -31,7 +30,7 @@ type Review struct { ReviewLanguage *string `json:"reviewLanguage"` Comments *[]Comment `json:"comments"` LabelIds *[]int `json:"labelIds"` - ExternalId *string `json:"externalId"` + ExternalId *string `json:"externalId"` // Must have v param >= 20220120 ReviewLabels *[]ReviewLabel `json:"reviewLabels"` ReviewType *string `json:"reviewType"` Recommendation *string `json:"recommendation"` From 91bd932fd7d318fdb8a85484443366281e261446 Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Thu, 20 Jan 2022 18:12:54 -0500 Subject: [PATCH 218/285] review-service: add external id to update object (#265) --- review_service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/review_service.go b/review_service.go index 804dde3..cb54512 100644 --- a/review_service.go +++ b/review_service.go @@ -69,6 +69,7 @@ type ReviewUpdateOptions struct { LocationId string `json:"locationId,omitempty"` Status string `json:"status,omitempty"` FlagStatus string `json:"flagStatus,omitempty"` + ExternalId *string `json:"externalId"` // Must have v param >= 20220120 } type ReviewUpdateResponse struct { From c03f9c195c8f14fc5099863c2c8ed09295265f42 Mon Sep 17 00:00:00 2001 From: jpark Date: Thu, 20 Jan 2022 12:40:47 -0500 Subject: [PATCH 219/285] add new answers values to analytics structs --- analytics_data.go | 33 +++++++++++++++++++- analytics_service.go | 73 +++++++++++++++++++++++--------------------- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/analytics_data.go b/analytics_data.go index f312e70..561870b 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -61,6 +61,10 @@ type AnalyticsData struct { CumulativeRating *float64 `json:"Rolling Average Rating"` Competitor *string `json:"competitor"` Clicks *int `json:"CLICKS"` + AnswersSearchesWithClicks *int `json:"ANSWERS_SEARCHES_WITH_CLICKS"` + AnswersSearches *int `json:"ANSWERS_SEARCHES"` + Week *string `json:"week"` + AnswersKGResultRate *float64 `json:"ANSWERS_KG_RESULT_RATE"` } func (y AnalyticsData) GetCompetitor() string { @@ -448,7 +452,6 @@ func (y AnalyticsData) GetEntityId() string { return "" } - func (y AnalyticsData) GetMonth() string { if y.Month != nil { return *y.Month @@ -462,3 +465,31 @@ func (y AnalyticsData) GetClicks() int { } return 0 } + +func (y AnalyticsData) GetAnswersSearches() int { + if y.AnswersSearches != nil { + return *y.AnswersSearches + } + return 0 +} + +func (y AnalyticsData) GetAnswersSearchesWithClicks() int { + if y.AnswersSearchesWithClicks != nil { + return *y.AnswersSearchesWithClicks + } + return 0 +} + +func (y AnalyticsData) GetWeek() string { + if y.Week != nil { + return *y.Week + } + return "" +} + +func (y AnalyticsData) GetAnswersKGResultRate() float64 { + if y.AnswersKGResultRate != nil { + return *y.AnswersKGResultRate + } + return -1 +} \ No newline at end of file diff --git a/analytics_service.go b/analytics_service.go index 5613f1f..05f0542 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -7,41 +7,44 @@ type AnalyticsService struct { } type AnalyticsFilters struct { - StartDate *string `json:"startDate"` - EndDate *string `json:"endDate"` - LocationIds *[]string `json:"locationIds"` - EntityTypes *[]string `json:"entityType"` - FolderId *int `json:"folderId"` - Countries *[]string `json:"countries"` - LocationLabels *[]string `json:"locationLabels"` - Platforms *[]string `json:"platforms"` - GoogleActionType *[]string `json:"googleActionType"` - CustomerActionType *[]string `json:"customerActionType"` - GoogleQueryType *[]string `json:"googleQueryType"` - Hours *[]int `json:"hours"` - Ratings *[]int `json:"ratings"` - FrequentWords *[]string `json:"frequentWords"` - Partners *[]int `json:"partners"` - ReviewLabels *[]int `json:"reviewLabels"` - PageTypes *[]string `json:"pageTypes"` - ListingsLiveType *string `json:"listingsLiveType"` - QueryTemplate *[]string `json:"queryTemplate"` - SearchEngine *[]string `json:"searchEngine"` - Keyword *[]string `json:"keyword"` - Competitor *[]string `json:"competitor"` - MatchPosition *[]string `json:"matchPosition"` - SearchResultType *[]string `json:"searchResultType"` - MatchType *[]string `json:"matchType"` - MinSearchFrequency *int `json:"minSearchFrequency"` - MaxSearchFrequency *int `json:"maxSearchFrequency"` - FoursquareCheckinType *string `json:"foursquareCheckinType"` - FoursquareCheckinAge *string `json:"foursquareCheckinAge"` - FoursquareCheckinGender *string `json:"foursquareCheckinGender"` - InstagramContentType *string `json:"instagramContentType"` - Age *[]string `json:"age"` - Gender *string `json:"gender"` - FacebookImpressionType *[]string `json:"facebookImpressionType"` - FacebookStoryType *[]string `json:"facebookStoryType"` + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + LocationIds *[]string `json:"locationIds"` + EntityTypes *[]string `json:"entityType"` + FolderId *int `json:"folderId"` + Countries *[]string `json:"countries"` + LocationLabels *[]string `json:"locationLabels"` + Platforms *[]string `json:"platforms"` + GoogleActionType *[]string `json:"googleActionType"` + CustomerActionType *[]string `json:"customerActionType"` + GoogleQueryType *[]string `json:"googleQueryType"` + Hours *[]int `json:"hours"` + Ratings *[]int `json:"ratings"` + FrequentWords *[]string `json:"frequentWords"` + Partners *[]int `json:"partners"` + ReviewLabels *[]int `json:"reviewLabels"` + PageTypes *[]string `json:"pageTypes"` + ListingsLiveType *string `json:"listingsLiveType"` + QueryTemplate *[]string `json:"queryTemplate"` + SearchEngine *[]string `json:"searchEngine"` + Keyword *[]string `json:"keyword"` + Competitor *[]string `json:"competitor"` + MatchPosition *[]string `json:"matchPosition"` + SearchResultType *[]string `json:"searchResultType"` + MatchType *[]string `json:"matchType"` + MinSearchFrequency *int `json:"minSearchFrequency"` + MaxSearchFrequency *int `json:"maxSearchFrequency"` + FoursquareCheckinType *string `json:"foursquareCheckinType"` + FoursquareCheckinAge *string `json:"foursquareCheckinAge"` + FoursquareCheckinGender *string `json:"foursquareCheckinGender"` + InstagramContentType *string `json:"instagramContentType"` + Age *[]string `json:"age"` + Gender *string `json:"gender"` + FacebookImpressionType *[]string `json:"facebookImpressionType"` + FacebookStoryType *[]string `json:"facebookStoryType"` + AnswersExperience *[]string `json:"ANSWERS_EXPERIENCE"` + AnswersConfigurationVersionLabel *string `json:"ANSWERS_CONFIGURATION_VERSION_LABEL"` + AnswersTrafficType *[]string `json:"ANSWERS_TRAFFIC_TYPE"` } type AnalyticsReportRequest struct { From 2041c5ea906cfcd42399f21522ec68c89e265d94 Mon Sep 17 00:00:00 2001 From: Cory Blair <36084758+cblair28@users.noreply.github.com> Date: Thu, 10 Feb 2022 13:11:35 -0500 Subject: [PATCH 220/285] add Entity struct to Reviewer struct (#267) Co-authored-by: sharvey --- review.go | 19 ++++++++++++------- review_service.go | 19 ++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/review.go b/review.go index 5dd8c0a..fb712fc 100644 --- a/review.go +++ b/review.go @@ -3,13 +3,18 @@ package yext import "encoding/json" type Reviewer struct { - LocationId *string `json:"locationId,omitempty"` - FirstName *string `json:"firstName,omitempty"` - LastName *string `json:"lastName,omitempty"` - Contact *string `json:"contact,omitempty"` - Image *bool `json:"image,omitempty"` - TemplateId *string `json:"templateId,omitempty"` - LabelIds []*string `json:"labelIds,omitempty"` + LocationId *string `json:"locationId,omitempty"` + Entity *ReviewEntity `json:"entity,omitempty"` // Must have v param >= 20210728 + FirstName *string `json:"firstName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Contact *string `json:"contact,omitempty"` + Image *bool `json:"image,omitempty"` + TemplateId *string `json:"templateId,omitempty"` + LabelIds []*string `json:"labelIds,omitempty"` +} + +type ReviewEntity struct { + Id string `json:"id"` } type Review struct { diff --git a/review_service.go b/review_service.go index cb54512..9bfe084 100644 --- a/review_service.go +++ b/review_service.go @@ -77,15 +77,16 @@ type ReviewUpdateResponse struct { } type ReviewCreateInvitationResponse struct { - Id string `json:"id"` - LocationId string `json:"locationId"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Contact string `json:"contact"` - Image bool `json:"image"` - TemplateId int `json:"templateId"` - Status string `json:"status"` - Details string `json:"details"` + Id string `json:"id"` + LocationId string `json:"locationId"` + Entity *ReviewEntity `json:"entity,omitempty"` // Must have v param >= 20210728 + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Contact string `json:"contact"` + Image bool `json:"image"` + TemplateId int `json:"templateId"` + Status string `json:"status"` + Details string `json:"details"` } type ReviewCreateReviewResponse struct { From 29de764f220821e47a45d73ace48539a4f87e593 Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Mon, 7 Mar 2022 16:54:22 -0500 Subject: [PATCH 221/285] add pre processing option ot menu list data (#268) needed for subway etl to cut down on memory usage J=PC-122295 TEST=manual --- list_service.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/list_service.go b/list_service.go index fd585ff..1f00fdb 100644 --- a/list_service.go +++ b/list_service.go @@ -136,6 +136,29 @@ func (e *ListService) ListEventLists(opts *ListOptions) (*EventListsResponse, *R return v, r, nil } +func (e *ListService) ListAllMenuListsWithDataFunc(dataFunc func(list *MenuList) *MenuList) ([]*MenuList, error) { + var menuLists []*MenuList + var lr listRetriever = func(opts *ListOptions) (int, int, error) { + plr, _, err := e.ListMenuLists(opts) + if err != nil { + return 0, 0, err + } + + processedLists := []*MenuList{} + for _, list := range plr.MenuLists { + processedLists = append(processedLists, dataFunc(list)) + } + menuLists = append(menuLists, processedLists...) + return len(plr.MenuLists), plr.Count, err + } + + if err := listHelper(lr, &ListOptions{Limit: ListListMaxLimit}); err != nil { + return nil, err + } else { + return menuLists, nil + } +} + func (e *ListService) ListAllMenuLists() ([]*MenuList, error) { var menuLists []*MenuList var lr listRetriever = func(opts *ListOptions) (int, int, error) { From e2dbfc981e87bd439824abdec125bea587457419 Mon Sep 17 00:00:00 2001 From: sri-jayakumar Date: Tue, 8 Mar 2022 11:14:40 -0500 Subject: [PATCH 222/285] custom-field-service: add translations field to options (#269) --- location_customfield.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/location_customfield.go b/location_customfield.go index 6dd50ab..143258f 100644 --- a/location_customfield.go +++ b/location_customfield.go @@ -16,9 +16,15 @@ var ( UnsetPhotoValue = (*CustomLocationPhoto)(nil) ) +type Translation struct { + LanguageCode string `json:"languageCode"` + Value string `json:"value"` +} + type CustomFieldOption struct { - Key string `json:"key,omitempty"` - Value string `json:"value"` + Key string `json:"key,omitempty"` + Value string `json:"value"` + Translations []Translation `json:"translations,omitempty"` } type CustomFieldValue interface { From e9c26a7cd30ee20ddf1810926169834468ffd73e Mon Sep 17 00:00:00 2001 From: Marc Pittinsky Date: Thu, 17 Mar 2022 13:11:30 -0400 Subject: [PATCH 223/285] update service existinglocationaddresponse type for agreementid (#271) --- services_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services_service.go b/services_service.go index 09ebcd1..ddaf4bb 100644 --- a/services_service.go +++ b/services_service.go @@ -47,7 +47,7 @@ type ExistingLocationAddResponse struct { NewLocationData string `json:"newLocationData"` NewEntityData string `json:"newEntityData"` Skus []string `json:"skus"` - AgreementId string `json:"agreementId"` + AgreementId int `json:"agreementId"` Status string `json:"status"` DateSubmitted string `json:"dateSubmitted"` DateCompleted string `json:"dateCompleted"` From ce987ac3d1ba8080e37ba1f0165cd19fb2303d95 Mon Sep 17 00:00:00 2001 From: mtusman Date: Thu, 17 Mar 2022 20:19:31 +0000 Subject: [PATCH 224/285] add rendered option for entity language profiles (#270) --- entity_service.go | 4 ++++ entity_service_test.go | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/entity_service.go b/entity_service.go index c9f8351..467d430 100644 --- a/entity_service.go +++ b/entity_service.go @@ -43,6 +43,7 @@ type EntityListOptions struct { ListOptions SearchIDs []string ResolvePlaceholders bool + Rendered bool // will return resolved placeholders for language profiles EntityTypes []string Fields []string Filter string @@ -200,6 +201,9 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if opts.ResolvePlaceholders { q.Add("resolvePlaceholders", "true") } + if opts.Rendered { + q.Add("rendered", "true") + } if len(opts.EntityTypes) > 0 { q.Add("entityTypes", strings.Join(opts.EntityTypes, ",")) } diff --git a/entity_service_test.go b/entity_service_test.go index b313cd0..acb569f 100644 --- a/entity_service_test.go +++ b/entity_service_test.go @@ -57,6 +57,7 @@ func TestEntityListOptions(t *testing.T) { searchIDs string entityTypes string resolvePlaceholders bool + rendered bool filter string format string }{ @@ -141,6 +142,14 @@ func TestEntityListOptions(t *testing.T) { filter: "", format: "none", }, + { + opts: &EntityListOptions{Rendered: true}, + limit: "", + token: "", + searchIDs: "", + rendered: true, + filter: "", + }, } for _, test := range tests { @@ -162,6 +171,10 @@ func TestEntityListOptions(t *testing.T) { if v == "true" && !test.resolvePlaceholders || v == "" && test.resolvePlaceholders || v == "false" && test.resolvePlaceholders { t.Errorf("Wanted resolvePlaceholders %t, got %s", test.resolvePlaceholders, v) } + v = r.URL.Query().Get("rendered") + if v == "true" && !test.rendered || v == "" && test.rendered || v == "false" && test.rendered { + t.Errorf("Wanted rendered %t, got %s", test.rendered, v) + } if v := r.URL.Query().Get("filter"); v != test.filter { t.Errorf("Wanted filter %s, got %s", test.filter, v) } From 482a7d9566f2cb0b429883e3cce47319275dfcdb Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Thu, 24 Mar 2022 23:37:11 -0400 Subject: [PATCH 225/285] adds GoogleMyBusinessLabels field to location entities (#272) TBRing --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 9a33f42..e2dfd0b 100644 --- a/entity_location.go +++ b/entity_location.go @@ -113,6 +113,7 @@ type LocationEntity struct { FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` From 82bffe9ea41eca845b7576754ce936c6d1d55ed0 Mon Sep 17 00:00:00 2001 From: afitzgerald Date: Sat, 9 Apr 2022 02:18:16 -0400 Subject: [PATCH 226/285] adds pickupdelivery field --- entity_restaurant.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_restaurant.go b/entity_restaurant.go index bbe9e64..c4414ce 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -41,6 +41,7 @@ type RestaurantEntity struct { Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` Geomodifier *string `json:"geomodifier,omitempty"` + PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From 692866ca4f121f6ecbea35c5ea968eaeb7ff329c Mon Sep 17 00:00:00 2001 From: nyellowhair Date: Tue, 12 Apr 2022 11:06:48 -0700 Subject: [PATCH 227/285] added employmentType field to job entities --- entity_job.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/entity_job.go b/entity_job.go index 1d2e81a..1f4cccd 100644 --- a/entity_job.go +++ b/entity_job.go @@ -4,21 +4,32 @@ import ( "encoding/json" ) -const ENTITYTYPE_JOB EntityType = "job" +const ( + ENTITYTYPE_JOB EntityType = "job" + + EmploymentTypeFullTime EmploymentType = "FULL_TIME" + EmploymentTypePartTime EmploymentType = "PART_TIME" + EmploymentTypeContractor EmploymentType = "CONTRACTOR" + EmploymentTypeTemporary EmploymentType = "TEMPORARY" + EmploymentTypeIntern EmploymentType = "INTERN" + EmploymentTypeVolunteer EmploymentType = "VOLUNTEER" + EmploymentTypePerDiem EmploymentType = "PER_DIEM" + EmploymentTypeOther EmploymentType = "OTHER" +) type JobEntity struct { BaseEntity //Job Info - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Timezone *string `json:"timezone,omitempty"` - EmploymentType *string `json:"employmentType,omitempty"` - DatePosted *string `json:"datePosted,omitempty"` - ValidThrough *string `json:"validThrough,omitempty"` - Keywords *[]string `json:"keywords,omitempty"` - Location *JobLocation `json:"location,omitempty"` - HiringOrganization *string `json:"hiringOrganization,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Timezone *string `json:"timezone,omitempty"` + EmploymentType *EmploymentType `json:"employmentType,omitempty"` + DatePosted *string `json:"datePosted,omitempty"` + ValidThrough *string `json:"validThrough,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + Location *JobLocation `json:"location,omitempty"` + HiringOrganization *string `json:"hiringOrganization,omitempty"` // Urls ApplicationURL *string `json:"applicationUrl,omitempty"` @@ -58,7 +69,7 @@ func (j JobEntity) GetTimezone() string { return "" } -func (j JobEntity) GetEmploymentType() string { +func (j JobEntity) GetEmploymentType() EmploymentType { if j.EmploymentType != nil { return *j.EmploymentType } @@ -111,3 +122,11 @@ func (j *JobEntity) String() string { b, _ := json.Marshal(j) return string(b) } + +type EmploymentType string + +func NewEmploymentType(v EmploymentType) *EmploymentType { + p := new(EmploymentType) + *p = v + return p +} From e8b1aef24bfa9ecbfa695f24510a9455aaa7d1ac Mon Sep 17 00:00:00 2001 From: nyellowhair Date: Wed, 4 May 2022 15:33:28 -0700 Subject: [PATCH 228/285] added pickuphours to location entity --- entity_location.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/entity_location.go b/entity_location.go index e2dfd0b..901be98 100644 --- a/entity_location.go +++ b/entity_location.go @@ -53,6 +53,7 @@ type LocationEntity struct { Hours **Hours `json:"hours,omitempty"` AccessHours **Hours `json:"accessHours,omitempty"` DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + PickupHours **Hours `json:"pickupHours,omitempty"` Closed **bool `json:"closed,omitempty"` Description *string `json:"description,omitempty"` AdditionalHoursText *string `json:"additionalHoursText,omitempty"` @@ -112,11 +113,11 @@ type LocationEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` From 4f4205a0588b89296ca92b770c8021062d6a21d9 Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Thu, 19 May 2022 14:33:56 -0400 Subject: [PATCH 229/285] rnd: add progress bar to show entity loading from API progress (#273) --- entity_service.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/entity_service.go b/entity_service.go index 467d430..e0280e4 100644 --- a/entity_service.go +++ b/entity_service.go @@ -5,6 +5,8 @@ import ( "net/url" "reflect" "strings" + + "github.com/schollz/progressbar/v3" ) const ( @@ -92,10 +94,12 @@ func (e *EntityService) WithClient(c *Client) *EntityService { // TODO: Add List for SearchID (similar to location-service). Follow up with Techops to see if SearchID is implemented func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { - var entities []Entity - if opts == nil { - opts = &EntityListOptions{} - } + var ( + entities []Entity + totalCountRetrieved = false + bar = progressbar.Default(-1) + ) + opts.ListOptions = ListOptions{Limit: EntityListMaxLimit} var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { opts.ListOptions = *listOptions @@ -104,7 +108,19 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { return "", err } + //show progress bar if number of entities > 250 + if resp.Count > 250 { + if !totalCountRetrieved { + bar = progressbar.Default(int64(resp.Count)) + bar.Add(len(resp.typedEntites)) + totalCountRetrieved = true + } else { + bar.Add(len(resp.typedEntites)) + } + } + entities = append(entities, resp.typedEntites...) + return resp.PageToken, nil } From ce8dd24d1d9ddfa663223682c501bd3016869866 Mon Sep 17 00:00:00 2001 From: nirmalpatel94 Date: Thu, 19 May 2022 19:17:54 -0400 Subject: [PATCH 230/285] fix(entity_service): add nil check J=CO-2055 TEST=manual --- entity_service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/entity_service.go b/entity_service.go index e0280e4..bbcf4e1 100644 --- a/entity_service.go +++ b/entity_service.go @@ -100,6 +100,10 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { bar = progressbar.Default(-1) ) + if opts == nil { + opts = &EntityListOptions{} + } + opts.ListOptions = ListOptions{Limit: EntityListMaxLimit} var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { opts.ListOptions = *listOptions From c1083c9039759b1072f2bd8e5e835955438fddb6 Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:39:59 -0400 Subject: [PATCH 231/285] entity_service: add hide progress bar option (#275) --- entity_service.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/entity_service.go b/entity_service.go index bbcf4e1..0b49ab1 100644 --- a/entity_service.go +++ b/entity_service.go @@ -50,6 +50,7 @@ type EntityListOptions struct { Fields []string Filter string Format RichTextFormat + HideProgressBar bool } // Used for Create and Edit @@ -113,7 +114,7 @@ func (e *EntityService) ListAll(opts *EntityListOptions) ([]Entity, error) { } //show progress bar if number of entities > 250 - if resp.Count > 250 { + if resp.Count > 250 && !opts.HideProgressBar { if !totalCountRetrieved { bar = progressbar.Default(int64(resp.Count)) bar.Add(len(resp.typedEntites)) From 6e9dd4f58f69b68d4849bbbc657def6d90d12bb9 Mon Sep 17 00:00:00 2001 From: Tyler Redshaw Date: Tue, 12 Jul 2022 11:37:40 -0400 Subject: [PATCH 232/285] Add Geomodifier to Healthcare Professional entity --- entity_healthcare_professional.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index db79360..3be3679 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -35,6 +35,7 @@ type HealthcareProfessionalEntity struct { Address *Address `json:"address,omitempty"` AddressHidden **bool `json:"addressHidden,omitempty"` ISORegionCode *string `json:"isoRegionCode,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` // Other Contact Info AlternatePhone *string `json:"alternatePhone,omitempty"` @@ -602,3 +603,10 @@ func (y HealthcareProfessionalEntity) GetGooglePlaceId() string { } return "" } + +func (y HealthcareProfessionalEntity) GetGeomodifier() string { + if y.Geomodifier != nil { + return GetString(y.Geomodifier) + } + return "" +} From 6b0330095a8c914e5b3788150212a97b8853a693 Mon Sep 17 00:00:00 2001 From: Nathaniel Gonzalez <105673033+NathanielGE@users.noreply.github.com> Date: Fri, 15 Jul 2022 14:28:33 -0400 Subject: [PATCH 233/285] making a user's createdDate field accessible (#277) --- user.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/user.go b/user.go index dee3b16..162cb1d 100644 --- a/user.go +++ b/user.go @@ -15,6 +15,7 @@ type User struct { SSO *bool `json:"sso,omitempty"` ACLs []ACL `json:"acl,omitempty"` LastLoginDate *string `json:"lastLoginDate,omitempty"` + CreatedDate *string `json:"createdDate,omitempty"` } func (u *User) GetId() string { @@ -79,6 +80,13 @@ func (u *User) GetLastLoginDate() string { return *u.LastLoginDate } +func (u *User) GetCreatedDate() string { + if u.CreatedDate == nil { + return "" + } + return *u.CreatedDate +} + func (u *User) String() string { b, _ := json.Marshal(u) return string(b) From f640e473b72f5831247d2b6e6a8cac2775146e07 Mon Sep 17 00:00:00 2001 From: JohnCho92 <56008387+JohnCho92@users.noreply.github.com> Date: Fri, 15 Jul 2022 14:57:29 -0400 Subject: [PATCH 234/285] add brunch hours and drive-through hours to restaurant entity (#279) J=PC-155508 Co-authored-by: JohnCho92 --- entity_restaurant.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/entity_restaurant.go b/entity_restaurant.go index c4414ce..79f12b0 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -31,16 +31,18 @@ type RestaurantEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - DeliveryHours **Hours `json:"deliveryHours,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` // Lats & Lngs From db2df01facfe168040feca1d6079987e13bf11c5 Mon Sep 17 00:00:00 2001 From: Nathan <37886474+Zavoky@users.noreply.github.com> Date: Tue, 2 Aug 2022 12:35:24 -0700 Subject: [PATCH 235/285] added option to consider nil booleans as non-zero values (#281) when nilIsEmpty is set to true, the differ considers nil booleans as the zero value ("false"). This commit introduces nilBoolIsEmpty, a flag specifically for booleans that can override nilIsEmpty --- cftasset_diff.go | 2 +- client.go | 4 +- entity.go | 13 ++++- entity_diff.go | 16 +++--- entity_diff_test.go | 109 +++++++++++++++++++++++------------- entity_service.go | 33 ++++++++++- entity_test.go | 2 +- language_profile_service.go | 18 +++++- location.go | 5 +- location_diff.go | 16 ++++-- location_diff_test.go | 11 ++-- 11 files changed, 158 insertions(+), 71 deletions(-) diff --git a/cftasset_diff.go b/cftasset_diff.go index 4ad4273..77be70a 100644 --- a/cftasset_diff.go +++ b/cftasset_diff.go @@ -13,7 +13,7 @@ func (a *CFTAsset) Diff(b *CFTAsset) (*CFTAsset, bool) { return nil, true } - delta, isDiff := GenericDiff(a, b, true, true) + delta, isDiff := GenericDiff(a, b, true, true, true, true) if !isDiff { return nil, isDiff } diff --git a/client.go b/client.go index f340d1e..c88a17b 100644 --- a/client.go +++ b/client.go @@ -69,9 +69,9 @@ func NewClient(config *Config) *Client { c.CFTAssetService.RegisterDefaultAssetValues() c.ActivityLogService = &ActivityLogService{client: c} c.AnalyticsService = &AnalyticsService{client: c} - c.EntityService = &EntityService{client: c} + c.EntityService = &EntityService{client: c, nilBoolIsEmpty: true} c.EntityService.RegisterDefaultEntities() - c.LanguageProfileService = &LanguageProfileService{client: c} + c.LanguageProfileService = &LanguageProfileService{client: c, nilBoolIsEmpty: true} c.AccountService = &AccountService{client: c} c.ServicesService = &ServicesService{client: c} c.LanguageProfileService.RegisterDefaultEntities() diff --git a/entity.go b/entity.go index 7eab02a..aac7bc8 100644 --- a/entity.go +++ b/entity.go @@ -22,8 +22,9 @@ type EntityMeta struct { } type BaseEntity struct { - Meta *EntityMeta `json:"meta,omitempty"` - nilIsEmpty bool + Meta *EntityMeta `json:"meta,omitempty"` + nilIsEmpty bool + nilBoolIsEmpty bool } func (b *BaseEntity) GetEntityId() string { @@ -94,10 +95,18 @@ func (b *BaseEntity) GetNilIsEmpty() bool { return b.nilIsEmpty } +func (b *BaseEntity) GetNilBoolIsEmpty() bool { + return b.nilBoolIsEmpty +} + func (b *BaseEntity) SetNilIsEmpty(val bool) { b.nilIsEmpty = val } +func (b *BaseEntity) SetNilBoolIsEmpty(val bool) { + b.nilBoolIsEmpty = val +} + type RawEntity map[string]interface{} func (r *RawEntity) GetEntityId() string { diff --git a/entity_diff.go b/entity_diff.go index ba08dbb..eefa3d1 100644 --- a/entity_diff.go +++ b/entity_diff.go @@ -32,7 +32,7 @@ func InstanceOf(val interface{}) interface{} { return reflect.New(reflect.TypeOf(val)).Interface() } -func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (interface{}, bool) { +func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB bool, nilBoolIsEmptyA bool, nilBoolIsEmptyB bool) (interface{}, bool) { var ( aV, bV = reflect.ValueOf(a), reflect.ValueOf(b) isDiff = false @@ -98,7 +98,7 @@ func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB boo // First, use recursion to handle a field that is a struct or a pointer to a struct // If Kind() == struct, this is likely an embedded struct if valA.Kind() == reflect.Struct { - d, diff := GenericDiff(valA.Addr().Interface(), valB.Addr().Interface(), nilIsEmptyA, nilIsEmptyB) + d, diff := GenericDiff(valA.Addr().Interface(), valB.Addr().Interface(), nilIsEmptyA, nilIsEmptyB, nilBoolIsEmptyA, nilBoolIsEmptyB) if diff { isDiff = true reflect.ValueOf(delta).Elem().FieldByName(nameA).Set(reflect.ValueOf(d).Elem()) @@ -106,7 +106,7 @@ func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB boo continue // If it's a pointer to a struct we need to handle it in a special way: } else if valA.Kind() == reflect.Ptr && Indirect(valA).Kind() == reflect.Struct { - d, diff := GenericDiff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB) + d, diff := GenericDiff(valA.Interface(), valB.Interface(), nilIsEmptyA, nilIsEmptyB, nilBoolIsEmptyA, nilBoolIsEmptyB) if diff { isDiff = true Indirect(reflect.ValueOf(delta)).FieldByName(nameA).Set(reflect.ValueOf(d)) @@ -114,7 +114,7 @@ func GenericDiff(a interface{}, b interface{}, nilIsEmptyA bool, nilIsEmptyB boo continue } - if IsZeroValue(valA, nilIsEmptyA) && IsZeroValue(valB, nilIsEmptyB) { + if IsZeroValue(valA, nilIsEmptyA, nilBoolIsEmptyA) && IsZeroValue(valB, nilIsEmptyB, nilBoolIsEmptyB) { continue } @@ -171,7 +171,7 @@ func Diff(a Entity, b Entity) (Entity, bool, error) { rawA, okA := a.(*RawEntity) rawB, okB := b.(*RawEntity) if okA && okB { - delta, isDiff := RawEntityDiff(*rawA, *rawB, GetNilIsEmpty(a), GetNilIsEmpty(b)) + delta, isDiff := RawEntityDiff(*rawA, *rawB, GetNilIsEmpty(a), GetNilIsEmpty(b), GetNilBoolIsEmpty(a), GetNilBoolIsEmpty(b)) if !isDiff { return nil, isDiff, nil } @@ -179,14 +179,14 @@ func Diff(a Entity, b Entity) (Entity, bool, error) { return &rawDelta, isDiff, nil } - delta, isDiff := GenericDiff(a, b, GetNilIsEmpty(a), GetNilIsEmpty(b)) + delta, isDiff := GenericDiff(a, b, GetNilIsEmpty(a), GetNilIsEmpty(b), GetNilBoolIsEmpty(a), GetNilBoolIsEmpty(b)) if !isDiff { return nil, isDiff, nil } return delta.(Entity), isDiff, nil } -func RawEntityDiff(a map[string]interface{}, b map[string]interface{}, nilIsEmptyA bool, nilIsEmptyB bool) (map[string]interface{}, bool) { +func RawEntityDiff(a map[string]interface{}, b map[string]interface{}, nilIsEmptyA bool, nilIsEmptyB bool, nilBoolIsEmptyA bool, nilBoolIsEmptyB bool) (map[string]interface{}, bool) { var ( aAsMap = a bAsMap = b @@ -202,7 +202,7 @@ func RawEntityDiff(a map[string]interface{}, b map[string]interface{}, nilIsEmpt _, aIsMap := aVal.(map[string]interface{}) _, bIsMap := bVal.(map[string]interface{}) if aIsMap && bIsMap { - subFieldsDelta, subFieldsAreDiff := RawEntityDiff(aVal.(map[string]interface{}), bVal.(map[string]interface{}), nilIsEmptyA, nilIsEmptyB) + subFieldsDelta, subFieldsAreDiff := RawEntityDiff(aVal.(map[string]interface{}), bVal.(map[string]interface{}), nilIsEmptyA, nilIsEmptyB, nilBoolIsEmptyA, nilBoolIsEmptyB) if subFieldsAreDiff { delta[key] = subFieldsDelta isDiff = true diff --git a/entity_diff_test.go b/entity_diff_test.go index 9611183..1ae7d2a 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -6,14 +6,16 @@ import ( ) type diffTest struct { - name string - property string - isDiff bool - baseValue interface{} - newValue interface{} - baseNilIsEmpty bool - newNilIsEmpty bool - deltaValue interface{} + name string + property string + isDiff bool + baseValue interface{} + newValue interface{} + baseNilIsEmpty bool + newNilIsEmpty bool + baseNilBoolIsEmpty bool + newNilBoolIsEmpty bool + deltaValue interface{} } func setValOnProperty(val interface{}, property string, entity Entity) { @@ -342,38 +344,59 @@ func TestEntityDiff(t *testing.T) { isDiff: false, }, diffTest{ - name: "**Bool: base is nil (nil is empty), new is zero value (K)", - property: "Closed", - baseValue: nil, - newValue: NullableBool(false), - baseNilIsEmpty: true, - isDiff: false, - }, - diffTest{ - name: "**Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", - property: "Closed", - baseValue: nil, - newValue: NullableBool(false), - baseNilIsEmpty: true, - newNilIsEmpty: true, - isDiff: false, - }, - diffTest{ - name: "**Bool: base is zero value, new is nil (nil is empty) (L)", - property: "Closed", - baseValue: NullableBool(false), - newValue: nil, - newNilIsEmpty: true, - isDiff: false, - }, - diffTest{ - name: "**Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", - property: "Closed", - baseValue: NullableBool(false), - newValue: nil, - baseNilIsEmpty: true, - newNilIsEmpty: true, - isDiff: false, + name: "**Bool: base is nil (nil is empty), new is zero value (K)", + property: "Closed", + baseValue: nil, + newValue: NullableBool(false), + baseNilIsEmpty: true, + baseNilBoolIsEmpty: true, + newNilBoolIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "**Bool: base is nil (nil is empty), new is zero value (nil is empty) (L)", + property: "Closed", + baseValue: nil, + newValue: NullableBool(false), + baseNilIsEmpty: true, + newNilIsEmpty: true, + baseNilBoolIsEmpty: true, + newNilBoolIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "**Bool: base is zero value, new is nil (nil is empty) (L)", + property: "Closed", + baseValue: NullableBool(false), + newValue: nil, + newNilIsEmpty: true, + baseNilBoolIsEmpty: true, + newNilBoolIsEmpty: true, + isDiff: false, + }, + diffTest{ + name: "**Bool: base is zero value (nil is empty), new is nil (nil is empty) (L)", + property: "Closed", + baseValue: NullableBool(false), + newValue: nil, + baseNilIsEmpty: true, + newNilIsEmpty: true, + baseNilBoolIsEmpty: true, + newNilBoolIsEmpty: true, + isDiff: false, + }, + // test M differ from similar tests for other types, as it doesn't consider nil to be the zero value + diffTest{ + name: "**Bool: base is nil (nil is empty), new is zero value (M)", + property: "Closed", + baseValue: nil, + newValue: NullableBool(false), + baseNilIsEmpty: true, + newNilIsEmpty: true, + baseNilBoolIsEmpty: false, + newNilBoolIsEmpty: false, + isDiff: true, + deltaValue: NullableBool(false), }, // Struct tests (Address) diffTest{ @@ -829,6 +852,12 @@ func TestEntityDiff(t *testing.T) { if test.newNilIsEmpty { setNilIsEmpty(newEntity) } + if test.baseNilBoolIsEmpty { + setNilBoolIsEmpty(baseEntity) + } + if test.newNilBoolIsEmpty { + setNilBoolIsEmpty(newEntity) + } delta, isDiff, _ := Diff(baseEntity, newEntity) if isDiff != test.isDiff { t.Errorf("Expected isDiff: %t. Got: %t", test.isDiff, isDiff) diff --git a/entity_service.go b/entity_service.go index 0b49ab1..1efbdc6 100644 --- a/entity_service.go +++ b/entity_service.go @@ -37,8 +37,9 @@ func (r RichTextFormat) ToString() string { } type EntityService struct { - client *Client - Registry *EntityRegistry + client *Client + Registry *EntityRegistry + nilBoolIsEmpty bool } type EntityListOptions struct { @@ -168,6 +169,9 @@ func (e *EntityService) List(opts *EntityListOptions) (*EntityListResponse, *Res entities := []Entity{} for _, entity := range typedEntities { setNilIsEmpty(entity) + if e.nilBoolIsEmpty { + setNilBoolIsEmpty(entity) + } entities = append(entities, entity) } v.typedEntites = entities @@ -253,6 +257,9 @@ func (e *EntityService) Get(id string) (Entity, *Response, error) { } setNilIsEmpty(entity) + if e.nilBoolIsEmpty { + setNilBoolIsEmpty(entity) + } return entity, r, nil } @@ -264,6 +271,13 @@ func setNilIsEmpty(i interface{}) { } } +func setNilBoolIsEmpty(i interface{}) { + m := reflect.ValueOf(i).MethodByName("SetNilBoolIsEmpty") + if m.IsValid() { + m.Call([]reflect.Value{reflect.ValueOf(true)}) + } +} + func GetNilIsEmpty(i interface{}) bool { m := reflect.ValueOf(i).MethodByName("GetNilIsEmpty") if m.IsValid() { @@ -275,6 +289,17 @@ func GetNilIsEmpty(i interface{}) bool { return false } +func GetNilBoolIsEmpty(i interface{}) bool { + m := reflect.ValueOf(i).MethodByName("GetNilBoolIsEmpty") + if m.IsValid() { + values := m.Call([]reflect.Value{}) + if len(values) == 1 { + return values[0].Interface().(bool) + } + } + return false +} + func (e *EntityService) Create(y Entity) (*Response, error) { var requrl = entityPath u, err := url.Parse(requrl) @@ -345,3 +370,7 @@ func (e *EntityService) Edit(y Entity) (*Response, error) { func (e *EntityService) Delete(id string) (*Response, error) { return e.client.DoRequestJSON("DELETE", fmt.Sprintf("%s/%s", entityPath, id), nil, nil) } + +func (e *EntityService) SetNilBoolIsEmpty(nilBoolIsEmpty bool) { + e.nilBoolIsEmpty = nilBoolIsEmpty +} diff --git a/entity_test.go b/entity_test.go index 9e87b79..b7b36a5 100644 --- a/entity_test.go +++ b/entity_test.go @@ -804,7 +804,7 @@ func TestSetValue(t *testing.T) { if err != nil { t.Errorf("Got err: %s", err) } - if delta, isDiff := RawEntityDiff(*test.Raw, *test.Want, false, false); isDiff { + if delta, isDiff := RawEntityDiff(*test.Raw, *test.Want, false, false, false, false); isDiff { t.Errorf("Got: %v, Wanted: %v, Delta: %v", test.Raw, test.Want, delta) } } diff --git a/language_profile_service.go b/language_profile_service.go index 0b28986..76d08be 100644 --- a/language_profile_service.go +++ b/language_profile_service.go @@ -11,8 +11,9 @@ const ( ) type LanguageProfileService struct { - client *Client - registry *EntityRegistry + client *Client + registry *EntityRegistry + nilBoolIsEmpty bool } type LanguageProfileListResponse struct { @@ -48,6 +49,9 @@ func (l *LanguageProfileService) Get(id string, languageCode string) (*Entity, * return nil, r, err } setNilIsEmpty(entity) + if l.nilBoolIsEmpty { + setNilBoolIsEmpty(entity) + } return &entity, r, nil } @@ -68,6 +72,9 @@ func (l *LanguageProfileService) List(id string) ([]Entity, *Response, error) { } for _, profile := range typedProfiles { setNilIsEmpty(profile) + if l.nilBoolIsEmpty { + setNilBoolIsEmpty(profile) + } profiles = append(profiles, profile) } return profiles, r, nil @@ -138,6 +145,9 @@ func (l *LanguageProfileService) listAllHelper(opts *EntityListOptions) (*Langua for _, entity := range typedEntities { setNilIsEmpty(entity) + if l.nilBoolIsEmpty { + setNilBoolIsEmpty(entity) + } } v.typedProfiles = typedEntities @@ -182,3 +192,7 @@ func (l *LanguageProfileService) Delete(id string, languageCode string) (*Respon } return r, nil } + +func (l *LanguageProfileService) SetNilBoolIsEmpty(nilBoolIsEmpty bool) { + l.nilBoolIsEmpty = nilBoolIsEmpty +} diff --git a/location.go b/location.go index 288fe91..6dfc6ae 100644 --- a/location.go +++ b/location.go @@ -34,8 +34,9 @@ type Location struct { Language *string `json:"language,omitempty"` CustomFields map[string]interface{} `json:"customFields,omitempty"` - hydrated bool - nilIsEmpty bool + hydrated bool + nilIsEmpty bool + nilBoolIsEmpty bool // Address Fields Name *string `json:"locationName,omitempty"` diff --git a/location_diff.go b/location_diff.go index 5f06e50..0c3310d 100644 --- a/location_diff.go +++ b/location_diff.go @@ -57,7 +57,7 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { } } - if IsZeroValue(valA, y.nilIsEmpty) && IsZeroValue(valB, b.nilIsEmpty) { + if IsZeroValue(valA, y.nilIsEmpty, y.nilBoolIsEmpty) && IsZeroValue(valB, b.nilIsEmpty, b.nilBoolIsEmpty) { continue } @@ -88,7 +88,7 @@ func (y Location) Diff(b *Location) (d *Location, diff bool) { diff = true d.CustomFields[field] = value } - } else if !(IsZeroValue(reflect.ValueOf(value), b.nilIsEmpty) && y.nilIsEmpty) { + } else if !(IsZeroValue(reflect.ValueOf(value), b.nilIsEmpty, b.nilBoolIsEmpty) && y.nilIsEmpty) { d.CustomFields[field] = value diff = true } @@ -128,7 +128,7 @@ func getUnderlyingValue(v interface{}) interface{} { return rv.Interface() } -func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { +func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool, interpretNilBooleansAsZeroValue bool) bool { if !v.IsValid() { return true } @@ -147,13 +147,17 @@ func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool) bool { case reflect.Float64: return v.Float() == 0.0 case reflect.Ptr, reflect.Interface: - if v.IsNil() && !interpretNilAsZeroValue { + isNil := v.IsNil() + if isNil && v.Type() == reflect.TypeOf((**bool)(nil)) { + return interpretNilBooleansAsZeroValue + } + if isNil && !interpretNilAsZeroValue { return false } - return IsZeroValue(v.Elem(), true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) + return IsZeroValue(v.Elem(), true, interpretNilBooleansAsZeroValue) // interpretNilAsZeroValue needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) case reflect.Struct: for i, n := 0, v.NumField(); i < n; i++ { - if !IsZeroValue(v.Field(i), true) { + if !IsZeroValue(v.Field(i), true, interpretNilBooleansAsZeroValue) { return false } } diff --git a/location_diff_test.go b/location_diff_test.go index 96bf69a..051a8d9 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -1579,10 +1579,11 @@ func TestHoursAreEquivalentDiff(t *testing.T) { func TestIsZeroValue(t *testing.T) { tests := []struct { - name string - i interface{} - nilIsEmpty bool - want bool + name string + i interface{} + nilIsEmpty bool + nilBoolIsEmpty bool + want bool }{ { name: "Non-Empty String", @@ -1738,7 +1739,7 @@ func TestIsZeroValue(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if isZeroValue := IsZeroValue(reflect.ValueOf(test.i), test.nilIsEmpty); test.want != isZeroValue { + if isZeroValue := IsZeroValue(reflect.ValueOf(test.i), test.nilIsEmpty, test.nilBoolIsEmpty); test.want != isZeroValue { t.Errorf(`Expected IsZeroValue: %t\nGot:%t`, test.want, isZeroValue) } }) From 9acb0b1c9dd6baf7ad1866c20a476255d15a5b7b Mon Sep 17 00:00:00 2001 From: nyellowhair Date: Tue, 2 Aug 2022 17:49:25 -0700 Subject: [PATCH 236/285] updated logic for **bools found in pointers --- location_diff.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/location_diff.go b/location_diff.go index 0c3310d..2ae8131 100644 --- a/location_diff.go +++ b/location_diff.go @@ -154,10 +154,10 @@ func IsZeroValue(v reflect.Value, interpretNilAsZeroValue bool, interpretNilBool if isNil && !interpretNilAsZeroValue { return false } - return IsZeroValue(v.Elem(), true, interpretNilBooleansAsZeroValue) // interpretNilAsZeroValue needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) + return IsZeroValue(v.Elem(), true, true) // Needs to be true for case of double pointer **Hours where **Hours is nil (we want this to be zero) case reflect.Struct: for i, n := 0, v.NumField(); i < n; i++ { - if !IsZeroValue(v.Field(i), true, interpretNilBooleansAsZeroValue) { + if !IsZeroValue(v.Field(i), true, true) { return false } } From 781b27dc8cb1f1a67c0742e8d309830ef741560f Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Sun, 7 Aug 2022 01:46:39 -0400 Subject: [PATCH 237/285] Entities facebookusername (#282) * adds facebookusername field for location entities * fixes formatting?? --- entity_location.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_location.go b/entity_location.go index 901be98..d34bce2 100644 --- a/entity_location.go +++ b/entity_location.go @@ -112,6 +112,7 @@ type LocationEntity struct { FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + FacebookUsername *string `json:"facebookVanityUrl,omitempty"` // It's called vanity url in json but Username in front end GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` From 40db20f28d97daddb2e990fed79a2dea371db290 Mon Sep 17 00:00:00 2001 From: JohnCho92 <56008387+JohnCho92@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:57:08 -0400 Subject: [PATCH 238/285] add Slug to Healthcare Professional entity (#283) Co-authored-by: JohnCho92 --- entity_healthcare_professional.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 3be3679..1dac23b 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -29,6 +29,7 @@ type HealthcareProfessionalEntity struct { CategoryIds *[]string `json:"categoryIds,omitempty"` Closed **bool `json:"closed,omitempty"` Keywords *[]string `json:"keywords,omitempty"` + Slug *string `json:"slug,omitempty"` // Address Fields Name *string `json:"name,omitempty"` From 6f5d9882b7d4f5539ebaa9d172a980ec642dac85 Mon Sep 17 00:00:00 2001 From: JohnCho92 <56008387+JohnCho92@users.noreply.github.com> Date: Fri, 26 Aug 2022 13:44:23 -0400 Subject: [PATCH 239/285] add Slug to Healthcare Facility entity (#284) Co-authored-by: JohnCho92 --- entity_healthcare_facility.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 12216f1..29e82ac 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -15,6 +15,7 @@ type HealthcareFacilityEntity struct { CategoryIds *[]string `json:"categoryIds,omitempty"` Closed **bool `json:"closed,omitempty"` Keywords *[]string `json:"keywords,omitempty"` + Slug *string `json:"slug,omitempty"` // Address Fields Name *string `json:"name,omitempty"` From 28266ec9b7bd5619a16159746d9f5fc627d998fe Mon Sep 17 00:00:00 2001 From: Joseph Baik Date: Tue, 30 Aug 2022 13:37:07 -0400 Subject: [PATCH 240/285] PC-182867: add google entity relationship location entity field (#285) * PC-182867: add google entity relationship location entity field * make it double pointer --- entity_location.go | 55 ++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/entity_location.go b/entity_location.go index d34bce2..6554411 100644 --- a/entity_location.go +++ b/entity_location.go @@ -10,19 +10,21 @@ import ( ) const ( - ENTITYTYPE_LOCATION EntityType = "location" - FACEBOOKCTA_TYPE_BOOKNOW = "BOOK_NOW" - FACEBOOKCTA_TYPE_CALLNOW = "CALL_NOW" - FACEBOOKCTA_TYPE_CONTACTUS = "CONTACT_US" - FACEBOOKCTA_TYPE_LEARNMORE = "LEARN_MORE" - FACEBOOKCTA_TYPE_OFF = "NONE" - FACEBOOKCTA_TYPE_PLAYGAME = "PLAY_GAME" - FACEBOOKCTA_TYPE_SENDEMAIL = "SEND_EMAIL" - FACEBOOKCTA_TYPE_SENDMESSAGE = "SEND_MESSAGE" - FACEBOOKCTA_TYPE_SHOPNOW = "SHOP_NOW" - FACEBOOKCTA_TYPE_SIGNUP = "SIGN_UP" - FACEBOOKCTA_TYPE_USEAPP = "USE_APP" - FACEBOOKCTA_TYPE_WATCHVIDEO = "WATCH_VIDEO" + ENTITYTYPE_LOCATION EntityType = "location" + FACEBOOKCTA_TYPE_BOOKNOW = "BOOK_NOW" + FACEBOOKCTA_TYPE_CALLNOW = "CALL_NOW" + FACEBOOKCTA_TYPE_CONTACTUS = "CONTACT_US" + FACEBOOKCTA_TYPE_LEARNMORE = "LEARN_MORE" + FACEBOOKCTA_TYPE_OFF = "NONE" + FACEBOOKCTA_TYPE_PLAYGAME = "PLAY_GAME" + FACEBOOKCTA_TYPE_SENDEMAIL = "SEND_EMAIL" + FACEBOOKCTA_TYPE_SENDMESSAGE = "SEND_MESSAGE" + FACEBOOKCTA_TYPE_SHOPNOW = "SHOP_NOW" + FACEBOOKCTA_TYPE_SIGNUP = "SIGN_UP" + FACEBOOKCTA_TYPE_USEAPP = "USE_APP" + FACEBOOKCTA_TYPE_WATCHVIDEO = "WATCH_VIDEO" + GOOGLE_ENTITIY_RELATIONSHIP_TYPE_DEPARTMENTIN = "DEPARTMENT_IN" + GOOGLE_ENTITY_RELATIONSHIP_TYPE_INDEPENDENT = "INDEPENDENT_ESTABLISHMENT_IN" ) // Location is the representation of a Location in Yext Location Manager. @@ -112,13 +114,14 @@ type LocationEntity struct { FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - FacebookUsername *string `json:"facebookVanityUrl,omitempty"` // It's called vanity url in json but Username in front end + FacebookUsername *string `json:"facebookVanityUrl,omitempty"` // It's called vanity url in json but Username in front end - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GoogleEntityRelationship **GoogleEntityRelationship `json:"googleEntityRelationship,omitempty"` + GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` @@ -177,6 +180,20 @@ type UberLink struct { Presentation *string `json:"presentation,omitempty"` } +type GoogleEntityRelationship struct { + Type *string `json:"type,omitempty"` + PlaceId *string `json:"placeId,omitempty"` +} + +func NullableGoogleEntityRelationship(g *GoogleEntityRelationship) **GoogleEntityRelationship { + return &g +} + +func NullGoogleEntityRelationship() **GoogleEntityRelationship { + var g *GoogleEntityRelationship + return &g +} + func NullableUberLink(u *UberLink) **UberLink { return &u } From c1716a4ef78840fbeeb2c76346874ce6c09c7867 Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Thu, 8 Sep 2022 12:05:34 -0700 Subject: [PATCH 241/285] Update Financial Professional Field types for DisclosureLink AndroidAppURL IOSAppURL LandingPageUrl (#286) --- entity_financial_professional.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/entity_financial_professional.go b/entity_financial_professional.go index a09171d..ab3b439 100644 --- a/entity_financial_professional.go +++ b/entity_financial_professional.go @@ -60,10 +60,10 @@ type FinancialProfessional struct { OrderUrl **Website `json:"orderUrl,omitempty"` ReservationUrl **Website `json:"reservationUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` - LandingPageUrl **Website `json:"landingPageUrl,omitempty"` - IOSAppURL **Website `json:"iosAppUrl,omitempty"` - AndroidAppURL **Website `json:"androidAppUrl,omitempty"` - DisclosureLink **Website `json:"disclosureLink,omitempty"` + LandingPageUrl *string `json:"landingPageUrl,omitempty"` + IOSAppURL *string `json:"iosAppUrl,omitempty"` + AndroidAppURL *string `json:"androidAppUrl,omitempty"` + DisclosureLink *string `json:"disclosureLink,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` // Social Media From c2ef04bbbcebbae21d1aff6ac3bb1bd66db767f8 Mon Sep 17 00:00:00 2001 From: mtusman Date: Fri, 23 Sep 2022 16:23:12 +0100 Subject: [PATCH 242/285] add invitationId to ReviewCreateInvitationResponse struct (#287) --- review_service.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/review_service.go b/review_service.go index 9bfe084..1ff182c 100644 --- a/review_service.go +++ b/review_service.go @@ -77,16 +77,17 @@ type ReviewUpdateResponse struct { } type ReviewCreateInvitationResponse struct { - Id string `json:"id"` - LocationId string `json:"locationId"` - Entity *ReviewEntity `json:"entity,omitempty"` // Must have v param >= 20210728 - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Contact string `json:"contact"` - Image bool `json:"image"` - TemplateId int `json:"templateId"` - Status string `json:"status"` - Details string `json:"details"` + InvitationUID string `json:"invitationUid"` + Id string `json:"id"` + LocationId string `json:"locationId"` + Entity *ReviewEntity `json:"entity,omitempty"` // Must have v param >= 20210728 + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Contact string `json:"contact"` + Image bool `json:"image"` + TemplateId int `json:"templateId"` + Status string `json:"status"` + Details string `json:"details"` } type ReviewCreateReviewResponse struct { From 379d11f9d7148da7dbee0b7548ea265bfecb28ef Mon Sep 17 00:00:00 2001 From: Catherine Dworak Date: Mon, 26 Sep 2022 14:15:01 -0400 Subject: [PATCH 243/285] change isclosed from **bool to bool to treat nil and false the same (#288) --- entity_diff_test.go | 247 ++++++++++++++++++++++++++++------------- entity_location.go | 45 ++------ entity_test.go | 1 + location_diff_test.go | 6 +- location_hours.go | 4 +- location_hours_test.go | 10 +- 6 files changed, 192 insertions(+), 121 deletions(-) diff --git a/entity_diff_test.go b/entity_diff_test.go index 1ae7d2a..918532c 100644 --- a/entity_diff_test.go +++ b/entity_diff_test.go @@ -776,6 +776,96 @@ func TestEntityDiff(t *testing.T) { newNilIsEmpty: true, isDiff: false, }, + diffTest{ + name: "Hours: base is IsClosed: omitted (defaults to false), new is IsClosed: false. ", + property: "Hours", + baseValue: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + })}), + newValue: NullableHours(&Hours{ + Monday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Tuesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Wednesday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Thursday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Friday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Saturday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + }), + Sunday: NullableDayHours(&DayHours{ + OpenIntervals: &[]Interval{ + Interval{Start: "08:00", End: "16:30"}, + }, + IsClosed: false, + })}), + isDiff: false, + }, // EmbeddedStruct tests diffTest{ name: "Base Entity: different Ids", @@ -1087,7 +1177,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1099,7 +1189,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-22"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1112,7 +1202,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-22"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1127,7 +1217,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(false), + IsClosed: false, }, }, }), @@ -1139,7 +1229,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1152,7 +1242,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1184,7 +1274,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1197,7 +1287,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1267,11 +1357,11 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-21"), - IsClosed: NullableBool(true), + IsClosed: true, }, HolidayHours{ Date: String("2019-01-22"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1283,11 +1373,11 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-23"), - IsClosed: NullableBool(true), + IsClosed: true, }, HolidayHours{ Date: String("2019-01-24"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1300,11 +1390,11 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("2019-01-23"), - IsClosed: NullableBool(true), + IsClosed: true, }, HolidayHours{ Date: String("2019-01-24"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1380,7 +1470,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), }), }, @@ -1389,7 +1479,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(false), + IsClosed: false, }), }), }, @@ -1399,7 +1489,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(false), + IsClosed: false, }), }), }, @@ -1411,16 +1501,16 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, HolidayHours{ Date: String("01-23-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1430,16 +1520,16 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-22-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, HolidayHours{ Date: String("01-23-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1452,11 +1542,11 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-22-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, HolidayHours{ Date: String("01-23-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1469,7 +1559,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1480,24 +1570,24 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1507,7 +1597,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1518,24 +1608,24 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1563,7 +1653,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1574,24 +1664,24 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1610,7 +1700,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1646,7 +1736,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1656,7 +1746,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1667,24 +1757,24 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1695,7 +1785,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1706,19 +1796,19 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), }), }, @@ -1730,7 +1820,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1741,19 +1831,19 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), }), }, @@ -1833,7 +1923,7 @@ func TestEntityDiffComplex(t *testing.T) { LocationEntity: LocationEntity{ Hours: NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -1844,24 +1934,24 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Wednesday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Thursday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Sunday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1880,7 +1970,7 @@ func TestEntityDiffComplex(t *testing.T) { HolidayHours: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, }), @@ -1926,7 +2016,7 @@ func TestEntityDiffComplex(t *testing.T) { }, }), Friday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Saturday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -2511,7 +2601,7 @@ func TestInstanceOf(t *testing.T) { var ( h = &[]HolidayHours{ HolidayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }, } j = InstanceOf(h) @@ -2523,7 +2613,7 @@ func TestInstanceOf(t *testing.T) { var ( hours = NullableHours(&Hours{ Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), }) k = InstanceOf(hours) @@ -2536,6 +2626,7 @@ func TestInstanceOf(t *testing.T) { t.Error("**Hours instance is nil") } + var ( address = &Address{ Line1: String("7900 Westpark"), diff --git a/entity_location.go b/entity_location.go index 6554411..819e160 100644 --- a/entity_location.go +++ b/entity_location.go @@ -402,7 +402,7 @@ func NewHoursUnspecifiedAllWeek() *Hours { type DayHours struct { OpenIntervals *[]Interval `json:"openIntervals,omitempty"` - IsClosed **bool `json:"isClosed,omitempty"` + IsClosed bool `json:"isClosed,omitempty"` } func NullableDayHours(d *DayHours) **DayHours { @@ -422,7 +422,7 @@ func GetDayHours(d **DayHours) *DayHours { } func (d *DayHours) SetClosed() { - d.IsClosed = NullableBool(true) + d.IsClosed = true d.OpenIntervals = nil } @@ -435,7 +435,7 @@ func (d *DayHours) GetIntervals() []Interval { func (d *DayHours) AddHours(start string, end string) { intervals := []Interval{} - d.IsClosed = nil + d.IsClosed = false if d.OpenIntervals != nil { intervals = *d.OpenIntervals } @@ -447,7 +447,7 @@ func (d *DayHours) AddHours(start string, end string) { } func (d *DayHours) SetHours(start string, end string) { - d.IsClosed = nil + d.IsClosed = false d.OpenIntervals = &[]Interval{ Interval{ Start: start, @@ -532,40 +532,19 @@ func (h *Hours) SetUnspecifiedAllWeek() { func (h *Hours) SetUnspecified(w Weekday) { switch w { case Sunday: - h.Sunday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Sunday = NullDayHours() case Monday: - h.Monday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Monday = NullDayHours() case Tuesday: - h.Tuesday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Tuesday = NullDayHours() case Wednesday: - h.Wednesday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Wednesday = NullDayHours() case Thursday: - h.Thursday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Thursday = NullDayHours() case Friday: - h.Friday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Friday = NullDayHours() case Saturday: - h.Saturday = NullableDayHours(&DayHours{ - OpenIntervals: nil, - IsClosed: nil, - }) + h.Saturday = NullDayHours() } } @@ -1016,7 +995,7 @@ func (y LocationEntity) IsClosed() bool { type HolidayHours struct { Date *string `json:"date"` OpenIntervals *[]Interval `json:"openIntervals,omitempty"` - IsClosed **bool `json:"isClosed,omitempty"` + IsClosed bool `json:"isClosed,omitempty"` IsRegularHours **bool `json:"isRegularHours,omitempty"` } diff --git a/entity_test.go b/entity_test.go index b7b36a5..1fe8149 100644 --- a/entity_test.go +++ b/entity_test.go @@ -104,6 +104,7 @@ func TestEntityJSONSerialization(t *testing.T) { {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: nil}}, `{}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullHours()}}, `{"hours":null}`}, {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullableHours(&Hours{Monday: NullDayHours(), Tuesday: NullDayHours(), Wednesday: NullDayHours(), Thursday: NullDayHours(), Friday: NullDayHours(), Saturday: NullDayHours(), Sunday: NullDayHours()})}}, `{"hours":{"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":null,"saturday":null,"sunday":null}}`}, + {&CustomLocationEntity{LocationEntity: LocationEntity{Hours: NullableHours(NewHoursUnspecifiedAllWeek())}}, `{"hours":{"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":null,"saturday":null,"sunday":null}}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: String("")}}, `{"cf_Url":""}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFUrl: nil}}, `{}`}, {&CustomLocationEntity{CustomEntity: CustomEntity{CFTextList: &[]string{}}}, `{"cf_TextList":[]}`}, diff --git a/location_diff_test.go b/location_diff_test.go index 051a8d9..82ed8a2 100644 --- a/location_diff_test.go +++ b/location_diff_test.go @@ -1708,7 +1708,7 @@ func TestIsZeroValue(t *testing.T) { i: &[]HolidayHours{ HolidayHours{ Date: String("01-21-2019"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, nilIsEmpty: true, @@ -1717,7 +1717,7 @@ func TestIsZeroValue(t *testing.T) { { name: "**struct", i: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), nilIsEmpty: true, want: false, @@ -1725,7 +1725,7 @@ func TestIsZeroValue(t *testing.T) { { name: "**struct", i: NullableDayHours(&DayHours{ - IsClosed: NullableBool(false), + IsClosed: false, }), nilIsEmpty: true, want: true, diff --git a/location_hours.go b/location_hours.go index 4ee4f31..63e2a10 100644 --- a/location_hours.go +++ b/location_hours.go @@ -217,7 +217,7 @@ func (h *LocationHoursHelper) StructSerialize() **Hours { func (h *LocationHoursHelper) StructSerializeDay(weekday Weekday) *DayHours { if h.HoursAreUnspecified(weekday) || h.HoursAreClosed(weekday) || len(h.GetHours(weekday)) == 0 { return &DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, } } var d = &DayHours{} @@ -374,7 +374,7 @@ func LocationHolidayHoursToHolidayHours(l *LocationHolidayHours) (*HolidayHours, Date: String(l.Date), } if l.Hours == "" { - h.IsClosed = NullableBool(true) + h.IsClosed = true } else { intervalsList := []Interval{} intervals := strings.Split(l.Hours, ",") diff --git a/location_hours_test.go b/location_hours_test.go index 08cc836..a501389 100644 --- a/location_hours_test.go +++ b/location_hours_test.go @@ -204,7 +204,7 @@ func TestStructSerialize(t *testing.T) { }, }), Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -268,7 +268,7 @@ func TestStructSerialize(t *testing.T) { }, }), Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -332,7 +332,7 @@ func TestStructSerialize(t *testing.T) { }, }), Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -400,7 +400,7 @@ func TestStructSerialize(t *testing.T) { }, }), Monday: NullableDayHours(&DayHours{ - IsClosed: NullableBool(true), + IsClosed: true, }), Tuesday: NullableDayHours(&DayHours{ OpenIntervals: &[]Interval{ @@ -529,7 +529,7 @@ func TestHolidayHoursConvert(t *testing.T) { }, Want: &HolidayHours{ Date: String("2018-12-25"), - IsClosed: NullableBool(true), + IsClosed: true, }, }, } From 35ffb5952309d0139830b93889898a3fdab3976e Mon Sep 17 00:00:00 2001 From: Noah Siskind Date: Thu, 13 Oct 2022 16:00:57 +0200 Subject: [PATCH 244/285] Listings (#289) * add listings service * add list all endpoint * fix listing response obj --- client.go | 2 + listings_service.go | 148 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 listings_service.go diff --git a/client.go b/client.go index c88a17b..c45e904 100644 --- a/client.go +++ b/client.go @@ -49,6 +49,7 @@ type Client struct { LanguageProfileService *LanguageProfileService AccountService *AccountService ServicesService *ServicesService + ListingsService *ListingsService } func NewClient(config *Config) *Client { @@ -75,6 +76,7 @@ func NewClient(config *Config) *Client { c.AccountService = &AccountService{client: c} c.ServicesService = &ServicesService{client: c} c.LanguageProfileService.RegisterDefaultEntities() + c.ListingsService = &ListingsService{client: c} return c } diff --git a/listings_service.go b/listings_service.go new file mode 100644 index 0000000..ded7e49 --- /dev/null +++ b/listings_service.go @@ -0,0 +1,148 @@ +package yext + +import ( + "net/url" + "strings" +) + +const ( + listingsPath = "listings" + ListingsListMaxLimit = 100 +) + +type AlternateBrands struct { + BrandName string `json:"brandName"` + ListingURL string `json:"listingUrl"` +} + +type Listing struct { + ID string `json:"id"` + LocationID string `json:"locationId"` + AccountID string `json:"accountId"` + PublisherID string `json:"publisherId"` + Status string `json:"status"` + AdditionalStatus string `json:"additionalStatus,omitempty"` + ListingURL string `json:"listingUrl"` + ScreenshotURL string `json:"screenshotUrl"` + AlternateBrands *[]AlternateBrands `json:"alternateBrands"` + LoginURL string `json:"loginUrl,omitempty"` +} + +type ListingResponse struct { + Count int `json:"count"` + Listings []Listing `json:"listings"` + PageToken string `json:"pageToken"` +} + +type TokenResponseObject struct { + AccessToken string `json:"access_token"` + InstanceURL string `json:"instance_url"` + Id string `json:"id"` + TokenType string `json:"token_type"` + IssuedAt string `json:"issued_at"` + Signature string `json:"signature"` +} + +type ListingsService struct { + client *Client +} + +type ListingsListOptions struct { + ListOptions + EntityIds []string `json:"entityIds"` + Language string `json:"language"` + PublisherIds []string `json:"publisherIds"` + Statuses []string `json:"statuses"` +} + +// ListAll performs the API call outlined here +// https://hitchhikers.yext.com/docs/knowledgeapis/listings/listingsmanagement/listings/ +func (l *ListingsService) ListAll(opts *ListingsListOptions) ([]Listing, error) { + var ( + listings []Listing + ) + + if opts == nil { + opts = &ListingsListOptions{} + } + + opts.ListOptions = ListOptions{Limit: ListingsListMaxLimit} + var lg tokenListRetriever = func(listOptions *ListOptions) (string, error) { + opts.ListOptions = *listOptions + resp, _, err := l.List(opts) + if err != nil { + return "", err + } + + listings = append(listings, resp.Listings...) + + return resp.PageToken, nil + } + + if err := tokenListHelper(lg, &opts.ListOptions); err != nil { + return nil, err + } + return listings, nil +} + +// List performs the API call outlined here +// https://hitchhikers.yext.com/docs/knowledgeapis/listings/listingsmanagement/listings/ +func (l *ListingsService) List(opts *ListingsListOptions) (*ListingResponse, *Response, error) { + var ( + requrl = listingsPath + "/listings" + err error + ) + + if opts != nil { + requrl, err = addListingListOptions(requrl, opts) + if err != nil { + return nil, nil, err + } + } + + if opts != nil { + requrl, err = addListOptions(requrl, &opts.ListOptions) + if err != nil { + return nil, nil, err + } + } + + v := &ListingResponse{} + r, err := l.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + + return v, r, nil +} + +// addListingListOptions adds options to query that are specific to the listings API +func addListingListOptions(requrl string, opts *ListingsListOptions) (string, error) { + if opts == nil { + return requrl, nil + } + + u, err := url.Parse(requrl) + if err != nil { + return "", err + } + + q := u.Query() + if len(opts.EntityIds) > 0 { + q.Add("entityIds", strings.Join(opts.EntityIds, ",")) + } + if len(opts.Statuses) > 0 { + q.Add("statuses", strings.Join(opts.Statuses, ",")) + } + if len(opts.PublisherIds) > 0 { + q.Add("publisherIds", strings.Join(opts.PublisherIds, ",")) + } + if opts.Language != "" { + q.Add("language", opts.Language) + } + + u.RawQuery = q.Encode() + + return u.String(), nil + +} From 25a07c09e904c9ced4d1fe41f372ebb3e6b2efb1 Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Tue, 13 Dec 2022 15:48:38 -0500 Subject: [PATCH 245/285] adds entity_product.go for product entity type (#291) --- entity_product.go | 159 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 entity_product.go diff --git a/entity_product.go b/entity_product.go new file mode 100644 index 0000000..8ee241b --- /dev/null +++ b/entity_product.go @@ -0,0 +1,159 @@ +package yext + +import ( + "encoding/json" +) + +const ENTITYTYPE_PRODUCT EntityType = "product" + +type ProductEntity struct { + BaseEntity + AvailabilityDate *string `json:"availabilityDate,omitempty"` + Brand *string `json:"brand,omitempty"` + Bundle **bool `json:"bundle,omitempty"` + Color *string `json:"color,omitempty"` + Condition *string `json:"condition,omitempty"` + EnergyEfficiencyClass *string `json:"energyEfficiencyClass,omitempty"` + ExpirationDate *string `json:"expirationDate,omitempty"` + GTIN *string `json:"gtin,omitempty"` + IncludesAdultContent **bool `json:"includesAdultContent,omitempty"` + InstallmentPlan **Plan `json:"installmentPlan,omitempty"` + InventoryQuantity *int `json:"inventoryQuantity,omitempty"` + LoyaltyPoints **LoyaltyPoints `json:"loyaltyPoints,omitempty"` + Material *string `json:"material,omitempty"` + MaximumEnergyEfficiencyClass *string `json:"maximumEnergyEfficiencyClass,omitempty"` + MinimumEnergyEfficiencyClass *string `json:"minimumEnergyEfficiencyClass,omitempty"` + MPN *string `json:"mpn,omitempty"` + NumberOfItemsInPack *int `json:"numberOfItemsInPack,omitempty"` + Pattern *string `json:"pattern,omitempty"` + Price **Price `json:"price,omitempty"` + ProductHighlights *UnorderedStrings `json:"productHighlights,omitempty"` + ProductionCost **Price `json:"productionCost,omitempty"` + RichTextDescription *string `json:"richTextDescription,omitempty"` + Sales *[]**Sale `json:"sales,omitempty"` + SalesChannel *string `json:"salesChannel,omitempty"` + ShippingHeight **ProductUnit `json:"shippingHeight,omitempty"` + ShippingLength **ProductUnit `json:"shippingLength,omitempty"` + ShippingWeight **ProductUnit `json:"shippingWeight,omitempty"` + ShippingWidth **ProductUnit `json:"shippingWidth,omitempty"` + Size *string `json:"size,omitempty"` + SizeSystem *string `json:"sizeSystem,omitempty"` + SizeType *[]string `json:"sizeType,omitempty"` + SKU *string `json:"sku,omitempty"` + StockStatus *string `json:"stockStatus,omitempty"` + SubscriptionPlan Plan `json:"subscriptionPlan,omitempty"` + TargetAgeGroup *string `json:"targetAgeGroup,omitempty"` + TargetGender *string `json:"targetGender,omitempty"` + UnitPricingBaseMeasure **ProductUnit `json:"unitPricingBaseMeasure,omitempty"` + UnitPricingMeasure **ProductUnit `json:"unitPricingMeasure,omitempty"` + Name *string `json:"name,omitempty"` + Keywords *[]string `json:"keywords,omitempty"` + PhotoGallery *[]Photo `json:"photoGallery,omitempty"` + Videos *[]Video `json:"videos,omitempty"` + Timezone *string `json:"timezone,omitempty"` +} + +func (l *ProductEntity) UnmarshalJSON(data []byte) error { + type Alias ProductEntity + a := &struct { + *Alias + }{ + Alias: (*Alias)(l), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + return UnmarshalEntityJSON(l, data) +} + +func (y ProductEntity) GetName() string { + if y.Name != nil { + return GetString(y.Name) + } + return "" +} + +func (y ProductEntity) GetRichTextDescription() string { + if y.RichTextDescription != nil { + return *y.RichTextDescription + } + return "" +} + +func (y ProductEntity) String() string { + b, _ := json.Marshal(y) + return string(b) +} + +func (y ProductEntity) GetKeywords() (v []string) { + if y.Keywords != nil { + v = *y.Keywords + } + return v +} + +func (y ProductEntity) GetVideos() (v []Video) { + if y.Videos != nil { + v = *y.Videos + } + return v +} + +type Plan struct { + NumberOfPeriods *int `json:"numberOfPeriods,omitempty"` + Price **Price `json:"price,omitempty"` + Period *string `json:"period,omitempty"` +} + +func NullablePlan(p *Plan) **Plan { + return &p +} + +func NullPlan() **Plan { + var p *Plan + return &p +} + +type LoyaltyPoints struct { + LoyaltyProgramName *string `json:"loyaltyProgramName,omitempty"` + NumberOfPoints *int `json:"numberOfPoints,omitempty"` + RatioToCurrency *float64 `json:"ratioToCurrency,omitempty"` +} + +func NullableLoyaltyPoints(p *LoyaltyPoints) **LoyaltyPoints { + return &p +} + +func NullLoyaltyPoints() **LoyaltyPoints { + var p *LoyaltyPoints + return &p +} + +type Sale struct { + SalePrice **Price `json:"salePrice,omitempty"` + SaleStartDate *string `json:"saleStartDate,omitempty"` + SaleEndDate *string `json:"saleEndDate,omitempty"` +} + +func NullableSale(s *Sale) **Sale { + return &s +} + +func NullSale() **Sale { + var s *Sale + return &s +} + +type ProductUnit struct { + Unit *string `json:"unit,omitempty"` + Value *string `json:"value,omitempty"` +} + +func NullableProductUnit(p *ProductUnit) **ProductUnit { + return &p +} + +func NullProductUnit() **ProductUnit { + var p *ProductUnit + return &p +} From c2a08163f30c7fe2074b2eb388c3883836b9d6d6 Mon Sep 17 00:00:00 2001 From: mtusman Date: Fri, 13 Jan 2023 16:18:22 +0000 Subject: [PATCH 246/285] modify AnalyticsData struct (#292) --- analytics_data.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/analytics_data.go b/analytics_data.go index 561870b..02ee6dc 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -25,9 +25,11 @@ type AnalyticsData struct { GooglePhoneCalls *int `json:"Google Phone Calls"` YelpCustomerActions *int `json:"Yelp Customer Actions"` AverageRating *float64 `json:"Average Rating"` - NewReviews *int `json:"Reviews"` + NewReviews *int `json:"Reviews"` // used to unmarshal new reviews for api versions on or after 20210729 + NewReviewsAlternative *int `json:"New Reviews"` // used to unmarshal new reviews for api versions before 20210729 StorepagesSessions *int `json:"Storepages Sessions"` - StorepagesPageviews *int `json:"Pages Views"` + StorepagesPageviews *int `json:"Pages Views"` // used to unmarshal page views for api versions before 20210729 + StorepagesPageviewsAlternative *int `json:"Page Views"` // used to unmarshal page views for api versions on or after 20210729 StorepagesDrivingdirections *int `json:"Driving Directions"` StorepagesPhonecalls *int `json:"Taps to Call"` StorepagesCalltoactionclicks *int `json:"Call to Action Clicks"` @@ -65,6 +67,7 @@ type AnalyticsData struct { AnswersSearches *int `json:"ANSWERS_SEARCHES"` Week *string `json:"week"` AnswersKGResultRate *float64 `json:"ANSWERS_KG_RESULT_RATE"` + ResponseCount *int `json:"Response Count"` } func (y AnalyticsData) GetCompetitor() string { @@ -256,6 +259,13 @@ func (y AnalyticsData) GetNewReviews() int { return 0 } +func (y AnalyticsData) GetNewReviewsAlternative() int { + if y.NewReviewsAlternative != nil { + return *y.NewReviewsAlternative + } + return 0 +} + func (y AnalyticsData) GetStorepagesSessions() int { if y.StorepagesSessions != nil { return *y.StorepagesSessions @@ -270,6 +280,13 @@ func (y AnalyticsData) GetStorepagesPageviews() int { return 0 } +func (y AnalyticsData) GetStorepagesPageviewsAlternative() int { + if y.StorepagesPageviewsAlternative != nil { + return *y.StorepagesPageviewsAlternative + } + return 0 +} + func (y AnalyticsData) GetStorepagesDrivingdirections() int { if y.StorepagesDrivingdirections != nil { return *y.StorepagesDrivingdirections @@ -459,6 +476,13 @@ func (y AnalyticsData) GetMonth() string { return "" } +func (y AnalyticsData) GetResponseRate() int { + if y.ResponseRate != nil { + return *y.ResponseRate + } + return 0 +} + func (y AnalyticsData) GetClicks() int { if y.Clicks != nil { return *y.Clicks @@ -492,4 +516,11 @@ func (y AnalyticsData) GetAnswersKGResultRate() float64 { return *y.AnswersKGResultRate } return -1 -} \ No newline at end of file +} + +func (y AnalyticsData) GetResponseCount() int { + if y.ResponseCount != nil { + return *y.ResponseCount + } + return 0 +} From 71542dba245d6390608ff44c88ddb017d75e3430 Mon Sep 17 00:00:00 2001 From: nyellowhair Date: Tue, 17 Jan 2023 11:39:44 -0700 Subject: [PATCH 247/285] Adding Google Place ID & Facebook Store ID --- entity_location.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/entity_location.go b/entity_location.go index 819e160..e16df3a 100644 --- a/entity_location.go +++ b/entity_location.go @@ -114,11 +114,13 @@ type LocationEntity struct { FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + FacebookStoreId *string `json:"facebookStoreId,omitempty"` FacebookUsername *string `json:"facebookVanityUrl,omitempty"` // It's called vanity url in json but Username in front end GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GoogleEntityRelationship **GoogleEntityRelationship `json:"googleEntityRelationship,omitempty"` GoogleMyBusinessLabels *[]string `json:"googleMyBusinessLabels,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` @@ -402,7 +404,7 @@ func NewHoursUnspecifiedAllWeek() *Hours { type DayHours struct { OpenIntervals *[]Interval `json:"openIntervals,omitempty"` - IsClosed bool `json:"isClosed,omitempty"` + IsClosed bool `json:"isClosed,omitempty"` } func NullableDayHours(d *DayHours) **DayHours { @@ -995,7 +997,7 @@ func (y LocationEntity) IsClosed() bool { type HolidayHours struct { Date *string `json:"date"` OpenIntervals *[]Interval `json:"openIntervals,omitempty"` - IsClosed bool `json:"isClosed,omitempty"` + IsClosed bool `json:"isClosed,omitempty"` IsRegularHours **bool `json:"isRegularHours,omitempty"` } From 8778db6df0ec5cf387b7eaa610e136503616fe0d Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Fri, 20 Jan 2023 03:52:36 -0500 Subject: [PATCH 248/285] adds PrimaryPhoto field to products (#293) * adds PrimaryPhoto field to products * adds landingpageurl too --- entity_product.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/entity_product.go b/entity_product.go index 8ee241b..d19f6e0 100644 --- a/entity_product.go +++ b/entity_product.go @@ -19,6 +19,7 @@ type ProductEntity struct { IncludesAdultContent **bool `json:"includesAdultContent,omitempty"` InstallmentPlan **Plan `json:"installmentPlan,omitempty"` InventoryQuantity *int `json:"inventoryQuantity,omitempty"` + LandingPageURL *string `json:"landingPageUrl,omitempty"` LoyaltyPoints **LoyaltyPoints `json:"loyaltyPoints,omitempty"` Material *string `json:"material,omitempty"` MaximumEnergyEfficiencyClass *string `json:"maximumEnergyEfficiencyClass,omitempty"` @@ -27,6 +28,7 @@ type ProductEntity struct { NumberOfItemsInPack *int `json:"numberOfItemsInPack,omitempty"` Pattern *string `json:"pattern,omitempty"` Price **Price `json:"price,omitempty"` + PrimaryPhoto **Photo `json:"primaryPhoto,omitempty"` ProductHighlights *UnorderedStrings `json:"productHighlights,omitempty"` ProductionCost **Price `json:"productionCost,omitempty"` RichTextDescription *string `json:"richTextDescription,omitempty"` From d0c7b05a5b4adf33b866cfaa02ac293fa410bf13 Mon Sep 17 00:00:00 2001 From: Jesuye David <12143814+jesuyedavid@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:32:39 -0500 Subject: [PATCH 249/285] add createreviewliveapi to review service (#294) * add createreviewliveapi to review service * use double pointer for review entity struct --- review.go | 24 +++++++++++---- review_service.go | 76 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/review.go b/review.go index fb712fc..0b26e6d 100644 --- a/review.go +++ b/review.go @@ -17,6 +17,11 @@ type ReviewEntity struct { Id string `json:"id"` } +func NullableReviewEntity(v ReviewEntity) **ReviewEntity { + p := &v + return &p +} + type Review struct { Id *int `json:"id"` LocationId *string `json:"locationId"` @@ -44,18 +49,25 @@ type Review struct { } type ReviewCreate struct { - LocationId *string `json:"locationId"` - ExternalId *string `json:"externalId"` // Must have v param >= 20220120 - AccountId *string `json:"accountId"` - Rating *float64 `json:"rating"` - Content *string `json:"content"` - AuthorName *string `json:"authorName"` + LocationId *string `json:"locationId,omitempty"` + ExternalId *string `json:"externalId,omitempty"` // Must have v param >= 20220120 + AccountId *string `json:"accountId,omitempty"` + Rating *float64 `json:"rating,omitempty"` + Content *string `json:"content,omitempty"` + AuthorName *string `json:"authorName,omitempty"` AuthorEmail *string `json:"authorEmail,omitempty"` Status *string `json:"status,omitempty"` FlagStatus *string `json:"flagStatus,omitempty"` ReviewLanguage *string `json:"reviewLanguage,omitempty"` TransactionId *string `json:"transactionId,omitempty"` Date *string `json:"date,omitempty"` + + //LiveAPI Specific Fields + ReviewEntity **ReviewEntity `json:"entity,omitempty"` + Title *string `json:"title,omitempty"` + ReviewLabelNames *[]string `json:"reviewLabelNames,omitempty"` + InvitationUID *string `json:"invitationUid,omitempty"` + ReviewDate *string `json:"reviewDate,omitempty"` } type Comment struct { diff --git a/review_service.go b/review_service.go index 1ff182c..ebdba1c 100644 --- a/review_service.go +++ b/review_service.go @@ -1,7 +1,10 @@ package yext import ( + "encoding/json" "fmt" + "io/ioutil" + "net/http" "net/url" "strconv" "strings" @@ -77,17 +80,17 @@ type ReviewUpdateResponse struct { } type ReviewCreateInvitationResponse struct { - InvitationUID string `json:"invitationUid"` - Id string `json:"id"` - LocationId string `json:"locationId"` - Entity *ReviewEntity `json:"entity,omitempty"` // Must have v param >= 20210728 - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Contact string `json:"contact"` - Image bool `json:"image"` - TemplateId int `json:"templateId"` - Status string `json:"status"` - Details string `json:"details"` + InvitationUID string `json:"invitationUid"` + Id string `json:"id"` + LocationId string `json:"locationId"` + Entity **ReviewEntity `json:"entity,omitempty"` // Must have v param >= 20210728 + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Contact string `json:"contact"` + Image bool `json:"image"` + TemplateId int `json:"templateId"` + Status string `json:"status"` + Details string `json:"details"` } type ReviewCreateReviewResponse struct { @@ -259,6 +262,57 @@ func (l *ReviewService) CreateReview(jsonData *ReviewCreate) (*ReviewCreateRevie return v, r, nil } +//the new way to create reviews on the yext platform +//refer to https://yextops.slack.com/archives/C01269F1ZTL/p1634751884059700 +func (l *ReviewService) CreateReviewLiveAPI(jsonData *ReviewCreate) (*ReviewCreateReviewResponse, *Response, error) { + reviewCreateReviewResponse := &ReviewCreateReviewResponse{} + baseURL := "https://liveapi.yext.com" + if strings.Contains(l.client.Config.BaseUrl, "sandbox") { + baseURL = "https://liveapi-sandbox.yext.com" + } + + reviewSubmissionLiveAPIURL := fmt.Sprintf("%s/v2/accounts/%s/reviewSubmission?api_key=%s&v=%s", baseURL, l.client.Config.AccountId, l.client.Config.ApiKey, l.client.Config.Version) + + client := &http.Client{} + req, err := http.NewRequest(http.MethodPost, reviewSubmissionLiveAPIURL, strings.NewReader(jsonData.String())) + if err != nil { + fmt.Println(err) + return nil, nil, err + } + + req.Header.Add("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return nil, nil, err + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, nil, err + } + + reviewResponse := &Response{} + + err = json.Unmarshal(body, reviewResponse) + if err != nil { + return nil, nil, err + } + + err = json.Unmarshal(*reviewResponse.ResponseRaw, reviewCreateReviewResponse) + if err != nil { + return nil, nil, err + } + + if len(reviewResponse.Meta.Errors) > 0 { + return nil, reviewResponse, fmt.Errorf("error creating review: %s", reviewResponse.Meta.Errors[0].Message) + } + + return reviewCreateReviewResponse, reviewResponse, nil +} + type ReviewUpdateLabelOptions struct { LabelIds *[]int `json:"labelIds,omitempty"` } From 8e8aa17d1cea495ef8d48219123251d5943a74cf Mon Sep 17 00:00:00 2001 From: mtusman Date: Thu, 9 Mar 2023 15:16:25 +0000 Subject: [PATCH 250/285] add support for listing impressions metric (#295) --- analytics_data.go | 16 ++++++++++++++++ analytics_service.go | 1 + 2 files changed, 17 insertions(+) diff --git a/analytics_data.go b/analytics_data.go index 02ee6dc..a1cab7f 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -68,6 +68,8 @@ type AnalyticsData struct { Week *string `json:"week"` AnswersKGResultRate *float64 `json:"ANSWERS_KG_RESULT_RATE"` ResponseCount *int `json:"Response Count"` + ListingImpressions *int `json:"LISTINGS_IMPRESSIONS"` + App *string `json:"APP"` } func (y AnalyticsData) GetCompetitor() string { @@ -524,3 +526,17 @@ func (y AnalyticsData) GetResponseCount() int { } return 0 } + +func (y AnalyticsData) GetListingImpressions() int { + if y.ListingImpressions != nil { + return *y.ListingImpressions + } + return 0 +} + +func (y AnalyticsData) GetApp() string { + if y.App != nil { + return *y.App + } + return "" +} diff --git a/analytics_service.go b/analytics_service.go index 05f0542..ab581db 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -10,6 +10,7 @@ type AnalyticsFilters struct { StartDate *string `json:"startDate"` EndDate *string `json:"endDate"` LocationIds *[]string `json:"locationIds"` + EntityIds *[]string `json:"entityIds"` EntityTypes *[]string `json:"entityType"` FolderId *int `json:"folderId"` Countries *[]string `json:"countries"` From f7f2a276e001d4c3a2b6f38bb4a3ee96ef3fe18a Mon Sep 17 00:00:00 2001 From: mleifer Date: Thu, 13 Apr 2023 15:53:19 -0400 Subject: [PATCH 251/285] add rankTrackingCompetitors to location entity --- entity_location.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/entity_location.go b/entity_location.go index e16df3a..437c108 100644 --- a/entity_location.go +++ b/entity_location.go @@ -139,6 +139,9 @@ type LocationEntity struct { TimeZoneUtcOffset *string `json:"timeZoneUtcOffset,omitempty"` Timezone *string `json:"timezone,omitempty"` + + //Competitors + RankTrackingCompetitors *[]Competitor `json:"rankTrackingCompetitors,omitempty"` } func (l *LocationEntity) UnmarshalJSON(data []byte) error { @@ -1053,3 +1056,8 @@ func NullableFAQ(c FAQField) **FAQField { p := &c return &p } + +type Competitor struct { + Name *string `json:"name,omitempty"` + Website *string `json:"website,omitempty"` +} From cec6d5ae1708e8bb4483898ac3b9091bff2484a6 Mon Sep 17 00:00:00 2001 From: Aidan Fitzgerald Date: Fri, 21 Apr 2023 11:12:47 -0400 Subject: [PATCH 252/285] add timezone field for restaurant entities (#297) --- entity_restaurant.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/entity_restaurant.go b/entity_restaurant.go index 79f12b0..b720e40 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -90,6 +90,9 @@ type RestaurantEntity struct { // Reviews ReviewGenerationUrl *string `json:"reviewGenerationUrl,omitempty"` FirstPartyReviewPage *string `json:"firstPartyReviewPage,omitempty"` + + TimeZoneUtcOffset *string `json:"timeZoneUtcOffset,omitempty"` + Timezone *string `json:"timezone,omitempty"` } func (r *RestaurantEntity) UnmarshalJSON(data []byte) error { From 148e145149e54bd38e9313ea7da1509bfc869fe0 Mon Sep 17 00:00:00 2001 From: kdelacruz Date: Thu, 27 Apr 2023 13:35:52 -0400 Subject: [PATCH 253/285] feat(custom): amend pr comments:wq to entity healthcare professional/healthcare facility for ETL use J=PC-213266 TEST=manual --- entity_healthcare_facility.go | 14 ++++++++------ entity_healthcare_professional.go | 13 +++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/entity_healthcare_facility.go b/entity_healthcare_facility.go index 29e82ac..c4184db 100644 --- a/entity_healthcare_facility.go +++ b/entity_healthcare_facility.go @@ -8,6 +8,7 @@ const ENTITYTYPE_HEALTHCAREFACILITY EntityType = "healthcareFacility" // Location is the representation of a Location in Yext Location Manager. // For details see https://www.yext.com/support/platform-api/#Administration_API/Locations.htm + type HealthcareFacilityEntity struct { BaseEntity @@ -90,12 +91,13 @@ type HealthcareFacilityEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` - GooglePlaceId *string `json:"googlePlaceId,omitempty"` - GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` + GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` + GoogleEntityRelationship **GoogleEntityRelationship `json:"googleEntityRelationship,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 1dac23b..db225c1 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -106,12 +106,13 @@ type HealthcareProfessionalEntity struct { FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` - GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` - GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` - GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` - GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` - GooglePlaceId *string `json:"googlePlaceId,omitempty"` - GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` + GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` + GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` + GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` + GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` + GooglePlaceId *string `json:"googlePlaceId,omitempty"` + GoogleMyBusinessLabels *UnorderedStrings `json:"googleMyBusinessLabels,omitempty"` + GoogleEntityRelationship **GoogleEntityRelationship `json:"googleEntityRelationship,omitempty"` InstagramHandle *string `json:"instagramHandle,omitempty"` TwitterHandle *string `json:"twitterHandle,omitempty"` From c79eb9867aa679adc4ef019a3d81f952c157fd12 Mon Sep 17 00:00:00 2001 From: John Cho <56008387+JohnCho92@users.noreply.github.com> Date: Wed, 17 May 2023 13:49:21 -0400 Subject: [PATCH 254/285] add facebook location descriptor and username fields for restaurant entities (#299) Co-authored-by: JohnCho92 --- entity_restaurant.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/entity_restaurant.go b/entity_restaurant.go index b720e40..d2d4142 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -70,9 +70,11 @@ type RestaurantEntity struct { UberTripBranding **UberTripBranding `json:"uberTripBranding,omitempty"` // Social Media - FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` - FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` - FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + FacebookCoverPhoto **Image `json:"facebookCoverPhoto,omitempty"` + FacebookLocationDescriptor *string `json:"facebookDescriptor,omitempty"` + FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` + FacebookProfilePhoto **Image `json:"facebookProfilePhoto,omitempty"` + FacebookUsername *string `json:"facebookVanityUrl,omitempty"` GoogleCoverPhoto **Image `json:"googleCoverPhoto,omitempty"` GooglePreferredPhoto *string `json:"googlePreferredPhoto,omitempty"` From 34f39dee3adced60ba5eb22431086e997686473b Mon Sep 17 00:00:00 2001 From: nirmalpatel94 Date: Tue, 18 Jul 2023 12:11:06 -0400 Subject: [PATCH 255/285] feat: add custom unmarshalling for newer V param's custom fields - newer API versions return custom fields differently. This change will give some backwards compatibility J=PC-219314 TEST=manual --- customfield.go | 86 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/customfield.go b/customfield.go index 9252b36..86b28a4 100644 --- a/customfield.go +++ b/customfield.go @@ -1,5 +1,9 @@ package yext +import ( + "encoding/json" +) + const ( CUSTOMFIELDTYPE_YESNO = "BOOLEAN" CUSTOMFIELDTYPE_SINGLELINETEXT = "TEXT" @@ -19,20 +23,26 @@ const ( CUSTOMFIELDTYPE_RICHTEXT = "RICH_TEXT" ) +type EntityRelationship struct { + SupportedDestinationEntityTypeIds []string `json:"supportedDestinationEntityTypeIds,omitempty"` + Type string `json:"type,omitempty"` +} + type CustomFieldValidation struct { - MinCharLength int `json:"minCharLength,omitempty"` - MaxCharLength int `json:"maxCharLength,omitempty"` - MinItemCount int `json:"minItemCount,omitempty"` - MaxItemCount int `json:"maxItemCount,omitempty"` - MinValue float64 `json:"minValue,omitempty"` - MaxValue float64 `json:"maxValue,omitempty"` - MinDate string `json:"minDate,omitempty"` - MaxDate string `json:"maxDate,omitempty"` - AspectRatio string `json:"aspectRatio,omitempty"` - MinWidth int `json:"minWidth,omitempty"` - MinHeight int `json:"minHeight,omitempty"` - EntityTypes []string `json:"entityTypes,omitempty"` - RichTextFormats []string `json:"richTextFormats,omitempty"` + MinCharLength int `json:"minCharLength,omitempty"` + MaxCharLength int `json:"maxCharLength,omitempty"` + MinItemCount int `json:"minItemCount,omitempty"` + MaxItemCount int `json:"maxItemCount,omitempty"` + MinValue float64 `json:"minValue,omitempty"` + MaxValue float64 `json:"maxValue,omitempty"` + MinDate string `json:"minDate,omitempty"` + MaxDate string `json:"maxDate,omitempty"` + AspectRatio string `json:"aspectRatio,omitempty"` + MinWidth int `json:"minWidth,omitempty"` + MinHeight int `json:"minHeight,omitempty"` + EntityTypes []string `json:"entityTypes,omitempty"` + RichTextFormats []string `json:"richTextFormats,omitempty"` + EntityRelationship *EntityRelationship `json:"entityRelationship,omitempty"` } type CustomField struct { @@ -41,12 +51,60 @@ type CustomField struct { Name string `json:"name"` Options []CustomFieldOption `json:"options,omitempty"` // Only present for option custom fields Group string `json:"group"` - Description string `json:"description"` + Description string `json:"description,omitempty"` AlternateLanguageBehaviour string `json:"alternateLanguageBehavior"` EntityAvailability []EntityType `json:"entityAvailability"` Validation *CustomFieldValidation `json:"validation,omitempty"` // Needed for rich text formatting } +func (c *CustomField) UnmarshalJSON(data []byte) error { + type Alias CustomField + + // name and description are strings on older API versions (v param), and objects + // on the newer ones. For backwards compatibility, convert from object to string + a := &struct { + *Alias + Name json.RawMessage `json:"name"` + Description json.RawMessage `json:"description"` + }{ + Alias: (*Alias)(c), + } + if err := json.Unmarshal(data, &a); err != nil { + return err + } + + if a.Name != nil { + // try to unmarshal as string first, fallback to object + if err := json.Unmarshal(a.Name, &c.Name); err != nil { + var nameValue struct { + Value string `json:"value"` + } + + if err := json.Unmarshal(a.Name, &nameValue); err != nil { + return err + } + + c.Name = nameValue.Value + } + } + + if a.Description != nil { + // try to unmarshal as string first, fallback to object + if err := json.Unmarshal(a.Description, &c.Description); err != nil { + var val struct { + Value string `json:"value"` + } + + if err := json.Unmarshal(a.Description, &val); err != nil { + return err + } + + c.Description = val.Value + } + } + return nil +} + func (c CustomField) GetId() string { if c.Id == nil { return "" From 9bfb0c11da746ef98847de4a3cd4396b0330cb11 Mon Sep 17 00:00:00 2001 From: mtusman Date: Tue, 12 Sep 2023 11:01:46 +0100 Subject: [PATCH 256/285] =?UTF-8?q?migrate=20custom=20field=20endpoint=20t?= =?UTF-8?q?o=20optionally=20use=20the=20configuration=20end=E2=80=A6=20(#3?= =?UTF-8?q?01)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * migrate custom field endpoint to optionally use the configuration endpoint J=CR-3126 TEST=auto * refactor: respond to dev comments J=CR-3126 --- cac.go | 301 ++++++++++++++++++++ cac_service.go | 134 +++++++++ cac_service_test.go | 475 ++++++++++++++++++++++++++++++++ client.go | 8 + customfield.go | 286 +++++++++++++++++-- customfield_service.go | 48 +++- location_customfield_service.go | 14 + 7 files changed, 1235 insertions(+), 31 deletions(-) create mode 100644 cac.go create mode 100644 cac_service.go create mode 100644 cac_service_test.go diff --git a/cac.go b/cac.go new file mode 100644 index 0000000..20153cc --- /dev/null +++ b/cac.go @@ -0,0 +1,301 @@ +package yext + +import "unicode" + +const ( + CONFIGFIELDTYPE_YESNO = "boolean" + CONFIGFIELDTYPE_DECIMAL = "decimal" + CONFIGFIELDTYPE_DATE = "date" + CONFIGFIELDTYPE_DAILYTIMES = "dailyTimes" + CONFIGFIELDTYPE_HOURS = "hours" + CONFIGFIELDTYPE_IMAGE = "image" + CONFIGFIELDTYPE_OPTION = "option" + CONFIGFIELDTYPE_VIDEO = "video" + CONFIGFIELDTYPE_RICHTEXT = "richText" + CONFIGFIELDTYPE_RICHTEXTV2 = "richTextV2" + CONFIGFIELDTYPE_LIST = "list" + CONFIGFIELDTYPE_STRING = "string" + CONFIGFIELDTYPE_MARKDOWN = "markdown" + CONFIGFIELDTYPE_CTA = "cta" + CONFIGFIELDTYPE_PRICE = "price" + CONFIGFIELDTYPE_ENTITYREFERENCE = "entityReference" +) + +type ConfigFieldEligibilityGroup struct { + Id *string `json:"$id,omitempty"` + Schema string `json:"$schema,omitempty"` + Name string `json:"name,omitempty"` + EntityType EntityType `json:"entityType,omitempty"` + Fields []string `json:"fields,omitempty"` + Required []string `json:"required,omitempty"` +} + +type ConfigTranslation struct { + LocaleCode string `json:"localeCode,omitempty"` + Value string `json:"value,omitempty"` +} + +type ConfigOption struct { + DisplayName string `json:"displayName,omitempty"` + DisplayNameTranslation []ConfigTranslation `json:"displayNameTranslation,omitempty"` + TextValue string `json:"textValue,omitempty"` +} + +type ConfigOptionType struct { + Option []ConfigOption `json:"option,omitempty"` + NoOptions bool `json:"noOptions,omitempty"` +} + +type ConfigStringType struct { + Stereotype string `json:"stereotype,omitempty"` + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` +} + +type ConfigDescriptionType struct { + StereoType string `json:"stereoType,omitempty"` + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` +} + +type ConfigVideoType struct { + IsSimpleVideo bool `json:"isSimpleVideo,omitempty"` + DescriptionType *ConfigDescriptionType `json:"descriptionType,omitempty"` +} + +type ConfigMinSizeType struct { + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` +} + +type ConfigImageType struct { + IsSimpleImage bool `json:"isSimpleImage,omitempty"` + MinSize *ConfigMinSizeType `json:"minSize,omitempty"` + AllowedAspectRatio *[]struct { + HorizontalFactor int `json:"horizontalFactor,omitempty"` + VerticalFactor int `json:"verticalFactor,omitempty"` + } `json:"allowedAspectRatio,omitempty"` + UnconstrainedAspectRatioAllowed bool `json:"unconstrainedAspectRatioAllowed,omitempty"` + NoAllowedAspectRatio bool `json:"noAllowedAspectRatio,omitempty"` +} + +type ConfigEntityReferenceType struct { + SupportedEntityTypeIds []string `json:"supportedEntityTypeIds,omitempty"` + RelatedFieldId string `json:"relatedFieldId,omitempty"` + Type string `json:"type,omitempty"` +} + +type ConfigListSubType struct { + *ConfigType + EntityReferenceType *ConfigEntityReferenceType `json:"entityReferenceType,omitempty"` +} + +type ConfigListType struct { + TypeId string `json:"typeId,omitempty"` + Type ConfigListSubType `json:"type,omitempty"` + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` +} + +type ConfigRichTextType struct { + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` + SupportedFormat []string `json:"supportedFormat,omitempty"` + NoSupportedFormats bool `json:"noSupportedFormats,omitempty"` +} + +type ConfigRichTextV2Type struct { + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` + SupportedFormatFilter *[]struct { + SupportedFormats []string `json:"supportedFormats,omitempty"` + } `json:"supportedFormatFilter,omitempty"` + NoSupportedFormat bool `json:"noSupportedFormat,omitempty"` +} + +type ConfigMarkdownType struct { + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` +} + +type ConfigBooleanType map[string]interface{} + +type ConfigDayType struct { + Year string `json:"year,omitempty"` + Month string `json:"month,omitempty"` + Day string `json:"day,omitempty"` +} + +type ConfigDateType struct { + MinValue *ConfigDayType `json:"minValue,omitempty"` + MaxValue *ConfigDayType `json:"maxValue,omitempty"` +} + +type ConfigDecimalType struct { + MinValue string `json:"minValue,omitempty"` + MaxValue string `json:"maxValue,omitempty"` +} + +type ConfigType struct { + BooleanType *ConfigBooleanType `json:"booleanType,omitempty"` + DateType *ConfigDateType `json:"dateType,omitempty"` + DecimalType *ConfigDecimalType `json:"decimalType,omitempty"` + ImageType *ConfigImageType `json:"imageType,omitempty"` + RichTextV2Type *ConfigRichTextV2Type `json:"richTextV2Type,omitempty"` + ListType *ConfigListType `json:"listType,omitempty"` + MarkdownType *ConfigMarkdownType `json:"markdownType,omitempty"` + OptionType *ConfigOptionType `json:"optionType,omitempty"` + RichTextType *ConfigRichTextType `json:"richTextType,omitempty"` + StringType *ConfigStringType `json:"stringType,omitempty"` + VideoType *ConfigVideoType `json:"videoType,omitempty"` +} + +type ConfigField struct { + Id *string `json:"$id,omitempty"` + Description string `json:"description,omitempty"` + Schema string `json:"$schema,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Group string `json:"group,omitempty"` + Localization string `json:"localization,omitempty"` + Type *ConfigType `json:"type,omitempty"` + TypeId string `json:"typeId,omitempty"` +} + +func (c *ConfigField) TransformToCustomField(entityAvailability []EntityType) *CustomField { + r := &CustomField{ + Id: c.Id, + Type: c.GetCustomFieldType(), + Name: c.DisplayName, + AlternateLanguageBehaviour: c.Localization, + Options: c.GetCustomFieldOptions(), + Group: "NONE", + Description: c.Description, + EntityAvailability: entityAvailability, + } + + if c.Localization == "LOCALE_SPECIFIC" { + r.AlternateLanguageBehaviour = "LANGUAGE_SPECIFIC" + } + + for i, char := range c.Group { + if unicode.IsDigit(char) { + r.Group = c.Group[:i] + "_" + c.Group[i:] + break + } + } + + return r +} + +func (c *ConfigField) GetCustomFieldType() string { + switch c.TypeId { + case CONFIGFIELDTYPE_YESNO: + return CUSTOMFIELDTYPE_YESNO + case CONFIGFIELDTYPE_DECIMAL: + return CUSTOMFIELDTYPE_NUMBER + case CONFIGFIELDTYPE_DATE: + return CUSTOMFIELDTYPE_DATE + case CONFIGFIELDTYPE_DAILYTIMES: + return CUSTOMFIELDTYPE_DAILYTIMES + case CONFIGFIELDTYPE_HOURS: + return CUSTOMFIELDTYPE_HOURS + case CONFIGFIELDTYPE_OPTION: + return CUSTOMFIELDTYPE_SINGLEOPTION + case CONFIGFIELDTYPE_RICHTEXT: + return CUSTOMFIELDTYPE_RICHTEXT + case CONFIGFIELDTYPE_RICHTEXTV2: + return CUSTOMFIELDTYPE_RICHTEXTV2 + case CONFIGFIELDTYPE_MARKDOWN: + return CUSTOMFIELDTYPE_MARKDOWN + case CONFIGFIELDTYPE_CTA: + return CUSTOMFIELDTYPE_CALLTOACTION + case CONFIGFIELDTYPE_PRICE: + return CUSTOMFIELDTYPE_PRICE + case CONFIGFIELDTYPE_IMAGE: + if c.Type.ImageType != nil && c.Type.ImageType.IsSimpleImage { + return CUSTOMFIELDTYPE_SIMPLEPHOTO + } + return CUSTOMFIELDTYPE_PHOTO + case CONFIGFIELDTYPE_VIDEO: + if c.Type.VideoType != nil && c.Type.VideoType.IsSimpleVideo { + return CUSTOMFIELDTYPE_SIMPLEVIDEO + } + return CUSTOMFIELDTYPE_VIDEO + case CONFIGFIELDTYPE_LIST: + if c.Type.ListType != nil { + switch c.Type.ListType.TypeId { + case CONFIGFIELDTYPE_OPTION: + return CUSTOMFIELDTYPE_MULTIOPTION + case CONFIGFIELDTYPE_STRING: + if c.Type.ListType.Type.StringType != nil && c.Type.ListType.Type.StringType.Stereotype == "SIMPLE" { + return CUSTOMFIELDTYPE_TEXTLIST + } + case CONFIGFIELDTYPE_IMAGE: + if c.Type.ListType.Type.ImageType != nil && c.Type.ListType.Type.ImageType.IsSimpleImage { + return CUSTOMFIELDTYPE_SIMPLEPHOTOGALLERY + } else { + return CUSTOMFIELDTYPE_GALLERY + } + case CONFIGFIELDTYPE_VIDEO: + if c.Type.ListType.Type.VideoType != nil && c.Type.ListType.Type.VideoType.IsSimpleVideo { + return CUSTOMFIELDTYPE_SIMPLYVIDEOGALLERY + } else { + return CUSTOMFIELDTYPE_VIDEOGALLERY + } + case CONFIGFIELDTYPE_ENTITYREFERENCE: + return CUSTOMFIELDTYPE_ENTITYLIST + } + } + return CUSTOMFIELDTYPE_LIST + case CONFIGFIELDTYPE_STRING: + if c.Type.StringType != nil { + switch c.Type.StringType.Stereotype { + case "SIMPLE": + return CUSTOMFIELDTYPE_SINGLELINETEXT + case "MULTILINE": + return CUSTOMFIELDTYPE_MULTILINETEXT + case "URL": + return CUSTOMFIELDTYPE_URL + case "SLUG": + return CUSTOMFIELDTYPE_SLUG + } + } + return CUSTOMFIELDTYPE_SINGLELINETEXT + } + + return CUSTOMFIELDTYPE_STRUCT +} + +func (c *ConfigField) GetCustomFieldOptions() []CustomFieldOption { + var ( + r []CustomFieldOption + o []ConfigOption + ) + + customFieldType := c.GetCustomFieldType() + if customFieldType == CUSTOMFIELDTYPE_SINGLEOPTION { + o = c.Type.OptionType.Option + } else if customFieldType == CUSTOMFIELDTYPE_MULTIOPTION { + o = c.Type.ListType.Type.OptionType.Option + } else { + return nil + } + + for _, entry := range o { + var translations = []Translation{} + for _, translation := range entry.DisplayNameTranslation { + translations = append(translations, Translation{ + LanguageCode: translation.LocaleCode, + Value: translation.Value, + }) + } + + r = append(r, CustomFieldOption{ + Key: entry.TextValue, + Value: entry.DisplayName, + Translations: translations, + }) + } + + return r +} diff --git a/cac_service.go b/cac_service.go new file mode 100644 index 0000000..ca2233c --- /dev/null +++ b/cac_service.go @@ -0,0 +1,134 @@ +package yext + +import ( + "fmt" +) + +const ( + ConfigResourceNamesPath = "config/resourcenames/km" + ConfigResourcesPath = "config/resources/km" + ConfigFieldSubType = "field" + ConfigFieldEligibilityGroupSubType = "field-eligibility-group" +) + +// ConfigFieldService utilises the config API to retrieve & edit information about custom fields using the same +// structs as the custom field service. This is so that it can be used as a drop-in replacement for the custom field +// service once it is deprecated. See CR-3126 for more information. +type ConfigFieldService struct { + client *Client +} + +func (s *ConfigFieldService) ListAll() ([]*CustomField, error) { + var ( + customFields []*CustomField + fieldEligibilityGroupsByField = make(map[string][]EntityType) + fieldEligibilityGroups []string + fields []string + ) + + _, err := s.client.DoRequest("GET", fmt.Sprintf("%s/%s", ConfigResourceNamesPath, ConfigFieldEligibilityGroupSubType), &fieldEligibilityGroups) + if err != nil { + return nil, err + } + + for _, fieldEligibilityGroup := range fieldEligibilityGroups { + v := &ConfigFieldEligibilityGroup{} + _, err := s.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", ConfigResourcesPath, ConfigFieldEligibilityGroupSubType, fieldEligibilityGroup), v) + if err != nil { + return nil, err + } + + for _, field := range v.Fields { + fieldEligibilityGroupsByField[field] = append(fieldEligibilityGroupsByField[field], v.EntityType) + } + } + + _, err = s.client.DoRequest("GET", fmt.Sprintf("%s/%s", ConfigResourceNamesPath, ConfigFieldSubType), &fields) + if err != nil { + return nil, err + } + + for _, field := range fields { + v := &ConfigField{} + _, err := s.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", ConfigResourcesPath, ConfigFieldSubType, field), v) + if err != nil { + return nil, err + } + customFields = append(customFields, v.TransformToCustomField(fieldEligibilityGroupsByField[field])) + } + return customFields, nil +} + +func (s *ConfigFieldService) Create(cf *CustomField) (*Response, error) { + r, err := s.client.DoRequestJSON("POST", fmt.Sprintf("%s/%s", ConfigResourcesPath, ConfigFieldSubType), cf.TransformToConfigField(), nil) + if err != nil { + return r, err + } + + for _, entityType := range cf.EntityAvailability { + v := &ConfigFieldEligibilityGroup{} + r, err := s.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s.default", ConfigResourcesPath, ConfigFieldEligibilityGroupSubType, entityType), v) + if err != nil { + return r, err + } + + v.Fields = append(v.Fields, GetString(cf.Id)) + r, err = s.client.DoRequestJSON("PATCH", fmt.Sprintf("%s/%s/%s.default", ConfigResourcesPath, ConfigFieldEligibilityGroupSubType, entityType), v, nil) + if err != nil { + return r, err + } + } + + return r, nil +} + +func (s *ConfigFieldService) Edit(cf *CustomField) (*Response, error) { + updatedDefinition := cf.TransformToConfigField() + if cf.Type == CUSTOMFIELDTYPE_SINGLEOPTION || cf.Type == CUSTOMFIELDTYPE_MULTIOPTION { + // get current field definition since we don't want to overwrite any validation settings + existingDefinition := &ConfigField{} + r, err := s.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s", ConfigResourcesPath, ConfigFieldSubType, GetString(cf.Id)), existingDefinition) + if err != nil { + return r, err + } + + updatedDefinition.Type = existingDefinition.Type + if cf.Type == CUSTOMFIELDTYPE_SINGLEOPTION { + updatedDefinition.Type.OptionType.Option = cf.GetConfigOptions() + } else { + updatedDefinition.Type.ListType.Type.ConfigType.OptionType.Option = cf.GetConfigOptions() + } + } else { + updatedDefinition.Type = nil + } + + r, err := s.client.DoRequestJSON("PATCH", fmt.Sprintf("%s/%s/%s", ConfigResourcesPath, ConfigFieldSubType, GetString(cf.Id)), updatedDefinition, nil) + if err != nil { + return r, err + } + + for _, entityType := range cf.EntityAvailability { + v := &ConfigFieldEligibilityGroup{} + r, err := s.client.DoRequest("GET", fmt.Sprintf("%s/%s/%s.default", ConfigResourcesPath, ConfigFieldEligibilityGroupSubType, entityType), v) + if err != nil { + return r, err + } + + alreadyInFieldEligibilityGroup := false + for _, field := range v.Fields { + if field == GetString(cf.Id) { + alreadyInFieldEligibilityGroup = true + break + } + } + if !alreadyInFieldEligibilityGroup { + v.Fields = append(v.Fields, GetString(cf.Id)) + r, err = s.client.DoRequestJSON("PUT", fmt.Sprintf("%s/%s/%s.default", ConfigResourcesPath, ConfigFieldEligibilityGroupSubType, entityType), v, nil) + if err != nil { + return r, err + } + } + } + + return r, nil +} diff --git a/cac_service_test.go b/cac_service_test.go new file mode 100644 index 0000000..8fc1208 --- /dev/null +++ b/cac_service_test.go @@ -0,0 +1,475 @@ +package yext + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "strings" + "testing" +) + +type DataType struct { + path string + data []byte +} + +func TestConfigFieldListAll(t *testing.T) { + tests := []struct { + data []DataType + want []*CustomField + }{ + { + data: []DataType{ + { + path: "/accounts/me/config/resourcenames/km/field-eligibility-group", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["entityTypeA.default"]}`), + }, + { + path: "/accounts/me/config/resources/km/field-eligibility-group/entityTypeA.default", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id":"entityTypeA.default", "entityType": "entityTypeA", "fields": []}}`), + }, + { + path: "/accounts/me/config/resourcenames/km/field", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["fieldA"]}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldA", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id": "fieldA", "typeId": "boolean"}}`), + }, + }, + want: []*CustomField{ + { + Id: String("fieldA"), + Type: CUSTOMFIELDTYPE_YESNO, + EntityAvailability: nil, + }, + }, + }, + { + data: []DataType{ + { + path: "/accounts/me/config/resourcenames/km/field-eligibility-group", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["entityTypeA.default"]}`), + }, + { + path: "/accounts/me/config/resources/km/field-eligibility-group/entityTypeA.default", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id":"entityTypeA.default", "entityType": "entityTypeA", "fields": ["fieldA"]}}`), + }, + { + path: "/accounts/me/config/resourcenames/km/field", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["fieldA"]}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldA", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id": "fieldA", "typeId": "boolean"}}`), + }, + }, + want: []*CustomField{ + { + Id: String("fieldA"), + Type: CUSTOMFIELDTYPE_YESNO, + EntityAvailability: []EntityType{"entityTypeA"}, + }, + }, + }, + { + data: []DataType{ + { + path: "/accounts/me/config/resourcenames/km/field-eligibility-group", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["entityTypeA.default", "entityTypeB.default"]}`), + }, + { + path: "/accounts/me/config/resources/km/field-eligibility-group/entityTypeA.default", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id":"entityTypeA.default", "entityType": "entityTypeA", "fields": ["fieldA"]}}`), + }, + { + path: "/accounts/me/config/resources/km/field-eligibility-group/entityTypeB.default", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id":"entityTypeB.default", "entityType": "entityTypeB", "fields": ["fieldA", "fieldB"]}}`), + }, + { + path: "/accounts/me/config/resourcenames/km/field", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["fieldA", "fieldB"]}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldA", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id": "fieldA", "typeId": "boolean"}}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldB", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id": "fieldB", "typeId": "decimal"}}`), + }, + }, + want: []*CustomField{ + { + Id: String("fieldA"), + Type: CUSTOMFIELDTYPE_YESNO, + EntityAvailability: []EntityType{"entityTypeA", "entityTypeB"}, + }, + { + Id: String("fieldB"), + Type: CUSTOMFIELDTYPE_NUMBER, + EntityAvailability: []EntityType{"entityTypeB"}, + }, + }, + }, + { + data: []DataType{ + { + path: "/accounts/me/config/resourcenames/km/field-eligibility-group", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["entityTypeA.default"]}`), + }, + { + path: "/accounts/me/config/resources/km/field-eligibility-group/entityTypeA.default", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": {"$id":"entityTypeA.default", "entityType": "entityTypeA", "fields": ["fieldA"]}}`), + }, + { + path: "/accounts/me/config/resourcenames/km/field", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["fieldA"]}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldA", + data: []byte( + `{ + "meta": {"errors": [], "uuid": ""}, + "response": { + "$id": "fieldA", + "typeId": "list", + "type": { + "listType": { + "typeId": "option", + "type": { + "optionType": { + "option": [ + {"displayName": "Option A", "textValue": "optionA", "displayNameTranslation": [{"localeCode": "fr", "value": "Option A FR"}]}, + {"displayName": "Option B", "textValue": "optionB", "displayNameTranslation": [{"localeCode": "fr", "value": "Option B FR"}]} + ] + } + } + } + } + } + }`), + }, + }, + want: []*CustomField{ + { + Id: String("fieldA"), + Type: CUSTOMFIELDTYPE_MULTIOPTION, + EntityAvailability: []EntityType{"entityTypeA"}, + Options: []CustomFieldOption{ + { + Key: "optionA", + Value: "Option A", + Translations: []Translation{ + { + LanguageCode: "fr", + Value: "Option A FR", + }, + }, + }, + { + Key: "optionB", + Value: "Option B", + Translations: []Translation{ + { + LanguageCode: "fr", + Value: "Option B FR", + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + setup() + client.WithConfigAPIForCFs() + reqs := 0 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + reqs++ + if reqs > len(test.data) { + t.Errorf("Too many requests sent to config endpoint - got %d, expected %d", reqs, len(test.data)) + } + for _, d := range test.data { + if r.URL.Path == d.path { + w.Write(d.data) + return + } + } + t.Errorf("Unexpected request to %s", r.URL.Path) + }) + + got, _ := client.CustomFieldService.ListAll() + if !reflect.DeepEqual(got, test.want) { + var ( + gotResults []string + wantResults []string + ) + for _, g := range got { + gotResults = append(gotResults, fmt.Sprintf("%+v", g)) + } + for _, w := range test.want { + wantResults = append(wantResults, fmt.Sprintf("%+v", w)) + } + t.Errorf("CustomFieldService.ListAll() = %s, want %s", gotResults, wantResults) + } + teardown() + } +} + +func TestConfigFieldCreate(t *testing.T) { + tests := []struct { + count int + cf *CustomField + }{ + { + count: 1, + cf: &CustomField{ + Id: String("fieldA"), + }, + }, + { + count: 3, + cf: &CustomField{ + Id: String("fieldA"), + EntityAvailability: []EntityType{"entityTypeA"}, + }, + }, + { + count: 5, + cf: &CustomField{ + Id: String("fieldA"), + EntityAvailability: []EntityType{"entityTypeA", "entityTypeB"}, + }, + }, + } + + for _, test := range tests { + setup() + client.WithConfigAPIForCFs() + reqs := 0 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + reqs++ + if reqs > test.count { + t.Errorf("Too many requests sent to config endpoint - got %d, expected %d", reqs, test.count) + } + v := &mockResponse{} + if strings.Contains(r.URL.Path, "config/resources/km/field-eligibility-group") { + v = &mockResponse{Response: ConfigFieldEligibilityGroup{}} + } + data, _ := json.Marshal(v) + w.Write(data) + }) + + _, err := client.CustomFieldService.Create(test.cf) + if err != nil { + t.Errorf("CustomFieldService.Create() returned error: %v", err) + } + if reqs != test.count { + t.Errorf("Too few requests sent to config endpoint - got %d, expected %d", reqs, test.count) + } + teardown() + } +} + +func TestConfigFieldEdit(t *testing.T) { + tests := []struct { + count int + cf *CustomField + }{ + { + count: 1, + cf: &CustomField{ + Id: String("fieldA"), + }, + }, + { + count: 2, + cf: &CustomField{ + Id: String("fieldA"), + Type: CUSTOMFIELDTYPE_SINGLEOPTION, + }, + }, + { + count: 3, + cf: &CustomField{ + Id: String("fieldA"), + EntityAvailability: []EntityType{"entityTypeA"}, + }, + }, + { + count: 5, + cf: &CustomField{ + Id: String("fieldA"), + EntityAvailability: []EntityType{"entityTypeA", "entityTypeB"}, + }, + }, + { + count: 2, + cf: &CustomField{ + Id: String("fieldC"), + EntityAvailability: []EntityType{"entityTypeA"}, + }, + }, + } + + for _, test := range tests { + setup() + client.WithConfigAPIForCFs() + reqs := 0 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + reqs++ + if reqs > test.count { + t.Errorf("Too many requests sent to config endpoint - got %d, expected %d", reqs, test.count) + } + v := &mockResponse{} + if strings.Contains(r.URL.Path, "config/resources/km/field-eligibility-group") { + v = &mockResponse{Response: ConfigFieldEligibilityGroup{Fields: []string{"fieldC"}}} + } else if strings.Contains(r.URL.Path, "config/resources/km/field") { + v = &mockResponse{Response: ConfigField{Type: &ConfigType{OptionType: &ConfigOptionType{}}}} + } + data, _ := json.Marshal(v) + w.Write(data) + }) + + _, err := client.CustomFieldService.Edit(test.cf) + if err != nil { + t.Errorf("CustomFieldService.Create() returned error: %v", err) + } + if reqs != test.count { + t.Errorf("Too few requests sent to config endpoint - got %d, expected %d", reqs, test.count) + } + teardown() + } +} + +func TestCustomFieldOptionId(t *testing.T) { + tests := []struct { + data []DataType + fieldName string + optionValue string + wantId string + }{ + { + data: []DataType{ + { + path: "/accounts/me/config/resourcenames/km/field", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["fieldA"]}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldA", + data: []byte( + `{ + "meta": {"errors": [], "uuid": ""}, + "response": { + "$id": "fieldA", + "displayName": "Field A", + "typeId": "option", + "type": { + "optionType": { + "option": [ + {"displayName": "Option A", "textValue": "optionA", "displayNameTranslation": [{"localeCode": "fr", "value": "Option A FR"}]}, + {"displayName": "Option B", "textValue": "optionB", "displayNameTranslation": [{"localeCode": "fr", "value": "Option B FR"}]} + ] + } + } + } + }`), + }, + }, + fieldName: "Field A", + optionValue: "Option A", + wantId: "optionA", + }, + { + data: []DataType{ + { + path: "/accounts/me/config/resourcenames/km/field", + data: []byte(`{"meta": {"errors": [], "uuid": ""}, "response": ["fieldA", "fieldB"]}`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldA", + data: []byte( + `{ + "meta": {"errors": [], "uuid": ""}, + "response": { + "$id": "fieldA", + "displayName": "Field A", + "typeId": "option", + "type": { + "optionType": { + "option": [ + {"displayName": "Option A", "textValue": "optionA", "displayNameTranslation": [{"localeCode": "fr", "value": "Option A FR"}]}, + {"displayName": "Option B", "textValue": "optionB", "displayNameTranslation": [{"localeCode": "fr", "value": "Option B FR"}]} + ] + } + } + } + }`), + }, + { + path: "/accounts/me/config/resources/km/field/fieldB", + data: []byte( + `{ + "meta": {"errors": [], "uuid": ""}, + "response": { + "$id": "fieldB", + "displayName": "Field B", + "typeId": "list", + "type": { + "listType": { + "typeId": "option", + "type": { + "optionType": { + "option": [ + {"displayName": "Option C", "textValue": "optionC", "displayNameTranslation": [{"localeCode": "fr", "value": "Option C FR"}]}, + {"displayName": "Option D", "textValue": "optionD", "displayNameTranslation": [{"localeCode": "fr", "value": "Option D FR"}]} + ] + } + } + } + } + } + }`), + }, + }, + fieldName: "Field B", + optionValue: "Option D", + wantId: "optionD", + }, + } + for _, test := range tests { + setup() + client.WithConfigAPIForCFs() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + for _, d := range test.data { + if r.URL.Path == d.path { + w.Write(d.data) + return + } + } + v := &mockResponse{} + if strings.Contains(r.URL.Path, "config/resources/km/field-eligibility-group") { + v = &mockResponse{Response: ConfigFieldEligibilityGroup{}} + } else if strings.Contains(r.URL.Path, "config/resourcenames/km/field-eligibility-group") { + v = &mockResponse{Response: []string{}} + } + data, _ := json.Marshal(v) + w.Write(data) + }) + + client.CustomFieldService.MustCacheCustomFields() + + gotId, err := client.CustomFieldService.CustomFieldManager.CustomFieldOptionId(test.fieldName, test.optionValue) + if err != nil { + t.Errorf("CustomFieldService.CustomFieldOptionId() returned error: %v", err) + } + if gotId != test.wantId { + t.Errorf("CustomFieldService.CustomFieldOptionId() = %v, want %v", gotId, test.wantId) + } + teardown() + } +} diff --git a/client.go b/client.go index c45e904..f512738 100644 --- a/client.go +++ b/client.go @@ -50,6 +50,8 @@ type Client struct { AccountService *AccountService ServicesService *ServicesService ListingsService *ListingsService + ConfigFieldService *ConfigFieldService + UseConfigAPIForCFs bool } func NewClient(config *Config) *Client { @@ -77,6 +79,12 @@ func NewClient(config *Config) *Client { c.ServicesService = &ServicesService{client: c} c.LanguageProfileService.RegisterDefaultEntities() c.ListingsService = &ListingsService{client: c} + c.ConfigFieldService = &ConfigFieldService{client: c} + return c +} + +func (c *Client) WithConfigAPIForCFs() *Client { + c.UseConfigAPIForCFs = true return c } diff --git a/customfield.go b/customfield.go index 86b28a4..42f9c57 100644 --- a/customfield.go +++ b/customfield.go @@ -2,25 +2,38 @@ package yext import ( "encoding/json" + "strings" ) const ( - CUSTOMFIELDTYPE_YESNO = "BOOLEAN" - CUSTOMFIELDTYPE_SINGLELINETEXT = "TEXT" - CUSTOMFIELDTYPE_MULTILINETEXT = "MULTILINE_TEXT" - CUSTOMFIELDTYPE_SINGLEOPTION = "SINGLE_OPTION" - CUSTOMFIELDTYPE_URL = "URL" - CUSTOMFIELDTYPE_DATE = "DATE" - CUSTOMFIELDTYPE_NUMBER = "NUMBER" - CUSTOMFIELDTYPE_MULTIOPTION = "MULTI_OPTION" - CUSTOMFIELDTYPE_TEXTLIST = "TEXT_LIST" - CUSTOMFIELDTYPE_PHOTO = "PHOTO" - CUSTOMFIELDTYPE_GALLERY = "GALLERY" - CUSTOMFIELDTYPE_VIDEO = "VIDEO" - CUSTOMFIELDTYPE_HOURS = "HOURS" - CUSTOMFIELDTYPE_DAILYTIMES = "DAILY_TIMES" - CUSTOMFIELDTYPE_ENTITYLIST = "ENTITY_LIST" - CUSTOMFIELDTYPE_RICHTEXT = "RICH_TEXT" + CUSTOMFIELDTYPE_YESNO = "BOOLEAN" + CUSTOMFIELDTYPE_SINGLELINETEXT = "TEXT" + CUSTOMFIELDTYPE_MULTILINETEXT = "MULTILINE_TEXT" + CUSTOMFIELDTYPE_SINGLEOPTION = "SINGLE_OPTION" + CUSTOMFIELDTYPE_URL = "URL" + CUSTOMFIELDTYPE_DATE = "DATE" + CUSTOMFIELDTYPE_NUMBER = "NUMBER" + CUSTOMFIELDTYPE_MULTIOPTION = "MULTI_OPTION" + CUSTOMFIELDTYPE_TEXTLIST = "TEXT_LIST" + CUSTOMFIELDTYPE_SIMPLEPHOTO = "SIMPLE_PHOTO" + CUSTOMFIELDTYPE_PHOTO = "PHOTO" + CUSTOMFIELDTYPE_GALLERY = "GALLERY" + CUSTOMFIELDTYPE_SIMPLEVIDEO = "SIMPLE_VIDEO" + CUSTOMFIELDTYPE_VIDEO = "VIDEO" + CUSTOMFIELDTYPE_VIDEOGALLERY = "VIDEO_GALLERY" + CUSTOMFIELDTYPE_HOURS = "HOURS" + CUSTOMFIELDTYPE_DAILYTIMES = "DAILY_TIMES" + CUSTOMFIELDTYPE_ENTITYLIST = "ENTITY_LIST" + CUSTOMFIELDTYPE_RICHTEXT = "RICH_TEXT" + CUSTOMFIELDTYPE_RICHTEXTV2 = "RICH_TEXT_V2" + CUSTOMFIELDTYPE_SIMPLYVIDEOGALLERY = "SIMPLE_VIDEO_GALLERY" + CUSTOMFIELDTYPE_SIMPLEPHOTOGALLERY = "SIMPLE_PHOTO_GALLERY" + CUSTOMFIELDTYPE_MARKDOWN = "MARKDOWN" + CUSTOMFIELDTYPE_SLUG = "SLUG" + CUSTOMFIELDTYPE_CALLTOACTION = "CALL_TO_ACTION" + CUSTOMFIELDTYPE_PRICE = "PRICE" + CUSTOMFIELDTYPE_LIST = "LIST" + CUSTOMFIELDTYPE_STRUCT = "STRUCT" ) type EntityRelationship struct { @@ -105,6 +118,247 @@ func (c *CustomField) UnmarshalJSON(data []byte) error { return nil } +func (c *CustomField) TransformToConfigField() *ConfigField { + r := &ConfigField{ + Id: c.Id, + Schema: "https://schema.yext.com/config/km/field/v1", + Description: c.Description, + DisplayName: c.Name, + Group: strings.Replace(c.Group, "_", "", -1), + Localization: c.AlternateLanguageBehaviour, + Type: c.GetConfigType(), + TypeId: c.GetConfigFieldTypeId(), + } + + if c.AlternateLanguageBehaviour == "LANGUAGE_SPECIFIC" { + r.Localization = "LOCALE_SPECIFIC" + } + + return r +} + +func (c *CustomField) GetConfigFieldTypeId() string { + switch c.Type { + case CUSTOMFIELDTYPE_YESNO: + return CONFIGFIELDTYPE_YESNO + case CUSTOMFIELDTYPE_NUMBER: + return CONFIGFIELDTYPE_DECIMAL + case CUSTOMFIELDTYPE_DATE: + return CONFIGFIELDTYPE_DATE + case CUSTOMFIELDTYPE_DAILYTIMES: + return CONFIGFIELDTYPE_DAILYTIMES + case CUSTOMFIELDTYPE_HOURS: + return CONFIGFIELDTYPE_HOURS + case CUSTOMFIELDTYPE_SINGLEOPTION: + return CONFIGFIELDTYPE_OPTION + case CUSTOMFIELDTYPE_RICHTEXT: + return CONFIGFIELDTYPE_RICHTEXT + case CUSTOMFIELDTYPE_RICHTEXTV2: + return CONFIGFIELDTYPE_RICHTEXTV2 + case CUSTOMFIELDTYPE_MARKDOWN: + return CONFIGFIELDTYPE_MARKDOWN + case CUSTOMFIELDTYPE_CALLTOACTION: + return CONFIGFIELDTYPE_CTA + case CUSTOMFIELDTYPE_PRICE: + return CONFIGFIELDTYPE_PRICE + case CUSTOMFIELDTYPE_SIMPLEPHOTO, CUSTOMFIELDTYPE_PHOTO: + return CONFIGFIELDTYPE_IMAGE + case CUSTOMFIELDTYPE_SIMPLEVIDEO, CUSTOMFIELDTYPE_VIDEO: + return CONFIGFIELDTYPE_VIDEO + case CUSTOMFIELDTYPE_MULTIOPTION, + CUSTOMFIELDTYPE_TEXTLIST, + CUSTOMFIELDTYPE_SIMPLEPHOTOGALLERY, + CUSTOMFIELDTYPE_GALLERY, + CUSTOMFIELDTYPE_SIMPLYVIDEOGALLERY, + CUSTOMFIELDTYPE_VIDEOGALLERY, + CUSTOMFIELDTYPE_ENTITYLIST: + return CONFIGFIELDTYPE_LIST + case CUSTOMFIELDTYPE_SINGLELINETEXT, + CUSTOMFIELDTYPE_MULTILINETEXT, + CUSTOMFIELDTYPE_URL, + CUSTOMFIELDTYPE_SLUG: + return CONFIGFIELDTYPE_STRING + } + return c.Type +} + +func (c *CustomField) GetConfigType() *ConfigType { + configFieldTypeId := c.GetConfigFieldTypeId() + switch configFieldTypeId { + case CONFIGFIELDTYPE_YESNO: + return &ConfigType{ + BooleanType: &ConfigBooleanType{}, + } + case CONFIGFIELDTYPE_DECIMAL: + return &ConfigType{ + DecimalType: &ConfigDecimalType{}, + } + case CONFIGFIELDTYPE_DATE: + return &ConfigType{ + DateType: &ConfigDateType{}, + } + case CONFIGFIELDTYPE_IMAGE: + r := &ConfigType{ + ImageType: &ConfigImageType{ + UnconstrainedAspectRatioAllowed: true, + }, + } + + if c.Type == CUSTOMFIELDTYPE_SIMPLEPHOTO { + r.ImageType.IsSimpleImage = true + } + return r + case CONFIGFIELDTYPE_VIDEO: + r := &ConfigType{ + VideoType: &ConfigVideoType{}, + } + + if c.Type == CUSTOMFIELDTYPE_SIMPLEVIDEO { + r.VideoType.IsSimpleVideo = true + } + return r + case CONFIGFIELDTYPE_LIST: + switch c.Type { + case CUSTOMFIELDTYPE_MULTIOPTION: + return &ConfigType{ + ListType: &ConfigListType{ + TypeId: "option", + Type: ConfigListSubType{ + ConfigType: &ConfigType{ + OptionType: &ConfigOptionType{ + Option: c.GetConfigOptions(), + }, + }, + }, + }, + } + case CUSTOMFIELDTYPE_TEXTLIST: + return &ConfigType{ + ListType: &ConfigListType{ + TypeId: "string", + Type: ConfigListSubType{ + ConfigType: &ConfigType{ + StringType: &ConfigStringType{ + Stereotype: "SIMPLE", + }, + }, + }, + }, + } + case CUSTOMFIELDTYPE_SIMPLEPHOTOGALLERY, CUSTOMFIELDTYPE_GALLERY: + r := &ConfigType{ + ListType: &ConfigListType{ + TypeId: "image", + Type: ConfigListSubType{ + ConfigType: &ConfigType{ + ImageType: &ConfigImageType{ + UnconstrainedAspectRatioAllowed: true, + }, + }, + }, + }, + } + + if c.Type == CUSTOMFIELDTYPE_SIMPLEPHOTOGALLERY { + r.ListType.Type.ImageType.IsSimpleImage = true + } + + return r + case CUSTOMFIELDTYPE_SIMPLYVIDEOGALLERY, CUSTOMFIELDTYPE_VIDEOGALLERY: + r := &ConfigType{ + ListType: &ConfigListType{ + TypeId: "video", + Type: ConfigListSubType{ + ConfigType: &ConfigType{ + VideoType: &ConfigVideoType{}, + }, + }, + }, + } + + if c.Type == CUSTOMFIELDTYPE_SIMPLYVIDEOGALLERY { + r.ListType.Type.VideoType.IsSimpleVideo = true + } + + return r + case CUSTOMFIELDTYPE_ENTITYLIST: + return &ConfigType{ + ListType: &ConfigListType{ + TypeId: "entityReference", + Type: ConfigListSubType{ + EntityReferenceType: &ConfigEntityReferenceType{}, + }, + }, + } + } + case CONFIGFIELDTYPE_OPTION: + return &ConfigType{ + OptionType: &ConfigOptionType{ + Option: c.GetConfigOptions(), + }, + } + case CONFIGFIELDTYPE_RICHTEXT: + return &ConfigType{ + RichTextType: &ConfigRichTextType{}, + } + case CONFIGFIELDTYPE_RICHTEXTV2: + return &ConfigType{ + RichTextV2Type: &ConfigRichTextV2Type{}, + } + case CONFIGFIELDTYPE_MARKDOWN: + return &ConfigType{ + MarkdownType: &ConfigMarkdownType{}, + } + case CONFIGFIELDTYPE_STRING: + r := &ConfigType{ + StringType: &ConfigStringType{}, + } + + switch c.Type { + case CUSTOMFIELDTYPE_SINGLELINETEXT: + r.StringType.Stereotype = "SIMPLE" + case CUSTOMFIELDTYPE_MULTILINETEXT: + r.StringType.Stereotype = "MULTILINE" + case CUSTOMFIELDTYPE_URL: + r.StringType.Stereotype = "URL" + case CUSTOMFIELDTYPE_SLUG: + r.StringType.Stereotype = "SLUG" + } + + return r + } + + return nil +} + +func (c *CustomField) GetConfigOptions() []ConfigOption { + var ( + r []ConfigOption + ) + + configFieldTypeId := c.GetConfigFieldTypeId() + if !(configFieldTypeId == CONFIGFIELDTYPE_OPTION || configFieldTypeId == CONFIGFIELDTYPE_LIST) { + return nil + } + + for _, entry := range c.Options { + var translations []ConfigTranslation + for _, translation := range entry.Translations { + translations = append(translations, ConfigTranslation{ + LocaleCode: translation.LanguageCode, + Value: translation.Value, + }) + } + r = append(r, ConfigOption{ + TextValue: entry.Key, + DisplayName: entry.Value, + DisplayNameTranslation: translations, + }) + } + + return r +} + func (c CustomField) GetId() string { if c.Id == nil { return "" diff --git a/customfield_service.go b/customfield_service.go index a0669fe..3166f9e 100644 --- a/customfield_service.go +++ b/customfield_service.go @@ -24,6 +24,10 @@ type CustomFieldResponse struct { } func (s *CustomFieldService) ListAll() ([]*CustomField, error) { + if s.client.UseConfigAPIForCFs { + return s.client.ConfigFieldService.ListAll() + } + var customFields []*CustomField var lr listRetriever = func(opts *ListOptions) (int, int, error) { cfr, _, err := s.List(opts) @@ -56,6 +60,10 @@ func (s *CustomFieldService) List(opts *ListOptions) (*CustomFieldResponse, *Res } func (s *CustomFieldService) Create(cf *CustomField) (*Response, error) { + if s.client.UseConfigAPIForCFs { + return s.client.ConfigFieldService.Create(cf) + } + asJSON, err := json.Marshal(cf) if err != nil { return nil, err @@ -69,6 +77,9 @@ func (s *CustomFieldService) Create(cf *CustomField) (*Response, error) { } func (s *CustomFieldService) Edit(cf *CustomField) (*Response, error) { + if s.client.UseConfigAPIForCFs { + return s.client.ConfigFieldService.Edit(cf) + } asJSON, err := json.Marshal(cf) if err != nil { return nil, err @@ -84,6 +95,9 @@ func (s *CustomFieldService) Edit(cf *CustomField) (*Response, error) { } func (s *CustomFieldService) Delete(cf *CustomField) (*Response, error) { + if s.client.UseConfigAPIForCFs { + return nil, fmt.Errorf("deleting custom fields is not supported in the Config API") + } asJSON, err := json.Marshal(cf) if err != nil { return nil, err @@ -282,11 +296,13 @@ func (c *CustomFieldManager) GetMultiOptionNames(fieldName string, optionIds *Un // MustGetMultiOptionNames returns a slice containing the names of the options that are set // Example Use: // Custom field name: "Available Breeds" -// Custom field option ID to name map: { -// "2421": "Labrador Retriever", -// "2422": "Chihuahua", -// "2423": "Boston Terrier" -// } +// +// Custom field option ID to name map: { +// "2421": "Labrador Retriever", +// "2422": "Chihuahua", +// "2423": "Boston Terrier" +// } +// // Custom field options that are set: "2421" and "2422" // cfmanager.MustGetMultiOptionNames("Available Breeds", loc.AvailableBreeds) returns ["Labrador Retriever", "Chihuahua"] func (c *CustomFieldManager) MustGetMultiOptionNames(fieldName string, options *UnorderedStrings) []string { @@ -317,12 +333,13 @@ func (c *CustomFieldManager) GetAllOptionNames(fieldName string) ([]string, erro // It returns an empty slice if the field has no options // Example Use: // Custom field name: "Dog Height" -// Custom field option ID to name map: { -// "1423": "Tall", -// "1424": "Short", -// } -// cfmanager.MustGetAllOptionNames("Dog Height") returns []string{"Tall', "Short"} // +// Custom field option ID to name map: { +// "1423": "Tall", +// "1424": "Short", +// } +// +// cfmanager.MustGetAllOptionNames("Dog Height") returns []string{"Tall', "Short"} func (c *CustomFieldManager) MustGetAllOptionNames(fieldName string) []string { if names, err := c.GetAllOptionNames(fieldName); err != nil { panic(err) @@ -347,13 +364,14 @@ func (c *CustomFieldManager) GetSingleOptionName(fieldName string, optionId **st // option is unset (i.e. if its value is a NullString) // Example Use: // Custom field name: "Dog Height" -// Custom field option ID to name map: { -// "1423": "Tall", -// "1424": "Short", -// } +// +// Custom field option ID to name map: { +// "1423": "Tall", +// "1424": "Short", +// } +// // Custom field option that is set: "1424" // cfmanager.MustGetSingleOptionName("Dog Height", loc.DogHeight) returns "Short" -// func (c *CustomFieldManager) MustGetSingleOptionName(fieldName string, optionId **string) string { if id, err := c.GetSingleOptionName(fieldName, optionId); err != nil { panic(err) diff --git a/location_customfield_service.go b/location_customfield_service.go index 3c15a60..b2705f5 100644 --- a/location_customfield_service.go +++ b/location_customfield_service.go @@ -13,6 +13,10 @@ type LocationCustomFieldService struct { } func (s *LocationCustomFieldService) ListAll() ([]*CustomField, error) { + if s.client.UseConfigAPIForCFs { + return s.client.ConfigFieldService.ListAll() + } + var customFields []*CustomField var lr listRetriever = func(opts *ListOptions) (int, int, error) { cfr, _, err := s.List(opts) @@ -45,6 +49,10 @@ func (s *LocationCustomFieldService) List(opts *ListOptions) (*CustomFieldRespon } func (s *LocationCustomFieldService) Create(cf *CustomField) (*Response, error) { + if s.client.UseConfigAPIForCFs { + return s.client.ConfigFieldService.Create(cf) + } + asJSON, err := json.Marshal(cf) if err != nil { return nil, err @@ -59,6 +67,9 @@ func (s *LocationCustomFieldService) Create(cf *CustomField) (*Response, error) } func (s *LocationCustomFieldService) Edit(cf *CustomField) (*Response, error) { + if s.client.UseConfigAPIForCFs { + return s.client.ConfigFieldService.Edit(cf) + } asJSON, err := json.Marshal(cf) if err != nil { return nil, err @@ -74,6 +85,9 @@ func (s *LocationCustomFieldService) Edit(cf *CustomField) (*Response, error) { } func (s *LocationCustomFieldService) Delete(customFieldId string) (*Response, error) { + if s.client.UseConfigAPIForCFs { + return nil, errors.New("cannot delete custom fields via the config API") + } return s.client.DoRequest("DELETE", fmt.Sprintf("%s/%s", customFieldPath, customFieldId), nil) } From 1d0529ba7aa6001d352db4bd0b6a927e5ca0c745 Mon Sep 17 00:00:00 2001 From: Kunal Patel Date: Sun, 26 Nov 2023 22:23:55 -0500 Subject: [PATCH 257/285] feat: add config option to use EU host domain for yextapis.com (#302) --- config.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.go b/config.go index b235ac3..1dd5ff5 100644 --- a/config.go +++ b/config.go @@ -11,6 +11,7 @@ import ( const ( SandboxHost string = "api-sandbox.yext.com" ProductionHost string = "api.yext.com" + EUHost string = "api.eu.yextapis.com" AccountId string = "me" Version string = "20180226" ) @@ -60,6 +61,10 @@ func (c *Config) WithSandboxHost() *Config { return c.WithHost(SandboxHost) } +func (c *Config) WithEUHost() *Config { + return c.WithHost(EUHost) +} + func (c *Config) WithProductionHost() *Config { return c.WithHost(ProductionHost) } From ae2f67f593ea0d6dc759f63dad0cfca64220e1e1 Mon Sep 17 00:00:00 2001 From: Jun Park Date: Tue, 19 Dec 2023 10:53:08 -0500 Subject: [PATCH 258/285] fix: update AnalyticsFilters FolderId name and type J=PC-220346 --- analytics_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics_service.go b/analytics_service.go index ab581db..b432fc9 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -12,7 +12,7 @@ type AnalyticsFilters struct { LocationIds *[]string `json:"locationIds"` EntityIds *[]string `json:"entityIds"` EntityTypes *[]string `json:"entityType"` - FolderId *int `json:"folderId"` + FolderIds *[]int `json:"folderIds"` Countries *[]string `json:"countries"` LocationLabels *[]string `json:"locationLabels"` Platforms *[]string `json:"platforms"` From ffd40cc98fe482b8cf62dd67d375e4ad9764bca4 Mon Sep 17 00:00:00 2001 From: Jun Park Date: Fri, 5 Jan 2024 12:16:07 -0500 Subject: [PATCH 259/285] fix: update click metric to new click count metric --- analytics_data.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analytics_data.go b/analytics_data.go index a1cab7f..d1ce1e6 100644 --- a/analytics_data.go +++ b/analytics_data.go @@ -62,7 +62,7 @@ type AnalyticsData struct { PartnerSite *string `json:"site"` CumulativeRating *float64 `json:"Rolling Average Rating"` Competitor *string `json:"competitor"` - Clicks *int `json:"CLICKS"` + Clicks *int `json:"CLICK_COUNT"` AnswersSearchesWithClicks *int `json:"ANSWERS_SEARCHES_WITH_CLICKS"` AnswersSearches *int `json:"ANSWERS_SEARCHES"` Week *string `json:"week"` From 39bb0baec27ce7e01dd7521621abc0f0b371d263 Mon Sep 17 00:00:00 2001 From: Stephanie Harvey <31486703+stephh2@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:16:51 -0500 Subject: [PATCH 260/285] feat(hotel & restaurant): add additional fields to base struct (#304) J=none TEST=manual Co-authored-by: sseverance --- entity_hotel.go | 9 +++++++++ entity_restaurant.go | 29 ++++++++++++++++------------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index ad20732..de1161e 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -209,6 +209,7 @@ type HotelEntity struct { EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` + HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` @@ -221,6 +222,14 @@ type HotelEntity struct { IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` + RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` + RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` + InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` + ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` + SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` + AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` + EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` + EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` } const ( diff --git a/entity_restaurant.go b/entity_restaurant.go index d2d4142..e177d0c 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -31,19 +31,22 @@ type RestaurantEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - DeliveryHours **Hours `json:"deliveryHours,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` - PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` + MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` + AcceptsReservations **Ternary `json:"kitchenHours,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From 257a54d51e76a475715ab2317a1c88cfa49547df Mon Sep 17 00:00:00 2001 From: Joseph Baik Date: Thu, 18 Jan 2024 11:46:54 -0500 Subject: [PATCH 261/285] add product lists to financial professional entities (#306) J=PC-234251 TEST=auto --- entity_financial_professional.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/entity_financial_professional.go b/entity_financial_professional.go index ab3b439..677f5d1 100644 --- a/entity_financial_professional.go +++ b/entity_financial_professional.go @@ -47,6 +47,9 @@ type FinancialProfessional struct { // Spelling of json tag 'specialities' is intentional to match mispelling in Yext API Specialties *[]string `json:"specialities,omitempty"` + // Lists + ProductLists **Lists `json:"productLists,omitempty"` + Languages *[]string `json:"languages,omitempty"` Logo **Photo `json:"logo,omitempty"` PaymentOptions *[]string `json:"paymentOptions,omitempty"` @@ -367,6 +370,10 @@ func (y FinancialProfessional) GetSpecialties() (v []string) { return v } +func (y FinancialProfessional) GetProductLists() (v *Lists) { + return GetLists(y.ProductLists) +} + func (y FinancialProfessional) GetServices() (v []string) { if y.Services != nil { v = *y.Services From 399f2aee117d8c8f0d30be4e3faabcd5fdd4c07c Mon Sep 17 00:00:00 2001 From: Jun Park Date: Wed, 14 Feb 2024 11:03:27 -0500 Subject: [PATCH 262/285] fix: remove deprecated metrics J=PC-220346 --- analytics_service.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/analytics_service.go b/analytics_service.go index b432fc9..eabc853 100644 --- a/analytics_service.go +++ b/analytics_service.go @@ -35,10 +35,6 @@ type AnalyticsFilters struct { MatchType *[]string `json:"matchType"` MinSearchFrequency *int `json:"minSearchFrequency"` MaxSearchFrequency *int `json:"maxSearchFrequency"` - FoursquareCheckinType *string `json:"foursquareCheckinType"` - FoursquareCheckinAge *string `json:"foursquareCheckinAge"` - FoursquareCheckinGender *string `json:"foursquareCheckinGender"` - InstagramContentType *string `json:"instagramContentType"` Age *[]string `json:"age"` Gender *string `json:"gender"` FacebookImpressionType *[]string `json:"facebookImpressionType"` From 51f8fc3c406ec5e8a8f20f1e5992510f8540d7fa Mon Sep 17 00:00:00 2001 From: Dana Gurland Date: Tue, 20 Feb 2024 14:00:31 -0500 Subject: [PATCH 263/285] fix(restaurant): add DineInHours to base struct J=PC-237814 TEST=manual --- entity_restaurant.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_restaurant.go b/entity_restaurant.go index e177d0c..7abb933 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -47,6 +47,7 @@ type RestaurantEntity struct { MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` AcceptsReservations **Ternary `json:"kitchenHours,omitempty"` KitchenHours **Hours `json:"kitchenHours,omitempty"` + DineInHours **Hours `json:"dineInHours,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` From 8d50bfcc9bae7fdf5e1900913d72334e45e3fca2 Mon Sep 17 00:00:00 2001 From: Nirmal Patel Date: Wed, 6 Mar 2024 19:47:23 -0500 Subject: [PATCH 264/285] feat(reviews): add new review api fields - https://yexttest.atlassian.net/browse/PB-21550 J=PC-233325 TEST=manual --- review.go | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/review.go b/review.go index 0b26e6d..162b4f4 100644 --- a/review.go +++ b/review.go @@ -46,9 +46,11 @@ type Review struct { Recommendation *string `json:"recommendation"` TransactionId *string `json:"transactionId"` InvitationId *string `json:"invitationId"` + ApiIdentifier *string `json:"apiIdentifier"` } type ReviewCreate struct { + EntityId *string `json:"entityId,omitempty"` LocationId *string `json:"locationId,omitempty"` ExternalId *string `json:"externalId,omitempty"` // Must have v param >= 20220120 AccountId *string `json:"accountId,omitempty"` @@ -61,6 +63,7 @@ type ReviewCreate struct { ReviewLanguage *string `json:"reviewLanguage,omitempty"` TransactionId *string `json:"transactionId,omitempty"` Date *string `json:"date,omitempty"` + PublisherId *string `json:"publisherId,omitempty"` //LiveAPI Specific Fields ReviewEntity **ReviewEntity `json:"entity,omitempty"` @@ -71,14 +74,16 @@ type ReviewCreate struct { } type Comment struct { - Id *int `json:"id"` - ParentId *int `json:"parentId"` - PublisherDate *int `json:"publisherDate"` - AuthorName *string `json:"authorName"` - AuthorEmail *string `json:"authorEmail"` - AuthorRole *string `json:"authorRole"` - Content *string `json:"content"` - Visibility *string `json:"visibility"` + Id *int `json:"id"` + ParentId *int `json:"parentId"` + PublisherDate *int `json:"publisherDate"` + AuthorName *string `json:"authorName"` + AuthorEmail *string `json:"authorEmail"` + AuthorRole *string `json:"authorRole"` + Content *string `json:"content"` + Visibility *string `json:"visibility"` + SuppressReviewerContact *bool `json:"suppressReviewerContact"` + Date *string `json:"date"` } type ReviewLabel struct { @@ -289,6 +294,20 @@ func (y Comment) GetContent() string { return "" } +func (y Comment) GetDate() string { + if y.Date != nil { + return *y.Date + } + return "" +} + +func (y Comment) GetSuppressReviewerContact() bool { + if y.SuppressReviewerContact == nil { + return false + } + return *y.SuppressReviewerContact +} + func (y Comment) GetVisibility() string { if y.Visibility != nil { return *y.Visibility From f6d684aefca66b766167be24d5ab327e3f7264f5 Mon Sep 17 00:00:00 2001 From: Jeffrey Rhoads <46790862+Jeffrey-Rhoads17@users.noreply.github.com> Date: Thu, 28 Mar 2024 09:37:05 -0700 Subject: [PATCH 265/285] feat(custom): add nonbinary gender to healthcare professional entity (#310) J=PC-240725 TEST=manual --- entity_healthcare_professional.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index db225c1..5a5938e 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -20,6 +20,7 @@ const ( GENDER_MALE = "MALE" GENDER_FEMALE = "FEMALE" GENDER_UNSPECIFIED = "UNSPECIFIED" + GENDER_NONBINARY = "NONBINARY" ) type HealthcareProfessionalEntity struct { From 36f22ff56335253f54b5446106c55f13f7094666 Mon Sep 17 00:00:00 2001 From: Lilian Wang Date: Fri, 29 Mar 2024 13:06:45 -0400 Subject: [PATCH 266/285] feat(custom): change supported format struct for richtextv2 field (#311) J=PC-240487 TEST=manual Co-authored-by: Lilian Wang --- cac.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cac.go b/cac.go index 20153cc..47e31f2 100644 --- a/cac.go +++ b/cac.go @@ -105,12 +105,14 @@ type ConfigRichTextType struct { } type ConfigRichTextV2Type struct { - MinLength int `json:"minLength,omitempty"` - MaxLength int `json:"maxLength,omitempty"` - SupportedFormatFilter *[]struct { - SupportedFormats []string `json:"supportedFormats,omitempty"` - } `json:"supportedFormatFilter,omitempty"` - NoSupportedFormat bool `json:"noSupportedFormat,omitempty"` + MinLength int `json:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty"` + SupportedFormatFilter *ConfigSupportedFormats `json:"supportedFormatFilter,omitempty"` + NoSupportedFormat bool `json:"noSupportedFormat,omitempty"` +} + +type ConfigSupportedFormats struct { + SupportedFormats []string `json:"supportedFormats,omitempty"` } type ConfigMarkdownType struct { From 70869ee86b38fa104da6baf38622c1bc16165601 Mon Sep 17 00:00:00 2001 From: Jacob Knutson Date: Wed, 8 May 2024 14:24:37 -0400 Subject: [PATCH 267/285] feat: add notifyUser field to user J=PC-244952 --- user.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/user.go b/user.go index 162cb1d..73f8a42 100644 --- a/user.go +++ b/user.go @@ -16,6 +16,7 @@ type User struct { ACLs []ACL `json:"acl,omitempty"` LastLoginDate *string `json:"lastLoginDate,omitempty"` CreatedDate *string `json:"createdDate,omitempty"` + NotifyUser *bool `json:"notifyUser,omitempty"` } func (u *User) GetId() string { @@ -87,6 +88,13 @@ func (u *User) GetCreatedDate() string { return *u.CreatedDate } +func (u *User) GetNotifyUser() bool { + if u.NotifyUser == nil { + return false + } + return *u.NotifyUser +} + func (u *User) String() string { b, _ := json.Marshal(u) return string(b) From 7c11c04b063778f790bbf7a3533cb2f697262595 Mon Sep 17 00:00:00 2001 From: Daniel Kang <97117406+dkang3021@users.noreply.github.com> Date: Thu, 9 May 2024 14:16:37 -0400 Subject: [PATCH 268/285] feat(entity_service): add convertRichTextToHTML param (#313) J=PC-244921 TEST=manual --- entity_service.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/entity_service.go b/entity_service.go index 1efbdc6..7a18c76 100644 --- a/entity_service.go +++ b/entity_service.go @@ -44,14 +44,15 @@ type EntityService struct { type EntityListOptions struct { ListOptions - SearchIDs []string - ResolvePlaceholders bool - Rendered bool // will return resolved placeholders for language profiles - EntityTypes []string - Fields []string - Filter string - Format RichTextFormat - HideProgressBar bool + SearchIDs []string + ResolvePlaceholders bool + Rendered bool // will return resolved placeholders for language profiles + EntityTypes []string + Fields []string + Filter string + Format RichTextFormat + HideProgressBar bool + ConvertRichTextToHTML bool } // Used for Create and Edit @@ -238,6 +239,9 @@ func addEntityListOptions(requrl string, opts *EntityListOptions) (string, error if opts.Format != RichTextFormatDefault { q.Add("format", opts.Format.ToString()) } + if opts.ConvertRichTextToHTML { + q.Add("convertRichTextToHTML", "true") + } u.RawQuery = q.Encode() From 919a4bf47eb2f0f04a06d5ebc649cb093ae72da4 Mon Sep 17 00:00:00 2001 From: peteros086 <67395997+peteros086@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:28:24 -0400 Subject: [PATCH 269/285] feat(hotel+restaurant): add addl fields (#314) J=none|TEST=manual --- entity_hotel.go | 126 +++++++++++++++++++++++++++++-------------- entity_restaurant.go | 51 +++++++++++------- 2 files changed, 118 insertions(+), 59 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index de1161e..5e31297 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -32,15 +32,23 @@ type HotelEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field - CheckOutTime *string `json:"checkOutTime,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field + CheckOutTime *string `json:"checkOutTime,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + AvailableRoomTypes *UnorderedStrings `json:"availableRoomTypes,omitempty"` + HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` + AccessHours **Hours `json:"accessHours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + HappyHours **Hours `json:"happyHours,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` + KidActivities **Hours `json:"kidActivities,omitempty"` + FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` // Social Media FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` @@ -193,43 +201,81 @@ type HotelEntity struct { WiFiDetails *UnorderedStrings `json:"wifiDetails,omitempty"` // Business - MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` - BusinessCenter **Ternary `json:"businessCenter,omitempty"` + MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` + BusinessCenter **Ternary `json:"businessCenter,omitempty"` + BlackOwnedBusiness **Ternary `json:"blackOwnedBusiness,omitempty"` // Accessibility MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` // Covid & Cleanliness Fields - DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` - CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` - GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` - CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` - EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` - EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` - EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` - HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` - HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` - PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` - ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` - PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` - PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` - LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` - PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` - CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` - SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` - MasksRequired **Ternary `json:"masksRequired,omitempty"` - IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` - DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` - SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` - RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` - RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` - InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` - ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` - SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` - AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` - EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` - EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` + DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` + CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` + GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` + CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` + EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` + EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` + EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` + HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` + HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` + PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` + ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` + PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` + PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` + LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` + PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` + CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` + SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` + MasksRequired **Ternary `json:"masksRequired,omitempty"` + IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` + DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` + SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` + RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` + RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` + InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` + ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` + SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` + AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` + EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` + EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` + CarbonFreeEnergySources **Ternary `json:"carbonFreeEnergySources,omitempty"` + CompostableFoodContainersAndCutlery **Ternary `json:"compostableFoodContainersAndCutlery,omitempty"` + CompostsExcessFood **Ternary `json:"compostsExcessFood,omitempty"` + DonatesExcessFood **Ternary `json:"donatesExcessFood,omitempty"` + EnergyConservationProgram **Ternary `json:"energyConservationProgram,omitempty"` + EnergyEfficientHeatingAndCoolingSystems **Ternary `json:"energyEfficientHeatingAndCoolingSystems,omitempty"` + EnergyEfficientLighting **Ternary `json:"energyEfficientLighting,omitempty"` + EnergySavingThermostats **Ternary `json:"energySavingThermostats,omitempty"` + FoodWasteReductionProgram **Ternary `json:"foodWasteReductionProgram,omitempty"` + IndependentOrganizationAuditsEnergyUse **Ternary `json:"independentOrganizationAuditsEnergyUse,omitempty"` + IndependentOrganizationAuditsWaterUse **Ternary `json:"independentOrganizationAuditsWaterUse,omitempty"` + LinenReuseProgram **Ternary `json:"linenReuseProgram,omitempty"` + LocallySourcedFoodAndBeverages **Ternary `json:"locallySourcedFoodAndBeverages,omitempty"` + OrganicCageFreeEggs **Ternary `json:"organicCageFreeEggs,omitempty"` + OrganicFoodAndBeverages **Ternary `json:"organicFoodAndBeverages,omitempty"` + RecyclingProgram **Ternary `json:"recyclingProgram,omitempty"` + RefillableToiletryContainers **Ternary `json:"refillableToiletryContainers,omitempty"` + ResponsiblePurchasingPolicy **Ternary `json:"responsiblePurchasingPolicy,omitempty"` + ResponsiblySourcesSeafood **Ternary `json:"responsiblySourcesSeafood,omitempty"` + SafelyDisposesBatteries **Ternary `json:"safelyDisposesBatteries,omitempty"` + SafelyDisposesElectronics **Ternary `json:"safelyDisposesElectronics,omitempty"` + SafelyDisposesLightbulbs **Ternary `json:"safelyDisposesLightbulbs,omitempty"` + SafelyHandlesHazardousSubstances **Ternary `json:"safelyHandlesHazardousSubstances,omitempty"` + SingleUsePlasticStrawsBanned **Ternary `json:"singleUsePlasticStrawsBanned,omitempty"` + SingleUsePlasticWaterBottlesBanned **Ternary `json:"singleUsePlasticWaterBottlesBanned,omitempty"` + SoapDonationProgram **Ternary `json:"soapDonationProgram,omitempty"` + StyrofoamFoodContainersBanned **Ternary `json:"styrofoamFoodContainersBanned,omitempty"` + ToiletryDonationProgram **Ternary `json:"toiletryDonationProgram,omitempty"` + TowelReuseProgram **Ternary `json:"towelReuseProgram,omitempty"` + VeganMealsAvailable **Ternary `json:"veganMealsAvailable,omitempty"` + VegetarianMealsAvailable **Ternary `json:"vegetarianMealsAvailable,omitempty"` + WaterBottleFillingStations **Ternary `json:"waterBottleFillingStations,omitempty"` + WaterSavingShowers **Ternary `json:"waterSavingShowers,omitempty"` + WaterSavingSinks **Ternary `json:"waterSavingSinks,omitempty"` + WaterSavingToilets **Ternary `json:"waterSavingToilets,omitempty"` + BreeamRating *string `json:"breeamRating,omitempty"` + LeedRating *string `json:"leedRating,omitempty"` } const ( diff --git a/entity_restaurant.go b/entity_restaurant.go index 7abb933..a009990 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -31,23 +31,31 @@ type RestaurantEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - DeliveryHours **Hours `json:"deliveryHours,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` - PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` - MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` - AcceptsReservations **Ternary `json:"kitchenHours,omitempty"` - KitchenHours **Hours `json:"kitchenHours,omitempty"` - DineInHours **Hours `json:"dineInHours,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` + MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` + AcceptsReservations **Ternary `json:"acceptsReservations,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` + DineInHours **Hours `json:"dineInHours,omitempty"` + AccessHours **Hours `json:"accessHours,omitempty"` + HappyHours **Hours `json:"happyHours,omitempty"` + OnlineServiceHours **Hours `json:"onlineServiceHours,omitempty"` + PickupHours **Hours `json:"pickupHours,omitempty"` + SeniorHours **Hours `json:"seniorHours,omitempty"` + TakeoutHours **Hours `json:"takeoutHours,omitempty"` + HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` + FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -68,6 +76,7 @@ type RestaurantEntity struct { ReservationUrl **Website `json:"reservationUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + DeliveryUrl **Website `json:"deliveryUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -85,8 +94,12 @@ type RestaurantEntity struct { GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` - InstagramHandle *string `json:"instagramHandle,omitempty"` - TwitterHandle *string `json:"twitterHandle,omitempty"` + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + LinkedInUrl *string `json:"linkedInUrl,omitempty"` + PinterestUrl *string `json:"pinterestUrl,omitempty"` + TikTokUrl *string `json:"tikTokUrl,omitempty"` + YouTubeChannelUrl *string `json:"youTubeChannelUrl,omitempty"` PhotoGallery *[]Photo `json:"photoGallery,omitempty"` Videos *[]Video `json:"videos,omitempty"` From d47ec010df4fa4a6bfa9f5aad50319ff593db1c9 Mon Sep 17 00:00:00 2001 From: Jeffrey Rhoads <46790862+Jeffrey-Rhoads17@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:28:36 -0400 Subject: [PATCH 270/285] fix(custom): allow for multiple data types of IDs from Review Update endpoint to account for newer A (#315) J=PC-254056 TEST=manual --- review_service.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/review_service.go b/review_service.go index ebdba1c..7e936fd 100644 --- a/review_service.go +++ b/review_service.go @@ -14,7 +14,7 @@ const reviewsPath = "reviews" const reviewInvitePath = "reviewinvites" -//Review update enums +// Review update enums const ( ReviewStatusLive = "LIVE" ReviewStatusQuarantined = "QUARANTINED" @@ -76,7 +76,30 @@ type ReviewUpdateOptions struct { } type ReviewUpdateResponse struct { - Id string `json:"id"` + Id IdWrapper `json:"id"` +} + +// Wrapper types are helpful because the Yext API versions have different data types that the ID is returned as. +// These wrappers allow unmarshalling and using as a string vs using an interface and type casting every time +type IdWrapper string + +func (w *IdWrapper) UnmarshalJSON(data []byte) (err error) { + if id, err := strconv.Atoi(string(data)); err == nil { + str := strconv.Itoa(id) + *w = IdWrapper(str) + return nil + } + var str string + err = json.Unmarshal(data, &str) + if err != nil { + return err + } + *w = IdWrapper(str) + return nil +} + +func (n IdWrapper) String() string { + return string(n) } type ReviewCreateInvitationResponse struct { @@ -262,8 +285,8 @@ func (l *ReviewService) CreateReview(jsonData *ReviewCreate) (*ReviewCreateRevie return v, r, nil } -//the new way to create reviews on the yext platform -//refer to https://yextops.slack.com/archives/C01269F1ZTL/p1634751884059700 +// the new way to create reviews on the yext platform +// refer to https://yextops.slack.com/archives/C01269F1ZTL/p1634751884059700 func (l *ReviewService) CreateReviewLiveAPI(jsonData *ReviewCreate) (*ReviewCreateReviewResponse, *Response, error) { reviewCreateReviewResponse := &ReviewCreateReviewResponse{} baseURL := "https://liveapi.yext.com" From 2e0a9278c9e8d6a354566770512f92e4822a0f6e Mon Sep 17 00:00:00 2001 From: Jeffrey Rhoads <46790862+Jeffrey-Rhoads17@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:15:21 -0400 Subject: [PATCH 271/285] Revert "feat(hotel+restaurant): add addl fields (#314)" (#316) This reverts commit 919a4bf47eb2f0f04a06d5ebc649cb093ae72da4. --- entity_hotel.go | 126 ++++++++++++++----------------------------- entity_restaurant.go | 51 +++++++----------- 2 files changed, 59 insertions(+), 118 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index 5e31297..de1161e 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -32,23 +32,15 @@ type HotelEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field - CheckOutTime *string `json:"checkOutTime,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - AvailableRoomTypes *UnorderedStrings `json:"availableRoomTypes,omitempty"` - HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` - AccessHours **Hours `json:"accessHours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - HappyHours **Hours `json:"happyHours,omitempty"` - KitchenHours **Hours `json:"kitchenHours,omitempty"` - KidActivities **Hours `json:"kidActivities,omitempty"` - FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field + CheckOutTime *string `json:"checkOutTime,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` // Social Media FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` @@ -201,81 +193,43 @@ type HotelEntity struct { WiFiDetails *UnorderedStrings `json:"wifiDetails,omitempty"` // Business - MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` - BusinessCenter **Ternary `json:"businessCenter,omitempty"` - BlackOwnedBusiness **Ternary `json:"blackOwnedBusiness,omitempty"` + MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` + BusinessCenter **Ternary `json:"businessCenter,omitempty"` // Accessibility MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` // Covid & Cleanliness Fields - DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` - CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` - GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` - CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` - EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` - EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` - EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` - HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` - HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` - PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` - ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` - PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` - PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` - LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` - PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` - CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` - SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` - MasksRequired **Ternary `json:"masksRequired,omitempty"` - IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` - DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` - SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` - RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` - RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` - InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` - ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` - SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` - AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` - EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` - EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` - CarbonFreeEnergySources **Ternary `json:"carbonFreeEnergySources,omitempty"` - CompostableFoodContainersAndCutlery **Ternary `json:"compostableFoodContainersAndCutlery,omitempty"` - CompostsExcessFood **Ternary `json:"compostsExcessFood,omitempty"` - DonatesExcessFood **Ternary `json:"donatesExcessFood,omitempty"` - EnergyConservationProgram **Ternary `json:"energyConservationProgram,omitempty"` - EnergyEfficientHeatingAndCoolingSystems **Ternary `json:"energyEfficientHeatingAndCoolingSystems,omitempty"` - EnergyEfficientLighting **Ternary `json:"energyEfficientLighting,omitempty"` - EnergySavingThermostats **Ternary `json:"energySavingThermostats,omitempty"` - FoodWasteReductionProgram **Ternary `json:"foodWasteReductionProgram,omitempty"` - IndependentOrganizationAuditsEnergyUse **Ternary `json:"independentOrganizationAuditsEnergyUse,omitempty"` - IndependentOrganizationAuditsWaterUse **Ternary `json:"independentOrganizationAuditsWaterUse,omitempty"` - LinenReuseProgram **Ternary `json:"linenReuseProgram,omitempty"` - LocallySourcedFoodAndBeverages **Ternary `json:"locallySourcedFoodAndBeverages,omitempty"` - OrganicCageFreeEggs **Ternary `json:"organicCageFreeEggs,omitempty"` - OrganicFoodAndBeverages **Ternary `json:"organicFoodAndBeverages,omitempty"` - RecyclingProgram **Ternary `json:"recyclingProgram,omitempty"` - RefillableToiletryContainers **Ternary `json:"refillableToiletryContainers,omitempty"` - ResponsiblePurchasingPolicy **Ternary `json:"responsiblePurchasingPolicy,omitempty"` - ResponsiblySourcesSeafood **Ternary `json:"responsiblySourcesSeafood,omitempty"` - SafelyDisposesBatteries **Ternary `json:"safelyDisposesBatteries,omitempty"` - SafelyDisposesElectronics **Ternary `json:"safelyDisposesElectronics,omitempty"` - SafelyDisposesLightbulbs **Ternary `json:"safelyDisposesLightbulbs,omitempty"` - SafelyHandlesHazardousSubstances **Ternary `json:"safelyHandlesHazardousSubstances,omitempty"` - SingleUsePlasticStrawsBanned **Ternary `json:"singleUsePlasticStrawsBanned,omitempty"` - SingleUsePlasticWaterBottlesBanned **Ternary `json:"singleUsePlasticWaterBottlesBanned,omitempty"` - SoapDonationProgram **Ternary `json:"soapDonationProgram,omitempty"` - StyrofoamFoodContainersBanned **Ternary `json:"styrofoamFoodContainersBanned,omitempty"` - ToiletryDonationProgram **Ternary `json:"toiletryDonationProgram,omitempty"` - TowelReuseProgram **Ternary `json:"towelReuseProgram,omitempty"` - VeganMealsAvailable **Ternary `json:"veganMealsAvailable,omitempty"` - VegetarianMealsAvailable **Ternary `json:"vegetarianMealsAvailable,omitempty"` - WaterBottleFillingStations **Ternary `json:"waterBottleFillingStations,omitempty"` - WaterSavingShowers **Ternary `json:"waterSavingShowers,omitempty"` - WaterSavingSinks **Ternary `json:"waterSavingSinks,omitempty"` - WaterSavingToilets **Ternary `json:"waterSavingToilets,omitempty"` - BreeamRating *string `json:"breeamRating,omitempty"` - LeedRating *string `json:"leedRating,omitempty"` + DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` + CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` + GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` + CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` + EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` + EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` + EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` + HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` + HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` + PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` + ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` + PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` + PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` + LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` + PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` + CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` + SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` + MasksRequired **Ternary `json:"masksRequired,omitempty"` + IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` + DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` + SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` + RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` + RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` + InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` + ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` + SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` + AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` + EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` + EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` } const ( diff --git a/entity_restaurant.go b/entity_restaurant.go index a009990..7abb933 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -31,31 +31,23 @@ type RestaurantEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - DeliveryHours **Hours `json:"deliveryHours,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` - PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` - MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` - AcceptsReservations **Ternary `json:"acceptsReservations,omitempty"` - KitchenHours **Hours `json:"kitchenHours,omitempty"` - DineInHours **Hours `json:"dineInHours,omitempty"` - AccessHours **Hours `json:"accessHours,omitempty"` - HappyHours **Hours `json:"happyHours,omitempty"` - OnlineServiceHours **Hours `json:"onlineServiceHours,omitempty"` - PickupHours **Hours `json:"pickupHours,omitempty"` - SeniorHours **Hours `json:"seniorHours,omitempty"` - TakeoutHours **Hours `json:"takeoutHours,omitempty"` - HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` - FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` + MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` + AcceptsReservations **Ternary `json:"kitchenHours,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` + DineInHours **Hours `json:"dineInHours,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -76,7 +68,6 @@ type RestaurantEntity struct { ReservationUrl **Website `json:"reservationUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` - DeliveryUrl **Website `json:"deliveryUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -94,12 +85,8 @@ type RestaurantEntity struct { GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` - InstagramHandle *string `json:"instagramHandle,omitempty"` - TwitterHandle *string `json:"twitterHandle,omitempty"` - LinkedInUrl *string `json:"linkedInUrl,omitempty"` - PinterestUrl *string `json:"pinterestUrl,omitempty"` - TikTokUrl *string `json:"tikTokUrl,omitempty"` - YouTubeChannelUrl *string `json:"youTubeChannelUrl,omitempty"` + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` PhotoGallery *[]Photo `json:"photoGallery,omitempty"` Videos *[]Video `json:"videos,omitempty"` From d453c8db678179a900ddff08cab9c5ea3b4764c3 Mon Sep 17 00:00:00 2001 From: Jeffrey Rhoads <46790862+Jeffrey-Rhoads17@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:13:04 -0400 Subject: [PATCH 272/285] feat(hotel+restaurant): add addl fields (#317) J=none|TEST=manual Co-authored-by: Peter Oliveira Soens --- entity_hotel.go | 126 +++++++++++++++++++++++++++++-------------- entity_restaurant.go | 51 +++++++++++------- 2 files changed, 118 insertions(+), 59 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index de1161e..5e31297 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -32,15 +32,23 @@ type HotelEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field - CheckOutTime *string `json:"checkOutTime,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field + CheckOutTime *string `json:"checkOutTime,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + AvailableRoomTypes *UnorderedStrings `json:"availableRoomTypes,omitempty"` + HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` + AccessHours **Hours `json:"accessHours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + HappyHours **Hours `json:"happyHours,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` + KidActivities **Hours `json:"kidActivities,omitempty"` + FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` // Social Media FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` @@ -193,43 +201,81 @@ type HotelEntity struct { WiFiDetails *UnorderedStrings `json:"wifiDetails,omitempty"` // Business - MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` - BusinessCenter **Ternary `json:"businessCenter,omitempty"` + MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` + BusinessCenter **Ternary `json:"businessCenter,omitempty"` + BlackOwnedBusiness **Ternary `json:"blackOwnedBusiness,omitempty"` // Accessibility MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` // Covid & Cleanliness Fields - DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` - CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` - GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` - CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` - EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` - EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` - EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` - HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` - HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` - PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` - ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` - PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` - PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` - LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` - PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` - CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` - SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` - MasksRequired **Ternary `json:"masksRequired,omitempty"` - IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` - DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` - SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` - RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` - RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` - InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` - ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` - SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` - AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` - EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` - EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` + DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` + CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` + GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` + CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` + EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` + EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` + EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` + HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` + HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` + PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` + ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` + PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` + PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` + LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` + PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` + CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` + SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` + MasksRequired **Ternary `json:"masksRequired,omitempty"` + IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` + DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` + SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` + RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` + RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` + InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` + ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` + SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` + AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` + EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` + EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` + CarbonFreeEnergySources **Ternary `json:"carbonFreeEnergySources,omitempty"` + CompostableFoodContainersAndCutlery **Ternary `json:"compostableFoodContainersAndCutlery,omitempty"` + CompostsExcessFood **Ternary `json:"compostsExcessFood,omitempty"` + DonatesExcessFood **Ternary `json:"donatesExcessFood,omitempty"` + EnergyConservationProgram **Ternary `json:"energyConservationProgram,omitempty"` + EnergyEfficientHeatingAndCoolingSystems **Ternary `json:"energyEfficientHeatingAndCoolingSystems,omitempty"` + EnergyEfficientLighting **Ternary `json:"energyEfficientLighting,omitempty"` + EnergySavingThermostats **Ternary `json:"energySavingThermostats,omitempty"` + FoodWasteReductionProgram **Ternary `json:"foodWasteReductionProgram,omitempty"` + IndependentOrganizationAuditsEnergyUse **Ternary `json:"independentOrganizationAuditsEnergyUse,omitempty"` + IndependentOrganizationAuditsWaterUse **Ternary `json:"independentOrganizationAuditsWaterUse,omitempty"` + LinenReuseProgram **Ternary `json:"linenReuseProgram,omitempty"` + LocallySourcedFoodAndBeverages **Ternary `json:"locallySourcedFoodAndBeverages,omitempty"` + OrganicCageFreeEggs **Ternary `json:"organicCageFreeEggs,omitempty"` + OrganicFoodAndBeverages **Ternary `json:"organicFoodAndBeverages,omitempty"` + RecyclingProgram **Ternary `json:"recyclingProgram,omitempty"` + RefillableToiletryContainers **Ternary `json:"refillableToiletryContainers,omitempty"` + ResponsiblePurchasingPolicy **Ternary `json:"responsiblePurchasingPolicy,omitempty"` + ResponsiblySourcesSeafood **Ternary `json:"responsiblySourcesSeafood,omitempty"` + SafelyDisposesBatteries **Ternary `json:"safelyDisposesBatteries,omitempty"` + SafelyDisposesElectronics **Ternary `json:"safelyDisposesElectronics,omitempty"` + SafelyDisposesLightbulbs **Ternary `json:"safelyDisposesLightbulbs,omitempty"` + SafelyHandlesHazardousSubstances **Ternary `json:"safelyHandlesHazardousSubstances,omitempty"` + SingleUsePlasticStrawsBanned **Ternary `json:"singleUsePlasticStrawsBanned,omitempty"` + SingleUsePlasticWaterBottlesBanned **Ternary `json:"singleUsePlasticWaterBottlesBanned,omitempty"` + SoapDonationProgram **Ternary `json:"soapDonationProgram,omitempty"` + StyrofoamFoodContainersBanned **Ternary `json:"styrofoamFoodContainersBanned,omitempty"` + ToiletryDonationProgram **Ternary `json:"toiletryDonationProgram,omitempty"` + TowelReuseProgram **Ternary `json:"towelReuseProgram,omitempty"` + VeganMealsAvailable **Ternary `json:"veganMealsAvailable,omitempty"` + VegetarianMealsAvailable **Ternary `json:"vegetarianMealsAvailable,omitempty"` + WaterBottleFillingStations **Ternary `json:"waterBottleFillingStations,omitempty"` + WaterSavingShowers **Ternary `json:"waterSavingShowers,omitempty"` + WaterSavingSinks **Ternary `json:"waterSavingSinks,omitempty"` + WaterSavingToilets **Ternary `json:"waterSavingToilets,omitempty"` + BreeamRating *string `json:"breeamRating,omitempty"` + LeedRating *string `json:"leedRating,omitempty"` } const ( diff --git a/entity_restaurant.go b/entity_restaurant.go index 7abb933..a009990 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -31,23 +31,31 @@ type RestaurantEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - DeliveryHours **Hours `json:"deliveryHours,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` - PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` - MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` - AcceptsReservations **Ternary `json:"kitchenHours,omitempty"` - KitchenHours **Hours `json:"kitchenHours,omitempty"` - DineInHours **Hours `json:"dineInHours,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` + MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` + AcceptsReservations **Ternary `json:"acceptsReservations,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` + DineInHours **Hours `json:"dineInHours,omitempty"` + AccessHours **Hours `json:"accessHours,omitempty"` + HappyHours **Hours `json:"happyHours,omitempty"` + OnlineServiceHours **Hours `json:"onlineServiceHours,omitempty"` + PickupHours **Hours `json:"pickupHours,omitempty"` + SeniorHours **Hours `json:"seniorHours,omitempty"` + TakeoutHours **Hours `json:"takeoutHours,omitempty"` + HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` + FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -68,6 +76,7 @@ type RestaurantEntity struct { ReservationUrl **Website `json:"reservationUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` + DeliveryUrl **Website `json:"deliveryUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -85,8 +94,12 @@ type RestaurantEntity struct { GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` - InstagramHandle *string `json:"instagramHandle,omitempty"` - TwitterHandle *string `json:"twitterHandle,omitempty"` + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` + LinkedInUrl *string `json:"linkedInUrl,omitempty"` + PinterestUrl *string `json:"pinterestUrl,omitempty"` + TikTokUrl *string `json:"tikTokUrl,omitempty"` + YouTubeChannelUrl *string `json:"youTubeChannelUrl,omitempty"` PhotoGallery *[]Photo `json:"photoGallery,omitempty"` Videos *[]Video `json:"videos,omitempty"` From 2c02c9d32ef1e2080d444fb8be2cd9379512e77b Mon Sep 17 00:00:00 2001 From: peteros086 <67395997+peteros086@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:35:46 -0400 Subject: [PATCH 273/285] Revert "feat(hotel+restaurant): add addl fields (#317)" (#318) This reverts commit d453c8db678179a900ddff08cab9c5ea3b4764c3. --- entity_hotel.go | 126 ++++++++++++++----------------------------- entity_restaurant.go | 51 +++++++----------- 2 files changed, 59 insertions(+), 118 deletions(-) diff --git a/entity_hotel.go b/entity_hotel.go index 5e31297..de1161e 100644 --- a/entity_hotel.go +++ b/entity_hotel.go @@ -32,23 +32,15 @@ type HotelEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - Associations *[]string `json:"associations,omitempty"` - Brands *[]string `json:"brands,omitempty"` - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field - CheckOutTime *string `json:"checkOutTime,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - AvailableRoomTypes *UnorderedStrings `json:"availableRoomTypes,omitempty"` - HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` - AccessHours **Hours `json:"accessHours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - HappyHours **Hours `json:"happyHours,omitempty"` - KitchenHours **Hours `json:"kitchenHours,omitempty"` - KidActivities **Hours `json:"kidActivities,omitempty"` - FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + Associations *[]string `json:"associations,omitempty"` + Brands *[]string `json:"brands,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + CheckInTime *string `json:"checkInTime,omitempty"` // TODO: check type of time field + CheckOutTime *string `json:"checkOutTime,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` // Social Media FacebookPageUrl *string `json:"facebookPageUrl,omitempty"` @@ -201,81 +193,43 @@ type HotelEntity struct { WiFiDetails *UnorderedStrings `json:"wifiDetails,omitempty"` // Business - MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` - BusinessCenter **Ternary `json:"businessCenter,omitempty"` - BlackOwnedBusiness **Ternary `json:"blackOwnedBusiness,omitempty"` + MeetingRoomCount **int `json:"meetingRoomCount,omitempty"` + BusinessCenter **Ternary `json:"businessCenter,omitempty"` // Accessibility MobilityAccessible **Ternary `json:"mobilityAccessible,omitempty"` AccessibilityDetails *UnorderedStrings `json:"accessibilityDetails,omitempty"` // Covid & Cleanliness Fields - DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` - CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` - GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` - CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` - EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` - EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` - EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` - HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` - HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` - PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` - ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` - PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` - PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` - LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` - PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` - CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` - SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` - MasksRequired **Ternary `json:"masksRequired,omitempty"` - IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` - DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` - SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` - RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` - RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` - InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` - ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` - SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` - AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` - EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` - EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` - CarbonFreeEnergySources **Ternary `json:"carbonFreeEnergySources,omitempty"` - CompostableFoodContainersAndCutlery **Ternary `json:"compostableFoodContainersAndCutlery,omitempty"` - CompostsExcessFood **Ternary `json:"compostsExcessFood,omitempty"` - DonatesExcessFood **Ternary `json:"donatesExcessFood,omitempty"` - EnergyConservationProgram **Ternary `json:"energyConservationProgram,omitempty"` - EnergyEfficientHeatingAndCoolingSystems **Ternary `json:"energyEfficientHeatingAndCoolingSystems,omitempty"` - EnergyEfficientLighting **Ternary `json:"energyEfficientLighting,omitempty"` - EnergySavingThermostats **Ternary `json:"energySavingThermostats,omitempty"` - FoodWasteReductionProgram **Ternary `json:"foodWasteReductionProgram,omitempty"` - IndependentOrganizationAuditsEnergyUse **Ternary `json:"independentOrganizationAuditsEnergyUse,omitempty"` - IndependentOrganizationAuditsWaterUse **Ternary `json:"independentOrganizationAuditsWaterUse,omitempty"` - LinenReuseProgram **Ternary `json:"linenReuseProgram,omitempty"` - LocallySourcedFoodAndBeverages **Ternary `json:"locallySourcedFoodAndBeverages,omitempty"` - OrganicCageFreeEggs **Ternary `json:"organicCageFreeEggs,omitempty"` - OrganicFoodAndBeverages **Ternary `json:"organicFoodAndBeverages,omitempty"` - RecyclingProgram **Ternary `json:"recyclingProgram,omitempty"` - RefillableToiletryContainers **Ternary `json:"refillableToiletryContainers,omitempty"` - ResponsiblePurchasingPolicy **Ternary `json:"responsiblePurchasingPolicy,omitempty"` - ResponsiblySourcesSeafood **Ternary `json:"responsiblySourcesSeafood,omitempty"` - SafelyDisposesBatteries **Ternary `json:"safelyDisposesBatteries,omitempty"` - SafelyDisposesElectronics **Ternary `json:"safelyDisposesElectronics,omitempty"` - SafelyDisposesLightbulbs **Ternary `json:"safelyDisposesLightbulbs,omitempty"` - SafelyHandlesHazardousSubstances **Ternary `json:"safelyHandlesHazardousSubstances,omitempty"` - SingleUsePlasticStrawsBanned **Ternary `json:"singleUsePlasticStrawsBanned,omitempty"` - SingleUsePlasticWaterBottlesBanned **Ternary `json:"singleUsePlasticWaterBottlesBanned,omitempty"` - SoapDonationProgram **Ternary `json:"soapDonationProgram,omitempty"` - StyrofoamFoodContainersBanned **Ternary `json:"styrofoamFoodContainersBanned,omitempty"` - ToiletryDonationProgram **Ternary `json:"toiletryDonationProgram,omitempty"` - TowelReuseProgram **Ternary `json:"towelReuseProgram,omitempty"` - VeganMealsAvailable **Ternary `json:"veganMealsAvailable,omitempty"` - VegetarianMealsAvailable **Ternary `json:"vegetarianMealsAvailable,omitempty"` - WaterBottleFillingStations **Ternary `json:"waterBottleFillingStations,omitempty"` - WaterSavingShowers **Ternary `json:"waterSavingShowers,omitempty"` - WaterSavingSinks **Ternary `json:"waterSavingSinks,omitempty"` - WaterSavingToilets **Ternary `json:"waterSavingToilets,omitempty"` - BreeamRating *string `json:"breeamRating,omitempty"` - LeedRating *string `json:"leedRating,omitempty"` + DigitalGuestRoomKeys **Ternary `json:"digitalGuestRoomKeys,omitempty"` + CommonAreasAdvancedCleaning **Ternary `json:"commonAreasEnhancedCleaning,omitempty"` + GuestRoomsEnhancedCleaning **Ternary `json:"guestRoomsEnhancedCleaning,omitempty"` + CommercialGradeDisinfectantUsed **Ternary `json:"commercialGradeDisinfectantUsed,omitempty"` + EmployeesTrainedInCleaningProcedures **Ternary `json:"employeesTrainedInCleaningProcedures,omitempty"` + EmployeesTrainedInHandWashing **Ternary `json:"employeesTrainedInHandWashing,omitempty"` + EmployeesWearProtectiveEquipment **Ternary `json:"employeesWearProtectiveEquipment,omitempty"` + HighTouchItemsRemovedFromGuestRooms **Ternary `json:"highTouchItemsRemovedFromGuestRooms,omitempty"` + HighTouchItemsRemovedFromCommonAreas **Ternary `json:"highTouchItemsRemovedFromCommonAreas,omitempty"` + PlasticKeycardsDisinfectedOrDiscarded **Ternary `json:"plasticKeycardsDisinfectedOrDiscarded,omitempty"` + ContactlessCheckInCheckOut **Ternary `json:"contactlessCheckinOrCheckout,omitempty"` + PhysicalDistancingRequired **Ternary `json:"physicalDistancingRequired,omitempty"` + PlexiglassUsed **Ternary `json:"plexiglassUsed,omitempty"` + LimitedOccupancyInSharedAreas **Ternary `json:"limitedOccupancyInSharedAreas,omitempty"` + PrivateSpacesInWellnessAreas **Ternary `json:"privateSpacesInWellnessAreas,omitempty"` + CommonAreasArrangedForDistancing **Ternary `json:"commonAreasArrangedForDistancing,omitempty"` + SanitizerAvailable **Ternary `json:"sanitizerAvailable,omitempty"` + MasksRequired **Ternary `json:"masksRequired,omitempty"` + IndividuallyPackagedMealsAvailable **Ternary `json:"individuallyPackagedMealsAvailable,omitempty"` + DisposableFlatware **Ternary `json:"disposableFlatware,omitempty"` + SingleUseMenus **Ternary `json:"singleUseMenus,omitempty"` + RoomBookingsBuffer **Ternary `json:"roomBookingsBuffer,omitempty"` + RequestOnlyHousekeeping **Ternary `json:"requestOnlyHousekeeping,omitempty"` + InRoomHygieneKits **Ternary `json:"inRoomHygieneKits,omitempty"` + ProtectiveEquipmentAvailable **Ternary `json:"protectiveEquipmentAvailable,omitempty"` + SafeHandlingForFoodServices **Ternary `json:"safeHandlingForFoodServices,omitempty"` + AdditionalSanitationInFoodAreas **Ternary `json:"additionalSanitationInFoodAreas,omitempty"` + EcoCertifications *UnorderedStrings `json:"ecoCertifications,omitempty"` + EcoFriendlyToiletries **Ternary `json:"ecoFriendlyToiletries,omitempty"` } const ( diff --git a/entity_restaurant.go b/entity_restaurant.go index a009990..7abb933 100644 --- a/entity_restaurant.go +++ b/entity_restaurant.go @@ -31,31 +31,23 @@ type RestaurantEntity struct { Emails *[]string `json:"emails,omitempty"` // Location Info - Description *string `json:"description,omitempty"` - Hours **Hours `json:"hours,omitempty"` - BrunchHours **Hours `json:"brunchHours,omitempty"` - DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` - AdditionalHoursText *string `json:"additionalHoursText,omitempty"` - DeliveryHours **Hours `json:"deliveryHours,omitempty"` - YearEstablished **float64 `json:"yearEstablished,omitempty"` - Services *[]string `json:"services,omitempty"` - Languages *[]string `json:"languages,omitempty"` - Logo **Photo `json:"logo,omitempty"` - PaymentOptions *[]string `json:"paymentOptions,omitempty"` - Geomodifier *string `json:"geomodifier,omitempty"` - PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` - MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` - AcceptsReservations **Ternary `json:"acceptsReservations,omitempty"` - KitchenHours **Hours `json:"kitchenHours,omitempty"` - DineInHours **Hours `json:"dineInHours,omitempty"` - AccessHours **Hours `json:"accessHours,omitempty"` - HappyHours **Hours `json:"happyHours,omitempty"` - OnlineServiceHours **Hours `json:"onlineServiceHours,omitempty"` - PickupHours **Hours `json:"pickupHours,omitempty"` - SeniorHours **Hours `json:"seniorHours,omitempty"` - TakeoutHours **Hours `json:"takeoutHours,omitempty"` - HolidayHoursConversationEnabled **Ternary `json:"holidayHoursConversationEnabled,omitempty"` - FrequentlyAskedQuestions *[]FAQField `json:"frequentlyAskedQuestions,omitempty"` + Description *string `json:"description,omitempty"` + Hours **Hours `json:"hours,omitempty"` + BrunchHours **Hours `json:"brunchHours,omitempty"` + DriveThroughHours **Hours `json:"driveThroughHours,omitempty"` + AdditionalHoursText *string `json:"additionalHoursText,omitempty"` + DeliveryHours **Hours `json:"deliveryHours,omitempty"` + YearEstablished **float64 `json:"yearEstablished,omitempty"` + Services *[]string `json:"services,omitempty"` + Languages *[]string `json:"languages,omitempty"` + Logo **Photo `json:"logo,omitempty"` + PaymentOptions *[]string `json:"paymentOptions,omitempty"` + Geomodifier *string `json:"geomodifier,omitempty"` + PickupAndDeliveryServices *[]string `json:"pickupAndDeliveryServices,omitempty"` + MealsServed *UnorderedStrings `json:"mealsServed,omitempty"` + AcceptsReservations **Ternary `json:"kitchenHours,omitempty"` + KitchenHours **Hours `json:"kitchenHours,omitempty"` + DineInHours **Hours `json:"dineInHours,omitempty"` // Lats & Lngs DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` @@ -76,7 +68,6 @@ type RestaurantEntity struct { ReservationUrl **Website `json:"reservationUrl,omitempty"` WebsiteUrl **Website `json:"websiteUrl,omitempty"` FeaturedMessage **FeaturedMessage `json:"featuredMessage,omitempty"` - DeliveryUrl **Website `json:"deliveryUrl,omitempty"` // Uber UberLink **UberLink `json:"uberLink,omitempty"` @@ -94,12 +85,8 @@ type RestaurantEntity struct { GoogleProfilePhoto **Image `json:"googleProfilePhoto,omitempty"` GoogleWebsiteOverride **string `json:"googleWebsiteOverride,omitempty"` - InstagramHandle *string `json:"instagramHandle,omitempty"` - TwitterHandle *string `json:"twitterHandle,omitempty"` - LinkedInUrl *string `json:"linkedInUrl,omitempty"` - PinterestUrl *string `json:"pinterestUrl,omitempty"` - TikTokUrl *string `json:"tikTokUrl,omitempty"` - YouTubeChannelUrl *string `json:"youTubeChannelUrl,omitempty"` + InstagramHandle *string `json:"instagramHandle,omitempty"` + TwitterHandle *string `json:"twitterHandle,omitempty"` PhotoGallery *[]Photo `json:"photoGallery,omitempty"` Videos *[]Video `json:"videos,omitempty"` From d5e98d6e14381c8e1bf0fe6566434b7bc6ba271d Mon Sep 17 00:00:00 2001 From: Matthew Leifer Date: Fri, 26 Jul 2024 10:13:51 -0600 Subject: [PATCH 274/285] feat(assets): add folderids field J=none --- cftasset.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cftasset.go b/cftasset.go index 38588e6..eff2288 100644 --- a/cftasset.go +++ b/cftasset.go @@ -52,6 +52,7 @@ type CFTAsset struct { type ForEntities struct { MappingType MappingType `json:"mappingType,omitempty"` + FolderIds *UnorderedStrings `json:"folderIds,omitempty"` FolderId *string `json:"folderId,omitempty"` EntityIds *UnorderedStrings `json:"entityIds,omitempty"` LabelIds *UnorderedStrings `json:"labelIds,omitempty"` From db1616c0583d964a68bc52b57a5ad2e295a8a767 Mon Sep 17 00:00:00 2001 From: fsalas Date: Mon, 12 Aug 2024 13:30:05 -0600 Subject: [PATCH 275/285] feat(entities): add timestamp field to entity-meta J=PC-258895 --- entity.go | 1 + 1 file changed, 1 insertion(+) diff --git a/entity.go b/entity.go index aac7bc8..94883f5 100644 --- a/entity.go +++ b/entity.go @@ -19,6 +19,7 @@ type EntityMeta struct { Labels *UnorderedStrings `json:"labels,omitempty"` Language *string `json:"language,omitempty"` CountryCode *string `json:"countryCode,omitempty"` + Timestamp *string `json:"timestamp,omitempty"` } type BaseEntity struct { From d8187833ddb100e3a2191dce70ee937fe96f552f Mon Sep 17 00:00:00 2001 From: fsalas Date: Mon, 12 Aug 2024 15:42:04 -0600 Subject: [PATCH 276/285] feat(entities): add getter function for timestamp field J=PC-258895 --- entity.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/entity.go b/entity.go index 94883f5..d5bb8b5 100644 --- a/entity.go +++ b/entity.go @@ -62,6 +62,16 @@ func (b *BaseEntity) GetCountryCode() string { return "" } +func (b *BaseEntity) GetTimestamp() string { + if b == nil || b.Meta == nil { + return "" + } + if b.Meta.Timestamp != nil { + return *b.Meta.Timestamp + } + return "" +} + // GetLabels returns a list of labels. // Labels are stored in the system by ID, not by name // Given a label "Example Label" with ID "123" From c68fa76a51c225220515bd2872350daaa7862e11 Mon Sep 17 00:00:00 2001 From: fsalas Date: Tue, 13 Aug 2024 08:16:38 -0600 Subject: [PATCH 277/285] feat(entities): add GetTimestampAsTime function J=PC-258895 --- entity.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/entity.go b/entity.go index d5bb8b5..e3b6712 100644 --- a/entity.go +++ b/entity.go @@ -2,6 +2,7 @@ package yext import ( "encoding/json" + "time" ) type EntityType string @@ -72,6 +73,19 @@ func (b *BaseEntity) GetTimestamp() string { return "" } +func (b *BaseEntity) GetTimestampAsTime(layout string) (*time.Time, error) { + timeLayout := layout + if timeLayout == "" { + timeLayout = "2006-01-02T15:04:05Z" + } + + t, err := time.Parse(timeLayout, *b.Meta.Timestamp) + if err != nil { + return nil, err + } + return &t, nil +} + // GetLabels returns a list of labels. // Labels are stored in the system by ID, not by name // Given a label "Example Label" with ID "123" From bcdcdcad0cfba194b7eca462dfd773870dddf663 Mon Sep 17 00:00:00 2001 From: fsalas Date: Wed, 14 Aug 2024 21:18:05 -0600 Subject: [PATCH 278/285] Revert "feat(entities): add GetTimestampAsTime function" This reverts commit d840ea08cbd6537bebaa1cf8037be29bfc44a29c. --- entity.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/entity.go b/entity.go index e3b6712..d5bb8b5 100644 --- a/entity.go +++ b/entity.go @@ -2,7 +2,6 @@ package yext import ( "encoding/json" - "time" ) type EntityType string @@ -73,19 +72,6 @@ func (b *BaseEntity) GetTimestamp() string { return "" } -func (b *BaseEntity) GetTimestampAsTime(layout string) (*time.Time, error) { - timeLayout := layout - if timeLayout == "" { - timeLayout = "2006-01-02T15:04:05Z" - } - - t, err := time.Parse(timeLayout, *b.Meta.Timestamp) - if err != nil { - return nil, err - } - return &t, nil -} - // GetLabels returns a list of labels. // Labels are stored in the system by ID, not by name // Given a label "Example Label" with ID "123" From 3696c387f9ba298446ebd8e5a9ae20d89a4b9e3a Mon Sep 17 00:00:00 2001 From: Stephanie Harvey <31486703+stephh2@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:20:24 -0400 Subject: [PATCH 279/285] feat(license): add license service (#323) J=none --- client.go | 11 +++++- license.go | 37 +++++++++++++++++ license_service.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 license.go create mode 100644 license_service.go diff --git a/client.go b/client.go index f512738..7906a68 100644 --- a/client.go +++ b/client.go @@ -21,6 +21,8 @@ const ( ) type ListOptions struct { + usePageSize bool // some services use pageSize param instead of limit + Limit int Offset int DisableCountValidation bool @@ -51,6 +53,7 @@ type Client struct { ServicesService *ServicesService ListingsService *ListingsService ConfigFieldService *ConfigFieldService + LicenseService *LicenseService UseConfigAPIForCFs bool } @@ -79,6 +82,7 @@ func NewClient(config *Config) *Client { c.ServicesService = &ServicesService{client: c} c.LanguageProfileService.RegisterDefaultEntities() c.ListingsService = &ListingsService{client: c} + c.LicenseService = &LicenseService{client: c} c.ConfigFieldService = &ConfigFieldService{client: c} return c } @@ -390,8 +394,13 @@ func addListOptions(requrl string, opts *ListOptions) (string, error) { q.Add("name", opts.Name) } if opts.Limit != 0 { - q.Add("limit", strconv.Itoa(opts.Limit)) + if opts.usePageSize { + q.Add("pageSize", strconv.Itoa(opts.Limit)) + } else { + q.Add("limit", strconv.Itoa(opts.Limit)) + } } + if opts.PageToken != "" { q.Add("pageToken", opts.PageToken) } else if opts.Offset != 0 { diff --git a/license.go b/license.go new file mode 100644 index 0000000..393f455 --- /dev/null +++ b/license.go @@ -0,0 +1,37 @@ +package yext + +import "fmt" + +type AssignedEntity struct { + ExpirationDate string `json:"expirationDate,omitempty"` + EntityId string `json:"entityId,omitempty"` + LicensePackId string `json:"-"` +} + +func (l *AssignedEntity) pathToAssignment() string { + return fmt.Sprintf("%s/%s/assigned-entities/%s", packsPath, l.LicensePackId, l.EntityId) +} + +type LicensePack struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Total int `json:"total,omitempty"` + Assigned int `json:"assigned,omitempty"` + Available int `json:"available,omitempty"` + Notes string `json:"notes,omitempty"` +} + +type LicenseAssignment struct { + LicensePacks *LicensePack `json:"licensePack,omitempty"` + AssignedEntities []*AssignedEntity `json:"assignedEntities,omitempty"` +} + +type LicenseAssignmentListResponse struct { + LicenseAssignments []*LicenseAssignment `json:"licenseAssignments,omitempty"` + NextPageToken string `json:"nextPageToken,omitempty"` +} + +type LicensePacksListResponse struct { + LicensePacks []*LicensePack `json:"licensePacks,omitempty"` + NextPageToken string `json:"nextPageToken,omitempty"` +} diff --git a/license_service.go b/license_service.go new file mode 100644 index 0000000..1ccfbc4 --- /dev/null +++ b/license_service.go @@ -0,0 +1,98 @@ +package yext + +import "fmt" + +const ( + packsPath = "license-packs" +) + +type LicenseService struct { + client *Client +} + +func (u *LicenseService) ListPacks(opts *ListOptions) (*LicensePacksListResponse, *Response, error) { + if opts != nil { + opts.usePageSize = true + } + + requrl, err := addListOptions(packsPath, opts) + if err != nil { + return nil, nil, err + } + + v := &LicensePacksListResponse{} + r, err := u.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + return v, r, nil +} + +func (u *LicenseService) ListAllPacks() ([]*LicensePack, error) { + var licensePacks []*LicensePack + + var al tokenListRetriever = func(opts *ListOptions) (string, error) { + lpr, _, err := u.ListPacks(opts) + if err != nil { + return "", err + } + licensePacks = append(licensePacks, lpr.LicensePacks...) + return lpr.NextPageToken, err + } + + if err := tokenListHelper(al, nil); err != nil { + return nil, err + } else { + return licensePacks, nil + } +} + +func (u *LicenseService) ListAssignments(licensePackId string, opts *ListOptions) (*LicenseAssignmentListResponse, *Response, error) { + if opts != nil { + opts.usePageSize = true + } + requrl, err := addListOptions(fmt.Sprintf("%s/%s/assigned-entities", packsPath, licensePackId), opts) + if err != nil { + return nil, nil, err + } + + v := &LicenseAssignmentListResponse{} + r, err := u.client.DoRequest("GET", requrl, v) + if err != nil { + return nil, r, err + } + return v, r, nil +} + +func (u *LicenseService) ListAllAssignments(licensePackId string) ([]*LicenseAssignment, error) { + var licenseAssignments []*LicenseAssignment + + var al tokenListRetriever = func(opts *ListOptions) (string, error) { + lar, _, err := u.ListAssignments(licensePackId, opts) + if err != nil { + return "", err + } + licenseAssignments = append(licenseAssignments, lar.LicenseAssignments...) + return lar.NextPageToken, err + } + + if err := tokenListHelper(al, nil); err != nil { + return nil, err + } else { + return licenseAssignments, nil + } +} + +func (u *LicenseService) EditAssignment(l *AssignedEntity) (*Response, error) { + return u.client.DoRequestJSON("PUT", l.pathToAssignment(), l, nil) +} + +func (u *LicenseService) CreateAssignment(l *AssignedEntity) (*Response, error) { + return u.client.DoRequestJSON("POST", l.pathToAssignment(), nil, nil) +} + +func (u *LicenseService) DeleteAssignment(l *AssignedEntity) (*Response, error) { + return u.client.DoRequest("DELETE", l.pathToAssignment(), nil) +} + + From c64ffdef2c81af9df73e400569941a7f079a104b Mon Sep 17 00:00:00 2001 From: kaneleeyext <87987557+kaneleeyext@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:02:57 -0400 Subject: [PATCH 280/285] feat(license): fix passing opts to tokenListHelper (#324) J=none --- license_service.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/license_service.go b/license_service.go index 1ccfbc4..4c8253b 100644 --- a/license_service.go +++ b/license_service.go @@ -14,7 +14,7 @@ func (u *LicenseService) ListPacks(opts *ListOptions) (*LicensePacksListResponse if opts != nil { opts.usePageSize = true } - + requrl, err := addListOptions(packsPath, opts) if err != nil { return nil, nil, err @@ -40,7 +40,8 @@ func (u *LicenseService) ListAllPacks() ([]*LicensePack, error) { return lpr.NextPageToken, err } - if err := tokenListHelper(al, nil); err != nil { + opts := ListOptions{} + if err := tokenListHelper(al, &opts); err != nil { return nil, err } else { return licensePacks, nil @@ -76,7 +77,8 @@ func (u *LicenseService) ListAllAssignments(licensePackId string) ([]*LicenseAss return lar.NextPageToken, err } - if err := tokenListHelper(al, nil); err != nil { + opts := ListOptions{} + if err := tokenListHelper(al, &opts); err != nil { return nil, err } else { return licenseAssignments, nil @@ -94,5 +96,3 @@ func (u *LicenseService) CreateAssignment(l *AssignedEntity) (*Response, error) func (u *LicenseService) DeleteAssignment(l *AssignedEntity) (*Response, error) { return u.client.DoRequest("DELETE", l.pathToAssignment(), nil) } - - From 75ab0d070a3091470597a2d2ac2dc5e77c1ca15a Mon Sep 17 00:00:00 2001 From: tritpham <54277371+tritpham@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:48:19 -0400 Subject: [PATCH 281/285] feat(healthcare-professional): Add YextDisplayCoordinate property and Get functions (#325) --- entity_healthcare_professional.go | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/entity_healthcare_professional.go b/entity_healthcare_professional.go index 5a5938e..952c0d8 100644 --- a/entity_healthcare_professional.go +++ b/entity_healthcare_professional.go @@ -80,11 +80,12 @@ type HealthcareProfessionalEntity struct { EducationList *[]Education `json:"educationList,omitempty"` // Lats & Lngs - DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` - DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` - WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` - RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` - PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + DisplayCoordinate **Coordinate `json:"displayCoordinate,omitempty"` + DropoffCoordinate **Coordinate `json:"dropoffCoordinate,omitempty"` + WalkableCoordinate **Coordinate `json:"walkableCoordinate,omitempty"` + RoutableCoordinate **Coordinate `json:"routableCoordinate,omitempty"` + PickupCoordinate **Coordinate `json:"pickupCoordinate,omitempty"` + YextDisplayCoordinate **Coordinate `json:"yextDisplayCoordinate,omitempty"` // Lists Bios **Lists `json:"bios,omitempty"` @@ -425,6 +426,22 @@ func (y HealthcareProfessionalEntity) GetRoutableLng() float64 { return 0 } +func (y HealthcareProfessionalEntity) GetYextDisplayLat() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Latitude) + } + return 0 +} + +func (y HealthcareProfessionalEntity) GetYextDisplayLng() float64 { + c := GetCoordinate(y.YextDisplayCoordinate) + if c != nil { + return GetNullableFloat(c.Longitude) + } + return 0 +} + func (y HealthcareProfessionalEntity) GetBios() (v *Lists) { return GetLists(y.Bios) } From c880e4a3b8a0922963ca3e6d521d61c261dced49 Mon Sep 17 00:00:00 2001 From: Stephanie Harvey <31486703+stephh2@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:40:29 -0400 Subject: [PATCH 282/285] license-service: list license assignments in single object (#326) Co-authored-by: Stephanie Severance --- license_service.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/license_service.go b/license_service.go index 4c8253b..e4fdfdb 100644 --- a/license_service.go +++ b/license_service.go @@ -65,15 +65,22 @@ func (u *LicenseService) ListAssignments(licensePackId string, opts *ListOptions return v, r, nil } -func (u *LicenseService) ListAllAssignments(licensePackId string) ([]*LicenseAssignment, error) { - var licenseAssignments []*LicenseAssignment +func (u *LicenseService) ListAllAssignments(licensePackId string) (*LicenseAssignment, error) { + var licenseAssignments = &LicenseAssignment{} var al tokenListRetriever = func(opts *ListOptions) (string, error) { lar, _, err := u.ListAssignments(licensePackId, opts) if err != nil { return "", err } - licenseAssignments = append(licenseAssignments, lar.LicenseAssignments...) + + if len(lar.LicenseAssignments) == 0 { + return "", fmt.Errorf("error: no license assignments found for id %s", licensePackId) + } + + licenseAssignments.LicensePacks = lar.LicenseAssignments[0].LicensePacks + licenseAssignments.AssignedEntities = append(licenseAssignments.AssignedEntities, lar.LicenseAssignments[0].AssignedEntities...) + return lar.NextPageToken, err } From 16a7daac81abf6d0fd695d37cd149407c3bd512e Mon Sep 17 00:00:00 2001 From: ajgist <59585641+ajgist@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:19:58 -0500 Subject: [PATCH 283/285] entity: add CreatedTimestamp field to entityMeta struct (#327) --- entity.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/entity.go b/entity.go index d5bb8b5..009a045 100644 --- a/entity.go +++ b/entity.go @@ -12,14 +12,15 @@ type Entity interface { } type EntityMeta struct { - Id *string `json:"id,omitempty"` - AccountId *string `json:"accountId,omitempty"` - EntityType EntityType `json:"entityType,omitempty"` - FolderId *string `json:"folderId,omitempty"` - Labels *UnorderedStrings `json:"labels,omitempty"` - Language *string `json:"language,omitempty"` - CountryCode *string `json:"countryCode,omitempty"` - Timestamp *string `json:"timestamp,omitempty"` + Id *string `json:"id,omitempty"` + AccountId *string `json:"accountId,omitempty"` + EntityType EntityType `json:"entityType,omitempty"` + FolderId *string `json:"folderId,omitempty"` + Labels *UnorderedStrings `json:"labels,omitempty"` + Language *string `json:"language,omitempty"` + CountryCode *string `json:"countryCode,omitempty"` + Timestamp *string `json:"timestamp,omitempty"` + CreatedTimestamp *string `json:"createdTimestamp,omitempty"` } type BaseEntity struct { @@ -72,6 +73,16 @@ func (b *BaseEntity) GetTimestamp() string { return "" } +func (b *BaseEntity) GetCreatedTimestamp() string { + if b == nil || b.Meta == nil { + return "" + } + if b.Meta.CreatedTimestamp != nil { + return *b.Meta.CreatedTimestamp + } + return "" +} + // GetLabels returns a list of labels. // Labels are stored in the system by ID, not by name // Given a label "Example Label" with ID "123" From 19383a43dbbf55c8c2cf8bebf2a8e362decc3cfc Mon Sep 17 00:00:00 2001 From: Jared Hood <38025637+Jared-Hood@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:08:00 -0500 Subject: [PATCH 284/285] client: add optional Update-Operation-Id header to API requests (#328) --- client.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 7906a68..5c87ffc 100644 --- a/client.go +++ b/client.go @@ -21,7 +21,7 @@ const ( ) type ListOptions struct { - usePageSize bool // some services use pageSize param instead of limit + usePageSize bool // some services use pageSize param instead of limit Limit int Offset int @@ -55,6 +55,7 @@ type Client struct { ConfigFieldService *ConfigFieldService LicenseService *LicenseService UseConfigAPIForCFs bool + UpdateOperationId string } func NewClient(config *Config) *Client { @@ -92,6 +93,11 @@ func (c *Client) WithConfigAPIForCFs() *Client { return c } +func (c *Client) WithUpdateOperationIdHeader(id string) *Client { + c.UpdateOperationId = id + return c +} + // Default Client but with empty entity registries so that all entities are treated as Raw Entities func NewRawEntityClient(config *Config) *Client { c := NewClient(config) @@ -149,6 +155,11 @@ func (c *Client) NewRequestBody(method string, fullPath string, data []byte) (*h } req.Header.Set("Content-Type", "application/json") + + if c.UpdateOperationId != "" { + req.Header.Set("Update-Operation-Id", c.UpdateOperationId) + } + q := req.URL.Query() q.Add("api_key", c.Config.ApiKey) q.Add("v", c.Config.Version) From 5256e02abaac92333ad5b70897bfedbb0eb85d42 Mon Sep 17 00:00:00 2001 From: Jeffrey Rhoads <46790862+Jeffrey-Rhoads17@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:16:17 -0500 Subject: [PATCH 285/285] fix(custom): add option for list CF types and proper fallback for CFTs to set the type correctly (#329) J=PC-270239 TEST=manual --- cac.go | 2 +- customfield.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cac.go b/cac.go index 47e31f2..e820e8c 100644 --- a/cac.go +++ b/cac.go @@ -265,7 +265,7 @@ func (c *ConfigField) GetCustomFieldType() string { return CUSTOMFIELDTYPE_SINGLELINETEXT } - return CUSTOMFIELDTYPE_STRUCT + return c.TypeId } func (c *ConfigField) GetCustomFieldOptions() []CustomFieldOption { diff --git a/customfield.go b/customfield.go index 42f9c57..c53c9f9 100644 --- a/customfield.go +++ b/customfield.go @@ -171,7 +171,8 @@ func (c *CustomField) GetConfigFieldTypeId() string { CUSTOMFIELDTYPE_GALLERY, CUSTOMFIELDTYPE_SIMPLYVIDEOGALLERY, CUSTOMFIELDTYPE_VIDEOGALLERY, - CUSTOMFIELDTYPE_ENTITYLIST: + CUSTOMFIELDTYPE_ENTITYLIST, + CUSTOMFIELDTYPE_LIST: return CONFIGFIELDTYPE_LIST case CUSTOMFIELDTYPE_SINGLELINETEXT, CUSTOMFIELDTYPE_MULTILINETEXT,