Examples
Practical examples demonstrating how to use dmx-fun in real-world scenarios.
User Validation
A common scenario where you need to validate user input and handle various error cases.
import codes.domix.fun.Result;
public class UserValidator { public record User(String email, String password) { }
public Result<User, String> validateAndCreateUser( String email, String password ) { return validateEmail(email) .flatMap(e -> validatePassword(password) .map(p -> new User(e, p))); }
private Result<String, String> validateEmail(String email) { if (email == null || email.isEmpty()) { return Result.err("Email is required"); } if (!email.contains("@")) { return Result.err("Invalid email format"); } return Result.ok(email); }
private Result<String, String> validatePassword(String password) { if (password == null || password.length() < 8) { return Result.err("Password must be at least 8 characters"); } return Result.ok(password); }
public Result<User, String> createUser() { // Usage UserValidator validator = new UserValidator(); Result<User, String> result = validator.validateAndCreateUser( "user@example.com", "securepass123" );
String message = result.fold( user -> "User created: " + user.email(), error -> "Validation failed: " + error ); System.out.println(message);
return result; }
}Safe Configuration Loading
Loading configuration with fallback values and type conversions.
import codes.domix.fun.Option;import codes.domix.fun.Try;import java.util.Properties;
public class ConfigLoader { private final Properties props;
public ConfigLoader(Properties props) { this.props = props; }
public Option<String> getString(String key) { return Option.ofNullable(props.getProperty(key)); }
public int getInt(String key, int defaultValue) { return getString(key) .flatMap(this::parseIntSafe) .getOrElse(defaultValue); }
public boolean getBoolean(String key, boolean defaultValue) { return getString(key) .map(Boolean::parseBoolean) .getOrElse(defaultValue); }
private Option<Integer> parseIntSafe(String value) { return Try.of(() -> Integer.parseInt(value)) .toOption(); }
void main() { // Usage ConfigLoader config = new ConfigLoader(props);
int port = config.getInt("server.port", 8080); boolean sslEnabled = config.getBoolean("server.ssl.enabled", false); String host = config.getString("server.host") .getOrElse("localhost");
System.out.printf("Server is running on %s:%d%n", host, port); }}API Response Handling
Handling API responses with proper error handling and data transformation.
import codes.domix.fun.Try;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
public class APIResponseHandling { public record UserData(String name) { }
private final HttpClient client;
public APIResponseHandling() { this.client = HttpClient.newHttpClient(); }
public Try<UserData> fetchUser(String userId) { return Try.of(() -> { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com/users/" + userId)) .GET() .build();
return client.send(request, HttpResponse.BodyHandlers.ofString()); }) .flatMap(this::parseResponse) .map(this::parseUserData); }
private Try<String> parseResponse(HttpResponse<String> response) { if (response.statusCode() == 200) { return Try.success(response.body()); } else { return Try.failure( new RuntimeException("HTTP " + response.statusCode()) ); } }
private UserData parseUserData(String json) { // Parse JSON to UserData return new UserData(json); }
void main() { // Usage APIResponseHandling api = new APIResponseHandling();
Try<UserData> result = api.fetchUser("123");
String message = result .map(user -> "User: " + user.name()) .recover(throwable -> "Error: " + throwable.getMessage()) .get(); }}Data Pipeline
Building a data processing pipeline with multiple transformations.
import codes.domix.fun.Try;import java.util.List;import java.util.function.Function;import java.util.stream.Collectors;
public class DataPipeline { public record ProcessedData(String input) {
}
public record RawData(String input) { public boolean isValid() { return input.length() > 5; }
public TransformedData transform() { return new TransformedData(input.toUpperCase()); }
}
public record TransformedData(String input) { public ProcessedData enrich() { return new ProcessedData(input); } }
public static <T, R> Try<List<R>> process( List<T> items, Function<T, Try<R>> processor ) { return Try.of(() -> items.stream() .map(processor) .map(Try::get) .collect(Collectors.toList()) ); }
public Try<ProcessedData> processUserData(String input) { return parseInput(input) .flatMap(this::validate) .flatMap(this::transform) .flatMap(this::enrich); }
private Try<RawData> parseInput(String input) { return Try.of(() -> new RawData(input)); }
private Try<RawData> validate(RawData data) { return data.isValid() ? Try.success(data) : Try.failure(new RuntimeException("Invalid data")); }
private Try<TransformedData> transform(RawData data) { return Try.of(data::transform); }
private Try<ProcessedData> enrich(TransformedData data) { return Try.of(data::enrich); }
void main() { // Usage DataPipeline pipeline = new DataPipeline();
List<String> inputs = List.of("data1", "data2", "data3");
Try<List<ProcessedData>> results = DataPipeline.process( inputs, pipeline::processUserData );
final var result = results.fold( data -> "Processed " + data.size() + " items", error -> "Pipeline failed: " + error ); }}Caching with Options
Implementing a simple cache with Option types for safe lookups.
import codes.domix.fun.Option;import java.util.concurrent.ConcurrentHashMap;import java.util.function.Supplier;
public class Caching<K, V> { public record UserData(String name) { }
private final ConcurrentHashMap<K, V> store;
public Caching() { this.store = new ConcurrentHashMap<>(); }
public Option<V> get(K key) { return Option.ofNullable(store.get(key)); }
public V getOrCompute(K key, Supplier<V> supplier) { return get(key).getOrElseGet(() -> { V value = supplier.get(); store.put(key, value); return value; }); }
public Option<V> put(K key, V value) { return Option.some(store.put(key, value)); }
public Option<V> remove(K key) { return Option.ofNullable(store.remove(key)); }
public boolean contains(K key) { return get(key).isDefined(); }
class Database { public UserData loadUser(String userId) { return new UserData(userId); } }
void main() { Database database = new Database(); // Usage Caching<String, UserData> userCache = new Caching<>();
// Get from cache or compute UserData user = userCache.getOrCompute( "user:123", () -> database.loadUser("123") );
// Safe lookup Option<UserData> maybeUser = userCache.get("user:456"); maybeUser.peek(userData -> System.out.printf("Found %s%n", userData.name())); }}Combining Multiple Operations
Combining multiple operations that can fail independently.
import codes.domix.fun.Option;import codes.domix.fun.Try;
public class OrderProcessor { private final ProductRepo productRepo = new ProductRepo(); private final CustomerRepo customerRepo = new CustomerRepo();
record Customer() { }
record Product() { public boolean isAvailable() { return true; }
public boolean isDefined() { return true; } }
public record OrderRequest(String customerId, String productId, int quantity) { }
public record OrderResult(Customer customer, Product product, int quantity) { }
static class ProductRepo { public Try<Product> findById(String id) { return Try.success(new Product()); } }
static class CustomerRepo {
public Option<Customer> findById(String id) { return Option.some(new Customer()); } }
static class NotFoundException extends RuntimeException { public NotFoundException(String message) { super(message); } }
// main processing logic public Try<OrderResult> processOrder(OrderRequest request) { Try<Customer> customer = validateCustomer(request.customerId()); Try<Product> product = validateProduct(request.productId()); Try<Integer> quantity = validateQuantity(request.quantity());
// Combine all validations return customer.flatMap(c -> product.flatMap(p -> quantity.map(q -> createOrder(c, p, q) ) ) ); }
private Try<Customer> validateCustomer(String id) { return Try.of(() -> customerRepo.findById(id)) .flatMap(opt -> opt.isDefined() ? Try.success(opt.get()) : Try.failure(new NotFoundException("Customer not found")) ); }
private Try<Product> validateProduct(String id) { return productRepo.findById(id) .filter(Product::isAvailable) .flatMap(opt -> opt.isDefined() ? Try.success(opt) : Try.failure(new NotFoundException("Product %s unavailable".formatted(id))) ); }
private Try<Integer> validateQuantity(int qty) { return Try.success(qty) .filter( q -> q > 0 && q <= 100, () -> new IllegalArgumentException("Invalid quantity") ); }
private OrderResult createOrder(Customer c, Product p, int qty) { return new OrderResult(c, p, qty); }
private void handleError(Throwable error) { System.out.println("Error processing order: " + error.getMessage()); }
private void confirmOrder(OrderResult order) { System.out.println("Order created: " + order); }
void main() { // Usage OrderProcessor processor = new OrderProcessor();
Try<OrderResult> result = processor .processOrder( new OrderRequest("cust123", "prod456", 5) ) .onSuccess(processor::confirmOrder) .onFailure(processor::handleError); }}