blob: cb2d7a1dd4c542128f75ae0629b49aa4afc89452 [file]
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'http_utils.dart';
/// An HTTP response returned by a request handler.
///
/// A response encapsulates:
/// - the [contentType], if omitted a default is used;
/// - the [statusCode], if omitted [HttpStatus.ok] is used;
/// - the [body] contents as `Stream<Uint8List>`.
///
/// To return a response of a different underlying data type, use a constructor:
/// - [Response.string] for UTF-8 strings;
/// - [Response.stream] for a stream of bytes;
/// - [Response.json] for a JSON-encodable object;
/// - or [Response.emptyOk] for a default (empty) resposne that is always `200`.
@immutable
abstract final class Response {
const Response({this.statusCode = HttpStatus.ok});
/// Creates an UTF-8 string response of [content].
///
/// By default, uses [kContentTypeText] and [HttpStatus.ok].
factory Response.string(
String content, { //
MediaType? contentType,
int statusCode,
}) = _StringBody;
/// Creates a byte-encoded stream of [content].
///
/// By default, uses [kContentTypeBinary] and [HttpStatus.ok].
factory Response.stream(
Stream<Uint8List> content, { //
MediaType? contentType,
int statusCode,
}) = _StreamBody;
/// Creates a [Response] that serializes the specified JSON [value].
///
/// The [value] argument may be any JSON tyope (any scalar value, any object
/// that defines a `toJson()` method that returns a JSON type, or a [List] or
/// [Map] of other JSON types).
///
/// By default, uses [kContentTypeJson] and [HttpStatus.ok].
factory Response.json(Object? value, {int statusCode}) = _JsonBody;
/// A [Response] with an _empty_ [body] and [HttpStatus.ok].
static const Response emptyOk = _EmptyBody();
/// Format of the body.
MediaType? get contentType;
/// Status code of the response.
final int statusCode;
/// Content type of the response body as bytes.
///
/// **NOTE**: It is not guaranteed that the returned stream can be listened
/// to more than once.
Stream<Uint8List> get body;
}
final class _EmptyBody extends Response {
const _EmptyBody();
@override
MediaType? get contentType => null;
@override
Stream<Uint8List> get body => const Stream<Uint8List>.empty();
}
final class _StringBody extends Response {
const _StringBody(
this._content, { //
super.statusCode,
MediaType? contentType,
}) : _contentType = contentType;
final String _content;
@override
MediaType get contentType => _contentType ?? kContentTypeText;
final MediaType? _contentType;
@override
Stream<Uint8List> get body {
return Stream.fromIterable([utf8.encode(_content)]);
}
@override
String toString() {
return 'Body.forString($_content)';
}
}
final class _JsonBody extends Response {
const _JsonBody(this._content, {super.statusCode});
final Object? _content;
@override
MediaType get contentType => kContentTypeJson;
static final _utf8JsonEncoder = JsonUtf8Encoder();
@override
Stream<Uint8List> get body {
return Stream.fromIterable([
_utf8JsonEncoder.convert(_content) as Uint8List,
]);
}
@override
String toString() {
return 'Body.forString($_content)';
}
}
final class _StreamBody extends Response {
const _StreamBody(
this._stream, { //
super.statusCode,
MediaType? contentType,
}) : _contentType = contentType;
@override
MediaType get contentType => _contentType ?? kContentTypeBinary;
final MediaType? _contentType;
final Stream<Uint8List> _stream;
@override
Stream<Uint8List> get body => _stream;
}