Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/endpoints/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,19 @@ TypedRest provides a number of endpoint types modelling common REST patterns. Mo
- [Streaming Collection endpoint](reactive/streaming-collection.md) - collection of entities observable as append-only stream

The constructors of all endpoints except entry endpoints take a `referrer` parameter. This is uses to inherit relative URI bases and configuration such as [error handling](../error-handling/index.md) and [link handling](../link-handling/index.md).

## Navigating Between Endpoints

When creating child endpoints from a parent endpoint, the `referrer` parameter ensures that configuration is inherited, including serializers, error handlers, and authentication settings. This makes it easy to build endpoint hierarchies with consistent behavior.

TypedRest supports multiple ways to link endpoints together:

- **[Relative URIs](../link-handling/relative-uris.md)**: Hard-code the path to child resources using relative URIs. This is the simplest approach and works well for APIs with stable, predictable structures.

- **[URI Templates](../link-handling/uri-templates.md)**: Use parameterized URI patterns that can be resolved with specific values. This is useful for dynamic navigation where paths depend on runtime data.

- **[Link Headers](../link-handling/link-header.md)**: Extract links from HTTP Link headers provided by the server. This allows the server to guide navigation without embedding links in response bodies.

- **[HAL Links](../link-handling/hal.md)**: Use links embedded in HAL-formatted JSON responses. This is a standard hypermedia format that combines data and navigation in a single response.

For more details on link handling, see the [link handling documentation](../link-handling/index.md).
7 changes: 7 additions & 0 deletions docs/link-handling/.pages
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
nav:
- index.md
- relative-uris.md
- uri-templates.md
- link-header.md
- hal.md
- serializers.md
173 changes: 173 additions & 0 deletions docs/link-handling/hal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# HAL (Hypertext Application Language)

TypedRest supports links encoded in resources using the HAL format. HAL provides a standard way to embed links and related resources directly in JSON responses.

For details on the HAL specification, see the [HAL draft specification](https://datatracker.ietf.org/doc/html/draft-kelly-json-hal). TypedRest focuses on making HAL links easy to consume within your endpoint hierarchy.

## HAL Content Type

HAL uses the content type `application/hal+json` to indicate HAL-formatted responses. TypedRest automatically detects this content type and extracts links accordingly.

A typical HAL response looks like:

```json
{
"_links": {
"self": {
"href": "/contacts/1337"
},
"edit": {
"href": "/contacts/1337/edit"
},
"next": {
"href": "/contacts/1338"
}
},
"name": "John Smith",
"email": "[email protected]"
}
```

## Using HAL Links

HAL links are accessed using the same `Link()` and `GetLinks()` methods as HTTP Link headers:

=== "C#"

```csharp
var contact = new ElementEndpoint<Contact>(client, relativeUri: "./contacts/1337");
var contactData = await contact.ReadAsync();

// Retrieve a link from the HAL response
var editUri = contact.Link("edit");
if (editUri != null)
{
var editEndpoint = new Endpoint(contact, editUri);
}
```

=== "TypeScript"

```typescript
const contact = new ElementEndpoint<Contact>(client, "./contacts/1337");
const contactData = await contact.read();

// Retrieve a link from the HAL response
const editUri = contact.link("edit");
if (editUri) {
const editEndpoint = new Endpoint(contact, editUri);
}
```

## Multiple Links

HAL supports multiple links with the same relation type. Use `GetLinks()` to retrieve all of them:

=== "C#"

```csharp
var resource = new ElementEndpoint<Resource>(client, relativeUri: "./resource");
await resource.ReadAsync();

// Get all "alternate" links
var alternates = resource.GetLinks("alternate");
foreach (var alternate in alternates)
{
Console.WriteLine($"Alternate: {alternate}");
}
```

=== "TypeScript"

```typescript
const resource = new ElementEndpoint<Resource>(client, "./resource");
await resource.read();

// Get all "alternate" links
const alternates = resource.getLinks("alternate");
for (const alternate of alternates) {
console.log(`Alternate: ${alternate}`);
}
```

In HAL, multiple links are represented as an array:

```json
{
"_links": {
"alternate": [
{"href": "/resource.xml"},
{"href": "/resource.pdf"}
]
}
}
```

## Templated Links

HAL supports templated links using URI Templates ([RFC 6570](https://tools.ietf.org/html/rfc6570)):

```json
{
"_links": {
"search": {
"href": "/contacts{?name,email}",
"templated": true
}
}
}
```

Use `LinkTemplate()` to resolve templated links:

=== "C#"

```csharp
var contacts = new CollectionEndpoint<Contact>(client, relativeUri: "./contacts");
await contacts.ReadAllAsync();

// Resolve the templated link
var searchUri = contacts.LinkTemplate("search", new {name = "Smith"});
```

=== "TypeScript"

```typescript
const contacts = new CollectionEndpoint<Contact>(client, "./contacts");
await contacts.readAll();

// Resolve the templated link
const searchUri = contacts.linkTemplate("search", {name: "Smith"});
```

## Combining with Link Headers

TypedRest can extract links from both HTTP Link headers and HAL `_links` objects simultaneously. By default, both sources are checked, with header links taking precedence over HAL links when both provide a link with the same relation type.

If you need more control over link extraction priority or want to use only specific sources, you can configure the link extraction behavior:

=== "C#"

```csharp
var endpoint = new Endpoint(new Uri("http://example.com/resource"));

// Use AggregateLinkExtractor to combine multiple extractors
endpoint.LinkExtractor = new AggregateLinkExtractor(
new HeaderLinkExtractor(),
new HalLinkExtractor()
);
```

=== "TypeScript"

```typescript
const endpoint = new Endpoint(new URL("http://example.com/resource"));

// Use AggregateLinkExtractor to combine multiple extractors
endpoint.linkExtractor = new AggregateLinkExtractor(
new HeaderLinkExtractor(),
new HalLinkExtractor()
);
```

The `AggregateLinkExtractor` queries each extractor in order and returns the first match found, allowing you to control precedence by ordering the extractors.
39 changes: 34 additions & 5 deletions docs/link-handling/index.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
# Link handling

TypedRest supports a variety of different links:
TypedRest provides multiple ways to navigate between endpoints, ranging from simple hard-coded URIs to dynamic links provided by the server. This flexibility allows you to build clients that work with varying levels of API hypermedia support.

- Hard-coded relative URIs between [endpoints](../endpoints/index.md)
- Links transmitted via the HTTP Link Header
- Links encoded in resources via HAL
- URI templates
## Link Resolution Methods

TypedRest endpoints provide several methods for resolving links:

- **`Link(rel)`**: Retrieves a link with the specified relation type from HTTP Link headers or HAL `_links`
- **`GetLinks(rel)`**: Retrieves all links with the specified relation type (useful when multiple links share the same relation)
- **`LinkTemplate(rel, variables)`**: Resolves a URI template with the specified relation type and variables

These methods work consistently across all link sources, allowing your code to remain the same regardless of whether links come from headers, HAL, or default configurations.

## Link Sources

TypedRest supports multiple sources for link information:

### [Relative URIs](relative-uris.md)

Hard-coded relative URIs between [endpoints](../endpoints/index.md) are the simplest way to navigate. You specify the path when creating child endpoints, and TypedRest handles URI resolution. The non-standard `./` prefix pattern ensures proper path appending.

### [URI Templates](uri-templates.md)

URI Templates ([RFC 6570](https://tools.ietf.org/html/rfc6570)) allow parameterized URIs. Servers can provide templates that clients resolve with specific values, enabling flexible navigation without hard-coding every possible path.

### [Link Headers](link-header.md)

Links transmitted via the HTTP Link Header ([RFC 8288](https://tools.ietf.org/html/rfc8288)) provide navigation information without requiring specific response body formats. TypedRest also supports a non-standard `templated=true` extension for URI templates in headers.

### [HAL](hal.md)

Links encoded in resources via HAL (Hypertext Application Language) embed navigation directly in JSON responses using the `application/hal+json` content type and `_links` objects.

## Serializers

The [serializers](serializers.md) page documents how TypedRest handles different content formats (JSON, XML, BSON) and how serializer configuration is inherited between endpoints.
129 changes: 129 additions & 0 deletions docs/link-handling/link-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Link Header

TypedRest can extract links from HTTP Link headers as specified in [RFC 8288](https://tools.ietf.org/html/rfc8288). This allows servers to provide navigation information without embedding it in response bodies.

For details on the Link header format and semantics, see [RFC 8288](https://tools.ietf.org/html/rfc8288).

## Link Header Format

Link headers follow this basic format:

```http
Link: <https://example.com/page/2>; rel="next",
<https://example.com/page/1>; rel="prev"
```

Each link consists of:

- A URI reference in angle brackets (`<URI>`)
- One or more parameters, most importantly `rel` for the link relation type
- Optional additional parameters like `title`

## Extracting Links

TypedRest automatically extracts links from HTTP responses. Use the `Link()` method to retrieve a specific link by its relation type:

=== "C#"

```csharp
var contacts = new CollectionEndpoint<Contact>(client, relativeUri: "./contacts");
await contacts.ReadAllAsync(); // This performs a GET request

// Retrieve a link from the response headers
var nextPageUri = contacts.Link("next");
if (nextPageUri != null)
{
// Navigate to the next page
var nextPage = new CollectionEndpoint<Contact>(contacts, nextPageUri);
}
```

=== "TypeScript"

```typescript
const contacts = new CollectionEndpoint<Contact>(client, "./contacts");
await contacts.readAll(); // This performs a GET request

// Retrieve a link from the response headers
const nextPageUri = contacts.link("next");
if (nextPageUri) {
// Navigate to the next page
const nextPage = new CollectionEndpoint<Contact>(contacts, nextPageUri);
}
```

## Getting All Links

Use `GetLinks()` to retrieve all links with a specific relation type (useful when multiple links share the same relation):

=== "C#"

```csharp
var endpoint = new Endpoint(new Uri("http://example.com/resource"));
await endpoint.ReadMetaAsync(); // HEAD request to fetch headers

// Get all links with the "alternate" relation
var alternateLinks = endpoint.GetLinks("alternate");
foreach (var link in alternateLinks)
{
Console.WriteLine($"Alternate format: {link}");
}
```

=== "TypeScript"

```typescript
const endpoint = new Endpoint(new URL("http://example.com/resource"));
await endpoint.readMeta(); // HEAD request to fetch headers

// Get all links with the "alternate" relation
const alternateLinks = endpoint.getLinks("alternate");
for (const link of alternateLinks) {
console.log(`Alternate format: ${link}`);
}
```

## Templated Links in Headers

TypedRest implements a non-standard extension: `templated=true` parameter for URI templates in Link headers.

```http
Link: </contacts{?name,email}>; rel="search"; templated=true
```

This combines the Link header mechanism with URI template support. While not part of RFC 8288, this extension provides a convenient way to express templated links without requiring HAL or another hypermedia format.

!!! warning
The `templated=true` extension is not standard and may not be recognized by other HTTP clients or generic Link header parsers. If interoperability with non-TypedRest clients is important, consider using HAL for templated links instead.

When `templated=true` is present, use `LinkTemplate()` instead of `Link()`:

=== "C#"

```csharp
var contacts = new CollectionEndpoint<Contact>(client, relativeUri: "./contacts");
await contacts.ReadAllAsync();

// Resolve the templated link
var searchUri = contacts.LinkTemplate("search", new {name = "Smith"});
```

=== "TypeScript"

```typescript
const contacts = new CollectionEndpoint<Contact>(client, "./contacts");
await contacts.readAll();

// Resolve the templated link
const searchUri = contacts.linkTemplate("search", {name: "Smith"});
```

## Supported Link Header Attributes

TypedRest recognizes the following Link header parameters:

- **`rel`**: The link relation type (required)
- **`title`**: A human-readable title for the link (optional, currently extracted but not exposed)
- **`templated`**: Non-standard extension indicating the URI is a template (optional)

Other parameters defined in RFC 8288 may be present in headers but are not currently processed by TypedRest.
Loading