File Coverage

blib/lib/Protocol/FIX/Field.pm
Criterion Covered Total %
statement 33 33 100.0
branch 13 14 92.8
condition 5 6 83.3
subroutine 8 8 100.0
pod 5 5 100.0
total 64 66 96.9


line stmt bran cond sub pod time code
1             package Protocol::FIX::Field;
2              
3 11     11   149613 use strict;
  11         31  
  11         343  
4 11     11   57 use warnings;
  11         31  
  11         282  
5              
6 11     11   939 use Protocol::FIX;
  11         30  
  11         15321  
7              
8             our $VERSION = '0.08'; ## VERSION
9              
10             =head1 NAME
11              
12             Protocol::FIX::Field - FIX Message Field
13              
14             =head1 Description
15              
16             The following field types are known to the class. Validators are provided.
17              
18             AMT
19             BOOLEAN
20             CHAR
21             COUNTRY
22             CURRENCY
23             DATA
24             EXCHANGE
25             FLOAT
26             INT
27             LENGTH
28             LOCALMKTDATE
29             MONTHYEAR
30             MULTIPLEVALUESTRING
31             NUMINGROUP
32             PERCENTAGE
33             PRICE
34             PRICEOFFSET
35             QTY
36             SEQNUM
37             STRING
38             UTCDATEONLY
39             UTCTIMEONLY
40             UTCTIMESTAMP
41              
42             =cut
43              
44             # anyting defined and not containing delimiter
45             my $BOOLEAN_validator = sub { defined($_[0]) && $_[0] =~ /^[YN]$/ };
46             my $STRING_validator = sub { defined($_[0]) && $_[0] !~ /$Protocol::FIX::TAG_SEPARATOR/ };
47             my $INT_validator = sub { defined($_[0]) && $_[0] =~ /^-?\d+$/ };
48             my $LENGTH_validator = sub { defined($_[0]) && $_[0] =~ /^\d+$/ && $_[0] > 0 };
49             my $DATA_validator = sub { defined($_[0]) && length($_[0]) > 0 };
50             my $FLOAT_validator = sub { defined($_[0]) && $_[0] =~ /^-?\d+(\.?\d*)$/ };
51             my $CHAR_validator = sub { defined($_[0]) && $_[0] =~ /^[^$Protocol::FIX::TAG_SEPARATOR]$/ };
52             my $CURRENCY_validator = sub { defined($_[0]) && $_[0] =~ /^[^$Protocol::FIX::TAG_SEPARATOR]{3}$/ };
53             my $COUNTRY_validator = sub { defined($_[0]) && $_[0] =~ /^[A-Z]{2}$/ };
54              
55             my $MONTHYEAR_validator = sub {
56             my $d = shift;
57             # YYYYMM
58             # YYYYMMDD
59             # YYYYMMWW
60             my $ym_valid =
61             defined($d)
62             && $d =~ /^(\d{4})(\d{2})([w\d]\d)?$/
63             && ($2 >= 1)
64             && ($2 <= 12);
65              
66             return unless $ym_valid;
67              
68             my $r = $3;
69             return 1 unless $r;
70              
71             return ($r =~ /^w[1-6]$/)
72             || (($r =~ /^\d{2}$/) && ($r >= 1) && ($r <= 31));
73             };
74              
75             my $LOCALMKTDATE_validator = sub {
76             my $d = shift;
77             # YYYYMMDD
78             return
79             defined($d)
80             && $d =~ /^(\d{4})(\d{2})(\d{2})$/
81             && ($2 >= 1)
82             && ($2 <= 12)
83             && ($3 >= 1)
84             && ($3 <= 31);
85             };
86              
87             my $UTCTIMESTAMP_validator = sub {
88             my $t = shift;
89             # YYYYMMDD-HH:MM:SS
90             # YYYYMMDD-HH:MM:SS.sss
91             if (defined($t) && $t =~ /^(\d{4})(\d{2})(\d{2})-(\d{2}):(\d{2}):(\d{2})(\.\d{3})?$/) {
92             return
93             ($2 >= 1)
94             && ($2 <= 12)
95             && ($3 >= 1)
96             && ($3 <= 31)
97             && ($4 >= 0)
98             && ($4 <= 23)
99             && ($5 >= 0)
100             && ($5 <= 59)
101             && ($6 >= 0)
102             && ($5 <= 60);
103              
104             } else {
105             return;
106             }
107             };
108              
109             my $UTCTIMEONLY_validator = sub {
110             # HH:MM:SS
111             # HH:MM:SS.sss
112             my $t = shift;
113             if (defined($t) && $t =~ /^(\d{2}):(\d{2}):(\d{2})(\.\d{3})?$/) {
114             return
115             ($1 >= 0)
116             && ($1 <= 23)
117             && ($2 >= 0)
118             && ($2 <= 59)
119             && ($3 >= 0)
120             && ($3 <= 60);
121             } else {
122             return;
123             }
124             };
125              
126             my %per_type = (
127             BOOLEAN => $BOOLEAN_validator,
128             CHAR => $CHAR_validator,
129             STRING => $STRING_validator,
130             MULTIPLEVALUESTRING => $STRING_validator,
131             EXCHANGE => $STRING_validator,
132             INT => $INT_validator,
133             SEQNUM => $INT_validator,
134             LENGTH => $LENGTH_validator,
135             NUMINGROUP => $LENGTH_validator,
136             DATA => $DATA_validator,
137             FLOAT => $FLOAT_validator,
138             AMT => $FLOAT_validator,
139             PERCENTAGE => $FLOAT_validator,
140             PRICE => $FLOAT_validator,
141             QTY => $FLOAT_validator,
142             PRICEOFFSET => $FLOAT_validator,
143             CURRENCY => $CURRENCY_validator,
144             UTCTIMESTAMP => $UTCTIMESTAMP_validator,
145             LOCALMKTDATE => $LOCALMKTDATE_validator,
146             UTCDATEONLY => $LOCALMKTDATE_validator,
147             MONTHYEAR => $MONTHYEAR_validator,
148             UTCTIMEONLY => $UTCTIMEONLY_validator,
149             COUNTRY => $COUNTRY_validator,
150             );
151              
152             =head1 METHODS (for protocol developers)
153              
154             =head3 new
155              
156             new($class, $number, $name, $type, $values)
157              
158             Creates new Field (performed by Protocol, when it parses XML definition)
159              
160             =cut
161              
162             sub new {
163 6430     6430 1 77601 my ($class, $number, $name, $type, $values) = @_;
164              
165             die "Unsupported field type '$type'"
166 6430 50       13242 unless exists $per_type{$type};
167              
168 6430         19518 my $obj = {
169             number => $number,
170             name => $name,
171             type => $type,
172             };
173              
174 6430 100       11308 if ($values) {
175 1723         2463 my $reverse_values = {};
176 1723         6634 @{$reverse_values}{values %$values} = keys %$values;
  1723         11846  
177             $obj->{values} = {
178 1723         4831 by_id => $values,
179             by_name => $reverse_values,
180             };
181             }
182              
183 6430         13341 return bless $obj, $class;
184             }
185              
186             =head3 check
187              
188             check($self, $value)
189              
190             Returns C or C if the supplied value conforms type.
191              
192             If type has enumeration (i.e. "B" for "BID" and "O" for "OFFER"),
193             then it expects that human-readable value ("BID" / "OFFER") will be
194             provided as C<$value>. The values "B" or "O" will not bypass
195             the check.
196              
197             This method is used during message serialization L.
198              
199             =cut
200              
201             sub check {
202 950     950 1 2347 my ($self, $value) = @_;
203              
204             my $result =
205             $self->{values}
206             ? (defined($value) && exists $self->{values}->{by_name}->{$value})
207 950 100 100     3860 : $per_type{$self->{type}}->($value);
208              
209 950         2642 return $result;
210             }
211              
212             =head3 has_mapping
213              
214             has_mapping($self)
215              
216             returns true if field has enumeration
217              
218             =cut
219              
220             sub has_mapping {
221 123     123 1 901 my $self = shift;
222 123         347 return exists $self->{values};
223             }
224              
225             =head3 check_raw
226              
227             check_raw($self, $value)
228              
229             Returns C or C if the supplied value conforms type.
230              
231             If type has enumeration (i.e. "B" for "BID" and "O" for "OFFER"),
232             then it expects that enum value ("B" / "O") will be
233             provided as C<$value>. The values "BID" or "OFFER" will not bypass
234             the check.
235              
236             This method is used during message deserialization L.
237              
238             =cut
239              
240             sub check_raw {
241 283     283 1 561 my ($self, $value) = @_;
242              
243             my $result =
244             $self->{values}
245             ? (defined($value) && exists $self->{values}->{by_id}->{$value})
246 283 100 66     1064 : $per_type{$self->{type}}->($value);
247              
248 283         887 return $result;
249             }
250              
251             =head3 serialize
252              
253             serialize($self, $values)
254              
255             Serializes field value. If the value does not bypasses the type check,
256             an exception will be thrown.
257              
258             =cut
259              
260             sub serialize {
261 854     854 1 1716 my ($self, $value) = @_;
262              
263             my $packed_value = $self->{values}
264 854 100       1732 ? do {
265 695         2203 my $id = $self->{values}->{by_name}->{$value};
266             die("The value '$value' is not acceptable for field " . $self->{name})
267 695 100       1361 unless defined $id;
268 694         1422 $id;
269             }
270             : $value;
271             die("The value '$value' is not acceptable for field " . $self->{name})
272 853 100       1817 unless $self->check($value);
273              
274 849         3174 return $self->{number} . '=' . $packed_value;
275             }
276              
277             1;