mod chunk;
mod compile;
mod read;
pub use chunk::{
CHUNK_ENCODING, ChunkData, DecodeError, FieldRecord, MAXIMUM_CHUNK_BYTES, MAXIMUM_DEPTH,
MAXIMUM_RECORD_BYTES, ObjectRecord, PortalRecord, RECORD_VERSION, ZSTD_LEVEL, decode_chunk,
decode_record, encode_chunk, encode_record,
};
pub use compile::{COMPILER, Written, compile, write_pack};
pub use read::{
Anchor, DEFAULT_MAXIMUM_DECODED_BYTES, DEFAULT_MAXIMUM_PACK_BYTES, Definition, Identity,
MAXIMUM_OBJECT_INDEX, Pack, Page, Plane, StairwellTile, Summary, UidLocation, VerifyLimits,
Zone,
};
use std::fmt;
pub const FORMAT: &str = "tilewright.pack/1";
pub const APPLICATION_ID: i32 = 0x5457_5031;
pub const USER_VERSION: i32 = 2;
pub const META_KEYS: [&str; 7] = [
"chunk_encoding",
"compiler",
"format",
"profile",
"profile_id",
"profile_version",
"source_digest",
];
pub const SCHEMA_TABLES: [(&str, &str); 10] = [
(
"meta",
"CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL) WITHOUT ROWID",
),
(
"strings",
"CREATE TABLE strings (id INTEGER PRIMARY KEY, value TEXT NOT NULL UNIQUE)",
),
(
"planes",
"CREATE TABLE planes (id TEXT PRIMARY KEY, kind TEXT NOT NULL, chunk_size INTEGER NOT NULL, document TEXT NOT NULL) WITHOUT ROWID",
),
(
"zones",
"CREATE TABLE zones (plane TEXT NOT NULL, id TEXT NOT NULL, parent TEXT, document TEXT NOT NULL, PRIMARY KEY (plane, id)) WITHOUT ROWID",
),
(
"anchors",
"CREATE TABLE anchors (plane TEXT NOT NULL, id TEXT NOT NULL, layer INTEGER NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, PRIMARY KEY (plane, id)) WITHOUT ROWID",
),
(
"stairwells",
"CREATE TABLE stairwells (plane TEXT NOT NULL, id TEXT NOT NULL, document TEXT NOT NULL, PRIMARY KEY (plane, id)) WITHOUT ROWID",
),
(
"stairwell_steps",
"CREATE TABLE stairwell_steps (plane TEXT NOT NULL, layer INTEGER NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, stairwell TEXT NOT NULL, step INTEGER NOT NULL, proto TEXT NOT NULL, PRIMARY KEY (plane, layer, x, y)) WITHOUT ROWID",
),
(
"chunks",
"CREATE TABLE chunks (plane TEXT NOT NULL, layer INTEGER NOT NULL, cx INTEGER NOT NULL, cy INTEGER NOT NULL, encoding TEXT NOT NULL, data BLOB NOT NULL, PRIMARY KEY (plane, layer, cx, cy)) WITHOUT ROWID",
),
(
"definitions",
"CREATE TABLE definitions (type TEXT NOT NULL, id TEXT NOT NULL, document TEXT NOT NULL, PRIMARY KEY (type, id)) WITHOUT ROWID",
),
(
"object_uids",
"CREATE TABLE object_uids (uid TEXT PRIMARY KEY, plane TEXT NOT NULL, layer INTEGER NOT NULL, cx INTEGER NOT NULL, cy INTEGER NOT NULL, object INTEGER NOT NULL) WITHOUT ROWID",
),
];
pub(crate) fn is_identifier(text: &str) -> bool {
let bytes = text.as_bytes();
(1..=128).contains(&bytes.len())
&& (bytes[0].is_ascii_lowercase() || bytes[0].is_ascii_digit())
&& bytes
.iter()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"_.-".contains(byte))
}
pub(crate) fn is_namespace(text: &str) -> bool {
let (domain, rest) = text.split_once('/').unwrap_or((text, ""));
domain.contains('.')
&& is_identifier(domain)
&& !domain.starts_with('.')
&& !domain.ends_with('.')
&& (rest.is_empty() || is_identifier(rest))
}
pub(crate) fn is_field_name(text: &str) -> bool {
let bytes = text.as_bytes();
(1..=64).contains(&bytes.len())
&& bytes[0].is_ascii_lowercase()
&& bytes
.iter()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'_')
}
pub(crate) fn canonical_limits() -> tilewright_jsonc::Limits {
let limits = tilewright_jsonc::Limits::default();
tilewright_jsonc::Limits {
max_line_bytes: limits.max_input_bytes,
..limits
}
}
pub(crate) static SCHEMA: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
SCHEMA_TABLES
.iter()
.map(|(_, sql)| format!("{sql};"))
.collect::<Vec<_>>()
.join("\n")
});
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum ErrorCode {
Io,
Sqlite,
NotAFile,
TooLarge,
FileLength,
Schema,
Identity,
FreePages,
Meta,
Profile,
String,
StringIds,
Plane,
Zone,
Anchor,
Stairwell,
StairwellStep,
Definition,
Chunk,
ChunkEncoding,
ChunkData,
ChunkRecord,
Reference,
ObjectUid,
Integrity,
Digest,
DecodeBudget,
Invalid,
}
impl ErrorCode {
pub const ALL: [Self; 28] = [
Self::Io,
Self::Sqlite,
Self::NotAFile,
Self::TooLarge,
Self::FileLength,
Self::Schema,
Self::Identity,
Self::FreePages,
Self::Meta,
Self::Profile,
Self::String,
Self::StringIds,
Self::Plane,
Self::Zone,
Self::Anchor,
Self::Stairwell,
Self::StairwellStep,
Self::Definition,
Self::Chunk,
Self::ChunkEncoding,
Self::ChunkData,
Self::ChunkRecord,
Self::Reference,
Self::ObjectUid,
Self::Integrity,
Self::Digest,
Self::DecodeBudget,
Self::Invalid,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Io => "pack.io",
Self::Sqlite => "pack.sqlite",
Self::NotAFile => "pack.not_a_file",
Self::TooLarge => "pack.too_large",
Self::FileLength => "pack.file_length",
Self::Schema => "pack.schema",
Self::Identity => "pack.identity",
Self::FreePages => "pack.free_pages",
Self::Meta => "pack.meta",
Self::Profile => "pack.profile",
Self::String => "pack.string",
Self::StringIds => "pack.string_ids",
Self::Plane => "pack.plane",
Self::Zone => "pack.zone",
Self::Anchor => "pack.anchor",
Self::Stairwell => "pack.stairwell",
Self::StairwellStep => "pack.stairwell_step",
Self::Definition => "pack.definition",
Self::Chunk => "pack.chunk",
Self::ChunkEncoding => "pack.chunk_encoding",
Self::ChunkData => "pack.chunk_data",
Self::ChunkRecord => "pack.chunk_record",
Self::Reference => "pack.reference",
Self::ObjectUid => "pack.object_uid",
Self::Integrity => "pack.integrity",
Self::Digest => "pack.digest",
Self::DecodeBudget => "pack.decode_budget",
Self::Invalid => "pack.invalid",
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Sqlite(rusqlite::Error),
Chunk(DecodeError),
Rejected(ErrorCode, String),
Invalid(String),
}
impl Error {
#[must_use]
pub const fn code(&self) -> ErrorCode {
match self {
Self::Io(_) => ErrorCode::Io,
Self::Sqlite(_) => ErrorCode::Sqlite,
Self::Chunk(_) => ErrorCode::ChunkRecord,
Self::Rejected(code, _) => *code,
Self::Invalid(_) => ErrorCode::Invalid,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => write!(formatter, "I/O error: {error}"),
Self::Sqlite(error) => write!(formatter, "SQLite error: {error}"),
Self::Chunk(error) => error.fmt(formatter),
Self::Rejected(_, message) | Self::Invalid(message) => formatter.write_str(message),
}
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
impl From<rusqlite::Error> for Error {
fn from(error: rusqlite::Error) -> Self {
Self::Sqlite(error)
}
}
#[cfg(test)]
mod tests;