File Coverage

blib/lib/App/soapcli.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -c
2              
3             package App::soapcli;
4              
5             =head1 NAME
6              
7             App::soapcli - SOAP client for CLI with YAML and JSON input
8              
9             =head1 SYNOPSIS
10              
11             my $app = App::soapcli->new(argv => [qw( calculator.yml calculator.url )]);
12             $app->run;
13              
14             =head1 DESCRIPTION
15              
16             This is core module for soapcli(1) utility.
17              
18             =cut
19              
20              
21 4     4   167554 use 5.006;
  4         17  
  4         164  
22              
23 4     4   22 use strict;
  4         10  
  4         170  
24 4     4   21 use warnings;
  4         17  
  4         224  
25              
26             our $VERSION = '0.0201';
27              
28 4     4   4126 use Log::Report 'soapcli', syntax => 'SHORT';
  4         2994627  
  4         48  
29              
30 4     4   4551 use XML::LibXML;
  0            
  0            
31             use XML::Compile::WSDL11;
32             use XML::Compile::SOAP11;
33             use XML::Compile::Transport::SOAPHTTP;
34              
35             use constant::boolean;
36             use File::Slurp qw(read_file);
37             use Getopt::Long::Descriptive ();
38             use HTTP::Tiny ();
39             use YAML::Syck ();
40             use YAML::XS ();
41             use JSON::PP ();
42              
43              
44             =head1 ATTRIBUTES
45              
46             =over
47              
48             =item argv : ArrayRef
49              
50             Arguments list with options for the application.
51              
52             =back
53              
54             =head1 METHODS
55              
56             =over
57              
58             =item new (I<%args>)
59              
60             The default constructor.
61              
62             =cut
63              
64             sub new {
65             my ($class, %args) = @_;
66             return bless {
67             argv => [],
68             extra_argv => [],
69             %args,
70             } => $class;
71             };
72              
73              
74             =item new_with_options (%args)
75              
76             The constructor which initializes the object based on C<@ARGV> variable or
77             based on array reference if I option is set.
78              
79             =cut
80              
81             sub new_with_options {
82             my ($class, %args) = @_;
83              
84             my $argv = delete $args{argv};
85             local @ARGV = $argv ? @$argv : @ARGV;
86              
87             my ($opts, $usage) = Getopt::Long::Descriptive::describe_options(
88             "$0 %o data.yml [http://schema | schema.url]",
89             [ 'verbose|v', 'verbose mode with messages trace', ],
90             [ 'dump-xml-request|x', 'dump request as XML document', ],
91             [ 'help|h', 'print usage message and exit', ],
92             );
93              
94             die $usage->text if $opts->help or @ARGV < 1;
95              
96             return $class->new(extra_argv => [@ARGV], %$opts);
97             };
98              
99              
100             =item run ()
101              
102             Run the main job
103              
104             =back
105              
106             =cut
107              
108             sub run {
109             my ($self) = @_;
110              
111             my $arg_request = $self->{extra_argv}->[0];
112             my $servicename = do {
113             if ($arg_request =~ /^{/ or $arg_request eq '-') {
114             '';
115             }
116             else {
117             my $arg = $arg_request;
118             $arg =~ s/\.(url|yml|wsdl)$//;
119             $arg;
120             };
121             };
122              
123              
124             my $arg_wsdl = $self->{extra_argv}->[1];
125              
126             my $wsdlsrc = do {
127             if (defined $self->{extra_argv}->[1]) {
128             $self->{extra_argv}->[1];
129             }
130             else {
131             my $name = $servicename;
132             LOOP: {
133             do {
134             if (-f "$name.wsdl") {
135             $name .= '.wsdl';
136             last;
137             }
138             elsif (-f "$name.url") {
139             $name .= '.url';
140             last;
141             };
142             $name =~ s/[._-][^._-]*$//;
143             }
144             while ($name =~ /[._-]/);
145             $name .= '.wsdl';
146             };
147             $name;
148             };
149             };
150              
151             my $wsdldata = do {
152             if ($wsdlsrc =~ /\.url$/ or $wsdlsrc =~ m{://}) {
153             my $url = $wsdlsrc =~ m{://} ? $wsdlsrc : read_file($wsdlsrc, chomp=>TRUE);
154             chomp $url;
155             HTTP::Tiny->new->get($url)->{content};
156             }
157             elsif ($wsdlsrc =~ /\.wsdl$/ and -f $wsdlsrc) {
158             read_file($wsdlsrc);
159             };
160             } or die "Can not read WSDL data from `$wsdlsrc': $!\n";
161              
162              
163             my $arg_endpoint = $self->{extra_argv}->[2];
164              
165              
166             my $request = do {
167             if ($arg_request =~ /^{/) {
168             JSON::PP->new->utf8->relaxed->allow_barekey->decode($arg_request);
169             }
170             elsif ($arg_request eq '-') {
171             YAML::Syck::LoadFile(\*STDIN);
172             }
173             elsif ($arg_request =~ /\.json$/) {
174             JSON::PP->new->utf8->relaxed->allow_barekey->decode(read_file($arg_request));
175             }
176             else {
177             YAML::Syck::LoadFile($arg_request);
178             }
179             };
180              
181             die "Wrong request format from `$arg_request'\n" unless ref $request||'' eq 'HASH';
182              
183              
184             my $arg_operation = $self->{extra_argv}->[3];
185              
186             my $wsdldom = XML::LibXML->load_xml(string => $wsdldata);
187             my $imports = eval { $wsdldom->find('/wsdl:definitions/wsdl:types/xsd:schema/xsd:import') };
188              
189             my @schemas = eval { map { $_->getAttribute('schemaLocation') } $imports->get_nodelist };
190              
191             my $wsdl = XML::Compile::WSDL11->new;
192              
193             $wsdl->importDefinitions(\@schemas);
194             $wsdl->addWSDL($wsdldom);
195              
196             $wsdl->addHook(type => '{http://www.w3.org/2001/XMLSchema}hexBinary', before => sub {
197             my ($doc, $value, $path) = @_;
198             defined $value or return;
199             $value =~ m/^[0-9a-fA-F]+$/ or error __x"{path} contains illegal characters", path => $path;
200             return pack 'H*', $value;
201             });
202              
203             my $port = do {
204             if (defined $arg_endpoint and $arg_endpoint =~ /#(.*)$/) {
205             $1;
206             }
207             else {
208             undef;
209             }
210             };
211              
212             my $endpoint = do {
213             if (defined $arg_endpoint and $arg_endpoint !~ /^#/) {
214             my $url = $arg_endpoint =~ m{://} ? $arg_endpoint : read_file($arg_endpoint, chomp=>TRUE);
215             chomp $url;
216             $url =~ s/^(.*)#(.*)$/$1/;
217             $url;
218             }
219             else {
220             $wsdl->endPoint(
221             defined $port ? ( port => $port ) : (),
222             );
223             }
224             };
225              
226              
227             my $operation = do {
228             if (defined $arg_operation) {
229             $arg_operation
230             }
231             else {
232             my $o = (keys %$request)[0];
233             $request = $request->{$o};
234             $o;
235             }
236             };
237              
238              
239             my $http = XML::Compile::Transport::SOAPHTTP->new(
240             address => $endpoint,
241             );
242              
243             $http->userAgent->agent("soapcli/$VERSION");
244             $http->userAgent->env_proxy;
245              
246             my $action = eval { $wsdl->operation($operation)->soapAction() };
247              
248             my $transport = $http->compileClient(
249             action => $action,
250             );
251              
252              
253             $wsdl->compileCalls(
254             sloppy_floats => TRUE,
255             sloppy_integers => TRUE,
256             transport => $transport,
257             defined $port ? ( port => $port ) : (),
258             $self->{dump_xml_request} ? ( transport => sub { print $_[0]->toString(1); goto EXIT } ) : (),
259             );
260              
261             my ($response, $trace) = $wsdl->call($operation, $request);
262              
263             if ($self->{verbose}) {
264             print "---\n";
265             $trace->printRequest;
266             print YAML::XS::Dump({ Data => { $operation => $request } }), "\n";
267              
268             print "---\n";
269             $trace->printResponse;
270             print YAML::XS::Dump({ Data => $response }), "\n";
271             }
272             else {
273             print YAML::XS::Dump($response);
274             }
275              
276             EXIT:
277             return TRUE;
278             };
279              
280              
281             1;
282              
283              
284             =head1 SEE ALSO
285              
286             L, soapcli(1).
287              
288             =head1 AUTHOR
289              
290             Piotr Roszatycki
291              
292             =head1 LICENSE
293              
294             Copyright (c) 2011-2013 Piotr Roszatycki .
295              
296             This is free software; you can redistribute it and/or modify it under
297             the same terms as perl itself.
298              
299             See L