From: Dylan William Hardison Date: Wed, 23 Jan 2019 20:39:00 +0000 (-0500) Subject: Bug 1512815 - add object that encapsulates the include/exclude logic X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=37fb6a07246b6b86bb30003a95252bd7ec3c9848;p=thirdparty%2Fbugzilla.git Bug 1512815 - add object that encapsulates the include/exclude logic --- diff --git a/Bugzilla/WebService.pm b/Bugzilla/WebService.pm index da0fbfd32..eb6db2081 100644 --- a/Bugzilla/WebService.pm +++ b/Bugzilla/WebService.pm @@ -14,6 +14,8 @@ use strict; use warnings; use Bugzilla::WebService::Server; +use Bugzilla::WebService::Wants; +use PerlX::Maybe qw(maybe); # Used by the JSON-RPC server to convert incoming date fields apprpriately. use constant DATE_FIELDS => {}; @@ -37,6 +39,18 @@ sub login_exempt { return $class->LOGIN_EXEMPT->{$method}; } +sub wants_object { + my ($self) = @_; + return $self->{__wants_object} if $self->{__wants_object}; + my $params = Bugzilla->input_params; + my $wants = Bugzilla::WebService::Wants->new( + cache => Bugzilla->request_cache->{filter_wants} ||= {}, + maybe include_fields => $params->{include_fields}, + maybe exclude_fields => $params->{exclude_fields}, + ); + return $self->{__wants_object} = $wants; +} + 1; __END__ diff --git a/Bugzilla/WebService/Wants.pm b/Bugzilla/WebService/Wants.pm new file mode 100644 index 000000000..08d62253d --- /dev/null +++ b/Bugzilla/WebService/Wants.pm @@ -0,0 +1,131 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::WebService::Wants; +use 5.10.1; +use Moo; +use MooX::StrictConstructor; + +use Types::Standard qw(ArrayRef Str); +use List::MoreUtils qw(any none); + +has 'cache' => (is => 'ro', required => 1); +has ['exclude_fields', 'include_fields'] => + (is => 'ro', isa => ArrayRef [Str], default => sub { return [] }); +has ['include', 'exclude'] => (is => 'lazy'); +has ['include_type', 'exclude_type'] => (is => 'lazy'); + +sub _build_include { + my ($self) = @_; + return {map { $_ => 1 } grep { not m/^_/ } @{$self->include_fields}}; +} + +sub _build_exclude { + my ($self) = @_; + return {map { $_ => 1 } grep { not m/^_/ } @{$self->exclude_fields}}; +} + +sub _build_include_type { + my ($self) = @_; + my @include = @{$self->include_fields}; + if (@include) { + return {map { substr($_, 1) => 1 } grep {m/^_/} @include}; + } + else { + return {default => 1}; + } +} + +sub _build_exclude_type { + my ($self) = @_; + return {map { substr($_, 1) => 1 } grep {m/^_/} @{$self->exclude_fields}}; +} + +sub includes { + my ($self) = @_; + return keys %{$self->include}; +} + +sub excludes { + my ($self) = @_; + return keys %{$self->exclude}; +} + +sub include_types { + my ($self) = @_; + return keys %{$self->include_type}; +} + +sub exclude_types { + my ($self) = @_; + return keys %{$self->exclude_type}; +} + +sub is_empty { + my ($self) = @_; + return @{$self->include_fields} == 0 && @{$self->exclude_fields} == 0; +} + +sub is_specific { + my ($self) = @_; + return !$self->is_empty && !$self->exclude_types && !$self->include_types; +} + +sub match { + my ($self, $field, $types, $prefix) = @_; + + # Since this is operation is resource intensive, we will cache the results + # This assumes that $params->{*_fields} doesn't change between calls + my $cache = $self->cache; + $field = "${prefix}.${field}" if $prefix; + + my $include = $self->include; + my $exclude = $self->exclude; + + if (exists $cache->{$field}) { + return $cache->{$field}; + } + + # Mimic old behavior if no types provided + $types //= ['default']; + $types = [$types] if $types && !ref $types; + + # Explicit inclusion/exclusion + return $cache->{$field} = 0 if $exclude->{$field}; + return $cache->{$field} = 1 if $include->{$field}; + + my $include_type = $self->include_type; + my $exclude_type = $self->exclude_type; + + # If the user has asked to include all or exclude all + return $cache->{$field} = 0 if $exclude_type->{all}; + return $cache->{$field} = 1 if $include_type->{all}; + + # If the user has not asked for any fields specifically or if the user has asked + # for one or more of the field's types (and not excluded them) + foreach my $type (@$types) { + return $cache->{$field} = 0 if $exclude_type->{$type}; + return $cache->{$field} = 1 if $include_type->{$type}; + } + + my $wants = 0; + if ($prefix) { + + # Include the field if the parent is include (and this one is not excluded) + $wants = 1 if $include->{$prefix}; + } + else { + # We want to include this if one of the sub keys is included + my $key = $field . '.'; + my $len = length($key); + $wants = 1 if any { substr($_, 0, $len) eq $key } keys %$include; + } + + return $cache->{$field} = $wants; +} + +1; diff --git a/t/wants.t b/t/wants.t new file mode 100644 index 000000000..3413e8d99 --- /dev/null +++ b/t/wants.t @@ -0,0 +1,65 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. +use 5.10.1; +use strict; +use warnings; +use lib qw( . lib local/lib/perl5 ); + +use Test2::V0; +use ok 'Bugzilla::WebService::Wants'; + +my $empty = Bugzilla::WebService::Wants->new(cache => {}); +ok($empty->is_empty, "it is empty"); +ok(!$empty->is_specific, "no include/exclude is not specific"); + +my $non_empty_i + = Bugzilla::WebService::Wants->new(include_fields => ['foo'], cache => {}); +ok(!($non_empty_i->is_empty), "it is not empty with includes"); + +my $non_empty_e + = Bugzilla::WebService::Wants->new(exclude_fields => ['foo'], cache => {}); +ok(!($non_empty_e->is_empty), "it is not empty with excludes"); + +my $wants = Bugzilla::WebService::Wants->new( + exclude_fields => ['_extra'], + include_fields => ['_custom', '_default'], + cache => {} +); + +ok($wants->match('cf_last_resolved', ['custom', 'default']), 'cf_last_resolved is custom and default'); +ok(!$wants->match('triage_owner_id', ['extra']), 'triage owner is extra'); + +ok($wants->exclude_type->{extra}, "extra is excluded"); +ok($wants->include_type->{custom}, "custom is included"); + +$wants = Bugzilla::WebService::Wants->new( + exclude_fields => ['_custom'], + include_fields => ['cf_test'], + cache => {}, +); + +ok($wants->match('cf_test', ['default', 'custom']), "excludes are more specific"); + +$wants = Bugzilla::WebService::Wants->new( + exclude_fields => ['_default'], + include_fields => ['cf_test'], + cache => {}, +); + +ok(!$wants->is_specific, "not specific"); +ok($wants->match('cf_test', ['default', 'custom']), "excludes are more specific"); + +is([ $wants->includes ], ['cf_test']); + +$wants = Bugzilla::WebService::Wants->new( + exclude_fields => ['cf_evil'], + include_fields => ['cf_test'], + cache => {}, +); +ok($wants->is_specific, "has specific wants"); + +done_testing;