Skip to content

Commit

Permalink
feat: add hasField method for use with structs (#61)
Browse files Browse the repository at this point in the history
## Description

The PR introduces a `hasField` method to the `reflect` registry, to
address the use case explained in #60.

## Fixes #60 

## Checklist
- [x] I have read the **CONTRIBUTING.md** document.
- [x] My code follows the code style of this project.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] I have updated the documentation accordingly.
- [x] This change requires a change to the documentation on the website.

---------

Signed-off-by: Atomys <[email protected]>
  • Loading branch information
mbezhanov authored Aug 22, 2024
1 parent 13fe589 commit 44ae526
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 9 deletions.
18 changes: 18 additions & 0 deletions docs/registries/reflect.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ The function returns the kind (category) of the provided value (`src`) as a stri
{% endtab %}
{% endtabs %}

### <mark style="color:purple;">hasField</mark>

The function checks the presence of a field with the specified name (`name`) in the provided struct (`src`). It returns `true` if the field exists.

<table data-header-hidden><thead><tr><th width="164">Name</th><th>Value</th></tr></thead><tbody><tr><td>Signature</td><td><pre class="language-go"><code class="lang-go">HasField(name string, src any) bool
</code></pre></td></tr><tr><td>Must version</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr></tbody></table>

{% tabs %}
{% tab title="Template Example" %}
```go
{{ hasField "someExistingField" .someStruct }} // Output: true
{{ hasField "someNonExistingField" .someStruct }} // Output: false
{{ .someStruct | hasField "someExistingField" }} // Output: true
{{ .someStruct | hasField "someNonExistingField" }} // Output: false
```
{% endtab %}
{% endtabs %}

### <mark style="color:purple;">deepEqual</mark>

The function checks if two variables, `x` and `y`, are deeply equal by comparing their values and structures using `reflect.DeepEqual`.
Expand Down
23 changes: 23 additions & 0 deletions registry/reflect/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ func (rr *ReflectRegistry) KindOf(src any) string {
return reflect.ValueOf(src).Kind().String()
}

// HasField checks whether a struct has a field with a given name.
//
// Parameters:
//
// name string - the name of the field that is being checked.
// src any - the struct that is being checked.
//
// Returns:
//
// bool - true if the struct 'src' contains a field with the name 'name', false otherwise.
//
// Example:
//
// {{ hasField "someExistingField" .someStruct }} // Output: true
// {{ hasField "someNonExistingField" .someStruct }} // Output: false
func (rr *ReflectRegistry) HasField(name string, src any) bool {
rv := reflect.Indirect(reflect.ValueOf(src))
if rv.Kind() != reflect.Struct {
return false
}
return rv.FieldByName(name).IsValid()
}

// DeepEqual determines if two variables, 'x' and 'y', are deeply equal.
// It uses reflect.DeepEqual to evaluate equality.
//
Expand Down
23 changes: 23 additions & 0 deletions registry/reflect/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ func TestKindOf(t *testing.T) {
pesticide.RunTestCases(t, reflect.NewRegistry(), tc)
}

func TestHasField(t *testing.T) {
type A struct {
Foo string
}
type B struct {
Bar string
}

var tc = []pesticide.TestCase{
{Name: "TestHasFieldStructPointerTrue", Input: `{{ .V |hasField "Foo" }}`, Expected: "true", Data: map[string]any{"V": &A{Foo: "bar"}}},
{Name: "TestHasFieldStructPointerFalse", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": &B{Bar: "boo"}}},
{Name: "TestHasFieldStructTrue", Input: `{{ .V |hasField "Foo" }}`, Expected: "true", Data: map[string]any{"V": A{Foo: "bar"}}},
{Name: "TestHasFieldStructFalse", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": B{Bar: "boo"}}},
{Name: "TestHasFieldInt", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": 123}},
{Name: "TestHasFieldMap", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": map[string]string{"Foo": "bar"}}},
{Name: "TestHasFieldSlice", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": []int{1, 2, 3}}},
{Name: "TestHasFieldString", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": "foobar"}},
{Name: "TestHasFieldNil", Input: `{{ .V |hasField "Foo" }}`, Expected: "false", Data: map[string]any{"V": nil}},
}

pesticide.RunTestCases(t, reflect.NewRegistry(), tc)
}

func TestDeepEqual(t *testing.T) {
var tc = []pesticide.TestCase{
{Name: "TestDeepEqualInt", Input: `{{deepEqual 42 42}}`, Expected: "true"},
Expand Down
19 changes: 10 additions & 9 deletions registry/reflect/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ func (rr *ReflectRegistry) LinkHandler(fh sprout.Handler) error {
}

// RegisterFunctions registers all functions of the registry.
func (nr *ReflectRegistry) RegisterFunctions(funcsMap sprout.FunctionMap) error {
sprout.AddFunction(funcsMap, "typeIs", nr.TypeIs)
sprout.AddFunction(funcsMap, "typeIsLike", nr.TypeIsLike)
sprout.AddFunction(funcsMap, "typeOf", nr.TypeOf)
sprout.AddFunction(funcsMap, "kindIs", nr.KindIs)
sprout.AddFunction(funcsMap, "kindOf", nr.KindOf)
sprout.AddFunction(funcsMap, "deepEqual", nr.DeepEqual)
sprout.AddFunction(funcsMap, "deepCopy", nr.DeepCopy)
sprout.AddFunction(funcsMap, "mustDeepCopy", nr.MustDeepCopy)
func (rr *ReflectRegistry) RegisterFunctions(funcsMap sprout.FunctionMap) error {
sprout.AddFunction(funcsMap, "typeIs", rr.TypeIs)
sprout.AddFunction(funcsMap, "typeIsLike", rr.TypeIsLike)
sprout.AddFunction(funcsMap, "typeOf", rr.TypeOf)
sprout.AddFunction(funcsMap, "kindIs", rr.KindIs)
sprout.AddFunction(funcsMap, "kindOf", rr.KindOf)
sprout.AddFunction(funcsMap, "hasField", rr.HasField)
sprout.AddFunction(funcsMap, "deepEqual", rr.DeepEqual)
sprout.AddFunction(funcsMap, "deepCopy", rr.DeepCopy)
sprout.AddFunction(funcsMap, "mustDeepCopy", rr.MustDeepCopy)
return nil
}

0 comments on commit 44ae526

Please sign in to comment.