Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
## Unreleased
### Added
- Added `links` objects per json:api specification
- Added configuration option for pagination

### Changed
- CibouletteId::build_id moved to CibouletteIdSelector::build

- Better performance when building documents and responses
- Changed how resource are built, using a new structure holding more options

## [0.1.4] - 2021-05-13
### Fixed
Expand Down
2 changes: 0 additions & 2 deletions src/body/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ pub mod request_body_data;
pub mod resource_identifier;
pub mod resource_obj;
pub mod resource_obj_selector;
pub mod resource_type;
pub mod resource_type_builder;

#[cfg(test)]
mod tests;
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ pub enum CibouletteError {
ResponseTooManyMainData(String),
#[error("Wrong number of id, got {0}, expected {1}")]
WrongIdNumber(usize, usize),
#[error("No pagination field {1} for type {0}.")]
UnknownPaginationField(String, String),
#[error(transparent)]
CibouletteSelectorError(#[from] selector::CibouletteSelectorError),
/// When there is a failure while deserializing the JSON
Expand Down
10 changes: 10 additions & 0 deletions src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ pub enum CibouletteIdType {
Uuid(ArcStr),
}

impl CibouletteIdType {
pub fn name(&self) -> &ArcStr {
match self {
CibouletteIdType::Text(x) => x,
CibouletteIdType::Number(x) => x,
CibouletteIdType::Uuid(x) => x,
}
}
}

impl<'request> std::fmt::Display for CibouletteIdType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down
24 changes: 17 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ 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;
Expand Down Expand Up @@ -209,12 +207,24 @@ pub use responses::type_::{CibouletteResponseQuantity, CibouletteResponseRequire

pub use path::path_container::{CiboulettePath, CiboulettePathBuilder};
pub use selector::{CibouletteSelector, CibouletteSelectorError, CibouletteSelectorIterator};
pub use store::{
CibouletteRelationshipManyToManyOption, CibouletteRelationshipManyToManyOptionBuilder,
CibouletteRelationshipOneToManyOption, CibouletteRelationshipOneToManyOptionBuilder,
CibouletteRelationshipOption, CibouletteRelationshipOptionBuilder, CibouletteStore,
CibouletteStoreBuilder,
pub use store::builder::CibouletteStoreBuilder;
pub use store::relationships_options::{
CibouletteRelationshipManyToManyOption, CibouletteRelationshipOneToManyOption,
CibouletteRelationshipOption,
};
pub use store::relationships_options_builder::{
CibouletteRelationshipManyToManyOptionBuilder, CibouletteRelationshipOneToManyOptionBuilder,
CibouletteRelationshipOptionBuilder,
};
pub use store::resource_type::{CibouletteResourceRelationshipDetails, CibouletteResourceType};
pub use store::resource_type_builder::CibouletteResourceTypeBuilder;
pub use store::resource_type_configuration::{
CiboulettePaginationEncoding, CibouletteResourceTypeConfiguration,
CibouletteResourceTypePaginationConfiguration,
CibouletteResourceTypePaginationConfigurationBuilder,
};

pub use store::CibouletteStore;

#[cfg(test)]
pub mod test_helper;
Expand Down
5 changes: 2 additions & 3 deletions src/store/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,15 @@ impl CibouletteStoreBuilder {
pub fn add_type(
&mut self,
name: &str,
ids: CibouletteIdTypeSelector,
schema: MessyJsonObject,
config: CibouletteResourceTypeConfiguration,
) -> Result<(), CibouletteError> {
let name = name.to_string();
if self.map.contains_key(name.as_str())
// Check if type exists
{
return Err(CibouletteError::UniqType(name));
}
let t = CibouletteResourceTypeBuilder::new(name.clone(), ids, schema).build()?;
let t = CibouletteResourceTypeBuilder::new(name.clone(), config).build()?;
let index = self.graph.add_node(t); // Add the node
self.map.insert(name, index); // Save the index to the map
Ok(())
Expand Down
22 changes: 8 additions & 14 deletions src/store/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
use super::*;
use getset::CopyGetters;

mod builder;
mod many_to_many;
mod one_to_many;
mod relationships_options;
mod relationships_options_builder;
pub use builder::CibouletteStoreBuilder;
pub use relationships_options::{
CibouletteRelationshipManyToManyOption, CibouletteRelationshipOneToManyOption,
CibouletteRelationshipOption,
};
pub use relationships_options_builder::{
CibouletteRelationshipManyToManyOptionBuilder, CibouletteRelationshipOneToManyOptionBuilder,
CibouletteRelationshipOptionBuilder,
};
pub mod builder;
pub mod many_to_many;
pub mod one_to_many;
pub mod relationships_options;
pub mod relationships_options_builder;
pub mod resource_type;
pub mod resource_type_builder;
pub mod resource_type_configuration;

#[cfg(test)]
mod tests;
Expand Down
3 changes: 3 additions & 0 deletions src/body/resource_type.rs → src/store/resource_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct CibouletteResourceType {
relationships_type_to_alias: BTreeMap<ArcStr, ArcStr>,
schema: MessyJsonObject,
ids: CibouletteIdTypeSelector,
pagination: Option<CibouletteResourceTypePaginationConfiguration>,
name: ArcStr,
}

Expand All @@ -27,12 +28,14 @@ impl CibouletteResourceType {
name: ArcStr,
ids: CibouletteIdTypeSelector,
schema: MessyJsonObject,
pagination: Option<CibouletteResourceTypePaginationConfiguration>,
) -> Self {
CibouletteResourceType {
relationships: BTreeMap::new(),
relationships_type_to_alias: BTreeMap::new(),
schema,
ids,
pagination,
name,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ pub struct CibouletteResourceTypeBuilder {
relationships_type_to_alias: BTreeMap<ArcStr, ArcStr>,
schema: MessyJsonObject,
ids: CibouletteIdTypeSelector,
pagination: Option<CibouletteResourceTypePaginationConfigurationBuilder>,
name: ArcStr,
}

impl CibouletteResourceTypeBuilder {
/// Create a new type from a schema and a list of relationships
pub fn new(name: String, id_type: CibouletteIdTypeSelector, schema: MessyJsonObject) -> Self {
pub fn new(name: String, config: CibouletteResourceTypeConfiguration) -> Self {
CibouletteResourceTypeBuilder {
relationships: BTreeMap::new(),
relationships_type_to_alias: BTreeMap::new(),
schema,
ids: id_type,
schema: config.schema,
ids: config.ids,
pagination: config.pagination,
name: ArcStr::from(name),
}
}
Expand Down Expand Up @@ -56,11 +58,16 @@ impl CibouletteResourceTypeBuilder {
if let Some(x) = Self::check_member_name_obj(self.schema()) {
return Err(CibouletteError::InvalidMemberName(x));
}
Ok(CibouletteResourceType::new(
self.name,
self.ids,
self.schema,
))
let name = self.name;
let schema = self.schema;
let ids = self.ids;

let pagination = match self.pagination {
Some(pagination) => Some(pagination.build(name.as_str(), &ids, &schema)?),
None => None,
};

Ok(CibouletteResourceType::new(name, ids, schema, pagination))
}
}

Expand Down
121 changes: 121 additions & 0 deletions src/store/resource_type_configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use super::*;

/// Configuration needed to add a new type to the ciboulette store
#[derive(Clone, Debug, Getters, MutGetters)]
#[getset(get = "pub", get_mut = "pub(crate)")]
pub struct CibouletteResourceTypeConfiguration {
pub ids: CibouletteIdTypeSelector,
pub schema: MessyJsonObject,
pub pagination: Option<CibouletteResourceTypePaginationConfigurationBuilder>,
}

impl CibouletteResourceTypeConfiguration {
pub fn new(
ids: CibouletteIdTypeSelector,
schema: MessyJsonObject,
pagination: Option<CibouletteResourceTypePaginationConfigurationBuilder>,
) -> Self {
CibouletteResourceTypeConfiguration {
ids,
schema,
pagination,
}
}
}

/// Pagination option builder for new ciboulette type
#[derive(Clone, Debug, Getters, MutGetters)]
#[getset(get = "pub", get_mut = "pub(crate)")]
pub struct CibouletteResourceTypePaginationConfigurationBuilder {
/// A list of fields ot sort by. i.e `["created_at", "id"]`
pub sort_by: Vec<String>,
/// The discriminant keys. i.e. `["id"]`
pub discriminants: Vec<String>,
/// encoding when generating the links.
pub encoding: CiboulettePaginationEncoding,
}

impl CibouletteResourceTypePaginationConfigurationBuilder {
fn find_field_name(
name: &str,
resource_name: &str,
ids: &CibouletteIdTypeSelector,
schema: &MessyJsonObject,
) -> Result<ArcStr, CibouletteError> {
schema
.properties()
.get_key_value(name)
.map(|x| x.0.clone())
.or_else(|| {
ids.iter()
.find(|x| x.name().as_str() == name)
.map(|x| x.name().clone())
})
.ok_or_else(|| {
CibouletteError::UnknownField(resource_name.to_string(), name.to_string())
})
}

pub fn build(
self,
type_name: &str,
ids: &CibouletteIdTypeSelector,
schema: &MessyJsonObject,
) -> Result<CibouletteResourceTypePaginationConfiguration, CibouletteError> {
let mut sort_by: Vec<ArcStr> = Vec::with_capacity(self.sort_by.len());
let mut discriminants: Vec<ArcStr> = Vec::with_capacity(self.discriminants.len());
let mut sort_set = BTreeSet::new();
let mut discriminant_set = BTreeSet::new();

for sort_el in self.sort_by() {
if !sort_set.insert(sort_el.as_str()) {
continue;
}
sort_by.push(Self::find_field_name(
sort_el.as_str(),
type_name,
ids,
schema,
)?);
}
for discriminant in self.discriminants() {
if !sort_set.contains(discriminant.as_str()) {
return Err(CibouletteError::UnknownPaginationField(
type_name.to_string(),
discriminant.clone(),
));
}
let field = Self::find_field_name(discriminant.as_str(), type_name, ids, schema)?;
if !discriminant_set.insert(field.clone()) {
continue;
}
discriminants.push(field.clone());
}
Ok(CibouletteResourceTypePaginationConfiguration {
sort_by,
discriminants,
encoding: self.encoding,
})
}
}

/// Pagination option for new ciboulette type
#[derive(Clone, Debug, Getters, MutGetters, Hash)]
#[getset(get = "pub", get_mut = "pub(crate)")]
pub struct CibouletteResourceTypePaginationConfiguration {
/// A list of fields ot sort by. i.e `["created_at", "id"]`
pub sort_by: Vec<ArcStr>,
/// The discriminant keys. i.e. `["id"]`
pub discriminants: Vec<ArcStr>,
/// encoding when generating the links.
pub encoding: CiboulettePaginationEncoding,
}

/// Encoding option for pagination links
#[derive(Clone, Debug, Copy, Hash)]
pub enum CiboulettePaginationEncoding {
/// Comma separated
Raw,
/// Bincode -> base64 encoded
Base64,
}
Loading