diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 668e79aa..37739249 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -32,7 +32,7 @@ jobs: server-password: ${{ secrets.OSSRH_PASSWORD }} - name: Cache local Maven repository - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 1de5cdfa..61177ed6 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -20,12 +20,12 @@ jobs: cache: maven - name: Initialize CodeQL - uses: github/codeql-action/init@f055b5e672ed1ea4fd98a276788e4bcb5a64ad17 + uses: github/codeql-action/init@3d817349a4534f494b019aff837b9a577fdc5496 with: languages: java - name: Cache local Maven repository - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -45,4 +45,4 @@ jobs: verbose: true # optional (default = false) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f055b5e672ed1ea4fd98a276788e4bcb5a64ad17 + uses: github/codeql-action/analyze@3d817349a4534f494b019aff837b9a577fdc5496 diff --git a/.github/workflows/static-code-scanning.yaml b/.github/workflows/static-code-scanning.yaml index a2d896ec..c19702ee 100644 --- a/.github/workflows/static-code-scanning.yaml +++ b/.github/workflows/static-code-scanning.yaml @@ -33,12 +33,12 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f055b5e672ed1ea4fd98a276788e4bcb5a64ad17 + uses: github/codeql-action/init@3d817349a4534f494b019aff837b9a577fdc5496 with: languages: java - name: Autobuild - uses: github/codeql-action/autobuild@f055b5e672ed1ea4fd98a276788e4bcb5a64ad17 + uses: github/codeql-action/autobuild@3d817349a4534f494b019aff837b9a577fdc5496 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f055b5e672ed1ea4fd98a276788e4bcb5a64ad17 + uses: github/codeql-action/analyze@3d817349a4534f494b019aff837b9a577fdc5496 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b4c6198d..63f23943 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{".":"1.7.5"} \ No newline at end of file +{".":"1.7.6"} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b9d7eb..5accb8c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [1.7.6](https://github.com/open-feature/java-sdk/compare/v1.7.5...v1.7.6) (2024-03-22) + + +### ๐Ÿ› Bug Fixes + +* **deps:** update dependency io.cucumber:cucumber-bom to v7.16.0 ([#861](https://github.com/open-feature/java-sdk/issues/861)) ([433f94a](https://github.com/open-feature/java-sdk/commit/433f94a6ea541c5be2ee7a0f902098edce8ba3fc)) +* **deps:** update dependency org.projectlombok:lombok to v1.18.32 ([#854](https://github.com/open-feature/java-sdk/issues/854)) ([ee49872](https://github.com/open-feature/java-sdk/commit/ee49872dd56778ebb4a1ee23b596ffe812dca59c)) +* support immutable maps [#859](https://github.com/open-feature/java-sdk/issues/859) ([#860](https://github.com/open-feature/java-sdk/issues/860)) ([d51cacb](https://github.com/open-feature/java-sdk/commit/d51cacbff6827102d4e3ea5a737bd016d27b1fc2)) +* missing targeting key should return null ([#849](https://github.com/open-feature/java-sdk/issues/849)) ([48a196c](https://github.com/open-feature/java-sdk/commit/48a196c50df992e4ee1006d6b73b619e04f7a224)) + + +### ๐Ÿงน Chore + +* **deps:** update actions/cache digest to 0c45773 ([#853](https://github.com/open-feature/java-sdk/issues/853)) ([8b72323](https://github.com/open-feature/java-sdk/commit/8b723232a15d43980a5c78b5724f91efdfd4e5b4)) +* **deps:** update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.13.0 ([#852](https://github.com/open-feature/java-sdk/issues/852)) ([d23e4d8](https://github.com/open-feature/java-sdk/commit/d23e4d89169b8316b11d48014b43bc4dd14d7e29)) +* **deps:** update dependency org.apache.maven.plugins:maven-gpg-plugin to v3.2.1 ([#850](https://github.com/open-feature/java-sdk/issues/850)) ([b249bfb](https://github.com/open-feature/java-sdk/commit/b249bfb2ceb62178b9776023839e71234f76746b)) +* **deps:** update dependency org.awaitility:awaitility to v4.2.1 ([#845](https://github.com/open-feature/java-sdk/issues/845)) ([5b2813c](https://github.com/open-feature/java-sdk/commit/5b2813c659cdea9f6cdc8a145414de672423c15e)) +* **deps:** update github/codeql-action digest to 09d4101 ([#857](https://github.com/open-feature/java-sdk/issues/857)) ([83d7501](https://github.com/open-feature/java-sdk/commit/83d7501551efad1612f104d6c7e3064c361728c5)) +* **deps:** update github/codeql-action digest to 1ecc277 ([#846](https://github.com/open-feature/java-sdk/issues/846)) ([a763740](https://github.com/open-feature/java-sdk/commit/a763740e6b45b0d51c219675cd9e1839def61b8e)) +* **deps:** update github/codeql-action digest to 294b6df ([#851](https://github.com/open-feature/java-sdk/issues/851)) ([f4e17cc](https://github.com/open-feature/java-sdk/commit/f4e17ccae67cfe08f5e33dba984a4ea6117a9624)) +* **deps:** update github/codeql-action digest to 3d81734 ([#862](https://github.com/open-feature/java-sdk/issues/862)) ([675de14](https://github.com/open-feature/java-sdk/commit/675de140e49bd69860662f28aced3d4ff9344cd5)) +* **deps:** update github/codeql-action digest to 964f5e7 ([#856](https://github.com/open-feature/java-sdk/issues/856)) ([e1e15f4](https://github.com/open-feature/java-sdk/commit/e1e15f4442475e918a1ca5101af8c6ca9a4fa082)) + ## [1.7.5](https://github.com/open-feature/java-sdk/compare/v1.7.4...v1.7.5) (2024-03-14) diff --git a/README.md b/README.md index b6af8d41..36095f06 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ - - Release + + Release @@ -59,7 +59,7 @@ Note that this library is intended to be used in server-side contexts and has no dev.openfeature sdk - 1.7.5 + 1.7.6 ``` @@ -84,7 +84,7 @@ If you would like snapshot builds, this is the relevant repository information: ```groovy dependencies { - implementation 'dev.openfeature:sdk:1.7.5' + implementation 'dev.openfeature:sdk:1.7.6' } ``` diff --git a/pom.xml b/pom.xml index e846254a..845824f6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ dev.openfeature sdk - 1.7.5 + 1.7.6 UTF-8 @@ -45,7 +45,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.32 provided @@ -142,7 +142,7 @@ org.awaitility awaitility - 4.2.0 + 4.2.1 test @@ -154,7 +154,7 @@ io.cucumber cucumber-bom - 7.15.0 + 7.16.0 pom import @@ -229,7 +229,7 @@ maven-compiler-plugin - 3.12.1 + 3.13.0 @@ -472,7 +472,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.0 + 3.2.1 sign-artifacts diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index a7d7c2ea..e50fbe92 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -13,7 +13,7 @@ abstract class AbstractStructure implements Structure { } AbstractStructure(Map attributes) { - this.attributes = attributes; + this.attributes = new HashMap<>(attributes); } /** diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index 632448aa..0d02ba31 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -52,9 +52,12 @@ public ImmutableContext(Map attributes) { */ public ImmutableContext(String targetingKey, Map attributes) { if (targetingKey != null && !targetingKey.trim().isEmpty()) { - attributes.put(TARGETING_KEY, new Value(targetingKey)); + final Map actualAttribs = new HashMap<>(attributes); + actualAttribs.put(TARGETING_KEY, new Value(targetingKey)); + this.structure = new ImmutableStructure(actualAttribs); + } else { + this.structure = new ImmutableStructure(attributes); } - this.structure = new ImmutableStructure(attributes); } /** @@ -62,7 +65,8 @@ public ImmutableContext(String targetingKey, Map attributes) { */ @Override public String getTargetingKey() { - return this.getValue(TARGETING_KEY).asString(); + Value value = this.getValue(TARGETING_KEY); + return value == null ? null : value.asString(); } /** diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 69c22b84..b63f9b31 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -42,10 +42,10 @@ public MutableContext(Map attributes) { * @param attributes evaluation context attributes */ public MutableContext(String targetingKey, Map attributes) { + this.structure = new MutableStructure(attributes); if (targetingKey != null && !targetingKey.trim().isEmpty()) { - attributes.put(TARGETING_KEY, new Value(targetingKey)); + this.structure.attributes.put(TARGETING_KEY, new Value(targetingKey)); } - this.structure = new MutableStructure(attributes); } // override @Delegate methods so that we can use "add" methods and still return MutableContext, not Structure @@ -99,7 +99,8 @@ public void setTargetingKey(String targetingKey) { */ @Override public String getTargetingKey() { - return this.getValue(TARGETING_KEY).asString(); + Value value = this.getValue(TARGETING_KEY); + return value == null ? null : value.asString(); } /** diff --git a/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java b/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java index 7cfedec9..44a6f479 100644 --- a/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java +++ b/src/test/java/dev/openfeature/sdk/ImmutableContextTest.java @@ -1,5 +1,7 @@ package dev.openfeature.sdk; +import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,6 +14,17 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class ImmutableContextTest { + @DisplayName("attributes unable to allow mutation should not affect the immutable context") + @Test + void shouldNotAttemptToModifyAttributesForImmutableContext() { + final Map attributes = new HashMap<>(); + attributes.put("key1", new Value("val1")); + attributes.put("key2", new Value("val2")); + // should check the usage of Map.of() which is a more likely use case, but that API isn't available in Java 8 + EvaluationContext ctx = new ImmutableContext("targeting key", Collections.unmodifiableMap(attributes)); + attributes.put("key3", new Value("val3")); + assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); + } @DisplayName("attributes mutation should not affect the immutable context") @Test @@ -47,6 +60,12 @@ void shouldRetainTargetingKeyWhenOverridingContextTargetingKeyValueIsEmpty() { EvaluationContext merge = ctx.merge(overriding); assertEquals("targeting_key", merge.getTargetingKey()); } + @DisplayName("missing targeting key should return null") + @Test + void missingTargetingKeyShould() { + EvaluationContext ctx = new ImmutableContext(); + assertEquals(null, ctx.getTargetingKey()); + } @DisplayName("Merge should retain all the attributes from the existing context when overriding context is null") @Test diff --git a/src/test/java/dev/openfeature/sdk/MutableContextTest.java b/src/test/java/dev/openfeature/sdk/MutableContextTest.java new file mode 100644 index 00000000..1d462b12 --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/MutableContextTest.java @@ -0,0 +1,119 @@ +package dev.openfeature.sdk; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static dev.openfeature.sdk.EvaluationContext.TARGETING_KEY; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MutableContextTest { + + @DisplayName("attributes unable to allow mutation should not affect the Mutable context") + @Test + void shouldNotAttemptToModifyAttributesForMutableContext() { + final Map attributes = new HashMap<>(); + attributes.put("key1", new Value("val1")); + attributes.put("key2", new Value("val2")); + // should check the usage of Map.of() which is a more likely use case, but that API isn't available in Java 8 + EvaluationContext ctx = new MutableContext("targeting key", Collections.unmodifiableMap(attributes)); + attributes.put("key3", new Value("val3")); + assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, ctx.keySet().toArray()); + } + + @DisplayName("targeting key should be changed from the overriding context") + @Test + void shouldChangeTargetingKeyFromOverridingContext() { + HashMap attributes = new HashMap<>(); + attributes.put("key1", new Value("val1")); + attributes.put("key2", new Value("val2")); + EvaluationContext ctx = new MutableContext("targeting key", attributes); + EvaluationContext overriding = new MutableContext("overriding_key"); + EvaluationContext merge = ctx.merge(overriding); + assertEquals("overriding_key", merge.getTargetingKey()); + } + + @DisplayName("targeting key should not changed from the overriding context if missing") + @Test + void shouldRetainTargetingKeyWhenOverridingContextTargetingKeyValueIsEmpty() { + HashMap attributes = new HashMap<>(); + attributes.put("key1", new Value("val1")); + attributes.put("key2", new Value("val2")); + EvaluationContext ctx = new MutableContext("targeting_key", attributes); + EvaluationContext overriding = new MutableContext(""); + EvaluationContext merge = ctx.merge(overriding); + assertEquals("targeting_key", merge.getTargetingKey()); + } + @DisplayName("missing targeting key should return null") + @Test + void missingTargetingKeyShould() { + EvaluationContext ctx = new MutableContext(); + assertEquals(null, ctx.getTargetingKey()); + } + + @DisplayName("Merge should retain all the attributes from the existing context when overriding context is null") + @Test + void mergeShouldReturnAllTheValuesFromTheContextWhenOverridingContextIsNull() { + HashMap attributes = new HashMap<>(); + attributes.put("key1", new Value("val1")); + attributes.put("key2", new Value("val2")); + EvaluationContext ctx = new MutableContext("targeting_key", attributes); + EvaluationContext merge = ctx.merge(null); + assertEquals("targeting_key", merge.getTargetingKey()); + assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); + } + + @DisplayName("Merge should retain subkeys from the existing context when the overriding context has the same targeting key") + @Test + void mergeShouldRetainItsSubkeysWhenOverridingContextHasTheSameKey() { + HashMap attributes = new HashMap<>(); + HashMap overridingAttributes = new HashMap<>(); + HashMap key1Attributes = new HashMap<>(); + HashMap ovKey1Attributes = new HashMap<>(); + + key1Attributes.put("key1_1", new Value("val1_1")); + attributes.put("key1", new Value(new ImmutableStructure(key1Attributes))); + attributes.put("key2", new Value("val2")); + ovKey1Attributes.put("overriding_key1_1", new Value("overriding_val_1_1")); + overridingAttributes.put("key1", new Value(new ImmutableStructure(ovKey1Attributes))); + + EvaluationContext ctx = new MutableContext("targeting_key", attributes); + EvaluationContext overriding = new MutableContext("targeting_key", overridingAttributes); + EvaluationContext merge = ctx.merge(overriding); + assertEquals("targeting_key", merge.getTargetingKey()); + assertArrayEquals(new Object[]{"key1", "key2", TARGETING_KEY}, merge.keySet().toArray()); + + Value key1 = merge.getValue("key1"); + assertTrue(key1.isStructure()); + + Structure value = key1.asStructure(); + assertArrayEquals(new Object[]{"key1_1","overriding_key1_1"}, value.keySet().toArray()); + } + + @DisplayName("Merge should retain subkeys from the existing context when the overriding context doesn't have targeting key") + @Test + void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() { + HashMap attributes = new HashMap<>(); + HashMap key1Attributes = new HashMap<>(); + + key1Attributes.put("key1_1", new Value("val1_1")); + attributes.put("key1", new Value(new ImmutableStructure(key1Attributes))); + attributes.put("key2", new Value("val2")); + + EvaluationContext ctx = new MutableContext(attributes); + EvaluationContext overriding = new MutableContext(); + EvaluationContext merge = ctx.merge(overriding); + assertArrayEquals(new Object[]{"key1", "key2"}, merge.keySet().toArray()); + + Value key1 = merge.getValue("key1"); + assertTrue(key1.isStructure()); + + Structure value = key1.asStructure(); + assertArrayEquals(new Object[]{"key1_1"}, value.keySet().toArray()); + } +} diff --git a/version.txt b/version.txt index 6a126f40..de28578a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.7.5 +1.7.6