Skip to content

Commit 4c2dbe9

Browse files
authored
Encoding detection from #94 (#110)
1 parent 903bb2b commit 4c2dbe9

File tree

8 files changed

+103
-20
lines changed

8 files changed

+103
-20
lines changed

lib/src/client/client.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ class Client {
2626
: uri.replace(queryParameters: request.query),
2727
body: body)
2828
..headers.addAll({
29-
'Accept': MediaType.jsonApi,
30-
if (body.isNotEmpty) 'Content-Type': MediaType.jsonApi,
29+
'Accept': mediaType,
30+
if (body.isNotEmpty) 'Content-Type': mediaType,
3131
...request.headers
3232
}));
3333

@@ -49,5 +49,5 @@ class Client {
4949
response.body.isNotEmpty &&
5050
(response.headers['Content-Type'] ?? '')
5151
.toLowerCase()
52-
.startsWith(MediaType.jsonApi);
52+
.startsWith(mediaType);
5353
}

lib/src/client/disposable_handler.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class DisposableHandler implements HttpHandler {
1111
Future<HttpResponse> handle(HttpRequest request) async {
1212
final client = Client();
1313
try {
14-
return await PersistentHandler(client).call(request);
14+
return await PersistentHandler(client).handle(request);
1515
} finally {
1616
client.close();
1717
}
+35-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import 'dart:convert';
2+
13
import 'package:http/http.dart';
4+
import 'package:http_parser/http_parser.dart';
25
import 'package:json_api/http.dart';
36

47
/// Handler which relies on the built-in Dart HTTP client.
@@ -7,16 +10,45 @@ import 'package:json_api/http.dart';
710
class PersistentHandler {
811
/// Creates a new instance of the handler. Do not forget to call `close()` on
912
/// the [client] when it's not longer needed.
10-
PersistentHandler(this.client);
13+
PersistentHandler(this.client, {this.defaultEncoding = utf8});
1114

1215
final Client client;
16+
final Encoding defaultEncoding;
1317

14-
Future<HttpResponse> call(HttpRequest request) async {
18+
Future<HttpResponse> handle(HttpRequest request) async {
1519
final response = await Response.fromStream(
1620
await client.send(Request(request.method, request.uri)
1721
..headers.addAll(request.headers)
1822
..body = request.body));
19-
return HttpResponse(response.statusCode, body: response.body)
23+
final responseBody =
24+
_encodingForHeaders(response.headers).decode(response.bodyBytes);
25+
return HttpResponse(response.statusCode, body: responseBody)
2026
..headers.addAll(response.headers);
2127
}
28+
29+
/// Returns the encoding to use for a response with the given headers.
30+
///
31+
/// Defaults to [defaultEncoding] if the headers don't specify a charset or if that
32+
/// charset is unknown.
33+
Encoding _encodingForHeaders(Map<String, String> headers) =>
34+
_encodingForCharset(
35+
_contentTypeForHeaders(headers).parameters['charset']);
36+
37+
/// Returns the [Encoding] that corresponds to [charset].
38+
///
39+
/// Returns [defaultEncoding] if [charset] is null or if no [Encoding] was found that
40+
/// corresponds to [charset].
41+
Encoding _encodingForCharset(String? charset) {
42+
if (charset == null) return defaultEncoding;
43+
return Encoding.getByName(charset) ?? defaultEncoding;
44+
}
45+
46+
/// Returns the [MediaType] object for the given headers's content-type.
47+
///
48+
/// Defaults to `application/octet-stream`.
49+
MediaType _contentTypeForHeaders(Map<String, String> headers) {
50+
final contentType = headers['content-type'];
51+
if (contentType != null) return MediaType.parse(contentType);
52+
return MediaType('application', 'octet-stream');
53+
}
2254
}

lib/src/http/media_type.dart

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
class MediaType {
2-
static const jsonApi = 'application/vnd.api+json';
3-
}
1+
const mediaType = 'application/vnd.api+json';

lib/src/server/response.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'package:json_api/src/nullable.dart';
88
class Response<D extends OutboundDocument> extends HttpResponse {
99
Response(int statusCode, {this.document}) : super(statusCode) {
1010
if (document != null) {
11-
headers['Content-Type'] = MediaType.jsonApi;
11+
headers['Content-Type'] = mediaType;
1212
}
1313
}
1414

pubspec.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
name: json_api
2-
version: 5.0.0-rc.0
2+
version: 5.0.0-rc.1
33
homepage: https://github.com/f3ath/json-api-dart
44
description: A framework-agnostic implementations of JSON:API Client and Server. Supports JSON:API v1.0 (https://jsonapi.org)
55
environment:
66
sdk: '>=2.12.0 <3.0.0'
77

88
dependencies:
99
http: ^0.13.0
10+
http_parser: ^4.0.0
1011

1112
dev_dependencies:
1213
pedantic: ^1.10.0

test/unit/client/response.dart

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ final collectionMin = HttpResponse(200,
99
{'type': 'articles', 'id': '1'}
1010
]
1111
}))
12-
..headers.addAll({'Content-Type': MediaType.jsonApi});
12+
..headers.addAll({'Content-Type': mediaType});
1313

1414
final collectionFull = HttpResponse(200,
1515
body: jsonEncode({
@@ -80,7 +80,7 @@ final collectionFull = HttpResponse(200,
8080
}
8181
]
8282
}))
83-
..headers.addAll({'Content-Type': MediaType.jsonApi});
83+
..headers.addAll({'Content-Type': mediaType});
8484

8585
final primaryResource = HttpResponse(200,
8686
body: jsonEncode({
@@ -130,13 +130,13 @@ final primaryResource = HttpResponse(200,
130130
}
131131
]
132132
}))
133-
..headers.addAll({'Content-Type': MediaType.jsonApi});
133+
..headers.addAll({'Content-Type': mediaType});
134134
final relatedResourceNull = HttpResponse(200,
135135
body: jsonEncode({
136136
'links': {'self': 'http://example.com/articles/1/author'},
137137
'data': null
138138
}))
139-
..headers.addAll({'Content-Type': MediaType.jsonApi});
139+
..headers.addAll({'Content-Type': mediaType});
140140
final one = HttpResponse(200,
141141
body: jsonEncode({
142142
'links': {
@@ -179,7 +179,7 @@ final one = HttpResponse(200,
179179
}
180180
]
181181
}))
182-
..headers.addAll({'Content-Type': MediaType.jsonApi});
182+
..headers.addAll({'Content-Type': mediaType});
183183

184184
final oneEmpty = HttpResponse(200,
185185
body: jsonEncode({
@@ -223,7 +223,7 @@ final oneEmpty = HttpResponse(200,
223223
}
224224
]
225225
}))
226-
..headers.addAll({'Content-Type': MediaType.jsonApi});
226+
..headers.addAll({'Content-Type': mediaType});
227227

228228
final many = HttpResponse(200,
229229
body: jsonEncode({
@@ -235,7 +235,7 @@ final many = HttpResponse(200,
235235
{'type': 'tags', 'id': '12'}
236236
]
237237
}))
238-
..headers.addAll({'Content-Type': MediaType.jsonApi});
238+
..headers.addAll({'Content-Type': mediaType});
239239

240240
final noContent = HttpResponse(204);
241241

@@ -250,6 +250,6 @@ final error422 = HttpResponse(422,
250250
}
251251
]
252252
}))
253-
..headers.addAll({'Content-Type': MediaType.jsonApi});
253+
..headers.addAll({'Content-Type': mediaType});
254254

255255
final error500 = HttpResponse(500);

test/unit/http/enoding_test.dart

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/http.dart' as http;
4+
import 'package:http/testing.dart';
5+
import 'package:json_api/http.dart';
6+
import 'package:json_api/src/client/persistent_handler.dart';
7+
import 'package:test/test.dart';
8+
9+
void main() {
10+
group('Decode body with', () {
11+
final stringBodyRu = 'йцукен';
12+
final bytesBodyRu = utf8.encode(stringBodyRu);
13+
final stringBodyEn = 'qwerty';
14+
final bytesBodyEn = utf8.encode(stringBodyEn);
15+
16+
final buildResponse = (
17+
List<int> bytesBody,
18+
Encoding encoding,
19+
) async {
20+
final dartHttp = PersistentHandler(
21+
MockClient(
22+
(request) async {
23+
return http.Response.bytes(bytesBody, 200);
24+
},
25+
),
26+
defaultEncoding: encoding,
27+
);
28+
29+
return dartHttp.handle(HttpRequest('', Uri.parse('http://test.com')));
30+
};
31+
32+
test('UTF-8 ru', () async {
33+
final response = await buildResponse(bytesBodyRu, utf8);
34+
expect(response.body, equals(stringBodyRu));
35+
});
36+
37+
test('latin1 ru', () async {
38+
final response = await buildResponse(bytesBodyRu, latin1);
39+
expect(response.body, isNot(equals(stringBodyRu)));
40+
});
41+
42+
test('UTF-8 en', () async {
43+
final response = await buildResponse(bytesBodyEn, utf8);
44+
expect(response.body, equals(stringBodyEn));
45+
});
46+
47+
test('latin1 en', () async {
48+
final response = await buildResponse(bytesBodyEn, latin1);
49+
expect(response.body, equals(stringBodyEn));
50+
});
51+
});
52+
}

0 commit comments

Comments
 (0)