File Coverage

blib/lib/WebService/GrowthBook/Util.pm
Criterion Covered Total %
statement 83 83 100.0
branch 27 28 96.4
condition 16 20 80.0
subroutine 15 15 100.0
pod 0 9 0.0
total 141 155 90.9


line stmt bran cond sub pod time code
1             package WebService::GrowthBook::Util;
2 9     9   188964 use strict;
  9         22  
  9         485  
3 9     9   52 use warnings;
  9         16  
  9         603  
4 9     9   54 use Exporter qw(import);
  9         30  
  9         386  
5 9     9   6831 use URI;
  9         77606  
  9         762  
6 9     9   89 use List::Util qw(sum);
  9         18  
  9         1236  
7 9     9   4918 use String::CamelCase qw(decamelize);
  9         9546  
  9         13940  
8              
9             our $VERSION = '0.003'; ## VERSION
10              
11             our @EXPORT_OK = qw(gbhash in_range get_query_string_override get_equal_weights get_bucket_ranges adjust_args_camel_to_snake choose_variation in_namespace);
12              
13             sub fnv1a32 {
14 136     136 0 317 my ($str) = @_;
15 136         213 my $hval = 0x811C9DC5;
16 136         279 my $prime = 0x01000193;
17 136         290 my $uint32_max = 2 ** 32;
18              
19 136         673 foreach my $s (split //, $str) {
20 1260         1828 $hval = $hval ^ ord($s);
21 1260         2031 $hval = ($hval * $prime) % $uint32_max;
22             }
23              
24 136         435 return $hval;
25             }
26             sub gbhash {
27 114     114 0 12029 my ($seed, $value, $version) = @_;
28              
29 114 100       432 if ($version == 2) {
30 24         110 my $n = fnv1a32(fnv1a32($seed . $value));
31 24         101 return ($n % 10000) / 10000;
32             }
33 90 100       247 if ($version == 1) {
34 88         3143 my $n = fnv1a32($value . $seed);
35 88         339 return ($n % 1000) / 1000;
36             }
37 2         7 return undef;
38             }
39              
40             sub in_range {
41 157     157 0 317 my ($n, $range) = @_;
42 157   100     855 return $range->[0] <= $n && $n < $range->[1];
43             }
44              
45              
46             sub get_query_string_override {
47 96     96 0 19372 my ($id, $url, $num_variations) = @_;
48 96         4294 my $uri = URI->new($url);
49              
50             # Return undef if there is no query string
51 96 100       27025 return undef unless $uri->query;
52              
53 15         309 my %qs = $uri->query_form;
54              
55             # Return undef if the id is not in the query string
56 15 100       876 return undef unless exists $qs{$id};
57              
58 13         37 my $variation = $qs{$id};
59              
60             # Return undef if the variation is not defined or not a digit
61 13 100 66     108 return undef unless defined $variation && $variation =~ /^\d+$/;
62              
63 10         30 my $var_id = int($variation);
64              
65             # Return undef if the variation id is out of range
66 10 100 66     61 return undef if $var_id < 0 || $var_id >= $num_variations;
67              
68 8         57 return $var_id;
69             }
70              
71             sub get_equal_weights {
72 63     63 0 7205 my ($num_variations) = @_;
73 63 100       166 return [] if $num_variations < 1;
74 61         173 my $weight = 1 / $num_variations;
75 61         241 return [($weight) x $num_variations];
76             }
77              
78             sub get_bucket_ranges {
79 71     71 0 15417 my ($num_variations, $coverage, $weights) = @_;
80 71   50     189 $coverage //= 1;
81 71   66     219 $weights //= get_equal_weights($num_variations);
82              
83 71 100       221 if ($coverage < 0) {
84 1         4 $coverage = 0;
85             }
86 71 100       213 if ($coverage > 1) {
87 1         2 $coverage = 1;
88             }
89 71 100       230 if (@$weights != $num_variations) {
90 49         306 $weights = get_equal_weights($num_variations);
91             }
92 71 100 100     619 if (sum(@$weights) < 0.99 || sum(@$weights) > 1.01) {
93 2         7 $weights = get_equal_weights($num_variations);
94             }
95              
96 71         118 my $cumulative = 0;
97 71         107 my @ranges;
98 71         175 foreach my $w (@$weights) {
99 162         245 my $start = $cumulative;
100 162         271 $cumulative += $w;
101 162         462 push @ranges, [$start, $start + $coverage * $w];
102             }
103              
104 71         297 return \@ranges;
105             }
106              
107             sub choose_variation {
108 78     78 0 14130 my ($n, $ranges) = @_;
109 78         1649 for (my $i = 0; $i < @$ranges; $i++) {
110 142 100       399 if (in_range($n, $ranges->[$i])) {
111 67         1076 return $i;
112             }
113             }
114 11         49 return -1;
115             }
116              
117             sub adjust_args_camel_to_snake {
118 238     238 0 502 my ($args) = @_;
119 238         712 for my $key (keys %$args) {
120 680         3176 my $snake_key = decamelize($key);
121 680 100       7832 if ($key eq $snake_key) {
122 656         2122 next;
123             }
124 24         84 $args->{$snake_key} = delete $args->{$key};
125             }
126             }
127              
128             sub in_namespace {
129 19     19 0 15488 my ($user_id, $namespace) = @_;
130 19         107 my $n = gbhash("__" . $namespace->[0], $user_id, 1);
131 19 50       63 return 0 unless defined $n;
132 19   100     188 return $namespace->[1] <= $n && $n < $namespace->[2];
133             }
134             1;