-
-
Notifications
You must be signed in to change notification settings - Fork 415
Accept multiple content-types in servant-client. #552
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
Accept multiple content-types in servant-client. #552
Conversation
If only we could say pick some (i.e. at least one) of This makes APIs unusable where is e.g. |
@phadej: I'm not quite sure I understand what your routes look like. Is it something like this?
In this example, As long as you have ...hmm, maybe you're saying that you don't have a Let me give my reason for wanting this change. I'm running into the problem described in #292. I need to deal with an API that's randomly sending back responses in one of two content types. The way servant-client currently works, it is failing on one of the two content types, and throwing a |
Yes
It doesn't respect I could use a |
When defining a route like the following, ``` type API = Get '[JSON, PlainText] Int ``` servant-client is only able to receive responses with the content type that comes first in the list. In this example, it will only be able to receive `application/json` responses. This PR changes it so that servant-client will accept _any_ content-type in the list. In the example above, it will accept responses with a content type of `application/json` or `text/plain`.
9ba5cf3
to
2b737e4
Compare
In commit 2b737e4, I updated this PR to get rid of the conflict with |
@phadej Note that such a filtering thing could be useful in general and maybe even added to servant. I have done this in the past, you need it now as well, so it may be useful to other people out there. @cdepillabout Ideally I'd like to have the client pick the content type and only make the user "pay" (i.e have MimeUnrender instances) for the content types used that way. However, I know about all the nasty apps out there that don't necessarily respect the |
@alpmestan the problem with type families, is that one have to do tricks to make higher order ones. E.g.
Now we have to make tailor made type family for each case. Luckily |
So I don't think we should change the default behavior (because of the problem @phadej mentioned of this requiring more instances than necessary). But we could have a variant of What does everyone think? |
we can definitely give this a shot and see how useful it is to people. |
I also wouldn't want to change the default behavior of @cdepillabout: Having a function |
@soenkehahn: Okay, I'll give it a shot! |
Okay, I whipped something together. This is building on my original PR. The client :: HasClient api => Proxy api -> Client api
client p = clientWithRoute p defReq
class HasClient api where
type Client api :: *
clientWithRoute :: Proxy api -> Req -> Client api I changed type family FirstCT cts :: [*] where
FirstCT '[] = '[]
FirstCT (ct ': cts) = '[ct]
type family FirstCTVerb x :: * where
FirstCTVerb (Verb method statusCode cts r) = (Verb method statusCode (FirstCT cts) r)
FirstCTVerb (a :> b) = (a :> FirstCTVerb b)
FirstCTVerb (a :<|> b) = (FirstCTVerb a :<|> FirstCTVerb b)
FirstCTVerb a = a
client
:: forall api . HasClient (FirstCTVerb api)
=> Proxy api -> Client (FirstCTVerb api)
client _ = clientWithRoute apiWithOnlyFirstCT defReq
where
apiWithOnlyFirstCT :: Proxy (FirstCTVerb api)
apiWithOnlyFirstCT = Proxy The I also added a type family ReplaceCTs api (newCTs :: [*]) :: * where
ReplaceCTs (Verb method statusCode oldCTs r) newCTs = (Verb method statusCode newCTs r)
ReplaceCTs (a :> b) newCTs = (a :> ReplaceCTs b newCTs)
ReplaceCTs (a :<|> b) newCTs = (ReplaceCTs a newCTs :<|> ReplaceCTs b newCTs)
ReplaceCTs a newCTs = a
clientWithCTs
:: forall api cts . (HasClient (ReplaceCTs api cts))
=> Proxy cts -> Proxy api -> Client (ReplaceCTs api cts)
clientWithCTs _ _ = clientWithRoute replacedCTs defReq
where
replacedCTs :: Proxy (ReplaceCTs api cts)
replacedCTs = Proxy The downside of this new What does everyone think of this? |
I think it'd be nice if we didn't have to change |
@soenkehahn: Thanks for the feedback! I was trying to flesh out the API and get everyone's opinions, so I didn't mess with the tests yet. I didn't want to rewrite all the tests if my API suggestions were going to get shot down (which I expected to happen ;-)) I agree that it would be better if The previous suggestion from you (@soenkehahn) and @jkarni was to have a new function
The problem with this is that I couldn't figure out what type to make I ended up doing something similar with Can you think of a better way to write I am basing the above changes on my original pull request, which forces all the content-types to actually be receivable from the server. Maybe you're saying that this change in the receivable content-types shouldn't happen either? The problem is that without this change, I couldn't figure out a way to write |
@cdepillabout: Sorry for the delay. To me it looks like your What about something like this: https://gist.github.com/b94c48821d6463e11c170cc6cfbd2a8e? This would allow you to specify your wanted content-type, while still being completely safe. The current |
@soenkehahn Thanks for the reply! I'm currently at ICFP this week, so you'll have to give a week or so to look over your code! |
@soenkehahn Okay, I finally got a chance to sit down a look at your code.
This is correct.
So, I'm facing the same problem as #292. I'm dealing with a server that forces requests to have a content type of My PR allows the user to specify which content types they want to be able to receive. However, it complicates the types in the I can understand if this is a rather specific problem, and you don't want to complicate servant to be able to deal with it. |
Yeah, I think we definitely don't want to complicate the use-case of talking to standard-compliant servers. On the other hand we should try to follow the Robustness Principle. So ideally
@cdepillabout: Would this solve your problem? Are you still interested in working on this? What's the problem in implementing this without changing the type of |
@soenkehahn Thanks for the reply. I think I'll be able to use #614 and #615 to solve my problem. It seems like a better way than to this PR (#552). I'll close this PR. |
When defining a route like the following,
servant-client is only able to receive responses with the content type
that comes first in the list. In this example, it will only be able to
receive
application/json
responses.This PR changes it so that servant-client will accept any content-type
in the list.
In the example above, it will accept responses with a content type of
application/json
ortext/plain
.