mirror of
https://github.com/IBM/fp-go.git
synced 2026-02-02 11:24:29 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2374d7f1e4 |
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -64,3 +64,9 @@ type WithGeneric[T any] struct {
|
||||
Name string
|
||||
Value T
|
||||
}
|
||||
|
||||
// fp-go:Lens
|
||||
type DataBuilder struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user