elixir-tesla / tesla

The flexible HTTP client library for Elixir, with support for middleware and multiple adapters.
MIT License
2k stars 340 forks source link

Content type should be case insensitive #680

Closed greg-freewave closed 3 months ago

greg-freewave commented 3 months ago

When consuming a json api using tesla, the request body wasn't being correctly decoded as JSON. This is because the server was returning a content type of application/JSON but tesla expects content type be all lower case: https://github.com/elixir-tesla/tesla/blob/master/lib/tesla/middleware/json.ex#L48

According to the rfc https://datatracker.ietf.org/doc/html/rfc2045#section-2

All media type values, subtype values, and parameter names as defined are case-insensitive.

yordis commented 3 months ago

Could you share an example call for me to test manually? I would like to check the headers myself

greg-freewave commented 3 months ago

Just using this http mocking website, you can see the returned content type is application/JSON:

curl -v -X GET 'https://testlajson.free.beeceptor.com/todos' -v

I have no idea how long that link will run, I don't have an account at that website i just found it with a google search.

application/JSON body is a string:

Tesla.get(Tesla.client([Tesla.Middleware.JSON]), "https://testlajson.free.beeceptor.com/todos")
{:ok,
 %Tesla.Env{
   method: :get,
   url: "https://testlajson.free.beeceptor.com/todos",
   query: [],
   headers: [
     {"date", "Sun, 16 Jun 2024 00:19:32 GMT"},
     {"vary", "Accept-Encoding"},
     {"content-length", "577"},
     {"content-type", "application/JSON"},
     {"access-control-allow-origin", "*"},
     {"alt-svc", "h3=\":443\"; ma=2592000"},
     {"x-beeceptor-rule-id", "defaultget"}
   ],
   body: "[\n  {\n    \"id\": 1,\n    \"title\": \"Create an endpoint at Beeceptor.\",\n    \"completed\": true\n  },\n  {\n    \"id\": 2,\n    \"title\": \"Send a request to your new Beeceptor endpoint.\",\n    \"completed\": true\n  },\n  {\n    \"id\": 3,\n    \"title\": \"Create a mocking rule to send dummy data.\",\n    \"completed\": false\n  },\n  {\n    \"id\": 4,\n    \"title\": \"Try out a failure response by sending 500 HTTP status code.\",\n    \"completed\": false\n  },\n  {\n    \"id\": 5,\n    \"title\": \"Use the proxy feature to send a request to a real API. Intercept traffic using Beeceptor.\",\n    \"completed\": false\n  }\n]",
   status: 200,
   opts: [],
   __module__: Tesla,
   __client__: %Tesla.Client{
     fun: nil,
     pre: [{Tesla.Middleware.JSON, :call, [[]]}],
     post: [],
     adapter: nil
   }
 }}

application/json body is a list of maps:

> Tesla.get(Tesla.client([Tesla.Middleware.JSON]), "https://testlajson.free.beeceptor.com/todos")
{:ok,
 %Tesla.Env{
   method: :get,
   url: "https://testlajson.free.beeceptor.com/todos",
   query: [],
   headers: [
     {"date", "Sun, 16 Jun 2024 00:18:49 GMT"},
     {"vary", "Accept-Encoding"},
     {"content-length", "577"},
     {"content-type", "application/json"},
     {"access-control-allow-origin", "*"},
     {"alt-svc", "h3=\":443\"; ma=2592000"},
     {"x-beeceptor-rule-id", "defaultget"}
   ],
   body: [
     %{
       "completed" => true,
       "id" => 1,
       "title" => "Create an endpoint at Beeceptor."
     },
     %{
       "completed" => true,
       "id" => 2,
       "title" => "Send a request to your new Beeceptor endpoint."
     },
     %{
       "completed" => false,
       "id" => 3,
       "title" => "Create a mocking rule to send dummy data."
     },
     %{
       "completed" => false,
       "id" => 4,
       "title" => "Try out a failure response by sending 500 HTTP status code."
     },
     %{
       "completed" => false,
       "id" => 5,
       "title" => "Use the proxy feature to send a request to a real API. Intercept traffic using Beeceptor."
     }
   ],
   status: 200,
   opts: [],
   __module__: Tesla,
   __client__: %Tesla.Client{
     fun: nil,
     pre: [{Tesla.Middleware.JSON, :call, [[]]}],
     post: [],
     adapter: nil
   }
 }}