uploadFile method
POST /{resource}/upload — uploads a file for a single-file upload
field. field is the field's statePath, so a nested field is
addressable.
Fails with UploadFailed rather than throwing, including when the
host's transport has not implemented the optional
FilamentUploadTransport port — the same never-throw contract every
other write has.
Implementation
@override
Future<UploadResult> uploadFile(
String resourceKey,
String field, {
required List<int> bytes,
required String filename,
}) async {
// Capability is detected, not assumed: a host that never implemented
// the optional upload port gets an actionable message, not a crash or
// a silent no-op — this is also the signal Task 6's form field reads
// to stay read-only.
//
// `FilamentUploadTransport` is a sibling interface, not a subtype of
// `FilamentTransport`, so Dart cannot promote `_transport` from the
// `is!` check below — the explicit cast is required, not decorative.
if (_transport is! FilamentUploadTransport) {
return const UploadFailed(
'This host transport does not implement FilamentUploadTransport, '
'so files cannot be uploaded. Implement it alongside '
'FilamentTransport to enable this field.',
);
}
final transport = _transport as FilamentUploadTransport;
try {
final response = await transport.upload(
'$prefix/$resourceKey/upload',
bytes: bytes,
filename: filename,
field: field,
);
if (response.statusCode >= 200 && response.statusCode < 300) {
final path = response.body['path'];
return path is String
? UploadSuccess(path)
: UploadFailed(
'Upload succeeded but the server sent no path.',
statusCode: response.statusCode,
);
}
final message = response.body['message'];
return UploadFailed(
message is String ? message : null,
statusCode: response.statusCode,
);
} catch (e) {
// Same contract as create/update/destroy: the transport throws on
// socket/DNS/timeout, and an offline upload must come back as a
// failed result, never an unhandled async error.
return UploadFailed(messageOf(e));
}
}