1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-02-02 11:24:29 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Dr. Carsten Leue
2374d7f1e4 fix: support unexported fields for lenses
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-01-23 16:18:44 +01:00
11 changed files with 569 additions and 74 deletions

View File

@@ -536,9 +536,9 @@ func extractEmbeddedFields(embedType ast.Expr, fileImports map[string]string, fi
}
for _, name := range field.Names {
// Only export lenses for exported fields
if name.IsExported() {
fieldTypeName := getTypeName(field.Type)
// Generate lenses for both exported and unexported fields
fieldTypeName := getTypeName(field.Type)
if true { // Keep the block structure for minimal changes
isOptional := false
baseType := fieldTypeName
@@ -698,9 +698,9 @@ func parseFile(filename string) ([]structInfo, string, error) {
continue
}
for _, name := range field.Names {
// Only export lenses for exported fields
if name.IsExported() {
typeName := getTypeName(field.Type)
// Generate lenses for both exported and unexported fields
typeName := getTypeName(field.Type)
if true { // Keep the block structure for minimal changes
isOptional := false
baseType := typeName
isComparable := false

View File

@@ -1086,3 +1086,255 @@ type ComparableBox[T comparable] struct {
// Verify that MakeLensRef is NOT used (since both fields are comparable)
assert.NotContains(t, contentStr, "__lens.MakeLensRefWithName(", "Should not use MakeLensRefWithName when all fields are comparable")
}
func TestParseFileWithUnexportedFields(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type Config struct {
PublicName string
privateName string
PublicValue int
privateValue *int
}
`
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check Config struct
config := structs[0]
assert.Equal(t, "Config", config.Name)
assert.Len(t, config.Fields, 4, "Should include both exported and unexported fields")
// Check exported field
assert.Equal(t, "PublicName", config.Fields[0].Name)
assert.Equal(t, "string", config.Fields[0].TypeName)
assert.False(t, config.Fields[0].IsOptional)
// Check unexported field
assert.Equal(t, "privateName", config.Fields[1].Name)
assert.Equal(t, "string", config.Fields[1].TypeName)
assert.False(t, config.Fields[1].IsOptional)
// Check exported int field
assert.Equal(t, "PublicValue", config.Fields[2].Name)
assert.Equal(t, "int", config.Fields[2].TypeName)
assert.False(t, config.Fields[2].IsOptional)
// Check unexported pointer field
assert.Equal(t, "privateValue", config.Fields[3].Name)
assert.Equal(t, "*int", config.Fields[3].TypeName)
assert.True(t, config.Fields[3].IsOptional)
}
func TestGenerateLensHelpersWithUnexportedFields(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
// fp-go:Lens
type MixedStruct struct {
PublicField string
privateField int
OptionalPrivate *string
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Generate lens code
outputFile := "gen_lens.go"
err = generateLensHelpers(tmpDir, outputFile, false, false)
require.NoError(t, err)
// Verify the generated file exists
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
require.NoError(t, err)
// Read and verify the generated content
content, err := os.ReadFile(genPath)
require.NoError(t, err)
contentStr := string(content)
// Check for expected content
assert.Contains(t, contentStr, "package testpkg")
assert.Contains(t, contentStr, "MixedStructLenses")
assert.Contains(t, contentStr, "MakeMixedStructLenses")
// Check that lenses are generated for all fields (exported and unexported)
assert.Contains(t, contentStr, "PublicField __lens.Lens[MixedStruct, string]")
assert.Contains(t, contentStr, "privateField __lens.Lens[MixedStruct, int]")
assert.Contains(t, contentStr, "OptionalPrivate __lens.Lens[MixedStruct, *string]")
// Check lens constructors
assert.Contains(t, contentStr, "func(s MixedStruct) string { return s.PublicField }")
assert.Contains(t, contentStr, "func(s MixedStruct) int { return s.privateField }")
assert.Contains(t, contentStr, "func(s MixedStruct) *string { return s.OptionalPrivate }")
// Check setters
assert.Contains(t, contentStr, "func(s MixedStruct, v string) MixedStruct { s.PublicField = v; return s }")
assert.Contains(t, contentStr, "func(s MixedStruct, v int) MixedStruct { s.privateField = v; return s }")
assert.Contains(t, contentStr, "func(s MixedStruct, v *string) MixedStruct { s.OptionalPrivate = v; return s }")
}
func TestParseFileWithOnlyUnexportedFields(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type PrivateConfig struct {
name string
value int
enabled bool
}
`
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check PrivateConfig struct
config := structs[0]
assert.Equal(t, "PrivateConfig", config.Name)
assert.Len(t, config.Fields, 3, "Should include all unexported fields")
// Check all fields are unexported
assert.Equal(t, "name", config.Fields[0].Name)
assert.Equal(t, "value", config.Fields[1].Name)
assert.Equal(t, "enabled", config.Fields[2].Name)
}
func TestGenerateLensHelpersWithUnexportedEmbeddedFields(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
type BaseConfig struct {
publicBase string
privateBase int
}
// fp-go:Lens
type ExtendedConfig struct {
BaseConfig
PublicField string
privateField bool
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Generate lens code
outputFile := "gen_lens.go"
err = generateLensHelpers(tmpDir, outputFile, false, false)
require.NoError(t, err)
// Verify the generated file exists
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
require.NoError(t, err)
// Read and verify the generated content
content, err := os.ReadFile(genPath)
require.NoError(t, err)
contentStr := string(content)
// Check for expected content
assert.Contains(t, contentStr, "package testpkg")
assert.Contains(t, contentStr, "ExtendedConfigLenses")
// Check that lenses are generated for embedded unexported fields
assert.Contains(t, contentStr, "publicBase __lens.Lens[ExtendedConfig, string]")
assert.Contains(t, contentStr, "privateBase __lens.Lens[ExtendedConfig, int]")
// Check that lenses are generated for direct fields (both exported and unexported)
assert.Contains(t, contentStr, "PublicField __lens.Lens[ExtendedConfig, string]")
assert.Contains(t, contentStr, "privateField __lens.Lens[ExtendedConfig, bool]")
}
func TestParseFileWithMixedFieldVisibility(t *testing.T) {
// Create a temporary test file with various field visibility patterns
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type ComplexStruct struct {
// Exported fields
Name string
Age int
Email *string
// Unexported fields
password string
secretKey []byte
internalID *int
// Mixed with tags
PublicWithTag string ` + "`json:\"public,omitempty\"`" + `
privateWithTag int ` + "`json:\"private,omitempty\"`" + `
}
`
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check ComplexStruct
complex := structs[0]
assert.Equal(t, "ComplexStruct", complex.Name)
assert.Len(t, complex.Fields, 8, "Should include all fields regardless of visibility")
// Verify field names and types
fieldNames := []string{"Name", "Age", "Email", "password", "secretKey", "internalID", "PublicWithTag", "privateWithTag"}
for i, expectedName := range fieldNames {
assert.Equal(t, expectedName, complex.Fields[i].Name, "Field %d should be %s", i, expectedName)
}
// Check optional fields
assert.False(t, complex.Fields[0].IsOptional, "Name should not be optional")
assert.True(t, complex.Fields[2].IsOptional, "Email (pointer) should be optional")
assert.True(t, complex.Fields[5].IsOptional, "internalID (pointer) should be optional")
assert.True(t, complex.Fields[6].IsOptional, "PublicWithTag (with omitempty) should be optional")
assert.True(t, complex.Fields[7].IsOptional, "privateWithTag (with omitempty) should be optional")
}

View File

@@ -230,8 +230,16 @@ type Person struct {
Email string
Phone *string // Optional field
}
// fp-go:Lens
type Config struct {
PublicField string
privateField int // Unexported fields are supported!
}
```
**Note:** The generator supports both exported (uppercase) and unexported (lowercase) fields. Generated lenses for unexported fields will have lowercase names and can only be used within the same package as the struct.
2. **Run `go generate`**:
```bash
@@ -268,6 +276,7 @@ The generator supports:
- ✅ Embedded structs (fields are promoted)
- ✅ Optional fields (pointers and `omitempty` tags)
- ✅ Custom package imports
-**Unexported fields** (lowercase names) - lenses will have lowercase names matching the field names
See [samples/lens](../samples/lens) for complete examples.

View File

@@ -90,7 +90,7 @@ var (
// Example:
// builder := WithName("Alice")
// person := builder(&PartialPerson{})
WithName = partialPersonLenses.Name.Set
WithName = partialPersonLenses.name.Set
// WithAge is a builder function that sets the Age field of a PartialPerson.
// It returns an endomorphism that can be composed with other builder operations.
@@ -98,7 +98,7 @@ var (
// Example:
// builder := WithAge(25)
// person := builder(&PartialPerson{})
WithAge = partialPersonLenses.Age.Set
WithAge = partialPersonLenses.age.Set
// PersonPrism is a prism that converts between a builder pattern (Endomorphism[*PartialPerson])
// and a validated Person in both directions.
@@ -160,7 +160,7 @@ func buildPerson() ReaderOption[Endomorphism[*PartialPerson], *Person] {
// maybeName extracts the name from PartialPerson, validates it,
// and creates a setter for the Person's Name field if valid
maybeName := F.Flow3(
partialPersonLenses.Name.Get,
partialPersonLenses.name.Get,
namePrism.GetOption,
option.Map(personLenses.Name.Set),
)
@@ -168,7 +168,7 @@ func buildPerson() ReaderOption[Endomorphism[*PartialPerson], *Person] {
// maybeAge extracts the age from PartialPerson, validates it,
// and creates a setter for the Person's Age field if valid
maybeAge := F.Flow3(
partialPersonLenses.Age.Get,
partialPersonLenses.age.Get,
agePrism.GetOption,
option.Map(personLenses.Age.Set),
)
@@ -200,7 +200,7 @@ func buildEndomorphism() Reader[*Person, Endomorphism[*PartialPerson]] {
name := F.Flow3(
personLenses.Name.Get,
namePrism.ReverseGet,
partialPersonLenses.Name.Set,
partialPersonLenses.name.Set,
)
// age extracts the validated age, converts it to int,
@@ -208,7 +208,7 @@ func buildEndomorphism() Reader[*Person, Endomorphism[*PartialPerson]] {
age := F.Flow3(
personLenses.Age.Get,
agePrism.ReverseGet,
partialPersonLenses.Age.Set,
partialPersonLenses.age.Set,
)
// Combine the field extractors into a single builder

View File

@@ -81,7 +81,7 @@ func makePersonValidate() Validate[Endomorphism[*PartialPerson], *Person] {
// 2. Validate using nameCodec (ensures non-empty)
// 3. Map to a Person name setter if valid
valName := F.Flow3(
partialPersonLenses.Name.Get,
partialPersonLenses.name.Get,
nameCodec.Validate,
decode.Map[validation.Context](personLenses.Name.Set),
)
@@ -91,7 +91,7 @@ func makePersonValidate() Validate[Endomorphism[*PartialPerson], *Person] {
// 2. Validate using ageCodec (ensures >= 18)
// 3. Map to a Person age setter if valid
valAge := F.Flow3(
partialPersonLenses.Age.Get,
partialPersonLenses.age.Get,
ageCodec.Validate,
decode.Map[validation.Context](personLenses.Age.Set),
)

View File

@@ -151,8 +151,8 @@ func TestMakePersonCodec_Encode(t *testing.T) {
partial := builder(emptyPartialPerson)
// Assert
assert.Equal(t, "Eve", partial.Name)
assert.Equal(t, 28, partial.Age)
assert.Equal(t, "Eve", partial.name)
assert.Equal(t, 28, partial.age)
}
// TestMakePersonCodec_RoundTrip tests encoding and decoding round-trip

View File

@@ -2,7 +2,7 @@ package builder
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2026-01-23 12:56:01.1431839 +0100 CET m=+0.004353901
// 2026-01-23 16:15:30.703391 +0100 CET m=+0.003782501
import (
__lens "github.com/IBM/fp-go/v2/optics/lens"
@@ -15,105 +15,105 @@ import (
// PartialPersonLenses provides lenses for accessing fields of PartialPerson
type PartialPersonLenses struct {
// mandatory fields
Name __lens.Lens[PartialPerson, string]
Age __lens.Lens[PartialPerson, int]
name __lens.Lens[PartialPerson, string]
age __lens.Lens[PartialPerson, int]
// optional fields
NameO __lens_option.LensO[PartialPerson, string]
AgeO __lens_option.LensO[PartialPerson, int]
nameO __lens_option.LensO[PartialPerson, string]
ageO __lens_option.LensO[PartialPerson, int]
}
// PartialPersonRefLenses provides lenses for accessing fields of PartialPerson via a reference to PartialPerson
type PartialPersonRefLenses struct {
// mandatory fields
Name __lens.Lens[*PartialPerson, string]
Age __lens.Lens[*PartialPerson, int]
name __lens.Lens[*PartialPerson, string]
age __lens.Lens[*PartialPerson, int]
// optional fields
NameO __lens_option.LensO[*PartialPerson, string]
AgeO __lens_option.LensO[*PartialPerson, int]
nameO __lens_option.LensO[*PartialPerson, string]
ageO __lens_option.LensO[*PartialPerson, int]
// prisms
NameP __prism.Prism[*PartialPerson, string]
AgeP __prism.Prism[*PartialPerson, int]
nameP __prism.Prism[*PartialPerson, string]
ageP __prism.Prism[*PartialPerson, int]
}
// PartialPersonPrisms provides prisms for accessing fields of PartialPerson
type PartialPersonPrisms struct {
Name __prism.Prism[PartialPerson, string]
Age __prism.Prism[PartialPerson, int]
name __prism.Prism[PartialPerson, string]
age __prism.Prism[PartialPerson, int]
}
// MakePartialPersonLenses creates a new PartialPersonLenses with lenses for all fields
func MakePartialPersonLenses() PartialPersonLenses {
// mandatory lenses
lensName := __lens.MakeLensWithName(
func(s PartialPerson) string { return s.Name },
func(s PartialPerson, v string) PartialPerson { s.Name = v; return s },
"PartialPerson.Name",
lensname := __lens.MakeLensWithName(
func(s PartialPerson) string { return s.name },
func(s PartialPerson, v string) PartialPerson { s.name = v; return s },
"PartialPerson.name",
)
lensAge := __lens.MakeLensWithName(
func(s PartialPerson) int { return s.Age },
func(s PartialPerson, v int) PartialPerson { s.Age = v; return s },
"PartialPerson.Age",
lensage := __lens.MakeLensWithName(
func(s PartialPerson) int { return s.age },
func(s PartialPerson, v int) PartialPerson { s.age = v; return s },
"PartialPerson.age",
)
// optional lenses
lensNameO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[string]())(lensName)
lensAgeO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[int]())(lensAge)
lensnameO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[string]())(lensname)
lensageO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[int]())(lensage)
return PartialPersonLenses{
// mandatory lenses
Name: lensName,
Age: lensAge,
name: lensname,
age: lensage,
// optional lenses
NameO: lensNameO,
AgeO: lensAgeO,
nameO: lensnameO,
ageO: lensageO,
}
}
// MakePartialPersonRefLenses creates a new PartialPersonRefLenses with lenses for all fields
func MakePartialPersonRefLenses() PartialPersonRefLenses {
// mandatory lenses
lensName := __lens.MakeLensStrictWithName(
func(s *PartialPerson) string { return s.Name },
func(s *PartialPerson, v string) *PartialPerson { s.Name = v; return s },
"(*PartialPerson).Name",
lensname := __lens.MakeLensStrictWithName(
func(s *PartialPerson) string { return s.name },
func(s *PartialPerson, v string) *PartialPerson { s.name = v; return s },
"(*PartialPerson).name",
)
lensAge := __lens.MakeLensStrictWithName(
func(s *PartialPerson) int { return s.Age },
func(s *PartialPerson, v int) *PartialPerson { s.Age = v; return s },
"(*PartialPerson).Age",
lensage := __lens.MakeLensStrictWithName(
func(s *PartialPerson) int { return s.age },
func(s *PartialPerson, v int) *PartialPerson { s.age = v; return s },
"(*PartialPerson).age",
)
// optional lenses
lensNameO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[string]())(lensName)
lensAgeO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[int]())(lensAge)
lensnameO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[string]())(lensname)
lensageO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[int]())(lensage)
return PartialPersonRefLenses{
// mandatory lenses
Name: lensName,
Age: lensAge,
name: lensname,
age: lensage,
// optional lenses
NameO: lensNameO,
AgeO: lensAgeO,
nameO: lensnameO,
ageO: lensageO,
}
}
// MakePartialPersonPrisms creates a new PartialPersonPrisms with prisms for all fields
func MakePartialPersonPrisms() PartialPersonPrisms {
_fromNonZeroName := __option.FromNonZero[string]()
_prismName := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[string] { return _fromNonZeroName(s.Name) },
_fromNonZeroname := __option.FromNonZero[string]()
_prismname := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[string] { return _fromNonZeroname(s.name) },
func(v string) PartialPerson {
return PartialPerson{ Name: v }
return PartialPerson{ name: v }
},
"PartialPerson.Name",
"PartialPerson.name",
)
_fromNonZeroAge := __option.FromNonZero[int]()
_prismAge := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[int] { return _fromNonZeroAge(s.Age) },
_fromNonZeroage := __option.FromNonZero[int]()
_prismage := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[int] { return _fromNonZeroage(s.age) },
func(v int) PartialPerson {
return PartialPerson{ Age: v }
return PartialPerson{ age: v }
},
"PartialPerson.Age",
"PartialPerson.age",
)
return PartialPersonPrisms {
Name: _prismName,
Age: _prismAge,
name: _prismname,
age: _prismage,
}
}

View File

@@ -64,11 +64,11 @@ type (
//
// fp-go:Lens
type PartialPerson struct {
// Name is the person's name as a raw string, which may be empty or invalid.
Name string
// name is the person's name as a raw string, which may be empty or invalid.
name string
// Age is the person's age as a raw integer, which may be negative or otherwise invalid.
Age int
// age is the person's age as a raw integer, which may be negative or otherwise invalid.
age int
}
// Person represents a person record with validated fields.

View File

@@ -64,3 +64,9 @@ type WithGeneric[T any] struct {
Name string
Value T
}
// fp-go:Lens
type DataBuilder struct {
name string
value string
}

View File

@@ -21,6 +21,7 @@ import (
F "github.com/IBM/fp-go/v2/function"
L "github.com/IBM/fp-go/v2/optics/lens"
O "github.com/IBM/fp-go/v2/option"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
@@ -341,3 +342,125 @@ func TestCompanyRefLensesOptionalIdempotent(t *testing.T) {
assert.Equal(t, &newWebsiteValue, differentWebsite.Website)
assert.Equal(t, &websiteValue, company.Website, "Original should be unchanged")
}
func TestDataBuilderLensWithUnexportedFields(t *testing.T) {
// Test that lenses can access and modify unexported fields
// This demonstrates that the lens generator now supports unexported fields
// Create a DataBuilder with unexported fields
builder := DataBuilder{
name: "initial-name",
value: "initial-value",
}
// Create lenses
lenses := MakeDataBuilderLenses()
// Test Get on unexported fields
assert.Equal(t, "initial-name", lenses.name.Get(builder))
assert.Equal(t, "initial-value", lenses.value.Get(builder))
// Test Set on unexported fields
updatedName := lenses.name.Set("updated-name")(builder)
assert.Equal(t, "updated-name", updatedName.name)
assert.Equal(t, "initial-value", updatedName.value) // Other field unchanged
assert.Equal(t, "initial-name", builder.name) // Original unchanged
updatedValue := lenses.value.Set("updated-value")(builder)
assert.Equal(t, "initial-name", updatedValue.name) // Other field unchanged
assert.Equal(t, "updated-value", updatedValue.value)
assert.Equal(t, "initial-value", builder.value) // Original unchanged
// Test Modify on unexported fields
modifyName := F.Pipe1(
lenses.name,
L.Modify[DataBuilder](S.Append("-modified")),
)
modified := modifyName(builder)
assert.Equal(t, "initial-name-modified", modified.name)
assert.Equal(t, "initial-name", builder.name) // Original unchanged
// Test composition of modifications
updatedBoth := F.Pipe2(
builder,
lenses.name.Set("new-name"),
lenses.value.Set("new-value"),
)
assert.Equal(t, "new-name", updatedBoth.name)
assert.Equal(t, "new-value", updatedBoth.value)
assert.Equal(t, "initial-name", builder.name) // Original unchanged
assert.Equal(t, "initial-value", builder.value) // Original unchanged
}
func TestDataBuilderRefLensesWithUnexportedFields(t *testing.T) {
// Test that ref lenses work with unexported fields and maintain idempotency
builder := &DataBuilder{
name: "test-name",
value: "test-value",
}
refLenses := MakeDataBuilderRefLenses()
// Test Get on unexported fields
assert.Equal(t, "test-name", refLenses.name.Get(builder))
assert.Equal(t, "test-value", refLenses.value.Get(builder))
// Test idempotency - setting same value should return same pointer
sameName := refLenses.name.Set("test-name")(builder)
assert.Same(t, builder, sameName, "Setting name to same value should return identical pointer")
sameValue := refLenses.value.Set("test-value")(builder)
assert.Same(t, builder, sameValue, "Setting value to same value should return identical pointer")
// Test that setting different value creates new pointer
differentName := refLenses.name.Set("different-name")(builder)
assert.NotSame(t, builder, differentName, "Setting name to different value should return new pointer")
assert.Equal(t, "different-name", differentName.name)
assert.Equal(t, "test-name", builder.name, "Original should be unchanged")
differentValue := refLenses.value.Set("different-value")(builder)
assert.NotSame(t, builder, differentValue, "Setting value to different value should return new pointer")
assert.Equal(t, "different-value", differentValue.value)
assert.Equal(t, "test-value", builder.value, "Original should be unchanged")
}
func TestDataBuilderOptionalLensesWithUnexportedFields(t *testing.T) {
// Test optional lenses (LensO) with unexported fields
builder := DataBuilder{
name: "test",
value: "data",
}
lenses := MakeDataBuilderLenses()
// Test getting non-zero values as Some
nameOpt := lenses.nameO.Get(builder)
assert.True(t, O.IsSome(nameOpt))
assert.Equal(t, "test", O.GetOrElse(F.Zero[string])(nameOpt))
valueOpt := lenses.valueO.Get(builder)
assert.True(t, O.IsSome(valueOpt))
assert.Equal(t, "data", O.GetOrElse(F.Zero[string])(valueOpt))
// Test setting to Some
updatedName := lenses.nameO.Set(O.Some("new-test"))(builder)
assert.Equal(t, "new-test", updatedName.name)
// Test setting to None (zero value for string is "")
clearedName := lenses.nameO.Set(O.None[string]())(builder)
assert.Equal(t, "", clearedName.name)
// Test with zero value
emptyBuilder := DataBuilder{
name: "",
value: "",
}
emptyNameOpt := lenses.nameO.Get(emptyBuilder)
assert.True(t, O.IsNone(emptyNameOpt), "Empty string should be None")
emptyValueOpt := lenses.valueO.Get(emptyBuilder)
assert.True(t, O.IsNone(emptyValueOpt), "Empty string should be None")
}

View File

@@ -2,7 +2,7 @@ package lens
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-12-16 15:41:28.7198645 +0100 CET m=+0.003889201
// 2026-01-23 16:09:35.747264 +0100 CET m=+0.003865601
import (
__lens "github.com/IBM/fp-go/v2/optics/lens"
@@ -946,3 +946,108 @@ func MakeWithGenericPrisms[T any]() WithGenericPrisms[T] {
Value: _prismValue,
}
}
// DataBuilderLenses provides lenses for accessing fields of DataBuilder
type DataBuilderLenses struct {
// mandatory fields
name __lens.Lens[DataBuilder, string]
value __lens.Lens[DataBuilder, string]
// optional fields
nameO __lens_option.LensO[DataBuilder, string]
valueO __lens_option.LensO[DataBuilder, string]
}
// DataBuilderRefLenses provides lenses for accessing fields of DataBuilder via a reference to DataBuilder
type DataBuilderRefLenses struct {
// mandatory fields
name __lens.Lens[*DataBuilder, string]
value __lens.Lens[*DataBuilder, string]
// optional fields
nameO __lens_option.LensO[*DataBuilder, string]
valueO __lens_option.LensO[*DataBuilder, string]
// prisms
nameP __prism.Prism[*DataBuilder, string]
valueP __prism.Prism[*DataBuilder, string]
}
// DataBuilderPrisms provides prisms for accessing fields of DataBuilder
type DataBuilderPrisms struct {
name __prism.Prism[DataBuilder, string]
value __prism.Prism[DataBuilder, string]
}
// MakeDataBuilderLenses creates a new DataBuilderLenses with lenses for all fields
func MakeDataBuilderLenses() DataBuilderLenses {
// mandatory lenses
lensname := __lens.MakeLensWithName(
func(s DataBuilder) string { return s.name },
func(s DataBuilder, v string) DataBuilder { s.name = v; return s },
"DataBuilder.name",
)
lensvalue := __lens.MakeLensWithName(
func(s DataBuilder) string { return s.value },
func(s DataBuilder, v string) DataBuilder { s.value = v; return s },
"DataBuilder.value",
)
// optional lenses
lensnameO := __lens_option.FromIso[DataBuilder](__iso_option.FromZero[string]())(lensname)
lensvalueO := __lens_option.FromIso[DataBuilder](__iso_option.FromZero[string]())(lensvalue)
return DataBuilderLenses{
// mandatory lenses
name: lensname,
value: lensvalue,
// optional lenses
nameO: lensnameO,
valueO: lensvalueO,
}
}
// MakeDataBuilderRefLenses creates a new DataBuilderRefLenses with lenses for all fields
func MakeDataBuilderRefLenses() DataBuilderRefLenses {
// mandatory lenses
lensname := __lens.MakeLensStrictWithName(
func(s *DataBuilder) string { return s.name },
func(s *DataBuilder, v string) *DataBuilder { s.name = v; return s },
"(*DataBuilder).name",
)
lensvalue := __lens.MakeLensStrictWithName(
func(s *DataBuilder) string { return s.value },
func(s *DataBuilder, v string) *DataBuilder { s.value = v; return s },
"(*DataBuilder).value",
)
// optional lenses
lensnameO := __lens_option.FromIso[*DataBuilder](__iso_option.FromZero[string]())(lensname)
lensvalueO := __lens_option.FromIso[*DataBuilder](__iso_option.FromZero[string]())(lensvalue)
return DataBuilderRefLenses{
// mandatory lenses
name: lensname,
value: lensvalue,
// optional lenses
nameO: lensnameO,
valueO: lensvalueO,
}
}
// MakeDataBuilderPrisms creates a new DataBuilderPrisms with prisms for all fields
func MakeDataBuilderPrisms() DataBuilderPrisms {
_fromNonZeroname := __option.FromNonZero[string]()
_prismname := __prism.MakePrismWithName(
func(s DataBuilder) __option.Option[string] { return _fromNonZeroname(s.name) },
func(v string) DataBuilder {
return DataBuilder{ name: v }
},
"DataBuilder.name",
)
_fromNonZerovalue := __option.FromNonZero[string]()
_prismvalue := __prism.MakePrismWithName(
func(s DataBuilder) __option.Option[string] { return _fromNonZerovalue(s.value) },
func(v string) DataBuilder {
return DataBuilder{ value: v }
},
"DataBuilder.value",
)
return DataBuilderPrisms {
name: _prismname,
value: _prismvalue,
}
}