Skip to content

Commit c8dc171

Browse files
Add StripeContext object (#2057)
* add stripe context * clean up context creation * remove comment * make context public * pr feedback
1 parent babe753 commit c8dc171

File tree

11 files changed

+553
-6
lines changed

11 files changed

+553
-6
lines changed

src/main/java/com/stripe/StripeClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,11 @@ public StripeClientBuilder setStripeContext(String context) {
12231223
return this;
12241224
}
12251225

1226+
public StripeClientBuilder setStripeContext(StripeContext context) {
1227+
this.stripeContext = context == null ? null : context.toString();
1228+
return this;
1229+
}
1230+
12261231
public String getStripeContext() {
12271232
return this.stripeContext;
12281233
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.stripe;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import lombok.EqualsAndHashCode;
8+
9+
/**
10+
* The StripeContext class provides an immutable container for interacting with the `Stripe-Context`
11+
* header.
12+
*
13+
* <p>You can use it whenever you're initializing a `StripeClient` or sending `stripe_context` with
14+
* a request. It's also found in the `EventNotification.context` property.
15+
*/
16+
@EqualsAndHashCode
17+
public final class StripeContext {
18+
private final List<String> segments;
19+
20+
/** Creates a new StripeContext with no segments. */
21+
public StripeContext() {
22+
this(null);
23+
}
24+
25+
/**
26+
* Creates a new StripeContext with the specified segments.
27+
*
28+
* @param segments the list of context segments
29+
*/
30+
public StripeContext(List<String> segments) {
31+
this.segments =
32+
segments == null
33+
? Collections.emptyList()
34+
: Collections.unmodifiableList(new ArrayList<>(segments));
35+
}
36+
37+
/**
38+
* Returns a new StripeContext with the given segment added to the end.
39+
*
40+
* @param segment the segment to add
41+
* @return a new StripeContext instance with the segment appended
42+
*/
43+
public StripeContext push(String segment) {
44+
List<String> newSegments = new ArrayList<>(this.segments);
45+
newSegments.add(segment);
46+
return new StripeContext(newSegments);
47+
}
48+
49+
/**
50+
* Returns a new StripeContext with the last segment removed.
51+
*
52+
* @return a new StripeContext instance with the last segment removed
53+
*/
54+
public StripeContext pop() {
55+
if (segments.isEmpty()) {
56+
throw new IllegalStateException("Cannot pop from an empty StripeContext");
57+
}
58+
59+
List<String> newSegments = new ArrayList<>(this.segments);
60+
newSegments.remove(newSegments.size() - 1);
61+
return new StripeContext(newSegments);
62+
}
63+
64+
/**
65+
* Converts the context to a string by joining segments with '/'.
66+
*
67+
* @return string representation of the context segments joined by '/', `null` if there are no
68+
* segments (useful for clearing context)
69+
*/
70+
@Override
71+
public String toString() {
72+
return String.join("/", segments);
73+
}
74+
75+
/**
76+
* Parse a context string into a StripeContext instance.
77+
*
78+
* @param contextStr string to parse (segments separated by '/')
79+
* @return StripeContext instance with segments from the string
80+
*/
81+
public static StripeContext parse(String contextStr) {
82+
if (contextStr == null || contextStr.isEmpty()) {
83+
return new StripeContext();
84+
}
85+
86+
List<String> segments = Arrays.asList(contextStr.split("/"));
87+
return new StripeContext(segments);
88+
}
89+
90+
/**
91+
* Returns an unmodifiable list of the current segments.
92+
*
93+
* @return the list of segments
94+
*/
95+
public List<String> getSegments() {
96+
return segments;
97+
}
98+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.stripe.model;
2+
3+
import com.google.gson.JsonDeserializationContext;
4+
import com.google.gson.JsonDeserializer;
5+
import com.google.gson.JsonElement;
6+
import com.google.gson.JsonParseException;
7+
import com.stripe.StripeContext;
8+
import java.lang.reflect.Type;
9+
10+
public class StripeContextDeserializer implements JsonDeserializer<StripeContext> {
11+
12+
@Override
13+
public StripeContext deserialize(
14+
JsonElement json, Type typeOfT, JsonDeserializationContext context)
15+
throws JsonParseException {
16+
if (json == null || json.isJsonNull()) {
17+
return null;
18+
}
19+
20+
String contextString = json.getAsString().trim();
21+
if (contextString.isEmpty()) {
22+
return null;
23+
}
24+
return StripeContext.parse(contextString);
25+
}
26+
}

src/main/java/com/stripe/model/v2/EventNotification.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.gson.JsonObject;
44
import com.google.gson.annotations.SerializedName;
55
import com.stripe.StripeClient;
6+
import com.stripe.StripeContext;
67
import com.stripe.exception.StripeException;
78
import com.stripe.model.StripeObject;
89
import com.stripe.model.v2.Event.RelatedObject;
@@ -71,7 +72,7 @@ public static class Reason {
7172

7273
/** [Optional] Authentication context needed to fetch the event or related object. */
7374
@SerializedName("context")
74-
public String context;
75+
public StripeContext context;
7576

7677
/** [Optional] Reason for the event. */
7778
@SerializedName("reason")
@@ -98,14 +99,17 @@ public static EventNotification fromJson(String payload, StripeClient client) {
9899

99100
EventNotification e = ApiResource.GSON.fromJson(payload, cls);
100101
e.client = client;
102+
101103
return e;
102104
}
103105

104106
private RawRequestOptions getRequestOptions() {
105107
if (context == null) {
106108
return null;
107109
}
108-
return new RawRequestOptions.RawRequestOptionsBuilder().setStripeContext(context).build();
110+
return new RawRequestOptions.RawRequestOptionsBuilder()
111+
.setStripeContext(context.toString())
112+
.build();
109113
}
110114

111115
/* retrieves the full payload for an event. Protected because individual push classes use it, but type it correctly */

src/main/java/com/stripe/net/ApiResource.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.stripe.net;
22

33
import com.google.gson.*;
4+
import com.stripe.StripeContext;
45
import com.stripe.exception.InvalidRequestException;
56
import com.stripe.model.*;
67
import com.stripe.model.v2.EventTypeAdapterFactory;
@@ -54,6 +55,7 @@ private static Gson createGson(boolean shouldSetResponseGetter) {
5455
.registerTypeAdapter(EphemeralKey.class, new EphemeralKeyDeserializer())
5556
.registerTypeAdapter(Event.Data.class, new EventDataDeserializer())
5657
.registerTypeAdapter(Event.Request.class, new EventRequestDeserializer())
58+
.registerTypeAdapter(StripeContext.class, new StripeContextDeserializer())
5759
.registerTypeAdapter(ExpandableField.class, new ExpandableFieldDeserializer())
5860
.registerTypeAdapter(Instant.class, new InstantDeserializer())
5961
.registerTypeAdapterFactory(new EventTypeAdapterFactory())

src/main/java/com/stripe/net/RawRequestOptions.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.stripe.net;
22

3+
import com.stripe.StripeContext;
34
import java.net.PasswordAuthentication;
45
import java.net.Proxy;
56
import java.util.Map;
@@ -81,6 +82,12 @@ public RawRequestOptionsBuilder setStripeContext(String stripeContext) {
8182
return this;
8283
}
8384

85+
@Override
86+
public RawRequestOptionsBuilder setStripeContext(StripeContext stripeContext) {
87+
super.setStripeContext(stripeContext);
88+
return this;
89+
}
90+
8491
@Override
8592
public RawRequestOptionsBuilder setStripeAccount(String stripeAccount) {
8693
super.setStripeAccount(stripeAccount);

src/main/java/com/stripe/net/RequestOptions.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ public RequestOptionsBuilder setStripeContext(String context) {
235235
return this;
236236
}
237237

238+
public RequestOptionsBuilder setStripeContext(com.stripe.StripeContext context) {
239+
this.stripeContext = context != null ? context.toString() : null;
240+
return this;
241+
}
242+
243+
/**
244+
* Empties the current builder value for StripeContext, which will defer to the client options.
245+
*
246+
* <p>To send no context at all, call `setContext(new StripeContext())`or set the context to an
247+
* empty string.
248+
*/
238249
public RequestOptionsBuilder clearStripeContext() {
239250
this.stripeContext = null;
240251
return this;
@@ -472,15 +483,28 @@ static RequestOptions merge(StripeResponseGetterOptions clientOptions, RequestOp
472483
clientOptions.getProxyCredential() // proxyCredential
473484
);
474485
}
486+
487+
// callers need to be able to explicitly unset context per-request
488+
// an empty StripeContext serializes to a "", so check for that and empty context out if it's
489+
// there.
490+
String stripeContext;
491+
if (options.getStripeContext() != null) {
492+
String requestContext = options.getStripeContext().trim();
493+
if (requestContext.isEmpty()) {
494+
stripeContext = null;
495+
} else {
496+
stripeContext = requestContext;
497+
}
498+
} else {
499+
stripeContext = clientOptions.getStripeContext();
500+
}
475501
return new RequestOptions(
476502
options.getAuthenticator() != null
477503
? options.getAuthenticator()
478504
: clientOptions.getAuthenticator(),
479505
options.getClientId() != null ? options.getClientId() : clientOptions.getClientId(),
480506
options.getIdempotencyKey(),
481-
options.getStripeContext() != null
482-
? options.getStripeContext()
483-
: clientOptions.getStripeContext(),
507+
stripeContext,
484508
options.getStripeAccount() != null
485509
? options.getStripeAccount()
486510
: clientOptions.getStripeAccount(),

src/test/java/com/stripe/StripeClientTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ public void parsesEventNotificationWithRelatedObject()
248248
assertEquals("evt_234", eventNotification.getId());
249249
assertEquals("v1.billing.meter.error_report_triggered", eventNotification.getType());
250250
assertEquals(Instant.parse("2022-02-15T00:27:45.330Z"), eventNotification.created);
251-
assertEquals("org_123", eventNotification.context);
251+
assertEquals("org_123", eventNotification.getContext().toString());
252252
assertInstanceOf(V1BillingMeterErrorReportTriggeredEventNotification.class, eventNotification);
253253
assertEquals("request", eventNotification.getReason().getType());
254254
assertEquals("abc123", eventNotification.getReason().getRequest().getId());

0 commit comments

Comments
 (0)