From d3c8d409b631f559239e48fb93eb5e4f9181254f Mon Sep 17 00:00:00 2001 From: "Matthew M. Keeler" Date: Tue, 4 Jun 2024 08:53:09 -0400 Subject: [PATCH 1/3] feat: Support to_h and to_json methods for LDContext (#284) --- lib/ldclient-rb/context.rb | 43 ++++++++++++++++++++++++++ lib/ldclient-rb/reference.rb | 10 ++++++ spec/context_spec.rb | 59 ++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/lib/ldclient-rb/context.rb b/lib/ldclient-rb/context.rb index 6f4fec49..1f92ea9a 100644 --- a/lib/ldclient-rb/context.rb +++ b/lib/ldclient-rb/context.rb @@ -334,6 +334,49 @@ def [](key) multi_kind? ? individual_context(key.to_s) : get_value(key) end + # + # Convert the LDContext to a JSON string. + # + # @param args [Array] + # @return [String] + # + def to_json(*args) + JSON.generate(to_h, *args) + end + + # + # Convert the LDContext to a hash. If the LDContext is invalid, the hash will contain an error key with the error + # message. + # + # @return [Hash] + # + def to_h + return {error: error} unless valid? + return hash_single_kind unless multi_kind? + + hash = {kind: 'multi'} + @contexts.each do |context| + single_kind_hash = context.to_h + kind = single_kind_hash.delete(:kind) + hash[kind] = single_kind_hash + end + + hash + end + + protected def hash_single_kind + hash = attributes.nil? ? {} : attributes.clone + + hash[:kind] = kind + hash[:key] = key + + hash[:name] = name unless name.nil? + hash[:anonymous] = anonymous if anonymous + hash[:_meta] = {privateAttributes: private_attributes} unless private_attributes.empty? + + hash + end + # # Retrieve the value of any top level, addressable attribute. # diff --git a/lib/ldclient-rb/reference.rb b/lib/ldclient-rb/reference.rb index d25ee06b..8c248fe3 100644 --- a/lib/ldclient-rb/reference.rb +++ b/lib/ldclient-rb/reference.rb @@ -238,6 +238,16 @@ def hash ([error] + components).hash end + # + # Convert the Reference to a JSON string. + # + # @param args [Array] + # @return [String] + # + def to_json(*args) + JSON.generate(@raw_path, *args) + end + # # Performs unescaping of attribute reference path components: # diff --git a/spec/context_spec.rb b/spec/context_spec.rb index be261515..44be8e4a 100644 --- a/spec/context_spec.rb +++ b/spec/context_spec.rb @@ -1,4 +1,5 @@ require "ldclient-rb/context" +require "json" module LaunchDarkly describe LDContext do @@ -148,6 +149,64 @@ module LaunchDarkly expect(org_first.fully_qualified_key).to eq("org:b-org-key:user:a-user-key") end end + + describe "converts back to JSON format" do + it "single kind contexts" do + contextHash = { + key: "launchdarkly", + kind: "org", + address: { + street: "1999 Harrison St Suite 1100", + city: "Oakland", + state: "CA", + zip: "94612", + _meta: { + privateAttributes: ["city"], + }, + }, + } + context = subject.create(contextHash) + contextJson = context.to_json + backToHash = JSON.parse(contextJson, symbolize_names: true) + + expect(backToHash).to eq(contextHash) + end + + it "multi kind contexts" do + contextHash = { + kind: "multi", + "org": { + key: "launchdarkly", + address: { + street: "1999 Harrison St Suite 1100", + city: "Oakland", + state: "CA", + zip: "94612", + }, + _meta: { + privateAttributes: ["address/city"], + }, + }, + "user": { + key: "user-key", + name: "Ruby", + anonymous: true, + }, + } + context = subject.create(contextHash) + contextJson = context.to_json + backToHash = JSON.parse(contextJson, symbolize_names: true) + + expect(backToHash).to eq(contextHash) + end + + it "invalid context returns error" do + context = subject.create({ key: "", kind: "user", name: "testing" }) + expect(context.valid?).to be false + expect(context.to_h).to eq({ error: "context key must not be empty" }) + expect(context.to_json).to eq({ error: "context key must not be empty" }.to_json) + end + end end describe "context counts" do From 7d5b051ec1b1e8990a7fb3def5798f064acd5e04 Mon Sep 17 00:00:00 2001 From: "Matthew M. Keeler" Date: Mon, 10 Jun 2024 13:04:35 -0400 Subject: [PATCH 2/3] fix: Increment flag & segment versions when reloading from file data source (#285) The file data source allows specifying flag information as a full flag definition, or as a shorted map of flag key:value mappings. In the case of the flag values, or in the case of a malformed flag definition, a flag version might not be specified. When this happens, users of the flag tracker will notice an error because the version comparison code will encounter an unexpected nil value. To prevent this from happening, the file data source should be setting a version for each flag or segment it reads. When these items are modified in the LaunchDarkly UI, we automatically increment the version associated with the item. To make this easier for the user going forward, the file data source will handle incrementing this version number each time the file is re-read. --- .../impl/integrations/file_data_source.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/ldclient-rb/impl/integrations/file_data_source.rb b/lib/ldclient-rb/impl/integrations/file_data_source.rb index c5ee917f..1e7ad078 100644 --- a/lib/ldclient-rb/impl/integrations/file_data_source.rb +++ b/lib/ldclient-rb/impl/integrations/file_data_source.rb @@ -40,6 +40,9 @@ def initialize(data_store, data_source_update_sink, logger, options={}) @poll_interval = options[:poll_interval] || 1 @initialized = Concurrent::AtomicBoolean.new(false) @ready = Concurrent::Event.new + + @version_lock = Mutex.new + @last_version = 1 end def initialized? @@ -93,14 +96,22 @@ def load_all end def load_file(path, all_data) + version = 1 + @version_lock.synchronize { + version = @last_version + @last_version += 1 + } + parsed = parse_content(IO.read(path)) (parsed[:flags] || {}).each do |key, flag| + flag[:version] = version add_item(all_data, FEATURES, flag) end (parsed[:flagValues] || {}).each do |key, value| - add_item(all_data, FEATURES, make_flag_with_value(key.to_s, value)) + add_item(all_data, FEATURES, make_flag_with_value(key.to_s, value, version)) end (parsed[:segments] || {}).each do |key, segment| + segment[:version] = version add_item(all_data, SEGMENTS, segment) end end @@ -134,10 +145,11 @@ def add_item(all_data, kind, item) items[key] = Model.deserialize(kind, item) end - def make_flag_with_value(key, value) + def make_flag_with_value(key, value, version) { key: key, on: true, + version: version, fallthrough: { variation: 0 }, variations: [ value ], } From 9f6c90244e302f075c3810d4abee5bafe445c476 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:14:11 -0400 Subject: [PATCH 3/3] chore(main): release 8.5.0 (#279) :robot: I have created a release *beep* *boop* --- ## [8.5.0](https://github.com/launchdarkly/ruby-server-sdk/compare/8.4.2...8.5.0) (2024-06-10) ### Features * Support to_h and to_json methods for LDContext ([#284](https://github.com/launchdarkly/ruby-server-sdk/issues/284)) ([d3c8d40](https://github.com/launchdarkly/ruby-server-sdk/commit/d3c8d409b631f559239e48fb93eb5e4f9181254f)) ### Bug Fixes * Increment flag & segment versions when reloading from file data source ([#285](https://github.com/launchdarkly/ruby-server-sdk/issues/285)) ([7d5b051](https://github.com/launchdarkly/ruby-server-sdk/commit/7d5b051ec1b1e8990a7fb3def5798f064acd5e04)) * Log warning if client init timeout is considered high ([#278](https://github.com/launchdarkly/ruby-server-sdk/issues/278)) ([61f4c7e](https://github.com/launchdarkly/ruby-server-sdk/commit/61f4c7e589e9d0da94e4f289e9c601aa36028c95)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ PROVENANCE.md | 2 +- lib/ldclient-rb/version.rb | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7f3ab495..56b4dffb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "8.4.2" + ".": "8.5.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 448158b3..c396af08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [8.5.0](https://github.com/launchdarkly/ruby-server-sdk/compare/8.4.2...8.5.0) (2024-06-10) + + +### Features + +* Support to_h and to_json methods for LDContext ([#284](https://github.com/launchdarkly/ruby-server-sdk/issues/284)) ([d3c8d40](https://github.com/launchdarkly/ruby-server-sdk/commit/d3c8d409b631f559239e48fb93eb5e4f9181254f)) + + +### Bug Fixes + +* Increment flag & segment versions when reloading from file data source ([#285](https://github.com/launchdarkly/ruby-server-sdk/issues/285)) ([7d5b051](https://github.com/launchdarkly/ruby-server-sdk/commit/7d5b051ec1b1e8990a7fb3def5798f064acd5e04)) +* Log warning if client init timeout is considered high ([#278](https://github.com/launchdarkly/ruby-server-sdk/issues/278)) ([61f4c7e](https://github.com/launchdarkly/ruby-server-sdk/commit/61f4c7e589e9d0da94e4f289e9c601aa36028c95)) + ## [8.4.2](https://github.com/launchdarkly/ruby-server-sdk/compare/8.4.1...8.4.2) (2024-05-03) diff --git a/PROVENANCE.md b/PROVENANCE.md index 4cc73457..a597331d 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -9,7 +9,7 @@ To verify SLSA provenance attestations, we recommend using [slsa-verifier](https ``` # Set the version of the SDK to verify -SDK_VERSION=8.4.2 +SDK_VERSION=8.5.0 ``` diff --git a/lib/ldclient-rb/version.rb b/lib/ldclient-rb/version.rb index bb3e7898..cfe2e29d 100644 --- a/lib/ldclient-rb/version.rb +++ b/lib/ldclient-rb/version.rb @@ -1,3 +1,3 @@ module LaunchDarkly - VERSION = "8.4.2" # x-release-please-version + VERSION = "8.5.0" # x-release-please-version end