Skip to content

Encoding detection from #94 #110

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

Merged
merged 3 commits into from
Apr 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class Client {
: uri.replace(queryParameters: request.query),
body: body)
..headers.addAll({
'Accept': MediaType.jsonApi,
if (body.isNotEmpty) 'Content-Type': MediaType.jsonApi,
'Accept': mediaType,
if (body.isNotEmpty) 'Content-Type': mediaType,
...request.headers
}));

Expand All @@ -49,5 +49,5 @@ class Client {
response.body.isNotEmpty &&
(response.headers['Content-Type'] ?? '')
.toLowerCase()
.startsWith(MediaType.jsonApi);
.startsWith(mediaType);
}
2 changes: 1 addition & 1 deletion lib/src/client/disposable_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class DisposableHandler implements HttpHandler {
Future<HttpResponse> handle(HttpRequest request) async {
final client = Client();
try {
return await PersistentHandler(client).call(request);
return await PersistentHandler(client).handle(request);
} finally {
client.close();
}
Expand Down
38 changes: 35 additions & 3 deletions lib/src/client/persistent_handler.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:json_api/http.dart';

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

final Client client;
final Encoding defaultEncoding;

Future<HttpResponse> call(HttpRequest request) async {
Future<HttpResponse> handle(HttpRequest request) async {
final response = await Response.fromStream(
await client.send(Request(request.method, request.uri)
..headers.addAll(request.headers)
..body = request.body));
return HttpResponse(response.statusCode, body: response.body)
final responseBody =
_encodingForHeaders(response.headers).decode(response.bodyBytes);
return HttpResponse(response.statusCode, body: responseBody)
..headers.addAll(response.headers);
}

/// Returns the encoding to use for a response with the given headers.
///
/// Defaults to [defaultEncoding] if the headers don't specify a charset or if that
/// charset is unknown.
Encoding _encodingForHeaders(Map<String, String> headers) =>
_encodingForCharset(
_contentTypeForHeaders(headers).parameters['charset']);

/// Returns the [Encoding] that corresponds to [charset].
///
/// Returns [defaultEncoding] if [charset] is null or if no [Encoding] was found that
/// corresponds to [charset].
Encoding _encodingForCharset(String? charset) {
if (charset == null) return defaultEncoding;
return Encoding.getByName(charset) ?? defaultEncoding;
}

/// Returns the [MediaType] object for the given headers's content-type.
///
/// Defaults to `application/octet-stream`.
MediaType _contentTypeForHeaders(Map<String, String> headers) {
final contentType = headers['content-type'];
if (contentType != null) return MediaType.parse(contentType);
return MediaType('application', 'octet-stream');
}
}
4 changes: 1 addition & 3 deletions lib/src/http/media_type.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
class MediaType {
static const jsonApi = 'application/vnd.api+json';
}
const mediaType = 'application/vnd.api+json';
2 changes: 1 addition & 1 deletion lib/src/server/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:json_api/src/nullable.dart';
class Response<D extends OutboundDocument> extends HttpResponse {
Response(int statusCode, {this.document}) : super(statusCode) {
if (document != null) {
headers['Content-Type'] = MediaType.jsonApi;
headers['Content-Type'] = mediaType;
}
}

Expand Down
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name: json_api
version: 5.0.0-rc.0
version: 5.0.0-rc.1
homepage: https://github.com/f3ath/json-api-dart
description: A framework-agnostic implementations of JSON:API Client and Server. Supports JSON:API v1.0 (https://jsonapi.org)
environment:
sdk: '>=2.12.0 <3.0.0'

dependencies:
http: ^0.13.0
http_parser: ^4.0.0

dev_dependencies:
pedantic: ^1.10.0
Expand Down
16 changes: 8 additions & 8 deletions test/unit/client/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ final collectionMin = HttpResponse(200,
{'type': 'articles', 'id': '1'}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});

final collectionFull = HttpResponse(200,
body: jsonEncode({
Expand Down Expand Up @@ -80,7 +80,7 @@ final collectionFull = HttpResponse(200,
}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});

final primaryResource = HttpResponse(200,
body: jsonEncode({
Expand Down Expand Up @@ -130,13 +130,13 @@ final primaryResource = HttpResponse(200,
}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});
final relatedResourceNull = HttpResponse(200,
body: jsonEncode({
'links': {'self': 'http://example.com/articles/1/author'},
'data': null
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});
final one = HttpResponse(200,
body: jsonEncode({
'links': {
Expand Down Expand Up @@ -179,7 +179,7 @@ final one = HttpResponse(200,
}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});

final oneEmpty = HttpResponse(200,
body: jsonEncode({
Expand Down Expand Up @@ -223,7 +223,7 @@ final oneEmpty = HttpResponse(200,
}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});

final many = HttpResponse(200,
body: jsonEncode({
Expand All @@ -235,7 +235,7 @@ final many = HttpResponse(200,
{'type': 'tags', 'id': '12'}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});

final noContent = HttpResponse(204);

Expand All @@ -250,6 +250,6 @@ final error422 = HttpResponse(422,
}
]
}))
..headers.addAll({'Content-Type': MediaType.jsonApi});
..headers.addAll({'Content-Type': mediaType});

final error500 = HttpResponse(500);
52 changes: 52 additions & 0 deletions test/unit/http/enoding_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:json_api/http.dart';
import 'package:json_api/src/client/persistent_handler.dart';
import 'package:test/test.dart';

void main() {
group('Decode body with', () {
final stringBodyRu = 'йцукен';
final bytesBodyRu = utf8.encode(stringBodyRu);
final stringBodyEn = 'qwerty';
final bytesBodyEn = utf8.encode(stringBodyEn);

final buildResponse = (
List<int> bytesBody,
Encoding encoding,
) async {
final dartHttp = PersistentHandler(
MockClient(
(request) async {
return http.Response.bytes(bytesBody, 200);
},
),
defaultEncoding: encoding,
);

return dartHttp.handle(HttpRequest('', Uri.parse('http://test.com')));
};

test('UTF-8 ru', () async {
final response = await buildResponse(bytesBodyRu, utf8);
expect(response.body, equals(stringBodyRu));
});

test('latin1 ru', () async {
final response = await buildResponse(bytesBodyRu, latin1);
expect(response.body, isNot(equals(stringBodyRu)));
});

test('UTF-8 en', () async {
final response = await buildResponse(bytesBodyEn, utf8);
expect(response.body, equals(stringBodyEn));
});

test('latin1 en', () async {
final response = await buildResponse(bytesBodyEn, latin1);
expect(response.body, equals(stringBodyEn));
});
});
}