rjapi 0.0.1

A framework-agnostic JSON:API 1.1 implementation for Rust
Documentation
# rjapi - JSON:API 1.1 Implementation for Rust

[![Crates.io](https://img.shields.io/crates/v/rjapi.svg)](https://crates.io/crates/rjapi)
[![Documentation](https://docs.rs/rjapi/badge.svg)](https://docs.rs/rjapi)
[![License](https://img.shields.io/crates/l/rjapi.svg)](https://github.com/mohdbk/rjapi/blob/master/LICENSE)

A framework-agnostic, ergonomic, and type-safe implementation of the [JSON:API 1.1 specification](https://jsonapi.org/) 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`:

```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.

```rust
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.

```rust
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.

```rust
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](https://docs.rs/rjapi).

## Examples

Check out the [examples](examples/) directory for more detailed usage examples:

- [Basic Usage]examples/basic.rs
- [Axum Integration]examples/axum.rs
- [Error Handling]examples/error_handling.rs

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Contributing

Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) 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