msr/lib.rs
1//! # MSR — Morpion Solitaire Record format
2//!
3//! A small, self-describing interchange format for [Morpion Solitaire] games:
4//! the move list plus optional provenance (who/what/when produced it, how hard,
5//! and a few derived facts), in either a compact `MS1:` envelope or plain JSON.
6//! This crate is the reference reader, writer and **validator** for the format.
7//!
8//! It is deliberately self-contained — it does not depend on any solver — so any
9//! tool can read, write and verify records with it. The published crate is
10//! `morpion-solitaire-record`; it is imported as `msr`.
11//!
12//! ## Why MSR (vs. Pentasol)
13//!
14//! The community Pentasol text format covers only the 5T/5D variants and stores
15//! moves alone. MSR is lossless for **all four** variants (4T/4D/5T/5D), carries
16//! provenance metadata, and offers both a compact and a human-readable form. A
17//! Pentasol bridge is provided for migration.
18//!
19//! ## Example
20//!
21//! ```
22//! use msr::{Record, RecordMove, Variant, Direction};
23//!
24//! let moves = vec![RecordMove { x: 3, y: -1, dir: Direction::V, pos: 4 }];
25//! let mut rec = Record::new(Variant::T5, moves);
26//! rec.description = Some("a one-move demo".into());
27//!
28//! let compact = msr::encode(&rec).unwrap(); // "MS1:…"
29//! let back = msr::decode(&compact).unwrap();
30//! assert_eq!(rec, back);
31//! ```
32//!
33//! ## Specification & references
34//!
35//! The normative format definition is the MSR specification shipped with the
36//! source (`docs/spec/0.1/msr.md`) and the JSON Schema (`docs/spec/0.1/`). The rules
37//! validated here are those of Morpion Solitaire; see the project bibliography
38//! (`docs/BIBLIOGRAPHY.md`).
39//!
40//! [Morpion Solitaire]: https://en.wikipedia.org/wiki/Join_Five
41
42#![forbid(unsafe_code)]
43#![warn(missing_docs)]
44
45mod codec;
46mod cross;
47mod model;
48mod validate;
49
50pub use codec::{decode, encode, encode_json, PREFIX};
51pub use cross::initial_cross;
52pub use model::{Direction, Record, RecordMove, Solver, Variant};
53pub use validate::{validate, ValidationError};
54
55/// The format version this crate reads and writes (the `version` field),
56/// `major.minor`.
57pub const FORMAT_VERSION: &str = "0.1";
58
59/// The specification version implemented (`docs/spec/0.1/msr.md`).
60pub const SPEC_VERSION: &str = "0.1";
61
62/// Errors from encoding or decoding a record.
63#[derive(Debug)]
64pub enum Error {
65 /// The `MS1:` payload was not valid Base64.
66 Base64(String),
67 /// The Base64 payload did not DEFLATE-decompress.
68 Inflate(String),
69 /// The JSON was malformed or did not match the schema.
70 Json(serde_json::Error),
71}
72
73impl std::fmt::Display for Error {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Error::Base64(e) => write!(f, "invalid Base64: {e}"),
77 Error::Inflate(e) => write!(f, "invalid DEFLATE stream: {e}"),
78 Error::Json(e) => write!(f, "invalid JSON: {e}"),
79 }
80 }
81}
82
83impl std::error::Error for Error {
84 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
85 match self {
86 Error::Json(e) => Some(e),
87 _ => None,
88 }
89 }
90}
91
92impl From<serde_json::Error> for Error {
93 fn from(e: serde_json::Error) -> Self {
94 Error::Json(e)
95 }
96}