From c77d70534b5a4390a8307eea67463f885bde4cda Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Tue, 19 May 2026 14:23:00 +0100 Subject: [PATCH 1/3] refactor: rebase --- build.gradle.kts | 3 +- .../cli/executor/command/ApplyCommand.java | 28 +++++-- .../output/ExecutionResultFormatter.java | 16 ++-- .../output/PendingChangesFormatter.java | 48 ++++++++++++ .../output/PipelineAbortedFormatter.java | 56 ++++++++++++++ .../cli/executor/output/TableFormatter.java | 4 +- .../executor/result/ResponseResultReader.java | 8 +- .../result/ResponseResultReaderTest.java | 73 ++++++++++++++++++- 8 files changed, 212 insertions(+), 24 deletions(-) create mode 100644 src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java create mode 100644 src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java diff --git a/build.gradle.kts b/build.gradle.kts index 5af6d6b..f9c57a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,8 @@ description = "Flamingock CLI for executing changes in applications" val jacksonVersion = "2.16.0" val picocliVersion = "4.7.5" -val flamingockVersion = "1.1.0" +val flamingockVersion = "1.3.0" + repositories { mavenLocal() // For local development with unpublished versions diff --git a/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java b/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java index 5a707a5..2fd6d60 100644 --- a/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java +++ b/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java @@ -21,9 +21,14 @@ import io.flamingock.cli.executor.orchestration.ExecutionOptions; import io.flamingock.cli.executor.output.ConsoleFormatter; import io.flamingock.cli.executor.output.ExecutionResultFormatter; +import io.flamingock.cli.executor.output.PendingChangesFormatter; +import io.flamingock.cli.executor.output.PipelineAbortedFormatter; import io.flamingock.cli.executor.util.VersionProvider; import io.flamingock.internal.common.core.operation.OperationType; -import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.ExecutionOutcome; +import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; +import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; +import io.flamingock.internal.common.core.response.data.StagedRunOutcome; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -122,19 +127,18 @@ public Integer call() { .appArgs(passthroughArgs.getAppArgs()) .build(); - CommandResult result = commandExecutor.execute( + CommandResult result = commandExecutor.execute( jarFile.getAbsolutePath(), OperationType.EXECUTE_APPLY, - ExecuteResponseData.class, + ExecutionOutcome.class, options ); if (result.isSuccess()) { if (!quiet) { - // Print detailed execution summary - ExecuteResponseData data = result.getData(); + ExecutionOutcome data = result.getData(); if (data != null) { - ExecutionResultFormatter.print(data); + printOutcome(data); } else { ConsoleFormatter.printSuccess(result.getDurationMs()); } @@ -143,13 +147,23 @@ public Integer call() { } else { // Print execution summary if available (even on failure, shows what was applied) if (!quiet && result.getData() != null) { - ExecutionResultFormatter.print(result.getData()); + printOutcome(result.getData()); } ConsoleFormatter.printFailure(result.getErrorCode(), result.getErrorMessage()); return result.getExitCode(); } } + private static void printOutcome(ExecutionOutcome outcome) { + if (outcome instanceof StagedRunOutcome) { + ExecutionResultFormatter.print((StagedRunOutcome) outcome); + } else if (outcome instanceof PipelineAbortedOutcome) { + PipelineAbortedFormatter.print((PipelineAbortedOutcome) outcome); + } else if (outcome instanceof PendingChangesOutcome) { + PendingChangesFormatter.print((PendingChangesOutcome) outcome); + } + } + private FlamingockExecutorCli getRootCommand() { return parent != null ? parent.getParent() : null; } diff --git a/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java b/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java index 5653678..155410b 100644 --- a/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java +++ b/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java @@ -18,7 +18,7 @@ import io.flamingock.internal.common.core.response.data.ChangeResult; import io.flamingock.internal.common.core.response.data.ChangeStatus; import io.flamingock.internal.common.core.response.data.ErrorInfo; -import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.StagedRunOutcome; import io.flamingock.internal.common.core.response.data.ExecutionStatus; import io.flamingock.internal.common.core.response.data.StageResult; @@ -41,7 +41,7 @@ private ExecutionResultFormatter() { * @param result the execution result data * @return formatted string for display */ - public static String format(ExecuteResponseData result) { + public static String format(StagedRunOutcome result) { StringBuilder sb = new StringBuilder("\n"); for (StageResult stage : result.getStages()) { @@ -126,7 +126,6 @@ private static String formatStatus(ExecutionStatus status) { case FAILED: return "FAILED"; case PARTIAL: - return "PARTIAL"; case NO_CHANGES: return "NO CHANGES"; default: @@ -137,7 +136,7 @@ private static String formatStatus(ExecutionStatus status) { /** * Formats the stages summary line. */ - private static String formatStagesSummary(ExecuteResponseData result) { + private static String formatStagesSummary(StagedRunOutcome result) { if (result.getFailedStages() > 0) { return String.format(" Stages: %d completed, %d failed%n", result.getCompletedStages(), result.getFailedStages()); @@ -149,7 +148,7 @@ private static String formatStagesSummary(ExecuteResponseData result) { /** * Formats the changes summary line. */ - private static String formatChangesSummary(ExecuteResponseData result) { + private static String formatChangesSummary(StagedRunOutcome result) { return String.format(" Changes: %d applied, %d skipped, %d failed%n", result.getAppliedChanges(), result.getSkippedChanges(), result.getFailedChanges()); } @@ -160,8 +159,9 @@ private static String formatChangesSummary(ExecuteResponseData result) { private static String formatErrorDetails(ErrorInfo error) { StringBuilder sb = new StringBuilder(); sb.append("\n Error:\n"); - if (error.getChangeId() != null) { - sb.append(String.format(" Change: %s%n", error.getChangeId())); + if (error.getChangeIds() != null && !error.getChangeIds().isEmpty()) { + String label = error.getChangeIds().size() == 1 ? "Change" : "Changes"; + sb.append(String.format(" %s: %s%n", label, String.join(", ", error.getChangeIds()))); } if (error.getStageId() != null) { sb.append(String.format(" Stage: %s%n", error.getStageId())); @@ -230,7 +230,7 @@ private static String centerText(String text, int width) { * * @param result the execution result data */ - public static void print(ExecuteResponseData result) { + public static void print(StagedRunOutcome result) { System.out.print(format(result)); } } diff --git a/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java b/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java new file mode 100644 index 0000000..f456493 --- /dev/null +++ b/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * 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. + */ +package io.flamingock.cli.executor.output; + +import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; + +/** + * Formats {@link PendingChangesOutcome} for CLI output. + */ +public final class PendingChangesFormatter { + + private static final String SEPARATOR = "--------------------------------------------------------------------------------"; + + private PendingChangesFormatter() { + } + + public static String format(PendingChangesOutcome outcome) { + StringBuilder sb = new StringBuilder("\n"); + sb.append(SEPARATOR).append("\n"); + sb.append("PENDING CHANGES DETECTED").append("\n"); + sb.append(SEPARATOR).append("\n"); + if (outcome.getMessage() != null) { + sb.append(String.format(" Message: %s%n", outcome.getMessage())); + } + if (outcome.getTimestamp() != null) { + sb.append(String.format(" Detected: %s%n", outcome.getTimestamp())); + } + sb.append(SEPARATOR).append("\n"); + return sb.toString(); + } + + public static void print(PendingChangesOutcome outcome) { + System.out.print(format(outcome)); + } +} diff --git a/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java b/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java new file mode 100644 index 0000000..77af2b3 --- /dev/null +++ b/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 Flamingock (https://www.flamingock.io) + * + * 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. + */ +package io.flamingock.cli.executor.output; + +import io.flamingock.internal.common.core.response.data.ErrorInfo; +import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; + +/** + * Formats {@link PipelineAbortedOutcome} for CLI output. + */ +public final class PipelineAbortedFormatter { + + private static final String SEPARATOR = "--------------------------------------------------------------------------------"; + + private PipelineAbortedFormatter() { + } + + public static String format(PipelineAbortedOutcome outcome) { + StringBuilder sb = new StringBuilder("\n"); + sb.append(SEPARATOR).append("\n"); + sb.append("PIPELINE ABORTED").append("\n"); + sb.append(SEPARATOR).append("\n"); + sb.append(String.format(" Reason: %s%n", outcome.getReason())); + sb.append(String.format(" Duration: %sms%n", outcome.getTotalDurationMs())); + + ErrorInfo error = outcome.getError(); + if (error != null) { + if (error.getErrorType() != null) { + sb.append(String.format(" Type: %s%n", error.getErrorType())); + } + if (error.getMessage() != null) { + sb.append(String.format(" Message: %s%n", error.getMessage())); + } + } + + sb.append(SEPARATOR).append("\n"); + return sb.toString(); + } + + public static void print(PipelineAbortedOutcome outcome) { + System.out.print(format(outcome)); + } +} diff --git a/src/main/java/io/flamingock/cli/executor/output/TableFormatter.java b/src/main/java/io/flamingock/cli/executor/output/TableFormatter.java index 930e918..bb2c1f7 100644 --- a/src/main/java/io/flamingock/cli/executor/output/TableFormatter.java +++ b/src/main/java/io/flamingock/cli/executor/output/TableFormatter.java @@ -183,7 +183,7 @@ private void printDataRow(AuditEntryDto entry, List columns, boolea private String getColumnValue(AuditEntryDto entry, int columnIndex) { switch (columnIndex) { case 0: // Change ID - return entry.getTaskId(); + return entry.getChangeId(); case 2: // Author return entry.getAuthor(); case 3: // Time @@ -196,7 +196,7 @@ private String getColumnValue(AuditEntryDto entry, int columnIndex) { private String getExtendedColumnValue(AuditEntryDto entry, int columnIndex) { switch (columnIndex) { case 0: // Change ID - return entry.getTaskId(); + return entry.getChangeId(); case 2: // Exec ID return truncate(entry.getExecutionId(), EXECUTION_ID_WIDTH); case 3: // Author diff --git a/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java b/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java index da56bc2..5908cfc 100644 --- a/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java +++ b/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java @@ -21,7 +21,9 @@ import io.flamingock.internal.common.core.response.ResponseEnvelope; import io.flamingock.internal.common.core.response.data.AuditFixResponseData; import io.flamingock.internal.common.core.response.data.AuditListResponseData; -import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; +import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; +import io.flamingock.internal.common.core.response.data.StagedRunOutcome; import io.flamingock.internal.common.core.response.data.IssueGetResponseData; import io.flamingock.internal.common.core.response.data.IssueListResponseData; import io.flamingock.internal.util.JsonObjectMapper; @@ -49,7 +51,9 @@ private void registerSubtypes() { objectMapper.registerSubtypes( new NamedType(AuditListResponseData.class, "audit_list"), new NamedType(AuditFixResponseData.class, "audit_fix"), - new NamedType(ExecuteResponseData.class, "execute"), + new NamedType(StagedRunOutcome.class, "execute"), + new NamedType(PipelineAbortedOutcome.class, "pipeline_aborted"), + new NamedType(PendingChangesOutcome.class, "pending_changes"), new NamedType(IssueListResponseData.class, "issue_list"), new NamedType(IssueGetResponseData.class, "issue_get") ); diff --git a/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java b/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java index d9aa04c..4e87099 100644 --- a/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java +++ b/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java @@ -16,8 +16,11 @@ package io.flamingock.cli.executor.result; import io.flamingock.internal.common.core.response.ResponseEnvelope; +import io.flamingock.internal.common.core.response.data.AbortReason; import io.flamingock.internal.common.core.response.data.AuditListResponseData; -import io.flamingock.internal.common.core.response.data.ExecuteResponseData; +import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; +import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; +import io.flamingock.internal.common.core.response.data.StagedRunOutcome; import io.flamingock.internal.common.core.response.data.ExecutionStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -74,8 +77,8 @@ void shouldParseValidExecuteResponse() throws IOException { Files.write(responseFile, json.getBytes(StandardCharsets.UTF_8)); // When - ResponseResultReader.ResponseResult result = - reader.readTyped(responseFile, ExecuteResponseData.class); + ResponseResultReader.ResponseResult result = + reader.readTyped(responseFile, StagedRunOutcome.class); // Then assertTrue(result.isSuccess()); @@ -132,7 +135,7 @@ void shouldParseValidAuditListResponse() throws IOException { assertNotNull(result.getData()); assertNotNull(result.getData().getEntries()); assertEquals(2, result.getData().getEntries().size()); - assertEquals("change-001", result.getData().getEntries().get(0).getTaskId()); + assertEquals("change-001", result.getData().getEntries().get(0).getChangeId()); assertEquals("developer", result.getData().getEntries().get(0).getAuthor()); assertEquals("APPLIED", result.getData().getEntries().get(0).getState()); assertEquals(250, result.getDurationMs()); @@ -151,6 +154,68 @@ void shouldReturnEmptyWhenFileNotFound() { assertFalse(result.isPresent()); } + @Test + @DisplayName("Should parse valid pipeline_aborted response JSON") + void shouldParseValidPipelineAbortedResponse() throws IOException { + String json = "{\n" + + " \"success\": true,\n" + + " \"operation\": \"EXECUTE_APPLY\",\n" + + " \"timestamp\": \"2026-02-09T10:00:00Z\",\n" + + " \"durationMs\": 50,\n" + + " \"data\": {\n" + + " \"@type\": \"pipeline_aborted\",\n" + + " \"reason\": \"LOCK_FAILED\",\n" + + " \"totalDurationMs\": 50,\n" + + " \"error\": {\n" + + " \"errorType\": \"LockException\",\n" + + " \"message\": \"lock not acquired\",\n" + + " \"changeIds\": []\n" + + " }\n" + + " }\n" + + "}"; + + Path responseFile = tempDir.resolve("response.json"); + Files.write(responseFile, json.getBytes(StandardCharsets.UTF_8)); + + ResponseResultReader.ResponseResult result = + reader.readTyped(responseFile, PipelineAbortedOutcome.class); + + assertTrue(result.isSuccess()); + assertNotNull(result.getData()); + assertEquals(AbortReason.LOCK_FAILED, result.getData().getReason()); + assertEquals(50, result.getData().getTotalDurationMs()); + assertNotNull(result.getData().getError()); + assertEquals("LockException", result.getData().getError().getErrorType()); + assertEquals("lock not acquired", result.getData().getError().getMessage()); + } + + @Test + @DisplayName("Should parse valid pending_changes response JSON") + void shouldParseValidPendingChangesResponse() throws IOException { + String json = "{\n" + + " \"success\": true,\n" + + " \"operation\": \"EXECUTE_VALIDATE\",\n" + + " \"timestamp\": \"2026-02-09T10:00:00Z\",\n" + + " \"durationMs\": 10,\n" + + " \"data\": {\n" + + " \"@type\": \"pending_changes\",\n" + + " \"message\": \"pending changes detected\",\n" + + " \"timestamp\": \"2026-02-09T10:00:00Z\"\n" + + " }\n" + + "}"; + + Path responseFile = tempDir.resolve("response.json"); + Files.write(responseFile, json.getBytes(StandardCharsets.UTF_8)); + + ResponseResultReader.ResponseResult result = + reader.readTyped(responseFile, PendingChangesOutcome.class); + + assertTrue(result.isSuccess()); + assertNotNull(result.getData()); + assertEquals("pending changes detected", result.getData().getMessage()); + assertNotNull(result.getData().getTimestamp()); + } + @Test @DisplayName("Should handle corrupt JSON gracefully") void shouldHandleCorruptJson() throws IOException { From 7cb781f5d8c0424375ced4e2ec06fb8a333c1254 Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Tue, 19 May 2026 14:23:22 +0100 Subject: [PATCH 2/3] refactor: rebase --- build.gradle.kts | 1 - .../cli/executor/command/ApplyCommand.java | 43 ++++++----- .../output/ExecutionResultFormatter.java | 16 ++-- .../output/PendingChangesFormatter.java | 17 ++--- .../output/PipelineAbortedFormatter.java | 20 ++--- .../executor/result/ResponseResultReader.java | 8 +- .../result/ResponseResultReaderTest.java | 74 +++++++++---------- 7 files changed, 81 insertions(+), 98 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f9c57a8..a3f9c3b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,6 @@ val jacksonVersion = "2.16.0" val picocliVersion = "4.7.5" val flamingockVersion = "1.3.0" - repositories { mavenLocal() // For local development with unpublished versions mavenCentral() diff --git a/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java b/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java index 2fd6d60..f9737b7 100644 --- a/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java +++ b/src/main/java/io/flamingock/cli/executor/command/ApplyCommand.java @@ -25,10 +25,8 @@ import io.flamingock.cli.executor.output.PipelineAbortedFormatter; import io.flamingock.cli.executor.util.VersionProvider; import io.flamingock.internal.common.core.operation.OperationType; -import io.flamingock.internal.common.core.response.data.ExecutionOutcome; -import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; -import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; -import io.flamingock.internal.common.core.response.data.StagedRunOutcome; +import io.flamingock.internal.common.core.response.ResponseError; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -127,40 +125,47 @@ public Integer call() { .appArgs(passthroughArgs.getAppArgs()) .build(); - CommandResult result = commandExecutor.execute( + CommandResult result = commandExecutor.execute( jarFile.getAbsolutePath(), OperationType.EXECUTE_APPLY, - ExecutionOutcome.class, + ExecuteResponseData.class, options ); if (result.isSuccess()) { if (!quiet) { - ExecutionOutcome data = result.getData(); + ExecuteResponseData data = result.getData(); if (data != null) { - printOutcome(data); + ExecutionResultFormatter.print(data); } else { ConsoleFormatter.printSuccess(result.getDurationMs()); } } return 0; } else { - // Print execution summary if available (even on failure, shows what was applied) - if (!quiet && result.getData() != null) { - printOutcome(result.getData()); + if (!quiet) { + if (result.getData() != null) { + ExecutionResultFormatter.print(result.getData()); + } + ResponseError error = new ResponseError( + result.getErrorCode(), + result.getErrorMessage(), + false + ); + printEnvelopeError(error); } - ConsoleFormatter.printFailure(result.getErrorCode(), result.getErrorMessage()); return result.getExitCode(); } } - private static void printOutcome(ExecutionOutcome outcome) { - if (outcome instanceof StagedRunOutcome) { - ExecutionResultFormatter.print((StagedRunOutcome) outcome); - } else if (outcome instanceof PipelineAbortedOutcome) { - PipelineAbortedFormatter.print((PipelineAbortedOutcome) outcome); - } else if (outcome instanceof PendingChangesOutcome) { - PendingChangesFormatter.print((PendingChangesOutcome) outcome); + private static void printEnvelopeError(ResponseError error) { + String code = error.getCode(); + if ("LOCK_ERROR".equals(code)) { + PipelineAbortedFormatter.print(error); + } else if ("PENDING_CHANGES".equals(code)) { + PendingChangesFormatter.print(error); + } else { + ConsoleFormatter.printFailure(error.getCode(), error.getMessage()); } } diff --git a/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java b/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java index 155410b..ca7d887 100644 --- a/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java +++ b/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java @@ -18,7 +18,7 @@ import io.flamingock.internal.common.core.response.data.ChangeResult; import io.flamingock.internal.common.core.response.data.ChangeStatus; import io.flamingock.internal.common.core.response.data.ErrorInfo; -import io.flamingock.internal.common.core.response.data.StagedRunOutcome; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; import io.flamingock.internal.common.core.response.data.ExecutionStatus; import io.flamingock.internal.common.core.response.data.StageResult; @@ -41,7 +41,7 @@ private ExecutionResultFormatter() { * @param result the execution result data * @return formatted string for display */ - public static String format(StagedRunOutcome result) { + public static String format(ExecuteResponseData result) { StringBuilder sb = new StringBuilder("\n"); for (StageResult stage : result.getStages()) { @@ -58,9 +58,9 @@ public static String format(StagedRunOutcome result) { sb.append(formatStagesSummary(result)); sb.append(formatChangesSummary(result)); - // Print error details if failed - if (result.isFailed() && result.getError() != null) { - sb.append(formatErrorDetails(result.getError())); + // Print per-stage error details for any failed stages + for (StageResult stage : result.getStages()) { + stage.getState().getErrorInfo().ifPresent(info -> sb.append(formatErrorDetails(info))); } sb.append(SEPARATOR).append("\n"); @@ -136,7 +136,7 @@ private static String formatStatus(ExecutionStatus status) { /** * Formats the stages summary line. */ - private static String formatStagesSummary(StagedRunOutcome result) { + private static String formatStagesSummary(ExecuteResponseData result) { if (result.getFailedStages() > 0) { return String.format(" Stages: %d completed, %d failed%n", result.getCompletedStages(), result.getFailedStages()); @@ -148,7 +148,7 @@ private static String formatStagesSummary(StagedRunOutcome result) { /** * Formats the changes summary line. */ - private static String formatChangesSummary(StagedRunOutcome result) { + private static String formatChangesSummary(ExecuteResponseData result) { return String.format(" Changes: %d applied, %d skipped, %d failed%n", result.getAppliedChanges(), result.getSkippedChanges(), result.getFailedChanges()); } @@ -230,7 +230,7 @@ private static String centerText(String text, int width) { * * @param result the execution result data */ - public static void print(StagedRunOutcome result) { + public static void print(ExecuteResponseData result) { System.out.print(format(result)); } } diff --git a/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java b/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java index f456493..6731ba4 100644 --- a/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java +++ b/src/main/java/io/flamingock/cli/executor/output/PendingChangesFormatter.java @@ -15,10 +15,10 @@ */ package io.flamingock.cli.executor.output; -import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; +import io.flamingock.internal.common.core.response.ResponseError; /** - * Formats {@link PendingChangesOutcome} for CLI output. + * Renders the "pending changes detected" failure scenario from an envelope-level {@link ResponseError}. */ public final class PendingChangesFormatter { @@ -27,22 +27,19 @@ public final class PendingChangesFormatter { private PendingChangesFormatter() { } - public static String format(PendingChangesOutcome outcome) { + public static String format(ResponseError error) { StringBuilder sb = new StringBuilder("\n"); sb.append(SEPARATOR).append("\n"); sb.append("PENDING CHANGES DETECTED").append("\n"); sb.append(SEPARATOR).append("\n"); - if (outcome.getMessage() != null) { - sb.append(String.format(" Message: %s%n", outcome.getMessage())); - } - if (outcome.getTimestamp() != null) { - sb.append(String.format(" Detected: %s%n", outcome.getTimestamp())); + if (error != null && error.getMessage() != null) { + sb.append(String.format(" Message: %s%n", error.getMessage())); } sb.append(SEPARATOR).append("\n"); return sb.toString(); } - public static void print(PendingChangesOutcome outcome) { - System.out.print(format(outcome)); + public static void print(ResponseError error) { + System.out.print(format(error)); } } diff --git a/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java b/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java index 77af2b3..5f264ec 100644 --- a/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java +++ b/src/main/java/io/flamingock/cli/executor/output/PipelineAbortedFormatter.java @@ -15,11 +15,10 @@ */ package io.flamingock.cli.executor.output; -import io.flamingock.internal.common.core.response.data.ErrorInfo; -import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; +import io.flamingock.internal.common.core.response.ResponseError; /** - * Formats {@link PipelineAbortedOutcome} for CLI output. + * Renders the "pipeline aborted" failure scenario from an envelope-level {@link ResponseError}. */ public final class PipelineAbortedFormatter { @@ -28,29 +27,24 @@ public final class PipelineAbortedFormatter { private PipelineAbortedFormatter() { } - public static String format(PipelineAbortedOutcome outcome) { + public static String format(ResponseError error) { StringBuilder sb = new StringBuilder("\n"); sb.append(SEPARATOR).append("\n"); sb.append("PIPELINE ABORTED").append("\n"); sb.append(SEPARATOR).append("\n"); - sb.append(String.format(" Reason: %s%n", outcome.getReason())); - sb.append(String.format(" Duration: %sms%n", outcome.getTotalDurationMs())); - - ErrorInfo error = outcome.getError(); if (error != null) { - if (error.getErrorType() != null) { - sb.append(String.format(" Type: %s%n", error.getErrorType())); + if (error.getCode() != null) { + sb.append(String.format(" Type: %s%n", error.getCode())); } if (error.getMessage() != null) { sb.append(String.format(" Message: %s%n", error.getMessage())); } } - sb.append(SEPARATOR).append("\n"); return sb.toString(); } - public static void print(PipelineAbortedOutcome outcome) { - System.out.print(format(outcome)); + public static void print(ResponseError error) { + System.out.print(format(error)); } } diff --git a/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java b/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java index 5908cfc..da56bc2 100644 --- a/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java +++ b/src/main/java/io/flamingock/cli/executor/result/ResponseResultReader.java @@ -21,9 +21,7 @@ import io.flamingock.internal.common.core.response.ResponseEnvelope; import io.flamingock.internal.common.core.response.data.AuditFixResponseData; import io.flamingock.internal.common.core.response.data.AuditListResponseData; -import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; -import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; -import io.flamingock.internal.common.core.response.data.StagedRunOutcome; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; import io.flamingock.internal.common.core.response.data.IssueGetResponseData; import io.flamingock.internal.common.core.response.data.IssueListResponseData; import io.flamingock.internal.util.JsonObjectMapper; @@ -51,9 +49,7 @@ private void registerSubtypes() { objectMapper.registerSubtypes( new NamedType(AuditListResponseData.class, "audit_list"), new NamedType(AuditFixResponseData.class, "audit_fix"), - new NamedType(StagedRunOutcome.class, "execute"), - new NamedType(PipelineAbortedOutcome.class, "pipeline_aborted"), - new NamedType(PendingChangesOutcome.class, "pending_changes"), + new NamedType(ExecuteResponseData.class, "execute"), new NamedType(IssueListResponseData.class, "issue_list"), new NamedType(IssueGetResponseData.class, "issue_get") ); diff --git a/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java b/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java index 4e87099..bae418f 100644 --- a/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java +++ b/src/test/java/io/flamingock/cli/executor/result/ResponseResultReaderTest.java @@ -16,11 +16,8 @@ package io.flamingock.cli.executor.result; import io.flamingock.internal.common.core.response.ResponseEnvelope; -import io.flamingock.internal.common.core.response.data.AbortReason; import io.flamingock.internal.common.core.response.data.AuditListResponseData; -import io.flamingock.internal.common.core.response.data.PendingChangesOutcome; -import io.flamingock.internal.common.core.response.data.PipelineAbortedOutcome; -import io.flamingock.internal.common.core.response.data.StagedRunOutcome; +import io.flamingock.internal.common.core.response.data.ExecuteResponseData; import io.flamingock.internal.common.core.response.data.ExecutionStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -77,8 +74,8 @@ void shouldParseValidExecuteResponse() throws IOException { Files.write(responseFile, json.getBytes(StandardCharsets.UTF_8)); // When - ResponseResultReader.ResponseResult result = - reader.readTyped(responseFile, StagedRunOutcome.class); + ResponseResultReader.ResponseResult result = + reader.readTyped(responseFile, ExecuteResponseData.class); // Then assertTrue(result.isSuccess()); @@ -106,14 +103,14 @@ void shouldParseValidAuditListResponse() throws IOException { " \"@type\": \"audit_list\",\n" + " \"entries\": [\n" + " {\n" + - " \"taskId\": \"change-001\",\n" + + " \"changeId\": \"change-001\",\n" + " \"author\": \"developer\",\n" + " \"state\": \"APPLIED\",\n" + " \"stageId\": \"stage-1\",\n" + " \"executionMillis\": 100\n" + " },\n" + " {\n" + - " \"taskId\": \"change-002\",\n" + + " \"changeId\": \"change-002\",\n" + " \"author\": \"developer\",\n" + " \"state\": \"APPLIED\",\n" + " \"stageId\": \"stage-1\",\n" + @@ -155,65 +152,60 @@ void shouldReturnEmptyWhenFileNotFound() { } @Test - @DisplayName("Should parse valid pipeline_aborted response JSON") - void shouldParseValidPipelineAbortedResponse() throws IOException { + @DisplayName("Should parse envelope-level lock failure response") + void shouldParseLockFailureResponse() throws IOException { String json = "{\n" + - " \"success\": true,\n" + + " \"success\": false,\n" + " \"operation\": \"EXECUTE_APPLY\",\n" + " \"timestamp\": \"2026-02-09T10:00:00Z\",\n" + " \"durationMs\": 50,\n" + - " \"data\": {\n" + - " \"@type\": \"pipeline_aborted\",\n" + - " \"reason\": \"LOCK_FAILED\",\n" + - " \"totalDurationMs\": 50,\n" + - " \"error\": {\n" + - " \"errorType\": \"LockException\",\n" + - " \"message\": \"lock not acquired\",\n" + - " \"changeIds\": []\n" + - " }\n" + + " \"data\": null,\n" + + " \"error\": {\n" + + " \"code\": \"LOCK_ERROR\",\n" + + " \"message\": \"lock not acquired\",\n" + + " \"recoverable\": true\n" + " }\n" + "}"; Path responseFile = tempDir.resolve("response.json"); Files.write(responseFile, json.getBytes(StandardCharsets.UTF_8)); - ResponseResultReader.ResponseResult result = - reader.readTyped(responseFile, PipelineAbortedOutcome.class); + Optional envelope = reader.read(responseFile); - assertTrue(result.isSuccess()); - assertNotNull(result.getData()); - assertEquals(AbortReason.LOCK_FAILED, result.getData().getReason()); - assertEquals(50, result.getData().getTotalDurationMs()); - assertNotNull(result.getData().getError()); - assertEquals("LockException", result.getData().getError().getErrorType()); - assertEquals("lock not acquired", result.getData().getError().getMessage()); + assertTrue(envelope.isPresent()); + assertFalse(envelope.get().isSuccess()); + assertNotNull(envelope.get().getError()); + assertEquals("LOCK_ERROR", envelope.get().getError().getCode()); + assertEquals("lock not acquired", envelope.get().getError().getMessage()); + assertTrue(envelope.get().getError().isRecoverable()); } @Test - @DisplayName("Should parse valid pending_changes response JSON") - void shouldParseValidPendingChangesResponse() throws IOException { + @DisplayName("Should parse envelope-level pending changes failure response") + void shouldParsePendingChangesFailureResponse() throws IOException { String json = "{\n" + - " \"success\": true,\n" + + " \"success\": false,\n" + " \"operation\": \"EXECUTE_VALIDATE\",\n" + " \"timestamp\": \"2026-02-09T10:00:00Z\",\n" + " \"durationMs\": 10,\n" + - " \"data\": {\n" + - " \"@type\": \"pending_changes\",\n" + + " \"data\": null,\n" + + " \"error\": {\n" + + " \"code\": \"PENDING_CHANGES\",\n" + " \"message\": \"pending changes detected\",\n" + - " \"timestamp\": \"2026-02-09T10:00:00Z\"\n" + + " \"recoverable\": false\n" + " }\n" + "}"; Path responseFile = tempDir.resolve("response.json"); Files.write(responseFile, json.getBytes(StandardCharsets.UTF_8)); - ResponseResultReader.ResponseResult result = - reader.readTyped(responseFile, PendingChangesOutcome.class); + Optional envelope = reader.read(responseFile); - assertTrue(result.isSuccess()); - assertNotNull(result.getData()); - assertEquals("pending changes detected", result.getData().getMessage()); - assertNotNull(result.getData().getTimestamp()); + assertTrue(envelope.isPresent()); + assertFalse(envelope.get().isSuccess()); + assertNotNull(envelope.get().getError()); + assertEquals("PENDING_CHANGES", envelope.get().getError().getCode()); + assertEquals("pending changes detected", envelope.get().getError().getMessage()); } @Test From a5d27cc4e28216d3b434a0974aa9a713e3c16a72 Mon Sep 17 00:00:00 2001 From: Antonio Perez Dieppa Date: Tue, 19 May 2026 16:42:58 +0100 Subject: [PATCH 3/3] refactor: rebase --- .../cli/executor/output/ExecutionResultFormatter.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java b/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java index ca7d887..0576181 100644 --- a/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java +++ b/src/main/java/io/flamingock/cli/executor/output/ExecutionResultFormatter.java @@ -58,9 +58,9 @@ public static String format(ExecuteResponseData result) { sb.append(formatStagesSummary(result)); sb.append(formatChangesSummary(result)); - // Print per-stage error details for any failed stages - for (StageResult stage : result.getStages()) { - stage.getState().getErrorInfo().ifPresent(info -> sb.append(formatErrorDetails(info))); + // Print error details if failed + if (result.isFailed() && result.getError() != null) { + sb.append(formatErrorDetails(result.getError())); } sb.append(SEPARATOR).append("\n"); @@ -159,9 +159,8 @@ private static String formatChangesSummary(ExecuteResponseData result) { private static String formatErrorDetails(ErrorInfo error) { StringBuilder sb = new StringBuilder(); sb.append("\n Error:\n"); - if (error.getChangeIds() != null && !error.getChangeIds().isEmpty()) { - String label = error.getChangeIds().size() == 1 ? "Change" : "Changes"; - sb.append(String.format(" %s: %s%n", label, String.join(", ", error.getChangeIds()))); + if (error.getChangeId() != null) { + sb.append(String.format(" Change: %s%n", error.getChangeId())); } if (error.getStageId() != null) { sb.append(String.format(" Stage: %s%n", error.getStageId()));