1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
//! # Introduction //! //! **_Ciboulette_** is a **`JSON:API`** library. //! //! It allows one to parse request and build response respecting the `JSON:API` //! [specifications](https://jsonapi.org/format/). //! //! It aims to have a low memory footprint and be **fast**. //! //! # High level view of components //! //! At a high level, an `API` is constitued of [resource types](CibouletteResourceType). The resource type are organized //! in a graph representing their [relationships](CibouletteResourceRelationshipDetails) as edges. //! //! In addition to the graph, an adgacent map is used to efficiently retrieve [resource types](CibouletteResourceType) //! by their alias. This whole structure is held in a [store](CibouletteStore). //! //! ## Resource types //! //! The [resource types](CibouletteResourceType) can be built using a [resource type builder](CibouletteResourceTypeBuilder). //! It's made of : //! //! - A name, that will later be used as an alias to fetch the [resource types](CibouletteResourceType) from the [store](CibouletteStore)'s graph. //! - A id type, which will be used to deserialize the ids the requests. //! - A [schema](messy_json::MessyJsonObject) which will be used to deserialize the body of the requests and serialize the response. //! //! ## Relationship options //! //! ### Many-to-Many //! //! The [option struct](CibouletteRelationshipManyToManyOption) map a resource "A" to another resource "C" through another resource "B" (bucket) //! //! ```ascii //! Resource A Resource B (bucket) Resource C //! ┌─────────────────┐ ┌─────────────────────────────────────────────┐ ┌─────────────────┐ //! │ │ │ │ │ │ //! │ peoples──►id───┼──┼──►people_id◄──people-article──►article_id◄──┼──┼──id◄──articles │ //! │ │ │ │ │ │ //! └─────────────────┘ └─────────────────────────────────────────────┘ └─────────────────┘ //! ``` //! //! When creating a Many-to-Many relationships (`A <-> C`), we'll also create a Many-to-One relationship between the table //! `A -> B`, `C -> B`, `B -> A` and `B -> C` so that we can reference the relationship directly. //! //! ### One-to-Many / Many-to-One //! //! The [option struct](CibouletteRelationshipOneToManyOption) map a "many" resource to a "one" resource. //! //! ```ascii //! Many table One table //! ┌──────────────────────────────────────────────────┐ ┌──────────────────────────────────────┐ //! │ │ │ │ //! │ many_table_element_0──────────►many_table_key_0──┼──┼──►one_table_id◄───one_table_element │ //! │ │ │ ▲ ▲ │ //! │ │ │ │ │ │ //! │ many_table_element_1──────────►many_table_key_1──┼──┼─────┘ │ │ //! │ │ │ │ │ //! │ │ │ │ │ //! │ many_table_element_2──────────►many_table_key_2──┼──┼────────────┘ │ //! │ │ │ │ //! │ │ └──────────────────────────────────────┘ //! └──────────────────────────────────────────────────┘ //! ``` //! //! In the option a field is used to determined if a Many-to-One/One-to-Many relationship is part of Many-to-Many relationship. //! //! ## Requests //! //! Every requests boils down to the same components. But there is some quirks : //! //! - Creation request can be valid without resource [id](CibouletteResourceIdentifierPermissive), //! - Update request can have a body of [resource identifier](CibouletteResourceIdentifier). //! //! Every requests must first be deserialized using the [request builder](CibouletteRequestBuilder). Then it can be built //! into an generic [request](CibouletteRequest). From that, one can convert to the desired request type depending on the //! [intention](CibouletteIntention). Trying to convert a generic [request](CibouletteRequest) to an incompatible sub-type //! will result in an [error](CibouletteError). The correct conversion map goes like this : //! //! | Intention | Request type | //! |---------------------------------------|-------------------------------------------| //! | [Create](CibouletteIntention::Create) | [Create request](CibouletteCreateRequest) | //! | [Read](CibouletteIntention::Read) | [Read request](CibouletteReadRequest) | //! | [Update](CibouletteIntention::Update) | [Update request](CibouletteUpdateRequest) | //! | [Delete](CibouletteIntention::Delete) | [Delete request](CibouletteDeleteRequest) | //! //! Every sub-type of requests implement a [common trait](CibouletteRequestCommons) to allow for genericity. //! //! ## Responses //! //! A response is built from a [request](CibouletteRequestCommons) and a list of [response element](CibouletteResponseElement). //! //! Depending on the [request](CibouletteRequestCommons), the [response](CibouletteResponse) will be built to the correct format. //! //! ### Response elements //! //! Each response should have a single main [resource type](CibouletteResourceType). //! //! //! The [response elements](CibouletteResponseElement) are composed as follow for an element part of the main [resource type](CibouletteResourceType): //! //! | Response element field | Always required | Description | //! |------------------------|:------------------------------------------------------:|--------------------------------------------------------------------------------------| //! | `type` | ✅ | The current element [resource type](CibouletteResourceType) | //! | `identifier` | ✅ | The current element [resource identifier](CibouletteResourceIdentifier) | //! | `data` | ❌ | The `JSON` data of the resource, if any | //! | `related`.`rel_chain` | ❌<br />(only for related data) | Chain of relation metadata from the main [resource type](CibouletteResourceType) | //! | `related`.`element` | ❌<br />(only for related data) | The [resource identifier](CibouletteResourceIdentifier) of the element it relates to | //! //! //! //! //! //! //! //! #![warn(clippy::all)] mod body; mod config; mod error_request; mod errors; mod id; mod intention; mod member_name; mod path; mod query; mod request_selector; mod requests; mod responses; mod serde_utils; mod store; #[cfg(test)] mod tests; use arcstr::ArcStr; use body::resource_obj::CibouletteResourceBuilderVisitor; use getset::{Getters, MutGetters}; use messy_json::*; use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_utils::{handle_ident_in_map_stateful, handle_ident_in_map_stateless}; use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::convert::{TryFrom, TryInto}; use std::sync::Arc; use url::Url; use uuid::Uuid; pub use body::body_optional_data::CibouletteOptionalData; pub use body::errors_obj::{CibouletteErrorLink, CibouletteErrorObj, CibouletteErrorSource}; pub use body::link::{ CibouletteBodyLink, CibouletteBodyPagination, CibouletteLink, CibouletteLinkObj, }; pub use body::relationship::{CibouletteRelationshipObject, CibouletteRelationshipObjectBuilder}; pub use body::request_body::{CibouletteBody, CibouletteBodyBuilder, CibouletteJsonApiVersion}; pub use body::request_body_data::{ CibouletteBodyData, CibouletteBodyDataBuilder, CibouletteBodyDataPermissive, }; pub use body::resource_identifier::{ CibouletteResourceIdentifier, CibouletteResourceIdentifierBuilder, CibouletteResourceIdentifierPermissive, CibouletteResourceIdentifierSelector, CibouletteResourceIdentifierSelectorBuilder, }; pub use body::resource_obj::{CibouletteResource, CibouletteResourceBuilder}; pub use body::resource_obj_selector::{ CibouletteResourceSelector, CibouletteResourceSelectorBuilder, }; pub use body::resource_type::{CibouletteResourceRelationshipDetails, CibouletteResourceType}; pub use body::resource_type_builder::CibouletteResourceTypeBuilder; pub use id::{CibouletteId, CibouletteIdBuilder, CibouletteIdType}; pub use intention::CibouletteIntention; pub use responses::request::CibouletteResponse; pub use responses::status::CibouletteResponseStatus; pub use query::{ CiboulettePageType, CibouletteQueryParameters, CibouletteQueryParametersBuilder, CibouletteSortingDirection, CibouletteSortingElement, }; pub use request_selector::CibouletteRequestSelector; pub use requests::create::CibouletteCreateRequest; pub use requests::delete::CibouletteDeleteRequest; pub use requests::read::CibouletteReadRequest; pub use requests::update::{ CibouletteUpdateRelationshipBody, CibouletteUpdateRequest, CibouletteUpdateRequestType, }; pub use responses::body::{ CibouletteResponseBody, CibouletteResponseBodyData, CibouletteResponseRelationshipObject, CibouletteResponseResource, CibouletteResponseResourceSelector, }; pub use responses::element::CibouletteResponseElement; pub use responses::element_identifier::{ CibouletteResourceResponseIdentifier, CibouletteResourceResponseIdentifierBuilder, CibouletteResourceResponseIdentifierSelector, CibouletteResourceResponseIdentifierSelectorBuilder, }; pub use responses::request_data_builder::CibouletteResponseDataBuilder; pub use config::CibouletteConfig; pub use error_request::CibouletteErrorRequest; pub use errors::{CibouletteClashDirection, CibouletteError, CiboulettePathType}; pub use member_name::check_member_name; pub use requests::request::{ CibouletteRequest, CibouletteRequestBuilder, CibouletteRequestCommons, }; pub use responses::type_::{CibouletteResponseQuantity, CibouletteResponseRequiredType}; pub use path::path_container::{CiboulettePath, CiboulettePathBuilder}; pub use store::{ CibouletteRelationshipManyToManyOption, CibouletteRelationshipManyToManyOptionBuilder, CibouletteRelationshipOneToManyOption, CibouletteRelationshipOneToManyOptionBuilder, CibouletteRelationshipOption, CibouletteRelationshipOptionBuilder, CibouletteStore, CibouletteStoreBuilder, };