package wasi:io@0.2.6;

/// 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.
@since(version = 0.2.0)
interface streams {
    @since(version = 0.2.0)
    use error.{error};
    @since(version = 0.2.0)
    use poll.{pollable};

    /// An error for input-stream and output-stream operations.
    @since(version = 0.2.0)
    variant stream-error {
        /// The last operation (a write or flush) failed before completion.
        ///
        /// More information is available in the `error` payload.
        ///
        /// After this, the stream will be closed. All future operations return
        /// `stream-error::closed`.
        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`.
    @since(version = 0.2.0)
    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.
        @since(version = 0.2.0)
        read: func(
            /// The maximum number of bytes to read
            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`.
        @since(version = 0.2.0)
        blocking-read: func(
            /// The maximum number of bytes to read
            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.
        @since(version = 0.2.0)
        skip: func(
            /// The maximum number of bytes to skip.
            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`.
        @since(version = 0.2.0)
        blocking-skip: func(
            /// The maximum number of bytes to skip.
            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.
        @since(version = 0.2.0)
        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`.
    ///
    /// Dropping an `output-stream` while there's still an active write in
    /// progress may result in the data being lost. Before dropping the stream,
    /// be sure to fully flush your writes.
    @since(version = 0.2.0)
    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.
        @since(version = 0.2.0)
        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.
        @since(version = 0.2.0)
        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
        /// ```
        @since(version = 0.2.0)
        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.
        @since(version = 0.2.0)
        flush: func() -> result<_, stream-error>;

        /// Request to flush buffered output, and block until flush completes
        /// and stream is ready for writing again.
        @since(version = 0.2.0)
        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 occurred. 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.
        @since(version = 0.2.0)
        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.
        @since(version = 0.2.0)
        write-zeroes: func(
            /// The number of zero-bytes to write
            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
        /// ```
        @since(version = 0.2.0)
        blocking-write-zeroes-and-flush: func(
            /// The number of zero-bytes to write
            len: u64
        ) -> result<_, stream-error>;

        /// Read from one stream and write to another.
        ///
        /// The behavior of splice is equivalent 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`.
        @since(version = 0.2.0)
        splice: func(
            /// The stream to read from
            src: borrow<input-stream>,
            /// The number of bytes to splice
            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`.
        @since(version = 0.2.0)
        blocking-splice: func(
            /// The stream to read from
            src: borrow<input-stream>,
            /// The number of bytes to splice
            len: u64,
        ) -> result<u64, stream-error>;
    }
}
