Crates.io | rabbithole-endpoint-actix |
lib.rs | rabbithole-endpoint-actix |
version | 0.3.1 |
source | src |
created_at | 2019-11-10 15:40:29.803612 |
updated_at | 2019-11-17 15:32:10.744585 |
description | JSON:API Actix-web backend |
homepage | https://github.com/UkonnRa/rabbithole-rs |
repository | https://github.com/UkonnRa/rabbithole-rs.git |
max_upload_size | |
id | 179952 |
size | 150,988 |
The rabbit-hole went straight on like a tunnel for some way, and then dipped suddenly down, so suddenly that Alice had not a moment to think about stopping herself before she found herself falling down what seemed to be a very deep well.
-- Alice's Adventures in Wonderland, by Lewis Carroll
Rabbithole-rs is a nearly well-typed, user-friendly JSON:API type system, with an easy-to-use Macro System to help you modelling the your data.
Inspired a lot by jsonapi-rust, in fact, all of the sample data in tests
are just from this crate. Nice job, michiel!
If you’ve ever argued with your team about the way your JSON responses should be formatted, JSON:API can be your anti-bikeshedding tool.
By following shared conventions, you can increase productivity, take advantage of generalized tooling, and focus on what matters: your application.
When you are designing a RESTful API, the most troubling problem is how to design the Data Structure, especially how to design the Error System. So JSON:API design a Specification for the people like you to specify some rules to help you handling the design problem and free your days!
Maybe the specification is LONG LONG and boring, like reading a textbook, but believe me, you will learn a lot from it, just like a textbook. :)
One of the main reason of this crate is that I need to support RSQL/FIQL, for I think it is a well-defined query system for complex querying. The JSON:API does not given a official Query/Filter solution, but I think RSQL/FIQL is good enough to handle my project.
For more infomation about RSQL/FIQL, see:
As a Scala player, I believe a well designed type system can just avoid a lot of problem. And I want to know if Rust's ADT System can handle the problem with this kind of complexity. In fact, it can handle it well.
As a Java developer, I prefer the annotation system a lot. Thankfully, Rust uses proc_macro system to give the users "most-exactly-the-same" experience.
For example, instead of:
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Dog {
id: String,
name: String,
age: i32,
main_flea: Flea,
fleas: Vec<Flea>,
}
jsonapi_model!(Dog; "dog"; has one main_flea; has many fleas);
I can model my data structure like this:
#[derive(rbh_derive::EntityDecorator, Serialize, Deserialize, Clone)]
#[entity(type = "dogs")]
pub struct Dog<'a> {
#[entity(id)]
pub id: String,
pub name: String,
#[entity(to_many)]
pub fleas: Vec<Flea>,
#[entity(to_many)]
pub friends: Vec<Dog<'a>>,
#[entity(to_one)]
#[serde(bound(deserialize = "Box<Human<'a>>: Deserialize<'de>"))]
pub master: Box<Human<'a>>,
#[entity(to_one)]
pub best_one: Option<Box<Dog<'a>>>,
}
For me, the second one is more beautiful.
Basic JSON:API model system
Basic Macro System
Basic tests
Query/Filter API
A high performance JSON:API Server
See the issue for detail.
meta
and links
fields when using Page QueryIn specification, when using page
query,
meta
object should add a totalPage
fieldlinks
object should add prev
, next
, first
and last
links
but rabbithole
cannot handle it automatically, users should add these fields by implementing
in Fetching::vec_to_document
manually。There are lots of type restrictions in rabbithole-rs. for example:
[#to_one]
decorator can only be use on a field with the type of being:
rabbithole::entity::Entity
rabbithole::entity::Entity
, where wrapper
class is one of:
Option<T>
Box<T>
&T
Option<Box<T>>
#[to_many]
decorator can only use on a field with (all of):
Now because of lacking the Reflection in Rust, the macro now can not check type errors at all, so some solutions may needed.
Because the API interface of JSON:API is complex, I think it's a redundant and boring work to write all the API interface following the specification yourself, so I will do the boring things for you!
The final goal of the project is just like crnk or elide, who can auto generate a bunch of API based on JUST the definition of the models (maybe DAOs). Here I want to just show what will the project look like finally.
The first step is define some API-friendly models.
// This is the derive crate which you can use to generate JSON:API specific traits
extern crate rabbithole_derive as rbh_derive;
#[derive(rbh_derive::EntityDecorator, Serialize, Deserialize, Clone)]
#[entity(type = "people")]
pub struct Human {
#[entity(id)]
pub id_code: Uuid,
pub name: String,
#[entity(to_many)]
pub dogs: Vec<Dog>,
}
#[derive(rbh_derive::EntityDecorator, Serialize, Deserialize, Clone)]
#[entity(type = "dogs")]
pub struct Dog {
#[entity(id)]
pub id: Uuid,
pub name: String,
}
rabbithole
does not bind with any specific databases, which means you have to write your own DAOs.
See rabbithole-endpoint-actix/examples/mock_gen.rs
for more details.
Fetching
traitFetching
trait is a mapping of "fetching data" part in JSON:API, which define a several operations:
GET /articles/1
GET /articles
GET /articles/1/author
, which can be both single and multipleGET /articles/1/relationships/comments
include
part)fields[TYPE]
part)sort
part)page
part)These are all we need to know in fetching data
part. So these operation are abstracted into Fenching
trait.
vec_to_document
part?If you want to transform a Vec<SingleEntity>
into Document
, it will do a lot of things like
excluding un-included resources,
retaining sparse fields,
etc. and etc., and of course I can help you in the background (using Entity::to_document_automatically
).
But more than extracting all the fields from databases and dropping them later, why not just leaving them in databases?
So here is the point. If you don't want to write the Vec<SingleEntity> to Document
code, just use Entity::to_document_automatically
,
or, you could assemble the Document
directly from the database.
...
(any other) part?fetch_collection
will be mapped into: /<ty>?<query>
fetch_single
will be mapped into: /<ty>/<id>?<query>
fetch_relationship
will be mapped into: /<ty>/<id>/relationships/<related_field>?<query>
fetch_related
will be mapped into: /<ty>/<id>/<related_field>?<query>
type Error
will be mapped into the error responses if possibletype Item
must be a SingleEntity