Skip to content

Commit

Permalink
Add "model" macro to simplify model definitions
Browse files Browse the repository at this point in the history
This removes a lot of repetitive attributes and type modifiers through the use
of an attribute-style procedural macro applied to our model structs.
  • Loading branch information
tjkirch committed Jan 6, 2020
1 parent debe8d2 commit 4a50760
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 116 deletions.
59 changes: 59 additions & 0 deletions workspaces/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions workspaces/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ skip = [
{ name = "laika", licenses = [] },
{ name = "migration-helpers", licenses = [] },
{ name = "migrator", licenses = [] },
{ name = "model-derive", licenses = [] },
{ name = "models", licenses = [] },
{ name = "moondog", licenses = [] },
{ name = "netdog", licenses = [] },
Expand Down
1 change: 1 addition & 0 deletions workspaces/models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ build = "build.rs"
[dependencies]
base64 = "0.11"
lazy_static = "1.2"
model-derive = { path = "model-derive" }
regex = "1.1"
serde = { version = "1.0", features = ["derive"] }
snafu = "0.6"
Expand Down
2 changes: 2 additions & 0 deletions workspaces/models/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ This `Settings` essentially becomes the schema for the variant's data store.

At the field level, standard Rust types can be used, or ["modeled types"](src/modeled_types) that add input validation.

The `#[model]` attribute on Settings and its sub-structs reduces duplication and adds some required metadata; see [its docs](model-derive/) for details.

### aws-k8s: Kubernetes

* [Model](src/aws-k8s/mod.rs)
Expand Down
20 changes: 20 additions & 0 deletions workspaces/models/model-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "model-derive"
version = "0.1.0"
authors = ["Tom Kirchner <[email protected]>"]
edition = "2018"
publish = false
build = "build.rs"

[lib]
path = "src/lib.rs"
proc-macro = true

[dependencies]
darling = "0.10"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", default-features = false, features = ["full", "parsing", "printing", "proc-macro", "visit-mut"] }

[build-dependencies]
cargo-readme = "3.1"
44 changes: 44 additions & 0 deletions workspaces/models/model-derive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# model-derive

Current version: 0.1.0

## Overview

This module provides a attribute-style procedural macro, `model`, that makes sure a struct is
ready to be used as an API model.

The goal is to reduce cognitive overhead when reading models.
We do this by automatically specifying required attributes on structs and fields.

Several arguments are available to override default behavior; see below.

## Changes it makes

### Visibility

All types must be public, so `pub` is added.
Override this (at a per-struct or per-field level) by specifying your own visibility.

### Derives

All structs must serde-`Serializable` and -`Deserializable`, and comparable via `PartialEq`.
`Debug` is added for convenience.
`Default` can also be added by specifying the argument `impl_default = true`.

### Serde

Structs have a `#[serde(...)]` attribute added to deny unknown fields and rename fields to kebab-case.
The struct can be renamed (for ser/de purposes) by specifying the argument `rename = "bla"`.

Fields have a `#[serde(...)]` attribute added to skip `Option` fields that are `None`.
This is because we accept updates in the API that are structured the same way as the model, but we don't want to require users to specify fields they aren't changing.
This can be disabled by specifying the argument `add_option = false`.

### Option

Fields are all wrapped in `Option<...>`.
Similar to the `serde` attribute added to fields, this is because we don't want users to have to specify fields they aren't changing, and can be disabled the same way, by specifying `add_option = false`.

## Colophon

This text was generated from `README.tpl` using [cargo-readme](https://crates.io/crates/cargo-readme), and includes the rustdoc from `src/lib.rs`.
9 changes: 9 additions & 0 deletions workspaces/models/model-derive/README.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# {{crate}}

Current version: {{version}}

{{readme}}

## Colophon

This text was generated from `README.tpl` using [cargo-readme](https://crates.io/crates/cargo-readme), and includes the rustdoc from `src/lib.rs`.
32 changes: 32 additions & 0 deletions workspaces/models/model-derive/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Automatically generate README.md from rustdoc.

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
// Check for environment variable "SKIP_README". If it is set,
// skip README generation
if env::var_os("SKIP_README").is_some() {
return;
}

let mut lib = File::open("src/lib.rs").unwrap();
let mut template = File::open("README.tpl").unwrap();

let content = cargo_readme::generate_readme(
&PathBuf::from("."), // root
&mut lib, // source
Some(&mut template), // template
// The "add x" arguments don't apply when using a template.
true, // add title
false, // add badges
false, // add license
true, // indent headings
)
.unwrap();

let mut readme = File::create("README.md").unwrap();
readme.write_all(content.as_bytes()).unwrap();
}
Loading

0 comments on commit 4a50760

Please sign in to comment.