# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::WebService::Constants;

use 5.10.1;
use strict;
use warnings;

use parent qw(Exporter);

our @EXPORT = qw(
  WS_ERROR_CODE

  STATUS_OK
  STATUS_CREATED
  STATUS_ACCEPTED
  STATUS_NO_CONTENT
  STATUS_MULTIPLE_CHOICES
  STATUS_BAD_REQUEST
  STATUS_NOT_FOUND
  STATUS_GONE
  REST_STATUS_CODE_MAP

  ERROR_UNKNOWN_FATAL
  ERROR_UNKNOWN_TRANSIENT

  XMLRPC_CONTENT_TYPE_WHITELIST
  REST_CONTENT_TYPE_WHITELIST

  WS_DISPATCH
);

# This maps the error names in global/*-error.html.tmpl to numbers.
# Generally, transient errors should have a number above 0, and
# fatal errors should have a number below 0.
#
# This hash should generally contain any error that could be thrown
# by the WebService interface. If it's extremely unlikely that the
# error could be thrown (like some CodeErrors), it doesn't have to
# be listed here.
#
# "Transient" means "If you resubmit that request with different data,
# it may work."
#
# "Fatal" means, "There's something wrong with Bugzilla, probably
# something an administrator would have to fix."
#
# NOTE: Numbers must never be recycled. If you remove a number, leave a
# comment that it was retired. Also, if an error changes its name, you'll
# have to fix it here.
use constant WS_ERROR_CODE => {

  # Generic errors (Bugzilla::Object and others) are 50-99.
  object_not_specified        => 50,
  reassign_to_empty           => 50,
  param_required              => 50,
  params_required             => 50,
  undefined_field             => 50,
  object_does_not_exist       => 51,
  param_must_be_numeric       => 52,
  number_not_numeric          => 52,
  param_invalid               => 53,
  number_too_large            => 54,
  number_too_small            => 55,
  illegal_date                => 56,
  param_integer_required      => 57,
  param_scalar_array_required => 58,

  # Bug errors usually occupy the 100-200 range.
  improper_bug_id_field_value => 100,
  bug_id_does_not_exist       => 101,
  bug_access_denied           => 102,
  bug_access_query            => 102,

  # These all mean "invalid alias"
  alias_too_long             => 103,
  alias_in_use               => 103,
  alias_is_numeric           => 103,
  alias_has_comma_or_space   => 103,
  multiple_alias_not_allowed => 103,

  # Misc. bug field errors
  illegal_field     => 104,
  freetext_too_long => 104,

  # Component errors
  require_component         => 105,
  component_name_too_long   => 105,
  product_unknown_component => 105,

  # Invalid Product
  no_products           => 106,
  entry_access_denied   => 106,
  product_access_denied => 106,
  product_disabled      => 106,

  # Invalid Summary
  require_summary => 107,

  # Invalid field name
  invalid_field_name => 108,

  # Not authorized to edit the bug
  product_edit_denied => 109,

  # Comment-related errors
  comment_is_private        => 110,
  comment_id_invalid        => 111,
  comment_too_long          => 114,
  comment_invalid_isprivate => 117,

  # Comment tagging
  comment_tag_disabled  => 125,
  comment_tag_invalid   => 126,
  comment_tag_too_long  => 127,
  comment_tag_too_short => 128,

  # See Also errors
  bug_url_invalid  => 112,
  bug_url_too_long => 112,

  # Insidergroup Errors
  user_not_insider => 113,

  # Note: 114 is above in the Comment-related section.
  # Bug update errors
  illegal_change => 115,

  # Dependency errors
  dependency_loop_single => 116,
  dependency_loop_multi  => 116,

  # Note: 117 is above in the Comment-related section.
  # Dup errors
  dupe_loop_detected => 118,
  dupe_id_required   => 119,

  # Bug-related group errors
  group_invalid_removal         => 120,
  group_restriction_not_allowed => 120,

  # Status/Resolution errors
  missing_resolution            => 121,
  resolution_not_allowed        => 122,
  illegal_bug_status_transition => 123,

  # Flag errors
  flag_status_invalid          => 129,
  flag_update_denied           => 130,
  flag_type_requestee_disabled => 131,
  flag_not_unique              => 132,
  flag_type_not_unique         => 133,
  flag_type_inactive           => 134,

  # Authentication errors are usually 300-400.
  invalid_login_or_password => 300,
  account_disabled          => 301,
  auth_invalid_email        => 302,
  extern_id_conflict        => -303,
  auth_failure              => 304,
  password_too_short        => 305,
  password_not_complex      => 305,
  api_key_not_valid         => 306,
  api_key_revoked           => 306,
  auth_invalid_token        => 307,

  # Except, historically, AUTH_NODATA, which is 410.
  login_required => 410,

  # User errors are 500-600.
  account_exists              => 500,
  illegal_email_address       => 501,
  auth_cant_create_account    => 501,
  account_creation_disabled   => 501,
  account_creation_restricted => 501,
  password_too_short          => 502,

  # Error 503 password_too_long no longer exists.
  invalid_username => 504,

  # This is from strict_isolation, but it also basically means
  # "invalid user."
  invalid_user_group          => 504,
  user_access_by_id_denied    => 505,
  user_access_by_match_denied => 505,

  # Attachment errors are 600-700.
  file_too_large       => 600,
  invalid_content_type => 601,

  # Error 602 attachment_illegal_url no longer exists.
  file_not_specified             => 603,
  missing_attachment_description => 604,

  # Error 605 attachment_url_disabled no longer exists.
  zero_length_file => 606,

  # Product erros are 700-800
  product_blank_name                   => 700,
  product_name_too_long                => 701,
  product_name_already_in_use          => 702,
  product_name_diff_in_case            => 702,
  product_must_have_description        => 703,
  product_must_have_version            => 704,
  product_must_define_defaultmilestone => 705,

  # Group errors are 800-900
  empty_group_name        => 800,
  group_exists            => 801,
  empty_group_description => 802,
  invalid_regexp          => 803,
  invalid_group_name      => 804,
  group_cannot_view       => 805,

  # Classification errors are 900-1000
  auth_classification_not_enabled => 900,

  # Search errors are 1000-1100
  buglist_parameters_required => 1000,

  # Flag type errors are 1100-1200
  flag_type_name_invalid        => 1101,
  flag_type_description_invalid => 1102,
  flag_type_cc_list_invalid     => 1103,
  flag_type_sortkey_invalid     => 1104,
  flag_type_not_editable        => 1105,

  # Component errors are 1200-1300
  component_already_exists => 1200,
  component_is_last        => 1201,
  component_has_bugs       => 1202,

  # Errors thrown by the WebService itself. The ones that are negative
  # conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
  xmlrpc_invalid_value          => -32600,
  unknown_method                => -32601,
  json_rpc_post_only            => 32610,
  json_rpc_invalid_callback     => 32611,
  xmlrpc_illegal_content_type   => 32612,
  json_rpc_illegal_content_type => 32613,
  rest_invalid_resource         => 32614,
};

# RESTful webservices use the http status code
# to describe whether a call was successful or
# to describe the type of error that occurred.
use constant STATUS_OK               => 200;
use constant STATUS_CREATED          => 201;
use constant STATUS_ACCEPTED         => 202;
use constant STATUS_NO_CONTENT       => 204;
use constant STATUS_MULTIPLE_CHOICES => 300;
use constant STATUS_BAD_REQUEST      => 400;
use constant STATUS_NOT_AUTHORIZED   => 401;
use constant STATUS_NOT_FOUND        => 404;
use constant STATUS_GONE             => 410;

# The integer value is the error code above returned by
# the related webvservice call. We choose the appropriate
# http status code based on the error code or use the
# default STATUS_BAD_REQUEST.
sub REST_STATUS_CODE_MAP {
  my $status_code_map = {
    51       => STATUS_NOT_FOUND,
    101      => STATUS_NOT_FOUND,
    102      => STATUS_NOT_AUTHORIZED,
    106      => STATUS_NOT_AUTHORIZED,
    109      => STATUS_NOT_AUTHORIZED,
    110      => STATUS_NOT_AUTHORIZED,
    113      => STATUS_NOT_AUTHORIZED,
    115      => STATUS_NOT_AUTHORIZED,
    120      => STATUS_NOT_AUTHORIZED,
    300      => STATUS_NOT_AUTHORIZED,
    301      => STATUS_NOT_AUTHORIZED,
    302      => STATUS_NOT_AUTHORIZED,
    303      => STATUS_NOT_AUTHORIZED,
    304      => STATUS_NOT_AUTHORIZED,
    410      => STATUS_NOT_AUTHORIZED,
    504      => STATUS_NOT_AUTHORIZED,
    505      => STATUS_NOT_AUTHORIZED,
    32614    => STATUS_NOT_FOUND,
    _default => STATUS_BAD_REQUEST
  };

  Bugzilla::Hook::process('webservice_status_code_map',
    {status_code_map => $status_code_map});

  return $status_code_map;
}

# These are the fallback defaults for errors not in ERROR_CODE.
use constant ERROR_UNKNOWN_FATAL     => -32000;
use constant ERROR_UNKNOWN_TRANSIENT => 32000;

use constant ERROR_GENERAL => 999;

use constant XMLRPC_CONTENT_TYPE_WHITELIST => qw(
  text/xml
  application/xml
);

# The first content type specified is used as the default.
use constant REST_CONTENT_TYPE_WHITELIST => qw(
  application/json
  application/javascript
  text/javascript
  text/html
);

sub WS_DISPATCH {

  # We "require" here instead of "use" above to avoid a dependency loop.
  require Bugzilla::Hook;
  my %hook_dispatch;
  Bugzilla::Hook::process('webservice', {dispatch => \%hook_dispatch});

  my $dispatch = {
    'Bugzilla'         => 'Bugzilla::WebService::Bugzilla',
    'Bug'              => 'Bugzilla::WebService::Bug',
    'Classification'   => 'Bugzilla::WebService::Classification',
    'Component'        => 'Bugzilla::WebService::Component',
    'FlagType'         => 'Bugzilla::WebService::FlagType',
    'Group'            => 'Bugzilla::WebService::Group',
    'Product'          => 'Bugzilla::WebService::Product',
    'User'             => 'Bugzilla::WebService::User',
    'BugUserLastVisit' => 'Bugzilla::WebService::BugUserLastVisit',
    %hook_dispatch
  };
  return $dispatch;
}

1;

=head1 B<Methods in need of POD>

=over

=item REST_STATUS_CODE_MAP

=item WS_DISPATCH

=back