Skip to content
/ rjapi Public

A framework-agnostic, type-safe JSON:API 1.1 implementation for Rust πŸ¦€

License

Notifications You must be signed in to change notification settings

mohdbk/rjapi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

rjapi - JSON:API 1.1 Implementation for Rust

Crates.io Documentation License

A framework-agnostic, ergonomic, and type-safe implementation of the JSON:API 1.1 specification for Rust applications.

Features

  • βœ… Compliant & Modern: Fully compliant with the JSON:API 1.1 specification.
  • πŸš€ Framework-Agnostic: Designed to work seamlessly with any web framework, including Axum, Actix Web, and Rocket.
  • πŸ›‘οΈ Type-Safe by Design: Leverages Rust's type system to prevent common errors at compile time. Application-specific models are used directly, reducing the need for manual and error-prone conversions.
  • ✨ Ergonomic API: A fluent, builder-based API for constructing responses and a powerful extractor for handling requests makes your code cleaner and more readable.
  • πŸ“¦ Lightweight & Efficient: A minimal dependency footprint and an architecture focused on performance ensure your application remains fast.
  • πŸ”§ Extensible: Easily extendable to fit your application's specific needs.
  • πŸ“š Comprehensive Documentation: Clear and detailed documentation with practical examples.

Installation

Add this to your Cargo.toml:

[dependencies]
rjapi = "0.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Usage

Defining a Resource

To make your models compatible with rjapi, implement the JsonApiResource trait.

use rjapi::JsonApiResource;
use serde::Serialize;
use serde_json::json;

#[derive(Serialize)]
struct Post {
    id: String,
    title: String,
    content: String,
}

impl JsonApiResource for Post {
    const RESOURCE_TYPE: &'static str = "posts";

    fn id(&self) -> String {
        self.id.clone()
    }

    fn attributes(&self) -> serde_json::Value {
        json!({
            "title": self.title,
            "content": self.content,
        })
    }
}

Creating a Response

Use the JsonApiResponse builder to construct type-safe, compliant JSON:API responses.

use rjapi::{JsonApiResponse, Resource};
# use rjapi::JsonApiResource;
# use serde::Serialize;
# use serde_json::json;
# #[derive(Serialize)]
# struct Post {
#     id: String,
#     title: String,
#     content: String,
# }
# impl JsonApiResource for Post {
#     const RESOURCE_TYPE: &'static str = "posts";
#     fn id(&self) -> String { self.id.clone() }
#     fn attributes(&self) -> serde_json::Value { json!({ "title": self.title, "content": self.content }) }
# }

let post = Post {
    id: "1".to_string(),
    title: "Hello, World!".to_string(),
    content: "This is the first post.".to_string(),
};

let resource = Resource {
    resource_type: Post::RESOURCE_TYPE.to_string(),
    id: post.id(),
    attributes: Some(post.attributes()),
    relationships: None,
    links: None,
    meta: None,
};

let response = JsonApiResponse::new(resource).build();

println!("{}", serde_json::to_string_pretty(&response).unwrap());

Framework Integration (Axum Example)

The JsonApiRequest extractor simplifies request handling in your web framework.

use axum::{
    extract::State,
    http::StatusCode,
    response::{IntoResponse, Response},
    Json, Router,
};
use rjapi::{JsonApiResponse, JsonApiResource, Resource, JsonApiRequest};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

// Application state
#[derive(Clone)]
struct AppState {
    posts: Arc<RwLock<Vec<Post>>>,
}

# #[derive(Clone, Serialize, Deserialize)]
# struct Post {
#     id: String,
#     title: String,
#     content: String,
# }
# impl JsonApiResource for Post {
#     const RESOURCE_TYPE: &'static str = "posts";
#     fn id(&self) -> String { self.id.clone() }
#     fn attributes(&self) -> serde_json::Value { serde_json::json!({ "title": self.title, "content": self.content }) }
# }

// A specific type for post creation attributes
#[derive(Deserialize)]
struct PostAttributes {
    title: String,
    content: String,
}

// Axum handler for creating a post
async fn create_post(
    State(state): State<AppState>,
    Json(request): Json<JsonApiRequest<PostAttributes>>,
) -> impl IntoResponse {
    let attributes = request.data.attributes;

    let post = Post {
        id: uuid::Uuid::new_v4().to_string(),
        title: attributes.title,
        content: attributes.content,
    };

    state.posts.write().await.push(post.clone());

    let resource = Resource {
        resource_type: Post::RESOURCE_TYPE.to_string(),
        id: post.id(),
        attributes: Some(serde_json::to_value(post).unwrap()),
        ..Default::default()
    };

    let response = JsonApiResponse::new(resource).build();
    (StatusCode::CREATED, Json(response))
}

Documentation

For full documentation, visit docs.rs/rjapi.

Examples

Check out the examples directory for more detailed usage examples:

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please read our Contributing Guide for details on how to submit pull requests, report issues, and suggest improvements.

Changelog

0.0.1 (Initial Release)

  • Framework-agnostic JSON:API 1.1 implementation
  • Support for resources, relationships, errors, and metadata
  • Middleware for content-type validation
  • Comprehensive examples and documentation

About

A framework-agnostic, type-safe JSON:API 1.1 implementation for Rust πŸ¦€

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

No packages published

Languages