Skip to content
This repository has been archived by the owner on Jun 28, 2020. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
elegaanz committed Sep 15, 2018
0 parents commit 9076ab2
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "canapi"
version = "0.1.0"
authors = ["Bat' <baptiste@gelez.xyz>"]

[dependencies]
serde = "1.0"
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Canapi

Common Canapi traits.

Canapi is a collection of Rust crate to make it possible to share a REST API definition between
clients and servers. You define your endpoints (`Endpoint` trait), bind them to your databse (`Provider` trait),
and you can re-use these endpoints in your client (`Fetch` trait).

This requires three separate crates: one for your API defintion, one for your server, and one for the client.

## Example

`my-api`, where you define the API.

```rust
use canapi::Endpoint;

#[derive(Default, Serialize, Deserialize)]
struct UserEndpoint {
id: Option<i32>,
name: Option<String>,
bio: Option<String>,
}

impl Endpoint for PostEndpoint {
type Id = i32;

fn endpoint() -> &'static str {
"/api/v1/users"
}
}
```

`my-server`, to bind endpoints to database (and expose them to the rest of the world)

```rust
use canapi::Provider;
use diesel::*; // Example with Diesel
use my_api::UserEndpoint;

/// Define the User model…

impl Provider<PgConnection> for User {
type Data = UserEndpoint;

fn get(conn: &PgConnection, id: i32) -> Option<Post> {
posts::table.filter(posts::id.eq(id))
.limit(1)
.get_result::<Post>(conn)
.ok()
}
}

// Use rocket, actix-web, iron, gotham or whatever you want to expose your endpoints…
```

`my-client`, to use your API (here with a potential WASM `Fetch` impl)

```rust
use canapi_wasm::WasmFetch;
use plume_api;

#[derive(Default)]
pub struct Api {
users: UserEndpoint
}

fn fetch_first_post() {
let api = Api::default();
let post = api.users.get::<WasmFetch>(1);
assert_eq(post.id, Some(1));
}

fn find_hello() {
let api = Api::default();
let post = api.users.find::<WasmFetch>(PostEndpoint {
name: Some(String::new("Jane")),
..PostEndpoint::default()
});
assert_eq!(post.title, Some(String::new("Jane")));
}
```
75 changes: 75 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
extern crate serde;

use serde::{Serialize, de::DeserializeOwned};
use std::fmt::Display;

pub enum Error {
Fetch(String),
SerDe(String),
NotFound(String),
Authorization(String),
}

/// API to DB link
///
/// `E` is the endpoint (API)
/// `P` is the provider (DB)
pub trait Provider<P> {
type Data: Endpoint;

/// Get a single result by ID
fn get(provider: &P, id: <Self::Data as Endpoint>::Id) -> Result<Self::Data, Error>;

/// List all matching results
fn list(provider: &P, query: Self::Data) -> Vec<Self::Data>;

/// Save a new object, and returns it
fn create(provider: &P, query: Self::Data) -> Result<Self::Data, Error>;

/// Update an object
fn update(provider: &P, id: <Self::Data as Endpoint>::Id, new_data: Self::Data) -> Result<Self::Data, Error>;

/// Delete an object
fn delete(provider: &P, id: <Self::Data as Endpoint>::Id);
}

/// API Endpoint, common to the server and the front
pub trait Endpoint: Default + Serialize + DeserializeOwned {
type Id: Display;

/// The URL on which this endpoint is mounted.
///
/// It should start with a /, and end without.
fn endpoint() -> &'static str;

fn get<F: Fetch>(&self, id: Self::Id) -> Result<Self, Error> {
F::fetch("GET", format!("{}/{}", Self::endpoint(), id), None)
}

fn list<F: Fetch>(&self) -> Result<Self, Error> {
F::fetch("GET", format!("{}", Self::endpoint()), None)
}

fn find<F: Fetch>(&self, query: Self) -> Result<Self, Error> {
F::fetch("GET", format!("{}", Self::endpoint()), Some(query))
}

fn create<F: Fetch>(&self, new: Self) -> Result<Self, Error> {
F::fetch("POST", format!("{}", Self::endpoint()), Some(new))
}

fn update<F: Fetch>(&self, id: Self::Id, data: Self) -> Result<Self, Error> {
F::fetch("PUT", format!("{}/{}", Self::endpoint(), id), Some(data))
}

fn delete<F: Fetch>(&self, id: Self::Id) -> Result<Self, Error> {
F::fetch("DELETE", format!("{}/{}", Self::endpoint(), id), None)
}
}

/// Anything that can perform a network request to fetch an endpoint
pub trait Fetch {

/// Fetch a given endpoint
fn fetch<T: Endpoint>(method: &'static str, url: String, query: Option<T>) -> Result<T, Error>;
}

0 comments on commit 9076ab2

Please sign in to comment.