# 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.14.0;
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

    API_AUTH_HEADERS
);

# 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,
    markdown_disabled => 140,
    # 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,
    product_admin_denied                 => 706,

    # 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,
    component_blank_name                   => 1210,
    component_blank_description            => 1211,
    multiple_components_update_not_allowed => 1212,
    component_need_initialowner            => 1213,

    # BugUserLastVisited errors
    user_not_involved => 1300,

    # 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;
};

# Custom HTTP headers that can be used for API authentication rather than
# passing as URL parameters. This is useful if you do not want sensitive
# information to show up in webserver log files.
use constant API_AUTH_HEADERS => {
    X_BUGZILLA_LOGIN    => 'Bugzilla_login',
    X_BUGZILLA_PASSWORD => 'Bugzilla_password',
    X_BUGZILLA_API_KEY  => 'Bugzilla_api_key',
    X_BUGZILLA_TOKEN    => 'Bugzilla_token',
};

1;

=head1 B<Methods in need of POD>

=over

=item REST_STATUS_CODE_MAP

=item WS_DISPATCH

=back