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.

UserValidation.java
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.

ConfigLoader.java
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.

APIResponseHandling.java
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.

DataPipeline.java
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.

Caching.java
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.

OrderProcessor.java
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);
}
}