Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set response correctly for json API responses #3545

Merged
merged 7 commits into from
Nov 18, 2020
Merged

Set response correctly for json API responses #3545

merged 7 commits into from
Nov 18, 2020

Conversation

dcaswel
Copy link
Contributor

@dcaswel dcaswel commented Aug 26, 2020

fixes #3079

Description
In the CodeIgniter\API\ResponseTrait it defaults the format to json and everything is treated as a json but when it goes to send the response, it uses the setBody function in the response object. The problem with this is that it never sets the bodyFormat correctly in the response object and that causes issues when running tests and trying to assert different things about the json because the response json is not encoded correctly. This change makes sure that the bodyFormat will be set correctly by using setJSON function when the format is json.

@dcaswel
Copy link
Contributor Author

dcaswel commented Aug 26, 2020

The errors being given by the Static Analysis Check are not very descriptive. If anyone can help with that it would be appreciated.

@samsonasik
Copy link
Member

@caswell-wc you need to fix travis build first https://travis-ci.org/github/codeigniter4/CodeIgniter4/jobs/721516405 because your change made different tests expectation. That's the first priority.

There were 3 failures:

1) CodeIgniter\API\ResponseTraitTest::testRespondSets404WithNoData

Failed asserting that 'null' is null.

/home/travis/build/codeigniter4/CodeIgniter4/tests/system/API/ResponseTraitTest.php:174

2) CodeIgniter\API\ResponseTraitTest::testRespondSetsStatusWithEmptyData

Failed asserting that 'null' is null.

/home/travis/build/codeigniter4/CodeIgniter4/tests/system/API/ResponseTraitTest.php:183

3) CodeIgniter\API\ResponseTraitTest::testRespondSetsCorrectBodyAndStatus

Failed asserting that 'application/json; charset=UTF-8' starts with "text/html".

/home/travis/build/codeigniter4/CodeIgniter4/tests/system/API/ResponseTraitTest.php:193

For phpstan, it seems your change cause ignored error can be removed:

  1. you can remove // @phpstan-ignore-line at system/HTTP/Response.php line 902
  2. you can remove // @phpstan-ignore-line at system/I18n/Time.php line 560

@samsonasik
Copy link
Member

I created PR #3547 for phpstan notice. You can focus on tests.

@samsonasik
Copy link
Member

please rebase latest develop

Copy link
Member

@michalsn michalsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need some tests for this.

system/API/ResponseTrait.php Outdated Show resolved Hide resolved
@MGatner
Copy link
Member

MGatner commented Aug 27, 2020

@jim-parry 's original intent was for this to be handled by a Format Filter, see #2185. I will leave it up to the team if that still seems worth doing, but I'm afraid this PR would preclude that functionality for the ReaponseTrait.

@MGatner
Copy link
Member

MGatner commented Aug 27, 2020

Actually looking at this change I'm afraid this would constitute a breaking change. I realize there is some expectation that ResponseTrait would handle the formatting for you, hence considering this a bug, but Jim was clear that was not the intent. Changing this would break current REST implementations using their own formatters, unless they were already checking for JSON/XML.

@dcaswel
Copy link
Contributor Author

dcaswel commented Aug 28, 2020

@MGatner I think I understand what you are saying about the breaking change. The real issue that I am trying to resolve here, as explained in #3079 is that if the body is formatted as json, it never actually sets Response::bodyFormat to 'json'. It stays as html. Because it never gets changed it ends up json_encoding the body of the response twice before the assertJSONFragment can json_decode it. I researched this when I first created the issue and found that if the bodyFormat gets updated correctly, it solves the problem which is why I used the setJSON function because it also sets the bodyFormat. The other option that I thought of was to create a setter on the Response object for bodyFormat and then call that in the ResponseTrait::format method when it is setting the content type on the response. Is that a more viable solution?

@samsonasik
Copy link
Member

samsonasik commented Aug 28, 2020

You need rebase instead of merge. Your commits history seems already ruined now, you can do like the following:

git checkout -b temp
git checkout json-api-response
git reset --hard HEAD~52
git checkout develop

# pull latest develop via git url
git pull [email protected]:codeigniter4/CodeIgniter4.git develop

# or pull latest develop via https url
git pull https://github.com/codeigniter4/CodeIgniter4.git develop

git checkout json-api-response
git  cherry-pick 8b4e5dcbf4028f0e07d2eafe7ace92d97cd943a1
git  cherry-pick d4e307cff53eb81fc887e9761c6c61585c15713e
git  cherry-pick 3ea8a08de3e599dee38b54ea35627a85d0a39b3d
git  cherry-pick 811c17d86ee30fcf36e12a1488a4f6960ff31fd4

# finally, force push
git push origin json-api-response

Next time, you need to rebase instead, I created a tutorial for clean rebase at https://samsonasik.wordpress.com/2015/09/16/practical-git-4-rebasing-conflicted-task-branch-against-primary-branch/

@MGatner
Copy link
Member

MGatner commented Aug 28, 2020

Definitely implement the rebase samsonasik outlined above.

I think I see your point now, the issue is not the formatting of the body but rather notifying the response of the format type. I will have to look at this a bit more, or hear feedback from others. Jim had this all figured out but I'm not nearly as adept at RESTful implementations as he was. I think his goal was to have the actual Controller to be "format agnostic" and have the Filter handle the formatting (unless overridden). This does seem to be in tension with the current ResponseTrait as it handles formatting currently. Maybe @lonnieezell can help.

@dcaswel
Copy link
Contributor Author

dcaswel commented Aug 28, 2020

I actually thought I had rebased it but I ran into issues along the way and when I look back at it, I think I know where I went wrong and it ended up merging instead. Once we figure out the best path to accomplish what we are trying to do, I will either fix that or I may just close this PR and create a new one.

@MGatner
Copy link
Member

MGatner commented Aug 29, 2020

Sounds good! Thank you for bringing this up - we'll get a little more discussion going and figure it out.

@MGatner MGatner mentioned this pull request Aug 30, 2020
5 tasks
@lonnieezell
Copy link
Member

I originally had a vision of being able to have the controller negotiate the correct content type with the client in a little utopian vision once myself. IIRC, playing with some API generation proved it didn't work out as well as hoped, as utopias often don't. :)

I prefer to allow the developer to explicitly declare the output format. The ResponseTrait seems like a good place to do that. However, we would have to provide for all supported output formats (which is only XML and JSON at this time) to be set, not just JSON.

My other concern with the filter Jim recommended was the possibility of conversion errors across the different formats and back. It might not be a big deal but I'm concerned the number of edge cases would be a pain. Especially for a feature where 90% of the use case is going to be to spit out JSON anyway lol.

@dcaswel
Copy link
Contributor Author

dcaswel commented Aug 31, 2020

@lonnieezell So are you thinking that we stick with the change in this PR but also add logic to use the Response::setXML when the format is 'xml'?

@dcaswel
Copy link
Contributor Author

dcaswel commented Oct 17, 2020

@lonnieezell @MGatner Do either of you have anymore thoughts about this. I can see 2 options.

  1. We could adjust the respond method as I have done here so that it uses Response::setJson() if the ResponseTrait::format is 'json' or Response::setXml() if the ResponseTrait::format is 'xml'.

  2. Create a setter on Response that would allow for setting Response::bodyFormat. Then either in ResponseTrait::format() or ResponseTrait::respond(), we use that setter to make sure the Response::bodyFormat is the same as ResponseTrait::format.

I like the second option better because it is allowing the ResponseTrait to format the response instead of needlessly sending the formatted response through another format method which of course gets ignored because it's already formatted. The only downside I see to the second option is that it does expose to the developer the ability to set the bodyFormat. I am not aware of a problem with this but you guys know the system better then I do.

@MGatner
Copy link
Member

MGatner commented Oct 19, 2020

@caswell-wc Could you rebase first? I'm trying to look back at my notes on this and the proposed changes but they are hard to see with all the intermediary cruft. Let me know if you need help with that command sequence - I think @samsonasik also has a handy blog on the subject.

@MGatner
Copy link
Member

MGatner commented Nov 5, 2020

Bump!

We were getting an error when running running setStatusCode on the response directly. This puts it back where it was before.
…tting it as json. Modified the format method so it sets the format to hmtl if the data is a string.
@dcaswel
Copy link
Contributor Author

dcaswel commented Nov 8, 2020

@MGatner Sorry about the confusion on this. I have been having a lot of issues with this rebase stuff. I'm pretty new to it. Anyway, I think I got it now. Please let me know if I still need to do something with it.

@MGatner
Copy link
Member

MGatner commented Nov 8, 2020

Yes looks much better! Thank you.

I think with 4.1 around the corner we do take the easy route to try to get this done.

We could adjust the respond method as I have done here so that it uses Response::setJson() if the ResponseTrait::format is 'json' or Response::setXml() if the ResponseTrait::format is 'xml'.

Let's do this for now (add XML support) and then if we need another pass at the handling later we can revisit.

…rmat is xml and added tests for the setJSON functionality.
Copy link
Member

@MGatner MGatner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! Looks good. Thanks so much. Anyone else? @samsonasik ?

Copy link
Member

@paulbalandan paulbalandan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make these returns as one liners?

system/API/ResponseTrait.php Outdated Show resolved Hide resolved
system/API/ResponseTrait.php Outdated Show resolved Hide resolved
system/API/ResponseTrait.php Outdated Show resolved Hide resolved
@paulbalandan paulbalandan merged commit 868438b into codeigniter4:develop Nov 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: Using assertJSONFragment with respond() in ResponseTrait
6 participants