Skip to content

Fix status_code type#728

Merged
mblaney merged 1 commit intosimplepie:masterfrom
Alkarex:fix_status_code
May 18, 2022
Merged

Fix status_code type#728
mblaney merged 1 commit intosimplepie:masterfrom
Alkarex:fix_status_code

Conversation

@Alkarex
Copy link
Contributor

@Alkarex Alkarex commented Apr 25, 2022

$status_code is declared as int but was sometimes null or string, breaking some contracts.
Downstream: FreshRSS/FreshRSS#4301

`$status_code` is declared as `int` but was sometimes null or string.
Downstream: FreshRSS/FreshRSS#4301
@jtojnar
Copy link
Member

jtojnar commented Apr 25, 2022

Would not having it type int|null and fixing the consumers be cleaner?

@Alkarex
Copy link
Contributor Author

Alkarex commented Apr 25, 2022

Would not having it type int|null and fixing the consumers be cleaner?

@jtojnar That would change the contract, and my personal preference is towards simpler types rather than more complex types, but besides that I do not really mind. In the case of my client, it would add a bit of code to check the returned type and/or cast to int (null to 0) before sending it to the rest of the pipeline that is expecting an HTTP status to be an integer.

@jtojnar
Copy link
Member

jtojnar commented Apr 25, 2022

Yeah, it would change the contract, but so does changing empty string to zero. Technically, zero would be an okay choice since it is not actually a valid status code but I generally prefer more explicit types for representing exceptional states over magic values. I have not really looked into the API but I would expect any consumer to filter such states out so they would never make it further in the pipeline anyway.

@Alkarex
Copy link
Contributor Author

Alkarex commented Apr 25, 2022

I have not really looked into the API but I would expect any consumer to filter such states out so they would never make it further in the pipeline anyway.

That is the point of the contract. SimplePie has been promising an int, which simplifies the code downstream. Having to check for non-int removes most of the advantage of the contract.

I do not really mind, but I believe my current suggestion is marginally more efficient.

@jtojnar
Copy link
Member

jtojnar commented Apr 25, 2022

That is the point of the contract. SimplePie has been promising an int,

That really depends on what you consider the contract. Observing Hyrum's Law you could just as well say the contract is int|''.

The only thing that is certain is that there is a discrepancy between the phpdoc annotation and the implementation.

As for API design, I generally prefer having fewer uncertainties by making corner cases explicit over code that something that appears to be simpler but the complexity is still there. You will almost certainly still need to have something like if ($simplepie->status_code() === 0) { return; } to avoid passing invalid status code downstream and having the invalid value as a different type makes it harder to forget about it and then have the downstream function crashing the app.


Thinking about this more, this is actually a BC break so we might need to change the PHPDoc annotation to int|'' instead, and only fix this in an major bump, if SimplePie follows semver.

@Art4
Copy link
Contributor

Art4 commented Apr 25, 2022

Thinking about this more, this is actually a BC break so we might need to change the PHPDoc annotation to int|'' instead, and only fix this in an major bump, if SimplePie follows semver.

Strictly speaking, you are right. But the PHPDoc has always indicated that the value must be an integer. But that sometimes null or an empty string is set could be called a bug, which will be fixed now.

In the future you would avoid such bugs by defining the property type. But as long as we can't do that, I would work towards the simplified types.

@Alkarex
Copy link
Contributor Author

Alkarex commented Apr 25, 2022

You will almost certainly still need to have something like if ($simplepie->status_code() === 0) { return; } to avoid passing invalid status code downstream and having the invalid value as a different type makes it harder to forget about it and then have the downstream function crashing the app.

Just for the record, in the case of my client downstream, the only assumption was that I should get an int as per contract. A zero is perfectly fine and is not crashing anything (why should it?).

@jtojnar
Copy link
Member

jtojnar commented Apr 25, 2022

I would expect any function accepting int in lieu of HTTP status code to do basic sanity checks on it and throw an exception for obviously invalid ones. For example, I just checked the code of a random library and it does precisely that:

https://github.com/guzzle/psr7/blob/5c693242bede743c23402bc5b9de62da04a882d7/src/Response.php#L101

@Alkarex
Copy link
Contributor Author

Alkarex commented Apr 25, 2022

I would expect any function accepting int in lieu of HTTP status code to do basic sanity checks on it and throw an exception for obviously invalid ones. For example, I just checked the code of a random library and it does precisely that:

https://github.com/guzzle/psr7/blob/5c693242bede743c23402bc5b9de62da04a882d7/src/Response.php#L101

That is precisely going in the same direction than my argumentation, thanks :-)

$this->assertStatusCodeRange($status); calls a function that expects an integer (as it should)....

https://github.com/guzzle/psr7/blob/5c693242bede743c23402bc5b9de62da04a882d7/src/Response.php#L154

So such a code might currently be breaking in some cases, before this PR.

@jtojnar
Copy link
Member

jtojnar commented Apr 25, 2022

Right, the typehint of the $status argument of the Response constructor only accepts int, which means you would not be able to pass it '' or null. That would force you to handle invalid state before even constructing the Response object, which is IMO the clean code approach. Additionally, it would make static analysis tools like PHPStan be easily able to point out possible runtime crashes. Whereas when using a magic int value, it is much easier to forget to treat that edge case.

@Art4 Art4 mentioned this pull request May 17, 2022
14 tasks
@mblaney
Copy link
Member

mblaney commented May 18, 2022

I think this is clear enough, if you want to propose a change @jtojnar I'm happy to look at it but will to merge this for now.

@mblaney mblaney merged commit 81345a3 into simplepie:master May 18, 2022
jtojnar added a commit to jtojnar/simplepie that referenced this pull request May 18, 2022
This is to distinguish a status_code being set on the type level,
making it harder to forget the case – static analysis would notify you.

This is technically a BC break but so is simplepie#728
and `null` behaves just like `0` from the perspective of non-strict comparison
so I would not expect issues in legacy applications.
@jtojnar
Copy link
Member

jtojnar commented May 18, 2022

I opened #732

@Alkarex Alkarex deleted the fix_status_code branch May 18, 2022 07:33
jtojnar added a commit to jtojnar/simplepie that referenced this pull request May 18, 2022
This is to distinguish a status_code being set on the type level,
making it harder to forget the case – static analysis would notify you.

This is technically a BC break but so is simplepie#728
and `null` behaves just like `0` from the perspective of non-strict comparison
so I would not expect issues in legacy applications.
jtojnar added a commit to jtojnar/simplepie that referenced this pull request May 19, 2022
This is to distinguish a status_code being set on the type level,
making it harder to forget the case – static analysis would notify you.

To make it more consistent, we also change the few cases that could make
`status_code` contain a `0` to replace it with `null`:

 - `CURLINFO_HTTP_CODE` can contain `0`, but only when the request fails
   with an error such as `CURLE_COULDNT_RESOLVE_HOST`, in which case
   the File constructor will fail.
 - HTTP parser, which could return `0` when a web server decides to
   return it as a status code for some reason.
   I added a code that will fail for `0` status code
   (though other invalid HTTP response codes may still appear successful).
 - Successful local requests were already using `null` correctly.

While at it, simplify few conditionals using de Morgan’s laws
for easier reasoning.

This is technically a BC break but so is simplepie#728
and `null` behaves just like `0` from the perspective of non-strict comparison
so I would not expect issues in legacy applications.
jtojnar added a commit to jtojnar/simplepie that referenced this pull request May 19, 2022
This is to distinguish a status_code being set on the type level,
making it harder to forget the case – static analysis would notify you.

To make it more consistent, we also change the few cases that could make
`status_code` contain a `0` to replace it with `null`:

 - `CURLINFO_HTTP_CODE` can contain `0`, but only when the request fails
   with an error such as `CURLE_COULDNT_RESOLVE_HOST`, in which case
   the File constructor will fail.
 - HTTP parser, which could return `0` when a web server decides to
   return it as a status code for some reason.
   I added a code that will fail for `0` status code
   (though other invalid HTTP response codes may still appear successful).
 - Successful local requests were already using `null` correctly.

While at it, simplify few conditionals using de Morgan’s laws
for easier reasoning.

This is technically a BC break but so is simplepie#728
and `null` behaves just like `0` from the perspective of non-strict comparison
so I would not expect issues in legacy applications.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants