atlassian-rust-api 0.1.0

A wrapper for the Atlassian REST API written in Rust
Documentation
# Atlassian Rust API

atlassian-rust-api is an async wrapper for the Atlassian REST API. It provides a simple, builder-pattern focused way to interact with the Atlassian products. It is based on the official REST APIs for each product.

Note that this is currently under heavy construction and I am currently focusing on Jira Data Center/Cloud for the moment. JSM and Confluence are in the pipeline right after, followed by the rest of the tools.

## Versions

- Jira Data Center: v9.17.0

# Features

## Cargo Feature Flags

- `jira`: Add access to the `jira` crate.
- `experimental`: Add access to experimental endpoints.

# Usage

See the `examples/` folder for more in-depth usage.

## Quickstart

Most of these quickstarts assume `tokio` is being used to provide the async runtime, but the library is agnostic of the async runtime.

### Jira

```rust
use atlassian_rust_api::Jira;

#[tokio::main]

async fn main() -> atlassian_rust_api::Result<()> {
	let jira = Jira::builder()
		.url("https://jira.example.com")
		.username("user")
		.password("password")
		.build()?; // Errs if the URL cannot be parsed.
	let issue = jira.get_issue("ABC-123").send().await?;
	if let Some(fields) = issue.get("fields") {
		println!("{:?}", fields);
	}

	Ok(())
}
```

#### Rocket example

```rust
#[macro_use]

extern crate rocket;

use atlassian_rust_api::Jira;

#[get("/<key>")]

async fn issue(key: &str) -> String {
	// This should really be managed by Rocket but this is a tiny example
	let jira = Jira::builder()
		.url("https://jira.example.com")
		.username("user")
		.password("password")
		.build().unwrap();
	let _issue = jira.get_issue(key).await.unwrap();
	if let Some(fields) = issue.get("fields") {
		if let Some(summary) = fields.get("summary") {
			return summary.to_string()
		}
		return "".to_string()
	}

	"".to_string()
}

#[launch]

fn rocket() -> _ {
	rocket::build().mount("/", routes![issue])
}
```

# Design Pattern

## Endpoints

Every endpoint is made up of 3 parts, the `EndpointBuilder`, the `EndpointRequest`, and the `impl Client` block in the endpoint. Each endpoint to one of the core REST API endpoints gets each of these things.

### EndpointBuilder

The `EndpointBuilder` is what the user interacts with and holds a copy of the `RestClient` for making the call to the REST API and the `EndpointRequest`. The builder implements the setters for the fields contained in the `EndpointRequest` as well as the `send()` function for executing the request.

```rust
pub struct EndpointBuilder {
	client: Arc<RestClient>,
	request: EndpointRequest,
}

impl EndpointBuilder {
	// For the impl Client block to create this
	fn new(client: Arc<RestClient>) -> EndpointBuilder {
		EndpointBuilder { client, request: EndpointRequest::default() }
	}

	// Required fields are for the impl Client block and it forces them to be set with
	// the function it defines to use this builder with
	fn required(mut self, required: i64) -> EndpointBuilder {
		self.request.required = required;
		self
	}

	// Public fields are for the user to set if they want
	pub fn optional(mut self, optional: impl Into<String>) -> EndpointBuilder {
		self.request.optional = Some(optional.into());
		self
	}

	// The actual sending of the request to the REST API
	pub async fn send(self) -> Result<serde_json::Value> {
		self.client.get(self.request).await
	}
}
```

### EndpointRequest

The `EndpointRequest` holds the information required to make the request to the REST API. It also implements the `Endpoint` trait which builds the URL, the query parameters, and the body of the request. It is separate from the `EndpointBuilder` so that each of the fields can remain private from the user and so that it can implement `Default`

```rust
#[derive(Default)]

struct EndpointRequest {
	required: i64,
	optional: Option<String>,
}

impl Endpoint for EndpointRequest {
	fn endpoint(&self) -> Cow<'static, str> {
		format!("resource/{}", self.required).into()
	}

	fn parameters(&self) -> QueryParams<'_> {
		let mut params = QueryParams::default();
		params.push_opt("optionalField", self.optional);
		params
	}
}
```

### impl Client Block

The `impl Client` block is created in the same file as the `EndpointBuilder` and `EndpointRequest` so that they don't have to be explicitly publicized. The user is responsible for setting required fields in the initial call and the `Client` fn should always return the `EndpointBuilder` regardless if there are zero fields for the endpoint. The user should be responsible for calling `.send().await?;` on the `EndpointBuilder` to execute the request.

```rust
impl Client {
	pub fn get_endpoint(&self, required: i64) -> EndpointBuilder {
		EndpointBuilder::new(Arc::clone(&self.client)).required(required)
	}
}
```