diff --git a/apollo-api/src/main/java/com/apollographql/apollo/api/InputType.java b/apollo-api/src/main/java/com/apollographql/apollo/api/InputType.java new file mode 100644 index 00000000000..c0a1b836e28 --- /dev/null +++ b/apollo-api/src/main/java/com/apollographql/apollo/api/InputType.java @@ -0,0 +1,7 @@ +package com.apollographql.apollo.api; + +import org.jetbrains.annotations.NotNull; + +public interface InputType { + @NotNull InputFieldMarshaller marshaller(); +} diff --git a/apollo-api/src/main/java/com/apollographql/apollo/api/ResponseField.java b/apollo-api/src/main/java/com/apollographql/apollo/api/ResponseField.java index b2e226da0a5..d60a8d9235d 100644 --- a/apollo-api/src/main/java/com/apollographql/apollo/api/ResponseField.java +++ b/apollo-api/src/main/java/com/apollographql/apollo/api/ResponseField.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; @@ -24,9 +23,9 @@ public class ResponseField { private final boolean optional; private final List conditions; - private static final String VARIABLE_IDENTIFIER_KEY = "kind"; - private static final String VARIABLE_IDENTIFIER_VALUE = "Variable"; - private static final String VARIABLE_NAME_KEY = "variableName"; + public static final String VARIABLE_IDENTIFIER_KEY = "kind"; + public static final String VARIABLE_IDENTIFIER_VALUE = "Variable"; + public static final String VARIABLE_NAME_KEY = "variableName"; /** * Factory method for creating a Field instance representing {@link Type#STRING}. @@ -233,13 +232,6 @@ public List conditions() { return conditions; } - public String cacheKey(Operation.Variables variables) { - if (arguments.isEmpty()) { - return fieldName(); - } - return String.format("%s(%s)", fieldName(), orderIndependentKey(arguments, variables)); - } - /** * Resolve field argument value by name. If argument represents a references to the variable, it will be resolved from * provided operation variables values. @@ -266,71 +258,12 @@ public String cacheKey(Operation.Variables variables) { return argumentValue; } - private String orderIndependentKey(Map objectMap, Operation.Variables variables) { - if (isArgumentValueVariableType(objectMap)) { - return orderIndependentKeyForVariableArgument(objectMap, variables); - } - List> sortedArguments = new ArrayList<>(objectMap.entrySet()); - Collections.sort(sortedArguments, new Comparator>() { - @Override public int compare(Map.Entry argumentOne, Map.Entry argumentTwo) { - return argumentOne.getKey().compareTo(argumentTwo.getKey()); - } - }); - StringBuilder independentKey = new StringBuilder(); - independentKey.append("{"); - for (int i = 0; i < sortedArguments.size(); i++) { - Map.Entry argument = sortedArguments.get(i); - if (argument.getValue() instanceof Map) { - //noinspection unchecked - final Map objectArg = (Map) argument.getValue(); - independentKey - .append("\"") - .append(argument.getKey()) - .append("\":") - .append(orderIndependentKey(objectArg, variables)); - } else { - independentKey - .append("\"") - .append(argument.getKey()) - .append("\":") - .append(asJsonValue(argument.getValue())); - } - if (i < sortedArguments.size() - 1) { - independentKey.append(","); - } - } - independentKey.append("}"); - return independentKey.toString(); - } - - private String asJsonValue(Object o) { - if (o instanceof Boolean || o instanceof Number) { - return o.toString(); - } else { - return "\"" + o.toString() + "\""; - } - } - - private boolean isArgumentValueVariableType(Map objectMap) { + public static boolean isArgumentValueVariableType(Map objectMap) { return objectMap.containsKey(VARIABLE_IDENTIFIER_KEY) && objectMap.get(VARIABLE_IDENTIFIER_KEY).equals(VARIABLE_IDENTIFIER_VALUE) && objectMap.containsKey(VARIABLE_NAME_KEY); } - private String orderIndependentKeyForVariableArgument(Map objectMap, Operation.Variables variables) { - Object variable = objectMap.get(VARIABLE_NAME_KEY); - //noinspection SuspiciousMethodCalls - Object resolvedVariable = variables.valueMap().get(variable); - if (resolvedVariable == null) { - return null; - } else if (resolvedVariable instanceof Map) { - //noinspection unchecked - return orderIndependentKey((Map) resolvedVariable, variables); - } else { - return asJsonValue(resolvedVariable); - } - } - /** * An abstraction for the field types */ diff --git a/apollo-api/src/test/java/com/apollographql/apollo/api/graphql/CacheKeyForFieldTest.java b/apollo-api/src/test/java/com/apollographql/apollo/api/graphql/CacheKeyForFieldTest.java deleted file mode 100644 index a5dfffdc460..00000000000 --- a/apollo-api/src/test/java/com/apollographql/apollo/api/graphql/CacheKeyForFieldTest.java +++ /dev/null @@ -1,236 +0,0 @@ -package com.apollographql.apollo.api.graphql; - -import com.apollographql.apollo.api.Operation; -import com.apollographql.apollo.api.ResponseField; -import com.apollographql.apollo.api.internal.UnmodifiableMapBuilder; - -import org.junit.Test; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.jetbrains.annotations.NotNull; - -import static com.google.common.truth.Truth.assertThat; - -public class CacheKeyForFieldTest { - - enum Episode { - JEDI - } - - @Test - public void testFieldWithNoArguments() { - ResponseField field = ResponseField.forString("hero", "hero", null, false, - Collections.emptyList()); - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero"); - } - - @Test - public void testFieldWithNoArgumentsWithAlias() { - ResponseField field = ResponseField.forString("r2", "hero", null, false, - Collections.emptyList()); - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero"); - } - - @Test - public void testFieldWithArgument() { - //noinspection unchecked - Map arguments = new UnmodifiableMapBuilder(1) - .put("episode", "JEDI") - .build(); - ResponseField field = createResponseField("hero", "hero", arguments); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); - } - - @Test - public void testFieldWithArgumentAndAlias() { - //noinspection unchecked - Map arguments = new UnmodifiableMapBuilder(1) - .put("episode", "JEDI") - .build(); - ResponseField field = createResponseField("r2", "hero", arguments); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); - } - - @Test - public void testFieldWithVariableArgument() { - //noinspection unchecked - UnmodifiableMapBuilder argument = new UnmodifiableMapBuilder(1) - .put("episode", new UnmodifiableMapBuilder(2) - .put("kind", "Variable") - .put("variableName", "episode") - .build()); - ResponseField field = createResponseField("hero", "hero", argument - .build()); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - HashMap map = new HashMap<>(); - map.put("episode", Episode.JEDI); - return map; - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); - } - - @Test - public void testFieldWithVariableArgumentNull() { - //noinspection unchecked - UnmodifiableMapBuilder argument = new UnmodifiableMapBuilder(1) - .put("episode", new UnmodifiableMapBuilder(2) - .put("kind", "Variable") - .put("variableName", "episode") - .build()); - ResponseField field = createResponseField("hero", "hero", argument - .build()); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - HashMap map = new HashMap<>(); - map.put("episode", null); - return map; - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"episode\":null})"); - } - - @Test - public void testFieldWithMultipleArgument() { - //noinspection unchecked - Map build = new UnmodifiableMapBuilder(1) - .put("episode", "JEDI") - .put("color", "blue") - .build(); - ResponseField field = createResponseField("hero", "hero", build); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"color\":\"blue\",\"episode\":\"JEDI\"})"); - } - - @Test - public void testFieldWithMultipleArgumentsOrderIndependent() { - //noinspection unchecked - Map arguments = new UnmodifiableMapBuilder(1) - .put("episode", "JEDI") - .put("color", "blue") - .build(); - ResponseField field = createResponseField("hero", "hero", arguments); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - - //noinspection unchecked - Map fieldTwoArguments = new UnmodifiableMapBuilder(1) - .put("color", "blue") - .put("episode", "JEDI") - .build(); - ResponseField fieldTwo = createResponseField("hero", "hero", fieldTwoArguments); - - assertThat(fieldTwo.cacheKey(variables)).isEqualTo(field.cacheKey(variables)); - } - - @Test - public void testFieldWithNestedObject() { - //noinspection unchecked - Map arguments = new UnmodifiableMapBuilder(1) - .put("episode", "JEDI") - .put("nested", new UnmodifiableMapBuilder(2) - .put("foo", 1) - .put("bar", 2) - .build()) - .build(); - ResponseField field = createResponseField("hero", "hero", arguments); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"episode\":\"JEDI\",\"nested\":{\"bar\":2,\"foo\":1}})"); - } - - @Test - public void testFieldWithNonPrimitiveValue() { - //noinspection unchecked - ResponseField field = ResponseField.forString("hero", "hero", new UnmodifiableMapBuilder(1) - .put("episode", Episode.JEDI) - .build(), false, Collections.emptyList()); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - return super.valueMap(); - } - }; - assertThat(field.cacheKey(variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); - } - - @Test - public void testFieldWithNestedObjectAndVariables() { - //noinspection unchecked - Map arguments = new UnmodifiableMapBuilder(1) - .put("episode", "JEDI") - .put("nested", new UnmodifiableMapBuilder(2) - .put("foo", new UnmodifiableMapBuilder(2) - .put("kind", "Variable") - .put("variableName", "stars") - .build()) - .put("bar", "2") - .build()) - .build(); - ResponseField field = createResponseField("hero", "hero", arguments); - - Operation.Variables variables = new Operation.Variables() { - @NotNull @Override public Map valueMap() { - HashMap map = new HashMap<>(); - map.put("stars", 1); - return map; - } - }; - assertThat(field.cacheKey(variables)).isEqualTo( - "hero({\"episode\":\"JEDI\",\"nested\":{\"bar\":\"2\",\"foo\":1}})"); - } - - private ResponseField createResponseField(String responseName, String fieldName) { - return createResponseField(responseName, fieldName, null); - } - - private ResponseField createResponseField(String responseName, String fieldName, Map arguments) { - return ResponseField.forString( - responseName, - fieldName, - arguments, - false, - Collections.emptyList()); - } -} diff --git a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/BuilderTypeSpecBuilder.kt b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/BuilderTypeSpecBuilder.kt index ace8f90412d..ea17b34e90a 100644 --- a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/BuilderTypeSpecBuilder.kt +++ b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/BuilderTypeSpecBuilder.kt @@ -92,7 +92,7 @@ class BuilderTypeSpecBuilder( } private fun inputFieldSetterMethodSpecs(): List { - return fields.filter { (_, fieldType) -> fieldType.isOptional(ClassNames.INPUT_TYPE) } + return fields.filter { (_, fieldType) -> fieldType.isOptional(ClassNames.INPUT) } .map { (fieldName, fieldType) -> val javaDoc = fieldJavaDocs[fieldName] inputFieldSetterMethodSpec(fieldName, fieldType, javaDoc) diff --git a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/ClassNames.kt b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/ClassNames.kt index 15f783daf14..acef8892427 100644 --- a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/ClassNames.kt +++ b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/ClassNames.kt @@ -29,9 +29,10 @@ object ClassNames { val JAVA_OPTIONAL: ClassName = ClassName.get("java.util", "Optional") val API_UTILS: ClassName = ClassName.get(Utils::class.java) val FRAGMENT: ClassName = ClassName.get(GraphqlFragment::class.java) - val INPUT_TYPE: ClassName = ClassName.get(Input::class.java) + val INPUT: ClassName = ClassName.get(Input::class.java) val BUILDER: ClassName = ClassName.get("", "Builder") val MUTATOR: ClassName = ClassName.get(Mutator::class.java) + val INPUT_TYPE: ClassName = ClassName.get(InputType::class.java) fun parameterizedListOf(type: Class): TypeName = ParameterizedTypeName.get(LIST, ClassName.get(type)) @@ -65,6 +66,6 @@ object ClassNames { ParameterizedTypeName.get(JAVA_OPTIONAL, type) fun parameterizedInputType(type: TypeName): TypeName = - ParameterizedTypeName.get(INPUT_TYPE, type) + ParameterizedTypeName.get(INPUT, type) } \ No newline at end of file diff --git a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/InputTypeSpecBuilder.kt b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/InputTypeSpecBuilder.kt index 7e437014dcb..eff0b15fc4e 100644 --- a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/InputTypeSpecBuilder.kt +++ b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/InputTypeSpecBuilder.kt @@ -19,6 +19,7 @@ class InputTypeSpecBuilder( TypeSpec.classBuilder(objectClassName) .addAnnotation(Annotations.GENERATED_BY_APOLLO) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addSuperinterface(ClassNames.INPUT_TYPE) .addConstructor() .addFields() .addBuilder() @@ -99,6 +100,7 @@ class InputTypeSpecBuilder( .addMethod(methodSpec) .build() return MethodSpec.methodBuilder(MARSHALLER_PARAM_NAME) + .addAnnotation(Annotations.OVERRIDE) .addModifiers(Modifier.PUBLIC) .returns(InputFieldMarshaller::class.java) .addStatement("return \$L", marshallerType) diff --git a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/OperationTypeSpecBuilder.kt b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/OperationTypeSpecBuilder.kt index 5b309681bac..880369190f9 100644 --- a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/OperationTypeSpecBuilder.kt +++ b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/OperationTypeSpecBuilder.kt @@ -173,7 +173,7 @@ class OperationTypeSpecBuilder( .map { (name, type) -> ParameterSpec.builder(type, name) .apply { - if (type.isOptional(ClassNames.INPUT_TYPE)) { + if (type.isOptional(ClassNames.INPUT)) { addAnnotation(Annotations.NONNULL) } } diff --git a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Util.kt b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Util.kt index d84a966e768..c4ae60ea37e 100644 --- a/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Util.kt +++ b/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Util.kt @@ -252,7 +252,7 @@ fun TypeName.isOptional(expectedOptionalType: ClassName? = null): Boolean { val rawType = (this as? ParameterizedTypeName)?.rawType ?: this return if (expectedOptionalType == null) { rawType == ClassNames.OPTIONAL || rawType == ClassNames.GUAVA_OPTIONAL || rawType == ClassNames.JAVA_OPTIONAL - || rawType == ClassNames.INPUT_TYPE + || rawType == ClassNames.INPUT } else { rawType == expectedOptionalType } @@ -269,7 +269,7 @@ fun TypeName.unwrapOptionalType(withoutAnnotations: Boolean = false): TypeName { fun TypeName.unwrapOptionalValue(varName: String, checkIfPresent: Boolean = true, transformation: ((CodeBlock) -> CodeBlock)? = null): CodeBlock { return if (isOptional() && this is ParameterizedTypeName) { - if (rawType == ClassNames.INPUT_TYPE) { + if (rawType == ClassNames.INPUT) { val valueCode = CodeBlock.of("\$L.value", varName) if (checkIfPresent) { CodeBlock.of("\$L != null ? \$L : null", valueCode, transformation?.invoke(valueCode) ?: valueCode) diff --git a/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ColorInput.java b/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ColorInput.java index 7598d1353ba..b890c7e9988 100644 --- a/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ColorInput.java +++ b/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ColorInput.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Double; @@ -13,7 +14,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class ColorInput { +public final class ColorInput implements InputType { private final int red; private final Input green; @@ -65,6 +66,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ReviewInput.java b/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ReviewInput.java index 06fa8ae6ea8..5348a9361d3 100644 --- a/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ReviewInput.java +++ b/apollo-compiler/src/test/graphql/com/example/input_object_type/type/ReviewInput.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Boolean; @@ -18,7 +19,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class ReviewInput { +public final class ReviewInput implements InputType { private final int stars; private final Input nullableIntFieldWithDefaultValue; @@ -197,6 +198,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ColorInput.java b/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ColorInput.java index 639e76d7ef5..b69a5702666 100644 --- a/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ColorInput.java +++ b/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ColorInput.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Double; @@ -13,7 +14,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class ColorInput { +public final class ColorInput implements InputType { private final int red; private final Input green; @@ -65,6 +66,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ReviewInput.java b/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ReviewInput.java index 4bfe040fefe..928f3114adb 100644 --- a/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ReviewInput.java +++ b/apollo-compiler/src/test/graphql/com/example/mutation_create_review/type/ReviewInput.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Boolean; @@ -18,7 +19,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class ReviewInput { +public final class ReviewInput implements InputType { private final int stars; private final Input nullableIntFieldWithDefaultValue; @@ -197,6 +198,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ColorInput.java b/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ColorInput.java index 79c36baf9cb..3ed9c54676d 100644 --- a/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ColorInput.java +++ b/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ColorInput.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Double; @@ -13,7 +14,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class ColorInput { +public final class ColorInput implements InputType { private final int red; private final Input green; @@ -65,6 +66,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ReviewInput.java b/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ReviewInput.java index 958f4650f60..f1ffcaed344 100644 --- a/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ReviewInput.java +++ b/apollo-compiler/src/test/graphql/com/example/mutation_create_review_semantic_naming/type/ReviewInput.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Boolean; @@ -17,7 +18,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class ReviewInput { +public final class ReviewInput implements InputType { private final int stars; private final Input nullableIntFieldWithDefaultValue; @@ -196,6 +197,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-compiler/src/test/graphql/com/example/reserved_words/type/TestInputType.java b/apollo-compiler/src/test/graphql/com/example/reserved_words/type/TestInputType.java index 08528f401a1..dc0c6022c7c 100644 --- a/apollo-compiler/src/test/graphql/com/example/reserved_words/type/TestInputType.java +++ b/apollo-compiler/src/test/graphql/com/example/reserved_words/type/TestInputType.java @@ -3,6 +3,7 @@ import com.apollographql.apollo.api.Input; import com.apollographql.apollo.api.InputFieldMarshaller; import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; import com.apollographql.apollo.api.internal.Utils; import java.io.IOException; import java.lang.Boolean; @@ -13,7 +14,7 @@ import org.jetbrains.annotations.Nullable; @Generated("Apollo GraphQL") -public final class TestInputType { +public final class TestInputType implements InputType { private final Input private_; private volatile int $hashCode; @@ -32,6 +33,7 @@ public static Builder builder() { return new Builder(); } + @Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override diff --git a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/CacheKeyBuilder.java b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/CacheKeyBuilder.java new file mode 100644 index 00000000000..344ecb99af3 --- /dev/null +++ b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/CacheKeyBuilder.java @@ -0,0 +1,10 @@ +package com.apollographql.apollo.internal.cache.normalized; + +import com.apollographql.apollo.api.Operation; +import com.apollographql.apollo.api.ResponseField; + +import org.jetbrains.annotations.NotNull; + +public interface CacheKeyBuilder { + @NotNull String build(@NotNull ResponseField field, @NotNull Operation.Variables variables); +} diff --git a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealApolloStore.java b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealApolloStore.java index 8a62bf81264..907778cbf55 100644 --- a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealApolloStore.java +++ b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealApolloStore.java @@ -15,10 +15,13 @@ import com.apollographql.apollo.cache.normalized.Record; import com.apollographql.apollo.internal.ApolloLogger; import com.apollographql.apollo.internal.field.CacheFieldValueResolver; -import com.apollographql.apollo.internal.response.RealResponseWriter; import com.apollographql.apollo.internal.response.RealResponseReader; +import com.apollographql.apollo.internal.response.RealResponseWriter; import com.apollographql.apollo.response.ScalarTypeAdapters; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,9 +35,6 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - import static com.apollographql.apollo.api.internal.Utils.checkNotNull; public final class RealApolloStore implements ApolloStore, ReadableStore, WriteableStore { @@ -44,6 +44,7 @@ public final class RealApolloStore implements ApolloStore, ReadableStore, Writea private final ReadWriteLock lock; private final Set subscribers; private final Executor dispatcher; + private final CacheKeyBuilder cacheKeyBuilder; final ApolloLogger logger; public RealApolloStore(@NotNull NormalizedCache normalizedCache, @NotNull CacheKeyResolver cacheKeyResolver, @@ -58,6 +59,7 @@ public RealApolloStore(@NotNull NormalizedCache normalizedCache, @NotNull CacheK this.logger = checkNotNull(logger, "logger == null"); this.lock = new ReentrantReadWriteLock(); this.subscribers = Collections.newSetFromMap(new WeakHashMap()); + this.cacheKeyBuilder = new RealCacheKeyBuilder(); } @Override public ResponseNormalizer> networkResponseNormalizer() { @@ -66,6 +68,10 @@ public RealApolloStore(@NotNull NormalizedCache normalizedCache, @NotNull CacheK @NotNull Map record) { return cacheKeyResolver.fromFieldRecordSet(field, record); } + + @NotNull @Override public CacheKeyBuilder cacheKeyBuilder() { + return cacheKeyBuilder; + } }; } @@ -74,6 +80,10 @@ public RealApolloStore(@NotNull NormalizedCache normalizedCache, @NotNull CacheK @NotNull @Override public CacheKey resolveCacheKey(@NotNull ResponseField field, @NotNull Record record) { return CacheKey.from(record.key()); } + + @NotNull @Override public CacheKeyBuilder cacheKeyBuilder() { + return cacheKeyBuilder; + } }; } @@ -344,7 +354,7 @@ T doRead(final Oper ResponseFieldMapper responseFieldMapper = operation.responseFieldMapper(); CacheFieldValueResolver fieldValueResolver = new CacheFieldValueResolver(cache, operation.variables(), - cacheKeyResolver(), CacheHeaders.NONE); + cacheKeyResolver(), CacheHeaders.NONE, cacheKeyBuilder); //noinspection unchecked RealResponseReader responseReader = new RealResponseReader<>(operation.variables(), rootRecord, fieldValueResolver, scalarTypeAdapters, ResponseNormalizer.NO_OP_NORMALIZER); @@ -364,7 +374,7 @@ Response doRead( } CacheFieldValueResolver fieldValueResolver = new CacheFieldValueResolver(cache, operation.variables(), - cacheKeyResolver(), cacheHeaders); + cacheKeyResolver(), cacheHeaders, cacheKeyBuilder); RealResponseReader responseReader = new RealResponseReader<>(operation.variables(), rootRecord, fieldValueResolver, scalarTypeAdapters, responseNormalizer); try { @@ -393,7 +403,7 @@ F doRead(final ResponseFieldMapper responseFieldM } CacheFieldValueResolver fieldValueResolver = new CacheFieldValueResolver(cache, variables, - cacheKeyResolver(), CacheHeaders.NONE); + cacheKeyResolver(), CacheHeaders.NONE, cacheKeyBuilder); //noinspection unchecked RealResponseReader responseReader = new RealResponseReader<>(variables, rootRecord, fieldValueResolver, scalarTypeAdapters, ResponseNormalizer.NO_OP_NORMALIZER); diff --git a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealCacheKeyBuilder.java b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealCacheKeyBuilder.java new file mode 100644 index 00000000000..0474572b8c1 --- /dev/null +++ b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/RealCacheKeyBuilder.java @@ -0,0 +1,89 @@ +package com.apollographql.apollo.internal.cache.normalized; + +import com.apollographql.apollo.api.InputType; +import com.apollographql.apollo.api.Operation; +import com.apollographql.apollo.api.ResponseField; +import com.apollographql.apollo.internal.json.JsonWriter; +import com.apollographql.apollo.internal.json.SortedInputFieldMapWriter; +import com.apollographql.apollo.internal.json.Utils; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +import okio.Buffer; + +import static com.apollographql.apollo.api.internal.Utils.checkNotNull; + +public class RealCacheKeyBuilder implements CacheKeyBuilder { + private final Comparator argumentNameComparator = new Comparator() { + @Override public int compare(String first, String second) { + return first.compareTo(second); + } + }; + + @NotNull @Override + public String build(@NotNull ResponseField field, @NotNull Operation.Variables variables) { + checkNotNull(field, "field == null"); + checkNotNull(variables, "variables == null"); + + if (field.arguments().isEmpty()) { + return field.fieldName(); + } + + Object resolvedArguments = resolveArguments(field.arguments(), variables); + try { + Buffer buffer = new Buffer(); + JsonWriter jsonWriter = JsonWriter.of(buffer); + jsonWriter.setSerializeNulls(true); + Utils.writeToJson(resolvedArguments, jsonWriter); + jsonWriter.close(); + return String.format("%s(%s)", field.fieldName(), buffer.readUtf8()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Map resolveArguments(Map objectMap, Operation.Variables variables) { + Map result = new TreeMap<>(argumentNameComparator); + for (Map.Entry entry : objectMap.entrySet()) { + if (entry.getValue() instanceof Map) { + Map nestedObjectMap = (Map) entry.getValue(); + if (ResponseField.isArgumentValueVariableType(nestedObjectMap)) { + result.put(entry.getKey(), resolveVariableArgument(nestedObjectMap, variables)); + } else { + result.put(entry.getKey(), resolveArguments(nestedObjectMap, variables)); + } + } else { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + private Object resolveVariableArgument(Map objectMap, Operation.Variables variables) { + Object variable = objectMap.get(ResponseField.VARIABLE_NAME_KEY); + //noinspection SuspiciousMethodCalls + Object resolvedVariable = variables.valueMap().get(variable); + if (resolvedVariable == null) { + return null; + } else if (resolvedVariable instanceof Map) { + //noinspection unchecked + return resolveArguments((Map) resolvedVariable, variables); + } else if (resolvedVariable instanceof InputType) { + try { + SortedInputFieldMapWriter inputFieldMapWriter = new SortedInputFieldMapWriter(argumentNameComparator); + ((InputType) resolvedVariable).marshaller().marshal(inputFieldMapWriter); + return resolveArguments(inputFieldMapWriter.map(), variables); + } catch (IOException e) { + // should never happen + throw new RuntimeException(e); + } + } else { + return resolvedVariable; + } + } +} diff --git a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/ResponseNormalizer.java b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/ResponseNormalizer.java index 6daab980d77..58e6aa438e7 100644 --- a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/ResponseNormalizer.java +++ b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/cache/normalized/ResponseNormalizer.java @@ -44,14 +44,14 @@ public Set dependentKeys() { } @Override public void willResolve(ResponseField field, Operation.Variables variables) { - String key = field.cacheKey(variables); + String key = cacheKeyBuilder().build(field, variables); path.add(key); } @Override public void didResolve(ResponseField field, Operation.Variables variables) { path.remove(path.size() - 1); Object value = valueStack.pop(); - String cacheKey = field.cacheKey(variables); + String cacheKey = cacheKeyBuilder().build(field, variables); String dependentKey = currentRecordBuilder.key() + "." + cacheKey; dependentKeys.add(dependentKey); currentRecordBuilder.addField(cacheKey, value); @@ -113,6 +113,8 @@ public Set dependentKeys() { @NotNull public abstract CacheKey resolveCacheKey(@NotNull ResponseField field, @NotNull R record); + @NotNull public abstract CacheKeyBuilder cacheKeyBuilder(); + void willResolveRecord(CacheKey cacheKey) { pathStack = new SimpleStack<>(); recordStack = new SimpleStack<>(); @@ -178,5 +180,13 @@ private String pathToString() { @NotNull @Override public CacheKey resolveCacheKey(@NotNull ResponseField field, @NotNull Object record) { return CacheKey.NO_KEY; } + + @NotNull @Override public CacheKeyBuilder cacheKeyBuilder() { + return new CacheKeyBuilder() { + @NotNull @Override public String build(@NotNull ResponseField field, @NotNull Operation.Variables variables) { + return CacheKey.NO_KEY.key(); + } + }; + } }; } diff --git a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/field/CacheFieldValueResolver.java b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/field/CacheFieldValueResolver.java index ae91121028c..4d645c64146 100644 --- a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/field/CacheFieldValueResolver.java +++ b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/field/CacheFieldValueResolver.java @@ -8,6 +8,7 @@ import com.apollographql.apollo.cache.normalized.CacheReference; import com.apollographql.apollo.cache.normalized.Record; import com.apollographql.apollo.internal.cache.normalized.ReadableStore; +import com.apollographql.apollo.internal.cache.normalized.CacheKeyBuilder; import java.util.ArrayList; import java.util.List; @@ -17,13 +18,15 @@ public final class CacheFieldValueResolver implements FieldValueResolver private final Operation.Variables variables; private final CacheKeyResolver cacheKeyResolver; private final CacheHeaders cacheHeaders; + private final CacheKeyBuilder cacheKeyBuilder; public CacheFieldValueResolver(ReadableStore readableCache, Operation.Variables variables, - CacheKeyResolver cacheKeyResolver, CacheHeaders cacheHeaders) { + CacheKeyResolver cacheKeyResolver, CacheHeaders cacheHeaders, CacheKeyBuilder cacheKeyBuilder) { this.readableCache = readableCache; this.variables = variables; this.cacheKeyResolver = cacheKeyResolver; this.cacheHeaders = cacheHeaders; + this.cacheKeyBuilder = cacheKeyBuilder; } @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) @@ -93,7 +96,7 @@ private Record valueForObject(Record record, ResponseField field) { @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) private T fieldValue(Record record, ResponseField field) { - String fieldKey = field.cacheKey(variables); + String fieldKey = cacheKeyBuilder.build(field, variables); if (!record.hasField(fieldKey)) { throw new NullPointerException("Missing value: " + field.fieldName()); } diff --git a/apollo-runtime/src/main/java/com/apollographql/apollo/internal/json/SortedInputFieldMapWriter.java b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/json/SortedInputFieldMapWriter.java new file mode 100644 index 00000000000..0cd5065924f --- /dev/null +++ b/apollo-runtime/src/main/java/com/apollographql/apollo/internal/json/SortedInputFieldMapWriter.java @@ -0,0 +1,158 @@ +package com.apollographql.apollo.internal.json; + +import com.apollographql.apollo.api.InputFieldMarshaller; +import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.ScalarType; +import com.apollographql.apollo.api.internal.Utils; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class SortedInputFieldMapWriter implements InputFieldWriter { + final Comparator fieldNameComparator; + final Map buffer; + + public SortedInputFieldMapWriter(@NotNull Comparator fieldNameComparator) { + this.fieldNameComparator = Utils.checkNotNull(fieldNameComparator, "fieldNameComparator == null"); + this.buffer = new TreeMap<>(fieldNameComparator); + } + + public Map map() { + return Collections.unmodifiableMap(buffer); + } + + @Override public void writeString(@NotNull String fieldName, String value) throws IOException { + buffer.put(fieldName, value); + } + + @Override public void writeInt(@NotNull String fieldName, Integer value) throws IOException { + buffer.put(fieldName, value); + } + + @Override public void writeLong(@NotNull String fieldName, Long value) throws IOException { + buffer.put(fieldName, value); + } + + @Override public void writeDouble(@NotNull String fieldName, Double value) throws IOException { + buffer.put(fieldName, value); + } + + @Override public void writeNumber(@NotNull String fieldName, Number value) throws IOException { + buffer.put(fieldName, value); + } + + @Override public void writeBoolean(@NotNull String fieldName, Boolean value) throws IOException { + buffer.put(fieldName, value); + } + + @Override + public void writeCustom(@NotNull String fieldName, ScalarType scalarType, Object value) throws IOException { + buffer.put(fieldName, value); + } + + @Override public void writeObject(@NotNull String fieldName, InputFieldMarshaller marshaller) throws IOException { + if (marshaller == null) { + buffer.put(fieldName, null); + } else { + SortedInputFieldMapWriter nestedWriter = new SortedInputFieldMapWriter(fieldNameComparator); + marshaller.marshal(nestedWriter); + buffer.put(fieldName, nestedWriter.buffer); + } + } + + @Override + public void writeList(@NotNull String fieldName, ListWriter listWriter) throws IOException { + if (listWriter == null) { + buffer.put(fieldName, null); + } else { + ListItemWriter listItemWriter = new ListItemWriter(fieldNameComparator); + listWriter.write(listItemWriter); + buffer.put(fieldName, listItemWriter.list); + } + } + + @Override public void writeMap(@NotNull String fieldName, Map value) throws IOException { + buffer.put(fieldName, value); + } + + @SuppressWarnings("unchecked") + private static class ListItemWriter implements InputFieldWriter.ListItemWriter { + final Comparator fieldNameComparator; + final List list = new ArrayList(); + + ListItemWriter(Comparator fieldNameComparator) { + this.fieldNameComparator = fieldNameComparator; + } + + @Override public void writeString(String value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeInt(Integer value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeLong(Long value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeDouble(Double value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeNumber(Number value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeBoolean(Boolean value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeCustom(ScalarType scalarType, Object value) throws IOException { + if (value != null) { + list.add(value); + } + } + + @Override public void writeObject(InputFieldMarshaller marshaller) throws IOException { + if (marshaller != null) { + SortedInputFieldMapWriter nestedWriter = new SortedInputFieldMapWriter(fieldNameComparator); + marshaller.marshal(nestedWriter); + list.add(nestedWriter.buffer); + } + } + + @Override public void writeList(ListWriter listWriter) throws IOException { + if (listWriter != null) { + ListItemWriter nestedListItemWriter = new ListItemWriter(fieldNameComparator); + listWriter.write(nestedListItemWriter); + list.add(nestedListItemWriter.list); + } + } + + @Override public void writeMap(Map value) throws IOException { + if (value != null) { + list.add(value); + } + } + } +} diff --git a/apollo-runtime/src/test/java/com/apollographql/apollo/internal/cache/normalized/CacheKeyBuilderTest.java b/apollo-runtime/src/test/java/com/apollographql/apollo/internal/cache/normalized/CacheKeyBuilderTest.java new file mode 100644 index 00000000000..36736d23a9f --- /dev/null +++ b/apollo-runtime/src/test/java/com/apollographql/apollo/internal/cache/normalized/CacheKeyBuilderTest.java @@ -0,0 +1,401 @@ +package com.apollographql.apollo.internal.cache.normalized; + +import com.apollographql.apollo.api.InputFieldMarshaller; +import com.apollographql.apollo.api.InputFieldWriter; +import com.apollographql.apollo.api.InputType; +import com.apollographql.apollo.api.Operation; +import com.apollographql.apollo.api.ResponseField; +import com.apollographql.apollo.api.ScalarType; +import com.apollographql.apollo.api.internal.UnmodifiableMapBuilder; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.truth.Truth.assertThat; + +public class CacheKeyBuilderTest { + private final CacheKeyBuilder cacheKeyBuilder = new RealCacheKeyBuilder(); + + enum Episode { + JEDI + } + + @Test + public void testFieldWithNoArguments() { + ResponseField field = ResponseField.forString("hero", "hero", null, false, + Collections.emptyList()); + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero"); + } + + @Test + public void testFieldWithNoArgumentsWithAlias() { + ResponseField field = ResponseField.forString("r2", "hero", null, false, + Collections.emptyList()); + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero"); + } + + @Test + public void testFieldWithArgument() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .build(); + ResponseField field = createResponseField("hero", "hero", arguments); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); + } + + @Test + public void testFieldWithArgumentAndAlias() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .build(); + ResponseField field = createResponseField("r2", "hero", arguments); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); + } + + @Test + public void testFieldWithVariableArgument() { + //noinspection unchecked + UnmodifiableMapBuilder argument = new UnmodifiableMapBuilder(1) + .put("episode", new UnmodifiableMapBuilder(2) + .put("kind", "Variable") + .put("variableName", "episode") + .build()); + ResponseField field = createResponseField("hero", "hero", argument + .build()); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + HashMap map = new HashMap<>(); + map.put("episode", Episode.JEDI); + return map; + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); + } + + @Test + public void testFieldWithVariableArgumentNull() { + //noinspection unchecked + UnmodifiableMapBuilder argument = new UnmodifiableMapBuilder(1) + .put("episode", new UnmodifiableMapBuilder(2) + .put("kind", "Variable") + .put("variableName", "episode") + .build()); + ResponseField field = createResponseField("hero", "hero", argument + .build()); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + HashMap map = new HashMap<>(); + map.put("episode", null); + return map; + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":null})"); + } + + @Test + public void testFieldWithMultipleArgument() { + //noinspection unchecked + Map build = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .put("color", "blue") + .build(); + ResponseField field = createResponseField("hero", "hero", build); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"color\":\"blue\",\"episode\":\"JEDI\"})"); + } + + @Test + public void testFieldWithMultipleArgumentsOrderIndependent() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .put("color", "blue") + .build(); + ResponseField field = createResponseField("hero", "hero", arguments); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + + //noinspection unchecked + Map fieldTwoArguments = new UnmodifiableMapBuilder(1) + .put("color", "blue") + .put("episode", "JEDI") + .build(); + ResponseField fieldTwo = createResponseField("hero", "hero", fieldTwoArguments); + + assertThat(cacheKeyBuilder.build(fieldTwo, variables)).isEqualTo(cacheKeyBuilder.build(field, variables)); + } + + @Test + public void testFieldWithNestedObject() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .put("nested", new UnmodifiableMapBuilder(2) + .put("foo", 1) + .put("bar", 2) + .build()) + .build(); + ResponseField field = createResponseField("hero", "hero", arguments); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":\"JEDI\",\"nested\":{\"bar\":2,\"foo\":1}})"); + } + + @Test + public void testFieldWithNonPrimitiveValue() { + //noinspection unchecked + ResponseField field = ResponseField.forString("hero", "hero", new UnmodifiableMapBuilder(1) + .put("episode", Episode.JEDI) + .build(), false, Collections.emptyList()); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + return super.valueMap(); + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":\"JEDI\"})"); + } + + @Test + public void testFieldWithNestedObjectAndVariables() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .put("nested", new UnmodifiableMapBuilder(2) + .put("foo", new UnmodifiableMapBuilder(2) + .put("kind", "Variable") + .put("variableName", "stars") + .build()) + .put("bar", "2") + .build()) + .build(); + ResponseField field = createResponseField("hero", "hero", arguments); + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + HashMap map = new HashMap<>(); + map.put("stars", 1); + return map; + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo("hero({\"episode\":\"JEDI\",\"nested\":{\"bar\":\"2\",\"foo\":1}})"); + } + + @Test + public void fieldInputTypeArgument() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", "JEDI") + .put("nested", new UnmodifiableMapBuilder(2) + .put("foo", new UnmodifiableMapBuilder(2) + .put("kind", "Variable") + .put("variableName", "testInput") + .build()) + .put("bar", "2") + .build()) + .build(); + ResponseField field = createResponseField("hero", "hero", arguments); + + final InputType testInput = new InputType() { + @NotNull @Override public InputFieldMarshaller marshaller() { + return new InputFieldMarshaller() { + @Override public void marshal(InputFieldWriter writer) throws IOException { + writer.writeString("string", "string"); + writer.writeInt("int", 1); + writer.writeLong("long", 2L); + writer.writeDouble("double", 3D); + writer.writeNumber("number", BigDecimal.valueOf(4)); + writer.writeBoolean("boolean", true); + writer.writeCustom("custom", new ScalarType() { + @Override public String typeName() { + return "EPISODE"; + } + + @Override public Class javaType() { + return String.class; + } + }, "JEDI"); + writer.writeObject("object", new InputFieldMarshaller() { + @Override public void marshal(InputFieldWriter writer) throws IOException { + writer.writeString("string", "string"); + writer.writeInt("int", 1); + } + }); + writer.writeList("list", new InputFieldWriter.ListWriter() { + @Override + public void write(@NotNull InputFieldWriter.ListItemWriter listItemWriter) throws IOException { + listItemWriter.writeString("string"); + listItemWriter.writeInt(1); + listItemWriter.writeLong(2L); + listItemWriter.writeDouble(3D); + listItemWriter.writeNumber(BigDecimal.valueOf(4)); + listItemWriter.writeBoolean(true); + listItemWriter.writeCustom(new ScalarType() { + @Override public String typeName() { + return "EPISODE"; + } + + @Override public Class javaType() { + return String.class; + } + }, "JEDI"); + listItemWriter.writeObject(new InputFieldMarshaller() { + @Override public void marshal(InputFieldWriter writer) throws IOException { + writer.writeString("string", "string"); + writer.writeInt("int", 1); + } + }); + listItemWriter.writeList(new InputFieldWriter.ListWriter() { + @Override + public void write(@NotNull InputFieldWriter.ListItemWriter listItemWriter) throws IOException { + listItemWriter.writeString("string"); + listItemWriter.writeInt(1); + } + }); + } + }); + } + }; + } + }; + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + HashMap map = new HashMap<>(); + map.put("testInput", testInput); + return map; + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo( + "hero({\"episode\":\"JEDI\",\"nested\":{\"bar\":\"2\",\"foo\":{\"boolean\":true,\"custom\":\"JEDI\",\"double\":3.0,\"int\":1,\"list\":[\"string\",1,2,3.0,4,true,\"JEDI\",{\"int\":1,\"string\":\"string\"},[\"string\",1]],\"long\":2,\"number\":4,\"object\":{\"int\":1,\"string\":\"string\"},\"string\":\"string\"}}})"); + } + + @Test + public void testFieldArgumentInputTypeWithNulls() { + //noinspection unchecked + Map arguments = new UnmodifiableMapBuilder(1) + .put("episode", null) + .put("nested", new UnmodifiableMapBuilder(2) + .put("foo", new UnmodifiableMapBuilder(2) + .put("kind", "Variable") + .put("variableName", "testInput") + .build()) + .put("bar", null) + .build()) + .build(); + ResponseField field = createResponseField("hero", "hero", arguments); + + final InputType testInput = new InputType() { + @NotNull @Override public InputFieldMarshaller marshaller() { + return new InputFieldMarshaller() { + @Override public void marshal(InputFieldWriter writer) throws IOException { + writer.writeString("string", null); + writer.writeInt("int", null); + writer.writeLong("long", null); + writer.writeDouble("double", null); + writer.writeNumber("number", null); + writer.writeBoolean("boolean", null); + writer.writeCustom("custom", new ScalarType() { + @Override public String typeName() { + return "EPISODE"; + } + + @Override public Class javaType() { + return String.class; + } + }, null); + writer.writeObject("object", null); + writer.writeList("listNull", null); + writer.writeList("listWithNulls", new InputFieldWriter.ListWriter() { + @Override + public void write(@NotNull InputFieldWriter.ListItemWriter listItemWriter) throws IOException { + listItemWriter.writeString(null); + listItemWriter.writeInt(null); + listItemWriter.writeLong(null); + listItemWriter.writeDouble(null); + listItemWriter.writeNumber(null); + listItemWriter.writeBoolean(null); + listItemWriter.writeCustom(new ScalarType() { + @Override public String typeName() { + return "EPISODE"; + } + + @Override public Class javaType() { + return String.class; + } + }, null); + listItemWriter.writeObject(null); + listItemWriter.writeList(null); + } + }); + writer.writeString("null", null); + } + }; + } + }; + + Operation.Variables variables = new Operation.Variables() { + @NotNull @Override public Map valueMap() { + HashMap map = new HashMap<>(); + map.put("testInput", testInput); + return map; + } + }; + assertThat(cacheKeyBuilder.build(field, variables)).isEqualTo( + "hero({\"episode\":null,\"nested\":{\"bar\":null,\"foo\":{\"boolean\":null,\"custom\":null,\"double\":null,\"int\":null,\"listNull\":null,\"listWithNulls\":[],\"long\":null,\"null\":null,\"number\":null,\"object\":null,\"string\":null}}})"); + } + + private ResponseField createResponseField(String responseName, String fieldName, Map arguments) { + return ResponseField.forString( + responseName, + fieldName, + arguments, + false, + Collections.emptyList()); + } +} diff --git a/apollo-runtime/src/test/java/com/apollographql/apollo/internal/reader/ResponseReaderTest.java b/apollo-runtime/src/test/java/com/apollographql/apollo/internal/reader/ResponseReaderTest.java index cbe2f09c73b..bbdb4aeef20 100644 --- a/apollo-runtime/src/test/java/com/apollographql/apollo/internal/reader/ResponseReaderTest.java +++ b/apollo-runtime/src/test/java/com/apollographql/apollo/internal/reader/ResponseReaderTest.java @@ -2,6 +2,7 @@ import com.google.common.truth.Truth; +import com.apollographql.apollo.internal.cache.normalized.CacheKeyBuilder; import com.apollographql.apollo.response.CustomTypeAdapter; import com.apollographql.apollo.api.Operation; import com.apollographql.apollo.api.OperationName; @@ -917,5 +918,13 @@ private static ScalarType scalarTypeFor(final Class clazz) { @Override public Set dependentKeys() { return Collections.emptySet(); } + + @NotNull @Override public CacheKeyBuilder cacheKeyBuilder() { + return new CacheKeyBuilder() { + @NotNull @Override public String build(@NotNull ResponseField field, @NotNull Operation.Variables variables) { + return CacheKey.NO_KEY.key(); + } + }; + } }; }