Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/main/java/com/stripe/model/StripeContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.stripe.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* StripeContext represents a context path for Stripe API requests.
*
* The context is used to access child accounts by adding segments,
* or parent accounts by removing segments. This class provides an
* immutable interface for manipulating context paths.
*/
public class StripeContext {
private final List<String> segments;

/**
* Initialize a StripeContext with no segments.
*/
public StripeContext() {
this.segments = Collections.emptyList();
}

/**
* Initialize a StripeContext with the given segments.
*
* @param segments List of context path segments
*/
public StripeContext(List<String> segments) {
this.segments = segments != null ?
Collections.unmodifiableList(new ArrayList<>(segments)) :
Collections.emptyList();
}

/**
* Parse a context string into a StripeContext instance.
*
* @param contextString A string like "a/b/c" to be split on "/"
* @return A new StripeContext instance with the parsed segments
*/
public static StripeContext parse(String contextString) {
if (contextString == null || contextString.isEmpty()) {
return new StripeContext();
}
return new StripeContext(Arrays.asList(contextString.split("/")));
}

/**
* Create a new StripeContext with the last segment removed.
*
* @return A new StripeContext instance with one fewer segment
* @throws IllegalStateException If context has no segments to remove
*/
public StripeContext parent() {
if (segments.isEmpty()) {
throw new IllegalStateException("Cannot get parent of empty context");
}
return new StripeContext(segments.subList(0, segments.size() - 1));
}

/**
* Create a new StripeContext with an additional segment appended.
*
* @param segment The segment to append to the context path
* @return A new StripeContext instance with the new segment added
*/
public StripeContext child(String segment) {
List<String> newSegments = new ArrayList<>(segments);
newSegments.add(segment);
return new StripeContext(newSegments);
}

/**
* Convert the StripeContext to its string representation.
*
* @return A string with segments joined by "/"
*/
@Override
public String toString() {
return String.join("/", segments);
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/stripe/model/ThinEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ThinEvent {

/** [Optional] Authentication context needed to fetch the event or related object. */
@SerializedName("context")
public String context;
public StripeContext context;

/** [Optional] Object containing the reference to API resource relevant to the event. */
@SerializedName("related_object")
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/stripe/net/ApiResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private static Gson createGson(boolean shouldSetResponseGetter) {
.registerTypeAdapter(Instant.class, new InstantDeserializer())
.registerTypeAdapterFactory(new EventTypeAdapterFactory())
.registerTypeAdapter(StripeRawJsonObject.class, new StripeRawJsonObjectDeserializer())
.registerTypeAdapter(StripeContext.class, new StripeContextDeserializer())
.registerTypeAdapterFactory(new StripeCollectionItemTypeSettingFactory())
.addReflectionAccessFilter(
new ReflectionAccessFilter() {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/stripe/net/RequestOptions.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.net;

import com.stripe.Stripe;
import com.stripe.model.StripeContext;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.Map;
Expand Down Expand Up @@ -235,6 +236,16 @@ public RequestOptionsBuilder setStripeContext(String context) {
return this;
}

public RequestOptionsBuilder setStripeContext(StripeContext context) {
if (context != null) {
String contextValue = context.toString();
this.stripeContext = !contextValue.isEmpty() ? contextValue : null;
} else {
this.stripeContext = null;
}
return this;
}

public RequestOptionsBuilder clearStripeContext() {
this.stripeContext = null;
return this;
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/stripe/net/StripeContextDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.stripe.net;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.stripe.model.StripeContext;
import java.lang.reflect.Type;

public class StripeContextDeserializer implements JsonDeserializer<StripeContext> {
@Override
public StripeContext deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
if (json.isJsonNull()) {
return null;
}

String contextString = json.getAsString();
return StripeContext.parse(contextString);
}
}
129 changes: 129 additions & 0 deletions src/test/java/com/stripe/model/StripeContextTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.stripe.model;

import static org.junit.jupiter.api.Assertions.*;

import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.Test;

public class StripeContextTest {
@Test
public void testEmptyContext() {
StripeContext context = new StripeContext();
assertEquals("", context.toString());
}

@Test
public void testContextWithSegments() {
StripeContext context = new StripeContext(Arrays.asList("a", "b", "c"));
assertEquals("a/b/c", context.toString());
}

@Test
public void testParseEmptyString() {
StripeContext context = StripeContext.parse("");
assertEquals("", context.toString());
}

@Test
public void testParseNullString() {
StripeContext context = StripeContext.parse(null);
assertEquals("", context.toString());
}

@Test
public void testParseSingleSegment() {
StripeContext context = StripeContext.parse("a");
assertEquals("a", context.toString());
}

@Test
public void testParseMultipleSegments() {
StripeContext context = StripeContext.parse("a/b/c");
assertEquals("a/b/c", context.toString());
}

@Test
public void testParentReturnsNewInstance() {
StripeContext context = StripeContext.parse("a/b/c");
StripeContext parent = context.parent();

// Original unchanged
assertEquals("a/b/c", context.toString());
// New instance with removed segment
assertEquals("a/b", parent.toString());
}

@Test
public void testParentOfSingleSegment() {
StripeContext context = StripeContext.parse("a");
StripeContext parent = context.parent();
assertEquals("", parent.toString());
}

@Test
public void testParentOfEmptyContextThrowsException() {
StripeContext context = new StripeContext();
assertThrows(IllegalStateException.class, () -> context.parent(),
"Cannot get parent of empty context");
}

@Test
public void testChildReturnsNewInstance() {
StripeContext context = StripeContext.parse("a/b");
StripeContext child = context.child("c");

// Original unchanged
assertEquals("a/b", context.toString());
// New instance with added segment
assertEquals("a/b/c", child.toString());
}

@Test
public void testChildOnEmptyContext() {
StripeContext context = new StripeContext();
StripeContext child = context.child("a");
assertEquals("a", child.toString());
}

@Test
public void testMethodChaining() {
StripeContext context = StripeContext.parse("a");
StripeContext result = context.child("b").child("c").parent();
assertEquals("a/b", result.toString());
}

@Test
public void testInitWithNullSegments() {
StripeContext context = new StripeContext(null);
assertEquals("", context.toString());
}

@Test
public void testInitWithEmptyList() {
StripeContext context = new StripeContext(Collections.emptyList());
assertEquals("", context.toString());
}

@Test
public void testEmptyContextDoesNotSetHeader() {
StripeContext emptyContext = new StripeContext();
RequestOptions options = RequestOptions.builder()
.setStripeContext(emptyContext)
.build();

// Empty context should result in null stripeContext
assertNull(options.getStripeContext());
}

@Test
public void testNonEmptyContextSetsHeader() {
StripeContext context = StripeContext.parse("org_123/proj_456");
RequestOptions options = RequestOptions.builder()
.setStripeContext(context)
.build();

// Non-empty context should set the header
assertEquals("org_123/proj_456", options.getStripeContext());
}
}
Loading