Skip to content

Commit

Permalink
fixes #325 add monad-result module to wrap success T and failure Status
Browse files Browse the repository at this point in the history
  • Loading branch information
stevehu committed Nov 9, 2018
1 parent bd35430 commit c04608d
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 0 deletions.
54 changes: 54 additions & 0 deletions monad-result/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!--
~ Copyright (c) 2016 Network New Technologies Inc.
~
~ 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.
-->

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.networknt</groupId>
<artifactId>light-4j</artifactId>
<version>1.5.22-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>

<artifactId>monad-result</artifactId>
<packaging>jar</packaging>
<description>A monad result wraps a T if success and Status if failure.</description>

<dependencies>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>status</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
38 changes: 38 additions & 0 deletions monad-result/src/main/java/com/networknt/monad/Failure.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.networknt.monad;

import com.networknt.status.Status;

import java.util.NoSuchElementException;

public final class Failure<T> implements Result<T> {

private final Status error;

private Failure(Status error) {
this.error = error;
}

public static <T> Result<T> of(Status error) {
return new Failure<>(error);
}

@Override
public boolean isSuccess() {
return false;
}

@Override
public Status getError() {
return error;
}

@Override
public T getResult() {
throw new NoSuchElementException("There is no result is Failure");
}

@Override
public String toString() {
return String.format("Failure[%s]", error.toString());
}
}
73 changes: 73 additions & 0 deletions monad-result/src/main/java/com/networknt/monad/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.networknt.monad;

import com.networknt.status.Status;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

public interface Result<T> {

boolean isSuccess();

default boolean isFailure() {
return !isSuccess();
}

Status getError();

T getResult();

/*
Use this method when you want to apply a function on the insides of the Result
without unwrapping it. Mapper won't be invoked if the Result is Failure
*/
default <R> Result<R> map(Function<? super T, ? extends R> mapper) {
return isSuccess() ?
Success.of(mapper.apply(getResult())) :
(Failure<R>) this;
}

/*
Use this method when you your mapping function is returning a Result<T> (which will make
the return type the Result<Result<T>> using just 'map').
*/
default <R> Result<R> flatMap(Function<? super T, Result<R>> mapper) {
return isSuccess() ?
mapper.apply(getResult()) :
(Failure<R>) this;
}

/*
Use this method if you want to reduce your Result to some type R. For example at the
end of the flow, you could convert it to two different client responses depending on
the insides.
*/
default <R> R fold(Function<? super T, ? extends R> successFunction, Function<Failure<R>, ? extends R> failureFunction) {
return isSuccess() ?
successFunction.apply(getResult()) :
failureFunction.apply((Failure<R>) this);
}

/*
Use this method when you have two instances of Result and you want to invoke a function on
their insides without unwrapping them separately. If both of them are Failures then only
the first (this) Failure will be returned.
*/
default <R, Z> Result<Z> lift(Result<R> other, BiFunction<? super T, ? super R, ? extends Z> function) {
return flatMap(first -> other.map(second -> function.apply(first, second)));
}

default Result<T> ifSuccess(Consumer<? super T> successConsumer) {
if (isSuccess()) {
successConsumer.accept(this.getResult());
}
return this;
}

default Result<T> ifFailure(Consumer<Failure<T>> failureConsumer) {
if (isFailure()) {
failureConsumer.accept((Failure<T>) this);
}
return this;
}
}
53 changes: 53 additions & 0 deletions monad-result/src/main/java/com/networknt/monad/Success.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.networknt.monad;

import com.networknt.status.Status;

import java.util.NoSuchElementException;
import java.util.Optional;

public final class Success<T> implements Result<T> {

public static final Result<Void> SUCCESS = new Success<>(null);

public static final Result OPTIONAL_SUCCESS = Success.ofOptional(null);

@SuppressWarnings("unchecked")
static <T> Result<Optional<T>> emptyOptional() {
return (Result<Optional<T>>) OPTIONAL_SUCCESS;
}

private final T result;

public static <T> Result<T> of(T result) {
return new Success<>(result);
}

public static <T> Result<Optional<T>> ofOptional(T result) {
return new Success<>(Optional.ofNullable(result));
}

private Success(T result) {
this.result = result;
}

@Override
public boolean isSuccess() {
return true;
}

@Override
public Status getError() {
throw new NoSuchElementException("There is no error in Success");
}

@Override
public T getResult() {
return result;
}

@Override
public String toString() {
final String value = result != null ? result.toString() : "";
return String.format("Success[%s]", value);
}
}
25 changes: 25 additions & 0 deletions monad-result/src/test/java/com/networknt/monad/ResultTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.networknt.monad;

import com.networknt.status.Status;
import org.junit.Assert;
import org.junit.Test;

import static com.networknt.monad.Success.SUCCESS;

public class ResultTest {
@Test
public void testResult() {
Status status = new Status(400, "ERR00000", "DEMO_STATUS", "This is an error", "ERROR");
Assert.assertTrue(SUCCESS.isSuccess());
Assert.assertTrue(!Failure.of(status).isSuccess());
Assert.assertTrue(!SUCCESS.isFailure());
Assert.assertTrue(Failure.of(status).isFailure());

Result result = Failure.of(status);
Assert.assertTrue(result.getError().equals(status));

String stringResult = "String result";
result = Success.of(stringResult);
Assert.assertEquals(stringResult, result.getResult());
}
}
72 changes: 72 additions & 0 deletions monad-result/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2016 Network New Technologies Inc.
~
~ 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.
-->

<configuration>
TODO create logger for audit only.
http://stackoverflow.com/questions/2488558/logback-to-log-different-messages-to-two-files
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Marker>PROFILER</Marker>
<!--<OnMatch>DENY</OnMatch>-->
<OnMatch>NEUTRAL</OnMatch>
</turboFilter>

<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5marker %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<appender name="log" class="ch.qos.logback.core.FileAppender">
<File>target/test.log</File>
<Append>false</Append>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %class{36}:%L %M - %msg%n</Pattern>
</layout>
</appender>

<!--audit log-->
<appender name="audit" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>target/audit.log</file> <!-- logfile location -->
<encoder>
<pattern>%-5level [%thread] %date{ISO8601} %F:%L - %msg%n</pattern> <!-- the layout pattern used to format log entries -->
<immediateFlush>true</immediateFlush>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>target/audit.log.%i.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>5</maxIndex> <!-- max number of archived logs that are kept -->
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>200MB</maxFileSize> <!-- The size of the logfile that triggers a switch to a new logfile, and the current one archived -->
</triggeringPolicy>
</appender>

<root level="trace">
<appender-ref ref="stdout" />
</root>

<logger name="com.networknt" level="trace">
<appender-ref ref="log"/>
</logger>

<logger name="Audit" level="trace" additivity="false">
<appender-ref ref="audit"/>
</logger>

</configuration>
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
<module>deref-token</module>
<module>ip-whitelist</module>
<module>encode-decode</module>
<module>monad-result</module>
</modules>

<dependencyManagement>
Expand Down Expand Up @@ -339,6 +340,11 @@
<artifactId>encode-decode</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>monad-result</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>server</artifactId>
Expand Down

0 comments on commit c04608d

Please sign in to comment.