File Coverage

blib/lib/Avro/Schema.pm
Criterion Covered Total %
statement 356 398 89.4
branch 150 202 74.2
condition 78 120 65.0
subroutine 60 63 95.2
pod 0 4 0.0
total 644 787 81.8


line stmt bran cond sub pod time code
1             # Licensed to the Apache Software Foundation (ASF) under one
2             # or more contributor license agreements. See the NOTICE file
3             # distributed with this work for additional information
4             # regarding copyright ownership. The ASF licenses this file
5             # to you under the Apache License, Version 2.0 (the
6             # "License"); you may not use this file except in compliance
7             # with the License. You may obtain a copy of the License at
8             #
9             # https://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing,
12             # software distributed under the License is distributed on an
13             # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14             # KIND, either express or implied. See the License for the
15             # specific language governing permissions and limitations
16             # under the License.
17              
18             package Avro::Schema;
19 6     6   3634 use strict;
  6         25  
  6         189  
20 6     6   38 use warnings;
  6         10  
  6         141  
21              
22 6     6   27 use Carp;
  6         12  
  6         392  
23 6     6   3475 use JSON::XS();
  6         26139  
  6         138  
24 6     6   2596 use Try::Tiny;
  6         10545  
  6         5449  
25              
26             our $VERSION = '1.11.2';
27              
28             my $json = JSON::XS->new->allow_nonref;
29              
30             sub parse {
31 70     70 0 24641 my $schema = shift;
32 70         130 my $json_string = shift;
33 70   50     283 my $names = shift || {};
34 70   50     211 my $namespace = shift || "";
35              
36             my $struct = try {
37 70     70   5064 $json->decode($json_string);
38             }
39             catch {
40 4     4   69 throw Avro::Schema::Error::Parse(
41             "Cannot parse json string: $_"
42             );
43 70         420 };
44 66         1052 return $schema->parse_struct($struct, $names, $namespace);
45             }
46              
47             sub to_string {
48 10     10 0 16 my $class = shift;
49 10         15 my $struct = shift;
50 10         125 return $json->encode($struct);
51             }
52              
53             sub parse_struct {
54 227     227 0 797 my $schema = shift;
55 227         319 my $struct = shift;
56 227   100     467 my $names = shift || {};
57 227   100     592 my $namespace = shift || "";
58              
59             ## 1.3.2 A JSON object
60 227 100       578 if (ref $struct eq 'HASH') {
    100          
61             my $type = $struct->{type}
62 87 50       221 or throw Avro::Schema::Error::Parse("type is missing");
63 87 100       247 if ( Avro::Schema::Primitive->is_type_valid($type) ) {
64 11         36 return Avro::Schema::Primitive->new(type => $type);
65             }
66             ## XXX technically we shouldn't allow error type other than in
67             ## a Protocol definition
68 76 100 100     370 if ($type eq 'record' or $type eq 'error') {
    100          
    100          
    100          
    50          
69 23         95 return Avro::Schema::Record->new(
70             struct => $struct,
71             names => $names,
72             namespace => $namespace,
73             );
74             }
75             elsif ($type eq 'enum') {
76 6         25 return Avro::Schema::Enum->new(
77             struct => $struct,
78             names => $names,
79             namespace => $namespace,
80             );
81             }
82             elsif ($type eq 'array') {
83 19         87 return Avro::Schema::Array->new(
84             struct => $struct,
85             names => $names,
86             namespace => $namespace,
87             );
88             }
89             elsif ($type eq 'map') {
90 17         76 return Avro::Schema::Map->new(
91             struct => $struct,
92             names => $names,
93             namespace => $namespace,
94             );
95             }
96             elsif ($type eq 'fixed') {
97 11         31 return Avro::Schema::Fixed->new(
98             struct => $struct,
99             names => $names,
100             namespace => $namespace,
101             );
102             }
103             else {
104 0         0 throw Avro::Schema::Error::Parse("unknown type: $type");
105             }
106             }
107             ## 1.3.2 A JSON array, representing a union of embedded types.
108             elsif (ref $struct eq 'ARRAY') {
109 16         62 return Avro::Schema::Union->new(
110             struct => $struct,
111             names => $names,
112             namespace => $namespace,
113             );
114             }
115             ## 1.3.2 A JSON string, naming a defined type.
116             else {
117 124         183 my $type = $struct;
118             ## It's one of our custom defined type
119            
120             ## Short name provided, prepend the namespace
121 124 50       295 if ( $type !~ /\./ ) {
122 124         240 my $fulltype = $namespace . '.' . $type;
123 124 100       279 if (exists $names->{$fulltype}) {
124 1         2 return $names->{$fulltype};
125             }
126             }
127            
128             ## Fully-qualified name
129 123 100       228 if (exists $names->{$type}) {
130 11         25 return $names->{$type};
131             }
132            
133             ## It's a primitive type
134 112         275 return Avro::Schema::Primitive->new(type => $type);
135             }
136             }
137              
138             sub match {
139 1908     1908 0 6750 my $class = shift;
140 1908         3771 my %param = @_;
141              
142             my $reader = $param{reader}
143 1908 50       3755 or croak "missing reader schema";
144             my $writer = $param{writer}
145 1908 50       3300 or croak "missing writer schema";
146              
147 1908 50       4462 my $wtype = ref $writer ? $writer->type : $writer;
148 1908 50       3547 my $rtype = ref $reader ? $reader->type : $reader;
149             ## 1.3.2 either schema is a union
150 1908 100 66     5584 return $wtype if $wtype eq 'union' or $rtype eq 'union';
151              
152             ## 1.3.2 both schemas have same primitive type
153 1905 100 100     4316 return $wtype if $wtype eq $rtype
154             && Avro::Schema::Primitive->is_type_valid($wtype);
155              
156             ## 1.3.2
157             ## int is promotable to long, float, or double
158 592 50 66     1086 if ($wtype eq 'int' && (
      66        
159             $rtype eq 'float' or $rtype eq 'long' or $rtype eq 'double'
160             )) {
161 3         10 return $rtype;
162             }
163             ## long is promotable to float or double
164 589 100 66     929 if ($wtype eq 'long' && (
      66        
165             $rtype eq 'float' or $rtype eq 'double'
166             )) {
167 1         5 return $rtype;
168             }
169             ## float is promotable to double
170 588 100 100     1034 if ($wtype eq 'float' && $rtype eq 'double') {
171 1         11 return $rtype;
172             }
173 587 100       1003 return 0 unless $rtype eq $wtype;
174              
175             ## 1.3.2 {subtype and/or names} match
176 575 100       1092 if ($rtype eq 'array') {
    100          
    100          
    100          
    50          
177 427 100       798 return $wtype if $class->match(
178             reader => $reader->items,
179             writer => $writer->items,
180             );
181             }
182             elsif ($rtype eq 'record') {
183 11 100       42 return $wtype if $reader->fullname eq $writer->fullname;
184             }
185             elsif ($rtype eq 'map') {
186 115 100       217 return $wtype if $class->match(
187             reader => $reader->values,
188             writer => $writer->values,
189             );
190             }
191             elsif ($rtype eq 'fixed') {
192 14 100 66     33 return $wtype if $reader->size eq $writer->size
193             && $reader->fullname eq $writer->fullname;
194             }
195             elsif ($rtype eq 'enum') {
196 8 100       19 return $wtype if $reader->fullname eq $writer->fullname;
197             }
198 7         31 return 0;
199             }
200              
201              
202             package Avro::Schema::Base;
203             our @ISA = qw/Avro::Schema/;
204 6     6   52 use Carp;
  6         11  
  6         1701  
205              
206             sub new {
207 226     226   334 my $class = shift;
208 226         431 my %param = @_;
209              
210 226         347 my $type = $param{type};
211 226 100       426 if (!$type) {
212 204         1362 my ($t) = $class =~ /::([^:]+)$/;
213 204         478 $type = lc ($t);
214             }
215 226         572 my $schema = bless {
216             type => $type,
217             }, $class;
218 226         524 return $schema;
219             }
220              
221             sub type {
222 5252     5252   7744 my $schema = shift;
223 5252         9287 return $schema->{type};
224             }
225              
226             sub to_string {
227 10     10   25 my $schema = shift;
228 10   50     46 my $known_names = shift || {};
229 10         42 return Avro::Schema->to_string($schema->to_struct($known_names));
230             }
231              
232             package Avro::Schema::Primitive;
233             our @ISA = qw/Avro::Schema::Base/;
234 6     6   46 use Carp;
  6         17  
  6         370  
235 6     6   38 use Config;
  6         12  
  6         357  
236 6     6   3395 use Regexp::Common qw/number/;
  6         16584  
  6         28  
237              
238             my %PrimitiveType = map { $_ => 1 } qw/
239             null
240             boolean
241             int
242             long
243             float
244             double
245             bytes
246             string
247             /;
248              
249             my %Singleton = ( );
250              
251             ## FIXME: useless lazy generation
252             sub new {
253 126     126   183 my $class = shift;
254 126         307 my %param = @_;
255              
256             my $type = $param{type}
257 126 50       306 or croak "Schema must have a type";
258              
259 126 100       253 throw Avro::Schema::Error::Parse("Not a primitive type $type")
260             unless $class->is_type_valid($type);
261              
262 125 100       267 if (! exists $Singleton{ $type } ) {
263 22         79 my $schema = $class->SUPER::new( type => $type );
264 22         55 $Singleton{ $type } = $schema;
265             }
266 125         385 return $Singleton{ $type };
267             }
268              
269             sub is_type_valid {
270 2101   50 2101   8987 return $PrimitiveType{ $_[1] || "" };
271             }
272              
273             ## Returns true or false wheter the given data is valid for
274             ## this schema
275             sub is_data_valid {
276 33     33   49 my $schema = shift;
277 33         44 my $data = shift;
278 33         50 my $type = $schema->{type};
279 33 100       69 if ($type eq 'int') {
280 6     6   23184 no warnings;
  6         15  
  6         2843  
281 13         44 my $packed_int = pack "l", $data;
282 13         29 my $unpacked_int = unpack "l", $packed_int;
283 13 100       40 return $unpacked_int eq $data ? 1 : 0;
284             }
285 20 100       44 if ($type eq 'long') {
286 11 50       78 if ($Config{use64bitint}) {
287 11 100       29 return 0 unless defined $data;
288 10         29 my $packed_int = pack "q", $data;
289 10         29 my $unpacked_int = unpack "q", $packed_int;
290 10 100       46 return $unpacked_int eq $data ? 1 : 0;
291              
292             }
293             else {
294 0         0 require Math::BigInt;
295 0         0 my $int = eval { Math::BigInt->new($data) };
  0         0  
296 0 0       0 if ($@) {
297 0         0 warn "probably a unblessed ref: $@";
298 0         0 return 0;
299             }
300 0 0       0 return 0 if $int->is_nan;
301 0         0 my $max = Math::BigInt->new( "0x7FFF_FFFF_FFFF_FFFF" );
302 0 0       0 return $int->bcmp($max) <= 0 ? 1 : 0;
303             }
304             }
305 9 100 66     51 if ($type eq 'float' or $type eq 'double') {
306 1 50       6 $data =~ /^$RE{num}{real}$/ ? return 1 : 0;
307             }
308 8 100 100     35 if ($type eq "bytes" or $type eq "string") {
309 5 100 100     28 return 1 unless !defined $data or ref $data;
310             }
311 5 100       20 if ($type eq 'null') {
312 3 100       16 return defined $data ? 0 : 1;
313             }
314 2 50       9 if ($type eq 'boolean') {
315 0 0       0 return 0 if ref $data; # sometimes risky
316 0 0       0 return 1 if $data =~ m{yes|no|y|n|t|f|true|false}i;
317 0         0 return 0;
318             }
319 2         7 return 0;
320             }
321              
322             sub to_struct {
323 10     10   18 my $schema = shift;
324 10         21 return $schema->type;
325             }
326              
327             package Avro::Schema::Named;
328             our @ISA = qw/Avro::Schema::Base/;
329 6     6   62 use Scalar::Util;
  6         11  
  6         4967  
330              
331             my %NamedType = map { $_ => 1 } qw/
332             record
333             enum
334             fixed
335             /;
336              
337             sub new {
338 152     152   218 my $class = shift;
339 152         320 my %param = @_;
340              
341 152         380 my $schema = $class->SUPER::new(%param);
342              
343 152   50     366 my $names = $param{names} || {};
344 152   50     295 my $struct = $param{struct} || {};
345 152         229 my $name = $struct->{name};
346 152 50 33     537 unless (defined $name && length $name) {
347 0         0 throw Avro::Schema::Error::Parse( "Missing name for $class" );
348             }
349 152         227 my $namespace = $struct->{namespace};
350 152 100 66     375 unless (defined $namespace && length $namespace) {
351 111         185 $namespace = $param{namespace};
352             }
353              
354 152         375 $schema->set_names($namespace, $name);
355 80         220 $schema->add_name($names);
356              
357 80         185 return $schema;
358             }
359              
360             sub is_type_valid {
361 36   50 36   114 return $NamedType{ $_[1] || "" };
362             }
363              
364             sub set_names {
365 152     152   232 my $schema = shift;
366 152         294 my ($namespace, $name) = @_;
367              
368 152   100     490 my @parts = split /\./, ($name || ""), -1;
369 152 100       325 if (@parts > 1) {
370 13         19 $name = pop @parts;
371 13         37 $namespace = join ".", @parts;
372 13 100       24 if (grep { ! length $_ } @parts) {
  20         57  
373 5         20 throw Avro::Schema::Error::Name(
374             "name '$name' is not a valid name"
375             );
376             }
377             }
378              
379             ## 1.3.2 The name portion of a fullname, and record field names must:
380             ## * start with [A-Za-z_]
381             ## * subsequently contain only [A-Za-z0-9_]
382 147         259 my $type = $schema->{type};
383 147 100 100     733 unless (length $name && $name =~ m/^[A-Za-z_][A-Za-z0-9_]*$/) {
384 31         117 throw Avro::Schema::Error::Name(
385             "name '$name' is not valid for $type"
386             );
387             }
388 116 100 100     336 if (defined $namespace && length $namespace) {
389 52         124 for (split /\./, $namespace, -1) {
390 68 100 100     270 unless ($_ && /^[A-Za-z_][A-Za-z0-9_]*$/) {
391 36         134 throw Avro::Schema::Error::Name(
392             "namespace '$namespace' is not valid for $type"
393             );
394             }
395             }
396             }
397 80         154 $schema->{name} = $name;
398 80         196 $schema->{namespace} = $namespace;
399             }
400              
401             sub add_name {
402 80     80   123 my $schema = shift;
403 80         119 my ($names) = @_;
404              
405 80         188 my $name = $schema->fullname;
406 80 50       191 if ( exists $names->{ $name } ) {
407 0         0 throw Avro::Schema::Error::Parse( "Name $name is already defined" );
408             }
409 80         177 $names->{$name} = $schema;
410 80         287 Scalar::Util::weaken( $names->{$name} );
411 80         124 return;
412             }
413              
414             sub fullname {
415 184     184   716 my $schema = shift;
416             return join ".",
417 368 100       1506 grep { defined $_ && length $_ }
418 184         307 map { $schema->{$_ } }
  368         736  
419             qw/namespace name/;
420             }
421              
422             sub namespace {
423 75     75   123 my $schema = shift;
424 75         162 return $schema->{namespace};
425             }
426              
427             package Avro::Schema::Record;
428             our @ISA = qw/Avro::Schema::Named/;
429 6     6   46 use Scalar::Util;
  6         11  
  6         15601  
430              
431             my %ValidOrder = map { $_ => 1 } qw/ascending descending ignore/;
432              
433             sub new {
434 135     135   46209 my $class = shift;
435 135         327 my %param = @_;
436              
437 135   100     521 my $names = $param{names} ||= {};
438 135         438 my $schema = $class->SUPER::new(%param);
439              
440             my $fields = $param{struct}{fields}
441 63 50       154 or throw Avro::Schema::Error::Parse("Record must have Fields");
442              
443 63 50       163 throw Avro::Schema::Error::Parse("Record.Fields must me an array")
444             unless ref $fields eq 'ARRAY';
445              
446 63         136 my $namespace = $schema->namespace;
447              
448 63         83 my @fields;
449 63         131 for my $field (@$fields) {
450 83         216 my $f = Avro::Schema::Field->new($field, $names, $namespace);
451 65         148 push @fields, $f;
452             }
453 45         105 $schema->{fields} = \@fields;
454 45         226 return $schema;
455             }
456              
457             sub to_struct {
458 6     6   11 my $schema = shift;
459 6   100     15 my $known_names = shift || {};
460             ## consider that this record type is now known (will serialize differently)
461 6         11 my $fullname = $schema->fullname;
462 6 100       19 if ($known_names->{ $fullname }++) {
463 3         6 return $fullname;
464             }
465             return {
466             type => $schema->{type},
467             name => $fullname,
468             fields => [
469 3         5 map { $_->to_struct($known_names) } @{ $schema->{fields} }
  6         12  
  3         6  
470             ],
471             };
472             }
473              
474             sub fields {
475 52     52   1957 my $schema = shift;
476 52         249 return $schema->{fields};
477             }
478              
479             sub fields_as_hash {
480 10     10   3021 my $schema = shift;
481 10 100       29 unless (exists $schema->{_fields_as_hash}) {
482             $schema->{_fields_as_hash} = {
483 4         9 map { $_->{name} => $_ } @{ $schema->{fields} }
  8         38  
  4         9  
484             };
485             }
486 10         51 return $schema->{_fields_as_hash};
487             }
488              
489             sub is_data_valid {
490 2     2   4 my $schema = shift;
491 2         4 my $data = shift;
492 2         3 for my $field (@{ $schema->{fields} }) {
  2         5  
493 2         4 my $key = $field->{name};
494 2 100       7 return 0 unless $field->is_data_valid($data->{$key});
495             }
496 1         4 return 1;
497             }
498              
499             package Avro::Schema::Field;
500              
501             sub to_struct {
502 6     6   6 my $field = shift;
503 6   50     13 my $known_names = shift || {};
504 6         11 my $type = $field->{type}->to_struct($known_names);
505 6         29 return { name => $field->{name}, type => $type };
506             }
507              
508             sub new {
509 84     84   115 my $class = shift;
510 84         147 my ($struct, $names, $namespace) = @_;
511              
512 84         130 my $name = $struct->{name};
513 84 50 33     275 throw Avro::Schema::Error::Parse("Record.Field.name is required")
514             unless defined $name && length $name;
515              
516 84         118 my $type = $struct->{type};
517 84 50 33     275 throw Avro::Schema::Error::Parse("Record.Field.name is required")
518             unless defined $type && length $type;
519              
520 84         230 $type = Avro::Schema->parse_struct($type, $names, $namespace);
521 82         239 my $field = { name => $name, type => $type };
522             #TODO: find where to weaken precisely
523             #Scalar::Util::weaken($struct->{type});
524              
525 82 100       178 if (exists $struct->{default}) {
526 28         61 my $is_valid = $type->is_data_valid($struct->{default});
527 28         358 my $t = $type->type;
528 28 100       112 throw Avro::Schema::Error::Parse(
529             "default value doesn't validate $t: '$struct->{default}'"
530             ) unless $is_valid;
531              
532             ## small Perlish special case
533 16 50       43 if ($type eq 'boolean') {
534 0 0       0 $field->{default} = $struct->{default} ? 1 : 0;
535             }
536             else {
537 16         47 $field->{default} = $struct->{default};
538             }
539             }
540 70 100       140 if (my $order = $struct->{order}) {
541             throw Avro::Schema::Error::Parse(
542             "Order '$order' is not valid'"
543 7 100       40 ) unless $ValidOrder{$order};
544 3         7 $field->{order} = $order;
545             }
546 66         176 return bless $field, $class;
547             }
548              
549             sub is_data_valid {
550 2     2   4 my $field = shift;
551 2         3 my $data = shift;
552 2 100       6 return 1 if $field->{type}->is_data_valid($data);
553 1         6 return 0;
554             }
555              
556             package Avro::Schema::Enum;
557             our @ISA = qw/Avro::Schema::Named/;
558              
559             sub new {
560 6     6   14 my $class = shift;
561 6         22 my %param = @_;
562 6         28 my $schema = $class->SUPER::new(%param);
563             my $struct = $param{struct}
564 6 50       36 or throw Avro::Schema::Error::Parse("Enum instantiation");
565 6   50     20 my $symbols = $struct->{symbols} || [];
566              
567 6 50       14 unless (@$symbols) {
568 0         0 throw Avro::Schema::Error::Parse("Enum needs at least one symbol");
569             }
570 6         12 my %symbols;
571 6         8 my $pos = 0;
572 6         15 for (@$symbols) {
573 15 50       28 if (ref $_) {
574 0         0 throw Avro::Schema::Error::Parse(
575             "Enum.symbol should be a string"
576             );
577             }
578             throw Avro::Schema::Error::Parse("Duplicate symbol in Enum")
579 15 50       31 if exists $symbols{$_};
580              
581 15         39 $symbols{$_} = $pos++;
582             }
583 6         24 $schema->{hash_symbols} = \%symbols;
584 6         37 return $schema;
585             }
586              
587             sub is_data_valid {
588 5     5   13 my $schema = shift;
589 5         7 my $data = shift;
590 5 100 66     39 return 1 if defined $data && exists $schema->{hash_symbols}{$data};
591 2         22 return 0;
592             }
593              
594             sub symbols {
595 10     10   16 my $schema = shift;
596 10 100       25 unless (exists $schema->{symbols}) {
597 3         10 my $sym = $schema->{hash_symbols};
598 3         19 $schema->{symbols} = [ sort { $sym->{$a} <=> $sym->{$b} } keys %$sym ];
  11         29  
599             }
600 10         41 return $schema->{symbols};
601             }
602              
603             sub symbols_as_hash {
604 5     5   8 my $schema = shift;
605 5   50     28 return $schema->{hash_symbols} || {};
606             }
607              
608             sub to_struct {
609 3     3   15 my $schema = shift;
610 3   100     11 my $known_names = shift || {};
611              
612 3         6 my $fullname = $schema->fullname;
613 3 50       12 if ($known_names->{ $fullname }++) {
614 0         0 return $fullname;
615             }
616             return {
617             type => 'enum',
618             name => $schema->fullname,
619 3         8 symbols => [ @{ $schema->symbols } ],
  3         6  
620             };
621             }
622              
623             package Avro::Schema::Array;
624             our @ISA = qw/Avro::Schema::Base/;
625              
626             sub new {
627 19     19   33 my $class = shift;
628 19         58 my %param = @_;
629 19         96 my $schema = $class->SUPER::new(%param);
630              
631             my $struct = $param{struct}
632 19 50       70 or throw Avro::Schema::Error::Parse("Enum instantiation");
633              
634             my $items = $struct->{items}
635 19 50       51 or throw Avro::Schema::Error::Parse("Array must declare 'items'");
636              
637 19         54 $items = Avro::Schema->parse_struct($items, $param{names}, $param{namespace});
638 19         100 $schema->{items} = $items;
639 19         92 return $schema;
640             }
641              
642             sub is_data_valid {
643 2     2   6 my $schema = shift;
644 2         5 my $default = shift;
645 2 50 33     14 return 1 if $default && ref $default eq 'ARRAY';
646 0         0 return 0;
647             }
648              
649             sub items {
650 2340     2340   3129 my $schema = shift;
651 2340         4971 return $schema->{items};
652             }
653              
654             sub to_struct {
655 6     6   13 my $schema = shift;
656 6   50     17 my $known_names = shift || {};
657              
658             return {
659             type => 'array',
660 6         23 items => $schema->{items}->to_struct($known_names),
661             };
662             }
663              
664             package Avro::Schema::Map;
665             our @ISA = qw/Avro::Schema::Base/;
666              
667             sub new {
668 17     17   33 my $class = shift;
669 17         58 my %param = @_;
670 17         92 my $schema = $class->SUPER::new(%param);
671              
672             my $struct = $param{struct}
673 17 50       52 or throw Avro::Schema::Error::Parse("Map instantiation");
674              
675 17         31 my $values = $struct->{values};
676 17 50 33     88 unless (defined $values && length $values) {
677 0         0 throw Avro::Schema::Error::Parse("Map must declare 'values'");
678             }
679 17         54 $values = Avro::Schema->parse_struct($values, $param{names}, $param{namespace});
680 17         48 $schema->{values} = $values;
681              
682 17         79 return $schema;
683             }
684              
685             sub is_data_valid {
686 1     1   2 my $schema = shift;
687 1         2 my $default = shift;
688 1 50 33     17 return 1 if $default && ref $default eq 'HASH';
689 0         0 return 0;
690             }
691              
692             sub values {
693 786     786   1075 my $schema = shift;
694 786         1872 return $schema->{values};
695             }
696              
697             sub to_struct {
698 6     6   11 my $schema = shift;
699 6   50     15 my $known_names = shift || {};
700              
701             return {
702             type => 'map',
703 6         31 values => $schema->{values}->to_struct($known_names),
704             };
705             }
706              
707             package Avro::Schema::Union;
708             our @ISA = qw/Avro::Schema::Base/;
709              
710             sub new {
711 16     16   29 my $class = shift;
712 16         48 my %param = @_;
713 16         63 my $schema = $class->SUPER::new(%param);
714             my $union = $param{struct}
715 16 50       58 or throw Avro::Schema::Error::Parse("Union.new needs a struct");
716              
717 16   50     62 my $names = $param{names} ||= {};
718              
719 16         29 my @schemas;
720             my %seen_types;
721 16         32 for my $struct (@$union) {
722 36         76 my $sch = Avro::Schema->parse_struct($struct, $names, $param{namespace});
723 36         78 my $type = $sch->type;
724              
725             ## 1.3.2 Unions may not contain more than one schema with the same
726             ## type, except for the named types record, fixed and enum. For
727             ## example, unions containing two array types or two map types are not
728             ## permitted, but two types with different names are permitted.
729 36 100       78 if (Avro::Schema::Named->is_type_valid($type)) {
730 9         23 $type = $sch->fullname; # resolve Named types to their name
731             }
732             ## XXX: I could define &type_name doing the correct resolution for all classes
733 36 100       96 if ($seen_types{ $type }++) {
734 3         23 throw Avro::Schema::Error::Parse(
735             "$type is present more than once in the union"
736             )
737             }
738             ## 1.3.2 Unions may not immediately contain other unions.
739 33 100       69 if ($type eq 'union') {
740 1         4 throw Avro::Schema::Error::Parse(
741             "Cannot embed unions in union"
742             );
743             }
744 32         76 push @schemas, $sch;
745             }
746 12         45 $schema->{schemas} = \@schemas;
747              
748 12         65 return $schema;
749             }
750              
751             sub schemas {
752 7     7   12 my $schema = shift;
753 7         20 return $schema->{schemas};
754             }
755              
756             sub is_data_valid {
757 0     0   0 my $schema = shift;
758 0         0 my $data = shift;
759 0         0 for my $type ( @{ $schema->{schemas} } ) {
  0         0  
760 0 0       0 if ( $type->is_data_valid($data) ) {
761 0         0 return 1;
762             }
763             }
764 0         0 return 0;
765             }
766              
767             sub to_struct {
768 0     0   0 my $schema = shift;
769 0   0     0 my $known_names = shift || {};
770 0         0 return [ map { $_->to_struct($known_names) } @{$schema->{schemas}} ];
  0         0  
  0         0  
771             }
772              
773             package Avro::Schema::Fixed;
774             our @ISA = qw/Avro::Schema::Named/;
775              
776             sub new {
777 11     11   20 my $class = shift;
778 11         32 my %param = @_;
779 11         40 my $schema = $class->SUPER::new(%param);
780              
781             my $struct = $param{struct}
782 11 50       28 or throw Avro::Schema::Error::Parse("Fixed instantiation");
783              
784 11         18 my $size = $struct->{size};
785 11 50 33     50 unless (defined $size && length $size) {
786 0         0 throw Avro::Schema::Error::Parse("Fixed must declare 'size'");
787             }
788 11 50       20 if (ref $size) {
789 0         0 throw Avro::Schema::Error::Parse(
790             "Fixed.size should be a scalar"
791             );
792             }
793 11 100 100     72 unless ($size =~ m{^\d+$} && $size > 0) {
794 4         16 throw Avro::Schema::Error::Parse(
795             "Fixed.size should be a positive integer"
796             );
797             }
798             # Cast into numeric so that it will be encoded as a JSON number
799 7         16 $schema->{size} = $size + 0;
800              
801 7         29 return $schema;
802             }
803              
804             sub is_data_valid {
805 0     0   0 my $schema = shift;
806 0         0 my $default = shift;
807 0         0 my $size = $schema->{size};
808 0 0 0     0 return 1 if $default && bytes::length $default == $size;
809 0         0 return 0;
810             }
811              
812             sub size {
813 51     51   99 my $schema = shift;
814 51         195 return $schema->{size};
815             }
816              
817             sub to_struct {
818 1     1   4 my $schema = shift;
819 1   50     4 my $known_names = shift || {};
820              
821 1         4 my $fullname = $schema->fullname;
822 1 50       5 if ($known_names->{ $fullname }++) {
823 0         0 return $fullname;
824             }
825              
826             return {
827             type => 'fixed',
828             name => $fullname,
829             size => $schema->{size},
830 1         6 };
831             }
832              
833             package Avro::Schema::Error::Parse;
834 6     6   2455 use parent 'Error::Simple';
  6         1543  
  6         40  
835              
836             package Avro::Schema::Error::Name;
837 6     6   29786 use parent 'Error::Simple';
  6         14  
  6         27  
838              
839             package Avro::Schema::Error::Mismatch;
840 6     6   417 use parent 'Error::Simple';
  6         10  
  6         19  
841              
842             1;