aboutsummaryrefslogtreecommitdiffstats
path: root/container-core
diff options
context:
space:
mode:
authorValerij Fredriksen <valerijf@vespa.ai>2024-02-26 15:49:47 +0100
committerValerij Fredriksen <valerijf@vespa.ai>2024-02-26 15:49:47 +0100
commitdb245fbde8ab6546520747917ab7ab5b94ec17dc (patch)
tree113c6bb12dadb4a0815de9f9f33ec3c88aab1395 /container-core
parente3cb3fe6e1684cbb54b44b4ae26193d3bdd624a1 (diff)
Move Json to vespajlib
Diffstat (limited to 'container-core')
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/Json.java241
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java1
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/JsonTest.java126
3 files changed, 1 insertions, 367 deletions
diff --git a/container-core/src/main/java/com/yahoo/restapi/Json.java b/container-core/src/main/java/com/yahoo/restapi/Json.java
deleted file mode 100644
index 518dade2d22..00000000000
--- a/container-core/src/main/java/com/yahoo/restapi/Json.java
+++ /dev/null
@@ -1,241 +0,0 @@
-package com.yahoo.restapi;
-
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Inspector;
-import com.yahoo.slime.ObjectTraverser;
-import com.yahoo.slime.Slime;
-import com.yahoo.slime.SlimeUtils;
-import com.yahoo.slime.Type;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.OptionalDouble;
-import java.util.OptionalLong;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import static com.yahoo.slime.Type.ARRAY;
-import static com.yahoo.slime.Type.STRING;
-
-/**
- * A {@link Slime} wrapper that throws {@link RestApiException.BadRequest} on missing members or invalid types.
- *
- * @author bjorncs
- */
-public class Json implements Iterable<Json> {
-
- private final Inspector inspector;
- // Used to keep track of the path to the current node for error messages
- private final String path;
-
- public static Json of(Slime slime) { return of(slime.get()); }
- public static Json of(Inspector inspector) { return new Json(inspector, ""); }
- public static Json of(String json) { return of(SlimeUtils.jsonToSlime(json)); }
-
- private Json(Inspector inspector, String path) {
- this.inspector = Objects.requireNonNull(inspector);
- this.path = Objects.requireNonNull(path);
- }
-
- public Json f(String field) { return field(field); }
- public Json field(String field) {
- requireType(Type.OBJECT);
- return new Json(inspector.field(field), path.isEmpty() ? field : "%s.%s".formatted(path, field));
- }
-
- public Json a(int index) { return entry(index); }
- public Json entry(int index) {
- requireType(ARRAY);
- return new Json(inspector.entry(index), "%s[%d]".formatted(path, index));
- }
-
- public int length() { return inspector.children(); }
- public boolean has(String field) { return inspector.field(field).valid(); }
- public boolean isPresent() { return inspector.valid(); }
- public boolean isMissing() { return !isPresent(); }
-
- public Optional<String> asOptionalString() { return Optional.ofNullable(asString(null)); }
- public String asString() { requireType(STRING); return inspector.asString(); }
- public String asString(String defaultValue) {
- if (isMissing()) return defaultValue;
- return asString();
- }
-
- public OptionalLong asOptionalLong() { return isMissing() ? OptionalLong.empty() : OptionalLong.of(asLong()); }
- public long asLong() { requireType(Type.LONG, Type.DOUBLE); return inspector.asLong(); }
- public long asLong(long defaultValue) {
- if (isMissing()) return defaultValue;
- return asLong();
- }
-
- public OptionalDouble asOptionalDouble() { return isMissing() ? OptionalDouble.empty() : OptionalDouble.of(asDouble()); }
- public double asDouble() { requireType(Type.LONG, Type.DOUBLE); return inspector.asDouble(); }
- public double asDouble(double defaultValue) {
- if (isMissing()) return defaultValue;
- return asDouble();
- }
-
- public Optional<Boolean> asOptionalBool() { return isMissing() ? Optional.empty() : Optional.of(asBool()); }
- public boolean asBool() { requireType(Type.BOOL); return inspector.asBool(); }
- public boolean asBool(boolean defaultValue) {
- if (isMissing()) return defaultValue;
- return asBool();
- }
-
- public List<Json> toList() {
- var list = new ArrayList<Json>(length());
- forEachEntry(json -> list.add(json));
- return List.copyOf(list);
- }
-
- public Stream<Json> stream() { return StreamSupport.stream(this.spliterator(), false); }
-
- public String toJson(boolean pretty) { return SlimeUtils.toJson(inspector, !pretty); }
-
- public boolean isString() { return inspector.type() == STRING; }
- public boolean isArray() { return inspector.type() == ARRAY; }
- public boolean isLong() { return inspector.type() == Type.LONG; }
- public boolean isDouble() { return inspector.type() == Type.DOUBLE; }
- public boolean isBool() { return inspector.type() == Type.BOOL; }
- public boolean isNumber() { return isLong() || isDouble(); }
- public boolean isObject() { return inspector.type() == Type.OBJECT; }
-
- @Override
- public Iterator<Json> iterator() {
- requireType(ARRAY);
- return new Iterator<>() {
- private int current = 0;
- @Override public boolean hasNext() { return current < length(); }
- @Override public Json next() { return entry(current++); }
- };
- }
-
- public void forEachField(BiConsumer<String, Json> consumer) {
- requireType(Type.OBJECT);
- inspector.traverse((ObjectTraverser) (name, inspector) -> {
- consumer.accept(name, field(name));
- });
- }
-
- public void forEachEntry(BiConsumer<Integer, Json> consumer) {
- requireType(ARRAY);
- for (int i = 0; i < length(); i++) {
- consumer.accept(i, entry(i));
- }
- }
-
- public void forEachEntry(Consumer<Json> consumer) {
- requireType(ARRAY);
- for (int i = 0; i < length(); i++) {
- consumer.accept(entry(i));
- }
- }
-
- private void requireType(Type... types) {
- requirePresent();
- if (!List.of(types).contains(inspector.type())) throw createInvalidTypeException(types);
- }
-
- private void requirePresent() { if (isMissing()) throw createMissingMemberException(); }
-
- private RestApiException.BadRequest createInvalidTypeException(Type... expected) {
- var expectedTypesString = Arrays.stream(expected).map(this::toString).collect(Collectors.joining("' or '", "'", "'"));
- var pathString = path.isEmpty() ? "JSON" : "JSON member '%s'".formatted(path);
- return new RestApiException.BadRequest(
- "Expected %s to be a %s but got '%s'"
- .formatted(pathString, expectedTypesString, toString(inspector.type())));
- }
-
- private RestApiException.BadRequest createMissingMemberException() {
- return new RestApiException.BadRequest(path.isEmpty() ? "Missing JSON" : "Missing JSON member '%s'".formatted(path));
- }
-
- private String toString(Type type) {
- return switch (type) {
- case NIX -> "null";
- case BOOL -> "boolean";
- case LONG -> "integer";
- case DOUBLE -> "float";
- case STRING, DATA -> "string";
- case ARRAY -> "array";
- case OBJECT -> "object";
- };
- }
-
- @Override
- public String toString() {
- return "Json{" +
- "inspector=" + inspector +
- ", path='" + path + '\'' +
- '}';
- }
-
- /** Provides a fluent API for building a {@link Slime} instance. */
- public static class Builder {
- protected final Cursor cursor;
-
- public static Builder.Array newArray() { return new Builder.Array(new Slime().setArray()); }
- public static Builder.Object newObject() { return new Builder.Object(new Slime().setObject()); }
- public static Builder.Object existingObject(Cursor cursor) { return new Builder.Object(cursor); }
-
- private Builder(Cursor cursor) { this.cursor = cursor; }
-
- public static class Array extends Builder {
- private Array(Cursor cursor) { super(cursor); }
-
- public Builder.Array add(Builder.Array array) {
- SlimeUtils.copyArray(array.cursor, cursor.addArray()); return this;
- }
- public Builder.Array add(Builder.Object object) {
- SlimeUtils.copyObject(object.cursor, cursor.addObject()); return this;
- }
- public Builder.Array add(Json json) {
- SlimeUtils.addValue(json.inspector, cursor.addObject()); return this;
- }
- /** Note: does not return {@code this}! */
- public Builder.Array addArray() { return new Array(cursor.addArray()); }
- /** Note: does not return {@code this}! */
- public Builder.Object addObject() { return new Object(cursor.addObject()); }
-
- public Builder.Array add(String value) { cursor.addString(value); return this; }
- public Builder.Array add(long value) { cursor.addLong(value); return this; }
- public Builder.Array add(double value) { cursor.addDouble(value); return this; }
- public Builder.Array add(boolean value) { cursor.addBool(value); return this; }
- }
-
- public static class Object extends Builder {
- private Object(Cursor cursor) { super(cursor); }
-
- public Builder.Object set(String field, Builder.Array array) {
- SlimeUtils.copyArray(array.cursor, cursor.setArray(field)); return this;
- }
- public Builder.Object set(String field, Builder.Object object) {
- SlimeUtils.copyObject(object.cursor, cursor.setObject(field)); return this;
- }
- public Builder.Object set(String field, Json json) {
- SlimeUtils.setObjectEntry(json.inspector, field, cursor); return this;
- }
- /** Note: does not return {@code this}! */
- public Builder.Array setArray(String field) { return new Array(cursor.setArray(field)); }
- /** Note: does not return {@code this}! */
- public Builder.Object setObject(String field) { return new Object(cursor.setObject(field)); }
-
- public Builder.Object set(String field, String value) { cursor.setString(field, value); return this; }
- public Builder.Object set(String field, long value) { cursor.setLong(field, value); return this; }
- public Builder.Object set(String field, double value) { cursor.setDouble(field, value); return this; }
- public Builder.Object set(String field, boolean value) { cursor.setBool(field, value); return this; }
- public Builder.Object set(String field, BigDecimal value) { cursor.setString(field, value.toPlainString()); return this; }
- }
-
- public Cursor slimeCursor() { return cursor; }
- public Json build() { return Json.of(cursor); }
- }
-}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
index 3204c6c348a..ef3bb1662f2 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
@@ -1,6 +1,7 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
+import ai.vespa.json.Json;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.container.jdisc.HttpResponse;
diff --git a/container-core/src/test/java/com/yahoo/restapi/JsonTest.java b/container-core/src/test/java/com/yahoo/restapi/JsonTest.java
deleted file mode 100644
index 0ef4872c908..00000000000
--- a/container-core/src/test/java/com/yahoo/restapi/JsonTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.yahoo.restapi;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * @author bjorncs
- */
-class JsonTest {
-
- @Test
- void parses_json_correctly() {
- var text =
- """
- {
- "string": "bar",
- "integer": 42,
- "floaty": 8.25,
- "bool": true,
- "array": [1, 2, 3],
- "quux": {
- "corge": "grault"
- }
- }
- """;
- var json = Json.of(text);
-
- // Primitive members
- assertEquals("bar", json.f("string").asString());
- assertTrue(json.f("string").asOptionalString().isPresent());
- assertEquals("bar", json.f("string").asOptionalString().get());
- assertEquals(42L, json.f("integer").asLong());
- assertEquals(42D, json.f("integer").asDouble());
- assertEquals(8.25D, json.f("floaty").asDouble());
- assertEquals(8L, json.f("floaty").asLong());
- assertTrue(json.f("bool").asBool());
-
- // Array member
- assertEquals(3, json.f("array").length());
- assertEquals(1L, json.f("array").entry(0).asLong());
- assertEquals(2L, json.f("array").entry(1).asLong());
- assertEquals(3L, json.f("array").entry(2).asLong());
- json.f("array").forEachEntry((i, entry) -> assertEquals(i + 1, entry.asLong()));
- int counter = 0;
- for (var entry : json.f("array")) {
- assertEquals(++counter, entry.asLong());
- }
-
- // Object member
- assertEquals("grault", json.f("quux").f("corge").asString());
- json.f("quux").forEachField((name, child) -> {
- assertEquals("corge", name);
- assertEquals("grault", child.asString());
- });
- }
-
- @Test
- void throws_on_missing_and_invalid_members() {
- var text =
- """
- {
- "string": "bar"
- }
- """;
- var json = Json.of(text);
-
- var exception = assertThrows(RestApiException.BadRequest.class, () -> json.f("unknown").asString());
- assertEquals("Missing JSON member 'unknown'", exception.getMessage());
-
- exception = assertThrows(RestApiException.BadRequest.class, () -> json.a(0));
- assertEquals("Expected JSON to be a 'array' but got 'object'", exception.getMessage());
-
- exception = assertThrows(RestApiException.BadRequest.class, () -> json.f("string").f("unknown"));
- assertEquals("Expected JSON member 'string' to be a 'object' but got 'string'", exception.getMessage());
-
- exception = assertThrows(RestApiException.BadRequest.class, () -> json.f("string").asLong());
- assertEquals("Expected JSON member 'string' to be a 'integer' or 'float' but got 'string'", exception.getMessage());
- }
-
- @Test
- void fallback_to_default_if_field_missing() {
- var text =
- """
- {
- "string": "bar"
- }
- """;
- var json = Json.of(text);
- assertEquals("foo", json.f("unknown").asString("foo"));
- assertEquals("foo", json.f("unknown").asOptionalString().orElse("foo"));
- assertEquals("bar", json.f("string").asString("foo"));
- assertEquals("bar", json.f("string").asOptionalString().orElse("foo"));
- }
-
- @Test
- void builds_expected_json() {
- var expected =
- """
- {
- "string": "bar",
- "integer": 42,
- "floaty": 8.25,
- "bool": true,
- "array": [
- 1,
- 2,
- 3
- ],
- "quux": {
- "corge": "grault"
- }
- }
- """;
- var json = Json.Builder.newObject()
- .set("string", "bar")
- .set("integer", 42)
- .set("floaty", 8.25)
- .set("bool", true)
- .set("array", Json.Builder.newArray().add(1).add(2).add(3))
- .set("quux", Json.Builder.newObject().set("corge", "grault"))
- .build()
- .toJson(true);
- assertEquals(expected, json);
- }
-}