Skip to content

Commit 5179d57

Browse files
Add comprehensive link handling and serializer documentation
Co-authored-by: bastianeicher <414366+bastianeicher@users.noreply.github.com>
1 parent dfec6bb commit 5179d57

8 files changed

Lines changed: 783 additions & 5 deletions

File tree

docs/endpoints/index.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,19 @@ TypedRest provides a number of endpoint types modelling common REST patterns. Mo
3131
- [Streaming Collection endpoint](reactive/streaming-collection.md) - collection of entities observable as append-only stream
3232

3333
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).
34+
35+
## Navigating Between Endpoints
36+
37+
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.
38+
39+
TypedRest supports multiple ways to link endpoints together:
40+
41+
- **[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.
42+
43+
- **[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.
44+
45+
- **[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.
46+
47+
- **[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.
48+
49+
For more details on link handling, see the [link handling documentation](../link-handling/index.md).

docs/link-handling/.pages

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

docs/link-handling/hal.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# HAL (Hypertext Application Language)
2+
3+
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.
4+
5+
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.
6+
7+
## HAL Content Type
8+
9+
HAL uses the content type `application/hal+json` to indicate HAL-formatted responses. TypedRest automatically detects this content type and extracts links accordingly.
10+
11+
A typical HAL response looks like:
12+
13+
```json
14+
{
15+
"_links": {
16+
"self": {
17+
"href": "/contacts/1337"
18+
},
19+
"edit": {
20+
"href": "/contacts/1337/edit"
21+
},
22+
"next": {
23+
"href": "/contacts/1338"
24+
}
25+
},
26+
"name": "John Smith",
27+
"email": "john@example.com"
28+
}
29+
```
30+
31+
## Using HAL Links
32+
33+
HAL links are accessed using the same `Link()` and `GetLinks()` methods as HTTP Link headers:
34+
35+
=== "C#"
36+
37+
```csharp
38+
var contact = new ElementEndpoint<Contact>(client, relativeUri: "./contacts/1337");
39+
var contactData = await contact.ReadAsync();
40+
41+
// Retrieve a link from the HAL response
42+
var editUri = contact.Link("edit");
43+
if (editUri != null)
44+
{
45+
var editEndpoint = new Endpoint(contact, editUri);
46+
}
47+
```
48+
49+
=== "TypeScript"
50+
51+
```typescript
52+
const contact = new ElementEndpoint<Contact>(client, "./contacts/1337");
53+
const contactData = await contact.read();
54+
55+
// Retrieve a link from the HAL response
56+
const editUri = contact.link("edit");
57+
if (editUri) {
58+
const editEndpoint = new Endpoint(contact, editUri);
59+
}
60+
```
61+
62+
## Multiple Links
63+
64+
HAL supports multiple links with the same relation type. Use `GetLinks()` to retrieve all of them:
65+
66+
=== "C#"
67+
68+
```csharp
69+
var resource = new ElementEndpoint<Resource>(client, relativeUri: "./resource");
70+
await resource.ReadAsync();
71+
72+
// Get all "alternate" links
73+
var alternates = resource.GetLinks("alternate");
74+
foreach (var alternate in alternates)
75+
{
76+
Console.WriteLine($"Alternate: {alternate}");
77+
}
78+
```
79+
80+
=== "TypeScript"
81+
82+
```typescript
83+
const resource = new ElementEndpoint<Resource>(client, "./resource");
84+
await resource.read();
85+
86+
// Get all "alternate" links
87+
const alternates = resource.getLinks("alternate");
88+
for (const alternate of alternates) {
89+
console.log(`Alternate: ${alternate}`);
90+
}
91+
```
92+
93+
In HAL, multiple links are represented as an array:
94+
95+
```json
96+
{
97+
"_links": {
98+
"alternate": [
99+
{"href": "/resource.xml"},
100+
{"href": "/resource.pdf"}
101+
]
102+
}
103+
}
104+
```
105+
106+
## Templated Links
107+
108+
HAL supports templated links using URI Templates ([RFC 6570](https://tools.ietf.org/html/rfc6570)):
109+
110+
```json
111+
{
112+
"_links": {
113+
"search": {
114+
"href": "/contacts{?name,email}",
115+
"templated": true
116+
}
117+
}
118+
}
119+
```
120+
121+
Use `LinkTemplate()` to resolve templated links:
122+
123+
=== "C#"
124+
125+
```csharp
126+
var contacts = new CollectionEndpoint<Contact>(client, relativeUri: "./contacts");
127+
await contacts.ReadAllAsync();
128+
129+
// Resolve the templated link
130+
var searchUri = contacts.LinkTemplate("search", new {name = "Smith"});
131+
```
132+
133+
=== "TypeScript"
134+
135+
```typescript
136+
const contacts = new CollectionEndpoint<Contact>(client, "./contacts");
137+
await contacts.readAll();
138+
139+
// Resolve the templated link
140+
const searchUri = contacts.linkTemplate("search", {name: "Smith"});
141+
```
142+
143+
## Combining with Link Headers
144+
145+
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.
146+
147+
If you need more control over link extraction priority or want to use only specific sources, you can configure the link extraction behavior:
148+
149+
=== "C#"
150+
151+
```csharp
152+
var endpoint = new Endpoint(new Uri("http://example.com/resource"));
153+
154+
// Use AggregateLinkExtractor to combine multiple extractors
155+
endpoint.LinkExtractor = new AggregateLinkExtractor(
156+
new HeaderLinkExtractor(),
157+
new HalLinkExtractor()
158+
);
159+
```
160+
161+
=== "TypeScript"
162+
163+
```typescript
164+
const endpoint = new Endpoint(new URL("http://example.com/resource"));
165+
166+
// Use AggregateLinkExtractor to combine multiple extractors
167+
endpoint.linkExtractor = new AggregateLinkExtractor(
168+
new HeaderLinkExtractor(),
169+
new HalLinkExtractor()
170+
);
171+
```
172+
173+
The `AggregateLinkExtractor` queries each extractor in order and returns the first match found, allowing you to control precedence by ordering the extractors.

docs/link-handling/index.md

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

3-
TypedRest supports a variety of different links:
3+
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.
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+
## Link Resolution Methods
6+
7+
TypedRest endpoints provide several methods for resolving links:
8+
9+
- **`Link(rel)`**: Retrieves a link with the specified relation type from HTTP Link headers or HAL `_links`
10+
- **`GetLinks(rel)`**: Retrieves all links with the specified relation type (useful when multiple links share the same relation)
11+
- **`LinkTemplate(rel, variables)`**: Resolves a URI template with the specified relation type and variables
12+
13+
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.
14+
15+
## Link Sources
16+
17+
TypedRest supports multiple sources for link information:
18+
19+
### [Relative URIs](relative-uris.md)
20+
21+
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.
22+
23+
### [URI Templates](uri-templates.md)
24+
25+
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.
26+
27+
### [Link Headers](link-header.md)
28+
29+
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.
30+
31+
### [HAL](hal.md)
32+
33+
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.
34+
35+
## Serializers
36+
37+
The [serializers](serializers.md) page documents how TypedRest handles different content formats (JSON, XML, BSON) and how serializer configuration is inherited between endpoints.

docs/link-handling/link-header.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Link Header
2+
3+
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.
4+
5+
For details on the Link header format and semantics, see [RFC 8288](https://tools.ietf.org/html/rfc8288).
6+
7+
## Link Header Format
8+
9+
Link headers follow this basic format:
10+
11+
```http
12+
Link: <https://example.com/page/2>; rel="next",
13+
<https://example.com/page/1>; rel="prev"
14+
```
15+
16+
Each link consists of:
17+
18+
- A URI reference in angle brackets (`<URI>`)
19+
- One or more parameters, most importantly `rel` for the link relation type
20+
- Optional additional parameters like `title`
21+
22+
## Extracting Links
23+
24+
TypedRest automatically extracts links from HTTP responses. Use the `Link()` method to retrieve a specific link by its relation type:
25+
26+
=== "C#"
27+
28+
```csharp
29+
var contacts = new CollectionEndpoint<Contact>(client, relativeUri: "./contacts");
30+
await contacts.ReadAllAsync(); // This performs a GET request
31+
32+
// Retrieve a link from the response headers
33+
var nextPageUri = contacts.Link("next");
34+
if (nextPageUri != null)
35+
{
36+
// Navigate to the next page
37+
var nextPage = new CollectionEndpoint<Contact>(contacts, nextPageUri);
38+
}
39+
```
40+
41+
=== "TypeScript"
42+
43+
```typescript
44+
const contacts = new CollectionEndpoint<Contact>(client, "./contacts");
45+
await contacts.readAll(); // This performs a GET request
46+
47+
// Retrieve a link from the response headers
48+
const nextPageUri = contacts.link("next");
49+
if (nextPageUri) {
50+
// Navigate to the next page
51+
const nextPage = new CollectionEndpoint<Contact>(contacts, nextPageUri);
52+
}
53+
```
54+
55+
## Getting All Links
56+
57+
Use `GetLinks()` to retrieve all links with a specific relation type (useful when multiple links share the same relation):
58+
59+
=== "C#"
60+
61+
```csharp
62+
var endpoint = new Endpoint(new Uri("http://example.com/resource"));
63+
await endpoint.ReadMetaAsync(); // HEAD request to fetch headers
64+
65+
// Get all links with the "alternate" relation
66+
var alternateLinks = endpoint.GetLinks("alternate");
67+
foreach (var link in alternateLinks)
68+
{
69+
Console.WriteLine($"Alternate format: {link}");
70+
}
71+
```
72+
73+
=== "TypeScript"
74+
75+
```typescript
76+
const endpoint = new Endpoint(new URL("http://example.com/resource"));
77+
await endpoint.readMeta(); // HEAD request to fetch headers
78+
79+
// Get all links with the "alternate" relation
80+
const alternateLinks = endpoint.getLinks("alternate");
81+
for (const link of alternateLinks) {
82+
console.log(`Alternate format: ${link}`);
83+
}
84+
```
85+
86+
## Templated Links in Headers
87+
88+
TypedRest implements a non-standard extension: `templated=true` parameter for URI templates in Link headers.
89+
90+
```http
91+
Link: </contacts{?name,email}>; rel="search"; templated=true
92+
```
93+
94+
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.
95+
96+
When `templated=true` is present, use `LinkTemplate()` instead of `Link()`:
97+
98+
=== "C#"
99+
100+
```csharp
101+
var contacts = new CollectionEndpoint<Contact>(client, relativeUri: "./contacts");
102+
await contacts.ReadAllAsync();
103+
104+
// Resolve the templated link
105+
var searchUri = contacts.LinkTemplate("search", new {name = "Smith"});
106+
```
107+
108+
=== "TypeScript"
109+
110+
```typescript
111+
const contacts = new CollectionEndpoint<Contact>(client, "./contacts");
112+
await contacts.readAll();
113+
114+
// Resolve the templated link
115+
const searchUri = contacts.linkTemplate("search", {name: "Smith"});
116+
```
117+
118+
## Supported Link Header Attributes
119+
120+
TypedRest recognizes the following Link header parameters:
121+
122+
- **`rel`**: The link relation type (required)
123+
- **`title`**: A human-readable title for the link (optional, currently extracted but not exposed)
124+
- **`templated`**: Non-standard extension indicating the URI is a template (optional)
125+
126+
Other parameters defined in RFC 8288 may be present in headers but are not currently processed by TypedRest.

0 commit comments

Comments
 (0)