<commit_before>package WWW::SFDC;
# ABSTRACT: Wrappers around the Salesforce.com APIs.

use strict;
use warnings;
use 5.12.0;

# VERSION

use Data::Dumper;
use Log::Log4perl ':easy';

use Moo;

has 'apiVersion',
  is => 'ro',
  isa => sub { LOGDIE "The API version ($_[0]) must be >= 31." unless $_[0] and $_[0] >= 31},
  default => '33.0';

has 'loginResult',
  is => 'rw',
  lazy => 1,
  builder => '_login';

has 'password',
  is => 'ro',
  required => 1;

has 'pollInterval',
  is => 'rw',
  default => 15;

has 'attempts',
  is => 'rw',
  default => 3;

has 'url',
  is => 'ro',
  default => "https://test.salesforce.com",
  isa => sub { $_[0] and $_[0] =~ s/\/$// or 1; }; #remove trailing slash

has 'username',
  is => 'ro',
  required => 1;

INIT: {
  for my $module (qw'
    Apex Constants Metadata Partner Tooling
  '){
    has $module,
      is => 'ro',
      lazy => 1,
      default => sub {
        my $self = shift;
        require "WWW/SFDC/$module.pm"; ## no critic
        "WWW::SFDC::$module"->new(session => $self);
      };
  }
}

sub _login {
  my $self = shift;

  INFO "Logging in...\t";

  my $request = SOAP::Lite
    ->proxy(
      $self->url()."/services/Soap/u/".$self->apiVersion()
    )
    ->readable(1)
    ->ns("urn:partner.soap.sforce.com","urn")
    ->call(
      'login',
      SOAP::Data->name("username")->value($self->username),
      SOAP::Data->name("password")->value($self->password)
    );

  TRACE "Request: " . Dumper $request;
  WWW::SFDC::CallException->throw(
    message => "Login failed: " . $request->faultstring,
    request => $request
  ) if $request->fault;
  return $request->result();
}

=method call

=cut

sub _doCall {
  my $self = shift;
  my ($attempts, $URL, $NS, @stuff) = @_;

  INFO "Starting $stuff[0] request";
  if (
    my $result = eval {
      SOAP::Lite
        ->proxy($URL)
        ->readable(1)
        ->default_ns($NS)
        ->call(
          @stuff,
          SOAP::Header->name("SessionHeader" => {
            "sessionId" => $self->loginResult()->{"sessionId"}
          })->uri($NS)
        )
    }
  ) {
    return $result;

  } elsif ($attempts--) {
    INFO "$stuff[0] failed: $@";
    INFO "Retrying ($attempts attempts remaining)";
    return $self->_doCall($attempts, $URL, $NS, @stuff);

  } else {
    WWW::SFDC::CallException->throw(
      message => "$stuff[0] failed: " . $@
    );
  }
}

sub call {
  my $self = shift;
  my $req;

  while (
    $req = $self->_doCall($self->attempts, @_)
    and $req->fault
  ) {
    TRACE "Operation request " => Dumper $req;
    if ($req->faultstring =~ /INVALID_SESSION_ID/) {
      $self->loginResult($self->_login());
    } else {
      WWW::SFDC::CallException->throw(
        message => "$_[2] failed: " . $req->faultstring,
        request => $req
      );
    }
  }

  return $req;
};

=method isSandbox

Returns 1 if the org associated with the given credentials are a sandbox. Use to
decide whether to sanitise metadata or similar.

=cut

sub isSandbox {
  my $self = shift;
  return $self->loginResult->{sandbox} eq  "true";
}

1;

package WWW::SFDC::CallException;
use strict;
use warnings;
use Moo;
with 'WWW::SFDC::Role::Exception';

has 'request',
  is => 'ro';

1;

__END__

=head1 SYNOPSIS

WWW::SFDC provides a set of packages which you can use to build useful
interactions with Salesforce.com's many APIs. Initially it was intended
for the construction of powerful and flexible deployment tools.

    use WWW::SFDC;

    my $session = WWW::SFDC->new(
      username => $username,
      password => <PASSWORD>,
      url => url,
      apiVersion => apiversion
    );

    # This will do queryMore until it's got everything<commit_msg>Add warning about instability<commit_after>package WWW::SFDC;
# ABSTRACT: Wrappers around the Salesforce.com APIs.

use strict;
use warnings;
use 5.12.0;

# VERSION

use Data::Dumper;
use Log::Log4perl ':easy';

use Moo;

has 'apiVersion',
  is => 'ro',
  isa => sub { LOGDIE "The API version ($_[0]) must be >= 31." unless $_[0] and $_[0] >= 31},
  default => '33.0';

has 'loginResult',
  is => 'rw',
  lazy => 1,
  builder => '_login';

has 'password',
  is => 'ro',
  required => 1;

has 'pollInterval',
  is => 'rw',
  default => 15;

has 'attempts',
  is => 'rw',
  default => 3;

has 'url',
  is => 'ro',
  default => "https://test.salesforce.com",
  isa => sub { $_[0] and $_[0] =~ s/\/$// or 1; }; #remove trailing slash

has 'username',
  is => 'ro',
  required => 1;

INIT: {
  for my $module (qw'
    Apex Constants Metadata Partner Tooling
  '){
    has $module,
      is => 'ro',
      lazy => 1,
      default => sub {
        my $self = shift;
        require "WWW/SFDC/$module.pm"; ## no critic
        "WWW::SFDC::$module"->new(session => $self);
      };
  }
}

sub _login {
  my $self = shift;

  INFO "Logging in...\t";

  my $request = SOAP::Lite
    ->proxy(
      $self->url()."/services/Soap/u/".$self->apiVersion()
    )
    ->readable(1)
    ->ns("urn:partner.soap.sforce.com","urn")
    ->call(
      'login',
      SOAP::Data->name("username")->value($self->username),
      SOAP::Data->name("password")->value($self->password)
    );

  TRACE "Request: " . Dumper $request;
  WWW::SFDC::CallException->throw(
    message => "Login failed: " . $request->faultstring,
    request => $request
  ) if $request->fault;
  return $request->result();
}

=method call

=cut

sub _doCall {
  my $self = shift;
  my ($attempts, $URL, $NS, @stuff) = @_;

  INFO "Starting $stuff[0] request";
  if (
    my $result = eval {
      SOAP::Lite
        ->proxy($URL)
        ->readable(1)
        ->default_ns($NS)
        ->call(
          @stuff,
          SOAP::Header->name("SessionHeader" => {
            "sessionId" => $self->loginResult()->{"sessionId"}
          })->uri($NS)
        )
    }
  ) {
    return $result;

  } elsif ($attempts--) {
    INFO "$stuff[0] failed: $@";
    INFO "Retrying ($attempts attempts remaining)";
    return $self->_doCall($attempts, $URL, $NS, @stuff);

  } else {
    WWW::SFDC::CallException->throw(
      message => "$stuff[0] failed: " . $@
    );
  }
}

sub call {
  my $self = shift;
  my $req;

  while (
    $req = $self->_doCall($self->attempts, @_)
    and $req->fault
  ) {
    TRACE "Operation request " => Dumper $req;
    if ($req->faultstring =~ /INVALID_SESSION_ID/) {
      $self->loginResult($self->_login());
    } else {
      WWW::SFDC::CallException->throw(
        message => "$_[2] failed: " . $req->faultstring,
        request => $req
      );
    }
  }

  return $req;
};

=method isSandbox

Returns 1 if the org associated with the given credentials are a sandbox. Use to
decide whether to sanitise metadata or similar.

=cut

sub isSandbox {
  my $self = shift;
  return $self->loginResult->{sandbox} eq  "true";
}

1;

package WWW::SFDC::CallException;
use strict;
use warnings;
use Moo;
with 'WWW::SFDC::Role::Exception';

has 'request',
  is => 'ro';

1;

__END__

=head1 EXPERIMENTAL

This module is quite unstable, as it's early in its development cycle. I'm
trying to avoid breaking too much, but until it hits 1.0, there is a risk of
breakage.

=head1 SYNOPSIS

WWW::SFDC provides a set of packages which you can use to build useful
interactions with Salesforce.com's many APIs. Initially it was intended
for the construction of powerful and flexible deployment tools.

    use WWW::SFDC;

    my $session = WWW::SFDC->new(
      username => $username,
      password => <PASSWORD>,
      url => url,
      apiVersion => apiversion
    );

    # This will do queryMore until it's got everything
{'fraction_non_alphanumeric': 0.17445853143159007, 'fraction_numerical': 0.009640781827786582, 'mean_word_length': 2.7287050713934025, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 153, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6367893', 'n_tokens_mistral': 2770, 'n_tokens_neox': 2648, 'n_words': 718}