@@ -86,7 +86,7 @@ public Template(String etag) {
8686 if (templateResponse .getVersion () != null ) {
8787 this .version = new Version (templateResponse .getVersion ());
8888 }
89- validateExperimentExposurePercents (this .parameters );
89+ validateExperimentExposurePercents (this .parameters , this . parameterGroups );
9090 this .etag = templateResponse .getEtag ();
9191 }
9292
@@ -280,25 +280,56 @@ public int hashCode() {
280280 return Objects .hash (etag , parameters , conditions , parameterGroups , version );
281281 }
282282
283- private static void validateExperimentExposurePercents (Map <String , Parameter > parameters )
284- throws FirebaseRemoteConfigException {
283+ private void validateExperimentExposurePercents (
284+ Map <String , Parameter > parameters ,
285+ Map <String , ParameterGroup > parameterGroups ) throws FirebaseRemoteConfigException {
286+ Map <String , Double > experimentExposurePercents = new HashMap <>();
287+ validateParameters (parameters , experimentExposurePercents );
288+ if (parameterGroups != null ) {
289+ for (ParameterGroup group : parameterGroups .values ()) {
290+ validateParameters (group .getParameters (), experimentExposurePercents );
291+ }
292+ }
293+ }
294+
295+ private void validateParameters (
296+ Map <String , Parameter > parameters ,
297+ Map <String , Double > experimentExposurePercents ) throws FirebaseRemoteConfigException {
285298 if (parameters == null ) {
286299 return ;
287300 }
288301 for (Map .Entry <String , Parameter > entry : parameters .entrySet ()) {
289302 Parameter parameter = entry .getValue ();
290- if (parameter == null || parameter .getConditionalValues () == null ) {
291- continue ;
303+ String parameterName = entry .getKey ();
304+ checkExposurePercent (parameter .getDefaultValue (), parameterName , experimentExposurePercents );
305+ if (parameter .getConditionalValues () != null ) {
306+ for (ParameterValue value : parameter .getConditionalValues ().values ()) {
307+ checkExposurePercent (value , parameterName , experimentExposurePercents );
308+ }
292309 }
293- for (ParameterValue value : parameter .getConditionalValues ().values ()) {
294- if (value instanceof ParameterValue .ExperimentValue ) {
295- double exposurePercent = ((ParameterValue .ExperimentValue ) value ).getExposurePercent ();
296- // Validate range [0, 100] and finiteness
297- if (exposurePercent < 0 || exposurePercent > 100 || !Double .isFinite (exposurePercent )) {
298- throw new FirebaseRemoteConfigException (
299- ErrorCode .INVALID_ARGUMENT ,
300- "Experiment exposure percent must be between 0 and 100 (" + entry .getKey () + ")" );
310+ }
311+ }
312+
313+ private void checkExposurePercent (
314+ ParameterValue value ,
315+ String parameterName ,
316+ Map <String , Double > experimentExposurePercents ) throws FirebaseRemoteConfigException {
317+ if (value instanceof ParameterValue .ExperimentValue ) {
318+ ParameterValue .ExperimentValue experimentValue = (ParameterValue .ExperimentValue ) value ;
319+ Double exposurePercent = experimentValue .getExposurePercent ();
320+ if (exposurePercent != null ) {
321+ // Enforce range [0, 100]
322+ if (exposurePercent < 0 || exposurePercent > 100 ) {
323+ return ;
324+ }
325+ // Enforce consistency for the same experimentId
326+ String experimentId = experimentValue .getExperimentId ();
327+ if (experimentExposurePercents .containsKey (experimentId )) {
328+ if (!Objects .equals (experimentExposurePercents .get (experimentId ), exposurePercent )) {
329+ return ;
301330 }
331+ } else {
332+ experimentExposurePercents .put (experimentId , exposurePercent );
302333 }
303334 }
304335 }
0 commit comments