lib/ldclient-rb/impl/model/serialization.rb in launchdarkly-server-sdk-6.4.0 vs lib/ldclient-rb/impl/model/serialization.rb in launchdarkly-server-sdk-7.0.0

- old
+ new

@@ -1,32 +1,72 @@ +require "ldclient-rb/impl/model/feature_flag" require "ldclient-rb/impl/model/preprocessed_data" +require "ldclient-rb/impl/model/segment" +# General implementation notes about the data model classes in LaunchDarkly::Impl::Model-- +# +# As soon as we receive flag/segment JSON data from LaunchDarkly (or, read it from a database), we +# transform it into the model classes FeatureFlag, Segment, etc. The constructor of each of these +# classes takes a hash (the parsed JSON), and transforms it into an internal representation that +# is more efficient for evaluations. +# +# Validation works as follows: +# - A property value that is of the correct type, but is invalid for other reasons (for example, +# if a flag rule refers to variation index 5, but there are only 2 variations in the flag), does +# not prevent the flag from being parsed and stored. It does cause a warning to be logged, if a +# logger was passed to the constructor. +# - If a value is completely invalid for the schema, the constructor may throw an +# exception, causing the whole data set to be rejected. This is consistent with the behavior of +# the strongly-typed SDKs. +# +# Currently, the model classes also retain the original hash of the parsed JSON. This is because +# we may need to re-serialize them to JSON, and building the JSON on the fly would be very +# inefficient, so each model class has a to_json method that just returns the same Hash. If we +# are able in the future to either use a custom streaming serializer, or pass the JSON data +# straight through from LaunchDarkly to a database instead of re-serializing, we could stop +# retaining this data. + module LaunchDarkly module Impl module Model # Abstraction of deserializing a feature flag or segment that was read from a data store or # received from LaunchDarkly. - def self.deserialize(kind, json, logger = nil) - return nil if json.nil? - item = JSON.parse(json, symbolize_names: true) - DataModelPreprocessing::Preprocessor.new(logger).preprocess_item!(kind, item) - item + # + # SDK code outside of Impl::Model should use this method instead of calling the model class + # constructors directly, so as not to rely on implementation details. + # + # @param kind [Hash] normally either FEATURES or SEGMENTS + # @param input [object] a JSON string or a parsed hash (or a data model object, in which case + # we'll just return the original object) + # @param logger [Logger|nil] logs warnings if there are any data validation problems + # @return [Object] the flag or segment (or, for an unknown data kind, the data as a hash) + def self.deserialize(kind, input, logger = nil) + return nil if input.nil? + return input if !input.is_a?(String) && !input.is_a?(Hash) + data = input.is_a?(Hash) ? input : JSON.parse(input, symbolize_names: true) + case kind + when FEATURES + FeatureFlag.new(data, logger) + when SEGMENTS + Segment.new(data, logger) + else + data + end end # Abstraction of serializing a feature flag or segment that will be written to a data store. - # Currently we just call to_json. + # Currently we just call to_json, but SDK code outside of Impl::Model should use this method + # instead of to_json, so as not to rely on implementation details. def self.serialize(kind, item) item.to_json end # Translates a { flags: ..., segments: ... } object received from LaunchDarkly to the data store format. def self.make_all_store_data(received_data, logger = nil) - preprocessor = DataModelPreprocessing::Preprocessor.new(logger) - flags = received_data[:flags] - preprocessor.preprocess_all_items!(FEATURES, flags) - segments = received_data[:segments] - preprocessor.preprocess_all_items!(SEGMENTS, segments) - { FEATURES => flags, SEGMENTS => segments } + { + FEATURES => (received_data[:flags] || {}).transform_values { |data| FeatureFlag.new(data, logger) }, + SEGMENTS => (received_data[:segments] || {}).transform_values { |data| Segment.new(data, logger) }, + } end end end end