Skip to content

Commit

Permalink
Add basic client supports for YAML
Browse files Browse the repository at this point in the history
  • Loading branch information
chacham committed Jul 18, 2021
1 parent b76ee44 commit 6299f07
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ private static <T> Entry<T> toEntry(Query<T> query, Revision normRev, QueryType
" (expected: " + queryType + ')');
}
return entryAsJson(query, normRev, content);
case IDENTITY_YAML:
if (receivedEntryType != com.linecorp.centraldogma.internal.thrift.EntryType.YAML) {
throw new CentralDogmaException("invalid entry type. entry type: " + receivedEntryType +
" (expected: " + queryType + ')');
}
return entryAsYaml(query, normRev, content);
case IDENTITY:
switch (receivedEntryType) {
case JSON:
Expand All @@ -266,6 +272,8 @@ private static <T> Entry<T> toEntry(Query<T> query, Revision normRev, QueryType
return entryAsText(query, normRev, content);
case DIRECTORY:
return unsafeCast(Entry.ofDirectory(normRev, query.path()));
case YAML:
return entryAsYaml(query, normRev, content);
}
}
throw new Error(); // Should never reach here.
Expand All @@ -283,6 +291,10 @@ private static <T> Entry<T> entryAsText(Query<T> query, Revision normRev, String
return unsafeCast(Entry.ofText(normRev, query.path(), content));
}

private static <T> Entry<T> entryAsYaml(Query<T> query, Revision normRev, String content) {
return unsafeCast(Entry.ofYaml(normRev, query.path(), content));
}

@Override
public CompletableFuture<Map<String, Entry<?>>> getFiles(String projectName, String repositoryName,
Revision revision, String pathPattern) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@

import javax.annotation.Nullable;

import org.yaml.snakeyaml.nodes.Node;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -103,6 +105,7 @@
import com.linecorp.centraldogma.common.RevisionNotFoundException;
import com.linecorp.centraldogma.common.ShuttingDownException;
import com.linecorp.centraldogma.internal.Jackson;
import com.linecorp.centraldogma.internal.SnakeYaml;
import com.linecorp.centraldogma.internal.Util;
import com.linecorp.centraldogma.internal.api.v1.WatchTimeout;

Expand Down Expand Up @@ -1015,6 +1018,8 @@ private static ArrayNode toJson(Iterable<? extends Change<?>> changes) {
final Class<?> contentType = c.type().contentType();
if (contentType == JsonNode.class) {
changeNode.set("content", (JsonNode) c.content());
} else if (contentType == Node.class) {
changeNode.put("content", SnakeYaml.serialize((Node) c.content()));
} else if (contentType == String.class) {
changeNode.put("content", (String) c.content());
}
Expand Down Expand Up @@ -1068,6 +1073,12 @@ private static <T> Entry<T> toEntry(Revision revision, JsonNode node, QueryType
" (expected: " + queryType + ')');
}
return entryAsJson(revision, node, entryPath);
case IDENTITY_YAML:
if (receivedEntryType != EntryType.YAML) {
throw new CentralDogmaException("invalid entry type. entry type: " + receivedEntryType +
" (expected: " + queryType + ')');
}
return entryAsYaml(revision, node, entryPath);
case IDENTITY:
switch (receivedEntryType) {
case JSON:
Expand Down Expand Up @@ -1100,13 +1111,10 @@ private static <T> Entry<T> entryAsJson(Revision revision, JsonNode node, String

private static <T> Entry<T> entryAsYaml(Revision revision, JsonNode node, String entryPath) {
final JsonNode content = getField(node, "content");
final String content0;
if (content.isContainerNode()) {
content0 = content.toString();
} else {
content0 = content.asText();
if (content.getNodeType() != JsonNodeType.STRING) {
throw new CentralDogmaException("Found invalid content for YAML entry");
}
return unsafeCast(Entry.ofYaml(revision, entryPath, content0));
return unsafeCast(Entry.ofYaml(revision, entryPath, SnakeYaml.readTree(content.asText())));
}

private static Commit toCommit(JsonNode node) {
Expand All @@ -1129,6 +1137,8 @@ private static <T> Change<T> toChange(JsonNode node) {
switch (type) {
case UPSERT_JSON:
return unsafeCast(Change.ofJsonUpsert(actualPath, getField(node, "content")));
case UPSERT_YAML:
return unsafeCast(Change.ofYamlUpsert(actualPath, getField(node, "content").asText()));
case UPSERT_TEXT:
return unsafeCast(Change.ofTextUpsert(actualPath, getField(node, "content").asText()));
case REMOVE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@

import javax.annotation.Nullable;

import org.yaml.snakeyaml.nodes.Node;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Converter;

import com.linecorp.centraldogma.common.ChangeFormatException;
import com.linecorp.centraldogma.internal.Jackson;
import com.linecorp.centraldogma.internal.SnakeYaml;

/**
* Provides a function converting back and forth between {@link Change} and
Expand Down Expand Up @@ -56,6 +59,9 @@ protected Change doForward(com.linecorp.centraldogma.common.Change<?> value) {
break;
case REMOVE:
break;
case UPSERT_YAML:
change.setContent(SnakeYaml.serialize((Node) value.content()));
break;
}
return change;
}
Expand All @@ -78,6 +84,9 @@ protected com.linecorp.centraldogma.common.Change<?> doBackward(Change c) {
case APPLY_TEXT_PATCH:
return com.linecorp.centraldogma.common.Change.ofTextPatch(c.getPath(),
c.getContent());
case UPSERT_YAML:
return com.linecorp.centraldogma.common.Change.ofYamlUpsert(c.getPath(),
c.getContent());
}

throw new Error();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public static EntryType convertEntryType(com.linecorp.centraldogma.common.EntryT
switch (type) {
case JSON:
return EntryType.JSON;
case YAML:
return EntryType.YAML;
case TEXT:
return EntryType.TEXT;
case DIRECTORY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ protected Query doForward(com.linecorp.centraldogma.common.Query<?> query) {
return new Query(query.path(), QueryType.IDENTITY_JSON, Collections.emptyList());
case JSON_PATH:
return new Query(query.path(), QueryType.JSON_PATH, query.expressions());
case IDENTITY_YAML:
return new Query(query.path(), QueryType.IDENTITY_YAML, Collections.emptyList());
}

throw new Error();
Expand All @@ -59,6 +61,8 @@ protected com.linecorp.centraldogma.common.Query<?> doBackward(Query query) {
return com.linecorp.centraldogma.common.Query.ofText(query.getPath());
case IDENTITY_JSON:
return com.linecorp.centraldogma.common.Query.ofJson(query.getPath());
case IDENTITY_YAML:
return com.linecorp.centraldogma.common.Query.ofYaml(query.getPath());
case JSON_PATH:
return com.linecorp.centraldogma.common.Query.ofJsonPath(query.getPath(),
query.getExpressions());
Expand Down
3 changes: 3 additions & 0 deletions common-legacy/src/main/thrift/CentralDogma.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum EntryType {
JSON = 1,
TEXT = 2,
DIRECTORY = 3,
YAML = 4,
}

enum ChangeType {
Expand All @@ -56,6 +57,7 @@ enum ChangeType {
RENAME = 4,
APPLY_JSON_PATCH = 5,
APPLY_TEXT_PATCH = 6,
UPSERT_YAML = 7,
}

enum PropertyType {
Expand Down Expand Up @@ -192,6 +194,7 @@ enum QueryType {
JSON_PATH = 2,
IDENTITY_TEXT = 3,
IDENTITY_JSON = 4,
IDENTITY_YAML = 5,
}

struct Query {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@

import static com.linecorp.centraldogma.internal.Util.validateFilePath;
import static com.linecorp.centraldogma.internal.Util.validateJsonFilePath;
import static com.linecorp.centraldogma.internal.Util.validateYamlFilePath;
import static java.util.Objects.requireNonNull;

import java.util.Objects;

import javax.annotation.Nullable;

import org.yaml.snakeyaml.nodes.Node;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand Down Expand Up @@ -100,6 +103,8 @@ private static DefaultChange<?> rejectIncompatibleContent(@Nullable JsonNode con

if (type.contentType() == JsonNode.class) {
validateJsonFilePath(path, "path");
} else if (type.contentType() == Node.class) {
validateYamlFilePath(path, "path");
} else {
validateFilePath(path, "path");
}
Expand Down
11 changes: 11 additions & 0 deletions common/src/main/java/com/linecorp/centraldogma/common/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import javax.annotation.Nullable;

import org.yaml.snakeyaml.nodes.Node;

import com.fasterxml.jackson.databind.JsonNode;

/**
Expand Down Expand Up @@ -86,6 +88,15 @@ static Query<JsonNode> ofJsonPath(String path, Iterable<String> jsonPaths) {
return new JsonPathQuery(path, jsonPaths);
}

/**
* Returns a newly-created {@link Query} that retrieves the YAML content as it is.
*
* @param path the path of a file being queried on
*/
static Query<Node> ofYaml(String path) {
return new IdentityQuery<>(path, QueryType.IDENTITY_YAML);
}

/**
* Returns a newly-created {@link Query} that applies a series of expressions to the content.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ public enum QueryType {
* Applies a series of <a href="https://github.com/json-path/JsonPath/blob/master/README.md">JSON path
* expressions</a> to the content.
*/
JSON_PATH(EnumSet.of(EntryType.JSON));
JSON_PATH(EnumSet.of(EntryType.JSON)),

/**
* Retrieves the YAML content as it is.
*/
IDENTITY_YAML(EnumSet.of(EntryType.YAML));

private final Set<EntryType> supportedEntryTypes;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,28 @@
import java.util.Iterator;
import java.util.Set;

import org.yaml.snakeyaml.nodes.Node;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.JsonNodeType;
Expand Down Expand Up @@ -78,7 +85,9 @@ public final class Jackson {
prettyMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);

registerModules(new SimpleModule().addSerializer(Instant.class, InstantSerializer.INSTANCE)
.addDeserializer(Instant.class, InstantDeserializer.INSTANT));
.addDeserializer(Instant.class, InstantDeserializer.INSTANT)
.addSerializer(Node.class, new YamlSerializer())
.addDeserializer(Node.class, new YamlDeserializer()));
}

private static final JsonFactory compactFactory = new JsonFactory(compactMapper);
Expand Down Expand Up @@ -382,4 +391,22 @@ private static class PrettyPrinterImpl extends DefaultPrettyPrinter {
_objectIndenter = objectIndenter;
}
}

private static class YamlSerializer extends JsonSerializer<Node> {

@Override
public void serialize(Node value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(SnakeYaml.serialize(value));
}
}

private static class YamlDeserializer extends JsonDeserializer<Node> {

@Override
public Node deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return SnakeYaml.readTree(p.getValueAsString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@ public static String serialize(Node data) {
return stringWriter.toString();
}

public static String dump(Object o) {
return yaml.dump(o);
}

private SnakeYaml() {}
}
15 changes: 15 additions & 0 deletions common/src/main/java/com/linecorp/centraldogma/internal/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public final class Util {
"^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+$");
private static final Pattern JSON_FILE_PATH_PATTERN = Pattern.compile(
"^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+\\.(?i)json$");
private static final Pattern YAML_FILE_PATH_PATTERN = Pattern.compile(
"^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+\\.(?i)ya?ml$");
private static final Pattern DIR_PATH_PATTERN = Pattern.compile(
"^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)*/?$");
private static final Pattern PATH_PATTERN_PATTERN = Pattern.compile("^[- /*_.,0-9a-zA-Z]+$");
Expand Down Expand Up @@ -113,6 +115,19 @@ public static boolean isValidJsonPath(String jsonPath) {
}
}

public static String validateYamlFilePath(String path, String paramName) {
requireNonNull(path, paramName);
checkArgument(isValidYamlFilePath(path),
"%s: %s (expected: %s)", paramName, path, YAML_FILE_PATH_PATTERN);
return path;
}

public static boolean isValidYamlFilePath(String path) {
requireNonNull(path, "path");
return !path.isEmpty() && path.charAt(0) == '/' &&
YAML_FILE_PATH_PATTERN.matcher(path).matches();
}

public static String validateDirPath(String path, String paramName) {
requireNonNull(path, paramName);
checkArgument(isValidDirPath(path),
Expand Down
20 changes: 20 additions & 0 deletions it/src/test/java/com/linecorp/centraldogma/it/GetFileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.yaml.snakeyaml.nodes.Node;

import com.fasterxml.jackson.databind.JsonNode;

Expand All @@ -38,6 +39,7 @@
import com.linecorp.centraldogma.common.QueryExecutionException;
import com.linecorp.centraldogma.common.RepositoryNotFoundException;
import com.linecorp.centraldogma.common.Revision;
import com.linecorp.centraldogma.internal.SnakeYaml;

class GetFileTest {

Expand All @@ -61,6 +63,24 @@ void getJsonAsText(ClientType clientType) throws Exception {
Change.ofRemoval("/test/foo.json")).join();
}

@ParameterizedTest
@EnumSource(ClientType.class)
void getYamlAsText(ClientType clientType) throws Exception {
final CentralDogma client = clientType.client(dogma);
client.push(dogma.project(), dogma.repo1(), Revision.HEAD, "Add a file",
Change.ofYamlUpsert("/test/foo.yaml", "a.b: \"c\"")).join();
final Entry<Node> yaml = client.getFile(dogma.project(), dogma.repo1(), Revision.HEAD,
Query.ofYaml("/test/foo.yaml")).join();
assertThat(SnakeYaml.serialize(yaml.content()))
.isEqualTo(SnakeYaml.serialize(SnakeYaml.readTree("a.b: \"c\"")));

final Entry<String> text = client.getFile(dogma.project(), dogma.repo1(), Revision.HEAD,
Query.ofText("/test/foo.yaml")).join();
assertThat(text.content()).isEqualTo(SnakeYaml.serialize(SnakeYaml.readTree("a.b: \"c\"")));
client.push(dogma.project(), dogma.repo1(), Revision.HEAD, "Remove a file",
Change.ofRemoval("/test/foo.yaml")).join();
}

@ParameterizedTest
@EnumSource(ClientType.class)
void invalidJsonPath(ClientType clientType) {
Expand Down
Loading

0 comments on commit 6299f07

Please sign in to comment.