diff --git a/src/main/java/dev/braintrust/config/BaseConfig.java b/src/main/java/dev/braintrust/config/BaseConfig.java index 66038704..6b00d152 100644 --- a/src/main/java/dev/braintrust/config/BaseConfig.java +++ b/src/main/java/dev/braintrust/config/BaseConfig.java @@ -5,7 +5,9 @@ import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import lombok.EqualsAndHashCode; +@EqualsAndHashCode class BaseConfig { /** Sentinal used to set null in the env. Only used for testing. */ static final String NULL_OVERRIDE = "BRAINTRUST_NULL_SENTINAL_" + System.currentTimeMillis(); diff --git a/src/main/java/dev/braintrust/config/BraintrustConfig.java b/src/main/java/dev/braintrust/config/BraintrustConfig.java index 2ae84b32..14a871e6 100644 --- a/src/main/java/dev/braintrust/config/BraintrustConfig.java +++ b/src/main/java/dev/braintrust/config/BraintrustConfig.java @@ -95,4 +95,87 @@ public URI fetchProjectURI() { var orgAndProject = client.getProjectAndOrgInfo().orElseThrow(); return BraintrustUtils.createProjectURI(appUrl(), orgAndProject); } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final Map envOverrides = new HashMap<>(); + + public Builder apiKey(String value) { + envOverrides.put("BRAINTRUST_API_KEY", value); + return this; + } + + public Builder apiUrl(String value) { + envOverrides.put("BRAINTRUST_API_URL", value); + return this; + } + + public Builder appUrl(String value) { + envOverrides.put("BRAINTRUST_APP_URL", value); + return this; + } + + public Builder tracesPath(String value) { + envOverrides.put("BRAINTRUST_TRACES_PATH", value); + return this; + } + + public Builder logsPath(String value) { + envOverrides.put("BRAINTRUST_LOGS_PATH", value); + return this; + } + + public Builder defaultProjectId(String value) { + if (value != null) { + envOverrides.put("BRAINTRUST_DEFAULT_PROJECT_ID", value); + } else { + envOverrides.put("BRAINTRUST_DEFAULT_PROJECT_ID", NULL_OVERRIDE); + } + return this; + } + + public Builder defaultProjectName(String value) { + if (value != null) { + envOverrides.put("BRAINTRUST_DEFAULT_PROJECT_NAME", value); + } else { + envOverrides.put("BRAINTRUST_DEFAULT_PROJECT_NAME", NULL_OVERRIDE); + } + return this; + } + + public Builder enableTraceConsoleLog(boolean value) { + envOverrides.put("BRAINTRUST_ENABLE_TRACE_CONSOLE_LOG", String.valueOf(value)); + return this; + } + + public Builder debug(boolean value) { + envOverrides.put("BRAINTRUST_DEBUG", String.valueOf(value)); + return this; + } + + public Builder requestTimeout(Duration value) { + envOverrides.put("BRAINTRUST_REQUEST_TIMEOUT", String.valueOf(value.getSeconds())); + return this; + } + + // hiding visibility. only used for testing + Builder experimentalOtelLogs(boolean value) { + envOverrides.put("BRAINTRUST_X_OTEL_LOGS", String.valueOf(value)); + return this; + } + + // hiding visibility. only used for testing + Builder exportSpansInMemoryForUnitTest(boolean value) { + envOverrides.put( + "BRAINTRUST_JAVA_EXPORT_SPANS_IN_MEMORY_FOR_UNIT_TEST", String.valueOf(value)); + return this; + } + + public BraintrustConfig build() { + return new BraintrustConfig(envOverrides); + } + } } diff --git a/src/test/java/dev/braintrust/config/BaseConfigTest.java b/src/test/java/dev/braintrust/config/BaseConfigTest.java index 7402a77c..752df36d 100644 --- a/src/test/java/dev/braintrust/config/BaseConfigTest.java +++ b/src/test/java/dev/braintrust/config/BaseConfigTest.java @@ -182,6 +182,15 @@ void testIntegrationWithAllTypes() { assertEquals(2.71828, config.getConfig("DOUBLE_VAR", 0.0), 0.00001); } + @Test + void testEquals() { + var cfg1 = new TestConfig(Map.of("FOO", "bar")); + var cfg2 = new TestConfig(Map.of("FOO", "bar")); + var cfg3 = new TestConfig(Map.of("FOO", "baz")); + assertEquals(cfg1, cfg2); + assertNotEquals(cfg1, cfg3); + } + static class TestConfig extends BaseConfig { TestConfig(Map envOverrides) { super(envOverrides); diff --git a/src/test/java/dev/braintrust/config/BraintrustConfigTest.java b/src/test/java/dev/braintrust/config/BraintrustConfigTest.java index d93ce4e1..8867671a 100644 --- a/src/test/java/dev/braintrust/config/BraintrustConfigTest.java +++ b/src/test/java/dev/braintrust/config/BraintrustConfigTest.java @@ -1,7 +1,13 @@ package dev.braintrust.config; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; class BraintrustConfigTest { @@ -28,4 +34,42 @@ void parentUsesProjectId() { "project_id:" + defaultConfig.defaultProjectId().orElseThrow(), defaultConfig.getBraintrustParentValue().orElseThrow()); } + + @Test + public void testBuilderEqualsEnv() { + var fromEnv = + BraintrustConfig.of( + "BRAINTRUST_API_KEY", "testkey", + "BRAINTRUST_DEFAULT_PROJECT_ID", "unit-test"); + var fromBuilder = + BraintrustConfig.builder().apiKey("testkey").defaultProjectId("unit-test").build(); + var otherBuilder = + BraintrustConfig.builder().apiKey("otherkey").defaultProjectId("unit-test").build(); + assertEquals(fromEnv, fromBuilder); + assertNotEquals(fromEnv, otherBuilder); + } + + @Test + public void testBuilderHasMethodForEveryField() { + List fieldsToSkip = List.of("envOverrides"); + // Get all fields from BraintrustConfig + Field[] configFields = BraintrustConfig.class.getDeclaredFields(); + + // Get all methods from Builder + Method[] builderMethods = BraintrustConfig.Builder.class.getDeclaredMethods(); + Set builderMethodNames = + Arrays.stream(builderMethods).map(Method::getName).collect(Collectors.toSet()); + + // For each field, verify there's a corresponding builder method + for (Field field : configFields) { + String configFieldName = field.getName(); + // Skip internal fields + if (fieldsToSkip.contains(configFieldName)) { + continue; + } + assertTrue( + builderMethodNames.contains(configFieldName), + "Builder is missing method for field: " + configFieldName); + } + } }