I'm populating a web service with form values for an address. A lot of times, there isn't a Street 2 value on the form. Is there an easy way to avoid getting the error: 'The provided argument for parameter {foo} is empty'?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

heddn’s picture

Title: Ability to flag parameters as optional » The provided argument for parameter is empty
Category: feature » support
Status: Needs review » Active
FileSize
27.73 KB

Attached is a screenshot of the data type created from the WSDL. Notice there is a street 2 text field. WSClient errors out when I don't at least put a space in the street 2 parameter. However, my webservice doesn't like an empty space in street 2 and errors on address validation.

heddn’s picture

Title: The provided argument for parameter is empty » Ability to flag parameters as optional
Category: support » feature

The reason this happens is because all parameters for wsclient seem to be created as required. The error comes from rules.core.inc:

// Make sure the given value is set if required (default).
    if (!isset($arg) && empty($info['allow null'])) {
      throw new RulesEvaluationException('The provided argument for parameter %name is empty.', array('%name' => $name), $this);
    }

I'm converting this into a feature request. Let's allow parameters to be marked as optional.

dman’s picture

Looks like that would be the equivalent of WSDL schema declaring minOccurs="0". It's part of the spec, so the client should be able to support that fine.
It's nice when there are formal examples to copy from!

I don't know if it's relevant, but the formal schema seems to care about the difference between an element not existing, and an element existing but being empty. Both those (different) cases are captured by the snippet above and treated the same there.
http://www.dimuthu.org/blog/2008/08/18/xml-schema-nillabletrue-vs-minocc...

heddn’s picture

Status: Active » Needs review
FileSize
2.89 KB

The attached patch also requires a patch to rules core. See #2050225: Parameters to data need the ability to allow NULL values.

It allows NULL values as parameters into data types (hook_rules_data_info). This allows optional values i.e. minOccurs="0". I originally also added logic for the web service operations to allow null values. However, the SOAP spec specifically doesn't allow them and a quick check of REST lead me to be believe it doesn't either. If that is not true, then its a simple step to re-add that logic as it was a change to data exposed from hook_rules_action_info(), requires no rules core changes and doesn't hurt SOAP (it's simply unnecessary).

heddn’s picture

With #2050225: Parameters to data need the ability to allow NULL values landing, this can get cleanly reviewed now.

Status: Active » Needs review
dman’s picture

I suspect a little that the issue I'm facing today is different from the one you are looking at with SOAP, but I've found that when trying to talk REST to some Google APIs
https://developers.google.com/freebase/v1/topic
then I can't allow my service descriptions in the WSClient version of Freebase API to set the unused 'optional' parameters to empty strings, or Google will complain.
EG, if the dateline = "" then the API complains that it isn't a valid date.

Here is what I'm doing about it right now: If the param is optional, and is empty, then don't send it

With this patch in place, all the Google EAT APIs are available to me now.

There remains a small issue of 'what if you want to set an 'optional' parameter to zero. And I'm not sure if my solution tromps on that. But it should be legal for an invocation to have all the optional params isset(), but sum of them set to NULL and ignored ... I think.

It's tricky in the same way this OP is tricky. so I'm dropping it into this issue.

othermachines’s picture

Hi, @dman,

Thanks for all your great work on this.

I've also come across the problem addressed in #7. The (RESTful) Podio API also doesn't like getting empty parameters. Unfortunately the patch didn't work for me because - and I'm not certain if this is something I've boned up on my end, but I don't think so - $arguments is never keyed by the parameter name. What this means is that, after I apply your patch, in the first foreach loop, $info['optional'] && empty($arguments[$param]) will pass for every optional parameter regardless of its value (or whether it has one). What I end up with is an empty $named_arguments array and no parameters.

I'm not entirely certain this is the right way to go, but below is a version of the function that works for me. I reverted back to the original code and then added a single foreach loop. It essentially waits until $named_arguments is all nicely filled out and only then plucks out the empty values.

** Edited: for clarity
** Edited (2014-04-12): Needs an isset()

  public function invoke($operation, array $arguments) {
     
    // ... 
    // ... snipped
    // ...
 
    // Fill in global parameters.
    foreach ($this->global_parameters as $param => $info) {
      if (!isset($named_arguments[$param])) {
        $named_arguments[$param] = $info['default value'];
      }
    }
 
    // Added:
    // Unset any optional parameters with empty values.
    if (isset($this->operations[$operation]['parameter'])) {
      foreach ($this->operations[$operation]['parameter'] as $param => $info) {
        if ($info['optional'] && empty($named_arguments[$param])) {
          unset($named_arguments[$param]);
        }
      }
    }
 
    return $this->endpoint()->call($operation, $named_arguments);
  }

Hope that helps. Cheers -

othermachines’s picture

Patch in #4 applies cleanly and works as advertised.

I'm wondering if it might not be a bad idea to provide the option for operations as well. REST has no official spec so I'd be surprised if devs aren't taking liberties there. Also I think this would bring us closer to a more elegant solution to dealing with outgoing 0/empty/NULL parameters (as discussed in #7-8).

@heddn, if you agree I'd be eager to review the new patch. I'm really stuck on the outgoing params issue and it's only logical to get this change committed first.

Thanks -

heddn’s picture

@othermachines, I've moved on from that project but if I recall, its simply a matter of copy/pasting what was done with parameters into operations.

othermachines’s picture

Thanks, @heddn. The attached patch includes:

  • UI setting 'Allow Null' for data type properties (from @heddn's patch in #4);

  • UI setting 'Allow Null' for operation parameters;

  • The following addition to invoke() method in WSClientServiceDescription class:

        // Unless explicitly permitted by 'allow null', unset optional parameters
        // with NULL or empty ('') values.
        if (isset($this->operations[$operation]['parameter'])) {
          foreach ($this->operations[$operation]['parameter'] as $param => $info) {
            if ((isset($info['optional']) && $info['optional']) && (!isset($info['allow null']) || !$info['allow null']) && ($named_arguments[$param] === '' || !isset($named_arguments[$param]))) {
              unset($named_arguments[$param]);
            }
          }
        }
    

    (0 and '0' will not be unset.)

It passes all my tests but, again, if someone's got a better way to deal with the params that go out, I'd love to hear it. The code is unusable for me unless I can get rid of the superfluous empty and NULL params.

Cheers -

basvredeling’s picture

I've closed #2286265: Remove empty parameters from service requests in favor of this approach. Making NULL values configurable.
If we make the NULL values a "per-parameter" option the solution below won't work. We could consider making it service-wide (the api either accepts null values completely, or it doesn't). I can't envision a service which allows a mix of both really.

$named_arguments = array_filter($named_arguments, 'strlen');
dman’s picture

Status: Needs review » Reviewed & tested by the community

I found the rationale, UI, and tested behaviour in patch #11 to be right on the money.
Running it locally gives desired results naturally, and it looks backwards-compatible.

In testing, the need for the 'required' flag threw me a little, but that was me.

I believe the truth table now is:

Input/Flags Not Required, not Send Null Required, not Send Null Not Required, Send Null Required, Send Null
"String value" "String value" "String value" "String value" "String value"
null (not sent) (not sent) (not sent) ""
"" (not sent) (not sent) (not sent) ""

* If there is a value, it is always sent.
* if the API is called with the parameter not set, it will not be sent unless it's 'required'
* if called with empty strings, they will NOT be sent unless it's both 'required' and 'send null' is set.
Importantly, even if 'send null' is set, an empty string will not be sent unless it's also required.

I've seen enough wacky web services now to be in favour of the per-parameter UII options, rather than a global per-service flag. (I'm working on Google APIs again today)

I've found the patch from @othermachines to apply clean still, be tidy and only touch the bit of the code it needs to. I'd be happy to put it in, but will wait for either any other thumbs up or if it's still stalled by the time I finish my current project interest I'll commit it myself.

(I only revisit this topic when I get to do academic work as opposed to commercial, so my activity here is periodic only. Sorry we've not been keeping on top of the issues very quickly)

basvredeling’s picture

sounds good

dman’s picture

Status: Reviewed & tested by the community » Fixed

committed and pushed
It's now in the -dev branch, will be in the next stable, whenever that is. I think there is some REST2 and Authentication stuff pending that should be rolled in before the next point release.

Thanks everyone for the input and reviews!

othermachines’s picture

Awesome. I've since been sidetracked by other things, but may have to come back and refresh now that things are moving forward again. Thanks -

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.