]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1512815 - add object that encapsulates the include/exclude logic
authorDylan William Hardison <dylan@hardison.net>
Wed, 23 Jan 2019 20:39:00 +0000 (15:39 -0500)
committerGitHub <noreply@github.com>
Wed, 23 Jan 2019 20:39:00 +0000 (15:39 -0500)
Bugzilla/WebService.pm
Bugzilla/WebService/Wants.pm [new file with mode: 0644]
t/wants.t [new file with mode: 0644]

index da0fbfd32cd5f986f994e8697ebfabe3e8ddde2c..eb6db20816f6d329802d26cf64136c44b28cec4e 100644 (file)
@@ -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 (file)
index 0000000..08d6225
--- /dev/null
@@ -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 (file)
index 0000000..3413e8d
--- /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;