File Coverage

blib/lib/Object/Pad/FieldAttr/Checked.pm
Criterion Covered Total %
statement 9 10 90.0
branch n/a
condition n/a
subroutine 4 5 80.0
pod n/a
total 13 15 86.6


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2023 -- leonerd@leonerd.org.uk
5              
6             package Object::Pad::FieldAttr::Checked 0.06;
7              
8 4     4   841448 use v5.14;
  4         40  
9 4     4   24 use warnings;
  4         10  
  4         125  
10              
11 4     4   660 use Object::Pad 0.802; # requires pre-filled ctx->bodyop when invoking gen_accessor_ops
  4         11618  
  4         201  
12              
13             require XSLoader;
14             XSLoader::load( __PACKAGE__, our $VERSION );
15              
16             =head1 NAME
17              
18             C - apply value constraint checks to C fields
19              
20             =head1 SYNOPSIS
21              
22             With L:
23              
24             use Object::Pad;
25             use Object::Pad::FieldAttr::Checked;
26             use Types::Standard qw( Num );
27              
28             class Point {
29             field $x :param :reader :Checked(Num);
30             field $y :param :reader :Checked(Num);
31             }
32              
33             Point->new( x => 123, y => "456" ); # this is fine
34              
35             Point->new( x => "hello", y => "world" ); # throws an exception
36              
37             Or, standalone:
38              
39             use Object::Pad;
40             use Object::Pad::FieldAttr::Checked;
41              
42             package Numerical {
43             use Scalar::Util qw( looks_like_number );
44             sub check { looks_like_number $_[1]; }
45             }
46              
47             class Point {
48             field $x :param :reader :Checked('Numerical');
49             field $y :param :reader :Checked('Numerical');
50             }
51              
52             ...
53              
54             =head1 DESCRIPTION
55              
56             This module provides a third-party field attribute for L-based
57             classes, which declares that values assigned to the field must conform to a
58             given constraint check.
59              
60             B The ability for L to take third-party field attributes
61             is still new and highly experimental, and subject to much API change in
62             future. As a result, this module should be considered equally experimental.
63              
64             Additionally, the behaviour provided by this module should be considered more
65             of a work-in-progress stepping stone. Ideally, constraint syntax ought
66             to be provided in a much more fundamental way by Perl itself, allowing it to
67             be used on C lexicals, subroutine parameters, and other places as well as
68             object fields. This module is more of a placeholder to allow some part of that
69             behaviour to be specified for object fields, while not getting in the way of a
70             more general, more powerful system being added in future.
71              
72             =head1 FIELD ATTRIBUTES
73              
74             =head2 :Checked
75              
76             field $name :Checked(EXPRESSION) ...;
77              
78             Declares that any value assigned to the field during the constructor or using
79             an accessor method must conform to the constraint checker specified by the
80             expression. Attempts to assign a non-conforming value will throw an exception
81             and the field will not be modified. Currently only scalar fields are
82             supported.
83              
84             At compiletime, the string given by I is C'ed in scalar
85             context, and its result is stored as part of the field's definition. The
86             expression must yield either an object reference, a code reference, or a
87             string containing the name of a package. In the case of an object or package,
88             a method called C must exist on it.
89              
90             During the string C of the expression, the C pragma is
91             specifically disabled, allowing a bareword to be used as the name of a
92             package. This feature may be removed in future, so be sure to quote package
93             names if required.
94              
95             field $x :Checked('CheckerPackage');
96              
97             At runtime, this constraint checker is used every time an attempt is made to
98             assign a value to the field I, whether that is
99             from C<:param> initialisation, or invoking a C<:writer> or C<:accessor>. The
100             checker is used as the invocant for invoking a C method, and the new
101             value for the field is passed as an argument. If the method returns true, the
102             assignment is allowed. If false, it is rejected with an exception and the
103             field itself remains unmodified.
104              
105             $ok = $checkerobj->check( $value ); # if an object
106              
107             $ok = $checkersub->( $value ); # if a code reference
108              
109             $ok = $checkerpkg->check( $value ); # if a package name
110              
111             (For performance reasons, the C method is actually resolved into a
112             function at compiletime when the C<:Checked> attribute is applied, and this
113             stored function is the one that is called at assignment time. If the method
114             itself is replaced later by globref assignment or other trickery, this updated
115             function will not be used.)
116              
117             As this is the interface supported by L, any constraint
118             object provided by that module is already supported here.
119              
120             use Types::Standard qw( Str Num );
121              
122             field $name :Checked(Str);
123             field $age :Checked(Num);
124              
125             B that direct assignment into the field variable by code
126             within the class is not checked. This is partly because of design
127             considerations, and partly because any way to implement that would be horribly
128             slow, or flat-out impossible. I this module used to
129             claim that even direct assignments would be checked. but this gave a false
130             sense of safety if deeply-nested containers were involved and modified from
131             within.
132              
133             =cut
134              
135             sub import
136             {
137 3     3   2409 $^H{"Object::Pad::FieldAttr::Checked/Checked"}++;
138             }
139              
140             sub unimport
141             {
142 0     0     delete $^H{"Object::Pad::FieldAttr::Checked/Checked"};
143             }
144              
145             =head1 TODO
146              
147             =over 4
148              
149             =item *
150              
151             Support C<:mutator> accessors as well. This is a bit harder because lvalue
152             magic.
153              
154             =back
155              
156             =head1 SEE ALSO
157              
158             =over 4
159              
160             =item *
161              
162             L - apply class type constraints to C fields
163              
164             =back
165              
166             =head1 AUTHOR
167              
168             Paul Evans
169              
170             =cut
171              
172             0x55AA;