urbit-http-api 0.7.1

Wraps the Urbit ship http api exposing it as an easy-to-use Rust crate.
Documentation
# Rust Urbit HTTP API

This library wraps the Urbit ship http interface exposing it as an easy-to-use Rust crate.

[![awesome urbit badge](https://img.shields.io/badge/~-awesome%20urbit-lightgrey)](https://github.com/urbit/awesome-urbit)

All implementation details such as auth cookies, EventSource connections, tracking message ids, and other such matters are automatically handled for you, and as enables a greatly improved experience in writing Rust apps that interact with Urbit ships.

This crate currently enables devs to:

1. Authorize oneself and open a channel with the ship.
2. Subscribe to any app/path so that one can read the events currently taking place inside of the ship.
3. Issue pokes/scries/threads.
4. Graph-store support with native Rust `Graph` interface for working with graphs.
5. Simple rust-based interface for Urbit chats.
6. Simple rust-based interface for Urbit notebooks.

## Basic Design

There are 3 main structs that this library exposes for interacting with an Urbit ship:

1. `ShipInterface`
2. `Channel`
3. `Subscription`

A `Subscription` is created by a `Channel` which is created by a `ShipInterface`. In other words, first you need to connect to an Urbit ship (using `ShipInterface`) before you can initiate a messaging `Channel`, before you can create a `Subscription` to an app/path.

### ShipInterface

The `ShipInterface` exposes a few useful methods that will be useful when creating apps.

The more commonly used methods below these allow you to create a new `ShipInterface` (thereby authorizing yourself with the ship), and create a new `Channel`.

```rust
/// Logs into the given ship and creates a new `ShipInterface`.
/// `ship_url` should be `http://ip:port` of the given ship. Example:
/// `http://0.0.0.0:8080`. `ship_code` is the code acquire from your ship
/// by typing `+code` in dojo.
pub fn new(ship_url: &str, ship_code: &str) -> Result<ShipInterface>;

/// Create a `Channel` using this `ShipInterface`
pub fn create_channel(&mut self) -> Result<Channel>;
```

You also have the ability to scry and run threads via spider.

```rust
/// Send a scry using the `ShipInterface`
pub fn scry(&self, app: &str, path: &str) -> Result<Response>;

/// Run a thread via spider using the `ShipInterface`
pub fn spider(&self, input_mark: &str, output_mark: &str, thread_name: &str, body: &JsonValue) -> Result<Response>;
```

### Channel

`Channel` is the most useful struct, because it holds methods related to interacting with both pokes and subscriptions.

It is instructive to look at the definition of the `Channel` struct to understand how it works:

```rust
// A Channel which is used to interact with a ship
pub struct Channel<'a> {
    /// `ShipInterface` this channel is created from
    pub ship_interface: &'a ShipInterface,
    /// The uid of the channel
    pub uid: String,
    /// The url of the channel
    pub url: String,
    // The list of `Subscription`s for this channel
    pub subscription_list: Vec<Subscription>,
    // / The `EventSource` for this channel which reads all of
    // / the SSE events.
    event_receiver: ReceiverSource,
    /// The current number of messages that have been sent out (which are
    /// also defined as message ids) via this `Channel`
    pub message_id_count: u64,
}
```

Once a `Channel` is created, an `EventSource` connection is created with the ship on a separate thread. This thread accepts all of the incoming events, and queues them on a (Rust) unbounded channel which is accessible internally via the `event_receiver`. This field itself isn't public, but processing events in this crate is handled with a much higher-level interface for the app developer.

Take note that a `Channel` has a `subscription_list`. As you will see below, each `Channel` exposes methods for creating subscriptions, which automatically get added to the `subscription_list`.
Once `Subscription`s are created/added to the list, the `Channel` will evidently start to receive event messages via SSE (which will be queued for reading in the `event_receiver`).

From the app developer's perspective, all one has to do is call the `parse_event_messages()` method on your `Channel`, and all of the queued events will be processed and passed on to the correct `Subscription`'s `message_list`. This is useful once multiple `Subscriptions` are created on a single channel, as the messages will be pre-sorted automatically for you.

Once the event messages are parsed, then one can simply call the `find_subscription` method in order to interact with the `Subscription` and read its messages.

The following are the useful methods exposed by a `Channel`:

```rust
/// Sends a poke over the channel
pub fn poke(&mut self, app: &str, mark: &str, json: &JsonValue) -> Result<Response>;

/// Create a new `Subscription` and thus subscribes to events on the ship with the provided app/path.
pub fn create_new_subscription(&mut self, app: &str, path: &str) -> Result<CreationID>;

/// Parses SSE messages for this channel and moves them into
/// the proper corresponding `Subscription`'s `message_list`.
pub fn parse_event_messages(&mut self);

/// Finds the first `Subscription` in the list which has a matching
/// `app` and `path`;
pub fn find_subscription(&self, app: &str, path: &str) -> Option<&Subscription>;

/// Finds the first `Subscription` in the list which has a matching
/// `app` and `path`, removes it from the list, and tells the ship
/// that you are unsubscribing.
pub fn unsubscribe(&mut self, app: &str, path: &str) -> Option<bool>;

/// Deletes the channel
pub fn delete_channel(self);

/// Exposes an interface for interacting with a ship's Graph Store directly.
pub fn graph_store(&mut self) -> GraphStore;

/// Exposes an interface for interacting with Urbit chats.
pub fn chat(&mut self) -> Chat;

/// Exposes an interface for interacting with Urbit notebooks.
pub fn notebook(&mute self) -> Notebook;

```

### Subscription

As mentioned in the previous section, a `Subscription` contains it's own `message_list` field where messages are stored after a `Channel` processes them.

From an app developer's perspective, this is the only useful feature of the `Subscription` struct. Once acquired, it is used simply to read the messages.

To improve the message reading experience, the `Subscription` struct exposes a useful method:

```rust
/// Pops a message from the front of `Subscription`'s `message_list`.
/// If no messages are left, returns `None`.
pub fn pop_message(&mut self) -> Option<String>;
```

## Code Examples

### Poke Example

This example displays how to connect to a ship using a `ShipInterface`, opening a `Channel`, issuing a `poke` over said channel, and then deleting the `Channel` to finish.

```rust
// Import the `ShipInterface` struct
use urbit_http_api::ShipInterface;

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();

    // Issue a poke over the channel
    let poke_res = channel.poke("hood", "helm-hi", &"This is a poke".into());

    // Cleanup/delete the `Channel` once finished
    channel.delete_channel();
}
```

### Graph Store Subscription Example

This example shows how to create, interact with, and delete a `Subscription`. In this scenario we desire to read all new updates from Graph Store via our `Subscription` for 10 seconds, and then perform cleanup.

```rust
use std::thread;
use std::time::Duration;
use urbit_http_api::ShipInterface;

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();
    // Create a `Subscription` for the `graph-store` app with the `/updates` path. This `Subscription`
    // is automatically added to the `Channel`'s `subscription_list`.
    channel
        .create_new_subscription("graph-store", "/updates")
        .unwrap();

    // Create a loop that iterates 10 times
    for _ in 0..10 {
        // Parse all of the event messages to move them into the correct
        // `Subscription`s in the `Channel`'s `subscription_list`.
        channel.parse_event_messages();

        // Find our graph-store `Subscription`
        let gs_sub = channel.find_subscription("graph-store", "/updates").unwrap();

        // Pop all of the messages from our `gs_sub` and print them
        loop {
            let pop_res = gs_sub.pop_message();
            if let Some(mess) = &pop_res {
                println!("Message: {:?}", mess);
            }
            // If no messages left, stop
            if let None = &pop_res {
                break;
            }
        }

        // Wait for 1 second before trying to parse the event messages again
        thread::sleep(Duration::new(1, 0));
    }

    // Once finished, unsubscribe/destroy our `Subscription`
    channel.unsubscribe("graph-store", "/updates");
    // Delete the channel
    channel.delete_channel();
}
```

### Urbit Chat Messaging Example

This example displays how to connect to a ship and send a message to an Urbit chat using the `Chat` struct interface.

```rust
// Import the `ShipInterface` struct
use urbit_http_api::{ShipInterface, chat::Message};

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();

    // Create a `Message` which is formatted properly for an Urbit chat
    let message = Message::new()
        // Add text to your message
        .add_text("Checkout this cool article by ~wicdev-wisryt:")
        // Add a URL link to your message after the previous text (which gets automatically added on a new line)
        .add_url("https://urbit.org/blog/io-in-hoon/")
        // Add an image URL to your message after the previous url (which gets automatically added on a new line as a rendered image)
        .add_url("https://media.urbit.org/site/posts/essays/zion-canyon-1.jpg");
    // Send the message to a chat hosted by ~zod named "test-93".
    // Note the connected ship must already have joined the chat in order to send a message to the chat.
    let _mess_res = channel
        .chat()
        .send_message("~zod", "test-93", &message);

    // Cleanup/delete the `Channel` once finished
    channel.delete_channel();
}
```

### Urbit Chat Subscription Example

This example shows how to utilize the higher-level `Chat` interface to subscribe to a chat and read all of the messages being posted in said chat.

```rust
use std::thread;
use std::time::Duration;
use urbit_http_api::ShipInterface;

fn main() {
    // Create a new `ShipInterface` for a local ~zod ship
    let mut ship_interface =
        ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    // Create a `Channel`
    let mut channel = ship_interface.create_channel().unwrap();
    // Subscribe to a specific chat, and obtain a `Receiver` back which contains a stream of messages from the chat
    let chat_receiver = channel
        .chat()
        .subscribe_to_chat("~mocrux-nomdep", "test-93")
        .unwrap();

    // Create a loop that iterates 10 times
    for _ in 0..10 {
        // If a message has been posted to the chat, unwrap it and acquire the `AuthoredMessage`
        if let Ok(authored_message) = chat_receiver.try_recv() {
            // Pretty print the author ship @p and the message contents
            println!(
                "~{}:{}",
                authored_message.author,
                authored_message.message.to_formatted_string()
            );
        }
        // Wait for 1 second before checking again
        thread::sleep(Duration::new(1, 0));
    }

    // Delete the channel
    channel.delete_channel();
}
```

---

This library was created by ~mocrux-nomdep([Robert Kornacki](https://github.com/robkorn)).