From afb8a0e744e9dbbb3dd6b49c913383d4272d3119 Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Thu, 28 Dec 2023 23:16:42 +0900 Subject: [PATCH] doc: vastly improve API docs throughout all crates --- crates/stef-build/src/definition.rs | 1 + crates/stef-build/src/lib.rs | 99 +++++++++-- crates/stef-cli/src/main.rs | 2 +- crates/stef-compiler/src/lib.rs | 2 +- crates/stef-compiler/src/resolve/mod.rs | 4 + crates/stef-compiler/src/simplify.rs | 8 +- crates/stef-compiler/src/validate/mod.rs | 4 + crates/stef-derive/src/lib.rs | 19 ++- crates/stef-lsp/src/main.rs | 3 +- crates/stef-parser/src/error.rs | 16 +- crates/stef/src/buf/decode.rs | 204 ++++++++++++++++++++--- crates/stef/src/buf/encode.rs | 21 +++ crates/stef/src/buf/size.rs | 17 ++ crates/stef/src/lib.rs | 57 ++++++- crates/stef/src/varint.rs | 17 ++ 15 files changed, 427 insertions(+), 47 deletions(-) diff --git a/crates/stef-build/src/definition.rs b/crates/stef-build/src/definition.rs index 2a1f562..212c9ab 100644 --- a/crates/stef-build/src/definition.rs +++ b/crates/stef-build/src/definition.rs @@ -8,6 +8,7 @@ use stef_compiler::simplify::{ use super::{decode, encode, size}; use crate::{BytesType, Opts}; +/// Take a single schema and convert it into Rust source code. #[must_use] pub fn compile_schema(opts: &Opts, Schema { definitions, .. }: &Schema<'_>) -> TokenStream { let definitions = definitions.iter().map(|def| compile_definition(opts, def)); diff --git a/crates/stef-build/src/lib.rs b/crates/stef-build/src/lib.rs index 853f287..30da507 100644 --- a/crates/stef-build/src/lib.rs +++ b/crates/stef-build/src/lib.rs @@ -1,6 +1,6 @@ -#![allow(missing_docs, clippy::missing_errors_doc, clippy::missing_panics_doc)] +//! Code generator crate for Rust projects that can be used in `build.rs` build scripts. -use std::{convert::AsRef, fmt::Debug, path::PathBuf}; +use std::{convert::AsRef, env, fmt::Debug, fs, path::PathBuf}; use miette::Report; use stef_parser::Schema; @@ -13,37 +13,89 @@ mod definition; mod encode; mod size; +/// Shorthand for the standard result type, that defaults to the crate level's [`Error`] type. pub type Result = std::result::Result; +/// Errors that can happen when generating Rust source code from Stef schema files. #[derive(Error)] pub enum Error { + /// The required OUT_DIR env var doesn't exist. + #[error("missing OUT_DIR environment variable")] + NoOutDir, + /// One of the user-provided glob patterns is invalid. #[error("failed to parse the glob pattern {glob:?}")] Pattern { + /// Source error of the problem. #[source] source: glob::PatternError, + /// The problematic pattern. glob: String, }, + /// Failed to iterate over the matching files for a pattern. #[error("failed to read files of a glob pattern")] Glob { + /// Source error of the problem. #[source] source: glob::GlobError, }, + /// The file name resulting from a glob pattern didn't produce a usable file path. + #[error("failed to get the file name from a found file path")] + NoFileName, + /// The file name wasn't valid UTF-8. + #[error("the file name was not encoded in valid UTF-8")] + NonUtf8FileName, + /// Failed to create the output directory for generated Rust source files. #[error("failed creating output directory at {path:?}")] Create { + /// Source error of the problem. #[source] source: std::io::Error, + /// The output directory path. path: PathBuf, }, + /// Failed to read one of the schema files. #[error("failed reading schema file at {file:?}")] Read { + /// Source error of the problem. #[source] source: std::io::Error, + /// The problematic file. file: PathBuf, }, + /// Failed to parse a Stef schema. #[error("failed parsing schema from {file:?}:\n{report:?}")] - Parse { report: Report, file: PathBuf }, + Parse { + /// Detailed report about the problem. + report: Report, + /// The problematic schema file. + file: PathBuf, + }, + /// Failed to compile a Stef schema. #[error("failed compiling schema from {file:?}:\n{report:?}")] - Compile { report: Report, file: PathBuf }, + Compile { + /// Detailed report about the problem. + report: Report, + /// The problematic schema file. + file: PathBuf, + }, + /// The code generator produced Rust code that isn't valid. + #[error("failed to generate valid Rust code")] + InvalidCode { + /// Source error of the problem. + #[source] + source: syn::Error, + /// The invalid Rust source code. + code: String, + }, + /// Failed to write a generated Rust source file. + #[error("failed writing Rust source file to {file:?}")] + Write { + /// Source error of the problem. + #[source] + source: std::io::Error, + /// The problematic file. + file: PathBuf, + }, } impl Debug for Error { @@ -52,36 +104,49 @@ impl Debug for Error { } } +/// Instance of the compiler, which is responsible to generate Rust source code from schema files. #[derive(Default)] pub struct Compiler { + /// The data type to use for Stef's `bytes` type. bytes_type: BytesType, } +/// The data type to use for Stef's `bytes` type, that is used throughout all generated schemas. #[derive(Clone, Copy, Default)] pub enum BytesType { + /// Use the default `Vec` type from Rust's stdlib. #[default] VecU8, + /// Use the [`bytes::Bytes`](https://docs.rs/bytes/latest/bytes/struct.Bytes.html) type. Bytes, } +/// Additional options to adjust the behavior of the Rust code generator. #[derive(Default)] pub struct Opts { bytes_type: BytesType, } impl Compiler { + /// Change the type that is used to represent Stef `bytes` byte arrays. #[must_use] pub fn with_bytes_type(mut self, value: BytesType) -> Self { self.bytes_type = value; self } + /// Compile the given list of Stef schema files (glob patterns) into Rust source code. + /// + /// # Errors + /// + /// Will return an `Err` if any of the various cases happen, which are described in the + /// [`Error`] type. pub fn compile(&self, schemas: &[impl AsRef]) -> Result<()> { init_miette(); - let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("stef"); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").ok_or(Error::NoOutDir)?).join("stef"); - std::fs::create_dir_all(&out_dir).map_err(|source| Error::Create { + fs::create_dir_all(&out_dir).map_err(|source| Error::Create { source, path: out_dir.clone(), })?; @@ -96,7 +161,7 @@ impl Compiler { })? { let path = schema.map_err(|e| Error::Glob { source: e })?; - let input = std::fs::read_to_string(&path).map_err(|source| Error::Read { + let input = fs::read_to_string(&path).map_err(|source| Error::Read { source, file: path.clone(), })?; @@ -106,7 +171,11 @@ impl Compiler { } for (path, input) in &inputs { - let stem = path.file_stem().unwrap().to_str().unwrap(); + let stem = path + .file_stem() + .ok_or(Error::NoFileName)? + .to_str() + .ok_or(Error::NonUtf8FileName)?; let schema = Schema::parse(input, Some(path)).map_err(|e| Error::Parse { report: Report::new(e), @@ -138,13 +207,19 @@ impl Compiler { for (stem, schema) in validated { let schema = stef_compiler::simplify_schema(schema); let code = definition::compile_schema(&opts, &schema); - let code = prettyplease::unparse( - &syn::parse2(code.clone()).unwrap_or_else(|_| panic!("{code}")), - ); + let code = prettyplease::unparse(&syn::parse2(code.clone()).map_err(|source| { + Error::InvalidCode { + source, + code: code.to_string(), + } + })?); let out_file = out_dir.join(format!("{stem}.rs",)); - std::fs::write(out_file, code).unwrap(); + fs::write(&out_file, code).map_err(|source| Error::Write { + source, + file: out_file, + })?; } Ok(()) diff --git a/crates/stef-cli/src/main.rs b/crates/stef-cli/src/main.rs index 33664b5..f6ab0b2 100644 --- a/crates/stef-cli/src/main.rs +++ b/crates/stef-cli/src/main.rs @@ -1,4 +1,4 @@ -#![allow(missing_docs, clippy::missing_errors_doc)] +//! Main command line interface for tooling support of Stef schema files. use std::{fs, process::ExitCode}; diff --git a/crates/stef-compiler/src/lib.rs b/crates/stef-compiler/src/lib.rs index 2f453c4..74257c5 100644 --- a/crates/stef-compiler/src/lib.rs +++ b/crates/stef-compiler/src/lib.rs @@ -18,7 +18,7 @@ //! stef_compiler::resolve_schemas(&[("test", &schema)]).unwrap(); //! ``` -#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] +#![allow(clippy::module_name_repetitions)] pub use resolve::schemas as resolve_schemas; pub use simplify::schema as simplify_schema; diff --git a/crates/stef-compiler/src/resolve/mod.rs b/crates/stef-compiler/src/resolve/mod.rs index 5f07065..2f657bb 100644 --- a/crates/stef-compiler/src/resolve/mod.rs +++ b/crates/stef-compiler/src/resolve/mod.rs @@ -25,6 +25,10 @@ mod error; /// schema. /// - Lastly, the not-found types from the first steps are checked for in the other schemas by /// utilizing the imports from the second step. +/// +/// # Errors +/// +/// Will return `Err` if any of the resolution steps fails. pub fn schemas(values: &[(&str, &Schema<'_>)]) -> Result<(), Error> { let modules = values .iter() diff --git a/crates/stef-compiler/src/simplify.rs b/crates/stef-compiler/src/simplify.rs index 0bcddae..b81ac50 100644 --- a/crates/stef-compiler/src/simplify.rs +++ b/crates/stef-compiler/src/simplify.rs @@ -14,7 +14,13 @@ pub struct Schema<'a> { } impl Schema<'_> { - /// Render the [JSON Schema](https://json-schema.org/draft-07/json-schema-release-notes) of the complete schema, which can help external tools to understand the structure or derive types from it. + /// Render the [JSON Schema](https://json-schema.org/draft-07/json-schema-release-notes) of the + /// complete schema, which can help external tools to understand the structure or derive types + /// from it. + /// + /// # Errors + /// + /// Will return `Err` if the schema fails to serialize as JSON string. #[cfg(feature = "json")] pub fn json_schema() -> serde_json::Result { let schema = schemars::schema_for!(Schema<'_>); diff --git a/crates/stef-compiler/src/validate/mod.rs b/crates/stef-compiler/src/validate/mod.rs index 001abb1..80703e9 100644 --- a/crates/stef-compiler/src/validate/mod.rs +++ b/crates/stef-compiler/src/validate/mod.rs @@ -63,6 +63,10 @@ impl From for Error { /// - Fields names in structs or enum variants are unique. /// - Generic type parameters in a struct or enum are unique. /// - All generic type parameters are used. +/// +/// # Errors +/// +/// Will return `Err` if any of validation steps fails. pub fn schema(value: &Schema<'_>) -> Result<(), Error> { names::validate_names_in_module(&value.definitions)?; value.definitions.iter().try_for_each(definition) diff --git a/crates/stef-derive/src/lib.rs b/crates/stef-derive/src/lib.rs index b22aebf..12748a2 100644 --- a/crates/stef-derive/src/lib.rs +++ b/crates/stef-derive/src/lib.rs @@ -1,9 +1,7 @@ -#![allow( - missing_docs, - clippy::missing_errors_doc, - clippy::module_name_repetitions, - clippy::too_many_lines -)] +//! Internal derive macros for several Stef crates, that reduce boilerplate or create more +//! specialized implementations that the stdlib derives. + +#![allow(clippy::module_name_repetitions, clippy::too_many_lines)] use syn::{parse_macro_input, DeriveInput}; @@ -12,6 +10,8 @@ mod cause; mod debug; mod error; +/// /// Derive the [`miette`](https://docs.rs/miette) and [`winnow`](https://docs.rs/winnow) traits for +/// an error struct that is coupled with a cause enum. #[proc_macro_derive(ParserError, attributes(err, rename))] pub fn parser_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -22,6 +22,12 @@ pub fn parser_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } } +/// Derive the [`miette`](https://docs.rs/miette) and [`winnow`](https://docs.rs/winnow) traits for +/// an error cause enum, which contains one of the possible causes for a failure in parsing. +/// +/// The first variant of any enum must be named _Parser_, and contain two unnamed fields with type +/// `ErrorKind` and `usize`. This variant catches generic parser errors from `winnow` and their +/// location. #[proc_macro_derive(ParserErrorCause, attributes(err, external, forward, rename))] pub fn parser_error_cause(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -32,6 +38,7 @@ pub fn parser_error_cause(input: proc_macro::TokenStream) -> proc_macro::TokenSt } } +/// Specialized [`core::fmt::Debug`] macro, which omits span fields from the output. #[proc_macro_derive(Debug)] pub fn debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/crates/stef-lsp/src/main.rs b/crates/stef-lsp/src/main.rs index ec877a1..306eb52 100644 --- a/crates/stef-lsp/src/main.rs +++ b/crates/stef-lsp/src/main.rs @@ -1,5 +1,6 @@ +//! Language Server Protocol server implementation for Stef schemas. + #![warn(clippy::expect_used, clippy::unwrap_used)] -#![allow(missing_docs)] use std::{collections::HashMap, net::Ipv4Addr, time::Instant}; diff --git a/crates/stef-parser/src/error.rs b/crates/stef-parser/src/error.rs index cfc8cf8..94d790a 100644 --- a/crates/stef-parser/src/error.rs +++ b/crates/stef-parser/src/error.rs @@ -4,7 +4,7 @@ //! The root element is the [`ParseSchemaError`], which forms a tree of errors down to a specific //! error that caused parsing to fail. -#![allow(missing_docs, clippy::module_name_repetitions)] +#![allow(clippy::module_name_repetitions)] use std::{ error::Error, @@ -27,6 +27,7 @@ pub use crate::parser::{ #[derive(Debug)] pub struct ParseSchemaError { pub(crate) source_code: NamedSource, + /// Specific cause of the error. pub cause: ParseSchemaCause, } @@ -76,11 +77,15 @@ impl Diagnostic for ParseSchemaError { } } +/// Specific cause that gives more details about the origin of the error. #[derive(Debug, Diagnostic)] pub enum ParseSchemaCause { + /// Non-specific general parser error. Parser(ErrorKind, usize), + /// Root-level comment for the schema is invalid. #[diagnostic(transparent)] Comment(ParseCommentError), + /// Single schema definition is invalid. #[diagnostic(transparent)] Definition(ParseDefinitionError), } @@ -133,21 +138,30 @@ where /// Reason why a single definition was invalid. #[derive(Debug, Diagnostic)] pub enum ParseDefinitionError { + /// Non-specific general parser error. Parser(ErrorKind, usize), + /// Invalid comment section. #[diagnostic(transparent)] Comment(ParseCommentError), + /// Invalid element attribute. #[diagnostic(transparent)] Attribute(ParseAttributeError), + /// Invalid module definition. #[diagnostic(transparent)] Module(ParseModuleError), + /// Invalid struct definition. #[diagnostic(transparent)] Struct(ParseStructError), + /// Invalid enum definition. #[diagnostic(transparent)] Enum(ParseEnumError), + /// Invalid const definition. #[diagnostic(transparent)] Const(ParseConstError), + /// Invalid alias definition. #[diagnostic(transparent)] Alias(ParseAliasError), + /// Invalid import definition. #[diagnostic(transparent)] Import(ParseImportError), } diff --git a/crates/stef/src/buf/decode.rs b/crates/stef/src/buf/decode.rs index ef5388b..a5d22fc 100644 --- a/crates/stef/src/buf/decode.rs +++ b/crates/stef/src/buf/decode.rs @@ -8,33 +8,43 @@ use std::{ pub use bytes::{Buf, Bytes}; -use crate::{varint, FieldEncoding, FieldId, NonZero, VariantId}; +use crate::{varint, FieldEncoding, FieldId, NonZero, NonZeroBytes, NonZeroString, VariantId}; +/// Result type alias for the decoding process, which defaults to the [`Error`] type for errors. pub type Result = std::result::Result; -#[derive(Debug)] +/// Error that can happen while trying to decode a Stef payload. +#[derive(Debug, thiserror::Error)] pub enum Error { + /// The passed buffer did not contain enough data to fully decode the payload. + #[error("not enough remaining data in the buffer to decode the value")] InsufficientData, - DecodeInt(varint::DecodeIntError), - NonUtf8(std::string::FromUtf8Error), - MissingField { id: u32, name: Option<&'static str> }, + /// A _Varint_ integer failed to decode. + #[error("failed to decode a varint integer")] + DecodeInt(#[from] varint::DecodeIntError), + /// A string value was not encoded in valid UTF-8. + #[error("string is not valid UTF-8")] + NonUtf8(#[from] std::string::FromUtf8Error), + /// The field of a struct or enum non-optional in the schema, but is missing from the payload. + #[error("required field is missing from the payload")] + MissingField { + /// Identifier of the field. + id: u32, + /// Name of the field (if it is a named field). + name: Option<&'static str>, + }, + /// An enum variant was found that does not exist in the schema. + #[error("encountered an unknown enum variant")] UnknownVariant(u32), + /// An unknown field encoding was found. + #[error("encountered an unknown field encoding")] UnknownEncoding(u32), + /// The value of a non-zero field was actually zero. + #[error("non-zero value was found to be zero")] Zero, } -impl From for Error { - fn from(value: varint::DecodeIntError) -> Self { - Self::DecodeInt(value) - } -} - -impl From for Error { - fn from(value: std::string::FromUtf8Error) -> Self { - Self::NonUtf8(value) - } -} - +/// Special field identifier that marks the end of a struct or enum variant. pub const END_MARKER: u32 = 0; macro_rules! ensure_size { @@ -45,16 +55,31 @@ macro_rules! ensure_size { }; } +/// Decode a Stef `bool` (`true` or `false`) value. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_bool(r: &mut impl Buf) -> Result { ensure_size!(r, 1); Ok(r.get_u8() != 0) } +/// Decode a Stef `u8` integer. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_u8(r: &mut impl Buf) -> Result { ensure_size!(r, 1); Ok(r.get_u8()) } +/// Decode a Stef `i8` integer. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_i8(r: &mut impl Buf) -> Result { ensure_size!(r, 1); Ok(r.get_i8()) @@ -63,6 +88,12 @@ pub fn decode_i8(r: &mut impl Buf) -> Result { macro_rules! decode_int { ($ty:ty) => { paste::paste! { + #[doc = "Decode a Stef `" $ty "` integer."] + /// + /// # Errors + /// + /// Will return `Err` if the buffer does not have enough remaining data to read the + /// value, or the _Varint_ decoding fails due to a missing end marker. pub fn [](r: &mut impl Buf) -> Result<$ty> { let (value, consumed) = varint::[](r.chunk())?; r.advance(consumed); @@ -78,20 +109,41 @@ macro_rules! decode_int { decode_int!(u16, u32, u64, u128); decode_int!(i16, i32, i64, i128); +/// Decode a Stef `f32` floating number. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_f32(r: &mut impl Buf) -> Result { ensure_size!(r, 4); Ok(r.get_f32()) } +/// Decode a Stef `f64` floating number. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_f64(r: &mut impl Buf) -> Result { ensure_size!(r, 8); Ok(r.get_f64()) } +/// Decode a UTF-8 encoded Stef `string`. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// string is not valid UTF-8. pub fn decode_string(r: &mut impl Buf) -> Result { String::from_utf8(decode_bytes_std(r)?).map_err(Into::into) } +/// Decode a Stef `bytes` raw byte array (represented as default Rust byte vector). +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_bytes_std(r: &mut impl Buf) -> Result> { let len = decode_u64(r)?; ensure_size!(r, len as usize); @@ -99,6 +151,11 @@ pub fn decode_bytes_std(r: &mut impl Buf) -> Result> { Ok(r.copy_to_bytes(len as usize).to_vec()) } +/// Decode a Stef `bytes` raw byte array (represented as [`bytes::Bytes`] type). +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value. pub fn decode_bytes_bytes(r: &mut impl Buf) -> Result { let len = decode_u64(r)?; ensure_size!(r, len as usize); @@ -106,6 +163,12 @@ pub fn decode_bytes_bytes(r: &mut impl Buf) -> Result { Ok(r.copy_to_bytes(len as usize)) } +/// Decode a Stef `vec` vector value. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// `T` type fails to decode. pub fn decode_vec(r: &mut R, decode: D) -> Result> where R: Buf, @@ -124,6 +187,12 @@ where Ok(vec) } +/// Decode a Stef `hash_map` hash map value. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// `K`/`V` type fails to decode. pub fn decode_hash_map( r: &mut R, decode_key: DK, @@ -148,6 +217,12 @@ where Ok(map) } +/// Decode a Stef `hash_set` hash set value. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// `T` type fails to decode. pub fn decode_hash_set(r: &mut R, decode: D) -> Result> where R: Buf, @@ -167,6 +242,12 @@ where Ok(set) } +/// Decode a Stef `option` option value. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// `T` type fails to decode. pub fn decode_option(r: &mut R, decode: D) -> Result> where R: Buf, @@ -180,6 +261,13 @@ where } } +/// Decode a Stef `[T; N]` array value. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// `T` type fails to decode. +#[allow(clippy::missing_panics_doc)] pub fn decode_array(r: &mut R, decode: D) -> Result<[T; N]> where R: Buf, @@ -215,6 +303,13 @@ macro_rules! ensure_not_empty { macro_rules! decode_non_zero_int { ($ty:ty) => { paste::paste! { + #[doc = "Decode a Stef `non_zero<" $ty ">` integer as [`NonZero" $ty:upper "`]."] + #[doc = "\n\n[`NonZero" $ty:upper "`]: core::num::NonZero" $ty:upper] + /// + /// # Errors + /// + /// Will return `Err` if the buffer does not have enough remaining data to read the + /// value, or the integer value is zero. pub fn []( r: &mut impl Buf, ) -> Result]> { @@ -231,13 +326,27 @@ macro_rules! decode_non_zero_int { decode_non_zero_int!(u8, u16, u32, u64, u128); decode_non_zero_int!(i8, i16, i32, i64, i128); -pub fn decode_non_zero_string(r: &mut impl Buf) -> Result> { +/// Decode a Stef `non_zero`. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, the +/// string is not valid UTF-8, or the string is empty. +#[allow(clippy::missing_panics_doc)] +pub fn decode_non_zero_string(r: &mut impl Buf) -> Result { String::from_utf8(decode_non_zero_bytes_std(r)?.into_inner()) - .map(|v| NonZero::::new(v).unwrap()) + .map(|v| NonZeroString::new(v).unwrap()) .map_err(Into::into) } -pub fn decode_non_zero_bytes_std(r: &mut impl Buf) -> Result>> { +/// Decode a Stef `non_zero` (represented as [`NonZeroBytes`]). +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// byte array is empty. +#[allow(clippy::missing_panics_doc)] +pub fn decode_non_zero_bytes_std(r: &mut impl Buf) -> Result { let len = decode_u64(r)?; ensure_not_empty!(len); ensure_size!(r, len as usize); @@ -245,6 +354,13 @@ pub fn decode_non_zero_bytes_std(r: &mut impl Buf) -> Result>> { Ok(NonZero::>::new(r.copy_to_bytes(len as usize).to_vec()).unwrap()) } +/// Decode a Stef `non_zero` (represented as [`NonZero`]<[`bytes::Bytes`]>). +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// byte array is empty. +#[allow(clippy::missing_panics_doc)] pub fn decode_non_zero_bytes_bytes(r: &mut impl Buf) -> Result> { let len = decode_u64(r)?; ensure_not_empty!(len); @@ -253,6 +369,13 @@ pub fn decode_non_zero_bytes_bytes(r: &mut impl Buf) -> Result> { Ok(NonZero::::new(r.copy_to_bytes(len as usize)).unwrap()) } +/// Decode a Stef `non_zero>`. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, the +/// collection is empty, or the `T` type fails to decode. +#[allow(clippy::missing_panics_doc)] pub fn decode_non_zero_vec(r: &mut R, decode: D) -> Result>> where R: Buf, @@ -272,6 +395,13 @@ where Ok(NonZero::>::new(vec).unwrap()) } +/// Decode a Stef `non_zero>`. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, the +/// collection is empty, or the `K`/`V` type fails to decode. +#[allow(clippy::missing_panics_doc)] pub fn decode_non_zero_hash_map( r: &mut R, decode_key: DK, @@ -297,6 +427,13 @@ where Ok(NonZero::>::new(map).unwrap()) } +/// Decode a Stef `non_zero>`. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, the +/// collection is empty, or the `T` type fails to decode. +#[allow(clippy::missing_panics_doc)] pub fn decode_non_zero_hash_set(r: &mut R, decode: D) -> Result>> where R: Buf, @@ -317,16 +454,35 @@ where Ok(NonZero::>::new(set).unwrap()) } +/// Decode a Stef field identifier. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, the +/// value fails to decode or the identifier has an invalid field encoding. #[inline] pub fn decode_id(r: &mut impl Buf) -> Result { decode_u32(r).and_then(|id| FieldId::from_u32(id).ok_or(Error::UnknownEncoding(id))) } +/// Decode a Stef enum variant identifier. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to read the value, or the +/// value fails to decode. #[inline] pub fn decode_variant_id(r: &mut impl Buf) -> Result { decode_u32(r).map(VariantId::new) } +/// Decode a field, but skip over the value instead of fully decoding it. +/// +/// # Errors +/// +/// Will return `Err` if the buffer does not have enough remaining data to skip over the value, or +/// the decoding of data in fails in the process. For example to skip a _Varint_, it still needs to +/// decoded partially to find its end. pub fn decode_skip(r: &mut impl Buf, encoding: FieldEncoding) -> Result<()> { match encoding { FieldEncoding::Varint => loop { @@ -359,7 +515,15 @@ pub fn decode_skip(r: &mut impl Buf, encoding: FieldEncoding) -> Result<()> { } } +/// Values that can decode themselves from Stef encoded data. pub trait Decode: Sized { + /// Read the encoded data from the provided buffer. + /// + /// # Errors + /// + /// Will return `Err` if the buffer does not have enough remaining data to read the value, or, + /// depending on the defined data structure, due to several possible issues that can arise when + /// trying to decode. fn decode(r: &mut impl Buf) -> Result; } diff --git a/crates/stef/src/buf/encode.rs b/crates/stef/src/buf/encode.rs index ffa036b..6a35658 100644 --- a/crates/stef/src/buf/encode.rs +++ b/crates/stef/src/buf/encode.rs @@ -4,14 +4,17 @@ pub use bytes::{BufMut, Bytes}; use crate::{varint, FieldId, NonZero, VariantId}; +/// Encode a Stef `bool` (`true` or `false`) value. pub fn encode_bool(w: &mut impl BufMut, value: bool) { w.put_u8(value.into()); } +/// Encode a Stef `u8` integer. pub fn encode_u8(w: &mut impl BufMut, value: u8) { w.put_u8(value); } +/// Encode a Stef `i8` integer. pub fn encode_i8(w: &mut impl BufMut, value: i8) { w.put_i8(value); } @@ -19,6 +22,7 @@ pub fn encode_i8(w: &mut impl BufMut, value: i8) { macro_rules! encode_int { ($ty:ty) => { paste::paste! { + #[doc = "Encode a Stef `" $ty "` integer."] pub fn [](w: &mut impl BufMut, value: $ty) { let (buf, len) = varint::[](value); w.put(&buf[..len]); @@ -33,27 +37,33 @@ macro_rules! encode_int { encode_int!(u16, u32, u64, u128); encode_int!(i16, i32, i64, i128); +/// Encode a Stef `f32` floating number. pub fn encode_f32(w: &mut impl BufMut, value: f32) { w.put_f32(value); } +/// Encode a Stef `f64` floating number. pub fn encode_f64(w: &mut impl BufMut, value: f64) { w.put_f64(value); } +/// Encode a UTF-8 encoded Stef `string`. pub fn encode_string(w: &mut impl BufMut, value: &str) { encode_bytes_std(w, value.as_bytes()); } +/// Encode a Stef `bytes` raw byte array (represented as default Rust byte slice). pub fn encode_bytes_std(w: &mut impl BufMut, value: &[u8]) { encode_u64(w, value.len() as u64); w.put(value); } +/// Encode a Stef `bytes` raw byte array (represented as [`bytes::Bytes`] type). pub fn encode_bytes_bytes(w: &mut impl BufMut, value: &Bytes) { encode_bytes_std(w, value); } +/// Encode a Stef `vec` vector value. pub fn encode_vec(w: &mut W, vec: &[T], size: S, encode: E) where W: BufMut, @@ -67,6 +77,7 @@ where } } +/// Encode a Stef `hash_map` hash map value. pub fn encode_hash_map( w: &mut W, map: &HashMap, @@ -94,6 +105,7 @@ pub fn encode_hash_map( } } +/// Encode a Stef `hash_set` hash set value. pub fn encode_hash_set(w: &mut W, set: &HashSet, size: S, encode: E) where W: BufMut, @@ -107,6 +119,7 @@ where } } +/// Encode a Stef `option` option value. pub fn encode_option(w: &mut W, option: &Option, encode: E) where W: BufMut, @@ -120,6 +133,7 @@ where } } +/// Encode a Stef `[T; N]` array value. pub fn encode_array(w: &mut W, array: &[T; N], size: S, encode: E) where W: BufMut, @@ -133,6 +147,7 @@ where } } +/// Encode a Stef `(T1, T2, ...)` tuple value. #[inline(always)] pub fn encode_tuple(w: &mut W, size: S, encode: E) where @@ -144,16 +159,19 @@ where encode(w); } +/// Encode a Stef field identifier. #[inline(always)] pub fn encode_id(w: &mut impl BufMut, id: FieldId) { encode_u32(w, id.into_u32()); } +/// Encode a Stef enum variant identifier. #[inline(always)] pub fn encode_variant_id(w: &mut impl BufMut, id: VariantId) { encode_u32(w, id.value); } +/// Encode a required Stef struct or enum field. #[inline(always)] pub fn encode_field(w: &mut W, id: FieldId, encode: E) where @@ -164,6 +182,7 @@ where encode(w); } +/// Encode an optional Stef struct or enum field. #[inline(always)] pub fn encode_field_option(w: &mut W, id: FieldId, option: &Option, encode: E) where @@ -176,7 +195,9 @@ where } } +/// Values that can encode themselves in the Stef format. pub trait Encode: super::Size { + /// Write the encoded data in the provided buffer. fn encode(&self, w: &mut impl BufMut); } diff --git a/crates/stef/src/buf/size.rs b/crates/stef/src/buf/size.rs index 53643b7..c770255 100644 --- a/crates/stef/src/buf/size.rs +++ b/crates/stef/src/buf/size.rs @@ -7,6 +7,8 @@ use crate::{varint, NonZero}; macro_rules! size_fixed { ($ty:ty => $size:literal) => { paste::paste! { + #[doc = "Calculate the size of a Stef `" $ty "`"] + /// fixed value which is always the same, regardless of the value itself. #[inline(always)] #[must_use] pub const fn [](_: $ty) -> usize { @@ -30,6 +32,8 @@ size_fixed!( macro_rules! size_int { ($ty:ty) => { paste::paste! { + #[doc = "Calculate the size of a Stef `" $ty "` integer, "] + /// which varies as it is encoded as a _Varint_. #[must_use] pub const fn [](value: $ty) -> usize { varint::[](value) @@ -44,20 +48,24 @@ macro_rules! size_int { size_int!(u16, u32, u64, u128); size_int!(i16, i32, i64, i128); +/// Calculate the size of a UTF-8 encoded Stef `string`. #[must_use] pub const fn size_string(value: &str) -> usize { size_bytes_std(value.as_bytes()) } +/// Calculate the size of a Stef `bytes` raw byte array (represented as default Rust byte slice). #[must_use] pub const fn size_bytes_std(value: &[u8]) -> usize { size_u64(value.len() as u64) + value.len() } +/// Calculate the size of a Stef `bytes` raw byte array (represented as [`bytes::Bytes`] type). pub const fn size_bytes_bytes(value: &Bytes) -> usize { size_u64(value.len() as u64) + value.len() } +/// Calculate the size of a Stef `vec` vector value. pub fn size_vec(vec: &[T], size: S) -> usize where S: Fn(&T) -> usize, @@ -65,6 +73,7 @@ where size_u64(vec.len() as u64) + vec.iter().map(size).sum::() } +/// Calculate the size of a Stef `hash_map` hash map value. pub fn size_hash_map(map: &HashMap, size_key: SK, size_value: SV) -> usize where SK: Fn(&K) -> usize, @@ -77,6 +86,7 @@ where .sum::() } +/// Calculate the size of a Stef `hash_set` hash set value. pub fn size_hash_set(set: &HashSet, size: S) -> usize where S: Fn(&T) -> usize, @@ -84,6 +94,7 @@ where size_u64(set.len() as u64) + set.iter().map(size).sum::() } +/// Calculate the size of a Stef `option` option value. pub fn size_option(option: Option<&T>, size: S) -> usize where S: Fn(&T) -> usize, @@ -91,6 +102,7 @@ where size_u8(0) + option.map_or(0, size) } +/// Calculate the size of a Stef `[T; N]` array value. pub fn size_array(array: &[T; N], size: S) -> usize where S: Fn(&T) -> usize, @@ -98,12 +110,14 @@ where size_u64(N as u64) + array.iter().map(size).sum::() } +/// Calculate the size of a Stef field or variant identifier. #[inline(always)] #[must_use] pub fn size_id(id: u32) -> usize { size_u32(id) } +/// Calculate the size of a required Stef struct or enum field. #[inline(always)] pub fn size_field(id: u32, size: S) -> usize where @@ -112,6 +126,7 @@ where size_id(id) + size() } +/// Calculate the size of an optional Stef struct or enum field. #[inline(always)] pub fn size_field_option(id: u32, option: Option<&T>, size: S) -> usize where @@ -120,7 +135,9 @@ where option.map_or(0, |value| size_id(id) + size(value)) } +/// Values that are able to calculate their encoded byte size, without actually encoding. pub trait Size { + /// Calculate the encoded byte size. fn size(&self) -> usize; } diff --git a/crates/stef/src/lib.rs b/crates/stef/src/lib.rs index 538c638..ecd3ce8 100644 --- a/crates/stef/src/lib.rs +++ b/crates/stef/src/lib.rs @@ -1,10 +1,9 @@ +//! Runtime support crate for the STEF encoding format. + #![allow( - missing_docs, clippy::cast_possible_truncation, clippy::implicit_hasher, clippy::inline_always, - clippy::missing_errors_doc, - clippy::missing_panics_doc, clippy::module_name_repetitions )] @@ -18,19 +17,29 @@ pub use buf::{Buf, BufMut, Bytes, Decode, Encode}; pub mod buf; pub mod varint; +/// Identifier for a single struct or enum variant field. +/// +/// This type contains the actual identifier, plus additional information that is encoded together +/// with it. It allows for convenient en- and decoding of the information. #[derive(Clone, Copy)] pub struct FieldId { + /// The real decoded field identifier. pub value: u32, + /// Encoding information for field skipping. pub encoding: FieldEncoding, } impl FieldId { + /// Create a new instance of a field identifier. #[inline] #[must_use] pub const fn new(value: u32, encoding: FieldEncoding) -> Self { Self { value, encoding } } + /// Convert from a raw `u32` into the field identifier. + /// + /// This returns `None` if the raw value contains an unknown field encoding. #[must_use] pub const fn from_u32(value: u32) -> Option { let Some(encoding) = FieldEncoding::from_u32(value) else { @@ -41,6 +50,7 @@ impl FieldId { Some(Self { value, encoding }) } + /// Convert the field identifier into a raw `u32`, which contains all its information. #[inline] #[must_use] pub const fn into_u32(self) -> u32 { @@ -48,6 +58,7 @@ impl FieldId { } } +/// Minimum detail about how a field is encoded, which allows to skip over a field if it's unknown. #[derive(Clone, Copy, Eq, PartialEq)] #[repr(u32)] pub enum FieldEncoding { @@ -64,6 +75,8 @@ pub enum FieldEncoding { } impl FieldEncoding { + /// Try to convert the raw field identifier (which carries the encoding information) into a + /// known field encoding. #[must_use] pub const fn from_u32(value: u32) -> Option { Some(match value & 0b111 { @@ -77,12 +90,18 @@ impl FieldEncoding { } } +/// Identifier for a single enum variant. +/// +/// Currently, this is identical to the raw value it contains, but might be extended to encode +/// additional information like the [`FieldId`] in the future. #[derive(Clone, Copy)] pub struct VariantId { + /// The real decoded variant identifier. pub value: u32, } impl VariantId { + /// Create a new instance of a variant identifier. #[inline] #[must_use] pub fn new(value: u32) -> Self { @@ -90,6 +109,23 @@ impl VariantId { } } +/// Convenience macro to include generated Rust code for Stef schemas. +/// +/// By default build scripts write output files into a special directory provided by Cargo as +/// `OUT_DIR` environment variable. The `stef-build` crate additional puts all generated files into +/// a `stef/` sub-folder instead of placing the files at the root. +/// +/// The final file name is derived from the schema file name as it is the same, except for the +/// different file extension. +/// +/// # Example +/// +/// Assuming a schema file named `sample.stef` that is being compiled the `stef-build` crate, the +/// final output destination would be: +/// +/// ```txt +/// $OUT_DIR/stef/sample.rs +/// ``` #[macro_export] macro_rules! include { ($name:literal) => { @@ -97,18 +133,26 @@ macro_rules! include { }; } +/// A container that guarantees that the contained type is not zero or not empty. +/// +/// What's exactly meant by _non-zero_ depends on the type itself. For example, integers define this +/// as value that are not literally `0` (but those are handled by Rust's built-in `NonZeroN` types +/// anyway), and collections define this as not being empty, meaning to always contain at least one +/// element. Similarly for strings, it means they always contain at least one character. #[derive(Clone, Debug, PartialEq)] pub struct NonZero(T); impl NonZero { /// ``` - /// let value = stef::NonZeroString::new("hello".to_owned()).unwrap(); + /// let value = stef::NonZero::::new("hello".to_owned()).unwrap(); /// assert_eq!("hello", value.get()); /// ``` pub fn get(&self) -> &T { &self.0 } + /// Extract the inner type out of the non-zero container, but lose the guarantee of not being + /// zero. pub fn into_inner(self) -> T { self.0 } @@ -141,8 +185,13 @@ non_zero_collection!(Bytes); non_zero_collection!(HashMap); non_zero_collection!(HashSet); +/// String (Stef's `non_zero`) that is guaranteed to not be empty. pub type NonZeroString = NonZero; +/// Byte vector (Stef's `non_zero`) that is guaranteed to not be empty. pub type NonZeroBytes = NonZero>; +/// Vector of `T` (Stef's `non_zero>`) that is guaranteed to not be empty. pub type NonZeroVec = NonZero>; +/// Hash map (Stef's `non_zero>`) that is guaranteed to not be empty. pub type NonZeroHashMap = NonZero>; +/// Hash set (Stef's `non_zero>`) that is guaranteed to not be empty. pub type NonZeroHashSet = NonZero>; diff --git a/crates/stef/src/varint.rs b/crates/stef/src/varint.rs index 213f7e5..d366c87 100644 --- a/crates/stef/src/varint.rs +++ b/crates/stef/src/varint.rs @@ -69,6 +69,7 @@ const fn max(a: usize, b: usize) -> usize { macro_rules! varint { ($ty:ty, $signed:ty) => { paste::paste! { + #[doc = "Encode a `" $ty "` as _Varint_."] #[inline] #[must_use] pub fn [](mut value: $ty) -> ([u8; max_size::<$ty>()], usize) { @@ -88,6 +89,12 @@ macro_rules! varint { (buf, buf.len()) } + #[doc = "Decode a _Varint_ back to a `" $ty "`."] + /// + /// # Errors + /// + /// Will return `Err` if the raw bytes don't contain an end marker within the possible + /// maximum byte count valid for the integer. #[inline] pub fn [](buf: &[u8]) -> Result<($ty, usize), DecodeIntError> { let mut value = 0; @@ -102,23 +109,32 @@ macro_rules! varint { Err(DecodeIntError) } + #[doc = "Calculate the byte size of a `" $ty "` encoded as _Varint_."] #[inline] #[must_use] pub const fn [](value: $ty) -> usize { size::<$ty>(value.leading_zeros() as usize) } + #[doc = "Encode a `" $signed "` as _Varint_."] #[inline] #[must_use] pub fn [](value: $signed) -> ([u8; max_size::<$ty>()], usize) { []([](value)) } + #[doc = "Decode a _Varint_ back to a `" $signed "`."] + /// + /// # Errors + /// + /// Will return `Err` if the raw bytes don't contain an end marker within the possible + /// maximum byte count valid for the integer. #[inline] pub fn [](buf: &[u8]) -> Result<($signed, usize), DecodeIntError> { [](buf).map(|(v, b)| ([](v), b)) } + #[doc = "Calculate the byte size of a `" $signed "` encoded as _Varint_."] #[inline] #[must_use] pub const fn [](value: $signed) -> usize { @@ -175,6 +191,7 @@ macro_rules! varint { varint!((u16, i16), (u32, i32), (u64, i64), (u128, i128)); +/// Error that can happen when trying to decode a _Varint_ back into a regular integer. #[derive(Debug, Error)] #[error("input was lacking a final marker for the end of the integer data")] pub struct DecodeIntError;