Crate doku[−][src]
Expand description
Doku
Doku is a framework for building aesthetic, human-readable documentation directly from the code; it allows you to effortlessly generate docs for configuration files, HTTP endpoints, and so on.
Say goodbye to stale, hand-written documentation - with Doku, code is the documentation!
Example
Say, you’re writing a tool that requires some JSON configuration to work:
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
/// Database's engine
db_engine: DbEngine,
/// Database's host
db_host: String,
/// Database's port
db_port: usize,
}
#[derive(Deserialize)]
enum DbEngine {
#[serde(rename = "pgsql")]
PostgreSQL,
#[serde(rename = "mysql")]
MySQL,
}
Now, with Doku, generating a documentation for your users is as simple as
adding #[derive(Document)]
:
use doku::Document;
#[derive(Deserialize, Document)]
struct Config {
/* ... */
}
#[derive(Deserialize, Document)]
enum DbEngine {
/* ... */
}
… and calling doku::to_json()
:
let doc = doku::to_json::<Config>();
println!("{}", doc); // says:
{
// Database's engine
"db_engine": "pgsql" | "mysql",
// Database's host
"db_host": "string",
// Database's port
"db_port": 123
}
Because doku::to_json()
returns a good-old String
, it’s easy to e.g.
create a test ensuring that docs are in sync with the code:
use std::fs;
#[test]
fn docs() {
let actual_docs = doku::to_json::<Config>();
let current_docs = fs::read_to_string("config.example.json").unwrap();
if current_docs != actual_docs {
fs::write("config.example.json.new", actual_docs);
panic!("`config.example.json` is stale; please see: `config.example.json.new`");
}
}
Let go & let the pipelines worry about your docs!
Plug and Play
Doku has been made with the plug-and-play approach in mind - it understands
the most common Serde annotations and comes with a predefined formatting
settings, so adding #[derive(Document)]
here and there should get you
started quickly & painlessly.
At the same time, Doku is extensible - if the formatting settings don’t
match your taste, there is a way to tune them; if the derive macro doesn’t
work because you use custom impl Serialize
, you can write impl Document
by hand, too.
So - come join the doc side!
Limits
Supported formats
Currently Doku provides functions for generating JSON docs; more formats, such as TOML, are on their way.
If you wanted, you could even implement a pretty-printer for your own type format - there’s no need to clone Doku, since all of the required types are exported fro here; getting started is as easy as:
fn to_my_own_format<T>() -> String
where
T: doku::Document
{
match T::ty().kind {
doku::TypeKind::String => "'tis a string!".to_string(),
doku::TypeKind::Struct { .. } => "'tis a struct!".to_string(),
_ => todo!(),
}
}
println!("{}", to_my_own_format::<String>());
Supported Serde annotations
Legend:
- ❌ = not supported (the derive macro will return an error)
- ✅ = supported
- ✅ + no-op = supported, but doesn’t affect the documentation
#[serde]
for containers:
- ❌
#[serde(rename = "...")]
- ❌
#[serde(rename(serialize = "..."))]
- ❌
#[serde(rename(deserialize = "..."))]
- ❌
#[serde(rename(serialize = "...", deserialize = "..."))]
- ❌
#[serde(rename_all = "...")]
- ❌
#[serde(rename_all(serialize = "..."))]
- ❌
#[serde(rename_all(deserialize = "..."))]
- ❌
#[serde(rename_all(serialize = "...", deserialize = "..."))]
- ✅
#[serde(deny_unknown_fields)]
(no-op) - ✅
#[serde(tag = "...")]
- ✅
#[serde(tag = "...", content = "...")]
- ✅
#[serde(untagged)]
- ❌
#[serde(bound = "...")]
- ❌
#[serde(bound(serialize = "..."))]
- ❌
#[serde(bound(deserialize = "..."))]
- ❌
#[serde(bound(serialize = "...", deserialize = "..."))]
- ✅
#[serde(default)]
(no-op) - ✅
#[serde(default = "...")]
(no-op) - ❌
#[serde(remote = "...")]
- ✅
#[serde(transparent)]
- ❌
#[serde(from = "...")]
- ❌
#[serde(try_from = "...")]
- ❌
#[serde(into = "...")]
- ✅
#[serde(crate = "...")]
(no-op)
#[serde]
for variants:
- ✅
#[serde(rename = "...")]
- ❌
#[serde(rename(serialize = "..."))]
- ❌
#[serde(rename(deserialize = "..."))]
- ❌
#[serde(rename(serialize = "...", deserialize = "..."))]
- ❌
#[serde(alias = "...")]
- ❌
#[serde(rename_all = "...")]
- ✅
#[serde(skip)]
- ✅
#[serde(skip_serializing)]
- ✅
#[serde(skip_deserializing)]
- ✅
#[serde(serialize_with = "...")]
(no-op) - ✅
#[serde(deserialize_with = "...")]
(no-op) - ✅
#[serde(with = "...")]
(no-op) - ❌
#[serde(bound = "...")]
- ❌
#[serde(borrow)]
- ❌
#[serde(borrow = "...")]
- ✅
#[serde(other)]
(no-op)
#[serde]
for fields:
- ✅
#[serde(rename = "...")]
- ❌
#[serde(rename(serialize = "..."))]
- ❌
#[serde(rename(deserialize = "..."))]
- ❌
#[serde(rename(serialize = "...", deserialize = "..."))]
- ❌
#[serde(alias = "...")]
- ✅
#[serde(default)]
(no-op) - ✅
#[serde(default = "...'")]
(no-op) - ✅
#[serde(skip)]
- ✅
#[serde(skip_serializing)]
- ✅
#[serde(skip_deserializing)]
- ✅
#[serde(skip_serializing_if = "...")]
(no-op) - ✅
#[serde(serialize_with = "...")]
(no-op) - ✅
#[serde(deserialize_with = "...")]
(no-op) - ✅
#[serde(with = "...")]
(no-op) - ❌
#[serde(borrow)]
(no-op) - ❌
#[serde(borrow = "...")]
(no-op) - ❌
#[serde(getter = "...")]
Supported language features
- ❌ generic types (https://github.com/anixe/doku/issues/3)
- ❌ recursive types (https://github.com/anixe/doku/issues/10)
How does it work?
When you wrap a type with #[derive(Document)]
:
#[derive(Document)]
struct User {
/// Who? Who?
#[doku(example = "alan.turing")]
login: String,
}
… this derive macro generates an impl doku::Document
:
impl doku::Document for User {
fn ty() -> doku::Type {
let login = doku::Field {
ty: doku::Type {
comment: Some("Who? Who?"),
example: Some(doku::Example::Simple("alan.turing")),
..String::ty()
},
flattened: false,
};
doku::Type::from(doku::TypeKind::Struct {
fields: doku::Fields::Named {
fields: vec![
("login", login)
],
},
transparent: false,
})
}
}
… and later, when you invoke doku::to_json<...>()
, it just calls this
fn ty()
method:
fn to_json<T>() -> String
where
T: doku::Document
{
match T::ty().kind {
doku::TypeKind::String => print_string(/* ... */),
doku::TypeKind::Struct { .. } => print_struct(/* ... */),
/* ... */
}
}
There’s no magic, no RTTI hacks, no unsafety - it’s all just Rust.
Modules
Macros
Structs
Enums
Defines the way enums are represented (https://serde.rs/enum-representations.html)
Defines the way skip_serializing/skip_deserializing are interpreted.
Traits
A type that’s understandable by Doku.
Functions
Generates a JSON documentation for specified type.
Generates a JSON documentation for specified type using custom formatting settings.
Generates a JSON documentation for specified type using custom formatting settings, and extracting example values from given serializable object.
Generates a JSON documentation for specified type, extracting example values from given serializable object.