CordaClient: use FlowResult

This commit is contained in:
djmil 2023-09-06 12:39:52 +02:00
parent 7df57cb4d2
commit a9b70b963c
9 changed files with 141 additions and 66 deletions

View File

@ -14,17 +14,23 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.Color; import djmil.cordacheckers.cordaclient.dao.Color;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
import djmil.cordacheckers.cordaclient.dao.VirtualNode; import djmil.cordacheckers.cordaclient.dao.VirtualNode;
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList; import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody; import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody; import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateRes;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalListRes;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty; import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalAction; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionRes;
@Service @Service
public class CordaClient { public class CordaClient {
@ -59,7 +65,7 @@ public class CordaClient {
* @param holdingIdentity * @param holdingIdentity
* @return GameProposals list in JSON form * @return GameProposals list in JSON form
*/ */
public String listGameProposals(HoldingIdentity holdingIdentity) { public List<GameProposal> gameProposalList(HoldingIdentity holdingIdentity) {
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
"list-" + UUID.randomUUID(), "list-" + UUID.randomUUID(),
@ -67,21 +73,27 @@ public class CordaClient {
new Empty() new Empty()
); );
final String gameProposalsJsonString = cordaFlowExecute( final GameProposalListRes listFlowResult = cordaFlowExecute(
holdingIdentity, holdingIdentity,
requestBody requestBody,
GameProposalListRes.class
); );
return gameProposalsJsonString; if (listFlowResult.failureStatus() != null) {
System.out.println("GameProposalCreateFlow failed: " + listFlowResult.failureStatus());
throw new RuntimeException("GameProsal: CreateFlow execution has failed");
}
return listFlowResult.successStatus();
} }
public String createGameProposal( public UUID gameProposalCreate(
HoldingIdentity sender, HoldingIdentity sender,
HoldingIdentity receiver, HoldingIdentity receiver,
Color receiverColor, Color receiverColor,
String message String message
) { ) throws JsonMappingException, JsonProcessingException {
final CreateGameProposal createGameProposal = new CreateGameProposal( final GameProposalCreateReq createGameProposal = new GameProposalCreateReq(
receiver.x500Name(), receiver.x500Name(),
receiverColor, receiverColor,
message message
@ -93,21 +105,28 @@ public class CordaClient {
createGameProposal createGameProposal
); );
final String createdGameProposalUuid = cordaFlowExecute( final GameProposalCreateRes createResult = cordaFlowExecute(
sender, sender,
requestBody requestBody,
GameProposalCreateRes.class
); );
return createdGameProposalUuid; if (createResult.failureStatus() != null) {
System.out.println("GameProposalCreateFlow failed: " + createResult.failureStatus());
throw new RuntimeException("GameProsal: CreateFlow execution has failed");
}
return createResult.successStatus();
} }
public String rejectGameProposal( public String gameProposalAction(
HoldingIdentity self, HoldingIdentity self,
String gameProposalUuid UUID gameProposalUuid,
GameProposalActionReq.Action action
) { ) {
final GameProposalAction rejectGameProposal = new GameProposalAction( final GameProposalActionReq rejectGameProposal = new GameProposalActionReq(
gameProposalUuid, gameProposalUuid.toString(),
GameProposalAction.Action.REJECT action
); );
final RequestBody requestBody = new RequestBody( final RequestBody requestBody = new RequestBody(
@ -116,15 +135,21 @@ public class CordaClient {
rejectGameProposal rejectGameProposal
); );
final String createdGameProposalUuid = cordaFlowExecute( final GameProposalActionRes actionResult = cordaFlowExecute(
self, self,
requestBody requestBody,
GameProposalActionRes.class
); );
return createdGameProposalUuid; if (actionResult.failureStatus() != null) {
System.out.println("GameProposalActionFlow failed: " + actionResult.failureStatus());
throw new RuntimeException("GameProsal: ActionFlow execution has failed");
}
return actionResult.successStatus();
} }
private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) { private <T> T cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody, Class<T> flowResultType) {
try { try {
final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody); final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody);
@ -134,15 +159,9 @@ public class CordaClient {
requestBodyJson requestBodyJson
); );
final String flowExecutionResult = cordaFlowPoll(startedFlow); final String flowResult = cordaFlowPoll(startedFlow);
// NOTE: return this.jsonMapper.readValue(flowResult, flowResultType);
// At this point, real production code, probably should convert data between CordaFlow
// abstarction into ReactApp abstraction. Instead, to limit boring json shuffling, all
// family of Corda.List flows were deliberatly designed to return frontend frendly JSONs.
// At the same time, all other Corda flows, simply return plain text string with
// operation result.
return flowExecutionResult;
} }
catch (Exception e) { catch (Exception e) {
throw new RuntimeException("Unable to perform "+requestBody.flowClassName() throw new RuntimeException("Unable to perform "+requestBody.flowClassName()
@ -204,15 +223,16 @@ public class CordaClient {
"CordaClient.cordaFlowPoll: empty getBody()" "CordaClient.cordaFlowPoll: empty getBody()"
); );
if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) {
System.out.println("Completed "+responseBody.flowResult());
return responseBody.flowResult();
} else
if (responseBody.flowError() != null) { if (responseBody.flowError() != null) {
return "Flow execution error: " +responseBody.flowError(); throw new RuntimeException("CordaClient.cordaFlowPoll: flow execution error: "
+responseBody.flowError());
}
if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) {
return responseBody.flowResult();
} }
} }
return "CordaClient.cordaFlowPoll: retry limit"; throw new RuntimeException ("CordaClient.cordaFlowPoll: retry limit");
} }
} }

View File

@ -1,5 +1,7 @@
package djmil.cordacheckers.cordaclient.dao; package djmil.cordacheckers.cordaclient.dao;
import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize @JsonDeserialize
@ -8,6 +10,6 @@ public record GameProposal(
String recipient, String recipient,
Color recipientColor, Color recipientColor,
String message, String message,
String id) { UUID id) {
} }

View File

@ -1,6 +1,6 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments; package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalAction(String gameProposalUuid, Action action) { public record GameProposalActionReq(String gameProposalUuid, Action action) {
public enum Action { public enum Action {
ACCEPT, ACCEPT,
REJECT, REJECT,

View File

@ -0,0 +1,5 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
public record GameProposalActionRes(String successStatus, String failureStatus) {
}

View File

@ -2,6 +2,6 @@ package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import djmil.cordacheckers.cordaclient.dao.Color; import djmil.cordacheckers.cordaclient.dao.Color;
public record CreateGameProposal(String opponentName, Color opponentColor, String message) { public record GameProposalCreateReq(String opponentName, Color opponentColor, String message) {
} }

View File

@ -0,0 +1,10 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize
public record GameProposalCreateRes(UUID successStatus, String failureStatus) {
}

View File

@ -0,0 +1,9 @@
package djmil.cordacheckers.cordaclient.dao.flow.arguments;
import java.util.List;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
public record GameProposalListRes(List<GameProposal> successStatus, String failureStatus) {
}

View File

@ -1,6 +1,8 @@
package djmil.cordacheckers.gameproposal; package djmil.cordacheckers.gameproposal;
import java.net.URI; import java.net.URI;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@ -9,9 +11,12 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import djmil.cordacheckers.cordaclient.CordaClient; import djmil.cordacheckers.cordaclient.CordaClient;
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity; import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.CreateGameProposal; import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalCreateReq;
import djmil.cordacheckers.cordaclient.dao.Color; import djmil.cordacheckers.cordaclient.dao.Color;
import djmil.cordacheckers.cordaclient.dao.GameProposal; import djmil.cordacheckers.cordaclient.dao.GameProposal;
import djmil.cordacheckers.user.HoldingIdentityResolver; import djmil.cordacheckers.user.HoldingIdentityResolver;
@ -36,9 +41,9 @@ public class GameProposalController {
public ResponseEntity<String> findAllUnconsumed( public ResponseEntity<String> findAllUnconsumed(
@AuthenticationPrincipal User player @AuthenticationPrincipal User player
) { ) {
String gpList = cordaClient.listGameProposals(player.getHoldingIdentity()); List<GameProposal> gpList = cordaClient.gameProposalList(player.getHoldingIdentity());
return ResponseEntity.ok(gpList); return ResponseEntity.ok(gpList.toString());
} }
// @PostMapping() // @PostMapping()
@ -50,15 +55,16 @@ public class GameProposalController {
@PostMapping() @PostMapping()
public ResponseEntity<Void> createGameProposal( public ResponseEntity<Void> createGameProposal(
@AuthenticationPrincipal User sender, @AuthenticationPrincipal User sender,
@RequestBody CreateGameProposal gpRequest, @RequestBody GameProposalCreateReq gpRequest,
UriComponentsBuilder ucb UriComponentsBuilder ucb
) { ) throws JsonMappingException, JsonProcessingException {
final HoldingIdentity gpSender = sender.getHoldingIdentity(); final HoldingIdentity gpSender = sender.getHoldingIdentity();
// TODO: throw execption with custom type // TODO: throw execption with custom type
final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName()); final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName());
final Color gpReceiverColor = gpRequest.opponentColor(); final Color gpReceiverColor = gpRequest.opponentColor();
String newGameProposalUuid = cordaClient.createGameProposal( // TODO handle expectionns here
UUID newGameProposalUuid = cordaClient.gameProposalCreate(
gpSender, gpSender,
gpReceiver, gpReceiver,
gpReceiverColor, gpReceiverColor,

View File

@ -6,14 +6,19 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import djmil.cordacheckers.cordaclient.dao.Color; import djmil.cordacheckers.cordaclient.dao.Color;
import djmil.cordacheckers.cordaclient.dao.GameProposal;
import djmil.cordacheckers.cordaclient.dao.VirtualNode; import djmil.cordacheckers.cordaclient.dao.VirtualNode;
import djmil.cordacheckers.cordaclient.dao.flow.arguments.GameProposalActionReq.Action;
import djmil.cordacheckers.user.HoldingIdentityResolver; import djmil.cordacheckers.user.HoldingIdentityResolver;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.List; import java.util.List;
import java.util.UUID;
import javax.naming.InvalidNameException; import javax.naming.InvalidNameException;
@ -34,44 +39,45 @@ public class CordaClientTest {
@Test @Test
void testGameProposalList() throws JsonProcessingException { void testGameProposalList() throws JsonProcessingException {
String resp = cordaClient.listGameProposals( List<GameProposal> gpList = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername("alice")); holdingIdentityResolver.getByUsername("Alice"));
System.out.println("testListGameProposals "+ resp); System.out.println("testListGameProposals\n"+ gpList);
} }
@Test @Test
void testGemeProposalCreate() { void testGemeProposalCreate() throws JsonMappingException, JsonProcessingException {
final String gpSender = "alice"; final String gpSender = "alice";
final String gpReceiver = "bob"; final String gpReceiver = "bob";
final Color gpReceiverColor = Color.WHITE; final Color gpReceiverColor = Color.WHITE;
final String gpMessage = "GameProposal create test"; final String gpMessage = "GameProposal create test";
final String gpUuid = cordaClient.createGameProposal( final UUID createdGpUuid = cordaClient.gameProposalCreate(
holdingIdentityResolver.getByUsername(gpSender), holdingIdentityResolver.getByUsername(gpSender),
holdingIdentityResolver.getByUsername(gpReceiver), holdingIdentityResolver.getByUsername(gpReceiver),
gpReceiverColor, gpReceiverColor,
gpMessage gpMessage
); );
String listResSender = cordaClient.listGameProposals( List<GameProposal> gpListSender = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpSender)); holdingIdentityResolver.getByUsername(gpSender));
String listResReceiver = cordaClient.listGameProposals( assertThat(findByUuid(gpListSender, createdGpUuid)).isNotNull();
List<GameProposal> gpListReceiver = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpReceiver)); holdingIdentityResolver.getByUsername(gpReceiver));
assertThat(listResSender).contains(gpUuid); assertThat(findByUuid(gpListReceiver, createdGpUuid)).isNotNull();
assertThat(listResReceiver).contains(gpUuid);
} }
@Test @Test
void testGemeProposalReject() { void testGemeProposalReject() throws JsonMappingException, JsonProcessingException {
final String gpSender = "alice"; final String gpSender = "alice";
final String gpReceiver = "bob"; final String gpReceiver = "bob";
final Color gpReceiverColor = Color.WHITE; final Color gpReceiverColor = Color.WHITE;
final String gpMessage = "GameProposal create test"; final String gpMessage = "GameProposal REJECT test";
final String gpUuid = cordaClient.createGameProposal( final UUID gpUuid = cordaClient.gameProposalCreate(
holdingIdentityResolver.getByUsername(gpSender), holdingIdentityResolver.getByUsername(gpSender),
holdingIdentityResolver.getByUsername(gpReceiver), holdingIdentityResolver.getByUsername(gpReceiver),
gpReceiverColor, gpReceiverColor,
@ -80,20 +86,37 @@ public class CordaClientTest {
System.out.println("Create GP UUID "+ gpUuid); System.out.println("Create GP UUID "+ gpUuid);
String listResSender = cordaClient.listGameProposals( assertThatThrownBy(() -> {
holdingIdentityResolver.getByUsername(gpSender)); cordaClient.gameProposalAction(
holdingIdentityResolver.getByUsername(gpSender),
gpUuid,
Action.REJECT);
});
String listResReceiver = cordaClient.listGameProposals( final String rejectRes = cordaClient.gameProposalAction(
holdingIdentityResolver.getByUsername(gpReceiver));
assertThat(listResSender).contains(gpUuid);
assertThat(listResReceiver).contains(gpUuid);
final String rejectRes = cordaClient.rejectGameProposal(
holdingIdentityResolver.getByUsername(gpReceiver), holdingIdentityResolver.getByUsername(gpReceiver),
"1ed70601-c79a-486d-b907-8537f317083a" gpUuid,
Action.REJECT
); );
assertThat(rejectRes).isEqualToIgnoringCase("REJECTED"); assertThat(rejectRes).isEqualToIgnoringCase("REJECTED");
List<GameProposal> gpListSender = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpSender));
assertThat(findByUuid(gpListSender, gpUuid)).isNull();
List<GameProposal> gpListReceiver = cordaClient.gameProposalList(
holdingIdentityResolver.getByUsername(gpReceiver));
assertThat(findByUuid(gpListReceiver, gpUuid)).isNull();
}
private GameProposal findByUuid(List<GameProposal> gpList, UUID uuid) {
for (GameProposal gameProposal : gpList) {
if (gameProposal.id().compareTo(uuid) == 0)
return gameProposal;
};
return null;
} }
} }