Skip to main content

TableLogic

Trait TableLogic 

Source
pub trait TableLogic:
    Send
    + Sync
    + 'static {
    type Row: Serialize + DeserializeOwned + Send + Sync;

    // Required methods
    fn name(&self) -> &'static str;
    fn file(&self) -> &'static str;
    fn title(&self) -> &'static str;
    fn schema(&self, ctx: &Context) -> Result<Schema, ApiError>;
    fn validate(
        &self,
        rows: &[Self::Row],
        ctx: &Context,
    ) -> Result<Vec<ValidationError>, ApiError>;

    // Provided methods
    fn parse(&self, text: &str) -> Result<Vec<Self::Row>, ParseError> { ... }
    fn serialize(&self, rows: &[Self::Row]) -> Result<String, Error> { ... }
    fn derive(
        &self,
        _rows: &[Self::Row],
        _ctx: &Context,
    ) -> Result<Vec<Value>, ApiError> { ... }
    fn siblings(&self, _ctx: &Context) -> Result<Value, ApiError> { ... }
}
Expand description

One table’s per-repository logic.

parse and serialize default to plain JSONL, so a table whose row type serializes the way it is stored implements neither. derive and siblings default to nothing, so a table with no derived values and no cross-table data implements neither.

Required Associated Types§

Required Methods§

Source

fn name(&self) -> &'static str

The route segment and ?table= value, such as books. The names app, health, shutdown, and stop are reserved: the first three are control endpoints and the fourth is the stop subcommand, which clap takes before the positional table name.

Source

fn file(&self) -> &'static str

The file under Data/, such as Books.jsonl.

It is a bare file name: no directory separators, nothing absolute, and not . or ... Building a crate::Server over a table that names anything else panics. It must exist before the editor can open the table; the editor edits a table, it does not create one.

Source

fn title(&self) -> &'static str

The heading the shell shows, such as Books.

Source

fn schema(&self, ctx: &Context) -> Result<Schema, ApiError>

Rebuilt on every read, so sibling-derived options and widths are current.

Source

fn validate( &self, rows: &[Self::Row], ctx: &Context, ) -> Result<Vec<ValidationError>, ApiError>

Problems with the rows, each reported against the row’s one-based position in the set. A write is not refused because of them: the editor persists what it is given and shows the errors beside the cells.

Provided Methods§

Source

fn parse(&self, text: &str) -> Result<Vec<Self::Row>, ParseError>

Source

fn serialize(&self, rows: &[Self::Row]) -> Result<String, Error>

Examples found in repository?
examples/library/main.rs (line 982)
943    fn lend(fields: &Fields, args: &ViewArgs, ctx: &Context) -> Result<String, ApiError> {
944        let title = args.get_or("title", "");
945        let borrower = fields.text("borrower");
946        let days = fields.integer("days")?;
947        let from = fields.date("from")?;
948        let condition = fields.text("condition").to_lowercase();
949
950        if borrower.is_empty() {
951            return Err(ApiError::bad_request("a loan needs a borrower"));
952        }
953        if days < 1 {
954            return Err(ApiError::bad_request("a loan is at least one day long"));
955        }
956        let start = days_from_civil(from).ok_or_else(|| {
957            ApiError::bad_request(format!("{from} is not a date in the calendar"))
958        })?;
959        let due = civil_from_days(start + days);
960
961        let mut books: Vec<Book> = ctx.rows(BOOKS_FILE)?;
962        let book = books
963            .iter_mut()
964            .find(|b| b.title == title)
965            .ok_or_else(|| ApiError::bad_request(format!("no book is called \"{title}\"")))?;
966        if book.lent.unwrap_or(false) {
967            return Err(ApiError::bad_request(format!("\"{title}\" is already out")));
968        }
969        book.lent = Some(true);
970        book.due = due.clone();
971        book.notes = format!("Lent to {borrower} on {from}, {condition}.");
972
973        let problems = Books.validate(&books, ctx)?;
974        if let Some(first) = problems.first() {
975            return Err(ApiError::bad_request(format!(
976                "the loan would leave row {} in a state the table refuses: {}",
977                first.line, first.message
978            )));
979        }
980
981        let text = Books
982            .serialize(&books)
983            .map_err(|e| ApiError::server(format!("could not serialize {BOOKS_FILE}: {e}")))?;
984        ctx.write(BOOKS_FILE, &text)?;
985
986        Ok(format!("\"{title}\" is out to {borrower} until {due}."))
987    }
Source

fn derive( &self, _rows: &[Self::Row], _ctx: &Context, ) -> Result<Vec<Value>, ApiError>

Values the editor displays but does not store, parallelling rows index for index. A computed column reads one of the keys of each row’s object through its from.

Source

fn siblings(&self, _ctx: &Context) -> Result<Value, ApiError>

Cross-table data a bespoke editor needs. The schema-driven editor ignores it.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§