def validate_arguments(self, value): # Get the schema associated with the selected action try: schema = Action.objects.get( name=self.initial_data.get('action')).arguments_schema except: raise serializers.ValidationError( 'Could not find arguments schema.') schemaValidator = JSONSchemaValidator(schema) errorResponse = {} errors = sorted(schemaValidator.iter_errors(value), key=lambda e: e.path) # Loop through ValidationErrors returned by JSONSchema # Each error contains a message and a path attribute # message: string human-readable error explanation # path: list containing path to offending element for error in errors: currentLevel = errorResponse # Loop through the path of the current error # e.g. ['surveys'][0]['weight'] for index, path in enumerate(error.path): # If this key already exists in our error response, step into it if path in currentLevel: currentLevel = currentLevel[path] continue else: # If we haven't reached the end of the path, add this path # as a key in our error response object and step into it if index < len(error.path) - 1: currentLevel[path] = {} currentLevel = currentLevel[path] continue # If we've reached the final path, set the error message else: currentLevel[path] = error.message if (errorResponse): raise serializers.ValidationError(errorResponse) return value
def validate_arguments(self, value): # Get the schema associated with the selected action try: schema = Action.objects.get(name=self.initial_data.get('action')).arguments_schema except: raise serializers.ValidationError('Could not find arguments schema.') schemaValidator = JSONSchemaValidator(schema) errorResponse = {} errors = sorted(schemaValidator.iter_errors(value), key=lambda e: e.path) # Loop through ValidationErrors returned by JSONSchema # Each error contains a message and a path attribute # message: string human-readable error explanation # path: list containing path to offending element for error in errors: currentLevel = errorResponse # Loop through the path of the current error # e.g. ['surveys'][0]['weight'] for index, path in enumerate(error.path): # If this key already exists in our error response, step into it if path in currentLevel: currentLevel = currentLevel[path] continue else: # If we haven't reached the end of the path, add this path # as a key in our error response object and step into it if index < len(error.path) - 1: currentLevel[path] = {} currentLevel = currentLevel[path] continue # If we've reached the final path, set the error message else: currentLevel[path] = error.message if (errorResponse): raise serializers.ValidationError(errorResponse) return value
def validate(self, data): data = super().validate(data) action = data.get("action") if action is None: action = self.instance.latest_revision.action arguments = data.get("arguments") if arguments is not None: # Ensure the value is a dict if not isinstance(arguments, dict): raise serializers.ValidationError({"arguments": "Must be an object."}) # Get the schema associated with the selected action schema = action.arguments_schema schemaValidator = JSONSchemaValidator(schema) errorResponse = {} errors = sorted(schemaValidator.iter_errors(arguments), key=lambda e: e.path) # Loop through ValidationErrors returned by JSONSchema # Each error contains a message and a path attribute # message: string human-readable error explanation # path: list containing path to offending element for error in errors: currentLevel = errorResponse # Loop through the path of the current error # e.g. ['surveys'][0]['weight'] for index, path in enumerate(error.path): # If this key already exists in our error response, step into it if path in currentLevel: currentLevel = currentLevel[path] continue else: # If we haven't reached the end of the path, add this path # as a key in our error response object and step into it if index < len(error.path) - 1: currentLevel[path] = {} currentLevel = currentLevel[path] continue # If we've reached the final path, set the error message else: currentLevel[path] = error.message if errorResponse: raise serializers.ValidationError({"arguments": errorResponse}) if self.instance is None: if data.get("extra_filter_expression", "").strip() == "": if not data.get("filter_object"): raise serializers.ValidationError( "one of extra_filter_expression or filter_object is required" ) else: if "extra_filter_expression" in data or "filter_object" in data: # If either is attempted to be updated, at least one of them must be truthy. if not data.get("extra_filter_expression", "").strip() and not data.get( "filter_object" ): raise serializers.ValidationError( "if extra_filter_expression is blank, " "at least one filter_object is required" ) return data
def validate_arguments(self, arguments, revision): """ Test if `arguments` follows all action-specific rules. Raises `ValidationError` if any rules are violated. """ # Make a default dict that always returns a default dict def default(): return defaultdict(default) errors = default() # Check for any JSON Schema violations schemaValidator = JSONSchemaValidator(self.arguments_schema) for error in schemaValidator.iter_errors(arguments): current_level = errors path = list(error.path) for part in path[:-1]: current_level = current_level[part] current_level[path[-1]] = error.message if errors: raise serializers.ValidationError({"arguments": errors}) if self.name == "preference-experiment": # Feature branch slugs should be unique within an experiment. branch_slugs = set() branch_values = set() for i, branch in enumerate(arguments.get("branches")): if branch["slug"] in branch_slugs: msg = self.errors["duplicate_branch_slug"] errors["branches"][i]["slug"] = msg if branch["value"] in branch_values: msg = self.errors["duplicate_branch_value"] errors["branches"][i]["value"] = msg branch_slugs.add(branch["slug"]) branch_values.add(branch["value"]) # Experiment slugs should be unique. experiment_recipes = Recipe.objects.filter( latest_revision__action=self) if revision.recipe and revision.recipe.id: experiment_recipes = experiment_recipes.exclude( id=revision.recipe.id) existing_slugs = set( r.latest_revision.arguments.get("slug") for r in experiment_recipes) if arguments.get("slug") in existing_slugs: msg = self.errors["duplicate_experiment_slug"] errors["slug"] = msg elif self.name == "preference-rollout": # Rollout slugs should be unique rollout_recipes = Recipe.objects.filter( latest_revision__action=self) if revision.recipe and revision.recipe.id: rollout_recipes = rollout_recipes.exclude( id=revision.recipe.id) existing_slugs = set( r.latest_revision.arguments.get("slug") for r in rollout_recipes) if arguments.get("slug") in existing_slugs: msg = self.errors["duplicate_rollout_slug"] errors["slug"] = msg elif self.name == "preference-rollback": # Rollback slugs should match rollouts rollouts = Recipe.objects.filter( latest_revision__action__name="preference-rollout") rollout_slugs = set(r.latest_revision.arguments["slug"] for r in rollouts) if arguments["rolloutSlug"] not in rollout_slugs: errors["slug"] = self.errors["rollout_slug_not_found"] elif self.name == "show-heartbeat": # Survey ID should be unique across all recipes other_recipes = Recipe.objects.filter(latest_revision__action=self) if revision.recipe and revision.recipe.id: other_recipes = other_recipes.exclude(id=revision.recipe.id) # So it *could* be that a different recipe's *latest_revision*'s argument # has this same surveyId but its *approved_revision* has a different surveyId. # It's unlikely in the real-world that different revisions, within a recipe, # has different surveyIds *and* that any of these clash with an entirely # different recipe. for recipe in other_recipes: if recipe.latest_revision.arguments["surveyId"] == arguments[ "surveyId"]: errors["surveyId"] = self.errors["duplicate_survey_id"] elif self.name == "opt-out-study": # Name should be unique across all recipes other_recipes = Recipe.objects.filter(latest_revision__action=self) if revision.recipe and revision.recipe.id: other_recipes = other_recipes.exclude(id=revision.recipe.id) for recipe in other_recipes: if recipe.latest_revision.arguments["name"] == arguments[ "name"]: errors["name"] = self.errors["duplicate_study_name"] # Raise errors, if any if errors: raise serializers.ValidationError({"arguments": errors})
def validate(self, data): data = super().validate(data) action = data.get("action") if action is None: action = self.instance.action arguments = data.get("arguments") if arguments is not None: # Ensure the value is a dict if not isinstance(arguments, dict): raise serializers.ValidationError({"arguments": "Must be an object."}) # Get the schema associated with the selected action schema = action.arguments_schema schemaValidator = JSONSchemaValidator(schema) errorResponse = {} errors = sorted(schemaValidator.iter_errors(arguments), key=lambda e: e.path) # Loop through ValidationErrors returned by JSONSchema # Each error contains a message and a path attribute # message: string human-readable error explanation # path: list containing path to offending element for error in errors: currentLevel = errorResponse # Loop through the path of the current error # e.g. ['surveys'][0]['weight'] for index, path in enumerate(error.path): # If this key already exists in our error response, step into it if path in currentLevel: currentLevel = currentLevel[path] continue else: # If we haven't reached the end of the path, add this path # as a key in our error response object and step into it if index < len(error.path) - 1: currentLevel[path] = {} currentLevel = currentLevel[path] continue # If we've reached the final path, set the error message else: currentLevel[path] = error.message if errorResponse: raise serializers.ValidationError({"arguments": errorResponse}) if self.instance is None: if data.get("extra_filter_expression", "").strip() == "": if not data.get("filter_object"): raise serializers.ValidationError( "one of extra_filter_expression or filter_object is required" ) else: if "extra_filter_expression" in data or "filter_object" in data: # If either is attempted to be updated, at least one of them must be truthy. if not data.get("extra_filter_expression", "").strip() and not data.get( "filter_object" ): raise serializers.ValidationError( "if extra_filter_expression is blank, " "at least one filter_object is required" ) return data