diff --git a/src/main/java/com/uber/cadence/internal/compatibility/proto/EnumMapper.java b/src/main/java/com/uber/cadence/internal/compatibility/proto/EnumMapper.java index dafd0c677..6978c615d 100644 --- a/src/main/java/com/uber/cadence/internal/compatibility/proto/EnumMapper.java +++ b/src/main/java/com/uber/cadence/internal/compatibility/proto/EnumMapper.java @@ -56,9 +56,11 @@ import com.uber.cadence.api.v1.WorkflowExecutionCloseStatus; import com.uber.cadence.api.v1.WorkflowIdReusePolicy; -class EnumMapper { +public final class EnumMapper { - static TaskListKind taskListKind(com.uber.cadence.TaskListKind t) { + private EnumMapper() {} + + public static TaskListKind taskListKind(com.uber.cadence.TaskListKind t) { if (t == null) { return TaskListKind.TASK_LIST_KIND_INVALID; } @@ -71,7 +73,7 @@ static TaskListKind taskListKind(com.uber.cadence.TaskListKind t) { throw new IllegalArgumentException("unexpected enum value"); } - static TaskListType taskListType(com.uber.cadence.TaskListType t) { + public static TaskListType taskListType(com.uber.cadence.TaskListType t) { if (t == null) { return TaskListType.TASK_LIST_TYPE_INVALID; } @@ -84,7 +86,7 @@ static TaskListType taskListType(com.uber.cadence.TaskListType t) { throw new IllegalArgumentException("unexpected enum value"); } - static EventFilterType eventFilterType(com.uber.cadence.HistoryEventFilterType t) { + public static EventFilterType eventFilterType(com.uber.cadence.HistoryEventFilterType t) { if (t == null) { return EventFilterType.EVENT_FILTER_TYPE_INVALID; } @@ -97,7 +99,7 @@ static EventFilterType eventFilterType(com.uber.cadence.HistoryEventFilterType t throw new IllegalArgumentException("unexpected enum value"); } - static QueryRejectCondition queryRejectCondition(com.uber.cadence.QueryRejectCondition t) { + public static QueryRejectCondition queryRejectCondition(com.uber.cadence.QueryRejectCondition t) { if (t == null) { return QueryRejectCondition.QUERY_REJECT_CONDITION_INVALID; } @@ -110,7 +112,8 @@ static QueryRejectCondition queryRejectCondition(com.uber.cadence.QueryRejectCon throw new IllegalArgumentException("unexpected enum value"); } - static QueryConsistencyLevel queryConsistencyLevel(com.uber.cadence.QueryConsistencyLevel t) { + public static QueryConsistencyLevel queryConsistencyLevel( + com.uber.cadence.QueryConsistencyLevel t) { if (t == null) { return QueryConsistencyLevel.QUERY_CONSISTENCY_LEVEL_INVALID; } @@ -123,7 +126,8 @@ static QueryConsistencyLevel queryConsistencyLevel(com.uber.cadence.QueryConsist throw new IllegalArgumentException("unexpected enum value"); } - static ContinueAsNewInitiator continueAsNewInitiator(com.uber.cadence.ContinueAsNewInitiator t) { + public static ContinueAsNewInitiator continueAsNewInitiator( + com.uber.cadence.ContinueAsNewInitiator t) { if (t == null) { return ContinueAsNewInitiator.CONTINUE_AS_NEW_INITIATOR_INVALID; } @@ -138,7 +142,8 @@ static ContinueAsNewInitiator continueAsNewInitiator(com.uber.cadence.ContinueAs throw new IllegalArgumentException("unexpected enum value"); } - static WorkflowIdReusePolicy workflowIdReusePolicy(com.uber.cadence.WorkflowIdReusePolicy t) { + public static WorkflowIdReusePolicy workflowIdReusePolicy( + com.uber.cadence.WorkflowIdReusePolicy t) { if (t == null) { return WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_INVALID; } @@ -155,7 +160,7 @@ static WorkflowIdReusePolicy workflowIdReusePolicy(com.uber.cadence.WorkflowIdRe throw new IllegalArgumentException("unexpected enum value"); } - static QueryResultType queryResultType(com.uber.cadence.QueryResultType t) { + public static QueryResultType queryResultType(com.uber.cadence.QueryResultType t) { if (t == null) { return QUERY_RESULT_TYPE_INVALID; } @@ -168,7 +173,7 @@ static QueryResultType queryResultType(com.uber.cadence.QueryResultType t) { throw new IllegalArgumentException("unexpected enum value"); } - static ArchivalStatus archivalStatus(com.uber.cadence.ArchivalStatus t) { + public static ArchivalStatus archivalStatus(com.uber.cadence.ArchivalStatus t) { if (t == null) { return ArchivalStatus.ARCHIVAL_STATUS_INVALID; } @@ -181,7 +186,7 @@ static ArchivalStatus archivalStatus(com.uber.cadence.ArchivalStatus t) { throw new IllegalArgumentException("unexpected enum value"); } - static ParentClosePolicy parentClosePolicy(com.uber.cadence.ParentClosePolicy t) { + public static ParentClosePolicy parentClosePolicy(com.uber.cadence.ParentClosePolicy t) { if (t == null) { return ParentClosePolicy.PARENT_CLOSE_POLICY_INVALID; } @@ -196,7 +201,7 @@ static ParentClosePolicy parentClosePolicy(com.uber.cadence.ParentClosePolicy t) throw new IllegalArgumentException("unexpected enum value"); } - static DecisionTaskFailedCause decisionTaskFailedCause( + public static DecisionTaskFailedCause decisionTaskFailedCause( com.uber.cadence.DecisionTaskFailedCause t) { if (t == null) { return DECISION_TASK_FAILED_CAUSE_INVALID; @@ -252,7 +257,7 @@ static DecisionTaskFailedCause decisionTaskFailedCause( throw new IllegalArgumentException("unexpected enum value"); } - static WorkflowExecutionCloseStatus workflowExecutionCloseStatus( + public static WorkflowExecutionCloseStatus workflowExecutionCloseStatus( com.uber.cadence.WorkflowExecutionCloseStatus t) { if (t == null) { return WorkflowExecutionCloseStatus.WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID; @@ -274,7 +279,7 @@ static WorkflowExecutionCloseStatus workflowExecutionCloseStatus( throw new IllegalArgumentException("unexpected enum value"); } - static QueryResultType queryTaskCompletedType(com.uber.cadence.QueryTaskCompletedType t) { + public static QueryResultType queryTaskCompletedType(com.uber.cadence.QueryTaskCompletedType t) { if (t == null) { return QUERY_RESULT_TYPE_INVALID; } diff --git a/src/main/java/com/uber/cadence/internal/compatibility/thrift/EnumMapper.java b/src/main/java/com/uber/cadence/internal/compatibility/thrift/EnumMapper.java index 1ec12c0ed..5feb79ed0 100644 --- a/src/main/java/com/uber/cadence/internal/compatibility/thrift/EnumMapper.java +++ b/src/main/java/com/uber/cadence/internal/compatibility/thrift/EnumMapper.java @@ -36,7 +36,9 @@ import com.uber.cadence.WorkflowExecutionCloseStatus; import com.uber.cadence.WorkflowIdReusePolicy; -class EnumMapper { +public final class EnumMapper { + + private EnumMapper() {} public static TaskListKind taskListKind(com.uber.cadence.api.v1.TaskListKind t) { switch (t) { @@ -311,6 +313,8 @@ public static DecisionTaskTimedOutCause decisionTaskTimedOutCause( return null; case CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_UNKNOWN_EXTERNAL_WORKFLOW_EXECUTION: return CancelExternalWorkflowExecutionFailedCause.UNKNOWN_EXTERNAL_WORKFLOW_EXECUTION; + case CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_COMPLETED: + return CancelExternalWorkflowExecutionFailedCause.WORKFLOW_ALREADY_COMPLETED; } throw new IllegalArgumentException("unexpected enum value"); } @@ -323,6 +327,8 @@ public static DecisionTaskTimedOutCause decisionTaskTimedOutCause( return null; case SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_UNKNOWN_EXTERNAL_WORKFLOW_EXECUTION: return SignalExternalWorkflowExecutionFailedCause.UNKNOWN_EXTERNAL_WORKFLOW_EXECUTION; + case SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_WORKFLOW_ALREADY_COMPLETED: + return SignalExternalWorkflowExecutionFailedCause.WORKFLOW_ALREADY_COMPLETED; } throw new IllegalArgumentException("unexpected enum value"); } diff --git a/src/test/java/com/uber/cadence/internal/compatibility/EnumMapperTest.java b/src/test/java/com/uber/cadence/internal/compatibility/EnumMapperTest.java new file mode 100644 index 000000000..131a3579f --- /dev/null +++ b/src/test/java/com/uber/cadence/internal/compatibility/EnumMapperTest.java @@ -0,0 +1,365 @@ +package com.uber.cadence.internal.compatibility; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.common.base.CaseFormat; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.uber.cadence.*; +import com.uber.cadence.api.v1.EventFilterType; +import com.uber.cadence.internal.compatibility.proto.EnumMapper; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import org.apache.thrift.TEnum; +import org.junit.Test; + +public class EnumMapperTest { + + @Test + public void testTaskListKind() { + assertAllValuesRoundTrip( + TaskListKind.class, + com.uber.cadence.api.v1.TaskListKind.TASK_LIST_KIND_INVALID, + EnumMapper::taskListKind, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::taskListKind); + } + + @Test + public void testQueryRejectionCondition() { + assertAllValuesRoundTrip( + QueryRejectCondition.class, + com.uber.cadence.api.v1.QueryRejectCondition.QUERY_REJECT_CONDITION_INVALID, + EnumMapper::queryRejectCondition, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::queryRejectCondition); + } + + @Test + public void testContinueAsNewInitiator() { + assertAllValuesRoundTrip( + ContinueAsNewInitiator.class, + com.uber.cadence.api.v1.ContinueAsNewInitiator.CONTINUE_AS_NEW_INITIATOR_INVALID, + EnumMapper::continueAsNewInitiator, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::continueAsNewInitiator); + } + + @Test + public void testWorkflowIdReusePolicy() { + assertAllValuesRoundTrip( + WorkflowIdReusePolicy.class, + com.uber.cadence.api.v1.WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_INVALID, + EnumMapper::workflowIdReusePolicy, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::workflowIdReusePolicy); + } + + @Test + public void testArchivalStatus() { + assertAllValuesRoundTrip( + ArchivalStatus.class, + com.uber.cadence.api.v1.ArchivalStatus.ARCHIVAL_STATUS_INVALID, + EnumMapper::archivalStatus, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::archivalStatus); + } + + @Test + public void testParentClosePolicy() { + assertAllValuesRoundTrip( + ParentClosePolicy.class, + com.uber.cadence.api.v1.ParentClosePolicy.PARENT_CLOSE_POLICY_INVALID, + EnumMapper::parentClosePolicy, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::parentClosePolicy); + } + + @Test + public void testDecisionTaskFailedCause() { + assertAllValuesRoundTrip( + DecisionTaskFailedCause.class, + com.uber.cadence.api.v1.DecisionTaskFailedCause.DECISION_TASK_FAILED_CAUSE_INVALID, + EnumMapper::decisionTaskFailedCause, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::decisionTaskFailedCause); + } + + @Test + public void testWorkflowExecutionCloseStatus() { + assertAllValuesRoundTrip( + WorkflowExecutionCloseStatus.class, + com.uber.cadence.api.v1.WorkflowExecutionCloseStatus + .WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID, + EnumMapper::workflowExecutionCloseStatus, + com.uber.cadence.internal.compatibility.thrift.EnumMapper::workflowExecutionCloseStatus); + } + + @Test + public void testTaskListType() { + assertMapping( + thriftToProtoIdentical( + TaskListType.class, + com.uber.cadence.api.v1.TaskListType.class, + com.uber.cadence.api.v1.TaskListType.TASK_LIST_TYPE_INVALID), + EnumMapper::taskListType); + } + + @Test + public void testEventFilterType() { + assertMapping( + thriftToProtoIdentical( + HistoryEventFilterType.class, + EventFilterType.class, + EventFilterType.EVENT_FILTER_TYPE_INVALID), + EnumMapper::eventFilterType); + } + + @Test + public void testQueryConsistencyLevel() { + assertMapping( + thriftToProtoIdentical( + QueryConsistencyLevel.class, + com.uber.cadence.api.v1.QueryConsistencyLevel.class, + com.uber.cadence.api.v1.QueryConsistencyLevel.QUERY_CONSISTENCY_LEVEL_INVALID), + EnumMapper::queryConsistencyLevel); + } + + @Test + public void testQueryResultType() { + assertMapping( + thriftToProtoIdentical( + QueryResultType.class, + com.uber.cadence.api.v1.QueryResultType.class, + com.uber.cadence.api.v1.QueryResultType.QUERY_RESULT_TYPE_INVALID), + EnumMapper::queryResultType); + } + + @Test + public void testQueryTaskCompletedType() { + Map mapping = + ImmutableMap.of( + QueryTaskCompletedType.COMPLETED, + com.uber.cadence.api.v1.QueryResultType.QUERY_RESULT_TYPE_ANSWERED, + QueryTaskCompletedType.FAILED, + com.uber.cadence.api.v1.QueryResultType.QUERY_RESULT_TYPE_FAILED); + assertAllValuesPresent(QueryTaskCompletedType.class, mapping); + assertMapping(mapping, EnumMapper::queryTaskCompletedType); + // ImmutableMap doesn't accept null + assertEquals( + com.uber.cadence.api.v1.QueryResultType.QUERY_RESULT_TYPE_INVALID, + EnumMapper.queryTaskCompletedType(null)); + } + + @Test + public void testDomainStatus() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.DomainStatus.class, + DomainStatus.class, + com.uber.cadence.api.v1.DomainStatus.DOMAIN_STATUS_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper::domainStatus); + } + + @Test + public void testPendingActivityState() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.PendingActivityState.class, + PendingActivityState.class, + com.uber.cadence.api.v1.PendingActivityState.PENDING_ACTIVITY_STATE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper::pendingActivityState); + } + + @Test + public void testPendingDecisionState() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.PendingDecisionState.class, + PendingDecisionState.class, + com.uber.cadence.api.v1.PendingDecisionState.PENDING_DECISION_STATE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper::pendingDecisionState); + } + + @Test + public void testIndexedValueType() { + Map mapping = + protoToThriftIdentical( + com.uber.cadence.api.v1.IndexedValueType.class, + IndexedValueType.class, + com.uber.cadence.api.v1.IndexedValueType.INDEXED_VALUE_TYPE_INVALID); + // This mapper uniquely throws when encountering this value + mapping.remove(com.uber.cadence.api.v1.IndexedValueType.INDEXED_VALUE_TYPE_INVALID); + assertMapping( + mapping, com.uber.cadence.internal.compatibility.thrift.EnumMapper::indexedValueType); + assertThrows( + IllegalArgumentException.class, + () -> + com.uber.cadence.internal.compatibility.thrift.EnumMapper.indexedValueType( + com.uber.cadence.api.v1.IndexedValueType.INDEXED_VALUE_TYPE_INVALID)); + } + + @Test + public void testEncodingType() { + Map mapping = + ImmutableMap.of( + com.uber.cadence.api.v1.EncodingType.ENCODING_TYPE_THRIFTRW, + EncodingType.ThriftRW, + com.uber.cadence.api.v1.EncodingType.ENCODING_TYPE_JSON, + EncodingType.JSON); + + assertAllValuesPresent( + com.uber.cadence.api.v1.EncodingType.class, + mapping, + com.uber.cadence.api.v1.EncodingType.ENCODING_TYPE_INVALID, + com.uber.cadence.api.v1.EncodingType.ENCODING_TYPE_PROTO3, + com.uber.cadence.api.v1.EncodingType.UNRECOGNIZED); + + assertMapping(mapping, com.uber.cadence.internal.compatibility.thrift.EnumMapper::encodingType); + // ImmutableMap doesn't accept null + assertNull( + com.uber.cadence.internal.compatibility.thrift.EnumMapper.encodingType( + com.uber.cadence.api.v1.EncodingType.ENCODING_TYPE_INVALID)); + // No thrift equivalent + assertThrows( + UnsupportedOperationException.class, + () -> + com.uber.cadence.internal.compatibility.thrift.EnumMapper.encodingType( + com.uber.cadence.api.v1.EncodingType.ENCODING_TYPE_PROTO3)); + } + + @Test + public void testTimeoutType() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.TimeoutType.class, + TimeoutType.class, + com.uber.cadence.api.v1.TimeoutType.TIMEOUT_TYPE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper::timeoutType); + } + + @Test + public void testDecisionTaskTimedOutCause() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.DecisionTaskTimedOutCause.class, + DecisionTaskTimedOutCause.class, + com.uber.cadence.api.v1.DecisionTaskTimedOutCause + .DECISION_TASK_TIMED_OUT_CAUSE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper::decisionTaskTimedOutCause); + } + + @Test + public void testCancelExternalWorkflowExecutionFailedCause() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.CancelExternalWorkflowExecutionFailedCause.class, + CancelExternalWorkflowExecutionFailedCause.class, + com.uber.cadence.api.v1.CancelExternalWorkflowExecutionFailedCause + .CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper + ::cancelExternalWorkflowExecutionFailedCause); + } + + @Test + public void testSignalExternalWorkflowExecutionFailedCause() { + + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.SignalExternalWorkflowExecutionFailedCause.class, + SignalExternalWorkflowExecutionFailedCause.class, + com.uber.cadence.api.v1.SignalExternalWorkflowExecutionFailedCause + .SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED_CAUSE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper + ::signalExternalWorkflowExecutionFailedCause); + } + + @Test + public void testchildWorkflowExecutionFailedCause() { + assertMapping( + protoToThriftIdentical( + com.uber.cadence.api.v1.ChildWorkflowExecutionFailedCause.class, + ChildWorkflowExecutionFailedCause.class, + com.uber.cadence.api.v1.ChildWorkflowExecutionFailedCause + .CHILD_WORKFLOW_EXECUTION_FAILED_CAUSE_INVALID), + com.uber.cadence.internal.compatibility.thrift.EnumMapper + ::childWorkflowExecutionFailedCause); + } + + private static , T extends Enum> void assertAllValuesRoundTrip( + Class fromType, T invalidType, Function to, Function inverse) { + F[] values = fromType.getEnumConstants(); + for (F fromValue : values) { + F result = inverse.apply(to.apply(fromValue)); + assertEquals("Round tripping " + fromValue.toString(), fromValue, result); + } + assertEquals("null -> invalid", invalidType, to.apply(null)); + assertNull("invalid -> null", inverse.apply(invalidType)); + } + + private static > void assertAllValuesPresent( + Class type, Map mapping, E... except) { + Set exclusions = ImmutableSet.copyOf(except); + for (E value : type.getEnumConstants()) { + if (!exclusions.contains(value)) { + assertTrue("Missing mapping for " + value, mapping.containsKey(value)); + } + } + } + + private static , T extends Enum> void assertMapping( + Map mapping, Function mapper) { + for (Map.Entry entry : mapping.entrySet()) { + F from = entry.getKey(); + T actual = mapper.apply(from); + T expected = entry.getValue(); + assertEquals("Mapping " + from, expected, actual); + } + } + + private static , T extends Enum & TEnum> Map protoToThriftIdentical( + Class from, Class to, F invalid) { + // There are more clever and succinct ways to do this but most Map types don't accept null as a + // key + Map result = new HashMap<>(); + Map inverse = thriftToProtoIdentical(to, from, invalid); + for (Map.Entry entry : inverse.entrySet()) { + result.put(entry.getValue(), entry.getKey()); + } + return result; + } + + private static & TEnum, T extends Enum> Map thriftToProtoIdentical( + Class from, Class to, T invalid) { + Map toByName = + Arrays.stream(to.getEnumConstants()) + .collect(ImmutableMap.toImmutableMap(Enum::name, x -> x)); + Map cases = new HashMap<>(); + for (F fromValue : from.getEnumConstants()) { + String protoName = getProtoNameFor(to, fromValue); + T expected = toByName.get(protoName); + Preconditions.checkState( + expected != null, + "Failed to find an equivalent for %s in %s with name %s", + fromValue, + to, + protoName); + cases.put(fromValue, expected); + } + cases.put(null, invalid); + return cases; + } + + private static String getProtoNameFor(Class protoType, Enum value) { + // TaskListType.Decision -> TASK_LIST_TYPE_DECISION + // EventFilterType.ALL_EVENT -> EVENT_FILTER_TYPE_ALL_EVENT + String typePart = + CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, protoType.getSimpleName()); + String valuePart = value.name(); + // Some Thrift enums use UPPER_CAMEL, some use UPPER_UNDERSCORE + if (!value.name().toUpperCase().equals(value.name())) { + valuePart = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, valuePart); + } + return typePart + "_" + valuePart; + } +}