Skip to content

Commit

Permalink
Add errors.md with list of Member API errors (#2080)
Browse files Browse the repository at this point in the history
- Move all REST API errors to single structure `{errors: []}`
- Detailed description for order & withdraw POST failures
- Add missing values validations
- Add missing allow_blank validations
- Add custom errors message for:
  - values validations
  - types validations
  - allow_blank validations
  - presence validations
  - custom validations
- Add errors.md with errors list & description
- Remove DB queries from desc
- Remove API::V2::Error and descendants
- Update ExceptionHandlers
- Move NamedParams to /market
- Add Order::InsufficientMarketLiquidity
- include_api_error rspec matcher

Co-authored-by: Yaroslav Savchuk <ysavchuk@heliostech.fr>
Co-authored-by: Maksim Naichuk <mnaichuk@heliostech.fr>
Co-authored-by: Ali Shanaakh <ashanaakh@heliostech.fr>
  • Loading branch information
3 people committed Feb 20, 2019
1 parent 562117f commit 659fd38
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 60 deletions.
2 changes: 1 addition & 1 deletion app/api/v2/account/withdraws.rb
Expand Up @@ -47,7 +47,7 @@ class Withdraws < Grape::API
desc: 'Wallet address on the Blockchain.'
requires :currency,
type: String,
values: { value: -> { Currency.coins.codes(bothcase: true) }, message: 'account.withdraw.currency_doesnt_exist'},
values: { value: -> { Currency.coins.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
desc: 'The currency code.'
requires :amount,
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
Expand Down
2 changes: 1 addition & 1 deletion app/api/v2/helpers.rb
Expand Up @@ -30,7 +30,7 @@ def trading_must_be_permitted!

def withdraw_api_must_be_enabled!
if ENV.false?('ENABLE_ACCOUNT_WITHDRAWAL_API')
error!({ errors: ['withdraw.status.disabled'] }, 422)
error!({ errors: ['account.withdraw.disabled_api'] }, 422)
end
end

Expand Down
75 changes: 34 additions & 41 deletions app/api/v2/market/named_params.rb
Expand Up @@ -7,70 +7,63 @@ module Market
module NamedParams
extend ::Grape::API::Helpers

params :currency do
requires :currency,
type: String,
values: { value: -> { Currency.enabled.pluck(:id) }, message: 'account.currency.doesnt_exist' },
desc: 'The currency code.'
end

params :market do
requires :market,
type: String,
values: { value: -> { ::Market.enabled.ids }, message: 'market.market.doesnt_exist' },
desc: -> { V2::Entities::Market.documentation[:id] }
type: String,
values: { value: -> { ::Market.enabled.ids }, message: 'market.market.doesnt_exist' },
desc: -> { V2::Entities::Market.documentation[:id] }
end

params :order do
requires :side,
type: String,
values: { value: %w(sell buy), message: 'market.order.invalid_side' },
desc: -> { V2::Entities::Order.documentation[:side] }
type: String,
values: { value: %w(sell buy), message: 'market.order.invalid_side' },
desc: -> { V2::Entities::Order.documentation[:side] }
requires :volume,
type: { value: BigDecimal, message: 'market.order.non_decimal_volume' },
values: { value: -> (v){ v.try(:positive?) }, message: 'market.order.non_positive_volume' },
desc: -> { V2::Entities::Order.documentation[:volume] }
type: { value: BigDecimal, message: 'market.order.non_decimal_volume' },
values: { value: -> (v){ v.try(:positive?) }, message: 'market.order.non_positive_volume' },
desc: -> { V2::Entities::Order.documentation[:volume] }
optional :ord_type,
type: String,
values: { value: -> { Order::TYPES}, message: 'market.order.invalid_type' },
default: 'limit',
desc: -> { V2::Entities::Order.documentation[:type] }
type: String,
values: { value: -> { Order::TYPES}, message: 'market.order.invalid_type' },
default: 'limit',
desc: -> { V2::Entities::Order.documentation[:type] }
given ord_type: ->(val) { val == 'limit' } do
requires :price,
type: { value: BigDecimal, message: 'market.order.non_decimal_price' },
values: { value: -> (p){ p.try(:positive?) }, message: 'market.order.non_positive_price' },
desc: -> { V2::Entities::Order.documentation[:price] }
type: { value: BigDecimal, message: 'market.order.non_decimal_price' },
values: { value: -> (p){ p.try(:positive?) }, message: 'market.order.non_positive_price' },
desc: -> { V2::Entities::Order.documentation[:price] }
end
end

params :order_id do
requires :id,
type: { value: Integer, message: 'market.order.non_integer_id' },
allow_blank: { value: false, message: 'market.order.empty_id' },
desc: -> { V2::Entities::Order.documentation[:id] }
type: { value: Integer, message: 'market.order.non_integer_id' },
allow_blank: { value: false, message: 'market.order.empty_id' },
desc: -> { V2::Entities::Order.documentation[:id] }
end

params :trade_filters do
optional :limit,
type: { value: Integer, message: 'market.trade.non_integer_limit' },
values: { value: 1..1000, message: 'market.trade.invalid_limit' },
default: 100,
desc: 'Limit the number of returned trades. Default to 100.'
type: { value: Integer, message: 'market.trade.non_integer_limit' },
values: { value: 1..1000, message: 'market.trade.invalid_limit' },
default: 100,
desc: 'Limit the number of returned trades. Default to 100.'
optional :page,
type: { value: Integer, message: 'market.trade.non_integer_page' },
allow_blank: { value: false, message: 'market.trade.empty_page' },
default: 1,
desc: 'Specify the page of paginated results.'
type: { value: Integer, message: 'market.trade.non_integer_page' },
allow_blank: { value: false, message: 'market.trade.empty_page' },
default: 1,
desc: 'Specify the page of paginated results.'
optional :timestamp,
type: { value: Integer, message: 'market.trade.non_integer_timestamp' },
allow_blank: { value: false, message: 'market.trade.empty_timestamp' },
desc: "An integer represents the seconds elapsed since Unix epoch."\
type: { value: Integer, message: 'market.trade.non_integer_timestamp' },
allow_blank: { value: false, message: 'market.trade.empty_timestamp' },
desc: "An integer represents the seconds elapsed since Unix epoch."\
"If set, only trades executed before the time will be returned."
optional :order_by,
type: String,
values: { value: %w(asc desc), message: 'market.trade.invalid_order_by' },
default: 'desc',
desc: "If set, returned trades will be sorted in specific order, default to 'desc'."
type: String,
values: { value: %w(asc desc), message: 'market.trade.invalid_order_by' },
default: 'desc',
desc: "If set, returned trades will be sorted in specific order, default to 'desc'."
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions app/api/v2/mount.rb
@@ -1,6 +1,8 @@
# encoding: UTF-8
# frozen_string_literal: true

require_dependency 'v2/validations'

module API
module V2
class Mount < Grape::API
Expand Down
32 changes: 21 additions & 11 deletions app/api/v2/validations.rb
Expand Up @@ -22,26 +22,36 @@ def validate_param!(attr, params)
end
end

class IntegerGTZero < Grape::Validations::Base
def validate_param!(name, params)
return unless params.key?(name)
return if params[name].to_s.to_i > 0

fail Grape::Exceptions::Validation,
params: [@scope.full_name(name)],
message: "#{name} must be greater than zero."
class PresenceValidator < Grape::Validations::PresenceValidator
# Default exception is costructed from `@api` class name.
# E.g
# @api.class => API::V2::Account::Withdraws
# default_message => "account.withdraw.missing_otp"

def message(_param)
api = @scope.instance_variable_get(:@api)
module_name = api.parent.name.humanize.demodulize
class_name = api.name.humanize.demodulize.singularize
# Return default API error message for Management module (no errors unify).
return super if module_name == 'management'

options_key?(:message) ? @option[:message] : default_exception(module_name, class_name)
end

def default_exception(module_name, class_name)
"#{module_name}.#{class_name}.missing_#{attrs.first}"
end
end

class ValidateTradeFromTo < Grape::Validations::Base
class IntegerGTZero < Grape::Validations::Base
def validate_param!(name, params)
return unless params.key?(name)
return unless params.key?(:to)
return if params[name].to_i < params[:to].to_i
return if params[name].to_s.to_i > 0

fail Grape::Exceptions::Validation,
params: [@scope.full_name(name)],
message: 'should be less than to.'
message: "#{name} must be greater than zero."
end
end

Expand Down
109 changes: 109 additions & 0 deletions docs/api/errors.md
@@ -0,0 +1,109 @@
# Peatio Member API Errors

## Shared errors

| Code | Description |
| ----------------------- | ----------------------------------- |
| `jwt.decode_and_verify` | Impossible to decode and verify JWT |
| `record.not_found` | Record Not found |
| `server.internal_error` | Internal Server Error |

## Account module

| Code | Description |
| --------------------------------------- | ---------------------------------------------- |
| `account.currency.doesnt_exist` | **Currency** doesn't exist in database |
| `account.balance.missing_currency` | Parameter **currency** is missing |
| `account.deposit.missing_currency` | Parameter **currency** is missing |
| `account.deposit.invalid_state` | Deposit **state** is not valid |
| `account.deposit.non_integer_limit` | Parameter **limit** should be integer |
| `account.deposit.invalid_limit` | Parameter **limit** is not valid |
| `account.deposit.non_positive_page` | Parameter **page** should be positive number |
| `account.deposit.empty_txid` | Parameter **txid** is empty |
| `account.withdraw.missing_txid` | Parameter **txid** is missing |
| `account.deposit.not_permitted` | Pass the corresponding verification steps to **deposit funds** |
| `account.withdraw.non_integer_limit` | Parameter **limit** should be integer |
| `account.withdraw.invalid_limit` | Parameter **limit** is not valid |
| `account.withdraw.non_positive_page` | Parameter **page** should be positive number |
| `account.withdraw.non_integer_otp` | Parameter **otp** should be integer |
| `account.withdraw.empty_otp` | Parameter **otp** is empty |
| `account.withdraw.missing_otp` | Parameter **otp** is missing |
| `account.withdraw.missing_rid` | Parameter **rid** is missing |
| `account.withdraw.missing_amount` | Parameter **amount** is missing |
| `account.withdraw.missing_currency` | Parameter **currency** is missing |
| `account.withdraw.empty_rid` | Parameter **rid** is empty |
| `account.withdraw.non_decimal_amount` | Parameter **amount** should be decimal |
| `account.withdraw.non_positive_amount` | Parameter **amount** should be positive number |
| `account.withdraw.insufficient_balance` | Account **balance** is insufficient |
| `account.withdraw.invalid_amount` | Parameter **amount** is not valid |
| `account.withdraw.create_error` | Failed to create withdraw |
| `account.withdraw.invalid_otp` | Parameter **otp** is not valid |
| `account.withdraw.disabled_api` | Withdrawal API is disabled |
| `account.withdraw.not_permitted` | Pass the corresponding verification steps to **withdraw funds** |
| `account.deposit_address.invalid_address_format` | Invalid parameter for deposit address format |
| `account.deposit_address.doesnt_support_cash_address_format` | Currency doesn't support cash address format |

## Market module

| Code | Description |
| -------------------------------------------- | ----------------------------------------------------------------|
| `market.account.insufficient_balance` | Account balance is insufficient |
| `market.market.doesnt_exist` | **Market** doesn't exist in database |
| `market.order.insufficient_market_liquidity` | Insufficient market liquidity |
| `market.order.invalid_volume_or_price` | Order **volume** or **price** is invalid for selected market |
| `market.order.create_error` | Failed to create order |
| `market.order.cancel_error` | Failed to cancel order |
| `market.order.market_order_price` | Market order doesn't have **price** |
| `market.order.invalid_state` | Parameter **state** is not valid |
| `market.order.invalid_limit` | Parameter **limit** is not valid |
| `market.order.non_integer_limit` | Parameter **limit** should be integer |
| `market.order.invalid_order_by` | Parameter **order_by** is not valid |
| `market.order.invalid_side` | Parameter **side** is not valid |
| `market.order.missing_market` | Parameter **market** is missing |
| `market.order.missing_side` | Parameter **side** is missing |
| `market.order.missing_volume` | Parameter **volume** is missing |
| `market.order.missing_price` | Parameter **price** is missing |
| `market.order.missing_id` | Parameter **id** is missing |
| `market.order.non_decimal_volume` | Parameter **volume** should be decimal |
| `market.order.non_positive_volume` | Parameter **volume** should be positive number |
| `market.order.invalid_type` | Parameter **type** is not valid |
| `market.order.non_decimal_price` | Parameter **price** should be decimal |
| `market.order.non_positive_price` | Parameter **price** should be positive number |
| `market.order.non_integer_id` | Parameter **id** should be integer |
| `market.order.empty_id` | Parameter **id** is empty |
| `market.trade.non_integer_limit` | Parameter **limit** should be integer |
| `market.trade.invalid_limit` | Parameter **limit** is not valid |
| `market.trade.empty_page` | Parameter **page** is empty |
| `market.trade.non_integer_timestamp` | Parameter **timestamp** should be integer |
| `market.trade.empty_timestamp` | Parameter **timestamp** is empty |
| `market.trade.invalid_order_by` | Parameter **order_by** is not valid |
| `market.trade.not_permitted` | Pass the corresponding verification steps to **enable trading** |

## Public module

| Code | Description |
| ----------------------------------------- | ---------------------------------------------|
| `public.currency.doesnt_exist` | **Currency** doesn't exist in database |
| `public.currency.invalid_type` | **Currency** type is not valid |
| `public.currency.missing_id` | Parameter **id** is missing |
| `public.market.missing_market` | Parameter **market** is missing |
| `public.market.doesnt_exist` | **Market** doesn't exist in database |
| `public.order_book.non_integer_ask_limit` | Parameter **ask_limit** should be integer |
| `public.order_book.invalid_ask_limit` | Parameter **ask_limit** is not valid |
| `public.order_book.non_integer_bid_limit` | Parameter **bid_limit** should be integer |
| `public.order_book.invalid_bid_limit` | Parameter **bid_limit** is not valid |
| `public.trade.invalid_limit` | Parameter **limit** is not valid |
| `public.trade.non_integer_limit` | Parameter **limit** should be integer |
| `public.trade.non_positive_page` | Parameter **page** should be positive number |
| `public.trade.non_integer_timestamp` | Parameter **timestamp** should be integer |
| `public.trade.invalid_order_by` | Parameter **order_by** is not valid |
| `public.market_depth.non_integer_limit` | Parameter **limit** should be integer |
| `public.market_depth.invalid_limit` | Parameter **limit** is not valid |
| `public.k_line.non_integer_period` | Parameter **period** should be integer |
| `public.k_line.invalid_period` | Parameter **period** is not valid |
| `public.k_line.non_integer_time_from` | Parameter **time_from** should be integer |
| `public.k_line.empty_time_from` | Parameter **time_from** is empty |
| `public.k_line.non_integer_time_to` | Parameter **time_to** should be integer |
| `public.k_line.empty_time_to` | Parameter **time_to** is empty |
| `public.k_line.non_integer_limit` | Parameter **limit** should be integer |
| `public.k_line.invalid_limit` | Parameter **limit** is not valid |

0 comments on commit 659fd38

Please sign in to comment.