Skip to content

Commit 36849f9

Browse files
committed
Added documentation for "Link handling"
1 parent 8d6e954 commit 36849f9

10 files changed

Lines changed: 437 additions & 6 deletions

File tree

docs/.pages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ nav:
33
- introduction.md
44
- getting-started
55
- endpoints
6+
- serializers
67
- error-handling
78
- link-handling
89
- code-generation

docs/endpoints/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ TypedRest provides a number of endpoint types modelling common REST patterns. Mo
3030
- [Streaming endpoint](reactive/streaming.md) - stream of entities via persistent connection
3131
- [Streaming Collection endpoint](reactive/streaming-collection.md) - collection of entities observable as append-only stream
3232

33-
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).
33+
The constructors of all endpoints (except entry endpoints) take a `referrer` parameter. This is used to inherit [relative URI bases](../link-handling/index.md), [serializers](../serializers/index.md) and [error handling](../error-handling/index.md).

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ TypedRest helps you build type-safe, fluent-style REST API clients. Common REST
115115
[Endpoints](endpoints/index.md)
116116
: Documentation for all endpoint types provided by TypedRest.
117117

118+
[Serializers](serializers/index.md)
119+
: How to convert objects to and from the wire format (e.g., JSON).
120+
118121
[Error handling](error-handling/index.md)
119122
: How to handle API errors with TypedRest.
120123

docs/link-handling/.pages

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
nav:
2+
- index.md
3+
- relative-uris.md
4+
- uri-templates.md
5+
- link-header.md
6+
- hal.md

docs/link-handling/hal.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# HAL (Hypertext Application Language)
2+
3+
TypedRest supports extracting links from response bodies using the [HAL specification](https://datatracker.ietf.org/doc/html/draft-kelly-json-hal). HAL provides a consistent format for embedding links and embedded resources within JSON responses.
4+
5+
## HAL format
6+
7+
HAL adds a `_links` object to JSON responses containing links organized by relation type:
8+
9+
```json
10+
{
11+
"id": 123,
12+
"name": "John Doe",
13+
"_links": {
14+
"self": { "href": "/users/123" },
15+
"orders": { "href": "/users/123/orders" },
16+
"search": { "href": "/users/{id}", "templated": true }
17+
}
18+
}
19+
```
20+
21+
## Content type
22+
23+
TypedRest recognizes HAL responses by the `application/hal+json` content type:
24+
25+
When this content type is present, TypedRest automatically parses the `_links` object and makes the links available through the standard link resolution methods.
26+
27+
## Using HAL links
28+
29+
After receiving a HAL response, you can resolve links just like with HTTP Link headers:
30+
31+
=== "C#"
32+
33+
```csharp
34+
await endpoint.ReadAsync();
35+
36+
// Resolve a single link
37+
Uri ordersUri = endpoint.Link("orders");
38+
39+
// Resolve a templated link
40+
Uri userUri = endpoint.LinkTemplate("search", new { id = "456" });
41+
```
42+
43+
=== "TypeScript"
44+
45+
```typescript
46+
await endpoint.read();
47+
48+
// Resolve a single link
49+
const ordersUri = endpoint.link("orders");
50+
51+
// Resolve a templated link
52+
const userUri = endpoint.linkTemplate("search", { id: "456" });
53+
```
54+
55+
## Multiple links
56+
57+
HAL supports multiple links for the same relation type using an array:
58+
59+
```json
60+
{
61+
"_links": {
62+
"item": [
63+
{ "href": "/items/1", "title": "First Item" },
64+
{ "href": "/items/2", "title": "Second Item" }
65+
]
66+
}
67+
}
68+
```
69+
70+
Retrieve all links with `GetLinks`:
71+
72+
=== "C#"
73+
74+
```csharp
75+
var items = endpoint.GetLinks("item");
76+
foreach (var (uri, title) in items)
77+
{
78+
Console.WriteLine($"{title}: {uri}");
79+
}
80+
```
81+
82+
=== "TypeScript"
83+
84+
```typescript
85+
const items = endpoint.getLinks("item");
86+
for (const { uri, title } of items) {
87+
console.log(`${title}: ${uri}`);
88+
}
89+
```
90+
91+
## Templated links
92+
93+
HAL links can be marked as [templates](uri-templates.md) with the `templated` property:
94+
95+
```json
96+
{
97+
"_links": {
98+
"find": {
99+
"href": "/users{?name,email}",
100+
"templated": true
101+
}
102+
}
103+
}
104+
```
105+
106+
=== "C#"
107+
108+
```csharp
109+
var findUri = endpoint.LinkTemplate("find", new { name = "John", email = "john@example.com" });
110+
// Result: /users?name=John&email=john%40example.com
111+
```
112+
113+
=== "TypeScript"
114+
115+
```typescript
116+
const findUri = endpoint.linkTemplate("find", { name: "John", email: "john@example.com" });
117+
// Result: /users? name=John&email=john%40example.com
118+
```

docs/link-handling/index.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
# Link handling
22

3-
TypedRest supports a variety of different links:
3+
TypedRest supports a variety of different ways to establish links between [endpoints](../endpoints/index.md):
44

5-
- Hard-coded relative URIs between [endpoints](../endpoints/index.md)
6-
- Links transmitted via the HTTP Link Header
7-
- Links encoded in resources via HAL
8-
- URI templates
5+
- [Hard-coded relative URIs](relative-uris.md)
6+
- [URI templates](uri-templates.md) for dynamic URL construction
7+
- [Links via HTTP Link Header](link-header.md) transmitted in response headers
8+
- [Links encoded in resources via HAL](hal.md) for hypermedia-driven APIs
9+
10+
## Link resolution
11+
12+
All endpoints provide methods for resolving links:
13+
14+
=== "C#"
15+
16+
- `GetLinks(rel)` - Resolves all links with a specific relation type
17+
- `Link(rel)` - Resolves a single link with a specific relation type
18+
- `LinkTemplate(rel, variables)` - Resolves a link template with variables
19+
20+
=== "TypeScript"
21+
22+
- `getLinks(rel)` - Resolves all links with a specific relation type
23+
- `link(rel)` - Resolves a single link with a specific relation type
24+
- `linkTemplate(rel, variables)` - Resolves a link template with variables
25+
26+
These methods use cached data from the last response. On cache miss, they perform a lazy lookup using HTTP `HEAD`.

docs/link-handling/link-header.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# HTTP Link header
2+
3+
TypedRest can extract links from HTTP `Link` headers as defined in [RFC 8288](https://tools.ietf. org/html/rfc8288). This enables HATEOAS-style navigation where the server provides links to related resources in response headers.
4+
5+
## Header format
6+
7+
The HTTP `Link` header follows this format:
8+
9+
```
10+
Link: <target-uri>; rel="relation-type"; title="optional title"
11+
```
12+
13+
Multiple links can be specified in a single header, separated by commas:
14+
15+
```
16+
Link: <http://example.com/users>; rel=self, <http://example.com/user/123>; rel=child
17+
```
18+
19+
## Extracting links
20+
21+
TypedRest automatically extracts links from response headers after each request. You can then resolve them:
22+
23+
=== "C#"
24+
25+
```csharp
26+
// Perform a request
27+
await endpoint.ReadAsync();
28+
29+
// Get all links with a specific relation type
30+
var links = endpoint.GetLinks("child");
31+
foreach (var (uri, title) in links)
32+
{
33+
Console.WriteLine($"URI: {uri}, Title: {title}");
34+
}
35+
36+
// Get a single link
37+
Uri collectionUri = endpoint.Link("next");
38+
```
39+
40+
=== "TypeScript"
41+
42+
```typescript
43+
// Perform a request
44+
await endpoint.read();
45+
46+
// Get all links with a specific relation type
47+
const links = endpoint.getLinks("child");
48+
for (const { uri, title } of links) {
49+
console.log(`URI: ${uri}, Title: ${title}`);
50+
}
51+
52+
// Get a single link
53+
const collectionUri = endpoint.link("next");
54+
```
55+
56+
## Templated links
57+
58+
TypedRest extends the standard Link header format to support [URI templates](uri-templates.md). Templates are indicated with `templated=true`:
59+
60+
```
61+
Link: </users/{id}>; rel=user; templated=true
62+
```
63+
64+
This is a non-standard extension, but it allows servers to provide dynamic link templates via headers without needing HAL or another hypermedia format in the response body.
65+
66+
=== "C#"
67+
68+
```csharp
69+
// Server response:
70+
// Link: </users/{id}>; rel=user; templated=true
71+
72+
var userUri = endpoint.LinkTemplate("user", new { id = "123" });
73+
// Result: http://example.com/users/123
74+
```
75+
76+
=== "TypeScript"
77+
78+
```typescript
79+
// Server response:
80+
// Link: </users/{id}>; rel=user; templated=true
81+
82+
const userUri = endpoint.linkTemplate("user", { id: "123" });
83+
// Result: http://example.com/users/123
84+
```
85+
86+
## Link header attributes
87+
88+
The following attributes are supported:
89+
90+
| Attribute | Description |
91+
|--------------|----------------------------------------------------|
92+
| `rel` | The relation type (required) |
93+
| `title` | Human-readable title for the link (optional) |
94+
| `templated` | Indicates a URI template when set to `true` (non-standard) |
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Relative URIs
2+
3+
The most straightforward way to navigate between endpoints is using hard-coded relative URIs. When creating child endpoints, you pass a relative URI that is resolved against the parent endpoint's URI.
4+
5+
## Basic usage
6+
7+
=== "C#"
8+
9+
```csharp
10+
var client = new EntryEndpoint(new Uri("http://example.com/api/"));
11+
var contacts = new CollectionEndpoint<Contact>(client, "contacts");
12+
// Results in: http://example.com/api/contacts
13+
```
14+
15+
=== "TypeScript"
16+
17+
```typescript
18+
const client = new EntryEndpoint(new URL("http://example.com/api/"));
19+
const contacts = new CollectionEndpoint<Contact>(client, "contacts");
20+
// Results in: http://example.com/api/contacts
21+
```
22+
23+
## Trailing slash pattern
24+
25+
Standard URI resolution can be tricky when the parent URI doesn't have a trailing slash. Consider:
26+
27+
- Base URI: `http://example.com/endpoint`
28+
- Relative URI: `subresource`
29+
- Standard resolution: `http://example.com/subresource` (replaces `endpoint`)
30+
31+
This is often not the desired behavior. TypedRest provides a non-standard `./` prefix pattern to handle this:
32+
33+
=== "C#"
34+
35+
```csharp
36+
var parent = new ElementEndpoint<MyEntity>(client, "endpoint");
37+
// parent.Uri = http://example.com/endpoint
38+
39+
// Without ./ prefix - replaces last segment
40+
var child1 = new ActionEndpoint(parent, "subresource");
41+
// child1.Uri = http://example.com/subresource
42+
43+
// With ./ prefix - appends to path
44+
var child2 = new ActionEndpoint(parent, "./subresource");
45+
// child2.Uri = http://example.com/endpoint/subresource
46+
```
47+
48+
=== "TypeScript"
49+
50+
```typescript
51+
const parent = new ElementEndpoint<MyEntity>(client, "endpoint");
52+
// parent.uri = http://example.com/endpoint
53+
54+
// Without ./ prefix - replaces last segment
55+
const child1 = new ActionEndpoint(parent, "subresource");
56+
// child1.uri = http://example.com/subresource
57+
58+
// With ./ prefix - appends to path
59+
const child2 = new ActionEndpoint(parent, "./subresource");
60+
// child2.uri = http://example.com/endpoint/subresource
61+
```
62+
63+
The `./` prefix tells TypedRest to ensure a trailing slash on the parent URI before resolving the relative URI. This makes the relative URI resolution behave as if the parent URI was `http://example.com/endpoint/`.
64+
65+
## Default links
66+
67+
You can also register default links that will be used when the server doesn't provide links for a specific relation type:
68+
69+
=== "C#"
70+
71+
```csharp
72+
class MyEndpoint : EndpointBase
73+
{
74+
public MyEndpoint(IEndpoint referrer, string relativeUri)
75+
: base(referrer, relativeUri)
76+
{
77+
SetDefaultLink("related", "./related-resource");
78+
}
79+
}
80+
```
81+
82+
=== "TypeScript"
83+
84+
```typescript
85+
class MyEndpoint extends Endpoint {
86+
constructor(referrer: Endpoint, relativeUri: string) {
87+
super(referrer, relativeUri);
88+
this.setDefaultLink("related", "./related-resource");
89+
}
90+
}
91+
```

0 commit comments

Comments
 (0)