Skip to main content

Crate slpc

Crate slpc 

Source
Expand description

§slpc

The library half of the Rust implementation of slipcase, a container format that attaches metadata to a file.

A .slpc file is a ZIP archive holding a payload file of any type together with a TOML metadata document describing it. The two become one file, so copying, moving, or sending the payload carries its metadata along.

§Reading

fn main() -> Result<(), slpc::Error> {
    let mut c = slpc::Container::open("report.pdf.slpc")?;
    println!("{} holds {}", c.version(), c.payload_name());
    std::io::copy(&mut c.payload()?, &mut std::io::stdout())?;
    Ok(())
}

The payload is a stream and is never read into memory; payload_size reports how long it is without decompressing any of it, and borrows shared so the name and the size can be asked for in one expression. Metadata is exposed as a document that keeps comments, key order, and whitespace across a rewrite, and as the member’s bytes for a caller who wants a different parser or a hash.

§Writing

Nothing here takes or returns a container. Each of these reads a stream and writes a stream, so a rewrite cannot accidentally hold a payload in memory.

use slpc::toml_edit::DocumentMut;

fn main() -> Result<(), slpc::Error> {
    let out = std::fs::File::create("report.pdf.slpc")?;
    slpc::pack_file("report.pdf", DocumentMut::new(), out)?;
    Ok(())
}

The metadata argument is anything convertible into a DocumentMut, which is a document, a table, or, with toml_edit’s serde feature turned on by the caller, whatever toml_edit::ser::to_document makes of a struct or a map. Building metadata from nothing has no formatting to preserve, so there is nothing for that conversion to lose.

pack_reader takes a payload from any Read and needs no Seek at either end, so a container can be packed from a pipe into a socket.

§Changing one that already exists

Repack replaces the metadata, the payload, or both. Every other member is copied through as stored bytes, in the order it arrived in, whether or not this build can decompress it — which is what the specification requires of an implementation rewriting a container, and what lets one be changed without being fully understood.

fn main() -> Result<(), slpc::Error> {
    let source = std::fs::File::open("report.pdf.slpc")?;
    let out = std::fs::File::create("report-v2.pdf.slpc")?;

    slpc::Repack::new(source)
        .payload_file("report-v2.pdf")?
        .write(out)?;
    Ok(())
}

The source and the destination both seek: a ZIP’s central directory is at the end of the file, and a member copied through already knows its compressed size, which belongs in the header rather than in a promise of one to come. Packing has neither constraint and keeps its Write-only destination.

A payload written under a new name carries payload.file with it. A payload written under the name the container already used leaves the metadata member alone, byte for byte. rewrite_metadata and rewrite_metadata_bytes are the metadata-only case, which should not need a builder to say.

Everything on the way out is checked against the rules the read path reads by, so what this writes is what it would accept back.

§Putting one on disk

Everything above writes into a stream the caller supplies. Turning on the fs feature adds Destination, which writes a container to a path: through a temporary file beside it, with the permissions a file there should have, renamed into place at the end. A write that fails partway leaves nothing behind rather than a truncated container that looks like one.

slpc = { version = "0.3", features = ["fs"] }

It is a feature rather than part of the default surface because a caller writing into a socket or a buffer should not acquire a temporary-file dependency to do it.

§Validating

fn main() -> Result<(), slpc::Error> {
    match slpc::validate(std::fs::File::open("report.pdf.slpc")?)? {
        slpc::Verdict::Conformant => println!("conformant"),
        other => println!("{other}"),
    }
    Ok(())
}

Four verdicts rather than two. A container whose metadata member cannot be read is neither conformant nor non-conformant, and one declaring a version this build does not implement is outside the question rather than failing it.

A container can fail elsewhere and still carry a metadata document worth reading — payload.file naming no member leaves one that parsed cleanly — so metadata_of returns that document and asks no conformance question. It is not a verdict and says nothing about whether the container conforms; validate is the only function here that answers that.

§No vocabulary

The two structural keys have typed accessors. Every other key is passed through unexamined, because there is nothing to examine it against.

The specification lives in excelano/slipcase and is the authority on the format.

This crate implements it and has no standing to change it. The command-line tool built on it is slipcase, in the same repository.

§License

MIT. See LICENSE.

Re-exports§

pub use toml_edit;

Structs§

Container
A slipcase container, open for reading.
Destination
A file that appears under its real name only once it has been written.
Repack
Change a container, keeping everything that is not being changed.

Enums§

EntryKind
What kind of entry an archive says a member is.
Error
Anything the library can fail with.
Malformed
Why a byte stream is not a conformant container, or why the container asked for could not be written.
NameError
Why payload.file is not a name a payload may have.
Unsupported
The container may well be conformant, and this build cannot handle it.
Verdict
What can be said about a container after reading it.

Constants§

METADATA_MEMBER
The archive member holding the metadata (SPEC 2.1).
PAYLOAD_FILE_KEY
The metadata key naming the payload member (SPEC 2.2).
VERSION
The version of the specification this build implements.
VERSION_KEY
The metadata key naming the specification version (SPEC 2.2).

Functions§

check_payload_name
Check a name against SPEC 2.3.
metadata_of
The metadata document of a byte stream, asking no conformance question.
pack_file
Pack a payload named by a path, taking payload.file from the path.
pack_reader
Pack a payload read from a stream.
rewrite_metadata
Replace a container’s metadata, preserving everything else.
rewrite_metadata_bytes
Replace a container’s metadata with bytes chosen by the caller.
validate
Report what can be said about a byte stream as a container.

Type Aliases§

Result
The library’s result type.