Module chill::path [] [src]

Types and traits for specifying databases, documents, views, etc.

Chill provides a rich set of types with which an application may specify CouchDB documents, views, attachments, and other database resources. By using special types—and not plain strings—the application programmer benefits from compile-time checks against many mundane mistakes, such as:

This page explains these types and their underlying design principles. Let's start with Chill's distinction between names, ids, and paths.

Names, ids, and paths

Chill has three categories of types for specifying CouchDB resources: names, ids, and paths. Knowing the differences between these three categories is critical for using Chill effectively.

Each name, id, and path is further divided into an owning type and a borrowing type.

Owning vs borrowing types

For each name, id, and path, Chill provides an owning type and a borrowing type. This distinction is similar to the PathBuf and Path types in the Rust standard library. However, in Chill, the borrowing types end with a -Ref suffix (e.g., DatabaseNameRef, DocumentPathRef), and the owning types lack a suffix (e.g., DatabaseName and DocumentPath).

use chill::*;

// Statically allocated:
let borrowing = DatabaseNameRef::from("foo");
assert_eq!("foo", borrowing.to_string());

// Heap-allocated copy of `borrowing`:
let owning = DatabaseName::from(borrowing);
assert_eq!("foo", owning.to_string());

// Borrowed from the heap, with a lifetime equivalent to `owning`:
let also_borrowing = DatabaseNameRef::from(&owning);
assert_eq!("foo", also_borrowing.to_string());

Another difference between Chill's borrowing types and the Path type in the Standard Library is that Chill's borrowing types are sized types. This means they don't need to be used behind a pointer such as & or Box. However, because they implement Copy and have borrowing semantics, in many ways they act like references. Consult the types' individual documentation for details.

By providing owning and borrowing types, Chill allows applications to eliminate heap allocations in many cases. Most methods in Chill that have a name, id, or path parameter use the borrowing type for that parameter, meaning no allocation is necessary. Conversion traits make this happen conveniently.

Conversion traits

The basic conversion traits are From and Into in the Standard Library. Names and ids—regardless whether they're owning or borrowing—implement From liberally, whereas paths implement From conservatively. Each path provides a custom conversion trait because path-parsing is fallible and may return a parse error.

Here are some examples:

use chill::*;

// Construct a name or id from a &str:
let db_name = DatabaseNameRef::from("foo");
let db_name = DatabaseName::from("foo");
let doc_id = DocumentIdRef::from("_design/bar");
let doc_id = DocumentId::from("_design/bar");

// Construct an owning name or id by moving a String into it:
let db_name = DatabaseName::from(String::from("foo"));
let doc_id = DocumentId::from(String::from("_design/bar"));

// Convert between owning and borrowing types:
let a = DatabaseNameRef::from("foo"); // does not allocate
let b = DatabaseName::from(a);        // allocates on the heap
let c = DatabaseNameRef::from(&b);    // does not allocate

// Document names are split into three subtypes: normal, design, and local.
let normal = NormalDocumentNameRef::from("foo");
let design = DesignDocumentNameRef::from("foo");
let local = LocalDocumentNameRef::from("foo");

// Take advantage of the document subtypes to enforce the correct //
// path-segment prefix (with zero heap allocations!):
let doc_id = DocumentIdRef::from(normal);
assert_eq!("foo", doc_id.to_string());
let doc_id = DocumentIdRef::from(design);
assert_eq!("_design/foo", doc_id.to_string());
let doc_id = DocumentIdRef::from(local);
assert_eq!("_local/foo", doc_id.to_string());

// Paths may be converted from a string literal and must begin with a slash.
let db_path = "/foo".into_database_path().unwrap();
assert_eq!("foo", db_path.database_name().to_string());
let doc_path = "/foo/_design/bar".into_document_path().unwrap();
assert_eq!("foo", doc_path.database_name().to_string());
assert_eq!("_design/bar", doc_path.document_id().to_string());

// Path conversions may fail:
"foo".into_database_path().unwrap_err();      // no leading slash
"/foo/bar".into_database_path().unwrap_err(); // too many segments
"/foo".into_document_path().unwrap_err();     // too few segments

Path conversions are special and deserve their own attention.

Path conversions

Unlike with names and ids, an application may construct a path from a static string but not from a runtime string. Instead, each path type provides a custom conversion trait (e.g., IntoDocumentPath) that allows the application to construct a path from constituent parts.

use chill::*;

// From a static string:
let source = "/foo/_design/bar";
let doc_path = source.into_document_path().unwrap();
assert_eq!("foo", doc_path.database_name().to_string());
assert_eq!("_design/bar", doc_path.document_id().to_string());

// From constituent parts:
let source = (DatabaseNameRef::from("foo"),
              DesignDocumentNameRef::from("bar"));
let doc_path = source.into_document_path().unwrap();
assert_eq!("foo", doc_path.database_name().to_string());
assert_eq!("_design/bar", doc_path.document_id().to_string());

// From constituent parts, which can be strings:
let source = ("/foo", "_design/bar");
let doc_path = source.into_document_path().unwrap();
assert_eq!("foo", doc_path.database_name().to_string());
assert_eq!("_design/bar", doc_path.document_id().to_string());

The idea here is that Chill prevents the application from concatenating URL path segments to form a path. This eliminates corner cases related to percent-encoding. Consider this example:

use chill::*;

let source = ("/foo", "bar/qux");
let db_path = source.into_document_path().unwrap();

// URI path: /foo/bar%2Fqux

CouchDB allows / as a valid character in a document name, though it must be percent-encoded as %2F when serialized in the request line of an HTTP request.

By forcing the application to construct the path from constituent parts, Chill guarantees that percent-encoding is correct. But what happens when parsing a static string?

use chill::*;

let source = "/foo/bar%2Fqux";
let db_path = source.into_document_path().unwrap();

// URI path: /foo/bar%252Fqux

The program compiles but doesn't do what's expected. Instead, it percent-encodes the % character (as %25) because % is also a valid character for document names and Chill has no way to disambiguate.

Chill could make static-string-conversions illegal and eliminate this corner case, but Chill's API would be less ergonomic. This is a case where convenience trumps type-safety. Instead, the application programmer must abide this one simple rule:

The application never observes a percent-encoded character.

If the programmer had followed this rule then they wouldn't have tried to manually percent-encode the path. But what if they tried this:

use chill::*;

let source = "/foo/bar/qux";
let db_path = source.into_document_path().unwrap(); // Panics!

The string "/foo/bar/qux" is an invalid document path because it contains too many path segments. It turns out some paths cannot be expressed as static strings and must instead be constructed from constituent parts. However, such cases are rare.

Invalid names, ids, and paths

CouchDB imposes many restrictions on resource names, and though Chill catches some parse errors when constructing paths, Chill doesn't enforce validity. For example, CouchDB requires all database names to begin with a letter, but Chill will allow the following:

use chill::*;
let db_name = DatabaseNameRef::from("_not_a_valid_name");

This compiles, but if the application uses the database name in a CouchDB action, then it will receive an error from the server.

This is true for all names, ids, and paths. The rationale is that the CouchDB server is the source of truth for validity, and Chill makes no attempt to duplicate this functionality.

It's also possible to do something like this:

use chill::*;
let source = ("/foo", NormalDocumentNameRef::from("_design/bar"));
let doc_path = source.into_document_path().unwrap();

// URI path: /foo/_design%2Fbar

In this example, the application may have intended to construct a path for a normal document, but Chill unambiguously constructed a path for a design document. This is a loophole in the Chill type system and further shows how Chill allows convenience to trump type-safety in some cases. Application programmers should be mindful when converting from raw strings.

Structs

AttachmentName

A heap-allocated attachment name, owned and managed internally.

AttachmentNameRef

A reference to a attachment name, owned elsewhere.

AttachmentPath
AttachmentPathRef
DatabaseName

A heap-allocated database name, owned and managed internally.

DatabaseNameRef

A reference to a database name, owned elsewhere.

DatabasePath
DatabasePathRef
DesignDocumentName

A heap-allocated design document name, owned and managed internally.

DesignDocumentNameRef

A reference to a design document name, owned elsewhere.

DesignDocumentPath
DesignDocumentPathRef
DocumentPath
DocumentPathRef
LocalDocumentName

A heap-allocated local document name, owned and managed internally.

LocalDocumentNameRef

A reference to a local document name, owned elsewhere.

NormalDocumentName

A heap-allocated normal document name, owned and managed internally.

NormalDocumentNameRef

A reference to a normal document name, owned elsewhere.

ViewName

A heap-allocated view name, owned and managed internally.

ViewNameRef

A reference to a view name, owned elsewhere.

ViewPath
ViewPathRef

Enums

DocumentId
DocumentIdRef

Constants

DESIGN_PREFIX
LOCAL_PREFIX
VIEW_PREFIX

Traits

IntoAttachmentPath
IntoDatabasePath
IntoDesignDocumentPath
IntoDocumentPath
IntoViewPath