Skip to content

Part III - Refactors #696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: ssrc-dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions remoteconfig/condition_evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,13 @@ func (ce *conditionEvaluator) evaluatePercentCondition(percentCondition *percent
}

func computeInstanceMicroPercentile(seed string, randomizationID string) uint32 {
seedPrefix := ""
var sb strings.Builder
if len(seed) > 0 {
seedPrefix = fmt.Sprintf("%s.", seed)
sb.WriteString(seed)
sb.WriteRune('.')
}
stringToHash := fmt.Sprintf("%s%s", seedPrefix, randomizationID)
sb.WriteString(randomizationID)
stringToHash := sb.String()

hash := sha256.New()
hash.Write([]byte(stringToHash))
Expand Down
26 changes: 13 additions & 13 deletions remoteconfig/condition_evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,41 +296,41 @@ func TestPercentConditionMicroPercent(t *testing.T) {
}{
{
description: "Evaluate LESS_OR_EQUAL to true when MicroPercent is max",
operator: "LESS_OR_EQUAL",
operator: lessThanOrEqual,
microPercent: 100_000_000,
outcome: true,
},
{
description: "Evaluate LESS_OR_EQUAL to false when MicroPercent is min",
operator: "LESS_OR_EQUAL",
operator: lessThanOrEqual,
microPercent: 0,
outcome: false,
},
{
description: "Evaluate LESS_OR_EQUAL to false when MicroPercent is not set (MicroPercent should use zero)",
operator: "LESS_OR_EQUAL",
operator: lessThanOrEqual,
outcome: false,
},
{
description: "Evaluate GREATER_THAN to true when MicroPercent is not set (MicroPercent should use zero)",
operator: "GREATER_THAN",
operator: greaterThan,
outcome: true,
},
{
description: "Evaluate GREATER_THAN max to false",
operator: "GREATER_THAN",
operator: greaterThan,
outcome: false,
microPercent: 100_000_000,
},
{
description: "Evaluate LESS_OR_EQUAL to 9571542 to true",
operator: "LESS_OR_EQUAL",
operator: lessThanOrEqual,
microPercent: 9_571_542, // instanceMicroPercentile of abcdef.123 (testSeed.testRandomizationID) is 9_571_542
outcome: true,
},
{
description: "Evaluate greater than 9571542 to true",
operator: "GREATER_THAN",
operator: greaterThan,
microPercent: 9_571_541, // instanceMicroPercentile of abcdef.123 (testSeed.testRandomizationID) is 9_571_542
outcome: true,
},
Expand Down Expand Up @@ -361,40 +361,40 @@ func TestPercentConditionMicroPercentRange(t *testing.T) {
}{
{
description: "Evaluate to false when microPercentRange is not set",
operator: "BETWEEN",
operator: between,
outcome: false,
},
{
description: "Evaluate to false when upper bound is not set",
microPercentLb: 0,
operator: "BETWEEN",
operator: between,
outcome: false,
},
{
description: "Evaluate to true when lower bound is not set and upper bound is max",
microPercentUb: 100_000_000,
operator: "BETWEEN",
operator: between,
outcome: true,
},
{
description: "Evaluate to true when between lower and upper bound", // instanceMicroPercentile of abcdef.123 (testSeed.testRandomizationID) is 9_571_542
microPercentLb: 9_000_000,
microPercentUb: 9_571_542, // interval is (9_000_000, 9_571_542]
operator: "BETWEEN",
operator: between,
outcome: true,
},
{
description: "Evaluate to false when lower and upper bounds are equal",
microPercentLb: 98_000_000,
microPercentUb: 98_000_000,
operator: "BETWEEN",
operator: between,
outcome: false,
},
{
description: "Evaluate to false when not between 9_400_000 and 9_500_000", // instanceMicroPercentile of abcdef.123 (testSeed.testRandomizationID) is 9_571_542
microPercentLb: 9_400_000,
microPercentUb: 9_500_000,
operator: "BETWEEN",
operator: between,
outcome: false,
},
}
Expand Down
5 changes: 3 additions & 2 deletions remoteconfig/remoteconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package remoteconfig allows clients to use Firebase Remote Config with Go.
// Package remoteconfig provides functions to fetch and evaluate a server-side Remote Config template.
package remoteconfig

import (
Expand Down Expand Up @@ -67,6 +67,7 @@ func newRcClient(client *internal.HTTPClient, conf *internal.RemoteConfigClientC
internal.WithHeader("x-goog-api-client", internal.GetMetricsHeader(conf.Version)),
}

// Handles errors for non-success HTTP status codes from Remote Config servers.
client.CreateErrFn = handleRemoteConfigError

return &rcClient{
Expand All @@ -77,7 +78,7 @@ func newRcClient(client *internal.HTTPClient, conf *internal.RemoteConfigClientC
}
}

// GetServerTemplate Initializes a new ServerTemplate instance and fetches the server template.
// GetServerTemplate initializes a new ServerTemplate instance and fetches the server template.
func (c *rcClient) GetServerTemplate(ctx context.Context,
defaultConfig map[string]any) (*ServerTemplate, error) {
template, err := c.InitServerTemplate(defaultConfig, "")
Expand Down
56 changes: 31 additions & 25 deletions remoteconfig/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package remoteconfig

import (
"slices"
"strconv"
"strings"
)
Expand All @@ -26,8 +27,8 @@ type ValueSource int
const (
sourceUnspecified ValueSource = iota
Static // Static represents a statically defined value.
Remote // Default represents a default value.
Default // Remote represents a value fetched from a remote source.
Remote // Remote represents a value fetched from a remote source.
Default // Default represents a default value.
)

// Value defines the interface for configuration values.
Expand All @@ -38,39 +39,50 @@ type value struct {

// Default Values for config parameters.
const (
DefaultValueForBoolean = false
DefaultValueForString = ""
DefaultValueForNumber = 0
defaultValueForBoolean = false
defaultValueForString = ""
defaultValueForNumber = 0
)

var booleanTruthyValues = []string{"1", "true", "t", "yes", "y", "on"}

// ServerConfig is the implementation of the ServerConfig interface.
type ServerConfig struct {
ConfigValues map[string]value
configValues map[string]value
}

// NewServerConfig creates a new ServerConfig instance.
func NewServerConfig(configValues map[string]value) *ServerConfig {
return &ServerConfig{ConfigValues: configValues}
func newServerConfig(configValues map[string]value) *ServerConfig {
return &ServerConfig{configValues: configValues}
}

// GetBoolean returns the boolean value associated with the given key.
//
// It returns true if the string value is "1", "true", "t", "yes", "y", or "on" (case-insensitive).
// Otherwise, or if the key is not found, it returns the default boolean value (false).
func (s *ServerConfig) GetBoolean(key string) bool {
return s.getValue(key).asBoolean()
}

// GetInt returns the integer value associated with the given key.
//
// If the parameter value cannot be parsed as an integer, or if the key is not found,
// it returns the default numeric value (0).
func (s *ServerConfig) GetInt(key string) int {
return s.getValue(key).asInt()
}

// GetFloat returns the float value associated with the given key.
//
// If the parameter value cannot be parsed as a float64, or if the key is not found,
// it returns the default float value (0).
func (s *ServerConfig) GetFloat(key string) float64 {
return s.getValue(key).asFloat()
}

// GetString returns the string value associated with the given key.
//
// If the key is not found, it returns the default string value ("").
func (s *ServerConfig) GetString(key string) string {
return s.getValue(key).asString()
}
Expand All @@ -82,16 +94,16 @@ func (s *ServerConfig) GetValueSource(key string) ValueSource {

// getValue returns the value associated with the given key.
func (s *ServerConfig) getValue(key string) *value {
if val, ok := s.ConfigValues[key]; ok {
if val, ok := s.configValues[key]; ok {
return &val
}
return newValue(Static, "")
return newValue(Static, defaultValueForString)
}

// newValue creates a new value instance.
func newValue(source ValueSource, customValue string) *value {
if customValue == "" {
customValue = DefaultValueForString
customValue = defaultValueForString
}
return &value{source: source, value: customValue}
}
Expand All @@ -104,41 +116,35 @@ func (v *value) asString() string {
// asBoolean returns the value as a boolean.
func (v *value) asBoolean() bool {
if v.source == Static {
return DefaultValueForBoolean
}

for _, truthyValue := range booleanTruthyValues {
if strings.ToLower(v.value) == truthyValue {
return true
}
return defaultValueForBoolean
}

return false
return slices.Contains(booleanTruthyValues, strings.ToLower(v.value))
}

// asInt returns the value as an integer.
func (v *value) asInt() int {
if v.source == Static {
return DefaultValueForNumber
return defaultValueForNumber
}
num, err := strconv.Atoi(v.value)

if err != nil {
return DefaultValueForNumber
return defaultValueForNumber
}

return num
}

// asFloat returns the value as an integer.
// asFloat returns the value as a float.
func (v *value) asFloat() float64 {
if v.source == Static {
return DefaultValueForNumber
return defaultValueForNumber
}
num, err := strconv.ParseFloat(v.value, 64)
num, err := strconv.ParseFloat(v.value, doublePrecision)

if err != nil {
return DefaultValueForNumber
return defaultValueForNumber
}

return num
Expand Down
111 changes: 111 additions & 0 deletions remoteconfig/server_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package remoteconfig

import "testing"

type configGetterTestCase struct {
name string
key string
expectedString string
expectedInt int
expectedBool bool
expectedFloat float64
expectedSource ValueSource
}

func getTestConfig() ServerConfig {
config := ServerConfig{
configValues: map[string]value{
paramOne: {
value: valueOne,
source: Default,
},
paramTwo: {
value: valueTwo,
source: Remote,
},
paramThree: {
value: valueThree,
source: Default,
},
paramFour: {
value: valueFour,
source: Remote,
},
},
}
return config
}

func TestServerConfigGetters(t *testing.T) {
config := getTestConfig()
testCases := []configGetterTestCase{
{
name: "Parameter Value : String, Default Source",
key: paramOne,
expectedString: valueOne,
expectedInt: 0,
expectedBool: false,
expectedFloat: 0,
expectedSource: Default,
},
{
name: "Parameter Value : JSON, Remote Source",
key: paramTwo,
expectedString: valueTwo,
expectedInt: 0,
expectedBool: false,
expectedFloat: 0,
expectedSource: Remote,
},
{
name: "Unknown Parameter Value",
key: "unknown_param",
expectedString: "",
expectedInt: 0,
expectedBool: false,
expectedFloat: 0,
expectedSource: Static,
},
{
name: "Parameter Value - Float, Default Source",
key: paramThree,
expectedString: "123456789.123",
expectedInt: 0,
expectedBool: false,
expectedFloat: 123456789.123,
expectedSource: Default,
},
{
name: "Parameter Value - Boolean, Remote Source",
key: paramFour,
expectedString: "1",
expectedInt: 1,
expectedBool: true,
expectedFloat: 1,
expectedSource: Remote,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := config.GetString(tc.key); got != tc.expectedString {
t.Errorf("GetString(%q): got %q, want %q", tc.key, got, tc.expectedString)
}

if got := config.GetInt(tc.key); got != tc.expectedInt {
t.Errorf("GetInt(%q): got %d, want %d", tc.key, got, tc.expectedInt)
}

if got := config.GetBoolean(tc.key); got != tc.expectedBool {
t.Errorf("GetBoolean(%q): got %t, want %t", tc.key, got, tc.expectedBool)
}

if got := config.GetFloat(tc.key); got != tc.expectedFloat {
t.Errorf("GetFloat(%q): got %f, want %f", tc.key, got, tc.expectedFloat)
}

if got := config.GetValueSource(tc.key); got != tc.expectedSource {
t.Errorf("GetValueSource(%q): got %v, want %v", tc.key, got, tc.expectedSource)
}
})
}
}
Loading