diff --git a/CHANGELOG.md b/CHANGELOG.md index 40177b4..f0e5dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add check on GitHub commit SHAs [#223](https://github.com/nbbrd/heylogs/issues/223) - Improve list command output [#231](https://github.com/nbbrd/heylogs/issues/231) - Add error severity to failures [#17](https://github.com/nbbrd/heylogs/issues/17) +- Add json formatting [#118](https://github.com/nbbrd/heylogs/issues/118) ### Changed diff --git a/heylogs-api/pom.xml b/heylogs-api/pom.xml index 7198cb1..49a15b3 100644 --- a/heylogs-api/pom.xml +++ b/heylogs-api/pom.xml @@ -84,6 +84,11 @@ com.github.nbbrd.java-io-util java-io-http + + com.google.code.gson + gson + 2.10.1 + diff --git a/heylogs-api/src/main/java/internal/heylogs/JsonFormat.java b/heylogs-api/src/main/java/internal/heylogs/JsonFormat.java new file mode 100644 index 0000000..9bd3712 --- /dev/null +++ b/heylogs-api/src/main/java/internal/heylogs/JsonFormat.java @@ -0,0 +1,130 @@ +package internal.heylogs; + +import com.google.gson.*; +import lombok.NonNull; +import nbbrd.design.MightBeGenerated; +import nbbrd.heylogs.Problem; +import nbbrd.heylogs.Resource; +import nbbrd.heylogs.Status; +import nbbrd.heylogs.TimeRange; +import nbbrd.heylogs.spi.Format; +import nbbrd.heylogs.spi.FormatType; +import nbbrd.heylogs.spi.RuleIssue; +import nbbrd.heylogs.spi.RuleSeverity; +import nbbrd.service.ServiceProvider; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +@ServiceProvider +public final class JsonFormat implements Format { + + public static final String ID = "json"; + + @Override + public @NonNull String getFormatId() { + return ID; + } + + @Override + public @NonNull String getFormatName() { + return "Outputs JSON-serialized results."; + } + + @Override + public @NonNull Set getSupportedFormatTypes() { + return EnumSet.allOf(FormatType.class); + } + + @Override + public void formatProblems(@NonNull Appendable appendable, @NonNull String source, @NonNull List problems) throws IOException { + try { + GSON.toJson(new FileProblems(source, problems), appendable); + } catch (JsonIOException ex) { + throw new IOException(ex); + } + } + + @Override + public void formatStatus(@NonNull Appendable appendable, @NonNull String source, @NonNull Status status) throws IOException { + try { + GSON.toJson(new FileStatus(source, status), appendable); + } catch (JsonIOException ex) { + throw new IOException(ex); + } + } + + @Override + public void formatResources(@NonNull Appendable appendable, @NonNull List resources) throws IOException { + try { + GSON.toJson(resources, appendable); + } catch (JsonIOException ex) { + throw new IOException(ex); + } + } + + @lombok.Value + private static class FileProblems { + String filePath; + List messages; + } + + @lombok.Value + private static class FileStatus { + String filePath; + Status status; + } + + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(Problem.class, (JsonSerializer) JsonFormat::serializeProblem) + .registerTypeAdapter(Problem.class, (JsonDeserializer) JsonFormat::deserializeProblem) + .registerTypeAdapter(TimeRange.class, (JsonSerializer) JsonFormat::serializeTimeRange) + .registerTypeAdapter(TimeRange.class, (JsonDeserializer) JsonFormat::deserializeTimeRange) + .setPrettyPrinting() + .create(); + + @MightBeGenerated + private static JsonElement serializeProblem(Problem src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.addProperty("ruleId", src.getId()); + result.addProperty("severity", src.getSeverity().toCode()); + result.addProperty("message", src.getIssue().getMessage()); + result.addProperty("line", src.getIssue().getLine()); + result.addProperty("column", src.getIssue().getColumn()); + return result; + } + + @MightBeGenerated + private static Problem deserializeProblem(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + JsonObject x = json.getAsJsonObject(); + return Problem + .builder() + .id(x.get("ruleId").getAsString()) + .severity(RuleSeverity.parseCode(x.get("severity").getAsInt())) + .issue(RuleIssue + .builder() + .message(x.get("message").getAsString()) + .line(x.get("line").getAsInt()) + .column(x.get("column").getAsInt()) + .build()) + .build(); + } + + @MightBeGenerated + private static JsonElement serializeTimeRange(TimeRange src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.addProperty("from", src.getFrom().toString()); + result.addProperty("to", src.getTo().toString()); + return result; + } + + @MightBeGenerated + private static TimeRange deserializeTimeRange(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + JsonObject x = json.getAsJsonObject(); + return TimeRange.of(LocalDate.parse(x.get("from").getAsString()), LocalDate.parse(x.get("to").getAsString())); + } +} diff --git a/heylogs-api/src/main/java/nbbrd/heylogs/spi/RuleSeverity.java b/heylogs-api/src/main/java/nbbrd/heylogs/spi/RuleSeverity.java index 287f2a8..4feec65 100644 --- a/heylogs-api/src/main/java/nbbrd/heylogs/spi/RuleSeverity.java +++ b/heylogs-api/src/main/java/nbbrd/heylogs/spi/RuleSeverity.java @@ -1,5 +1,14 @@ package nbbrd.heylogs.spi; +import lombok.NonNull; +import nbbrd.design.RepresentableAsInt; +import nbbrd.design.RepresentableAsString; +import nbbrd.design.StaticFactoryMethod; + +import java.util.stream.Stream; + +//@RepresentableAsString +@RepresentableAsInt(formatMethodName = "toCode", parseMethodName = "parseCode") @lombok.AllArgsConstructor public enum RuleSeverity { @@ -18,6 +27,16 @@ public enum RuleSeverity { */ ERROR(2); - @lombok.Getter private final int code; + + public int toCode() { + return code; + } + + @StaticFactoryMethod + public static @NonNull RuleSeverity parseCode(int code) { + return Stream.of(values()).filter(value -> value.code == code) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + } } diff --git a/heylogs-api/src/test/java/nbbrd/heylogs/HeylogsTest.java b/heylogs-api/src/test/java/nbbrd/heylogs/HeylogsTest.java index 2b63d2f..c80cc05 100644 --- a/heylogs-api/src/test/java/nbbrd/heylogs/HeylogsTest.java +++ b/heylogs-api/src/test/java/nbbrd/heylogs/HeylogsTest.java @@ -1,6 +1,7 @@ package nbbrd.heylogs; import internal.heylogs.SemverRule; +import internal.heylogs.StylishFormat; import nbbrd.heylogs.spi.Rule; import nbbrd.heylogs.spi.RuleIssue; import org.junit.jupiter.api.Test; @@ -56,7 +57,7 @@ public void testFormatProblems() throws IOException { .isThrownBy(() -> Heylogs.ofServiceLoader().formatProblems("other", new StringBuilder(), "", emptyList())); StringBuilder output = new StringBuilder(); - Heylogs.ofServiceLoader().formatProblems(FIRST_FORMAT_AVAILABLE, output, "file1", asList(Problem.builder().id("rule1").severity(ERROR).issue(RuleIssue.builder().message("some message").line(10).column(20).build()).build())); + Heylogs.ofServiceLoader().formatProblems(StylishFormat.ID, output, "file1", asList(Problem.builder().id("rule1").severity(ERROR).issue(RuleIssue.builder().message("some message").line(10).column(20).build()).build())); assertThat(output.toString()) .isEqualToIgnoringNewLines( "file1\n" + @@ -112,7 +113,7 @@ public void testFormatStatus() throws IOException { .isThrownBy(() -> Heylogs.ofServiceLoader().formatStatus("other", new StringBuilder(), "", Status.builder().build())); StringBuilder output = new StringBuilder(); - Heylogs.ofServiceLoader().formatStatus(FIRST_FORMAT_AVAILABLE, output, "file1", new Status( + Heylogs.ofServiceLoader().formatStatus(StylishFormat.ID, output, "file1", new Status( 1, TimeRange.of(LocalDate.of(2019, 2, 15), LocalDate.of(2019, 2, 15)), true, " ()",