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, CibouletteIdSelector, CibouletteIdType, CibouletteIdTypeSelector};
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,
};