Commit 3b9ba057 authored by Mattias Päivärinta's avatar Mattias Päivärinta
Browse files

Merge branch 'develop' into v1.2.0

parents 68f1fa2e f77f7ee1
......@@ -63,13 +63,12 @@ A subrule value HASHREF may have the following keys:
=over 4
=item optional
=item quantifier
Values: 'y'|'n' (default: n)
=item repeatable
Values: non-negative integer|'unbounded' (default: 1)
Values: 'required' | 'required-strict' | 'optional-free' |
'optional-not-empty' | 'optional-constrained' | 'empty-constrained' |
'omitted-constrained' | /repeatable (max \d+)?/ |
/optional-repeatable (max \d+)?/ (default: required)
=item line
......@@ -104,8 +103,8 @@ Registrar Object query:
- Registrar reply: { }
Domain name reply:
- Domain name details section: { }
- Domain name subsection 1: { optional: free }
- Empty line: { repeatable: 3, line: empty line }
- Domain name subsection 1: { quantifier: optional-free }
- Empty line: { quantifier: repeatable max 3, line: empty line }
- AWIP footer: { }
- Legal disclaimer: { }
Domain name subsection 1:
......@@ -125,9 +124,9 @@ Domain name subsection 5:
Domain name subsection 2: { }
Registrar reply:
- Registrar details section: { }
- Registrar subsection 1: { optional: free }
- Empty line: { repeatable: 3, line: empty line }
- AWIP footer: { optional: free }
- Registrar subsection 1: { quantifier: optional-free }
- Empty line: { quantifier: repeatable max 3, line: empty line }
- AWIP footer: { quantifier: optional-free }
- Legal disclaimer: { }
Registrar subsection 1:
Last updated footer: { }
......@@ -146,17 +145,17 @@ Registrar subsection 5:
Registrar subsection 2: { }
Name server reply type 1:
- Name server details section: { }
- Name server subsection 1: { optional: free }
- Empty line: { repeatable: 3, line: empty line }
- AWIP footer: { optional: free }
- Name server subsection 1: { quantifier: optional-free }
- Empty line: { quantifier: repeatable max 3, line: empty line }
- AWIP footer: { quantifier: optional-free }
- Legal disclaimer: { }
Name server details section:
- Server Name: { line: field, type: query name server }
- IP Address: { optional: free, repeatable: unbounded, line: field, type: query name server ip }
- Registrar: { optional: constrained, line: field, type: postal line }
- WHOIS Server: { optional: constrained, line: field, type: hostname }
- Referral URL: { optional: constrained, line: field, type: http url }
- Additional fields section: { optional: free }
- IP Address: { quantifier: optional-repeatable, line: field, type: query name server ip }
- Registrar: { quantifier: optional-constrained, line: field, type: postal line }
- WHOIS Server: { quantifier: optional-constrained, line: field, type: hostname }
- Referral URL: { quantifier: optional-constrained, line: field, type: http url }
- Additional field: { quantifier: optional-repeatable, line: field, keytype: name server object additional field key }
Name server subsection 1:
Last updated footer: { }
Name server subsection 2: { }
......@@ -174,132 +173,149 @@ Name server subsection 5:
Name server subsection 2: { }
Name server reply type 2:
- Multiple name servers section: { }
- Empty line: { optional: free, repeatable: 3, line: empty line }
- Empty line: { quantifier: optional-repeatable max 3, line: empty line }
- Last updated footer: { }
- Empty line: { repeatable: 3, line: empty line }
- AWIP footer: { optional: free }
- Empty line: { quantifier: repeatable max 3, line: empty line }
- AWIP footer: { quantifier: optional-free }
- Legal disclaimer: { }
Registrar details section:
- Registrar Name: { line: field, type: query registrar name }
- Street: { line: field, type: postal line, repeatable: unbounded }
- Street: { line: field, type: postal line, quantifier: repeatable }
- City: { line: field, type: postal line }
- State/Province: { optional: constrained, line: field, type: postal line }
- Postal Code: { optional: constrained, line: field, type: postal code }
- State/Province: { quantifier: optional-constrained, line: field, type: postal line }
- Postal Code: { quantifier: optional-constrained, line: field, type: postal code }
- Country: { line: field, type: country code }
- Phone Number: { line: field, type: phone number }
- Phone Ext: { optional: free, line: field, type: token }
- Fax number section: { optional: free }
- Email: { line: field, type: email address }
- WHOIS Server: { optional: constrained, line: field, type: hostname }
- Phone number section: { quantifier: repeatable }
- Phone Ext: { quantifier: optional-free, line: field, type: token }
- Fax number section: { quantifier: required }
- Email: { quantifier: repeatable, line: field, type: email address }
- WHOIS Server: { quantifier: optional-constrained, line: field, type: hostname }
- Referral URL: { line: field, type: http url }
- Admin contact section: { optional: free, repeatable: unbounded }
- Technical contact section: { optional: free, repeatable: unbounded }
- Additional fields section: { optional: free }
- Admin contact section: { quantifier: optional-repeatable }
- Technical contact section: { quantifier: optional-repeatable }
- Additional field: { quantifier: optional-repeatable, line: field, keytype: registrar object additional field key }
Admin contact section:
- Admin Contact: { line: field, type: postal line }
- Phone number section: { repeatable: unbounded }
- Fax number section: { optional: free, repeatable: unbounded }
- Email: { line: field, type: email address, repeatable: unbounded }
- Phone number section: { quantifier: repeatable }
- Fax number section: { quantifier: required }
- Email: { line: field, type: email address, quantifier: repeatable }
Technical contact section:
- Technical Contact: { line: field, type: postal line }
- Phone number section: { repeatable: unbounded }
- Fax number section: { optional: free, repeatable: unbounded }
- Email: { line: field, type: email address, repeatable: unbounded }
- Phone number section: { quantifier: repeatable }
- Fax number section: { quantifier: required }
- Email: { line: field, type: email address, quantifier: repeatable }
Phone number section:
- Phone Number: { line: field, type: phone number }
- Phone Ext: { optional: free, line: field, type: token }
- Phone Ext: { quantifier: optional-free, line: field, type: token }
Fax number section:
- Fax Number: { line: field, type: phone number }
- Fax Ext: { optional: free, line: field, type: token }
Fax number section type A: { quantifier: repeatable }
Fax number section type B: { quantifier: required }
Fax number section type C: { quantifier: required }
Fax number section type A:
- Fax Number: { line: field, type: phone number, quantifier: required-strict }
- Fax Ext: { line: field, type: token, quantifier: optional-free }
Fax number section type B:
- Fax Number: { line: field, type: void, quantifier: empty-constrained }
- Fax Ext: { line: field, type: token, quantifier: optional-free }
Fax number section type C:
- Fax Number: { line: field, type: void, quantifier: omitted-constrained }
Domain name details section:
- Domain Name: { line: field, type: query domain name }
- Internationalized Domain Name: { optional: free, line: field, type: u-label }
- Internationalized Domain Name: { quantifier: optional-free, line: field, type: u-label }
- Domain ID: { line: field, type: epp repo id }
- WHOIS Server: { optional: constrained, line: field, type: hostname }
- WHOIS Server: { quantifier: optional-constrained, line: field, type: hostname }
- Referral URL: { line: field, type: http url }
- Updated Date: { optional: constrained, line: field, type: time stamp }
- Updated Date: { quantifier: optional-constrained, line: field, type: time stamp }
- Creation Date: { line: field, type: time stamp }
- Registry Expiry Date: { line: field, type: time stamp }
- Sponsoring Registrar: { line: field, type: token }
- Sponsoring Registrar IANA ID: { line: field, type: positive integer }
- Domain Status: { repeatable: unbounded, line: field, type: domain status }
- Domain Status: { quantifier: repeatable, line: field, type: domain status }
- Registrant ID: { line: field, type: token }
- Registrant Name: { line: field, type: postal line }
- Registrant Organization: { optional: constrained, line: field, type: postal line }
- Registrant Street: { repeatable: unbounded, line: field, type: postal line }
- Registrant Organization: { quantifier: optional-constrained, line: field, type: postal line }
- Registrant Street: { quantifier: repeatable, line: field, type: postal line }
- Registrant City: { line: field, type: postal line }
- Registrant State/Province: { optional: constrained, line: field, type: postal line }
- Registrant Postal Code: { optional: constrained, line: field, type: postal code }
- Registrant State/Province: { quantifier: optional-constrained, line: field, type: postal line }
- Registrant Postal Code: { quantifier: optional-constrained, line: field, type: postal code }
- Registrant Country: { line: field, type: country code }
- Registrant Phone: { line: field, type: phone number }
- Registrant Phone Ext: { optional: constrained, line: field, type: token }
- Registrant Fax: { optional: constrained, line: field, type: phone number }
- Registrant Fax Ext: { optional: constrained, line: field, type: token }
- Registrant Phone Ext: { quantifier: optional-constrained, line: field, type: token }
- Registrant Fax: { quantifier: optional-constrained, line: field, type: phone number }
- Registrant Fax Ext: { quantifier: optional-constrained, line: field, type: token }
- Registrant Email: { line: field, type: email address }
- Admin ID: { line: field, type: token }
- Admin Name: { line: field, type: postal line }
- Admin Organization: { optional: constrained, line: field, type: postal line }
- Admin Street: { repeatable: unbounded, line: field, type: postal line }
- Admin Organization: { quantifier: optional-constrained, line: field, type: postal line }
- Admin Street: { quantifier: repeatable, line: field, type: postal line }
- Admin City: { line: field, type: postal line }
- Admin State/Province: { optional: constrained, line: field, type: postal line }
- Admin Postal Code: { optional: constrained, line: field, type: postal code }
- Admin State/Province: { quantifier: optional-constrained, line: field, type: postal line }
- Admin Postal Code: { quantifier: optional-constrained, line: field, type: postal code }
- Admin Country: { line: field, type: country code }
- Admin Phone: { line: field, type: phone number }
- Admin Phone Ext: { optional: constrained, line: field, type: token }
- Admin Fax: { optional: constrained, line: field, type: phone number }
- Admin Fax Ext: { optional: constrained, line: field, type: token }
- Admin Phone Ext: { quantifier: optional-constrained, line: field, type: token }
- Admin Fax: { quantifier: optional-constrained, line: field, type: phone number }
- Admin Fax Ext: { quantifier: optional-constrained, line: field, type: token }
- Admin Email: { line: field, type: email address }
- Tech ID: { line: field, type: token }
- Tech Name: { line: field, type: postal line }
- Tech Organization: { optional: constrained, line: field, type: postal line }
- Tech Street: { repeatable: unbounded, line: field, type: postal line }
- Tech Organization: { quantifier: optional-constrained, line: field, type: postal line }
- Tech Street: { quantifier: repeatable, line: field, type: postal line }
- Tech City: { line: field, type: postal line }
- Tech State/Province: { optional: constrained, line: field, type: postal line }
- Tech Postal Code: { optional: constrained, line: field, type: postal code }
- Tech State/Province: { quantifier: optional-constrained, line: field, type: postal line }
- Tech Postal Code: { quantifier: optional-constrained, line: field, type: postal code }
- Tech Country: { line: field, type: country code }
- Tech Phone: { line: field, type: phone number }
- Tech Phone Ext: { optional: constrained, line: field, type: token }
- Tech Fax: { optional: constrained, line: field, type: phone number }
- Tech Fax Ext: { optional: constrained, line: field, type: token }
- Tech Phone Ext: { quantifier: optional-constrained, line: field, type: token }
- Tech Fax: { quantifier: optional-constrained, line: field, type: phone number }
- Tech Fax Ext: { quantifier: optional-constrained, line: field, type: token }
- Tech Email: { line: field, type: email address }
- Billing contact section: { optional: free }
- Name server section: { repeatable: unbounded }
- Billing contact section: { quantifier: optional-free }
- Name server section: { }
- DNSSEC: { line: field, type: dnssec }
- Additional fields section: { optional: free }
- Additional field: { quantifier: optional-repeatable, line: field, keytype: domain name object additional field key }
Billing contact section:
- Billing ID: { line: field, type: token }
- Billing Name: { line: field, type: postal line }
- Billing Organization: { optional: free, line: field, type: postal line }
- Billing Street: { repeatable: unbounded, line: field, type: postal line }
- Billing Organization: { quantifier: optional-free, line: field, type: postal line }
- Billing Street: { quantifier: repeatable, line: field, type: postal line }
- Billing City: { line: field, type: postal line }
- Billing State/Province: { optional: free, line: field, type: postal line }
- Billing Postal Code: { optional: free, line: field, type: postal code }
- Billing State/Province: { quantifier: optional-free, line: field, type: postal line }
- Billing Postal Code: { quantifier: optional-free, line: field, type: postal code }
- Billing Country: { line: field, type: country code }
- Billing Phone: { line: field, type: phone number }
- Billing Phone Ext: { optional: free, line: field, type: token }
- Billing Fax: { optional: free, line: field, type: phone number }
- Billing Fax Ext: { optional: free, line: field, type: token }
- Billing Phone Ext: { quantifier: optional-free, line: field, type: token }
- Billing Fax: { quantifier: optional-free, line: field, type: phone number }
- Billing Fax Ext: { quantifier: optional-free, line: field, type: token }
- Billing Email: { line: field, type: email address }
Name server section:
- Name Server: { optional: constrained, line: field, type: hostname }
- IP Address: { optional: free, repeatable: unbounded, line: field, type: ip address }
Name server section type A: { quantifier: repeatable }
Name server section type B: { quantifier: repeatable }
Name server section type C: { quantifier: repeatable }
Name server section type A:
- Name Server: { quantifier: required-strict, line: field, type: hostname }
- IP address section: { quantifier: repeatable }
Name server section type B:
- Name Server: { quantifier: empty-constrained, line: field, type: void }
Name server section type C:
- Name Server: { quantifier: omitted-constrained, line: field, type: void }
IP address section:
- IP Address: { quantifier: optional-not-empty, line: field, type: ip address }
Multiple name servers section:
- Multiple name servers line: { line: multiple name servers line }
- ROID line: { line: roid line }
- ROID line: { line: roid line, repeatable: unbounded }
Additional fields section:
- Additional field: { repeatable: unbounded, line: field }
- ROID line: { line: roid line, quantifier: repeatable }
Last updated subsection 1:
Last updated footer: { }
Last updated subsection 2: { }
Last updated subsection 2:
- Empty line: { repeatable: 2, line: empty line }
- Empty line: { quantifier: repeatable max 2, line: empty line }
- Last updated footer: { }
Last updated footer:
- Last update line: { line: last update line }
AWIP footer:
- AWIP line: { line: awip line }
- Empty line: { repeatable: 3, line: empty line }
- Empty line: { quantifier: repeatable max 3, line: empty line }
Legal disclaimer:
- Non-empty line: { line: non-empty line }
- Any line: { optional: free, repeatable: unbounded, line: any line }
- Any line: { quantifier: optional-repeatable, line: any line }
......@@ -6,6 +6,7 @@ use 5.014;
use Carp;
use English;
use Readonly;
use URI;
use Regexp::IPv6;
......@@ -43,10 +44,14 @@ The default types are:
* token
* translation clause
* u-label
* domain name object additional field key
* registrar object additional field key
* name server object additional field key
* void
=cut
my %domain_status_codes = (
Readonly my %DOMAIN_STATUS_CODES => (
addPeriod => 1,
autoRenewPeriod => 1,
clientDeleteProhibited => 1,
......@@ -72,6 +77,85 @@ my %domain_status_codes = (
transferPeriod => 1,
);
Readonly my %DOMAIN_NAME_ADDITIONAL_FIELD_KEY_BLACKLIST => (
'Domain Name' => 1,
'Domain ID' => 1,
'WHOIS Server' => 1,
'Referral URL' => 1,
'Updated Date' => 1,
'Creation Date' => 1,
'Registry Expiry Date' => 1,
'Sponsoring Registrar' => 1,
'Sponsoring Registrar IANA ID' => 1,
'Domain Status' => 1,
'Registrant ID' => 1,
'Registrant Name' => 1,
'Registrant Organization' => 1,
'Registrant Street' => 1,
'Registrant City' => 1,
'Registrant State/Province' => 1,
'Registrant Postal Code' => 1,
'Registrant Country' => 1,
'Registrant Phone' => 1,
'Registrant Phone Ext' => 1,
'Registrant Fax' => 1,
'Registrant Fax Ext' => 1,
'Registrant Email' => 1,
'Admin ID' => 1,
'Admin Name' => 1,
'Admin Organization' => 1,
'Admin Street' => 1,
'Admin City' => 1,
'Admin State/Province' => 1,
'Admin Postal Code' => 1,
'Admin Country' => 1,
'Admin Phone' => 1,
'Admin Phone Ext' => 1,
'Admin Fax' => 1,
'Admin Fax Ext' => 1,
'Admin Email' => 1,
'Tech ID' => 1,
'Tech Name' => 1,
'Tech Organization' => 1,
'Tech Street' => 1,
'Tech City' => 1,
'Tech State/Province' => 1,
'Tech Postal Code' => 1,
'Tech Country' => 1,
'Tech Phone' => 1,
'Tech Phone Ext' => 1,
'Tech Fax' => 1,
'Tech Fax Ext' => 1,
'Tech Email' => 1,
'DNSSEC' => 1,
'Name Server' => 1,
'IP Address' => 1,
);
Readonly my %REGISTRAR_ADDITIONAL_FIELD_KEY_BLACKLIST => (
'Registrar Name' => 1,
'Street' => 1,
'City' => 1,
'State/Province' => 1,
'Postal Code' => 1,
'Country' => 1,
'Phone Number' => 1,
'Email' => 1,
'WHOIS Server' => 1,
'Referral URL' => 1,
'Admin Contact' => 1,
'Technical Contact' => 1,
'Fax Number' => 1,
);
Readonly my %NAME_SERVER_ADDITIONAL_FIELD_KEY_BLACKLIST => (
'Server Name' => 1,
'IP Address' => 1,
'Registrar' => 1,
'WHOIS Server' => 1,
'Referral URL' => 1,
);
my $ROID_SUFFIX = {};
my %default_types;
......@@ -122,7 +206,7 @@ my %default_types;
return ( 'expected domain status code' );
}
if ( !exists $domain_status_codes{$value} ) {
if ( !exists $DOMAIN_STATUS_CODES{$value} ) {
return ( 'expected domain status code' );
}
......@@ -296,7 +380,7 @@ my %default_types;
}
if ( $value =~ /^([^ ]+) {1,9}https:\/\/icann\.org\/epp#(.+)$/o ) {
if ( exists $domain_status_codes{$1} && $1 eq $2 ) {
if ( exists $DOMAIN_STATUS_CODES{$1} && $1 eq $2 ) {
return ();
}
}
......@@ -401,6 +485,48 @@ my %default_types;
return ();
},
'domain name object additional field key' => sub {
my $value = shift;
unless ( $value ) {
return ( 'expected domain name object additional field key' );
}
if ( exists $DOMAIN_NAME_ADDITIONAL_FIELD_KEY_BLACKLIST{$value} ) {
return ( 'forbidden domain name object additional field key' );
}
return ();
},
'registrar object additional field key' => sub {
my $value = shift;
unless ( $value ) {
return ( 'expected registrar object additional field key' );
}
if ( exists $REGISTRAR_ADDITIONAL_FIELD_KEY_BLACKLIST{$value} ) {
return ( 'forbidden registrar object additional field key' );
}
return ();
},
'name server object additional field key' => sub {
my $value = shift;
unless ( $value ) {
return ( 'expected name server object additional field key' );
}
if ( exists $NAME_SERVER_ADDITIONAL_FIELD_KEY_BLACKLIST{$value} ) {
return ( 'forbidden name server object additional field key' );
}
return ();
},
'void' => sub {
return ( 'no values are allowed for type void' );
},
);
=head1 CONSTRUCTORS
......
......@@ -50,8 +50,8 @@ sub extract_roid {
}
elsif ( $token eq 'roid line' ) {
ref $value eq 'ARRAY' or croak "'roid line' value expected to be arrayref";
defined $value->[0] or croak "'roid line' value expected to have roid at position 0";
defined $value->[1] or croak "'hostname' value expected to have roid at position 1";
defined $value->[0] or croak "'roid line' value expected to have roid at position 0";
defined $value->[1] or croak "'hostname' value expected to have roid at position 1";
my ( $roid, $hostname ) = @{$value};
my @errors;
push @errors, grep { $_ ne 'expected roid suffix to be a registered epp repo id' } $types->validate_type( 'roid', $roid );
......
......@@ -95,7 +95,7 @@ sub validate {
};
# Validate rule
my $result = _rule( $state, key => $rule );
my $result = _rule( $state, key => $rule, quantifier => 'required' );
# Pick up validation warnings
my @errors;
......@@ -193,7 +193,7 @@ sub _choice_section {
my $section_rule = shift or croak 'Missing argument: $section_rule';
ref $section_rule eq 'HASH' or croak 'Argument $section_rule must be hashref';
for my $key ( keys $section_rule ) {
for my $key ( sort keys %{$section_rule} ) {
my $params = $section_rule->{$key};
ref $params eq 'HASH' or confess "value of key '$key' must be a hashref";
......@@ -208,27 +208,34 @@ sub _choice_section {
sub _occurances {
my ( $state, %args ) = @_;
my $key = $args{'key'} or croak 'Missing argument: key';
my $line = $args{'line'};
my $type = $args{'type'};
my $key = $args{'key'} or croak 'Missing argument: key';
my $line = $args{'line'};
my $type = $args{'type'};
my $quantifier = $args{'quantifier'} || 'required';
my $keytype = $args{'keytype'};
my $min_occurs;
my $optional_type;
if ( ( $args{'optional'} || 'no' ) =~ /^(constrained|free)$/ ) {
$min_occurs = 0;
$optional_type = $1;
}
else {
$min_occurs = 1;
}
my $max_occurs;
if ( !exists $args{'repeatable'} ) {
$max_occurs = 1;
}
elsif ( $args{'repeatable'} ne 'unbounded' ) {
$max_occurs = int $args{'repeatable'};
$max_occurs >= 1 or croak 'Argument must not be zero or negative: repeatable';
for ( $quantifier ) {
when ( /^required$|^required-strict$/ ) {
$min_occurs = 1;
$max_occurs = 1;
}
when ( /^optional-free$|^optional-not-empty$|^optional-constrained$|^empty-constrained$|^omitted-constrained$/ ) {
$min_occurs = 0;
$max_occurs = 1;
}
when ( /^optional-repeatable(?: max ([1-9][0-9]*))?$/ ) {
$min_occurs = 0;
$max_occurs = $1;
}
when ( /^repeatable(?: max ([1-9][0-9]*))?$/ ) {
$min_occurs = 1;
$max_occurs = $1;
}
default {
croak "internal error: unhandled quantifier '$_'";
}
}
my $count = 0;
......@@ -236,7 +243,7 @@ sub _occurances {
my @errors;
while ( !defined $max_occurs || $count < $max_occurs ) {
my $line_before = $state->{lexer}->line_no;
my ( $parsed, $parsed_errors ) = _rule( $state, line => $line, key => $key, type => $type );
my ( $parsed, $parsed_errors ) = _rule( $state, line => $line, key => $key, type => $type, quantifier => $quantifier, keytype => $keytype );
if ( defined $parsed ) {
ref $parsed_errors eq 'ARRAY' or confess;
$count++;
......@@ -251,21 +258,30 @@ sub _occurances {
push @errors, @pending_empty_error;
@pending_empty_error = ();
}
elsif ( $min_occurs > 0 ) {
push @errors, sprintf( "line %d: field '%s' is required and must not be empty", $line_after - 1, $key );
elsif ( $quantifier =~ /^required$|^repeatable|^omitted-constrained$|^optional-not-empty$/ ) {
push @errors, sprintf( "line %d: field '%s' is %s and must not be present as an empty field", $line_after - 1, $key, $quantifier );
}
elsif ( $optional_type eq 'constrained' ) {
elsif ( $quantifier =~ /^optional-constrained$|^empty-constrained$/ ) {
push @errors, _set_empty_kind( $state, kind => 'empty field', line_no => $line_after - 1, key => $key );
}
}
else {
push @errors, @pending_empty_error;
@pending_empty_error = ();
if ( $parsed eq 'field' && $quantifier =~ /^empty-constrained$|^omitted-constrained$/ ) {
push @errors, sprintf( "line %d: field '%s' is %s and must not be present as a non-empty field", $line_after - 1, $key, $quantifier );
}
}
}
else {
if ( $count == 0 && defined $line && $line eq 'field' && $min_occurs == 0 && $optional_type eq 'constrained' ) {
push @errors, _set_empty_kind( $state, kind => 'omitted field', line_no => $state->{lexer}->line_no, key => $key );
if ( $count == 0 && defined $line && $line eq 'field' ) {
if ( $quantifier eq 'empty-constrained' ) {
push @errors, sprintf( "line %d: field '%s' is empty-constrained and must not be omitted", $state->{lexer}->line_no, $key );
}
elsif ( $quantifier =~ /^optional-constrained$|^omitted-constrained$/ ) {
push @errors, _set_empty_kind( $state, kind => 'omitted field', line_no => $state->{lexer}->line_no, key => $key );
}
}
last;
}
......@@ -282,12 +298,14 @@ sub _occurances {
## no critic (Subroutines::RequireArgUnpacking)
sub _rule {
my ( $state, %args ) = @_;
my $line = $args{'line'};
my $key = $args{'key'} or croak 'Missing argument: key';
my $type = $args{'type'};
my $line = $args{'line'};
my $key = $args{'key'} or croak 'Missing argument: key';
my $type = $args{'type'};
my $quantifier = $args{'quantifier'} or croak 'Missing argument: quantifier';
my $keytype = $args{'keytype'};
if ( defined $line || defined $type ) {
my ( $subtype, $result ) = _line( $state, line => $line, key => $key, type => $type );
my ( $subtype, $result ) = _line( $state, line => $line, key => $key, type => $type, quantifier => $quantifier, keytype => $keytype );
return ( $subtype, $result );
}
else {
......@@ -319,14 +337,21 @@ sub _rule {
sub _line {
my ( $state, %args ) = @_;
my $key = $args{'key'} or croak 'Missing argument: key';
my $line = $args{'line'};
my $type = $args{'type'};
( $line || $type ) or confess;
my $key = $args{'key'} or croak 'Missing argument: key';
my $line = $args{'line'};