Files
About Http.File
Section titled âAbout Http.Fileâ@typespec/http provides a special model, [TypeSpec.Http.File][typespec-http-file], that represents the concept of a file. The HTTP library has special behavior when a request, response, or multipart part body is-or-extends TypeSpec.Http.File.
using TypeSpec.Http;
op exampleDownload(): File;If File were any other ordinary model, the above operation would be interpreted as returning a structured JSON data object that represents the fields of the model. File, however, has special semantics. The HTTP library understands that an operation that returns a File or any model that extends File has the semantics of downloading a file with binary payload and arbitrary content-type from the server. We call these cases âfile bodiesâ to distinguish them from ordinary bodies.
Http.File has three properties that are understood to have special meaning when a request, response, or multipart payload has a file body.
contents: the contents of the file, which are the body of the request, response, or multipart payload. This location cannot be changed by subtypes ofHttp.File.contentType: (optional) the media (MIME) type of the file, which is sent in theContent-Typeheader of the request, response, or multipart payload. This location cannot be changed by subtypes ofHttp.File.filename: (optional) the name of the file, which is sent in thefilenameparameter of theContent-Dispositionheader of response and multipart payloads. By default, it cannot be sent in requests, asContent-Dispositionis only valid for response and multipart payloads. This location can be changed by subtypes ofHttp.Filethat apply HTTP metadata to the location. See Overriding thefilenamelocation below for more information.
Using Http.File in operations
Section titled âUsing Http.File in operationsâAn operation payload (request, response, or multipart part) has a file body if:
- The type of the body is effectively a model that is or extends
Http.FileAND - there is no explicit declaration of a
Content-Typeheader (seeFilewith an explicitContent-Typeheader below for reasoning and more information).
By âeffectively a model that is or extends Http.File,â we mean cases where an explicit body property is provided and its type is or extends Http.File as well as cases where Http.File is spread into a request or response payload or Http.File is intersected with other models in a request or response and the only non-metadata properties in the payload are properties of File (see When a model is effectively a File below for a more precise description with examples). The following sections contain examples of using Http.File in various contexts to define operations that have file bodies.
Downloading a file
Section titled âDownloading a fileâAll of the following TypeSpec operation definitions have file bodies in the response:
// The response is _exactly_ a File, so the response has a file body.op download(): File;
// The response has an explicit body that is a File, so the response has a file body.op download(): { @bodyRoot file: File;};
// The response is _effectively_ a File (`File` is the only thing spread into it), so the response has a file body.op download(): { ...File;};
// File is intersected with other models containing only HTTP metadata, so the response has a file body.op download(): OkResponse & File;
// The response has an explicit body that is _effectively_ a File, so the response has a file body.op download(): { @bodyRoot file: { ...File; };};Uploading a file
Section titled âUploading a fileâAll of the following TypeSpec operation definitions have file bodies in the request:
// The request has an explicit body that is _exactly_ a File, so the request has a file body.op upload(@bodyRoot file: File): void;
alias FileRequest = { @header("x-request-id") requestId: string;} & File;
// File is intersected with other models containing only HTTP metadata, so the request has a file body.op upload(...FileRequest): void;
// The request is _effectively_ a File (`File` is the only thing spread into it), so the request has a file body.op upload(...File): void;
// The request has an explicit body that is _effectively_ a File, so the request has a file body.op upload( @bodyRoot body: { ...File; },): void;Using files in multipart payloads
Section titled âUsing files in multipart payloadsâMultipart payloads are commonly used to upload files (e.g. in HTML forms). To declare a multipart part that has a file body, ensure the partâs type follows the same rules as for request and response payloads: it must either be a type that is effectively an instance of Http.File, or must have an explicit body property that effectively is-or-extends Http.File. All of the following examples declare multipart parts that have file bodies:
// The type of the form-data part is _exactly_ a File, so the part has a file body.op multipartUpload( @multipartBody fields: { file: HttpPart<File>; },): void;
// The type of the form-data part has an explicit body that is _exactly_ a File, so the part has a file body.op multipartUpload( @multipartBody fields: { file: HttpPart<{ @bodyRoot file: File; }>; },): void;
// The type of the mixed part is _exactly_ a File, so the part has a file body.op multipartMixedDownload(): { @multipartBody data: [HttpPart<File>];};
// The type of the mixed part has an explicit body that is _exactly_ a File, so the part has a file body.op multipartMixedDownload(): { @multipartBody data: [ HttpPart<{ @bodyRoot file: File; }> ];};All of the above examples will also have file bodies if File is replaced with a model that extends File or a model that is effectively File (e.g. {...File}).
You can also mix and match parts that have file bodies with other parts. The following TypeSpec gives a more comprehensive example of uploading data alongside files:
model Widget { id: string; name: string; weight: float64;}
op multipartUpload( @multipartBody fields: { // The widget is uploaded in a part named `widget` and uses form-urlencoded serialization. widget: HttpPart<{ @header contentType: "application/x-www-form-urlencoded"; @body widget: Widget; }>;
// The part named `attachments` can be sent multiple times, and each `attachments` part has a file body. attachments: HttpPart<File>[]; },): void;For more information about the handling of multipart payloads in @typespec/http, see Multipart.
When a model is effectively a File
Section titled âWhen a model is effectively a FileâIn the above sections, we used the idea of âeffectiveâ files. In the context of an HTTP operation, a model is effectively a file if it has all of the properties of Http.File (true properties of Http.File from a spread or intersection, not just properties that have the same shape as a File) AND after removing all of the applicable metadata properties, it has only properties of Http.File.
- A property of
Http.Filemeans a property that is actually sourced from theHttp.Filemodel, e.g. through spreadingFileinto another model or usingmodel issyntax. - Applicable metadata means an HTTP metadata decorator that applies in context. For example,
@pathis applicable in requests, but not response or multipart payloads.@statusCodeis applicable in responses, but not request or multipart payloads.
The following table shows which metadata annotations are applicable in which contexts:
| Metadata | @header | @query | @statusCode | @path |
|---|---|---|---|---|
| Request | â | â | â | â |
| Response | â | â | â | â |
| Multipart | â | â | â | â |
Examples that are effectively a File
Section titled âExamples that are effectively a Fileâ// The parameters of this operation are effectively a file because the @header parameter// is not considered when checking if the request is a fileop uploadFileWithHeader(@header("x-request-id") requestId: string, ...Http.File): void;
model CommonParameters { @query("api-version") apiVersion: string; @header("x-request-id") requestId: string;}
// The parameters of this operation are effectively a file because the common parameters// are all applicable metadata and not considered when checking if the request is a fileop uploadFileWithCommonParams(...CommonParameters, ...File): void;
// The response has a file body because the `@statusCode` property is not considered when// checking if the response is a fileop downloadFileWithStatusCode(@path name: string): { @statusCode _: 200; ...File;};
// The response has a file body because the `OkResponse` model only has response-applicable// metadata that is not considered when checking if the response is a fileop downloadFileWithIntersection(@path name: string): OkResponse & File;
model OpenAPIFile extends File<"application/json" | "application/yaml", string> { @path filename: string;}
// The response and request have file bodies because the common parameters are all// applicable metadata in the request, and the `OkResponse` model only contains// applicable metadata for the response.op uploadAndDownload(...CommonParameters, ...OpenAPIFile): OkResponse & OpenAPIFile;
model FileData { @header("x-created") created: utcDateTime; ...File;}
// The request has a file body because the `created` header is applicable metadata for// responses, and the rest of `FileData` is the properties of `File`.op upload(@bodyRoot file: FileData): OkResponse;
// The response has a file body because the `OkResponse` model only contains applicable// metadata for the response, and the `created` header is also applicable in the response.// The properties that are left over are the properties of `File`.op download(): OkResponse & FileData;Examples that are not effectively a File
Section titled âExamples that are not effectively a Fileâ// The request does not have a file body because the `userId` parameter is a body property,// so this will cause the `File` to be serialized as JSON in the request.op uploadFileWithExtraParam(userId: string, ...File): void;
model FileData { @query created: utcDateTime; ...File;}
// The response does not have a file body because `@query` metadata is not applicable// in responses, so the `created` property is placed in the body and the whole `FileData`// model is serialized as JSON.op download(): FileData;
model OpenAPIFile extends File<"application/json" | "application/yaml", string> { @path filename: string;}
model OpenAPIFileResponse { @statusCode statusCode: 200; ...SpecFile;}
// The request does not have a file body because the `statusCode` property is not// applicable metadata for requests, so the request body would be serialized as a JSON// object. The same model _would_ create a file body in a response, though.op upload(@bodyRoot data: OpenAPIFileResponse): void;Creating custom File models
Section titled âCreating custom File modelsâYou can declare custom types of files by providing arguments to the Http.File template or extending it. Custom files can be used to add additional constraints on the contents of files or to override the location metadata of the filename property. For example, to declare a file that can contain PNG or JPEG images:
alias ImageFile = File<"image/png" | "image/jpeg">;
// or
model ImageFile extends File<"image/png" | "image/jpeg"> {}
// or
model ImageFile extends File { contentType: "image/png" | "image/jpeg";}The above examples are equivalent ways to narrow the allowed media types of the fileâs contents. For convenience, you can specify the ContentType parameter of the File template inline, or you can override the type of the property in your own model that extends File.
The extra contentType information in these custom files provides an extra contractual guarantee about what kinds of data can be inside the file. In the above case, it is guaranteed to be either PNG or JPEG image data. The allowed Content-Type header values for the payload are also restricted to only allow those values that satisfy the contentType propertyâs type.
NOTE: While you can override the type of properties within Http.File by extending it, you cannot define additional properties.
Overriding the filename location
Section titled âOverriding the filename locationâBy default, the filename is located in the Content-Disposition header of response and multipart payloads, but that header is not valid for request payloads. If you wish to send the filename in a request, you must override the location. For example, the follwing TypeSpec defines an OpenAPIFile in which the filename is appended to the route path when a file is uploaded, but since @path only applies to requests, the filename will still be returned in the Content-Dispotion header in responses or multipart payloads:
model OpenAPIFile extends File<"application/json" | "application/yaml"> { @path filename: string;}
@route("/specs")interface Specs { upload(@bodyRoot file: OpenAPIFile): void;
download(@path name: string): OpenAPIFile;}NOTE: Header metadata is applicable in all contexts, so if you use a custom header (e.g. @header("x-filename") filename: string) in your custom file, beware that it will apply to request, response, and multipart payloads equally.
Textual files
Section titled âTextual filesâThe File template accepts a Contents argument that may be TypeSpec.string, TypeSpec.bytes, or any scalar that extends them. If the Contents argument is or extends TypeSpec.string, the file is considered a textual file. For example:
// Since `Contents` is `string`, this file type can only contain text data.alias TextFile = File<Contents = string>;
// This file type can only contain text and is guaranteed to have `contentType: "application/yaml"`.model YamlFile extends File<"application/yaml", string> {}
// This file is another way to declare YamlFile by overriding the type of `contentType`model YamlFile extends File<Contents = string> { contentType: "application/yaml";}Textual files provide an extra contractual guarantee that the contents of the file must be text (i.e. the contents can be represented as a string).
NOTE: TypeSpec does not prescribe any specific text encoding. Emitters and libraries should take care to honor the charset of the file if one is specified, and should assume UTF-8 encoding in the absence of any protocol-level indication of the text encoding on the wire.
Http.File as a structured model
Section titled âHttp.File as a structured modelâIn other cases, when Http.File is not itself the body of a request or response, it is treated as a structured model just like any other ordinary model. The TypeSpec HTTP library will generally warn you in cases where the File looks like it might indicate a file body, but does not because of the libraryâs rules.
File properties inside other models
Section titled âFile properties inside other modelsâIf a property of a model is a File, and that model is serialized as JSON, the structure of the File will be serialized as JSON inline, with the contents encoded as Base64 data. For example, in the following operation:
model Example { id: string; attachment?: File;}
op getExample(@path id: string): Example;The response body with the File serialized as JSON looks like:
{ "id": "<string>", "attachment": { "contentType": "<string?>", "filename": "<string?>", "contents": "<base64>" }}File inside a union
Section titled âFile inside a unionâIf File is a variant of a union in an exact body, it is not treated as a file body. For example:
// Warning: An HTTP File in a union is serialized as a structured model// instead of being treated as the contents of a file...op uploadFileOrString(@path id: string, @body data: File | string): void;The above operation accepts either a text/plain string or a JSON-serialized File object body, not a file body. To declare a single operation that accepts either a text/plain string or a file body, declare two separate operations using @sharedRoute:
@sharedRouteop uploadFile(@path id: string, @body data: File): void;
@sharedRouteop uploadString(@path id: string, @body data: string): void;File can be in a union in an HTTP response and still create a file body, but only if the union is itself the return type and not in an explicit body property. The HTTP library recognizes the variants of a union that is returned from an operation as individual and separate responses, and it is allowed to have a response type that is a File alongside other non-file responses, but if a single response has a type that is a union that contains file, the same warning as above will appear and the File will be treated as a structured model:
// This is allowed and creates a file body, as `File` and `string` are considered separate responses, so// this operation has two responses; the first has a file body, and the second has a `text/plain` string body.op downloadFileOrString(): File | string;
// The following does not create a file body, as it is only one response where the body of that single response// may be either a file or a string.
// Warning: An HTTP File in a union is serialized as a structured model// instead of being treated as the contents of a file...op downloadFileOrString(): { @bodyRoot data: File | string;};File with an explicit Content-Type header
Section titled âFile with an explicit Content-Type headerâOperations are only considered to have file bodies if there is no explicit declaration of a Content-Type header in the payload. If an explicit Content-Type header is present, the File is always considered a structured model and is not treated as a file body. The following operation does not have a file body:
// Warning: HTTP File body is serialized as a structured model in 'application/json' instead of being// treated as the contents of a file because an explicit Content-Type header is defined.op download(): { @header contentType: "application/json"; @body file: File<Contents = string>;};The explicit Content-Type header is not merely metadata. The HTTP library treats this header declaration as a directive about how to serialize the body. In other words, the operation above says âserialize the type of the body as JSON, and the type of the body is Http.File.â To declare an operation with a file body, where the file can only contain JSON data, provide the ContentType argument to the File template instead:
op download(): File<"application/json", string>;Similarly, and to maintain consistency, you cannot use an explicit Content-Type header to declare the content-type of binary files:
// Warning: HTTP File body is serialized as a structured model in 'image/png, image/jpeg' instead of being// treated as the contents of a file because an explicit Content-Type header is defined.op downloadImage(): { @header contentType: "image/png" | "image/jpeg"; @body file: File;};
// Do this insteadop downloadImage(): File<"image/png" | "image/jpeg">;Library and emitter authoring notes
Section titled âLibrary and emitter authoring notesâFor library/emitter developers working with the @typespec/http programmatic API, you can always determine if an operation request, response, or multipart payload is a file body by checking if the bodyKind of an HttpPayloadBody is "file". If the body kind is "single" (or any other kind), then the body is not a file body.
File bodies require special handling to account for the special nature of files. When processing file bodies:
- Assume that the
contentsof the file are always transmitted in the body without any further encoding. - Assume that the
contentTypeof the file always comes from theContent-Typeheader of the corresponding request, response, or multipart payload. - The
filenameshould come from theContent-Dispositionheader of the response or multipart payload (there is nofilenamein requests by default), but be aware that spec authors may override this location using HTTP property metadata decorators like@queryor@header. - The
isTextfield of theHttpOperationFileBodywill betrueif the file is contractually guaranteed to only contain text (i.e. if thecontentsproperty has a type that is or extendsTypeSpec.string). Textual files are a subset of binary files, with the guarantee that the contents are plain text that can be converted to astring. See Textual files above.
See the reference documentation of HttpPayloadBody and HttpOperationFileBody for more information.