Skip to content

Commit

Permalink
string literals for json and payload length checks.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpe42 committed Sep 20, 2023
1 parent 51a20cb commit e165795
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 255 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,22 @@ public String getDefaultRoutingKey() {

@Override
public CompletableFuture<PagerDutyEventResponse> acknowledgeEvent(final String routingKey, final String dedupKey) {
return eventAction(routingKey, dedupKey, "\",\"event_action\":\"acknowledge\"}");
return eventAction(routingKey, dedupKey, "acknowledge");
}

@Override
public CompletableFuture<PagerDutyEventResponse> resolveEvent(final String routingKey, final String dedupKey) {
return eventAction(routingKey, dedupKey, "\",\"event_action\":\"resolve\"}");
return eventAction(routingKey, dedupKey, "resolve");
}

private CompletableFuture<PagerDutyEventResponse> eventAction(final String routingKey,
final String dedupKey,
final String actionBody) {
final String eventAction) {
Objects.requireNonNull(routingKey, "Routing key is a required field.");
Objects.requireNonNull(dedupKey, "De-duplication key is a required field.");
final var json = "{\"routing_key\":\"" + routingKey + "\",\"dedup_key\":\"" + dedupKey + actionBody;
final var json = String.format("""
{"routing_key":"%s","dedup_key":"%s","event_action":"%s"}""",
routingKey, dedupKey, eventAction);
return createAndSendRequest(routingKey, json);
}

Expand All @@ -74,14 +76,11 @@ public CompletableFuture<PagerDutyEventResponse> triggerEvent(final String clien
? ""
: payload.getLinks().stream().map(PagerDutyLinkRef::toJson)
.collect(Collectors.joining(",", ",\"links\":[", "]"));
final var json = "{\"event_action\":\"trigger\",\"payload\":" + payloadJson
+ ",\"routing_key\":\"" + routingKey + '"'
+ ",\"dedup_key\":\"" + payload.getDedupKey() + '"'
+ ",\"client\":\"" + clientName + '"'
+ (clientUrl == null ? "" : ",\"client_url\":\"" + clientUrl + '"')
+ imagesJson
+ linksJson
+ '}';
final var json = String.format("""
{"event_action":"trigger","routing_key":"%s","dedup_key":"%s","payload":%s,"client":"%s"%s%s%s}""",
routingKey, payload.getDedupKey(), payloadJson, clientName,
(clientUrl == null ? "" : ",\"client_url\":\"" + clientUrl + '"'),
imagesJson, linksJson);
return createAndSendRequest(routingKey, json);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static java.time.ZoneOffset.UTC;

final class PagerDutyEventPayloadBuilder implements PagerDutyEventPayload.Builder {

private static final Map<String, Object> NO_CUSTOM_DETAILS = Map.of();
private static final List<PagerDutyLinkRef> NO_LINKS = List.of();
private static final List<PagerDutyImageRef> NO_IMAGES = List.of();

private String dedupKey;
private String summary;
private String source;
Expand All @@ -20,54 +25,77 @@ final class PagerDutyEventPayloadBuilder implements PagerDutyEventPayload.Builde
private List<PagerDutyImageRef> images;

PagerDutyEventPayloadBuilder() {
this.customDetails = Map.of();
this.links = List.of();
this.images = List.of();
this.customDetails = NO_CUSTOM_DETAILS;
this.links = NO_LINKS;
this.images = NO_IMAGES;
}

PagerDutyEventPayloadBuilder(final PagerDutyEventPayload prototype) {
this.dedupKey = prototype.getDedupKey();
this.summary = prototype.getSummary();
this.source = prototype.getSource();
this.severity = prototype.getSeverity();
this.timestamp = prototype.getTimestamp();
this.component = prototype.getComponent();
this.group = prototype.getGroup();
this.type = prototype.getType();
this.customDetails = prototype.getCustomDetails().size() > 1
? new LinkedHashMap<>(prototype.getCustomDetails())
: Map.copyOf(prototype.getCustomDetails());
this.links = prototype.getLinks().size() > 1
? new ArrayList<>(prototype.getLinks())
: List.copyOf(prototype.getLinks());
this.images = prototype.getImages().size() > 1
? new ArrayList<>(prototype.getImages())
: List.copyOf(prototype.getImages());
this.dedupKey(prototype.getDedupKey());
this.summary(prototype.getSummary());
this.source(prototype.getSource());
this.severity(prototype.getSeverity());
this.timestamp(prototype.getTimestamp());
this.component(prototype.getComponent());
this.group(prototype.getGroup());
this.type(prototype.getType());
final var customDetails = prototype.getCustomDetails();
this.customDetails = customDetails == null || customDetails.isEmpty()
? NO_CUSTOM_DETAILS
: customDetails.size() > 1
? new LinkedHashMap<>(customDetails)
: Map.copyOf(customDetails);
final var links = prototype.getLinks();
this.links = links == null || links.isEmpty()
? NO_LINKS
: links.size() > 1
? new ArrayList<>(links)
: List.copyOf(links);
final var images = prototype.getImages();
this.images = images == null || images.isEmpty()
? NO_IMAGES
: images.size() > 1
? new ArrayList<>(images)
: List.copyOf(images);
}

@Override
public PagerDutyEventPayload create() {
return new PagerDutyEventPayloadVal(
dedupKey == null || dedupKey.isBlank() ? UUID.randomUUID().toString() : dedupKey,
Objects.requireNonNull(summary, "'Summary' is a required payload field."),
Objects.requireNonNull(source, "'Source' is a required payload field."),
Objects.requireNonNull(severity, "'Severity' is a required payload field."),
timestamp == null ? ZonedDateTime.now(UTC) : timestamp,
Objects.requireNonNull(summary, "'Summary' is a required payload field.");
Objects.requireNonNull(source, "'Source' is a required payload field.");
Objects.requireNonNull(severity, "'Severity' is a required payload field.");
if (dedupKey == null || dedupKey.isBlank()) {
dedupKey = UUID.randomUUID().toString();
}
if (timestamp == null) {
timestamp = ZonedDateTime.now(UTC);
}
final var json = getPayloadJson();
return new PagerDutyEventPayloadRecord(
dedupKey,
summary,
source,
severity,
timestamp,
component, group, type,
getCustomDetails(),
links,
images);
customDetails.size() > 1 ? Collections.unmodifiableMap(customDetails) : customDetails,
links.size() > 1 ? Collections.unmodifiableList(links) : links,
images.size() > 1 ? Collections.unmodifiableList(images) : images,
json);
}

@Override
public Builder dedupKey(final String dedupKey) {
if (dedupKey != null && dedupKey.length() > 255) {
throw new IllegalArgumentException("Max length for 'dedup_key' is 255");
}
this.dedupKey = dedupKey;
return this;
}

@Override
public Builder summary(final String summary) {
this.summary = summary;
this.summary = summary.length() > 1_024 ? summary.substring(0, 1_024) : summary;
return this;
}

Expand Down Expand Up @@ -205,7 +233,7 @@ public String getType() {

@Override
public Map<String, Object> getCustomDetails() {
return customDetails == null ? Map.of() : customDetails;
return customDetails;
}

@Override
Expand All @@ -218,17 +246,75 @@ public List<PagerDutyImageRef> getImages() {
return images;
}

private static void appendString(final StringBuilder jsonBuilder, final String field, final String str) {
if (str != null && !str.isBlank()) {
jsonBuilder.append(",\"");
jsonBuilder.append(field);
jsonBuilder.append("\":\"");
jsonBuilder.append(str);
jsonBuilder.append('"');
}
}

private static String escapeQuotes(final String str) {
final char[] chars = str.toCharArray();
final char[] escaped = new char[chars.length << 1];
char c;
for (int escapes = 0, from = 0, dest = 0, to = 0; ; to++) {
if (to == chars.length) {
if (from == 0) {
return str;
} else {
final int len = to - from;
System.arraycopy(chars, from, escaped, dest, len);
dest += len;
return new String(escaped, 0, dest);
}
} else {
c = chars[to];
if (c == '\\') {
escapes++;
} else if (c == '"' && (escapes & 1) == 0) {
final int len = to - from;
System.arraycopy(chars, from, escaped, dest, len);
dest += len;
escaped[dest++] = '\\';
from = to;
escapes = 0;
} else {
escapes = 0;
}
}
}
}

private static String toJson(final Map<String, Object> object) {
return object.entrySet().stream().map(entry -> {
final var val = entry.getValue();
if (val instanceof Number || val instanceof Boolean) {
return '"' + entry.getKey() + "\":" + val;
} else {
final var str = val.toString();
return '"' + entry.getKey() + "\":\"" + (str.indexOf('"') < 0 ? str : escapeQuotes(str)) + '"';
}
}).collect(Collectors.joining(",", "{", "}"));
}

@Override
public String getPayloadJson() {
return "{\"summary\":\"" + summary
+ "\",\"source\":\"" + source
+ "\",\"severity\":\"" + severity
+ "\",\"timestamp\":\"" + timestamp
+ (component == null ? '"' : "\",\"component\":\"" + component + '"')
+ (group == null ? "" : ",\"group\":\"" + group + '"')
+ (type == null ? "" : ",\"type\":\"" + type + '"')
+ (customDetails == null ? "" : ",\"custom_details\":" + PagerDutyEventPayloadVal.toJson(customDetails))
+ '}';
final var jsonBuilder = new StringBuilder(2_048);
jsonBuilder.append(String.format("""
{"summary":"%s","source":"%s","severity":"%s","timestamp":"%s\"""",
summary, source, severity, timestamp));
appendString(jsonBuilder, "component", component);
appendString(jsonBuilder, "group", group);
appendString(jsonBuilder, "class", type);
if (!customDetails.isEmpty()) {
jsonBuilder.append("""
,"custom_details":""");
jsonBuilder.append(toJson(customDetails));
}
return jsonBuilder.append('}').toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package systems.comodal.pagerduty.event.data;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;

record PagerDutyEventPayloadRecord(String dedupKey,
String summary,
String source,
PagerDutySeverity severity,
ZonedDateTime timestamp,
String component,
String group,
String type,
Map<String, Object> customDetails,
List<PagerDutyLinkRef> links,
List<PagerDutyImageRef> images,
String json) implements PagerDutyEventPayload {

@Override
public String getDedupKey() {
return dedupKey;
}

@Override
public String getSummary() {
return summary;
}

@Override
public String getSource() {
return source;
}

@Override
public PagerDutySeverity getSeverity() {
return severity;
}

@Override
public ZonedDateTime getTimestamp() {
return timestamp;
}

@Override
public String getComponent() {
return component;
}

@Override
public String getGroup() {
return group;
}

@Override
public String getType() {
return type;
}

@Override
public Map<String, Object> getCustomDetails() {
return customDetails;
}

@Override
public List<PagerDutyLinkRef> getLinks() {
return links;
}

@Override
public List<PagerDutyImageRef> getImages() {
return images;
}

@Override
public String getPayloadJson() {
return json;
}

@Override
public String toString() {
return json;
}
}
Loading

0 comments on commit e165795

Please sign in to comment.