File Coverage

blib/lib/OData/QueryParams/DBIC.pm
Criterion Covered Total %
statement 128 128 100.0
branch 36 36 100.0
condition 17 20 85.0
subroutine 19 19 100.0
pod 1 1 100.0
total 201 204 98.5


line stmt bran cond sub pod time code
1             package OData::QueryParams::DBIC;
2              
3             # ABSTRACT: parse OData style query params and provide info for DBIC queries.
4              
5 13     13   913773 use v5.20;
  13         248  
6              
7 13     13   75 use strict;
  13         26  
  13         331  
8 13     13   98 use warnings;
  13         49  
  13         554  
9              
10 13     13   86 use feature 'signatures';
  13         26  
  13         2253  
11 13     13   98 no warnings 'experimental::signatures';
  13         27  
  13         489  
12              
13 13     13   6154 use parent 'Exporter';
  13         4042  
  13         68  
14              
15 13     13   840 use Carp qw(croak);
  13         31  
  13         645  
16 13     13   6185 use Mojo::Parameters;
  13         2549619  
  13         97  
17 13     13   8147 use OData::QueryParams::DBIC::FilterUtils qw(parser);
  13         40  
  13         933  
18 13     13   108 use List::Util qw(any);
  13         26  
  13         899  
19 13     13   120 use Scalar::Util qw(blessed);
  13         33  
  13         16450  
20              
21             our @EXPORT = qw(params_to_dbic);
22              
23             our $VERSION = '0.07';
24              
25 204     204 1 170923 sub params_to_dbic ( $query_string, %opts ) {
  204         403  
  204         398  
  204         298  
26 204         303 my $query;
27              
28 204 100       661 if ( blessed $query_string ) {
29 25 100       89 if ( $query_string->isa('Mojo::Parameters') ) {
30 24         44 $query = $query_string
31             }
32             else {
33 1         19 croak 'Invalid object';
34             }
35             }
36             else {
37 179         671 $query = Mojo::Parameters->new( $query_string );
38             }
39              
40 203         3950 my $params = $query->to_hash;
41              
42 203 100       24565 my $filter_key = $opts{strict} ? '$filter' : 'filter';
43 203         769 my %filter = _parse_filter( delete $params->{$filter_key}, %opts );
44              
45 101         182 my %dbic_opts;
46              
47             PARAM_KEY:
48 101         232 for my $param_key ( keys %{ $params } ) {
  101         350  
49 70         206 my $method = $param_key =~ s{\A\$}{}r;
50              
51 70 100 100     214 next PARAM_KEY if $opts{strict} && $param_key !~ m{\A\$}xms;
52              
53 69         441 my $sub = __PACKAGE__->can( '_parse_' . $method );
54 69 100       192 if ( $sub ) {
55 67         170 my %key_opts = $sub->( $params->{$param_key} );
56 67         268 %dbic_opts = (%dbic_opts, %key_opts);
57             }
58             }
59              
60 101         568 return \%filter, \%dbic_opts;
61             }
62              
63 11     11   19 sub _parse_top ( $top_data ) {
  11         17  
  11         15  
64 11 100       50 return if $top_data !~ m{\A[0-9]+\z};
65 10         31 return ( rows => $top_data );
66             }
67              
68 17     17   29 sub _parse_skip ( $skip_data ) {
  17         31  
  17         25  
69 17 100       81 return if $skip_data !~ m{\A[0-9]+\z};
70 14         55 return ( page => $skip_data + 1 );
71             }
72              
73 203     203   351 sub _parse_filter ( $filter_data, %opt ) {
  203         339  
  203         339  
  203         280  
74 203 100       602 return if !defined $filter_data;
75 155 100       374 return if $filter_data eq '';
76              
77 152         468 my $obj = parser->( $filter_data );
78 152         857 my %filter = _flatten_filter( $obj, %opt );
79              
80 50         221 return %filter;
81             }
82              
83 17     17   26 sub _parse_orderby ( $orderby_data ) {
  17         26  
  17         23  
84 17         97 my @order_bys = split /\s*,\s*/, $orderby_data;
85              
86 17         34 my @dbic_order_by;
87              
88 17         33 for my $order_by ( @order_bys ) {
89 31         45 my $direction;
90 31 100 100     167 $order_by =~ s{\s+(.*?)\z}{$1 && (lc $1 eq 'desc' || lc $1 eq 'asc') && ( $direction = lc $1 ); ''}e;
  20   100     139  
  20         54  
91              
92 31   100     98 $direction //= 'asc';
93              
94 31         111 push @dbic_order_by, { -$direction => $order_by };
95             }
96              
97 17         58 return order_by => \@dbic_order_by;
98             }
99              
100 22     22   39 sub _parse_select ( $select_data ) {
  22         79  
  22         34  
101 22 100       73 return if !length $select_data;
102 18         128 return columns => [ split /\s*,\s*/, $select_data ];
103             }
104              
105 176     176   299 sub _flatten_filter ($obj, %opt) {
  176         269  
  176         312  
  176         244  
106 176         1061 my %map = (
107             'lt' => '<',
108             'le' => '<=',
109             'gt' => '>',
110             'ge' => '>=',
111             'eq' => '==',
112             'ne' => '!=',
113             'and' => \&_build_bool,
114             'or' => \&_build_bool,
115             );
116              
117 176         369 my $op = $obj->{operator};
118              
119 176 100       702 croak 'Unknown op' if !defined $op;
120              
121 166         254 my %filter;
122              
123 166 100       463 if ( !exists $map{$op} ) {
124 2         366 croak 'Unsupported op: ' . $op;
125             }
126             else {
127 164         324 my $rule = $map{$op};
128 164         277 my $subject = $obj->{subject};
129 164         277 my $value = $obj->{value};
130              
131 164 100       384 if ( !defined $subject ) {
    100          
132 63         616 croak 'Unsupported expression';
133             }
134             elsif ( ref $rule ) {
135 12         45 my ($filter_key, $filter_value) = $rule->($obj, %opt);
136 12         43 $filter{$filter_key} = $filter_value;
137             }
138             else {
139 89 100       207 if ( ref $subject ) {
140 27         306 croak 'Complex expressions on the left side are not supported (yet)';
141             }
142              
143 62 100       202 if ( $value =~ m{\A'(.*)'\z} ) {
144 13         39 $value = $1;
145             }
146              
147 62   33     261 my $is_field = $obj->{sub_type} && $obj->{sub_type} eq 'field';
148 62         153 my $is_foreign_field = $subject =~ m{\A\w+\/};
149              
150 62 100 66     188 if ( $opt{me} && $is_field && !$is_foreign_field ) {
      100        
151 11         28 $subject = 'me.' . $subject;
152             }
153              
154 62         155 $subject =~ s{\A\w+\K/}{.};
155              
156 62         257 $filter{ $subject } = {
157             $rule => $value,
158             };
159             }
160             }
161              
162 74         410 return %filter;
163             }
164              
165 12     12   22 sub _build_bool ($obj, %opt) {
  12         20  
  12         22  
  12         16  
166 12         28 my $op = $obj->{operator};
167 12         25 my $subject = $obj->{subject};
168 12         20 my $value = $obj->{value};
169              
170 12         48 return "-$op" => [
171             { _flatten_filter( $subject, %opt ) },
172             { _flatten_filter( $value, %opt ) },
173             ];
174             }
175              
176             1;
177              
178             __END__