2020-02-12 13:09:21 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# A serializer for boolean values being stored in Redis.
|
|
|
|
#
|
|
|
|
# This is to ensure that booleans are stored in a consistent and
|
|
|
|
# testable way when being stored as strings in Redis.
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
#
|
|
|
|
# bool = Gitlab::Redis::Boolean.new(true)
|
|
|
|
# bool.to_s == "_b:1"
|
|
|
|
#
|
|
|
|
# Gitlab::Redis::Boolean.encode(true)
|
|
|
|
# => "_b:1"
|
|
|
|
#
|
|
|
|
# Gitlab::Redis::Boolean.decode("_b:1")
|
|
|
|
# => true
|
|
|
|
#
|
|
|
|
# Gitlab::Redis::Boolean.true?("_b:1")
|
|
|
|
# => true
|
|
|
|
#
|
|
|
|
# Gitlab::Redis::Boolean.true?("_b:0")
|
|
|
|
# => false
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Redis
|
|
|
|
class Boolean
|
|
|
|
LABEL = "_b"
|
|
|
|
DELIMITER = ":"
|
|
|
|
TRUE_STR = "1"
|
|
|
|
FALSE_STR = "0"
|
|
|
|
|
|
|
|
BooleanError = Class.new(StandardError)
|
|
|
|
NotABooleanError = Class.new(BooleanError)
|
|
|
|
NotAnEncodedBooleanStringError = Class.new(BooleanError)
|
|
|
|
|
|
|
|
def initialize(value)
|
|
|
|
@value = value
|
|
|
|
end
|
|
|
|
|
|
|
|
# @return [String] the encoded boolean
|
|
|
|
def to_s
|
|
|
|
self.class.encode(@value)
|
|
|
|
end
|
|
|
|
|
|
|
|
class << self
|
|
|
|
# Turn a boolean into a string for storage in Redis
|
|
|
|
#
|
|
|
|
# @param value [Boolean] true or false
|
|
|
|
# @return [String] the encoded boolean
|
|
|
|
# @raise [NotABooleanError] if the value isn't true or false
|
|
|
|
def encode(value)
|
2021-05-04 11:10:36 -04:00
|
|
|
raise NotABooleanError, value unless bool?(value)
|
2020-02-12 13:09:21 -05:00
|
|
|
|
|
|
|
[LABEL, to_string(value)].join(DELIMITER)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Decode a boolean string
|
|
|
|
#
|
|
|
|
# @param value [String] the stored boolean string
|
|
|
|
# @return [Boolean] true or false
|
|
|
|
# @raise [NotAnEncodedBooleanStringError] if the provided value isn't an encoded boolean
|
|
|
|
def decode(value)
|
2021-05-04 11:10:36 -04:00
|
|
|
raise NotAnEncodedBooleanStringError, value.class unless value.is_a?(String)
|
2020-02-12 13:09:21 -05:00
|
|
|
|
|
|
|
label, bool_str = *value.split(DELIMITER, 2)
|
|
|
|
|
2021-05-04 11:10:36 -04:00
|
|
|
raise NotAnEncodedBooleanStringError, label unless label == LABEL
|
2020-02-12 13:09:21 -05:00
|
|
|
|
|
|
|
from_string(bool_str)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Decode a boolean string, then test if it's true
|
|
|
|
#
|
|
|
|
# @param value [String] the stored boolean string
|
|
|
|
# @return [Boolean] is the value true?
|
|
|
|
# @raise [NotAnEncodedBooleanStringError] if the provided value isn't an encoded boolean
|
|
|
|
def true?(encoded_value)
|
|
|
|
decode(encoded_value)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Decode a boolean string, then test if it's false
|
|
|
|
#
|
|
|
|
# @param value [String] the stored boolean string
|
|
|
|
# @return [Boolean] is the value false?
|
|
|
|
# @raise [NotAnEncodedBooleanStringError] if the provided value isn't an encoded boolean
|
|
|
|
def false?(encoded_value)
|
|
|
|
!true?(encoded_value)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def bool?(value)
|
|
|
|
[true, false].include?(value)
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_string(bool)
|
|
|
|
bool ? TRUE_STR : FALSE_STR
|
|
|
|
end
|
|
|
|
|
|
|
|
def from_string(str)
|
2021-05-04 11:10:36 -04:00
|
|
|
raise NotAnEncodedBooleanStringError, str unless [TRUE_STR, FALSE_STR].include?(str)
|
2020-02-12 13:09:21 -05:00
|
|
|
|
|
|
|
str == TRUE_STR
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|