From 7c5f3787dd421e489ba814a700aaa70bd366ca95 Mon Sep 17 00:00:00 2001 From: Cristian Vidmar Date: Thu, 9 Oct 2025 10:51:13 +0200 Subject: [PATCH] feat: add GetOrInheritFieldValue function for hierarchical field retrieval --- docs/02-client/06-otherfunctions.md | 50 ++++++++++++++++++++++++++ erm/templates/contentful_vo_lib.gotmpl | 47 +++++++++++++++++++++++- test/testapi/gocontentfulvolib.go | 47 +++++++++++++++++++++++- 3 files changed, 142 insertions(+), 2 deletions(-) diff --git a/docs/02-client/06-otherfunctions.md b/docs/02-client/06-otherfunctions.md index 580d313..2e9a59f 100644 --- a/docs/02-client/06-otherfunctions.md +++ b/docs/02-client/06-otherfunctions.md @@ -6,6 +6,56 @@ Another thing you might want to know is the content type of an entry with a give (cc *ContentfulClient) GetContentTypeOfID(ctx, ID string) (contentType string) ``` +## Field inheritance + +When working with hierarchical content structures, you may need to retrieve field values that can be inherited from parent entries. The `GetOrInheritFieldValue` function provides this functionality: + +```go +func GetOrInheritFieldValue(ctx context.Context, contentfulShop *ContentfulClient, entryID string, field string, parentContentTypes []string, locale Locale) (any, error) +``` + +This function retrieves a field value from a GenericEntry, traversing up the full parent hierarchy if the field is not found in the current entry. It's particularly useful for content types that have parent-child relationships where certain fields should be inherited from parent entries. + +### Parameters + +- `ctx`: Context for the operation +- `contentfulShop`: The ContentfulClient instance +- `entryID`: The ID of the entry to start the search from +- `field`: The field name to retrieve +- `parentContentTypes`: List of content type IDs that should be considered as valid parents +- `locale`: The locale to retrieve the field value in + +### Return value + +Returns the field value as `any` type, or an error if: +- The entry cannot be retrieved +- A circular reference is detected in the parent hierarchy +- The field is not found in any parent entry +- Any parent entry cannot be retrieved + +### Example usage + +```go +ctx := context.Background() +parentContentTypes := []string{"category", "section"} + +// Try to get the "theme" field from the current entry or inherit it from parents +theme, err := GetOrInheritFieldValue(ctx, cc, "my-entry-id", "theme", parentContentTypes, contentful.DefaultLocale) +if err != nil { + log.Printf("Theme not found: %v", err) +} else { + log.Printf("Theme value: %v", theme) +} +``` + +### Important notes + +- The function first attempts to get the field value from the current entry +- If not found, it recursively traverses up the parent hierarchy +- Circular references are detected and will return an error to prevent infinite loops +- The function respects the locale parameter for localized field values +- Only entries with content types specified in `parentContentTypes` are considered as valid parents + ## Caveats and limitations - Avoid creating content types that have field IDs equal to reserved Go words (e.g. "type"). diff --git a/erm/templates/contentful_vo_lib.gotmpl b/erm/templates/contentful_vo_lib.gotmpl index c954cdf..79e1e13 100644 --- a/erm/templates/contentful_vo_lib.gotmpl +++ b/erm/templates/contentful_vo_lib.gotmpl @@ -568,7 +568,52 @@ func (ref EntryReference) GetParents(ctx context.Context, contentType ...string) } return candidateParents, nil } - return nil, errors.New("GetParents: reference VO and CC are both nil") +} + +func GetOrInheritFieldValue(ctx context.Context, contentfulShop *ContentfulClient, entryID string, field string, parentContentTypes []string, locale Locale) (any, error) { + entry, err := contentfulShop.GetGenericEntry(entryID) + if err != nil { + return nil, fmt.Errorf("failed to get entry %s: %v", entryID, err) + } + + // Try to get the field value from the current entry first + val, err := entry.FieldAsAny(field, locale) + if err == nil { + return val, nil + } + + return inheritFieldValueRecursive(ctx, contentfulShop, entry, field, parentContentTypes, locale, make(map[string]bool)) +} + +func inheritFieldValueRecursive(ctx context.Context, contentfulShop *ContentfulClient, entry *GenericEntry, field string, parentContentTypes []string, locale Locale, visited map[string]bool) (any, error) { + if visited[entry.Sys.ID] { + return nil, fmt.Errorf("circular reference detected for entry %s", entry.Sys.ID) + } + visited[entry.Sys.ID] = true + + parentRefs, err := commonGetParents(ctx, entry.CC, entry.Sys.ID, parentContentTypes) + if err != nil { + return nil, fmt.Errorf("failed to get parents for entry %s: %v", entry.Sys.ID, err) + } + + for _, parentRef := range parentRefs { + parentEntry, err := entry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, fmt.Errorf("failed to get parent entry %s: %v", parentRef.ID, err) + } + + val, err := parentEntry.FieldAsAny(field, locale) + if err == nil { + return val, nil + } + + inheritedVal, err := inheritFieldValueRecursive(ctx, contentfulShop, parentEntry, field, parentContentTypes, locale, visited) + if err == nil { + return inheritedVal, nil + } + } + + return nil, ErrNotSet } func HtmlToRichText(htmlSrc string) *RichTextNode { diff --git a/test/testapi/gocontentfulvolib.go b/test/testapi/gocontentfulvolib.go index bcfc850..c633012 100644 --- a/test/testapi/gocontentfulvolib.go +++ b/test/testapi/gocontentfulvolib.go @@ -597,7 +597,52 @@ func (ref EntryReference) GetParents(ctx context.Context, contentType ...string) } return candidateParents, nil } - return nil, errors.New("GetParents: reference VO and CC are both nil") +} + +func GetOrInheritFieldValue(ctx context.Context, contentfulShop *ContentfulClient, entryID string, field string, parentContentTypes []string, locale Locale) (any, error) { + entry, err := contentfulShop.GetGenericEntry(entryID) + if err != nil { + return nil, fmt.Errorf("failed to get entry %s: %v", entryID, err) + } + + // Try to get the field value from the current entry first + val, err := entry.FieldAsAny(field, locale) + if err == nil { + return val, nil + } + + return inheritFieldValueRecursive(ctx, contentfulShop, entry, field, parentContentTypes, locale, make(map[string]bool)) +} + +func inheritFieldValueRecursive(ctx context.Context, contentfulShop *ContentfulClient, entry *GenericEntry, field string, parentContentTypes []string, locale Locale, visited map[string]bool) (any, error) { + if visited[entry.Sys.ID] { + return nil, fmt.Errorf("circular reference detected for entry %s", entry.Sys.ID) + } + visited[entry.Sys.ID] = true + + parentRefs, err := commonGetParents(ctx, entry.CC, entry.Sys.ID, parentContentTypes) + if err != nil { + return nil, fmt.Errorf("failed to get parents for entry %s: %v", entry.Sys.ID, err) + } + + for _, parentRef := range parentRefs { + parentEntry, err := entry.CC.GetGenericEntry(parentRef.ID) + if err != nil { + return nil, fmt.Errorf("failed to get parent entry %s: %v", parentRef.ID, err) + } + + val, err := parentEntry.FieldAsAny(field, locale) + if err == nil { + return val, nil + } + + inheritedVal, err := inheritFieldValueRecursive(ctx, contentfulShop, parentEntry, field, parentContentTypes, locale, visited) + if err == nil { + return inheritedVal, nil + } + } + + return nil, ErrNotSet } func HtmlToRichText(htmlSrc string) *RichTextNode {