Skip to content

Commit

Permalink
Merge pull request #4676 from mirumee/add-error-codes-in-api
Browse files Browse the repository at this point in the history
Add error codes to API
  • Loading branch information
korycins committed Sep 19, 2019
2 parents 9e68873 + 702e735 commit d369a4b
Show file tree
Hide file tree
Showing 66 changed files with 2,151 additions and 198 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Migrated the old product attributes mapping to M2M - #4663 by @NyanKiyoshi
- Add translations to countries in shop query - #4732 by @fowczarek
- Added validations for minimum password length in settings - #4735 by @fowczarek
- Add error codes to mutations responses - #4676 by @Kwaidan00

## 2.8.0

Expand Down
22 changes: 22 additions & 0 deletions saleor/account/error_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from enum import Enum


class AccountErrorCode(Enum):
ACTIVATE_OWN_ACCOUNT = "activate_own_account"
ACTIVATE_SUPERUSER_ACCOUNT = "activate_superuser_account"
DEACTIVATE_OWN_ACCOUNT = "deactivate_own_account"
DEACTIVATE_SUPERUSER_ACCOUNT = "deactivate_superuser_account"
DELETE_NON_STAFF_USER = "delete_non_staff_user"
DELETE_OWN_ACCOUNT = "delete_own_account"
DELETE_STAFF_ACCOUNT = "delete_staff_account"
DELETE_SUPERUSER_ACCOUNT = "delete_superuser_account"
GRAPHQL_ERROR = "graphql_error"
INVALID = "invalid"
INVALID_PASSWORD = "invalid_password"
NOT_FOUND = "not_found"
PASSWORD_ENTIRELY_NUMERIC = "password_entirely_numeric"
PASSWORD_TOO_COMMON = "password_too_common"
PASSWORD_TOO_SHORT = "password_too_short"
PASSWORD_TOO_SIMILAR = "password_too_similar"
REQUIRED = "required"
UNIQUE = "unique"
3 changes: 2 additions & 1 deletion saleor/account/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import i18naddress
from django import forms
from django.core.exceptions import ValidationError
from django.forms.forms import BoundField
from django.utils.translation import pgettext_lazy
from django_countries import countries
Expand Down Expand Up @@ -178,7 +179,7 @@ def add_field_errors(self, errors):
error_msg = pgettext_lazy(
"Address form", "This value is invalid for selected country"
)
self.add_error(field, error_msg)
self.add_error(field, ValidationError(error_msg, code=error_code))

def validate_address(self, data):
try:
Expand Down
4 changes: 3 additions & 1 deletion saleor/account/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from phonenumber_field.phonenumber import to_python
from phonenumbers.phonenumberutil import is_possible_number

from .error_codes import AccountErrorCode


def validate_possible_number(phone, country=None):
phone_number = to_python(phone, country)
Expand All @@ -12,6 +14,6 @@ def validate_possible_number(phone, country=None):
or not phone_number.is_valid()
):
raise ValidationError(
_("The phone number entered is not valid."), code="invalid_phone_number"
_("The phone number entered is not valid."), code=AccountErrorCode.INVALID
)
return phone_number
22 changes: 22 additions & 0 deletions saleor/checkout/error_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from enum import Enum


class CheckoutErrorCode(Enum):
BILLING_ADDRESS_NOT_SET = "billing_address_not_set"
CHECKOUT_NOT_FULLY_PAID = "checkout_not_fully_paid"
GRAPHQL_ERROR = "graphql_error"
INSUFFICIENT_STOCK = "insufficient_stock"
INVALID = "invalid"
INVALID_SHIPPING_METHOD = "invalid_shipping_method"
NOT_FOUND = "not_found"
PAYMENT_ERROR = "payment_error"
QUANTITY_GREATER_THAN_LIMIT = "quantity_greater_than_limit"
REQUIRED = "required"
SHIPPING_ADDRESS_NOT_SET = "shipping_address_not_set"
SHIPPING_METHOD_NOT_APPLICABLE = "shipping_method_not_applicable"
SHIPPING_METHOD_NOT_SET = "shipping_method_not_set"
SHIPPING_NOT_REQUIRED = "shipping_not_required"
TAX_ERROR = "tax_error"
UNIQUE = "unique"
VOUCHER_NOT_APPLICABLE = "voucher_not_applicable"
ZERO_QUANTITY = "zero_quantity"
2 changes: 1 addition & 1 deletion saleor/checkout/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def clean_quantity(self):
self.variant.check_quantity(quantity)
except InsufficientStock as e:
msg = self.error_messages["insufficient-stock"]
raise forms.ValidationError(msg % e.item.quantity_available)
raise forms.ValidationError(msg % e.item.quantity_available, code=e.code)
return quantity

def clean(self):
Expand Down
28 changes: 22 additions & 6 deletions saleor/checkout/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ..account.forms import get_address_form
from ..account.models import Address, User
from ..account.utils import store_user_address
from ..checkout.error_codes import CheckoutErrorCode
from ..core.exceptions import InsufficientStock
from ..core.taxes import quantize_price, zero_taxed_money
from ..core.utils import to_local_currency
Expand Down Expand Up @@ -828,7 +829,12 @@ def add_voucher_code_to_checkout(checkout: Checkout, voucher_code: str, discount
add_voucher_to_checkout(checkout, voucher, discounts)
except NotApplicable:
raise ValidationError(
{"promo_code": "Voucher is not applicable to that checkout."}
{
"promo_code": ValidationError(
"Voucher is not applicable to that checkout.",
code=CheckoutErrorCode.VOUCHER_NOT_APPLICABLE,
)
}
)


Expand Down Expand Up @@ -1191,18 +1197,28 @@ def clean_checkout(checkout: Checkout, discounts):
"""Check if checkout can be completed."""
if checkout.is_shipping_required():
if not checkout.shipping_method:
raise ValidationError("Shipping method is not set")
raise ValidationError(
"Shipping method is not set",
code=CheckoutErrorCode.SHIPPING_METHOD_NOT_SET,
)
if not checkout.shipping_address:
raise ValidationError("Shipping address is not set")
raise ValidationError(
"Shipping address is not set",
code=CheckoutErrorCode.SHIPPING_ADDRESS_NOT_SET,
)
if not is_valid_shipping_method(checkout, discounts):
raise ValidationError(
"Shipping method is not valid for your shipping address"
"Shipping method is not valid for your shipping address",
code=CheckoutErrorCode.INVALID_SHIPPING_METHOD,
)

if not checkout.billing_address:
raise ValidationError("Billing address is not set")
raise ValidationError(
"Billing address is not set", code=CheckoutErrorCode.BILLING_ADDRESS_NOT_SET
)

if not is_fully_paid(checkout, discounts):
raise ValidationError(
"Provided payment methods can not cover the checkout's total " "amount"
"Provided payment methods can not cover the checkout's total amount",
code=CheckoutErrorCode.CHECKOUT_NOT_FULLY_PAID,
)
11 changes: 11 additions & 0 deletions saleor/core/error_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from enum import Enum


class ShopErrorCode(Enum):
ALREADY_EXISTS = "already_exists"
CANNOT_FETCH_TAX_RATES = "cannot_fetch_tax_rates"
GRAPHQL_ERROR = "graphql_error"
INVALID = "invalid"
NOT_FOUND = "not_found"
REQUIRED = "required"
UNIQUE = "unique"
4 changes: 4 additions & 0 deletions saleor/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from ..checkout.error_codes import CheckoutErrorCode


class InsufficientStock(Exception):
def __init__(self, item):
super().__init__("Insufficient stock for %r" % (item,))
self.item = item
self.code = CheckoutErrorCode.INSUFFICIENT_STOCK
12 changes: 10 additions & 2 deletions saleor/core/utils/promo_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
from django.core.exceptions import ValidationError

from ...discount.models import Voucher
from ...giftcard.error_codes import GiftCardErrorCode
from ...giftcard.models import GiftCard


class InvalidPromoCode(ValidationError):
def __init__(self, message=None, **kwargs):
if message is None:
message = {"promo_code": "Promo code is invalid"}
message = {
"promo_code": ValidationError(
"Promo code is invalid", code=GiftCardErrorCode.INVALID
)
}
super().__init__(message, **kwargs)


class PromoCodeAlreadyExists(ValidationError):
def __init__(self, message=None, **kwargs):
code = kwargs.get("code", GiftCardErrorCode.ALREADY_EXISTS)
if message is None:
message = {"promo_code": "Promo code already exists."}
message = {
"promo_code": ValidationError("Promo code already exists.", code=code)
}
super().__init__(message, **kwargs)


Expand Down
10 changes: 8 additions & 2 deletions saleor/core/utils/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from django.core.exceptions import ValidationError
from django.http.request import split_domain_port, validate_host

from ...account.error_codes import AccountErrorCode


def validate_storefront_url(url):
"""Validate the storefront URL.
Expand All @@ -15,10 +17,14 @@ def validate_storefront_url(url):
parsed_url = urlparse(url)
domain, _ = split_domain_port(parsed_url.netloc)
except ValueError as error:
raise ValidationError({"redirectUrl": str(error)})
raise ValidationError(
{"redirectUrl": str(error)}, code=AccountErrorCode.INVALID
)
if not validate_host(domain, settings.ALLOWED_CLIENT_HOSTS):
error_message = (
f"{domain or url} is not allowed. Please check "
"`ALLOWED_CLIENT_HOSTS` configuration."
)
raise ValidationError({"redirectUrl": error_message})
raise ValidationError(
{"redirectUrl": error_message}, code=AccountErrorCode.INVALID
)
3 changes: 2 additions & 1 deletion saleor/core/weight.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ def validate(self, weight):
raise Exception("%r is not a valid weight." % (weight,))
if weight.unit != unit:
raise forms.ValidationError(
"Invalid unit: %r (expected %r)." % (weight.unit, unit)
"Invalid unit: %r (expected %r)." % (weight.unit, unit),
code="invalid",
)
super().validate(weight.value)

Expand Down
10 changes: 10 additions & 0 deletions saleor/extensions/error_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from enum import Enum


class ExtensionsErrorCode(Enum):
GRAPHQL_ERROR = "graphql_error"
INVALID = "invalid"
PLUGIN_MISCONFIGURED = "plugin-misconfigured"
NOT_FOUND = "not_found"
REQUIRED = "required"
UNIQUE = "unique"
6 changes: 5 additions & 1 deletion saleor/extensions/plugins/avatax/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ....core.taxes import TaxError, TaxType, zero_taxed_money
from ... import ConfigurationTypeField
from ...base_plugin import BasePlugin
from ...error_codes import ExtensionsErrorCode
from . import (
META_FIELD,
META_NAMESPACE,
Expand Down Expand Up @@ -436,7 +437,10 @@ def validate_plugin_configuration(cls, plugin_configuration: "PluginConfiguratio
"To enable a plugin, you need to provide values for the "
"following fields: "
)
raise ValidationError(error_msg + ", ".join(missing_fields))
raise ValidationError(
error_msg + ", ".join(missing_fields),
code=ExtensionsErrorCode.PLUGIN_MISCONFIGURED,
)

@classmethod
def _hide_secret_configuration_fields(cls, configuration):
Expand Down
4 changes: 3 additions & 1 deletion saleor/extensions/plugins/vatlayer/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from prices import Money, MoneyRange, TaxedMoney, TaxedMoneyRange

from ....core.taxes import TaxType
from ....graphql.core.utils.error_codes import ExtensionsErrorCode
from ...base_plugin import BasePlugin
from . import (
DEFAULT_TAX_RATE_NAME,
Expand Down Expand Up @@ -327,7 +328,8 @@ def validate_plugin_configuration(cls, plugin_configuration: "PluginConfiguratio
"""Validate if provided configuration is correct."""
if not settings.VATLAYER_ACCESS_KEY and plugin_configuration.active:
raise ValidationError(
"Cannot be enabled without provided 'settings.VATLAYER_ACCESS_KEY'"
"Cannot be enabled without provided 'settings.VATLAYER_ACCESS_KEY'",
code=ExtensionsErrorCode.PLUGIN_MISCONFIGURED,
)

@classmethod
Expand Down
10 changes: 10 additions & 0 deletions saleor/giftcard/error_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from enum import Enum


class GiftCardErrorCode(Enum):
ALREADY_EXISTS = "already_exists"
GRAPHQL_ERROR = "graphql_error"
INVALID = "invalid"
NOT_FOUND = "not_found"
REQUIRED = "required"
UNIQUE = "unique"
22 changes: 20 additions & 2 deletions saleor/graphql/account/bulk_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from django.core.exceptions import ValidationError

from ...account import models
from ...account.error_codes import AccountErrorCode
from ..core.mutations import BaseBulkMutation, ModelBulkDeleteMutation
from ..core.types.common import AccountError
from .utils import CustomerDeleteMixin, StaffDeleteMixin


Expand All @@ -21,6 +23,8 @@ class Meta:
description = "Deletes customers."
model = models.User
permissions = ("account.manage_users",)
error_type_class = AccountError
error_type_field = "account_errors"

@classmethod
def perform_mutation(cls, root, info, **data):
Expand All @@ -34,6 +38,8 @@ class Meta:
description = "Deletes staff users."
model = models.User
permissions = ("account.manage_staff",)
error_type_class = AccountError
error_type_field = "account_errors"


class UserBulkSetActive(BaseBulkMutation):
Expand All @@ -49,16 +55,28 @@ class Meta:
description = "Activate or deactivate users."
model = models.User
permissions = ("account.manage_users",)
error_type_class = AccountError
error_type_field = "account_errors"

@classmethod
def clean_instance(cls, info, instance):
if info.context.user == instance:
raise ValidationError(
{"is_active": "Cannot activate or deactivate your own account."}
{
"is_active": ValidationError(
"Cannot activate or deactivate your own account.",
code=AccountErrorCode.ACTIVATE_OWN_ACCOUNT,
)
}
)
elif instance.is_superuser:
raise ValidationError(
{"is_active": "Cannot activate or deactivate superuser's account."}
{
"is_active": ValidationError(
"Cannot activate or deactivate superuser's account.",
code=AccountErrorCode.ACTIVATE_SUPERUSER_ACCOUNT,
)
}
)

@classmethod
Expand Down
17 changes: 14 additions & 3 deletions saleor/graphql/account/i18n.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.core.exceptions import ValidationError
from django_countries import countries

from ...account.error_codes import AccountErrorCode
from ...account.forms import get_address_form
from ...account.models import Address
from ...account.validators import validate_possible_number
Expand All @@ -20,17 +21,27 @@ def validate_address(cls, address_data: dict, instance=None):
validate_possible_number(phone, address_data.get("country"))
except ValidationError as exc:
raise ValidationError(
{"phone": f"'{phone}' is not a valid phone number."}
{
"phone": ValidationError(
f"'{phone}' is not a valid phone number.", code=exc.code
)
}
) from exc

country_code = address_data.get("country")
if country_code in countries.countries.keys():
address_form, _ = get_address_form(address_data, address_data["country"])
else:
raise ValidationError({"country": "Invalid country code."})
raise ValidationError(
{
"country": ValidationError(
"Invalid country code.", code=AccountErrorCode.INVALID
)
}
)

if not address_form.is_valid():
raise ValidationError(address_form.errors)
raise ValidationError(address_form.errors.as_data())

if not instance:
instance = Address()
Expand Down

0 comments on commit d369a4b

Please sign in to comment.