Skip to content

Commit 18cf2a4

Browse files
authored
feat: add flag to CacheControlInstrumentation to optionally allow max-age of zero (#392)
This change adds an optional flag to `CacheControlInstrumentation`, `allowZeroMaxAge`, to allow consumers to configure the behavior such that resolved header can validly resolve a header value of `max-age=0`.
1 parent 0f9eca8 commit 18cf2a4

File tree

2 files changed

+83
-14
lines changed

2 files changed

+83
-14
lines changed

graphql-java-support/src/main/java/com/apollographql/federation/graphqljava/caching/CacheControlInstrumentation.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
*/
3030
public class CacheControlInstrumentation extends SimplePerformantInstrumentation {
3131
private final int defaultMaxAge;
32+
private final boolean allowZeroMaxAge;
3233

3334
private static final Object CONTEXT_KEY = new Object();
3435
private static final String DIRECTIVE_NAME = "cacheControl";
@@ -37,11 +38,16 @@ public class CacheControlInstrumentation extends SimplePerformantInstrumentation
3738
private static final String INHERIT_MAX_AGE = "inheritMaxAge";
3839

3940
public CacheControlInstrumentation() {
40-
this(0);
41+
this(0, false);
4142
}
4243

4344
public CacheControlInstrumentation(int defaultMaxAge) {
45+
this(defaultMaxAge, false);
46+
}
47+
48+
public CacheControlInstrumentation(int defaultMaxAge, boolean allowZeroMaxAge) {
4449
this.defaultMaxAge = defaultMaxAge;
50+
this.allowZeroMaxAge = allowZeroMaxAge;
4551
}
4652

4753
@Nullable
@@ -51,7 +57,7 @@ public static String cacheControlHeaderFromGraphQLContext(GraphQLContext context
5157

5258
@Override
5359
public InstrumentationState createState(InstrumentationCreateStateParameters parameters) {
54-
return new CacheControlState();
60+
return new CacheControlState(allowZeroMaxAge);
5561
}
5662

5763
@Override
@@ -78,7 +84,7 @@ public void onCompleted(ExecutionResult executionResult, Throwable throwable) {
7884
public InstrumentationContext<ExecutionResult> beginField(
7985
InstrumentationFieldParameters parameters, InstrumentationState state) {
8086
CacheControlState cacheControlState = (CacheControlState) state;
81-
CacheControlPolicy fieldPolicy = new CacheControlPolicy();
87+
CacheControlPolicy fieldPolicy = new CacheControlPolicy(allowZeroMaxAge);
8288
boolean inheritMaxAge = false;
8389

8490
GraphQLUnmodifiedType unwrappedFieldType =
@@ -172,12 +178,21 @@ enum CacheControlScope {
172178
}
173179

174180
private static class CacheControlState implements InstrumentationState {
175-
public final CacheControlPolicy overallPolicy = new CacheControlPolicy();
181+
public final CacheControlPolicy overallPolicy;
182+
183+
public CacheControlState(boolean allowZeroMaxAge) {
184+
this.overallPolicy = new CacheControlPolicy(allowZeroMaxAge);
185+
}
176186
}
177187

178188
private static class CacheControlPolicy {
179189
@Nullable private Integer maxAge;
180190
@Nullable private CacheControlScope scope = CacheControlScope.PUBLIC;
191+
private final boolean allowZeroMaxAge;
192+
193+
public CacheControlPolicy(boolean allowZeroMaxAge) {
194+
this.allowZeroMaxAge = allowZeroMaxAge;
195+
}
181196

182197
void restrict(CacheControlPolicy policy) {
183198
if (policy.maxAge != null && (maxAge == null || policy.maxAge < maxAge)) {
@@ -222,14 +237,14 @@ void replace(@Nullable CacheControlScope scope) {
222237
}
223238

224239
public Optional<String> maybeAsString() {
225-
Integer maxAgeValue = maxAge == null ? 0 : maxAge;
226-
if (maxAgeValue.equals(0)) {
240+
if (maxAge == null || (!allowZeroMaxAge && maxAge.equals(0))) {
227241
return Optional.empty();
228242
}
229243

230244
CacheControlScope scopeValue = scope == null ? CacheControlScope.PUBLIC : scope;
245+
231246
return Optional.of(
232-
String.format("max-age=%d, %s", maxAgeValue, scopeValue.toString().toLowerCase()));
247+
String.format("max-age=%d, %s", maxAge, scopeValue.toString().toLowerCase()));
233248
}
234249

235250
public boolean hasMaxAge() {

graphql-java-support/src/test/java/com/apollographql/federation/graphqljava/caching/CacheControlInstrumentationTest.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class CacheControlInstrumentationTest {
2929
"enum CacheControlScope { PUBLIC PRIVATE }\n"
3030
+ "directive @cacheControl(maxAge: Int scope: CacheControlScope inheritMaxAge: Boolean) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION\n";
3131

32-
static GraphQL makeExecutor(String sdl, int defaultMaxAge) {
32+
static GraphQL makeExecutor(String sdl, int defaultMaxAge, boolean allowZeroMaxAge) {
3333
TypeDefinitionRegistry typeDefs = new SchemaParser().parse(DIRECTIVE_DEF + sdl);
3434

3535
RuntimeWiring resolvers =
@@ -42,21 +42,30 @@ static GraphQL makeExecutor(String sdl, int defaultMaxAge) {
4242
.build();
4343

4444
return GraphQL.newGraphQL(schema)
45-
.instrumentation(new CacheControlInstrumentation(defaultMaxAge))
45+
.instrumentation(new CacheControlInstrumentation(defaultMaxAge, allowZeroMaxAge))
4646
.build();
4747
}
4848

4949
static @Nullable String execute(String schema, String query) {
50-
return execute(schema, query, 0, new HashMap<>());
50+
return execute(schema, query, 0, false, new HashMap<>());
5151
}
5252

5353
static @Nullable String execute(String schema, String query, int defaultMaxAge) {
54-
return execute(schema, query, defaultMaxAge, new HashMap<>());
54+
return execute(schema, query, defaultMaxAge, false, new HashMap<>());
5555
}
5656

5757
static @Nullable String execute(
58-
String schema, String query, int defaultMaxAge, Map<String, Object> variables) {
59-
GraphQL graphql = makeExecutor(schema, defaultMaxAge);
58+
String schema, String query, int defaultMaxAge, boolean allowZeroMaxAge) {
59+
return execute(schema, query, defaultMaxAge, allowZeroMaxAge, new HashMap<>());
60+
}
61+
62+
static @Nullable String execute(
63+
String schema,
64+
String query,
65+
int defaultMaxAge,
66+
boolean allowZeroMaxAge,
67+
Map<String, Object> variables) {
68+
GraphQL graphql = makeExecutor(schema, defaultMaxAge, allowZeroMaxAge);
6069

6170
ExecutionInput input =
6271
ExecutionInput.newExecutionInput().query(query).variables(variables).build();
@@ -204,6 +213,51 @@ void overrideDefaultMaxAge() {
204213
assertNull(execute(schema, query, 10));
205214
}
206215

216+
@Test
217+
void overrideDefaultMaxAgeAllowZero() {
218+
String schema =
219+
"type Query {"
220+
+ " droid(id: ID!): Droid"
221+
+ "}"
222+
+ ""
223+
+ "type Droid @cacheControl(maxAge: 0) {"
224+
+ " id: ID!"
225+
+ " name: String"
226+
+ "}";
227+
String query = "{ droid(id: 2001) { name } }";
228+
assertEquals("max-age=0, public", execute(schema, query, 10, true));
229+
}
230+
231+
@Test
232+
void defaultMaxAgeAllowZero() {
233+
String schema =
234+
"type Query {"
235+
+ " droid(id: ID!): Droid"
236+
+ "}"
237+
+ ""
238+
+ "type Droid {"
239+
+ " id: ID!"
240+
+ " name: String"
241+
+ "}";
242+
String query = "{ droid(id: 2001) { name } }";
243+
assertEquals("max-age=0, public", execute(schema, query, 0, true));
244+
}
245+
246+
@Test
247+
void noMaxAgeWithDefaultMaxAge() {
248+
String schema =
249+
"type Query {"
250+
+ " droid(id: ID!): Droid"
251+
+ "}"
252+
+ ""
253+
+ "type Droid {"
254+
+ " id: ID!"
255+
+ " name: String"
256+
+ "}";
257+
String query = "{ droid(id: 2001) { name } }";
258+
assertEquals("max-age=10, public", execute(schema, query, 10, false));
259+
}
260+
207261
@Test
208262
void hintOnFieldOverrideMaxAgeHintOnReturnType() {
209263
String schema =
@@ -380,6 +434,6 @@ void entities() {
380434
rs.add(user);
381435

382436
variables.put("rs", rs);
383-
assertEquals("max-age=30, public", execute(schema, query, 0, variables));
437+
assertEquals("max-age=30, public", execute(schema, query, 0, false, variables));
384438
}
385439
}

0 commit comments

Comments
 (0)