diff --git a/api/functions/error_messages.py b/api/functions/error_messages.py index 153e8ef5d6..643c0b296d 100644 --- a/api/functions/error_messages.py +++ b/api/functions/error_messages.py @@ -20,3 +20,11 @@ def error_user_does_not_exist(): def error_invalid_credentials(): return str("Incorrect email or password") + + +def scalar_error_type(value_type, value): + return str("Value is not a valid " + str(value_type) + ": " + str(value)) + + +def scalar_error_only_types(value_types, expected_types, value): + return str("Can only validate " + str(value_types) + " as " + str(expected_types) + " but got a: " + str(type(value))) diff --git a/api/scalars/__init__.py b/api/scalars/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/scalars/email_address.py b/api/scalars/email_address.py new file mode 100644 index 0000000000..cac8af7355 --- /dev/null +++ b/api/scalars/email_address.py @@ -0,0 +1,49 @@ +import datetime +from re import compile +from graphene.types import Scalar +from graphql.language import ast +from graphql import GraphQLError + +from functions.error_messages import * + +EMAIL_ADDRESS_REGEX = r'''(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[ +\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[ +a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][ +0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[ +\x01-\x09\x0b\x0c\x0e-\x7f])+)\])''' + +EMAIL_ADDRESS_REGEX = compile(EMAIL_ADDRESS_REGEX) + + +class EmailAddress(Scalar): + '''A field whose value conforms to the standard internet email address format as specified in RFC822: https://www.w3.org/Protocols/rfc822/.''' + + @staticmethod + def serialize(value): + if not isinstance(value, str): + raise GraphQLError(scalar_error_type("String", value)) + + if not EMAIL_ADDRESS_REGEX.search(value): + raise GraphQLError(scalar_error_type("email address", value)) + + return value + + @staticmethod + def parse_value(value): + if not isinstance(value, str): + raise GraphQLError(scalar_error_type("String", value)) + + if not EMAIL_ADDRESS_REGEX.search(value): + raise GraphQLError(scalar_error_type("email address", value)) + + return value + + @staticmethod + def parse_literal(node): + if not isinstance(node, ast.StringValue): + raise GraphQLError(scalar_error_only_types("strings", "email address", str(ast.Type))) + + if not EMAIL_ADDRESS_REGEX.search(node.value): + raise GraphQLError(scalar_error_type("email address", node.value)) + + return node.value diff --git a/api/tests/test_email_address_scalar.py b/api/tests/test_email_address_scalar.py new file mode 100644 index 0000000000..07fe8140b8 --- /dev/null +++ b/api/tests/test_email_address_scalar.py @@ -0,0 +1,67 @@ +import sys +import os + +import pytest +from graphene.test import Client +from graphql import GraphQLScalarType +from graphql.language import ast +from graphql import GraphQLError + +import unittest + +# This is the only way I could get imports to work for unit testing. TODO: See if there is a better way! +PACKAGE_PARENT = '..' +SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))) +sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))) + +from scalars.email_address import * + + +class TestEmailAddressScalar(unittest.TestCase): + + def test_valid_email_serialize(self): + test_email = 'test.account@canada.ca' + assert EmailAddress.serialize(test_email) + + def test_valid_email_parse_value(self): + test_email = "test.account@canada.ca" + assert EmailAddress.parse_value(test_email) + + def test_valid_email_parse_literal(self): + assert EmailAddress.parse_literal(ast.StringValue( + value="test.account@canada.ca" + )) + + def test_invalid_email_serialize_not_email(self): + test_value = 'This Will Fail' + with self.assertRaisesRegex(GraphQLError, scalar_error_type("email address", test_value)): + EmailAddress.serialize(test_value) + + def test_invalid_email_serialize_wrong_type(self): + test_value = 1234 + with self.assertRaisesRegex(GraphQLError, scalar_error_type("String", test_value)): + EmailAddress.serialize(test_value) + + def test_invalid_email_parse_value_not_email(self): + test_value = 'This Will Fail' + with self.assertRaisesRegex(GraphQLError, scalar_error_type("email address", test_value)): + EmailAddress.parse_value(test_value) + + def test_invalid_email_parse_value_wrong_type(self): + test_value = 1234 + with self.assertRaisesRegex(GraphQLError, scalar_error_type("String", test_value)): + EmailAddress.parse_value(test_value) + + def test_invalid_email_parse_literal_not_email(self): + test_value = ast.StringValue( + value='This Will Fail' + ) + with self.assertRaisesRegex(GraphQLError, scalar_error_type("email address", test_value.value)): + EmailAddress.parse_literal(test_value) + + def test_invalid_email_parse_literal_wrong_ast_type(self): + test_value = ast.IntValue( + value="1234" + ) + with self.assertRaisesRegex(GraphQLError, scalar_error_only_types("strings", "email address", str(ast.Type))): + EmailAddress.parse_literal(test_value)