# @atlassian/ari

The official TypeScript library for creating, parsing and working with ARIs 🚀

The classes in this repository are auto-generated based upon the [ARI Registry](https://bitbucket.org/atlassian/atlassian-resource-identifier/src/master/registry/resource-owners.yaml). We plan to release a new version of this library at the start of every business week, AEST.

## Installation

To install this library, run:

```
yarn add @atlassian/ari
```

or

```
npm i @atlassian/ari
```

## Usage

This library aims to give you the primitives you need to get working with ARIs. To the extent we can, we aim to provide a lightweight API for our consumers.

### Importing

You can import a specific ARI class from a few places, depending on your bundle size requirements:

```typescript
// Import from an entry point specific to a resource owner (Confluence).
import { ConfluencePageAri } from '@atlassian/ari/confluence';

// Import from an entry point specific to a resource owner (Confluence) and a resource type (page).
import { ConfluencePageAri } from '@atlassian/ari/confluence/page';

// Import core classes from root entry point.
import { Ari, AnyAri, Ati } from '@atlassian/ari';
```

### Quick start

Most of the methods and classes you can interact with are shown below. For a more detailed reference, keep scrolling until the [API reference](#api-reference) section.

```typescript
// Import core classes from root entrypoint.
import { AnyAri, Ati, ResourceOwner, ResourceType } from '@atlassian/ari';
// Import from an entry-point specific to a resource owner (Confluence) and a resource type (page).
import { ConfluenceSiteAri } from '@atlassian/ari/confluence/site';

// ===========================================================================
// Option 1️⃣: work with ARI classes representing definitions from the registry.
// ===========================================================================
// - Create an ARI, using segments defined in the registry definition.
const pageAri = ConfluencePageAri.create({
  siteId: '123',
  pageId: '456',
});

// - Access specific segments for this ARI.
console.log(pageAri.siteId); // yields '123'
console.log(pageAri.pageId); // yields '456'

// - Access generic segments conforming to the ARI specification.
console.log(pageAri.cloudId); // yields '123'
console.log(pageAri.resourceOwner); // yields 'confluence'
console.log(pageAri.resourceType); // yields 'page'
console.log(pageAri.resourceId); // yields '456'

// - Access the ATI for this segment.
console.log(pageAri.ati); // yields Ati { resourceOwner: 'confluence', resourceType: 'page' }

// - Check if an ARI string (or class) can be interepted as this type.
console.log(ConfluenceSiteAri.check('ari:cloud:jira::site/ee95456c-a203-42dc-b284-7bbb3854457f')); // yields 'false'
console.log(ConfluenceSiteAri.check('ari:cloud:confluence::site/ee95456c-a203-42dc-b284-7bbb3854457f')); // yields 'true'

// ===========================================================================
// Option 2️⃣: work with generic ARI classes, conforming to the ARI specification.
// ===========================================================================
// - Create an ARI based on the specification (no convenience methods)
const pageAriTheSecond = AnyAri.create({
  resourceOwner: ResourceOwner.Confluence,
  cloudId: '123',
  resourceType: ResourceType.Page,
  resourceId: '456',
});

// - Access generic segments conforming to the ARI specification
console.log(pageAri.cloudId); // yields '123'
console.log(pageAri.resourceOwner); // yields 'confluence'
console.log(pageAri.resourceType); // yields 'page'
console.log(pageAri.resourceId); // yields '456'

// - Access the ATI for this segment
console.log(pageAri.ati); // yields Ati { resourceOwner: 'confluence', resourceType: 'page' }
```

Lastly, here is an example of parsing into a specific ARI type:

```typescript
import { ConfluencePageAri, JiraIssueAri, TrelloCardAri } from '@atlassian/ari';

const invokeApi = (ari: string): Promise<Ari> => {
  if (TrelloCardAri.check(ari)) {
    return this.http.client.post(`/trello/api/${ari}`);
  }

  if (JiraIssueAri.check(ari)) {
    return this.http.client.post(`/jira/api/${ari}`);
  }

  if (ConfluencePageAri.check(ari)) {
    return this.http.client.post(`/confluence/api/${ari}`);
  }
};

const ari = await invokeApi('ari:cloud:confluence:123:page/456'); // demonstrative of a valid Confluence Page ARI
console.log(ari.resourceId); // yields the resource ID returned from the upstream API
```

### API reference

#### Working with ARIs

##### Parsing an ARI

If you are parsing an ARI from the registry, you can use a specific `Ari` class to parse from `string` to that instance (e.g. `ConfluencePageAri`), or the generic `Ari` for unregistered ARIs.

```typescript
// Import from an entry-point specific to a resource owner (Confluence) and a resource type (page).
import { ConfluencePageAri } from '@atlassian/ari/confluence/page';

const ari = ConfluencePageAri.parse('ari:cloud:confluence:123:page/456'); // yields a ConfluencePageAri class instance
console.log(ari.siteId); // yields 123
console.log(ari.pageId); // yields 456
```

If you are parsing an ARI which does not exist in the registry (:paddlin:) you can use the core ARI class.

```typescript
// Import core classes from root entrypoint.
import { AnyAri } from '@atlassian/ari';

const ari = AnyAri.parse('ari:cloud:confluence:123:page/456'); // yields a generic ARI class instance
console.log(ari.cloudId); // yields 123
console.log(ari.resourceId); // yields 456
```

**Note:** if the invocation of `.parse()` fails, a `ValidationError` will be thrown.

```typescript
// Import core classes from root entrypoint.
import { AnyAri } from '@atlassian/ari';

try {
  const ari = AnyAri.parse('ari:cloud:confluence:123:unregistered-ari/456'); // yields a generic ARI class instance
} catch (err) {
  if (err.constructor.name === 'ValidationError') {
    // handle error
  }
}
```

##### Creating an ARI

If you are creating an ARI from the registry, you can use a specific `Ari` class to create from a set of options for that instance in conformance with its definition in the ARI registry (e.g. `ConfluencePageAri`), or the generic `Ari` for unregistered ARIs.

```typescript
// Import from an entry-point specific to a resource owner (Confluence) and a resource type (page).
import { ConfluencePageAri } from '@atlassian/ari/confluence/page';

const ari = ConfluencePageAri.create({
  // yields a ConfluencePageAri class instance
  siteId: '123',
  pageId: '456',
});

console.log(ari.siteId); // yields 123
console.log(ari.pageId); // yields 456
```

If you are creating an ARI which does not exist in the registry (double :paddlin:) you can use the core ARI class.

```typescript
// Import core classes from root entrypoint.
import { AnyAri } from '@atlassian/ari';

const ari = AnyAri.create({
  // yields a generic ARI class instance
  resourceOwner: 'confluence',
  cloudId: '123',
  resourceType: 'page',
  resourceId: '456',
});

console.log(ari.cloudId); // yields 123
console.log(ari.resourceId); // yields 456
```

**Note:** if the invocation of `.create()` or `parse()` fails, a `ValidationError` will be thrown.

```typescript
// Import core classes from root entrypoint.
import { AnyAri } from '@atlassian/ari';

try {
  const ari = AnyAri.parse('ari:cloud:confluence:123:unregistered-ari/456'); // yields a generic ARI class instance
} catch (err) {
  if (err.constructor.name === 'ValidationError') {
    // handle error
  }
}
```

##### Checking ARIs

You can use the `check()` function to determine if an ARI string or class fits the requirements of a certain ARI.

```typescript
import { AnyAri, ConfluenceSiteAri, JiraSiteAri } from '@atlassian/ari';

const result1 = AnyAri.check(ConfluenceSiteAri.create({ siteId: 'ee95456c-a203-42dc-b284-7bbb3854457f' }));
const result2 = JiraSiteAri.check('ari:cloud:confluence::site/ee95456c-a203-42dc-b284-7bbb3854457f');

console.log(result1); // yields true
console.log(result2); // yields false
```

#### Working with ATIs

##### Parsing an ATI

For ATIs, the use of more specific instance is not required. The core `Ati` class should be used:

```typescript
// Import core classes from root entrypoint.
import { Ati, ResourceOwner, ResourceType } from '@atlassian/ari';

const ati = Ati.parse('ati:cloud:confluence:page');
console.log(ati.resourceOwner); // yields `confluence`
console.log(ati.resourceType); // yields `page`
```

##### Creating an ATI

For ATIs, the use of more specific instance is not required. The core `Ati` class should be used:

```typescript
// Import core classes from root entrypoint.
import { Ati, ResourceOwner, ResourceType } from '@atlassian/ari';

const ati = Ati.create({
  resourceOwner: ResourceOwner.Confluence,
  resourceType: ResourceType.Page,
});

console.log(ati.toString()); // yields `ati:cloud:confluence:page`
```

#### Working with ARMs

##### Parsing an ARM

ARMs are exposed through the top-level `Arm` class. To parse an ARM string do the following:

```typescript
// Import `Arm` class from root entrypoint.
import { Arm } from '@atlassian/ari';

const arm = Arm.parse('arm:cloud:jira.*::board/.+');
console.log(arm.resourceOwnerMatcher); // yields `jira.*`
console.log(arm.resourceTypeMatcher); // yields `board`
```

##### Creating an ARM

You can create an ARM using the same `Arm` class mentioned above!

```typescript
// Import `Arm` from root entrypoint.
import { Arm } from '@atlassian/ari';

const arm = Arm.create({
  resourceOwnerMatcher: 'jira.*',
  cloudIdMatcher: '',
  resourceTypeMatcher: 'board',
  resourceIdMatcher: '.+',
});

console.log(arm.toString()); // yields `arm:cloud:jira.*::board/.+`
```

##### Matching an ARI against an ARM

You can match ARIs against ARMs using the `.match` method on ARM instances:

```typescript
// Import `Arm` from root entrypoint.
import { Arm } from '@atlassian/ari';
import { ConfluencePageAri } from '@atlassian/ari/confluence/page';

const ari = ConfluencePageAri.create({
  siteId: '123',
  pageId: '456',
});
const arm = Arm.create({
  resourceOwnerMatcher: 'confluence',
  cloudIdMatcher: '[0-9]+',
  resourceTypeMatcher: 'page',
  resourceIdMatcher: '.+',
});

console.log(arm.match(ari)); // yields `true`
```

## FAQ

**I have a feature request - where should I raise it?**

Please create a ticket on [this Trello board](https://trello.com/b/MRG2vKxD). We will try to get back to you ASAP!

**I cannot see an ARI which should exist in this repository. What do I do?**

Try to figure out who the resource owner of the ARI is, and _kindly_ ask them to add their ARI to the ARI Registry YAML 😄

We auto-generate classes based on what exists in the registry. Teams should be reflecting their ARI back into it, using the process defined [here](https://developer.atlassian.com/platform/atlassian-resource-identifier/resource-owners/processes/).

If your ARI has recently been added to the registry, you will have to wait until the start of the next business week (AEST) for the changes to be reflected in this library.

**I need a change to go through urgently. Who do I speak to?**

Come and chat to us in the [#ari-working-group](https://atlassian.slack.com/archives/CTXV0QAQ6) channel!

## Contributing

If you're keen on helping us build out this library, reach out to us in [#ari-working-group](https://atlassian.slack.com/archives/CTXV0QAQ6) 😄

As a Working Group, we meet every fortnight. Library developers and maintainers will be present - we'd love to meet you and collaborate with you on this!
