feat: add GetOrInheritFieldValue function for hierarchical field retrieval

This commit is contained in:
Cristian Vidmar 2025-10-09 10:51:13 +02:00
parent fb5ecfe7da
commit 7c5f3787dd
3 changed files with 142 additions and 2 deletions

View File

@ -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").

View File

@ -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 {

View File

@ -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 {