From da7f22156f7d62833fe112209234f6f3f55b7bac Mon Sep 17 00:00:00 2001 From: Parth Suthar Date: Fri, 1 Aug 2025 17:12:46 -0400 Subject: [PATCH 1/5] feat: add config metadata parsing through wasm --- build.gradle | 2 +- .../local/bucketing/LocalBucketing.java | 10 ++++ .../managers/EnvironmentConfigManager.java | 45 +++++++----------- .../server/local/model/ConfigMetadata.java | 14 +++--- .../local/model/EnvironmentMetadata.java | 6 ++- .../server/local/model/ProjectMetadata.java | 6 ++- src/main/resources/bucketing-lib.release.wasm | Bin 223167 -> 224670 bytes .../server/local/DevCycleLocalClientTest.java | 31 +++--------- 8 files changed, 51 insertions(+), 63 deletions(-) diff --git a/build.gradle b/build.gradle index 359b541a..9eaa6752 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 def wasmResourcePath = "$projectDir/src/main/resources" -def wasmVersion = "1.41.0" +def wasmVersion = "1.42.1" def wasmUrl = "https://unpkg.com/@devcycle/bucketing-assembly-script@$wasmVersion/build/bucketing-lib.release.wasm" task downloadDVCBucketingWASM(type: Download) { src wasmUrl diff --git a/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java b/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java index ff97a7bb..923b647a 100644 --- a/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java +++ b/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java @@ -382,5 +382,15 @@ private int getSDKKeyAddress(String sdkKey) { return sdkKeyAddresses.get(sdkKey); } + + public String getConfigMetadata(String sdkKey) { + int sdkKeyAddress = getSDKKeyAddress(sdkKey); + Func getConfigMetadataPtr = linker.get(store, "", "getConfigMetadata").get().func(); + WasmFunctions.Function1 getConfigMetadata = WasmFunctions.func( + store, getConfigMetadataPtr, I32, I32); + + int resultAddress = getConfigMetadata.call(sdkKeyAddress); + return readWasmString(resultAddress); + } } diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index 2dafbc3f..b18f0173 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -39,7 +39,8 @@ public final class EnvironmentConfigManager { private final DevCycleLocalOptions options; private ProjectConfig config; - private ConfigMetadata configMetadata; + private String configETag = ""; + private String configLastModified = ""; private final String sdkKey; private final int pollingIntervalMS; @@ -73,9 +74,7 @@ public void run() { } } catch (DevCycleException e) { DevCycleLogger.error("Failed to load config: " + e.getMessage(), e); - } catch (Exception e) { - DevCycleLogger.error("Unexpected error during config fetch: " + e.getMessage(), e); - } + } } }; @@ -84,15 +83,7 @@ public boolean isConfigInitialized() { } private ProjectConfig getConfig() throws DevCycleException { - // Handle initial request where configMetadata might be null - String etag = null; - String lastModified = null; - if (this.configMetadata != null) { - etag = this.configMetadata.configETag; - lastModified = this.configMetadata.configLastModified; - } - - Call config = this.configApiClient.getConfig(this.sdkKey, etag, lastModified); + Call config = this.configApiClient.getConfig(this.sdkKey, this.configETag, this.configLastModified); ProjectConfig fetchedConfig = getResponseWithRetries(config, 1); this.config = fetchedConfig; @@ -204,16 +195,13 @@ private ProjectConfig getConfigResponse(Call call) throws DevCycl String currentETag = response.headers().get("ETag"); String headerLastModified = response.headers().get("Last-Modified"); - // Check if we should skip this config due to older timestamp (only if configMetadata exists) - if (this.configMetadata != null && - !this.configMetadata.configLastModified.isEmpty() && - headerLastModified != null && !headerLastModified.isEmpty()) { + if (!this.configLastModified.isEmpty() && headerLastModified != null && !headerLastModified.isEmpty()) { ZonedDateTime parsedLastModified = ZonedDateTime.parse( headerLastModified, DateTimeFormatter.RFC_1123_DATE_TIME ); ZonedDateTime configLastModified = ZonedDateTime.parse( - this.configMetadata.configLastModified, + this.configLastModified, DateTimeFormatter.RFC_1123_DATE_TIME ); @@ -229,23 +217,18 @@ private ProjectConfig getConfigResponse(Call call) throws DevCycl localBucketing.storeConfig(sdkKey, mapper.writeValueAsString(config)); } catch (JsonProcessingException e) { if (this.config != null) { - String currentConfigInfo = (this.configMetadata != null) ? - " etag " + this.configMetadata.configETag + " last-modified: " + this.configMetadata.configLastModified : - ""; - DevCycleLogger.error("Unable to parse config with etag: " + currentETag + ". Using cache," + currentConfigInfo); + DevCycleLogger.error("Unable to parse config with etag: " + currentETag + ". Using cache, etag " + this.configETag + " last-modified: " + this.configLastModified); return this.config; } else { errorResponse.setMessage(e.getMessage()); throw new DevCycleException(HttpResponseCode.SERVER_ERROR, errorResponse); } } - this.configMetadata = new ConfigMetadata(currentETag, headerLastModified, config.getProject(), config.getEnvironment()); + this.configETag = currentETag; + this.configLastModified = headerLastModified; return response.body(); } else if (httpResponseCode == HttpResponseCode.NOT_MODIFIED) { - String cacheInfo = (this.configMetadata != null) ? - " etag: " + this.configMetadata.configETag + " last-modified: " + this.configMetadata.configLastModified : - " (no metadata available)"; - DevCycleLogger.debug("Config not modified, using cache," + cacheInfo); + DevCycleLogger.debug("Config not modified, using cache, etag: " + this.configETag + " last-modified: " + this.configLastModified); return this.config; } else { if (response.errorBody() != null) { @@ -291,6 +274,12 @@ public void cleanup() { } public ConfigMetadata getConfigMetadata() { - return configMetadata; + String configMetadata = localBucketing.getConfigMetadata(this.sdkKey); + try { + return OBJECT_MAPPER.readValue(configMetadata, ConfigMetadata.class); + } catch (JsonProcessingException e) { + DevCycleLogger.warning("Unable to parse config metadata: " + e.getMessage()); + return new ConfigMetadata(null, null); + } } } \ No newline at end of file diff --git a/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java index 120474b2..24faa575 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java @@ -1,16 +1,16 @@ package com.devcycle.sdk.server.local.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + public class ConfigMetadata { - public final String configETag; - public final String configLastModified; public final ProjectMetadata project; public final EnvironmentMetadata environment; - public ConfigMetadata(String currentETag, String headerLastModified, Project project, Environment environment) { - this.configETag = currentETag; - this.configLastModified = headerLastModified; - this.project = new ProjectMetadata(project._id, project.key); - this.environment = new EnvironmentMetadata(environment._id, environment.key); + @JsonCreator + public ConfigMetadata(@JsonProperty("project") ProjectMetadata project, @JsonProperty("environment") EnvironmentMetadata environment) { + this.project = project; + this.environment = environment; } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java index 515907cb..02ed265f 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java @@ -1,10 +1,14 @@ package com.devcycle.sdk.server.local.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + public class EnvironmentMetadata { public final String id; public final String key; - public EnvironmentMetadata(String id, String key) { + @JsonCreator + public EnvironmentMetadata(@JsonProperty("id") String id, @JsonProperty("key") String key) { this.id = id; this.key = key; } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java index 464dc2a9..d09e18a7 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java @@ -1,10 +1,14 @@ package com.devcycle.sdk.server.local.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + public class ProjectMetadata { public final String id; public final String key; - public ProjectMetadata(String id, String key) { + @JsonCreator + public ProjectMetadata(@JsonProperty("id") String id, @JsonProperty("key") String key) { this.id = id; this.key = key; } diff --git a/src/main/resources/bucketing-lib.release.wasm b/src/main/resources/bucketing-lib.release.wasm index 80994caaf48cfdb6c52d2eee9d4df25d479899fd..bb1855666c54b5432e1c9a46027100a9d4860ef7 100644 GIT binary patch delta 42908 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 z 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 diff --git a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java index 30156a65..12391f37 100644 --- a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java @@ -37,7 +37,9 @@ import com.devcycle.sdk.server.local.model.ConfigMetadata; import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; import com.devcycle.sdk.server.local.model.Environment; +import com.devcycle.sdk.server.local.model.EnvironmentMetadata; import com.devcycle.sdk.server.local.model.Project; +import com.devcycle.sdk.server.local.model.ProjectMetadata; @RunWith(MockitoJUnitRunner.class) public class DevCycleLocalClientTest { @@ -1035,14 +1037,9 @@ public void after(HookContext ctx, Variable variable) { // Check that config metadata has the expected structure ConfigMetadata metadata = ctx.getMetadata(); - Assert.assertNotNull("Config ETag should not be null", metadata.configETag); - Assert.assertNotNull("Config last modified should not be null", metadata.configLastModified); Assert.assertNotNull("Project metadata should not be null", metadata.project); Assert.assertNotNull("Environment metadata should not be null", metadata.environment); - // Verify basic metadata structure is present - Assert.assertFalse("Config ETag should not be empty", metadata.configETag.isEmpty()); - Assert.assertFalse("Config last modified should not be empty", metadata.configLastModified.isEmpty()); metadataChecked[0] = true; } @@ -1089,11 +1086,9 @@ public void onFinally(HookContext ctx, Optional> variab // Verify metadata is consistent across all hook stages Assert.assertEquals("Before and after metadata should be the same", - capturedMetadata[0].configETag, capturedMetadata[1].configETag); + capturedMetadata[0], capturedMetadata[1]); Assert.assertEquals("Before and finally metadata should be the same", - capturedMetadata[0].configETag, capturedMetadata[2].configETag); - Assert.assertEquals("Metadata timestamps should be consistent", - capturedMetadata[0].configLastModified, capturedMetadata[1].configLastModified); + capturedMetadata[0], capturedMetadata[2]); } @Test @@ -1115,8 +1110,6 @@ public void error(HookContext ctx, Throwable error) { // Verify metadata is accessible even in error hook Assert.assertNotNull("Metadata should be accessible in error hook", ctx.getMetadata()); ConfigMetadata metadata = ctx.getMetadata(); - Assert.assertNotNull("Config ETag should not be null in error hook", metadata.configETag); - Assert.assertNotNull("Config last modified should not be null in error hook", metadata.configLastModified); Assert.assertNotNull("Project metadata should not be null in error hook", metadata.project); Assert.assertNotNull("Environment metadata should not be null in error hook", metadata.environment); metadataCheckedInError[0] = true; @@ -1152,11 +1145,6 @@ public void after(HookContext ctx, Variable variable) { ConfigMetadata directMetadata = client.getMetadata(); Assert.assertNotNull("Direct metadata should not be null", directMetadata); - // The metadata in hooks should match the current client metadata - Assert.assertEquals("Hook metadata ETag should match current metadata", - directMetadata.configETag, capturedMetadata[0].configETag); - Assert.assertEquals("Hook metadata timestamp should match current metadata", - directMetadata.configLastModified, capturedMetadata[0].configLastModified); Assert.assertEquals("Hook metadata project should match current metadata", directMetadata.project, capturedMetadata[0].project); Assert.assertEquals("Hook metadata environment should match current metadata", @@ -1177,7 +1165,6 @@ public void variable_withMultipleHooks_allReceiveMetadata() throws DevCycleExcep public void after(HookContext ctx, Variable variable) { Assert.assertNotNull("First hook should receive metadata", ctx.getMetadata()); Assert.assertNotNull("First hook metadata should have project", ctx.getMetadata().project); - Assert.assertNotNull("First hook metadata should have config ETag", ctx.getMetadata().configETag); metadataChecked[0] = true; } }); @@ -1188,7 +1175,6 @@ public void after(HookContext ctx, Variable variable) { public void after(HookContext ctx, Variable variable) { Assert.assertNotNull("Second hook should receive metadata", ctx.getMetadata()); Assert.assertNotNull("Second hook metadata should have environment", ctx.getMetadata().environment); - Assert.assertNotNull("Second hook metadata should have last modified", ctx.getMetadata().configLastModified); metadataChecked[1] = true; } }); @@ -1212,16 +1198,12 @@ public void configMetadata_canBeConstructedWithMockData() { // Test ConfigMetadata construction ConfigMetadata metadata = new ConfigMetadata( - "test-etag-12345", - "2023-10-01T12:00:00Z", - mockProject, - mockEnvironment + new ProjectMetadata(mockProject._id, mockProject.key), + new EnvironmentMetadata(mockEnvironment._id, mockEnvironment.key) ); // Verify metadata is properly constructed Assert.assertNotNull("Metadata should not be null", metadata); - Assert.assertEquals("Config ETag should match", "test-etag-12345", metadata.configETag); - Assert.assertEquals("Config last modified should match", "2023-10-01T12:00:00Z", metadata.configLastModified); Assert.assertNotNull("Project metadata should not be null", metadata.project); Assert.assertNotNull("Environment metadata should not be null", metadata.environment); @@ -1231,6 +1213,5 @@ public void configMetadata_canBeConstructedWithMockData() { Assert.assertNotNull("HookContext should not be null", contextWithMetadata); Assert.assertEquals("Metadata should be accessible from context", metadata, contextWithMetadata.getMetadata()); - Assert.assertEquals("Config ETag should be accessible", "test-etag-12345", contextWithMetadata.getMetadata().configETag); } } \ No newline at end of file From b9121c344c9e5e73b21f1ef6110a301c08670da3 Mon Sep 17 00:00:00 2001 From: Parth Suthar Date: Fri, 1 Aug 2025 17:16:25 -0400 Subject: [PATCH 2/5] fix tests --- .../devcycle/sdk/server/local/DevCycleLocalClientTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java index 12391f37..4b209d1a 100644 --- a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java @@ -1146,9 +1146,9 @@ public void after(HookContext ctx, Variable variable) { Assert.assertNotNull("Direct metadata should not be null", directMetadata); Assert.assertEquals("Hook metadata project should match current metadata", - directMetadata.project, capturedMetadata[0].project); + directMetadata.project.id, capturedMetadata[0].project.id); Assert.assertEquals("Hook metadata environment should match current metadata", - directMetadata.environment, capturedMetadata[0].environment); + directMetadata.environment.id, capturedMetadata[0].environment.id); } @Test From 00bc37a7b6029f025fe7b331d963d1854a2cfb05 Mon Sep 17 00:00:00 2001 From: Parth Suthar Date: Tue, 5 Aug 2025 10:36:30 -0400 Subject: [PATCH 3/5] update the metadata classes to have proper decorators --- .../local/model/EnvironmentMetadata.java | 19 ++++++++++--------- .../server/local/model/ProjectMetadata.java | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java index 02ed265f..c0a781fb 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java @@ -1,15 +1,16 @@ package com.devcycle.sdk.server.local.model; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class EnvironmentMetadata { - public final String id; - public final String key; - - @JsonCreator - public EnvironmentMetadata(@JsonProperty("id") String id, @JsonProperty("key") String key) { - this.id = id; - this.key = key; - } + @JsonProperty("id") + public String id; + @JsonProperty("key") + public String key; } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java index d09e18a7..8068f30e 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java @@ -1,15 +1,16 @@ package com.devcycle.sdk.server.local.model; -import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class ProjectMetadata { - public final String id; - public final String key; - - @JsonCreator - public ProjectMetadata(@JsonProperty("id") String id, @JsonProperty("key") String key) { - this.id = id; - this.key = key; - } + @JsonProperty("id") + public String id; + @JsonProperty("key") + public String key; } From 2cbdef54272263df65eb5d5ea84d9db5d9e53757 Mon Sep 17 00:00:00 2001 From: Parth Suthar Date: Tue, 5 Aug 2025 10:39:52 -0400 Subject: [PATCH 4/5] remove no args decorator --- .../sdk/server/local/model/EnvironmentMetadata.java | 6 ++---- .../devcycle/sdk/server/local/model/ProjectMetadata.java | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java index c0a781fb..936d9f0a 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java @@ -2,15 +2,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @AllArgsConstructor -@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class EnvironmentMetadata { @JsonProperty("id") - public String id; + public final String id; @JsonProperty("key") - public String key; + public final String key; } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java index 8068f30e..8ed52d43 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java @@ -1,16 +1,14 @@ package com.devcycle.sdk.server.local.model; import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @AllArgsConstructor -@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class ProjectMetadata { @JsonProperty("id") - public String id; + public final String id; @JsonProperty("key") - public String key; + public final String key; } From 9e9b1f0910b6f70e1b5694896ffd3e6733cd102a Mon Sep 17 00:00:00 2001 From: Parth Suthar Date: Tue, 5 Aug 2025 11:13:33 -0400 Subject: [PATCH 5/5] update the metadata classes to have NoArgConstructor for parsing --- .../managers/EnvironmentConfigManager.java | 2 +- .../sdk/server/local/model/ConfigMetadata.java | 17 ++++++++--------- .../server/local/model/EnvironmentMetadata.java | 6 ++++-- .../sdk/server/local/model/ProjectMetadata.java | 6 ++++-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index b18f0173..5348da05 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -279,7 +279,7 @@ public ConfigMetadata getConfigMetadata() { return OBJECT_MAPPER.readValue(configMetadata, ConfigMetadata.class); } catch (JsonProcessingException e) { DevCycleLogger.warning("Unable to parse config metadata: " + e.getMessage()); - return new ConfigMetadata(null, null); + return new ConfigMetadata(); } } } \ No newline at end of file diff --git a/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java index 24faa575..c3a73ec3 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/ConfigMetadata.java @@ -1,16 +1,15 @@ package com.devcycle.sdk.server.local.model; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) public class ConfigMetadata { - public final ProjectMetadata project; - public final EnvironmentMetadata environment; + public ProjectMetadata project; + public EnvironmentMetadata environment; - @JsonCreator - public ConfigMetadata(@JsonProperty("project") ProjectMetadata project, @JsonProperty("environment") EnvironmentMetadata environment) { - this.project = project; - this.environment = environment; - } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java index 936d9f0a..c0a781fb 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/EnvironmentMetadata.java @@ -2,13 +2,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @AllArgsConstructor +@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class EnvironmentMetadata { @JsonProperty("id") - public final String id; + public String id; @JsonProperty("key") - public final String key; + public String key; } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java index 8ed52d43..8068f30e 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/ProjectMetadata.java @@ -1,14 +1,16 @@ package com.devcycle.sdk.server.local.model; import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @AllArgsConstructor +@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class ProjectMetadata { @JsonProperty("id") - public final String id; + public String id; @JsonProperty("key") - public final String key; + public String key; }