📖 4 min read (~ 800 words).

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

PositionRendering
in: formData parametertype: 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:

parameters
// 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

parameters
[
  {
    "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:

model
// 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

#/definitions/Attachment
{
  "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

PackageTypes
ioReader, ReadCloser, ReadSeeker, ReadSeekCloser, ReadWriter, ReaderAt, ReaderFrom, LimitedReader, ByteReader, ByteScanner
mime/multipartFile
github.com/go-openapi/runtimeNamedReadCloser

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