Graven

Proposal 0001: imports between schema files

Status: accepted and implemented (see Imports).

Motivation

Every .graven file is self-contained today. Types that many schemas need — an identifier, a money amount, a timestamp, an address — have to be copied into each file, and nothing checks that the copies stay identical.

Decisions

Syntax

// common.graven
namespace common;

/// RFC 4122 identifier, stored inline as 16 bytes.
struct Uuid { hi: u64, lo: u64 }

/// An amount in minor units of an ISO 4217 currency.
struct Money { cents: i64, currency: u16 }
// orders.graven
namespace orders;

import common;

table Order {
  id    @0: common.Uuid;
  total @1: common.Money;
}

import lines come after namespace and before any declaration. Unqualified names refer only to the file's own declarations, so adding a type to common can never change the meaning of a name in another file.

Resolution and checks

Namespaces and schema ids

A table keeps the namespace of the file that declares it: common.Address sent on its own has the schema id of "common.Address", wherever it is used. The existing schema id collision check covers imports unchanged.

Lockfiles and compatibility

Each file keeps its own lockfile. The importing file's lock also records, under an imports section, the layout of every imported type it uses (directly or through other imported types). common's own lock protects common against itself; the copy in orders.graven.lock makes gravenc update orders.graven refuse a change to common that would break orders, even when common was updated in a separate change. In a pull request that changes Money, the diff of every dependent lock shows who is affected.

Compatibility rules apply to imported types exactly as to local ones. Two new rules:

Rule When
import-removed a type the file uses no longer exists in the imported namespace
import-changed an imported type changed in a way the existing rules refuse; the message names the rule and the namespace that owns the type

Generated code (Rust)

Each file generates its own module, as today. The importing module refers to the imported one as super::<segment>:

pub mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); }
pub mod orders { include!(concat!(env!("OUT_DIR"), "/orders.rs")); }

orders::Order::total() returns common::Money: the same Rust type everywhere it is used.

Tools

Commands that take a single schema (inspect, to-json, from-json, diff, update) find imported namespaces among the .graven files in the schema's directory and its subdirectories, or in directories given with -I <dir>.

Alternatives considered

Test plan