org.eclipse.edc.spi.result.AbstractResult Maven / Gradle / Ivy
/*
* Copyright (c) 2022 Microsoft Corporation
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Microsoft Corporation - Initial implementation
*
*/
package org.eclipse.edc.spi.result;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Base result type used by services to indicate success or failure.
*
* Service operations should generally never throw checked exceptions. Instead, they should return concrete result types
* and raise unchecked exceptions only when an unexpected event happens, such as a programming error.
*
* @param The type of {@link Failure}.
* @param The type of the content.
* @param The result self type.
*/
public abstract class AbstractResult> {
private final T content;
private final F failure;
protected AbstractResult(T content, F failure) {
this.content = content;
this.failure = failure;
}
public T getContent() {
return content;
}
public F getFailure() {
return failure;
}
//will cause problems during JSON serialization if failure is null TODO: is this comment still valid?
@JsonIgnore
public List getFailureMessages() {
return failure == null ? List.of() : failure.getMessages();
}
public boolean succeeded() {
return failure == null;
}
public boolean failed() {
return !succeeded();
}
/**
* Returns a string that contains all the failure messages.
*
* @return a string that contains all the failure messages.
*/
@JsonIgnore // will cause problems during JSON serialization if failure is null TODO: is this comment still valid?
public String getFailureDetail() {
return failure == null ? null : failure.getFailureDetail();
}
/**
* Executes a {@link Consumer} if this {@link Result} is successful
*/
public R onSuccess(Consumer successAction) {
if (succeeded()) {
successAction.accept(getContent());
}
return self();
}
/**
* Executes a {@link Consumer} if this {@link Result} failed. Passes the {@link Failure} to the consumer
*/
public R onFailure(Consumer failureAction) {
if (failed()) {
failureAction.accept(getFailure());
}
return self();
}
/**
* Execute an action if this {@link Result} is not successful.
*
* @param failureAction The function that maps a {@link Failure} into the content.
* @return T the success value if successful, otherwise the object returned by the failureAction
*/
public T orElse(Function failureAction) {
if (failed()) {
return failureAction.apply(getFailure());
} else {
return getContent();
}
}
/**
* Throws an exception returned by the mapper {@link Function} if this {@link Result} is not successful.
*
* @param exceptionMapper The function that maps a {@link Failure} into an exception.
* @return T the success value
*/
public T orElseThrow(Function exceptionMapper) throws X {
if (failed()) {
throw exceptionMapper.apply(getFailure());
} else {
return getContent();
}
}
/**
* Return this instance, cast to the {@link R} self type.
*
* @return this instance.
*/
@SuppressWarnings({ "unchecked" })
public R self() {
return (R) this;
}
/**
* Map the content into another, applying the mapping function.
*
* @param mapFunction a function that converts the content into another.
* @param the new content type.
* @param the new result type.
* @return a new result with a mapped content if succeeded, a new failed one otherwise.
*/
public > R2 map(Function mapFunction) {
if (succeeded()) {
return newInstance(mapFunction.apply(getContent()), null);
} else {
return newInstance(null, getFailure());
}
}
/**
* Maps this {@link Result} into another. If this {@link Result} is successful, the content is discarded. If this
* {@link Result} failed, the failures are carried over.
*/
public > R2 mapEmpty() {
return map(it -> null);
}
/**
* Maps this {@link Result} into another. The content gets discarded, this method can be used to forward failures
* to the caller.
*/
public > R2 mapFailure() {
return map(it -> null);
}
/**
* Maps one result into another, applying the mapping function.
*
* @param mappingFunction a function converting this result into another
* @return the result of the mapping function
*/
public > R2 flatMap(Function mappingFunction) {
return mappingFunction.apply(self());
}
/**
* If the result is successful maps the content into a result applying the mapping function, otherwise do nothing.
*
* @param mappingFunction a function converting this result into another
* @return the result of the mapping function
*/
public > R2 compose(Function mappingFunction) {
if (succeeded()) {
return mappingFunction.apply(getContent());
} else {
return newInstance(null, getFailure());
}
}
/**
* If the result is failed maps the content into a result applying the mapping function, otherwise do nothing.
*
* @param mappingFunction a function converting this result into another when it's failed.
* @return the result of the mapping function
*/
public R recover(Function mappingFunction) {
if (succeeded()) {
return newInstance(getContent(), null);
} else {
return mappingFunction.apply(getFailure());
}
}
/**
* Returns a new result instance.
* This default implementation exists only to avoid breaking changes, in a future this should become an abstract
* method.
*
* @param content the content.
* @param failure the failure.
* @param the new result type.
* @param the new content type.
* @return a new result instance
*/
@NotNull
protected abstract , C1> R1 newInstance(@Nullable C1 content, @Nullable F failure);
}