package wasi:io@0.2.0;

interface error {
  /// A resource which represents some error information.
  ///
  /// The only method provided by this resource is `to-debug-string`,
  /// which provides some human-readable information about the error.
  ///
  /// In the `wasi:io` package, this resource is returned through the
  /// `wasi:io/streams/stream-error` type.
  ///
  /// To provide more specific error information, other interfaces may
  /// provide functions to further "downcast" this error into more specific
  /// error information. For example, `error`s returned in streams derived
  /// from filesystem types to be described using the filesystem's own
  /// error-code type, using the function
  /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter
  /// `borrow<error>` and returns
  /// `option<wasi:filesystem/types/error-code>`.
  ///
  /// The set of functions which can "downcast" an `error` into a more
  /// concrete type is open.
  resource error {
    /// Returns a string that is suitable to assist humans in debugging
    /// this error.
    ///
    /// WARNING: The returned string should not be consumed mechanically!
    /// It may change across platforms, hosts, or other implementation
    /// details. Parsing this string is a major platform-compatibility
    /// hazard.
    to-debug-string: func() -> string;
  }
}

/// A poll API intended to let users wait for I/O events on multiple handles
/// at once.
interface poll {
  /// `pollable` represents a single I/O event which may be ready, or not.
  resource pollable {
    /// Return the readiness of a pollable. This function never blocks.
    ///
    /// Returns `true` when the pollable is ready, and `false` otherwise.
    ready: func() -> bool;
    /// `block` returns immediately if the pollable is ready, and otherwise
    /// blocks until ready.
    ///
    /// This function is equivalent to calling `poll.poll` on a list
    /// containing only this pollable.
    block: func();
  }

  /// Poll for completion on a set of pollables.
  ///
  /// This function takes a list of pollables, which identify I/O sources of
  /// interest, and waits until one or more of the events is ready for I/O.
  ///
  /// The result `list<u32>` contains one or more indices of handles in the
  /// argument list that is ready for I/O.
  ///
  /// If the list contains more elements than can be indexed with a `u32`
  /// value, this function traps.
  ///
  /// A timeout can be implemented by adding a pollable from the
  /// wasi-clocks API to the list.
  ///
  /// This function does not return a `result`; polling in itself does not
  /// do any I/O so it doesn't fail. If any of the I/O sources identified by
  /// the pollables has an error, it is indicated by marking the source as
  /// being reaedy for I/O.
  poll: func(in: list<borrow<pollable>>) -> list<u32>;
}

/// WASI I/O is an I/O abstraction API which is currently focused on providing
/// stream types.
///
/// In the future, the component model is expected to add built-in stream types;
/// when it does, they are expected to subsume this API.
interface streams {
  use error.{error};
  use poll.{pollable};

  /// An error for input-stream and output-stream operations.
  variant stream-error {
    /// The last operation (a write or flush) failed before completion.
    ///
    /// More information is available in the `error` payload.
    last-operation-failed(error),
    /// The stream is closed: no more input will be accepted by the
    /// stream. A closed output-stream will return this error on all
    /// future operations.
    closed,
  }

  /// An input bytestream.
  ///
  /// `input-stream`s are *non-blocking* to the extent practical on underlying
  /// platforms. I/O operations always return promptly; if fewer bytes are
  /// promptly available than requested, they return the number of bytes promptly
  /// available, which could even be zero. To wait for data to be available,
  /// use the `subscribe` function to obtain a `pollable` which can be polled
  /// for using `wasi:io/poll`.
  resource input-stream {
    /// Perform a non-blocking read from the stream.
    ///
    /// When the source of a `read` is binary data, the bytes from the source
    /// are returned verbatim. When the source of a `read` is known to the
    /// implementation to be text, bytes containing the UTF-8 encoding of the
    /// text are returned.
    ///
    /// This function returns a list of bytes containing the read data,
    /// when successful. The returned list will contain up to `len` bytes;
    /// it may return fewer than requested, but not more. The list is
    /// empty when no bytes are available for reading at this time. The
    /// pollable given by `subscribe` will be ready when more bytes are
    /// available.
    ///
    /// This function fails with a `stream-error` when the operation
    /// encounters an error, giving `last-operation-failed`, or when the
    /// stream is closed, giving `closed`.
    ///
    /// When the caller gives a `len` of 0, it represents a request to
    /// read 0 bytes. If the stream is still open, this call should
    /// succeed and return an empty list, or otherwise fail with `closed`.
    ///
    /// The `len` parameter is a `u64`, which could represent a list of u8 which
    /// is not possible to allocate in wasm32, or not desirable to allocate as
    /// as a return value by the callee. The callee may return a list of bytes
    /// less than `len` in size while more bytes are available for reading.
    read: func(len: u64) -> result<list<u8>, stream-error>;
    /// Read bytes from a stream, after blocking until at least one byte can
    /// be read. Except for blocking, behavior is identical to `read`.
    blocking-read: func(len: u64) -> result<list<u8>, stream-error>;
    /// Skip bytes from a stream. Returns number of bytes skipped.
    ///
    /// Behaves identical to `read`, except instead of returning a list
    /// of bytes, returns the number of bytes consumed from the stream.
    skip: func(len: u64) -> result<u64, stream-error>;
    /// Skip bytes from a stream, after blocking until at least one byte
    /// can be skipped. Except for blocking behavior, identical to `skip`.
    blocking-skip: func(len: u64) -> result<u64, stream-error>;
    /// Create a `pollable` which will resolve once either the specified stream
    /// has bytes available to read or the other end of the stream has been
    /// closed.
    /// The created `pollable` is a child resource of the `input-stream`.
    /// Implementations may trap if the `input-stream` is dropped before
    /// all derived `pollable`s created with this function are dropped.
    subscribe: func() -> pollable;
  }

  /// An output bytestream.
  ///
  /// `output-stream`s are *non-blocking* to the extent practical on
  /// underlying platforms. Except where specified otherwise, I/O operations also
  /// always return promptly, after the number of bytes that can be written
  /// promptly, which could even be zero. To wait for the stream to be ready to
  /// accept data, the `subscribe` function to obtain a `pollable` which can be
  /// polled for using `wasi:io/poll`.
  resource output-stream {
    /// Check readiness for writing. This function never blocks.
    ///
    /// Returns the number of bytes permitted for the next call to `write`,
    /// or an error. Calling `write` with more bytes than this function has
    /// permitted will trap.
    ///
    /// When this function returns 0 bytes, the `subscribe` pollable will
    /// become ready when this function will report at least 1 byte, or an
    /// error.
    check-write: func() -> result<u64, stream-error>;
    /// Perform a write. This function never blocks.
    ///
    /// When the destination of a `write` is binary data, the bytes from
    /// `contents` are written verbatim. When the destination of a `write` is
    /// known to the implementation to be text, the bytes of `contents` are
    /// transcoded from UTF-8 into the encoding of the destination and then
    /// written.
    ///
    /// Precondition: check-write gave permit of Ok(n) and contents has a
    /// length of less than or equal to n. Otherwise, this function will trap.
    ///
    /// returns Err(closed) without writing if the stream has closed since
    /// the last call to check-write provided a permit.
    write: func(contents: list<u8>) -> result<_, stream-error>;
    /// Perform a write of up to 4096 bytes, and then flush the stream. Block
    /// until all of these operations are complete, or an error occurs.
    ///
    /// This is a convenience wrapper around the use of `check-write`,
    /// `subscribe`, `write`, and `flush`, and is implemented with the
    /// following pseudo-code:
    ///
    /// ```text
    /// let pollable = this.subscribe();
    /// while !contents.is_empty() {
    /// // Wait for the stream to become writable
    /// pollable.block();
    /// let Ok(n) = this.check-write(); // eliding error handling
    /// let len = min(n, contents.len());
    /// let (chunk, rest) = contents.split_at(len);
    /// this.write(chunk  );            // eliding error handling
    /// contents = rest;
    /// }
    /// this.flush();
    /// // Wait for completion of `flush`
    /// pollable.block();
    /// // Check for any errors that arose during `flush`
    /// let _ = this.check-write();         // eliding error handling
    /// ```
    blocking-write-and-flush: func(contents: list<u8>) -> result<_, stream-error>;
    /// Request to flush buffered output. This function never blocks.
    ///
    /// This tells the output-stream that the caller intends any buffered
    /// output to be flushed. the output which is expected to be flushed
    /// is all that has been passed to `write` prior to this call.
    ///
    /// Upon calling this function, the `output-stream` will not accept any
    /// writes (`check-write` will return `ok(0)`) until the flush has
    /// completed. The `subscribe` pollable will become ready when the
    /// flush has completed and the stream can accept more writes.
    flush: func() -> result<_, stream-error>;
    /// Request to flush buffered output, and block until flush completes
    /// and stream is ready for writing again.
    blocking-flush: func() -> result<_, stream-error>;
    /// Create a `pollable` which will resolve once the output-stream
    /// is ready for more writing, or an error has occured. When this
    /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an
    /// error.
    ///
    /// If the stream is closed, this pollable is always ready immediately.
    ///
    /// The created `pollable` is a child resource of the `output-stream`.
    /// Implementations may trap if the `output-stream` is dropped before
    /// all derived `pollable`s created with this function are dropped.
    subscribe: func() -> pollable;
    /// Write zeroes to a stream.
    ///
    /// This should be used precisely like `write` with the exact same
    /// preconditions (must use check-write first), but instead of
    /// passing a list of bytes, you simply pass the number of zero-bytes
    /// that should be written.
    write-zeroes: func(len: u64) -> result<_, stream-error>;
    /// Perform a write of up to 4096 zeroes, and then flush the stream.
    /// Block until all of these operations are complete, or an error
    /// occurs.
    ///
    /// This is a convenience wrapper around the use of `check-write`,
    /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with
    /// the following pseudo-code:
    ///
    /// ```text
    /// let pollable = this.subscribe();
    /// while num_zeroes != 0 {
    /// // Wait for the stream to become writable
    /// pollable.block();
    /// let Ok(n) = this.check-write(); // eliding error handling
    /// let len = min(n, num_zeroes);
    /// this.write-zeroes(len);         // eliding error handling
    /// num_zeroes -= len;
    /// }
    /// this.flush();
    /// // Wait for completion of `flush`
    /// pollable.block();
    /// // Check for any errors that arose during `flush`
    /// let _ = this.check-write();         // eliding error handling
    /// ```
    blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error>;
    /// Read from one stream and write to another.
    ///
    /// The behavior of splice is equivelant to:
    /// 1. calling `check-write` on the `output-stream`
    /// 2. calling `read` on the `input-stream` with the smaller of the
    /// `check-write` permitted length and the `len` provided to `splice`
    /// 3. calling `write` on the `output-stream` with that read data.
    ///
    /// Any error reported by the call to `check-write`, `read`, or
    /// `write` ends the splice and reports that error.
    ///
    /// This function returns the number of bytes transferred; it may be less
    /// than `len`.
    splice: func(src: borrow<input-stream>, len: u64) -> result<u64, stream-error>;
    /// Read from one stream and write to another, with blocking.
    ///
    /// This is similar to `splice`, except that it blocks until the
    /// `output-stream` is ready for writing, and the `input-stream`
    /// is ready for reading, before performing the `splice`.
    blocking-splice: func(src: borrow<input-stream>, len: u64) -> result<u64, stream-error>;
  }
}

world imports {
  import error;
  import poll;
  import streams;
}
