Page MenuHomePhabricator

Special:ApiSandbox treats a boolean API type as a checkbox type
Open, Needs TriagePublicBUG REPORT

Assigned To
None
Authored By
Dreamy_Jazz
Dec 13 2022, 12:51 AM
Referenced Files
F35860861: image.png
Dec 13 2022, 2:59 AM
F35860857: image.png
Dec 13 2022, 2:58 AM
F35860852: image.png
Dec 13 2022, 2:56 AM
F35860854: image.png
Dec 13 2022, 2:56 AM

Description

Steps to replicate the issue (include links if applicable):

  • Load any API in Special:ApiSandbox that has a boolean type
  • Run the API query
  • Look at the JSON output

What happens?:
The JSON output has no item there if the boolean was set to false, and has the value of "1" if it was set to true.

What should have happened instead?:
When set to false the boolean parameter should have been provided with a false like value. There should also be the option to exclude providing the parameter, like there is for the text input, by using a checkbox.

Software version (skip for WMF-hosted wikis like Wikipedia):
Core master branch

Other information (browser name/version, screenshots, etc.):
The used information (i.e. 1 for true and nothing for false) is what should happen when the type is checkbox. The boolean type has a important difference that the absence of a value is invalid. Either false or true like values must be provided.

Event Timeline

https://www.mediawiki.org/wiki/API:JSON_version_2#Changes_to_JSON_output_format

Return booleans as boolean true instead of empty-string. Where appropriate,[note 1] return boolean false instead of omitting the property.

^ It sounds a lot like what you're describing is the ApiSandbox is using formatversion=1 still...

Checked that and it doesn't seem to be the case as the above suggests that it would be an empty string (and not the string with 1).

Example after checking out https://gerrit.wikimedia.org/r/c/mediawiki/extensions/MassMessage/+/867298.

API parameter of boolean as defined in the code:

image.png (118×652 px, 14 KB)

That parameter in the ApiSandbox:

image.png (190×1 px, 21 KB)

The resulting output:

image.png (197×686 px, 26 KB)

The resulting output for true:

image.png (202×726 px, 21 KB)

Since https://gerrit.wikimedia.org/r/c/mediawiki/core/+/842531 the special page always request json with format version 2.

But it seems you are looking not at the output (where format version is for) of the api, you are looking at the "Show request data as: JSON" on the Result page of Special:ApiSandbox to get "Request JSON", the request parameter in json format.

But according to https://www.mediawiki.org/w/api.php#main.2Fdatatypes you have to use "present"/"absent" to define a boolean in the api request.

boolean
Boolean parameters work like HTML checkboxes: if the parameter is specified, regardless of value, it is considered true. For a false value, omit the parameter entirely.


You should not post tokens in public, I would assume thta is from a dev-wiki, so that a security thing, but it is better to not post them at all.

You should not post tokens in public, I would assume thta is from a dev-wiki, so that a security thing, but it is better to not post them at all.

Yeah. These tokens are from a localhost dev wiki with access just to my PC as far as I am aware, so it should be okay.

But according to https://www.mediawiki.org/w/api.php#main.2Fdatatypes you have to use "present"/"absent" to define a boolean in the api request.

boolean
Boolean parameters work like HTML checkboxes: if the parameter is specified, regardless of value, it is considered true. For a false value, omit the parameter entirely.

The PHP code doesn't seem to agree with this documentation. Here is an extract from the ParamValidator class:

public static $STANDARD_TYPES = [
		'boolean' => [ 'class' => TypeDef\BooleanDef::class ],
		'checkbox' => [ 'class' => TypeDef\PresenceBooleanDef::class ],

The PresenceBooleanDef class gives true if the request parameter key exists and false if it doesn't. BooleanDef gives false for specific values and true for specific values.

BooleanDef which recognises true values as:

public static $TRUEVALS = [ 'true', 't', 'yes', 'y', 'on', '1' ];

and false values of:

public static $FALSEVALS = [ 'false', 'f', 'no', 'n', 'off', '0' ];

BooleanDef also matches the empty string and null as false (not defined in the list but is in an OR condition after the value is converted using strtolower). However, testing and a read of the code suggests that ParamValidator::getValue before validating the type will, if there is a default defined by ParamValidator::DEFAULT_VALUE, set the value to be the default value. This means that when using the ApiSandbox, even if the user intentionally provides false, the default value will override it. Because this code to use the default chooses the default if the value is null, it seems that PresenceBooleanDef will also have a problem as it returns the value of null if the parameter is not present to ParamValidator::getValue.

Also in this case for T191044 the default I want is for there to be no change (i.e. the value is kept as null if it was not provided), so the type used needs to have three possible values (i.e. change to false, change to true or keep the same). As such being able to use the boolean type how it is defined in the PHP code would be ideal. However, I don't want to do that if the ApiSandbox won't understand how to deal with the boolean type in this way.

$TRUEVALS and $FALSEVALS are part of the class BooleanDef extends TypeDef, while the class PresenceBooleanDef extends TypeDef is just using (bool) cast.

The ApiParamValidator class defines the presence boolean type as relevant, while the Rest-API seems to use the other type (and defines a checkbox type to act like the presence boolean type).

I have no idea what to do to support the BooleanDef in Action-API (with relevant doc and param support in the base class and on the special page) and if that should be supported as well.
The current use of the PresenceBooleanDef should never give a null return and cannot support three-state-boolean.

When three values are needed, just use three values and call the default one nochange or such. That also helps in defining help messages

....
The current use of the PresenceBooleanDef should never give a null return and cannot support three-state-boolean.
]...

See extract from PresenceBooleanDef::getValue:

public function getValue( $name, array $settings, array $options ) {
	return $this->callbacks->hasParam( $name, $options ) ? true : null;
}

Null is returned to ParamValidator::getValue line 525 if hasParam returns false (which happens when there is no parameter). Because, if there is no param, the value is null as returned by PresenceBooleanDef::getValue it means that ParamValidator::getValue line 527 which is $value !== null will always be false when no parameter is provided which means that } elseif ( isset( $settings[self::PARAM_DEFAULT] ) ) { is checked (will be true if there is default). Therefore, if a default is set it will always be used. If the default is not false, then this means false cannot be specified.

Having a default for PresenceBooleanDef seems odd and counter-intuitive, as the parameter being missing means false by definition. Ideally setting a default for this should raise a warning and/or do nothing.

You are definitely right that null will never be it's value once the value has been returned by ParamValidator::getValue, but it has a problem with the default as listed above.

...

When three values are needed, just use three values and call the default one nochange or such. That also helps in defining help messages

Good idea on that. I'll use that as it is much clearer. Thanks for the suggestion.

Having a default for PresenceBooleanDef seems odd and counter-intuitive, as the parameter being missing means false by definition. Ideally setting a default for this should raise a warning and/or do nothing.

In theory, the fix for this could be if 'is-default' is specified as a option, then the validate method always returns false. This is because if the default is selected by the code, the parameter was missing and therefore should be false by definition.

....
The current use of the PresenceBooleanDef should never give a null return and cannot support three-state-boolean.
]...

See extract from PresenceBooleanDef::getValue:

public function getValue( $name, array $settings, array $options ) {
	return $this->callbacks->hasParam( $name, $options ) ? true : null;
}

Null is returned to ParamValidator::getValue line 525 if hasParam returns false (which happens when there is no parameter). Because, if there is no param, the value is null as returned by PresenceBooleanDef::getValue it means that ParamValidator::getValue line 527 which is $value !== null will always be false when no parameter is provided which means that } elseif ( isset( $settings[self::PARAM_DEFAULT] ) ) { is checked (will be true if there is default). Therefore, if a default is set it will always be used. If the default is not false, then this means false cannot be specified.

Having a default for PresenceBooleanDef seems odd and counter-intuitive, as the parameter being missing means false by definition. Ideally setting a default for this should raise a warning and/or do nothing.

You are definitely right that null will never be it's value once the value has been returned by ParamValidator::getValue, but it has a problem with the default as listed above.

TypeDef::getValue is documented to "Return null if the value wasn't present". PresenceBooleanDef::normalizeSettings sets the default of false when no is given and PresenceBooleanDef::checkSettings is checking if the default is false and would create a error message when not. From the code the unexpected default is already handled. Having the different between present and absent also helps to detect use of deprecated parameters (line 538).

...

When three values are needed, just use three values and call the default one nochange or such. That also helps in defining help messages

Good idea on that. I'll use that as it is much clearer. Thanks for the suggestion.

Just to explain a bit more. The concept of null/true/false is very technical and well known by a php developer. But in other programming languages this must not be known or possible to be usable. The Action-API can be triggered with any languages, so it should be easy to use. It also possible to be used by non-developer not aware of such technical concepts. In my opinion named option are easy to use when not only need a boolean flag.

Ah. I did not notice that was how the settings were checked. Thanks for the explanation, and apologies for missing that part.

I think the issue behind this task still stands? If not, then feel free to close.