# `restructed`
GitHub Repo Crates.io version docs.rs docs Unsafe Rust Forbidden

A quick and easy way to create derivative models of your existing types without repeating yourself all the damn time.
- Reduce number of structs you need to write - Allows deriving on generated structs - Allows generating multiple structs from one derive - Automatically generates `From` traits for original <> generated structs New features planned are available [here](https://github.com/NexRX/restructed/issues/1) on github. See below for examples of current usage & features. # Usage Add `restructed` to your project `Cargo.toml`: ```toml restructed = "0.2" ``` alternatively, run this in the project directory. ```sh cargo add restructed ``` Add the import and derive it on the target struct ```rust #[derive(restructed::Models)] struct User { id: i32, username: String } ``` And then add attributes for each model you want to create. ```rust #[derive(restructed::Models)] #[view(UserId, fields(id))] // <-- Simple subset of the deriving structs field #[patch(UserUpdatables, omit(id))] // <-- Wraps all fields with a Option type inside a new struct struct User { id: i32, username: String, } ``` Continue reading for the available models and their breakdowns and arguments in general. # Models These are arguments that can be applied to their respective attributes (i.e. `#[attr(args...)])`). ## `#[view]` A model that generates a subset of the original/parent deriving `Model`. It is useful for creating things like RESTful APIs or database views. | Argument Name | description | Required? | Type/Enum | Example | |-----------------|----------------------------------------------------|------------|-------------------------|-----------------------------------| | name | Name of the struct the generate | True+First | Identifier | `MyStruct` | | **fields** or | Field names in the original structure to include | False | List(Ident) | `fields(field1, field2, ...)` | | **omit** | Field names in the original structure to exclude | False | List(Ident) | `omit(field1, field2, ...)` | | derive | Things to derive on the newly generated struct | False | List(Path) | `derive(Debug, thiserror::Error)` | | preset | Behaviours and/or defaults to apply | False | none/write/read | `preset = "all"` | | attributes_with | Attributes to inherit at both struct & field level | False | none/oai/deriveless/all | `attributes_with = "all"` | ```rust #[derive(Clone, restructed::Models)] #[view(UserProfile, omit(id, password))] struct User { id: i32, // Not in `UserProfile` display_name: String, bio: String, extra: Option, password: String, // Not in `UserProfile` } ``` ## `#[patch]` A model that creates subsets of your data except each field's type is wrapped in a `Option` or a alternative type of Option implementation if specified. It is useful for creating RESTful API patch method types or database table patches where you only want to update fields if they were explicitly given (even to delete). | Argument Name | description | Required? | Type/Enum | Example | |-----------------|----------------------------------------------------|------------|-------------------------|-----------------------------------| | name | Name of the struct the generate | True+First | Identifier | `MyStruct` | | **fields** or | Field names in the original structure to include | False | List(Ident) | `fields(field1, field2, ...)` | | **omit** | Field names in the original structure to exclude | False | List(Ident) | `omit(field1, field2, ...)` | | derive | Things to derive on the newly generated struct | False | List(Path) | `derive(Debug, thiserror::Error)` | | preset | Behaviours and/or defaults to apply | False | none/write/read | `preset = "all"` | | attributes_with | Attributes to inherit at both struct and field level | False | none/oai/deriveless/all | `attributes_with = "all"` | | option | A alternative to `Option` to wrap fields with | False | Option/MaybeUndefined | `option = MaybeUndefined` | ```rust #[derive(Clone, restructed::Models)] #[patch(UserUpdate, fields(display_name, bio, extra, password))] struct User { id: i32, // Not in `UserUpdate` display_name: String, // Option in `UserUpdate` bio: String, // Option in `UserUpdate` extra: Option, // Option> in `UserUpdate` (If this isn't desired, see *option* arg and the *openapi* crate feature) password: String, // Not in `UserProfile` } ``` ## `#[model]` Not a model, used to define a *base* or *default* set of arguments to be applied to all models. Acts as an interface for taking arguments to apply more broadly and *doesn't* generate any models itself. There are two arguments possible. ### base A *list* of non-overridable arguments that are applied to all generated arguments that you can build on top of It doesn't prevent you from using the individual models later, but it also won't allow you to undo the effect individually. e.g. `#[model(base(...)]` | Argument Name | description | Required? | Type/Enum | Example | |-----------------|----------------------------------------------------|------------|-------------------------|-----------------------------------| | **fields** or | Field names in the original structure to include | False | List(Ident) | `fields(field1, field2, ...)` | | **omit** | Field names in the original structure to exclude | False | List(Ident) | `omit(field1, field2, ...)` | | derive | Things to derive on the newly generated struct | False | List(Path) | `derive(Debug, thiserror::Error)` | ### defaults Arguments given in this list are applied to all models where the argument isn't given. Meaning, if `#[model(defaults(fields(a, b)))]` and then later `#[view(omit(b))]` is written, the `fields(a, b)` earlier will not be applied because the two args are mutally exclusive, unlike with *base* arguments. e.g. `#[model(defaults(...))]` | Argument Name | description | Required? | Type/Enum | Example | |-----------------|----------------------------------------------------|------------|-------------------------|-----------------------------------| | **fields** or | Field names in the original structure to include | False | List(Ident) | `fields(field1, field2, ...)` | | **omit** | Field names in the original structure to exclude | False | List(Ident) | `omit(field1, field2, ...)` | | derive | Things to derive on the newly generated struct | False | List(Path) | `derive(Debug, thiserror::Error)` | | preset | Behaviours and/or defaults to apply | False | none/write/read | `preset = "all"` | | attributes_with | Attributes to inherit at both struct & field level | False | none/oai/deriveless/all | `attributes_with = "all"` | ### Example ```rust #[derive(Clone, restructed::Models)] #[model(base(derive(Debug)))] // All models now *MUST* derive Debug (despite parent) #[view(UserView)] #[patch(UserPatch)] struct User { id: i32, display_name: String, bio: String, extra: Option, password: String, } fn debug_models() { let user = User { id: 1, display_name: "Dude".to_string(), bio: "Too long didn't read".to_string(), extra: None, password: "ezpz".to_string(), }; let view: UserView = user.clone().into(); // Automatically gen from model print!("A view of a user {:?}", view); let patch: UserPatch = user.clone().into(); // Automatically gen from model print!("A patch of a user {:?}", patch); } ```
# Argument Behaviours ## `preset` A _string literal_ of the preset to use, presets are a set of defaults to apply to a model. *Below is a list of what arguments are composed in a preset.* [e.g. `preset = "none"`] - **none** - Does nothing and is the default behaviour [**Default**] - **write** *['openapi' Feature Flag]* - Designed to only show properties that can be written to. - `omit` - Applied as a base, any fields with `#[oai(read_only)]` attribute are removed, your fields/omit is applied after - `option` - **patch only** - Arg defaults to `MaybeUndefined` - **read** *['openapi' Feature Flag]* - Designed to only show properties that can always be read. - `omit` - Applied as a base, any fields with `#[oai(write_only)]` attribute are removed, your fields/omit is applied after - `option` - **patch only** - arg defaults to `MaybeUndefined` ## `attributes_with` A _string literal_ of the attributes to inherit at both struct & field level. *Below is a list of values.* [e.g. `attributes_with = "none"`] - **none** - Does not Includes any attributes [**Default**] - **oai** *['openapi' Feature Flag]* - Includes all Poem's OpenAPI attributes - **deriveless** - Includes all attributes but omits the derive attributes - **all** - Includes all attributes # Known Limitations - *Generic Structs & Enums* - At the moment, this crate **doesn't support** deriving models on Structs that need to be generic (e.g. deriving on a `Struct`). I just don't need the feature, contributions are welcome, however!
# Crate Features Links are to other crates GitHub pages that are related to the features.
## Poem OpenAPI Enables wrapping `Option` from the source struct with `MaybeUndefined` from the [poem-openapi](https://github.com/poem-web/poem/tree/master/poem-openapi) crate in `patch` models. All `oai(...)` attributes can also be explicitly copied over to the generated struct, meaning you keep all validators, etc. ```rust use restructed::Models; #[derive(poem_openapi::Object, Models)] #[oai(skip_serializing_if_is_none, rename_all = "camelCase")] #[model(base(derive(poem_openapi::Object, Debug)), defaults(preset = "read"))] #[patch(UserUpdate, preset = "write")] #[view(UserProfile)] #[view(UserNames, fields(username, name, surname))] pub struct User { #[oai(read_only)] pub id: u32, // profile #[oai(validator(min_length = 3, max_length = 16, pattern = r"^[a-zA-Z0-9_]*$"))] // oai attributes carry over with `preset = write/write` or attributes_with="oai" pub username: String, #[oai(validator(min_length = 5, max_length = 1024), write_only)] pub password: String, #[oai(validator(min_length = 2, max_length = 16, pattern = r"^[a-zA-Z\s]*$"))] pub name: Option, #[oai(validator(min_length = 2, max_length = 16, pattern = r"^[a-zA-Z\s]*$"))] pub surname: Option, // in patch modeels, this is `MaybeUndefined` type with default with preset `read` or `write` (or option = MaybeUndefined) #[oai(read_only)] pub joined: u64, } ```