mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-15 23:33:46 +02:00
711 lines
22 KiB
Go
711 lines
22 KiB
Go
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package assert provides functional assertion helpers for testing.
|
|
//
|
|
// This package wraps testify/assert functions in a Reader monad pattern,
|
|
// allowing for composable and functional test assertions. Each assertion
|
|
// returns a Reader that takes a *testing.T and performs the assertion.
|
|
//
|
|
// # Data Last Principle
|
|
//
|
|
// This package follows the "data last" functional programming principle, where
|
|
// the data being operated on comes as the last parameter in a chain of function
|
|
// applications. This design enables several powerful functional programming patterns:
|
|
//
|
|
// 1. **Partial Application**: You can create reusable assertion functions by providing
|
|
// configuration parameters first, leaving the data and testing context for later.
|
|
//
|
|
// 2. **Function Composition**: Assertions can be composed and combined before being
|
|
// applied to actual data.
|
|
//
|
|
// 3. **Point-Free Style**: You can pass assertion functions around without immediately
|
|
// providing the data they operate on.
|
|
//
|
|
// The general pattern is:
|
|
//
|
|
// assert.Function(config)(data)(testingContext)
|
|
// ↑ ↑ ↑
|
|
// expected actual *testing.T (always last)
|
|
//
|
|
// For single-parameter assertions:
|
|
//
|
|
// assert.Function(data)(testingContext)
|
|
// ↑ ↑
|
|
// actual *testing.T (always last)
|
|
//
|
|
// Examples of "data last" in action:
|
|
//
|
|
// // Multi-parameter: expected value → actual value → testing context
|
|
// assert.Equal(42)(result)(t)
|
|
// assert.ArrayContains(3)(numbers)(t)
|
|
//
|
|
// // Single-parameter: data → testing context
|
|
// assert.NoError(err)(t)
|
|
// assert.ArrayNotEmpty(arr)(t)
|
|
//
|
|
// // Partial application - create reusable assertions
|
|
// isPositive := assert.That(func(n int) bool { return n > 0 })
|
|
// // Later, apply to different values:
|
|
// isPositive(42)(t) // Passes
|
|
// isPositive(-5)(t) // Fails
|
|
//
|
|
// // Composition - combine assertions before applying data
|
|
// validateUser := func(u User) assert.Reader {
|
|
// return assert.AllOf([]assert.Reader{
|
|
// assert.Equal("Alice")(u.Name),
|
|
// assert.That(func(age int) bool { return age >= 18 })(u.Age),
|
|
// })
|
|
// }
|
|
// validateUser(user)(t)
|
|
//
|
|
// The package supports:
|
|
// - Equality and inequality assertions
|
|
// - Collection assertions (arrays, maps, strings)
|
|
// - Error handling assertions
|
|
// - Result type assertions
|
|
// - Custom predicate assertions
|
|
// - Composable test suites
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestExample(t *testing.T) {
|
|
// value := 42
|
|
// assert.Equal(42)(value)(t) // Curried style
|
|
//
|
|
// // Composing multiple assertions
|
|
// arr := []int{1, 2, 3}
|
|
// assertions := assert.AllOf([]assert.Reader{
|
|
// assert.ArrayNotEmpty(arr),
|
|
// assert.ArrayLength[int](3)(arr),
|
|
// assert.ArrayContains(2)(arr),
|
|
// })
|
|
// assertions(t)
|
|
// }
|
|
package assert
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/IBM/fp-go/v2/boolean"
|
|
"github.com/IBM/fp-go/v2/eq"
|
|
"github.com/IBM/fp-go/v2/option"
|
|
"github.com/IBM/fp-go/v2/reader"
|
|
"github.com/IBM/fp-go/v2/result"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
var (
|
|
// Eq is the equal predicate checking if objects are equal
|
|
Eq = eq.FromEquals(assert.ObjectsAreEqual)
|
|
)
|
|
|
|
// wrap1 is an internal helper function that wraps testify assertion functions
|
|
// into the Reader monad pattern with curried parameters.
|
|
//
|
|
// It takes a testify assertion function and converts it into a curried function
|
|
// that first takes an expected value, then an actual value, and finally returns
|
|
// a Reader that performs the assertion when given a *testing.T.
|
|
//
|
|
// Parameters:
|
|
// - wrapped: The testify assertion function to wrap
|
|
// - expected: The expected value for comparison
|
|
// - msgAndArgs: Optional message and arguments for assertion failure
|
|
//
|
|
// Returns:
|
|
// - A Kleisli function that takes the actual value and returns a Reader
|
|
func wrap1[T any](wrapped func(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool, expected T, msgAndArgs ...any) Kleisli[T] {
|
|
return func(actual T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return wrapped(t, expected, actual, msgAndArgs...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// NotEqual tests if the expected and the actual values are not equal.
|
|
//
|
|
// This function follows the "data last" principle - you provide the expected value first,
|
|
// then the actual value, and finally the testing.T context.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestNotEqual(t *testing.T) {
|
|
// value := 42
|
|
// assert.NotEqual(10)(value)(t) // Passes: 42 != 10
|
|
// assert.NotEqual(42)(value)(t) // Fails: 42 == 42
|
|
// }
|
|
func NotEqual[T any](expected T) Kleisli[T] {
|
|
return wrap1(assert.NotEqual, expected)
|
|
}
|
|
|
|
// Equal tests if the expected and the actual values are equal.
|
|
//
|
|
// This is one of the most commonly used assertions. It follows the "data last" principle -
|
|
// you provide the expected value first, then the actual value, and finally the testing.T context.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestEqual(t *testing.T) {
|
|
// result := 2 + 2
|
|
// assert.Equal(4)(result)(t) // Passes
|
|
//
|
|
// name := "Alice"
|
|
// assert.Equal("Alice")(name)(t) // Passes
|
|
//
|
|
// // Can be composed with other assertions
|
|
// user := User{Name: "Bob", Age: 30}
|
|
// assertions := assert.AllOf([]assert.Reader{
|
|
// assert.Equal("Bob")(user.Name),
|
|
// assert.Equal(30)(user.Age),
|
|
// })
|
|
// assertions(t)
|
|
// }
|
|
func Equal[T any](expected T) Kleisli[T] {
|
|
return wrap1(assert.Equal, expected)
|
|
}
|
|
|
|
// ArrayNotEmpty checks if an array is not empty.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestArrayNotEmpty(t *testing.T) {
|
|
// numbers := []int{1, 2, 3}
|
|
// assert.ArrayNotEmpty(numbers)(t) // Passes
|
|
//
|
|
// empty := []int{}
|
|
// assert.ArrayNotEmpty(empty)(t) // Fails
|
|
// }
|
|
func ArrayNotEmpty[T any](arr []T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.NotEmpty(t, arr)
|
|
}
|
|
}
|
|
|
|
// RecordNotEmpty checks if a map is not empty.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestRecordNotEmpty(t *testing.T) {
|
|
// config := map[string]int{"timeout": 30, "retries": 3}
|
|
// assert.RecordNotEmpty(config)(t) // Passes
|
|
//
|
|
// empty := map[string]int{}
|
|
// assert.RecordNotEmpty(empty)(t) // Fails
|
|
// }
|
|
func RecordNotEmpty[K comparable, T any](mp map[K]T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.NotEmpty(t, mp)
|
|
}
|
|
}
|
|
|
|
// StringNotEmpty checks if a string is not empty.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestStringNotEmpty(t *testing.T) {
|
|
// message := "Hello, World!"
|
|
// assert.StringNotEmpty(message)(t) // Passes
|
|
//
|
|
// empty := ""
|
|
// assert.StringNotEmpty(empty)(t) // Fails
|
|
// }
|
|
func StringNotEmpty(s string) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.NotEmpty(t, s)
|
|
}
|
|
}
|
|
|
|
// ArrayLength tests if an array has the expected length.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestArrayLength(t *testing.T) {
|
|
// numbers := []int{1, 2, 3, 4, 5}
|
|
// assert.ArrayLength[int](5)(numbers)(t) // Passes
|
|
// assert.ArrayLength[int](3)(numbers)(t) // Fails
|
|
// }
|
|
func ArrayLength[T any](expected int) Kleisli[[]T] {
|
|
return func(actual []T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.Len(t, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RecordLength tests if a map has the expected length.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestRecordLength(t *testing.T) {
|
|
// config := map[string]string{"host": "localhost", "port": "8080"}
|
|
// assert.RecordLength[string, string](2)(config)(t) // Passes
|
|
// assert.RecordLength[string, string](3)(config)(t) // Fails
|
|
// }
|
|
func RecordLength[K comparable, T any](expected int) Kleisli[map[K]T] {
|
|
return func(actual map[K]T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.Len(t, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// StringLength tests if a string has the expected length.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestStringLength(t *testing.T) {
|
|
// message := "Hello"
|
|
// assert.StringLength[any, any](5)(message)(t) // Passes
|
|
// assert.StringLength[any, any](10)(message)(t) // Fails
|
|
// }
|
|
func StringLength[K comparable, T any](expected int) Kleisli[string] {
|
|
return func(actual string) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.Len(t, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// NoError validates that there is no error.
|
|
//
|
|
// This is commonly used to assert that operations complete successfully.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestNoError(t *testing.T) {
|
|
// err := doSomething()
|
|
// assert.NoError(err)(t) // Passes if err is nil
|
|
//
|
|
// // Can be used with result types
|
|
// result := result.TryCatch(func() (int, error) {
|
|
// return 42, nil
|
|
// })
|
|
// assert.Success(result)(t) // Uses NoError internally
|
|
// }
|
|
func NoError(err error) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
// Error validates that there is an error.
|
|
//
|
|
// This is used to assert that operations fail as expected.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestError(t *testing.T) {
|
|
// err := validateInput("")
|
|
// assert.Error(err)(t) // Passes if err is not nil
|
|
//
|
|
// err2 := validateInput("valid")
|
|
// assert.Error(err2)(t) // Fails if err2 is nil
|
|
// }
|
|
func Error(err error) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.Error(t, err)
|
|
}
|
|
}
|
|
|
|
// Success checks if a [Result] represents success.
|
|
//
|
|
// This is a convenience function for testing Result types from the fp-go library.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestSuccess(t *testing.T) {
|
|
// res := result.Of[int](42)
|
|
// assert.Success(res)(t) // Passes
|
|
//
|
|
// failedRes := result.Error[int](errors.New("failed"))
|
|
// assert.Success(failedRes)(t) // Fails
|
|
// }
|
|
func Success[T any](res Result[T]) Reader {
|
|
return NoError(result.ToError(res))
|
|
}
|
|
|
|
// Failure checks if a [Result] represents failure.
|
|
//
|
|
// This is a convenience function for testing Result types from the fp-go library.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestFailure(t *testing.T) {
|
|
// res := result.Error[int](errors.New("something went wrong"))
|
|
// assert.Failure(res)(t) // Passes
|
|
//
|
|
// successRes := result.Of[int](42)
|
|
// assert.Failure(successRes)(t) // Fails
|
|
// }
|
|
func Failure[T any](res Result[T]) Reader {
|
|
return Error(result.ToError(res))
|
|
}
|
|
|
|
// ArrayContains tests if a value is contained in an array.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestArrayContains(t *testing.T) {
|
|
// numbers := []int{1, 2, 3, 4, 5}
|
|
// assert.ArrayContains(3)(numbers)(t) // Passes
|
|
// assert.ArrayContains(10)(numbers)(t) // Fails
|
|
//
|
|
// names := []string{"Alice", "Bob", "Charlie"}
|
|
// assert.ArrayContains("Bob")(names)(t) // Passes
|
|
// }
|
|
func ArrayContains[T any](expected T) Kleisli[[]T] {
|
|
return func(actual []T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.Contains(t, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ContainsKey tests if a key is contained in a map.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestContainsKey(t *testing.T) {
|
|
// config := map[string]int{"timeout": 30, "retries": 3}
|
|
// assert.ContainsKey[int]("timeout")(config)(t) // Passes
|
|
// assert.ContainsKey[int]("maxSize")(config)(t) // Fails
|
|
// }
|
|
func ContainsKey[T any, K comparable](expected K) Kleisli[map[K]T] {
|
|
return func(actual map[K]T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.Contains(t, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// NotContainsKey tests if a key is not contained in a map.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestNotContainsKey(t *testing.T) {
|
|
// config := map[string]int{"timeout": 30, "retries": 3}
|
|
// assert.NotContainsKey[int]("maxSize")(config)(t) // Passes
|
|
// assert.NotContainsKey[int]("timeout")(config)(t) // Fails
|
|
// }
|
|
func NotContainsKey[T any, K comparable](expected K) Kleisli[map[K]T] {
|
|
return func(actual map[K]T) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.NotContains(t, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// That asserts that a particular predicate matches.
|
|
//
|
|
// This is a powerful function that allows you to create custom assertions using predicates.
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestThat(t *testing.T) {
|
|
// // Test if a number is positive
|
|
// isPositive := func(n int) bool { return n > 0 }
|
|
// assert.That(isPositive)(42)(t) // Passes
|
|
// assert.That(isPositive)(-5)(t) // Fails
|
|
//
|
|
// // Test if a string is uppercase
|
|
// isUppercase := func(s string) bool { return s == strings.ToUpper(s) }
|
|
// assert.That(isUppercase)("HELLO")(t) // Passes
|
|
// assert.That(isUppercase)("Hello")(t) // Fails
|
|
//
|
|
// // Can be combined with Local for property testing
|
|
// type User struct { Age int }
|
|
// ageIsAdult := assert.Local(func(u User) int { return u.Age })(
|
|
// assert.That(func(age int) bool { return age >= 18 }),
|
|
// )
|
|
// user := User{Age: 25}
|
|
// ageIsAdult(user)(t) // Passes
|
|
// }
|
|
func That[T any](pred Predicate[T]) Kleisli[T] {
|
|
return func(a T) Reader {
|
|
return func(t *testing.T) bool {
|
|
if pred(a) {
|
|
return true
|
|
}
|
|
return assert.Fail(t, fmt.Sprintf("Preficate %v does not match value %v", pred, a))
|
|
}
|
|
}
|
|
}
|
|
|
|
// AllOf combines multiple assertion Readers into a single Reader that passes
|
|
// only if all assertions pass.
|
|
//
|
|
// This function uses boolean AND logic (MonoidAll) to combine the results of
|
|
// all assertions. If any assertion fails, the combined assertion fails.
|
|
//
|
|
// This is useful for grouping related assertions together and ensuring all
|
|
// conditions are met.
|
|
//
|
|
// Parameters:
|
|
// - readers: Array of assertion Readers to combine
|
|
//
|
|
// Returns:
|
|
// - A single Reader that performs all assertions and returns true only if all pass
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestUser(t *testing.T) {
|
|
// user := User{Name: "Alice", Age: 30, Active: true}
|
|
// assertions := assert.AllOf([]assert.Reader{
|
|
// assert.Equal("Alice")(user.Name),
|
|
// assert.Equal(30)(user.Age),
|
|
// assert.Equal(true)(user.Active),
|
|
// })
|
|
// assertions(t)
|
|
// }
|
|
//
|
|
//go:inline
|
|
func AllOf(readers []Reader) Reader {
|
|
return reader.MonadReduceArrayM(readers, boolean.MonoidAll)
|
|
}
|
|
|
|
// RunAll executes a map of named test cases, running each as a subtest.
|
|
//
|
|
// This function creates a Reader that runs multiple named test cases using
|
|
// Go's t.Run for proper test isolation and reporting. Each test case is
|
|
// executed as a separate subtest with its own name.
|
|
//
|
|
// The function returns true only if all subtests pass. This allows for
|
|
// better test organization and clearer test output.
|
|
//
|
|
// Parameters:
|
|
// - testcases: Map of test names to assertion Readers
|
|
//
|
|
// Returns:
|
|
// - A Reader that executes all named test cases and returns true if all pass
|
|
//
|
|
// Example:
|
|
//
|
|
// func TestMathOperations(t *testing.T) {
|
|
// testcases := map[string]assert.Reader{
|
|
// "addition": assert.Equal(4)(2 + 2),
|
|
// "multiplication": assert.Equal(6)(2 * 3),
|
|
// "subtraction": assert.Equal(1)(3 - 2),
|
|
// }
|
|
// assert.RunAll(testcases)(t)
|
|
// }
|
|
//
|
|
//go:inline
|
|
func RunAll(testcases map[string]Reader) Reader {
|
|
return func(t *testing.T) bool {
|
|
current := true
|
|
for k, r := range testcases {
|
|
current = current && t.Run(k, func(t1 *testing.T) {
|
|
r(t1)
|
|
})
|
|
}
|
|
return current
|
|
}
|
|
}
|
|
|
|
// Local transforms a Reader that works on type R1 into a Reader that works on type R2,
|
|
// by providing a function that converts R2 to R1. This allows you to focus a test on a
|
|
// specific property or subset of a larger data structure.
|
|
//
|
|
// This is particularly useful when you have an assertion that operates on a specific field
|
|
// or property, and you want to apply it to a complete object. Instead of extracting the
|
|
// property and then asserting on it, you can transform the assertion to work directly
|
|
// on the whole object.
|
|
//
|
|
// Parameters:
|
|
// - f: A function that extracts or transforms R2 into R1
|
|
//
|
|
// Returns:
|
|
// - A function that transforms a Reader[R1, Reader] into a Reader[R2, Reader]
|
|
//
|
|
// Example:
|
|
//
|
|
// type User struct {
|
|
// Name string
|
|
// Age int
|
|
// }
|
|
//
|
|
// // Create an assertion that checks if age is positive
|
|
// ageIsPositive := assert.That(func(age int) bool { return age > 0 })
|
|
//
|
|
// // Focus this assertion on the Age field of User
|
|
// userAgeIsPositive := assert.Local(func(u User) int { return u.Age })(ageIsPositive)
|
|
//
|
|
// // Now we can test the whole User object
|
|
// user := User{Name: "Alice", Age: 30}
|
|
// userAgeIsPositive(user)(t)
|
|
//
|
|
//go:inline
|
|
func Local[R1, R2 any](f func(R2) R1) func(Kleisli[R1]) Kleisli[R2] {
|
|
return reader.Local[Reader](f)
|
|
}
|
|
|
|
// LocalL is similar to Local but uses a Lens to focus on a specific property.
|
|
// A Lens is a functional programming construct that provides a composable way to
|
|
// focus on a part of a data structure.
|
|
//
|
|
// This function is particularly useful when you want to focus a test on a specific
|
|
// field of a struct using a lens, making the code more declarative and composable.
|
|
// Lenses are often code-generated or predefined for common data structures.
|
|
//
|
|
// Parameters:
|
|
// - l: A Lens that focuses from type S to type T
|
|
//
|
|
// Returns:
|
|
// - A function that transforms a Reader[T, Reader] into a Reader[S, Reader]
|
|
//
|
|
// Example:
|
|
//
|
|
// type Person struct {
|
|
// Name string
|
|
// Email string
|
|
// }
|
|
//
|
|
// // Assume we have a lens that focuses on the Email field
|
|
// var emailLens = lens.Prop[Person, string]("Email")
|
|
//
|
|
// // Create an assertion for email format
|
|
// validEmail := assert.That(func(email string) bool {
|
|
// return strings.Contains(email, "@")
|
|
// })
|
|
//
|
|
// // Focus this assertion on the Email property using a lens
|
|
// validPersonEmail := assert.LocalL(emailLens)(validEmail)
|
|
//
|
|
// // Test a Person object
|
|
// person := Person{Name: "Bob", Email: "bob@example.com"}
|
|
// validPersonEmail(person)(t)
|
|
//
|
|
//go:inline
|
|
func LocalL[S, T any](l Lens[S, T]) func(Kleisli[T]) Kleisli[S] {
|
|
return reader.Local[Reader](l.Get)
|
|
}
|
|
|
|
// fromOptionalGetter is an internal helper that creates an assertion Reader from
|
|
// an optional getter function. It asserts that the optional value is present (Some).
|
|
func fromOptionalGetter[S, T any](getter func(S) option.Option[T], msgAndArgs ...any) Kleisli[S] {
|
|
return func(s S) Reader {
|
|
return func(t *testing.T) bool {
|
|
return assert.True(t, option.IsSome(getter(s)), msgAndArgs...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// FromOptional creates an assertion that checks if an Optional can successfully extract a value.
|
|
// An Optional is an optic that represents an optional reference to a subpart of a data structure.
|
|
//
|
|
// This function is useful when you have an Optional optic and want to assert that the optional
|
|
// value is present (Some) rather than absent (None). The assertion passes if the Optional's
|
|
// GetOption returns Some, and fails if it returns None.
|
|
//
|
|
// This enables property-focused testing where you verify that a particular optional field or
|
|
// sub-structure exists and is accessible.
|
|
//
|
|
// Parameters:
|
|
// - opt: An Optional optic that focuses from type S to type T
|
|
//
|
|
// Returns:
|
|
// - A Reader that asserts the optional value is present when applied to a value of type S
|
|
//
|
|
// Example:
|
|
//
|
|
// type Config struct {
|
|
// Database *DatabaseConfig // Optional field
|
|
// }
|
|
//
|
|
// type DatabaseConfig struct {
|
|
// Host string
|
|
// Port int
|
|
// }
|
|
//
|
|
// // Create an Optional that focuses on the Database field
|
|
// dbOptional := optional.MakeOptional(
|
|
// func(c Config) option.Option[*DatabaseConfig] {
|
|
// if c.Database != nil {
|
|
// return option.Some(c.Database)
|
|
// }
|
|
// return option.None[*DatabaseConfig]()
|
|
// },
|
|
// func(c Config, db *DatabaseConfig) Config {
|
|
// c.Database = db
|
|
// return c
|
|
// },
|
|
// )
|
|
//
|
|
// // Assert that the database config is present
|
|
// hasDatabaseConfig := assert.FromOptional(dbOptional)
|
|
//
|
|
// config := Config{Database: &DatabaseConfig{Host: "localhost", Port: 5432}}
|
|
// hasDatabaseConfig(config)(t) // Passes
|
|
//
|
|
// emptyConfig := Config{Database: nil}
|
|
// hasDatabaseConfig(emptyConfig)(t) // Fails
|
|
//
|
|
//go:inline
|
|
func FromOptional[S, T any](opt Optional[S, T]) reader.Reader[S, Reader] {
|
|
return fromOptionalGetter(opt.GetOption, "Optional: %s", opt)
|
|
}
|
|
|
|
// FromPrism creates an assertion that checks if a Prism can successfully extract a value.
|
|
// A Prism is an optic used to select part of a sum type (tagged union or variant).
|
|
//
|
|
// This function is useful when you have a Prism optic and want to assert that a value
|
|
// matches a specific variant of a sum type. The assertion passes if the Prism's GetOption
|
|
// returns Some (meaning the value is of the expected variant), and fails if it returns None
|
|
// (meaning the value is a different variant).
|
|
//
|
|
// This enables variant-focused testing where you verify that a value is of a particular
|
|
// type or matches a specific condition within a sum type.
|
|
//
|
|
// Parameters:
|
|
// - p: A Prism optic that focuses from type S to type T
|
|
//
|
|
// Returns:
|
|
// - A Reader that asserts the prism successfully extracts when applied to a value of type S
|
|
//
|
|
// Example:
|
|
//
|
|
// type Result interface{ isResult() }
|
|
// type Success struct{ Value int }
|
|
// type Failure struct{ Error string }
|
|
//
|
|
// func (Success) isResult() {}
|
|
// func (Failure) isResult() {}
|
|
//
|
|
// // Create a Prism that focuses on Success variant
|
|
// successPrism := prism.MakePrism(
|
|
// func(r Result) option.Option[int] {
|
|
// if s, ok := r.(Success); ok {
|
|
// return option.Some(s.Value)
|
|
// }
|
|
// return option.None[int]()
|
|
// },
|
|
// func(v int) Result { return Success{Value: v} },
|
|
// )
|
|
//
|
|
// // Assert that the result is a Success
|
|
// isSuccess := assert.FromPrism(successPrism)
|
|
//
|
|
// result1 := Success{Value: 42}
|
|
// isSuccess(result1)(t) // Passes
|
|
//
|
|
// result2 := Failure{Error: "something went wrong"}
|
|
// isSuccess(result2)(t) // Fails
|
|
//
|
|
//go:inline
|
|
func FromPrism[S, T any](p Prism[S, T]) reader.Reader[S, Reader] {
|
|
return fromOptionalGetter(p.GetOption, "Prism: %s", p)
|
|
}
|