File uploads and byte streams
A Go type like io.Reader says that bytes will flow. It says nothing about
what they are, how they are framed, or how long they run. codescan recognizes
these types and answers with the only two things Swagger 2.0 lets it say about
opaque bytes — picked by where the field sits, not by anything in the
declaration.
The two answers
| Position | Rendering |
|---|---|
in: formData parameter | type: file |
| model field, body, response body, header, other parameters | {type: string, format: byte} |
type: file is the canonical upload shape, and formData is the only location
Swagger 2.0 permits it in. Everywhere else the bytes travel inside a JSON
document, which cannot carry raw octets — so they render as format: byte, the
base64-encoded string the specification defines for exactly this.
Uploading a file
Put the stream in a formData parameter and consume multipart/form-data:
// UploadParams uploads a file and its metadata.
//
// swagger:parameters uploadAttachment
type UploadParams struct {
// Upload is the file to store.
//
// in: formData
Upload multipart.File `json:"upload"`
// Caption describes the upload.
//
// in: formData
Caption string `json:"caption"`
}Full source: docs/examples/shaping/streams/streams.go
[
{
"type": "file",
"x-go-name": "Upload",
"x-go-type": "mime/multipart.File",
"description": "Upload is the file to store.",
"name": "upload",
"in": "formData"
},
{
"type": "string",
"x-go-name": "Caption",
"description": "Caption describes the upload.",
"name": "caption",
"in": "formData"
}
]
Full source: docs/examples/shaping/streams/testdata/upload_params.json
upload becomes type: file; the sibling caption is an ordinary form field.
multipart.File and io.Reader are interchangeable here — both are recognized.
Streams in a model or a body
Anywhere that is not a formData parameter, the same types render as base64 bytes:
// Attachment carries opaque byte streams as model fields.
//
// A stream says nothing about its own framing, so codescan does not invent one:
// each field renders as `{string, format: byte}` — the base64-encoded string
// Swagger 2.0 uses for arbitrary bytes.
//
// swagger:model
type Attachment struct {
// Content is the attachment payload.
Content io.Reader `json:"content"`
// Thumbnail is a closeable stream; the same answer applies.
Thumbnail io.ReadCloser `json:"thumbnail"`
// Checksum says what its bytes are, so the annotation wins over the default.
//
// swagger:strfmt base64
Checksum io.Reader `json:"checksum"`
}Full source: docs/examples/shaping/streams/streams.go
{
"description": "A stream says nothing about its own framing, so codescan does not invent one:\neach field renders as `{string, format: byte}` — the base64-encoded string\nSwagger 2.0 uses for arbitrary bytes.",
"type": "object",
"title": "Attachment carries opaque byte streams as model fields.",
"properties": {
"checksum": {
"description": "Checksum says what its bytes are, so the annotation wins over the default.",
"type": "string",
"format": "base64",
"x-go-name": "Checksum",
"x-go-type": "io.Reader"
},
"content": {
"description": "Content is the attachment payload.",
"type": "string",
"format": "byte",
"x-go-name": "Content",
"x-go-type": "io.Reader"
},
"thumbnail": {
"description": "Thumbnail is a closeable stream; the same answer applies.",
"type": "string",
"format": "byte",
"x-go-name": "Thumbnail",
"x-go-type": "io.ReadCloser"
}
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/streams"
}
Full source: docs/examples/shaping/streams/testdata/attachment.json
content and thumbnail carry {string, byte}. checksum carries
swagger:strfmt base64, and the annotation wins — which is the point of the
next section.
Say what the bytes are
The default is deliberately uninformative, because a stream is uninformative. When you know more, say so and codescan will step aside:
swagger:strfmt— name the format (base64,binary, a custom one);swagger:type— override the type outright;swagger:file— force the file shape where you want it and the position allows it.
What is recognized
| Package | Types |
|---|---|
io | Reader, ReadCloser, ReadSeeker, ReadSeekCloser, ReadWriter, ReaderAt, ReaderFrom, LimitedReader, ByteReader, ByteScanner |
mime/multipart | File |
github.com/go-openapi/runtime | NamedReadCloser |
Recognition is by identity — the exact named type — never by shape. An
interface of your own that happens to have a Read method is your type and is
documented as you declared it.
Because both renderings erase which stream it was — every type in the table
produces the same schema — each one also carries an
x-go-type extension naming the Go type it
came from, so a consumer can tell an io.Reader field from a multipart.File
one. SkipExtensions suppresses it along with the rest of the x-go-* family.
Note
io.Writer is not recognized, nor are the write-only closers. A sink the
caller writes into is not something that travels on the wire, so codescan does
not assume what you meant by putting one in an API type — it documents the type
structurally, and you override it if you had something in mind.
What’s next
- Forcing a conformant format — the
field-level
swagger:strfmtused above. - Type discovery — how codescan decides what a Go type becomes when no recognizer applies.
swagger:filereference.