Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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})
Exemplo n.º 5
0
    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