diff --git a/CHANGELOG-CodeGenerators.md b/CHANGELOG-CodeGenerators.md
index 7e9b83e..1785c9b 100644
--- a/CHANGELOG-CodeGenerators.md
+++ b/CHANGELOG-CodeGenerators.md
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.4.1] - 2026-08-23
+
+### Documentation
+
+- Added XML documentation comments to `HalResponseGenerator`, previously undocumented. The
+ companion `HalResponseAttribute` in the unpublished `Chatter.Rest.Hal.Core` project was
+ documented too. Documentation only: no API or behavior change.
+
## [0.4.0] - 2026-08-22
### Added
@@ -74,5 +82,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A `record` annotated with `[HalResponse]` previously generated nothing silently and now reports
`HAL0003`. Declare the type as a `partial class` to receive the HAL members.
-[Unreleased]: https://github.com/brenpike/Chatter.Rest.Hal/compare/codegen/v0.4.0...HEAD
+[Unreleased]: https://github.com/brenpike/Chatter.Rest.Hal/compare/codegen/v0.4.1...HEAD
+[0.4.1]: https://github.com/brenpike/Chatter.Rest.Hal/compare/codegen/v0.4.0...codegen/v0.4.1
[0.4.0]: https://github.com/brenpike/Chatter.Rest.Hal/compare/codegen/v0.3.0...codegen/v0.4.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34b07e9..5c4defd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [2.1.1] - 2026-08-23
+
+### Documentation
+
+- Added XML documentation comments to the 20 public fluent builder stage interfaces under
+ `Builders/Stages/`, which were previously undocumented. Documentation only: no API or
+ behavior change.
+- Enabled `GenerateDocumentationFile` for `Chatter.Rest.Hal`, so the package now ships the XML
+ documentation sidecar (`lib/net8.0/Chatter.Rest.Hal.xml` and
+ `lib/netstandard2.0/Chatter.Rest.Hal.xml`). The doc comments above previously never reached
+ the published package; consumers now get IntelliSense from them.
+- Fixed malformed doc comments surfaced by enabling generation: closed six unclosed
+ `` blocks in each of `IEmbeddedLinkObjectPropertiesSelectionStage` and
+ `IResourceLinkObjectPropertiesSelectionStage` (both also gained an interface summary and
+ documentation for their new-hiding `AsArray()` member), and replaced two unresolvable
+ `JsonNode.Deserialize` crefs with resolvable `JsonSerializer.Deserialize` references.
+
## [2.1.0] - 2026-08-23
### Fixed
@@ -91,6 +108,7 @@ rejected.
([#120](https://github.com/brenpike/Chatter.Rest.Hal/issues/120), open: empty-string href
tolerance).
-[Unreleased]: https://github.com/brenpike/Chatter.Rest.Hal/compare/hal/v2.1.0...HEAD
+[Unreleased]: https://github.com/brenpike/Chatter.Rest.Hal/compare/hal/v2.1.1...HEAD
+[2.1.1]: https://github.com/brenpike/Chatter.Rest.Hal/compare/hal/v2.1.0...hal/v2.1.1
[2.1.0]: https://github.com/brenpike/Chatter.Rest.Hal/compare/hal/v2.0.0...hal/v2.1.0
[2.0.0]: https://github.com/brenpike/Chatter.Rest.Hal/compare/hal/v1.1.0...hal/v2.0.0
diff --git a/CLAUDE.md b/CLAUDE.md
index e110cbd..c18a849 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -19,8 +19,8 @@ Chatter.Rest.Hal is a .NET/C# implementation of the HAL (Hypertext Application L
| `docs/usage.md` | Copy-paste examples for building, serializing, deserializing |
| `docs/development.md` | Build/test/pack commands, code style, test conventions, CI/CD |
| `docs/HAL_TEST_PLAN.md` | HAL spec-to-test mapping; consult for spec compliance and tests |
-| `docs/aspnetcore/requirements.md` | Package requirements for `Chatter.Rest.Hal.AspNetCore` |
-| `docs/aspnetcore/architecture.md` | Architecture, pseudocode, and test strategy for `Chatter.Rest.Hal.AspNetCore` |
+| `docs/aspnetcore/requirements.md` | Package requirements for `Chatter.Rest.Hal.AspNetCore` (planned package — design docs) |
+| `docs/aspnetcore/architecture.md` | Architecture, pseudocode, and test strategy for `Chatter.Rest.Hal.AspNetCore` (planned package — design docs) |
## Solution Structure
@@ -81,8 +81,8 @@ Observed conventions:
| Package | Version |
|---|---|
-| `Chatter.Rest.Hal` | `2.1.0` |
-| `Chatter.Rest.Hal.CodeGenerators` | `0.4.0` |
+| `Chatter.Rest.Hal` | `2.1.1` |
+| `Chatter.Rest.Hal.CodeGenerators` | `0.4.1` |
External dependency: `Chatter.Rest.UriTemplates` v0.1.0.
diff --git a/README.md b/README.md
index 1558e5b..0df9a6d 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,7 @@ Minimal, copy-paste example (build, serialize):
using System;
using System.Text.Json;
using Chatter.Rest.Hal;
+using Chatter.Rest.Hal.Builders;
// Build a simple resource with state and a self link
var resource = ResourceBuilder
@@ -96,6 +97,8 @@ Console.WriteLine(json);
*/
```
+> **Note:** `ResourceBuilder` lives in the `Chatter.Rest.Hal.Builders` namespace. Later snippets in this README omit the `using` directives for brevity; each builder example assumes `using Chatter.Rest.Hal;` and `using Chatter.Rest.Hal.Builders;`.
+
If you want compile-time helpers generated for your response types, also install the code generators package:
```bash
@@ -276,11 +279,13 @@ var resource = ResourceBuilder.WithState(new { currentlyProcessing = 14, shipped
"self": {
"href": "/orders"
},
- "curies": {
- "href": "http://example.com/docs/rels/{rel}",
- "templated": true,
- "name": "ea"
- },
+ "curies": [
+ {
+ "href": "http://example.com/docs/rels/{rel}",
+ "templated": true,
+ "name": "ea"
+ }
+ ],
"next": {
"href": "/orders?page=2"
},
@@ -288,18 +293,24 @@ var resource = ResourceBuilder.WithState(new { currentlyProcessing = 14, shipped
"href": "/orders{?id}",
"templated": true
},
- "ea:admin": {
- "href": "/admins/2",
- "title": "Fred"
- }
+ "ea:admin": [
+ {
+ "href": "/admins/2",
+ "title": "Fred"
+ },
+ {
+ "href": "/admins/5",
+ "title": "Kate"
+ }
+ ]
},
"_embedded": {
"ea:order": [
{
- "total": 10,
- "currency": "USD",
- "status": "shipped",
- "id": "6d5edc98-8b81-435f-ad7a-a66a60d91bd2",
+ "Id": "6d5edc98-8b81-435f-ad7a-a66a60d91bd2",
+ "Total": 10,
+ "Currency": "USD",
+ "Status": "shipped",
"_links": {
"self": {
"href": "/orders/6d5edc98-8b81-435f-ad7a-a66a60d91bd2"
@@ -315,10 +326,10 @@ var resource = ResourceBuilder.WithState(new { currentlyProcessing = 14, shipped
}
},
{
- "total": 20,
- "currency": "CAD",
- "status": "processing",
- "id": "418845a7-ec41-4288-83eb-22a8bb22e472",
+ "Id": "418845a7-ec41-4288-83eb-22a8bb22e472",
+ "Total": 20,
+ "Currency": "CAD",
+ "Status": "processing",
"_links": {
"self": {
"href": "/orders/418845a7-ec41-4288-83eb-22a8bb22e472"
@@ -338,7 +349,7 @@ var resource = ResourceBuilder.WithState(new { currentlyProcessing = 14, shipped
}
```
-> The output above shows two of the six orders. Remaining orders follow the same structure.
+> The output above shows two of the six orders. Remaining orders follow the same structure. `Order` property names serialize with their C# casing (`Id`, `Total`, ...) because the example does not configure a JSON naming policy.
---
@@ -468,7 +479,7 @@ var resources = resource!.GetResourceCollection("ea:order");
### Get a Link by relation
-Retrieve a single link by its relation. Returns `null` if not found, or throws an exception if multiple links with the same relation exist:
+Retrieve a single link by its relation. Returns `null` if not found. The lookup uses `SingleOrDefault`, so it throws `InvalidOperationException` if multiple links share the same relation — though since 2.0.0 duplicate relations cannot arise: `LinkCollection.Add` rejects a duplicate relation key, and deserialization normalizes duplicate relations last-wins before they reach the collection.
```csharp
var link = resource!.GetLinkOrDefault("self");
@@ -492,7 +503,7 @@ var linkObj = resource!.GetLinkObjectOrDefault("curies", "ea");
### Get a Link Object of a Link by relation only
-For relations with a single link object, retrieve it directly by relation:
+Retrieve the first link object for a relation. If the relation holds several link objects, this returns the **first** one (it uses `FirstOrDefault`) rather than throwing:
```csharp
var linkObj = resource!.GetLinkObjectOrDefault("self");
@@ -519,7 +530,6 @@ To force **all** link relations to serialize as JSON arrays regardless of count,
```csharp
using Chatter.Rest.Hal;
-using Chatter.Rest.Hal.Extensions;
// ASP.NET Core
services.AddControllers().AddJsonOptions(o =>
@@ -550,11 +560,10 @@ To force array representation for a **specific relation** only, use `AsArray()`
```csharp
var resource = ResourceBuilder.WithState(new { total = 5 })
- .AddLinks()
- .AddLink("orders").AsArray() // always emit as array
- .AddLinkObject("/orders/1")
- .AddSelf() // count-based (default behavior)
- .AddLinkObject("/api/orders")
+ .AddLink("orders").AsArray() // always emit as array
+ .AddLinkObject("/orders/1")
+ .AddSelf() // count-based (default behavior)
+ .AddLinkObject("/api/orders")
.Build();
```
diff --git a/docs/api.md b/docs/api.md
index e0cdce6..f5b88d2 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -164,13 +164,15 @@ public interface IAddResourceStage
}
```
+> **Return-target semantics.** `AddResource()` / `AddResource(state)` return the newly added embedded resource — chained link/curies/embed calls configure that resource. `AddResources()` returns the collection builder instead: link/curies/embed calls chained on its return value target the resource that **owns** the `_embedded` entry, not each added item. Per-item links, curies, and nested embeds must be configured inside the `builder` callback.
+
---
## 7. `IEmbeddedResourceCreationStage`
Namespace: `Chatter.Rest.Hal.Builders.Stages.Embedded`
-The embedded-context mirror of `IResourceCreationStage`. Returned by `AddResource()` / `AddResources()`. Supports the same link, curies, and embedded operations as the root resource stage, plus `Build()` which terminates the entire chain back to the root `Resource`.
+The embedded-context mirror of `IResourceCreationStage`, returned by the `IAddResourceStage` methods. It exposes the same link, curies, and embedded operations as the root resource stage, plus `Build()` which terminates the entire chain back to the root `Resource` — but what those operations target depends on which method returned the stage. After `AddResource()` / `AddResource(state)` they configure the newly added embedded resource; after `AddResources()` the stage is the collection builder, so link/curies/embed calls target the resource that owns the `_embedded` entry and per-item configuration happens only inside the `AddResources` callback (see the return-target semantics note in Section 6).
```csharp
public interface IEmbeddedResourceCreationStage :
@@ -313,7 +315,9 @@ Extension methods on `Resource` for navigating links and embedded resources.
```csharp
public static class ResourceExtensions
{
- // Find a link by relation. Returns null if not found.
+ // Find a link by relation. Returns null if not found. Implemented with
+ // SingleOrDefault: throws InvalidOperationException when multiple links
+ // share the same relation.
public static Link? GetLinkOrDefault(this Resource resource, string relation);
// Get all link objects for a relation. Returns null if the relation is not found.
@@ -345,13 +349,17 @@ Extension methods on `LinkCollection`.
```csharp
public static class LinkCollectionExtensions
{
- // Find a link by relation. Returns null if not found.
+ // Find a link by relation. Returns null if not found. Implemented with
+ // SingleOrDefault: throws InvalidOperationException when multiple links
+ // share the same relation.
public static Link? GetLinkOrDefault(this LinkCollection links, string relation);
// Get all link objects for a relation. Returns null if the relation is not found.
public static LinkObjectCollection? GetLinkObjects(this LinkCollection links, string relation);
// Get the first link object for a relation. Returns null if not found.
+ // Implemented with FirstOrDefault: a relation may legitimately carry
+ // several link objects, so this returns the first rather than throwing.
public static LinkObject? GetLinkObjectOrDefault(this LinkCollection links, string linkRelation);
// Get a named link object within a relation. Returns null if not found.
@@ -359,8 +367,13 @@ public static class LinkCollectionExtensions
// Expand a CURIE relation (e.g. "acme:widgets") to its full URI using the
// "curies" link relation defined in this collection. Returns the original
- // relation unchanged if no matching CURIE definition is found, the relation
- // contains no colon, or the CURIE template lacks the {rel} token.
+ // relation unchanged when any of the following holds:
+ // - the relation contains no colon, or the colon is the first character;
+ // - the reference after the colon is empty (e.g. "acme:");
+ // - no "curies" link relation exists, or no CURIE definition matches the
+ // prefix;
+ // - the matching CURIE definition is not marked "templated": true;
+ // - the CURIE template lacks the {rel} token.
// Returns an empty string when relation is null or empty.
public static string ExpandCurieRelation(this LinkCollection links, string relation);
}
@@ -376,6 +389,12 @@ Namespace: `Chatter.Rest.Hal`
public static class LinkObjectCollectionExtensions
{
// Get a link object by its name property. Returns null if not found.
+ // Implemented with SingleOrDefault: throws InvalidOperationException when
+ // multiple link objects in the collection share the same name. Note the
+ // asymmetry with the by-relation overload
+ // GetLinkObjectOrDefault(LinkCollection, string linkRelation) in §11, which
+ // uses FirstOrDefault and never throws when a relation carries several
+ // link objects.
public static LinkObject? GetLinkObjectOrDefault(this LinkObjectCollection linkObjects, string name);
}
@@ -435,7 +454,7 @@ public sealed class HalJsonOptions
### `JsonSerializerOptionsExtensions`
-Namespace: `Chatter.Rest.Hal.Extensions`
+Namespace: `Chatter.Rest.Hal`
```csharp
public static class JsonSerializerOptionsExtensions
@@ -498,7 +517,7 @@ var uri = link!.Expand(("q", "dotnet"), ("lang", "en"));
### Standalone `UriTemplate` Class
-The `UriTemplate` class in `Chatter.Rest.UriTemplates` can be used independently of the HAL library for RFC 6570 Levels 1-3 template expansion. See [docs/uri-templates/usage.md](uri-templates/usage.md) for the full API reference and operator examples.
+The `UriTemplate` class in `Chatter.Rest.UriTemplates` can be used independently of the HAL library for RFC 6570 Levels 1-3 template expansion. `Chatter.Rest.UriTemplates` is an external NuGet package dependency; see the [Chatter.Rest.UriTemplates package on NuGet](https://www.nuget.org/packages/Chatter.Rest.UriTemplates) for its API reference and operator examples.
---
diff --git a/docs/architecture.md b/docs/architecture.md
index e64491e..9d93d19 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -28,8 +28,8 @@ public sealed record Resource : IHalPart
- `State()` — returns the resource state as `T`, materialized as a detached projection from the state of record (the constructor-supplied `JsonElement`, or the lazy `_stateCreator` delegate on the deserialization path) on every call. Projections never become the serialization source, so mutating a returned object affects neither serialization nor equality; a state supplied directly as `T` is the one exception and is returned by reference. Returns `null` on failure rather than throwing.
- `As()` — converts the full `Resource` (including `_links`/`_embedded`) to `T`. A parsed resource converts from the retained source node; an in-memory resource is re-serialized on every call, so links, embedded resources and state added after an earlier `As()` call are reflected in the result. Use this to round-trip a HAL response into a typed DTO that declares `Links`/`Embedded` properties.
-- **Lazy init via `Func` delegates** — the internal deserialization constructor (`internal Resource(JsonNode?, Func, Func, Func)`) stores the three factory delegates. `Links` and `Embedded` property getters invoke these delegates on first access and cache the result, making deserialization allocation-lazy.
-- `StateObject` — internal property that drives `ResourceConverter.Write`. Its getter calls `State
IEmbeddedLinkObjectPropertiesSelectionStage Templated();
///
@@ -30,6 +36,7 @@ public interface IEmbeddedLinkObjectPropertiesSelectionStage : IEmbeddedLinkCrea
///
/// Its value is a string used as a hint to indicate the media type
/// expected when dereferencing the target resource.
+ ///
IEmbeddedLinkObjectPropertiesSelectionStage WithType(string type);
///
@@ -51,6 +58,7 @@ public interface IEmbeddedLinkObjectPropertiesSelectionStage : IEmbeddedLinkCrea
/// property.The notification SHOULD include the deprecation property's
/// value so that a client manitainer can easily find information about
/// the deprecation.
+ ///
IEmbeddedLinkObjectPropertiesSelectionStage WithDeprecationUrl(string deprecation);
///
@@ -79,6 +87,7 @@ public interface IEmbeddedLinkObjectPropertiesSelectionStage : IEmbeddedLinkCrea
///
/// Its value is a string which is a URI that hints about the profile(as
/// defined by [I-D.wilde-profile-link]) of the target resource.
+ ///
IEmbeddedLinkObjectPropertiesSelectionStage WithProfileUri(string profile);
///
@@ -93,6 +102,7 @@ public interface IEmbeddedLinkObjectPropertiesSelectionStage : IEmbeddedLinkCrea
///
/// Its value is a string and is intended for labelling the link with a
/// human-readable identifier(as defined by [RFC5988]).
+ ///
IEmbeddedLinkObjectPropertiesSelectionStage WithTitle(string title);
///
@@ -107,6 +117,19 @@ public interface IEmbeddedLinkObjectPropertiesSelectionStage : IEmbeddedLinkCrea
///
/// Its value is a string and is intended for indicating the language of
/// the target resource(as defined by [RFC5988]).
+ ///
IEmbeddedLinkObjectPropertiesSelectionStage WithHreflang(string hreflang);
+ ///
+ /// Forces the link relation being built to serialize as a JSON array even when it contains a
+ /// single . No-op for the "curies" relation, which is array-form by
+ /// default since 2.0.0 per HAL section 8.3
+ ///
+ /// A to continue building the
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.1
+ ///
+ /// 4.1.1. The reserved "_links" property values are either a Link Object or an
+ /// array of Link Objects.
+ ///
new IEmbeddedLinkObjectPropertiesSelectionStage AsArray();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Embedded/IEmbeddedResourceCreationStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Embedded/IEmbeddedResourceCreationStage.cs
index 2f724b3..565ea76 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Embedded/IEmbeddedResourceCreationStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Embedded/IEmbeddedResourceCreationStage.cs
@@ -1,5 +1,16 @@
namespace Chatter.Rest.Hal.Builders.Stages.Embedded;
+///
+/// The embedded-context mirror of , returned by the
+/// methods. What subsequent link/embed calls target depends
+/// on which method returned the stage: after
+/// (or its state overload) they configure the newly added embedded resource; after
+/// the stage is the collection builder, so
+/// "self"/link/curies additions target the resource that OWNS the "_embedded" entry and nested
+/// embed calls delegate to that owning resource — per-item configuration happens only inside the
+/// AddResources callback. The caller can also add sibling resources or build the root
+/// resource from this stage.
+///
public interface IEmbeddedResourceCreationStage : IBuildHalPart, IAddLinkToEmbeddedStage, IAddSelfLinkToEmbeddedStage, IAddCuriesLinkToEmbeddedStage, IAddResourceStage, IAddEmbeddedResourceToResourceStage, IBuildResource
{
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/IAddCuriesLinkStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/IAddCuriesLinkStage.cs
index f74d479..e5ab1a0 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/IAddCuriesLinkStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/IAddCuriesLinkStage.cs
@@ -1,6 +1,26 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which the reserved "curies" link relation can be added to the
+/// link collection being built. Context-neutral counterpart of
+/// and
+/// , implemented by builders that serve both
+/// the resource and embedded contexts.
+///
public interface IAddCuriesLinkStage
{
+ ///
+ /// Adds a "curies" link to the link collection being built. Repeated calls merge into the
+ /// single "curies" link, so each additional CURIE definition extends that link's array rather
+ /// than emitting a second "curies" entry.
+ ///
+ /// A to add CURIE s to the "curies" link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-8.2
+ ///
+ /// 8.2. Custom link relation types can be represented via "CURIE syntax" links: a set of Link
+ /// Objects under the reserved "curies" link relation, each with a "name" (the CURIE prefix)
+ /// and a templated "href" containing the {rel} token.
+ ///
ICuriesLinkCreationStage AddCuries();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/IAddEmbeddedResourceToResourceStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/IAddEmbeddedResourceToResourceStage.cs
index 58c9d4d..d0c29de 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/IAddEmbeddedResourceToResourceStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/IAddEmbeddedResourceToResourceStage.cs
@@ -2,7 +2,26 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which a named embedded resource entry can be added to the
+/// "_embedded" property of the resource being built. Available from both the root-resource and
+/// embedded builder contexts.
+///
public interface IAddEmbeddedResourceToResourceStage
{
+ ///
+ /// Adds an embedded resource entry with the supplied name to the resource being built
+ ///
+ /// The name that keys the entry within the resource's "_embedded" property
+ /// An to add one or more resources to the named entry
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.2
+ ///
+ /// 4.1.2. The reserved "_embedded" property is OPTIONAL.
+ ///
+ /// It is an object whose property names are link relation types (as
+ /// defined by [RFC5988]) and values are either a Resource Object or an
+ /// array of Resource Objects.
+ ///
IAddResourceStage AddEmbedded(string name);
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/IAddLinkStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/IAddLinkStage.cs
index 5a8cdc9..fdf7494 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/IAddLinkStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/IAddLinkStage.cs
@@ -1,6 +1,28 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which a link with an arbitrary relation can be added to the
+/// link collection being built. Context-neutral counterpart of
+/// and ,
+/// implemented by builders that serve both the resource and embedded contexts.
+///
public interface IAddLinkStage
{
+ ///
+ /// Adds a link with the supplied relation to the link collection being built. Repeating a
+ /// relation merges its s into the single link for that relation,
+ /// because HAL's "_links" is a JSON object keyed by relation.
+ ///
+ /// The link relation type of the link. Must not be null or whitespace
+ /// A to add s to the link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.1
+ ///
+ /// 4.1.1. The reserved "_links" property is OPTIONAL.
+ ///
+ /// It is an object whose property names are link relation types (as
+ /// defined by [RFC5988]) and values are either a Link Object or an array
+ /// of Link Objects.
+ ///
ILinkCreationStage AddLink(string rel);
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/IAddResourceStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/IAddResourceStage.cs
index 663bf83..cf02d65 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/IAddResourceStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/IAddResourceStage.cs
@@ -4,9 +4,38 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder, returned by
+/// , from which individual
+/// resources or a batch of resources can be added to the named "_embedded" entry.
+///
public interface IAddResourceStage
{
+ ///
+ /// Adds a resource with no state to the "_embedded" entry being built
+ ///
+ /// An to build the new embedded resource
IEmbeddedResourceCreationStage AddResource();
+ ///
+ /// Adds a resource with the supplied state to the "_embedded" entry being built
+ ///
+ /// The OPTIONAL state object whose properties become the embedded resource's state
+ /// An to build the new embedded resource
IEmbeddedResourceCreationStage AddResource(object? state);
+ ///
+ /// Adds one resource per item of to the "_embedded" entry being
+ /// built, each item becoming that resource's state. Also forces the entry to serialize as a
+ /// JSON array even when it contains a single resource.
+ ///
+ /// The type of the items in
+ /// The items to add as embedded resources
+ /// An OPTIONAL callback invoked with each item and its
+ /// to configure per-resource links, curies, and
+ /// embedded resources
+ /// An to continue building. Unlike
+ /// , the returned stage is the collection builder, NOT one of the
+ /// added resources: link/curies/embed calls chained on it target the resource that owns the
+ /// "_embedded" entry. Per-resource configuration happens only inside the
+ /// callback
IEmbeddedResourceCreationStage AddResources(IEnumerable resources, Action? builder = null);
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/IAddSelfLinkStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/IAddSelfLinkStage.cs
index d936eb0..2050583 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/IAddSelfLinkStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/IAddSelfLinkStage.cs
@@ -1,6 +1,25 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which the reserved "self" link relation can be added to the
+/// link collection being built. Context-neutral counterpart of
+/// and
+/// , implemented by builders that serve both the
+/// resource and embedded contexts.
+///
public interface IAddSelfLinkStage
{
+ ///
+ /// Adds a "self" link to the link collection being built. Repeated calls merge into the
+ /// single "self" link.
+ ///
+ /// A to add s to the "self" link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.1
+ ///
+ /// 4.1.1. Each Resource Object SHOULD contain a 'self' link that corresponds
+ /// with the IANA registered 'self' relation (as defined by [RFC5988])
+ /// whose target is the resource's URI.
+ ///
ILinkCreationStage AddSelf();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/ICuriesLinkCreationStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/ICuriesLinkCreationStage.cs
index dfcdb5d..0ea922d 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/ICuriesLinkCreationStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/ICuriesLinkCreationStage.cs
@@ -3,6 +3,12 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which CURIE s can be added to the
+/// "curies" link being built. Context-neutral union of
+/// and ,
+/// implemented by builders that serve both the resource and embedded contexts.
+///
public interface ICuriesLinkCreationStage : IResourceCuriesLinkCreationStage, IEmbeddedCuriesLinkCreationStage
{
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/ILinkCreationStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/ILinkCreationStage.cs
index e5ab20d..d650083 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/ILinkCreationStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/ILinkCreationStage.cs
@@ -3,6 +3,12 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which s can be added to the link
+/// being built, or the link's relation forced to serialize as a JSON array. Context-neutral union
+/// of and ,
+/// implemented by builders that serve both the resource and embedded contexts.
+///
public interface ILinkCreationStage : IResourceLinkCreationStage, IEmbeddedLinkCreationStage
{
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/ILinkObjectPropertiesSelectionStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/ILinkObjectPropertiesSelectionStage.cs
index fea8fae..3f32f51 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/ILinkObjectPropertiesSelectionStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/ILinkObjectPropertiesSelectionStage.cs
@@ -3,6 +3,14 @@
namespace Chatter.Rest.Hal.Builders.Stages;
+///
+/// A stage of the fluent builder from which the OPTIONAL HAL properties of the
+/// being built can be set, or the chain re-entered to add further links,
+/// curies, or embedded resources. Context-neutral union of
+/// and
+/// , implemented by builders that serve
+/// both the resource and embedded contexts.
+///
public interface ILinkObjectPropertiesSelectionStage : IResourceLinkObjectPropertiesSelectionStage, IEmbeddedLinkObjectPropertiesSelectionStage, IBuildResource
{
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddCuriesLinkToResourceStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddCuriesLinkToResourceStage.cs
index 0370491..f56e7fc 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddCuriesLinkToResourceStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddCuriesLinkToResourceStage.cs
@@ -1,6 +1,23 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// A stage of the fluent builder from which the reserved "curies" link relation can be added to
+/// the resource being built
+///
public interface IAddCuriesLinkToResourceStage
{
+ ///
+ /// Adds a "curies" link to the resource being built. Repeated calls merge into the single
+ /// "curies" link, so each additional CURIE definition extends that link's array rather than
+ /// emitting a second "curies" entry.
+ ///
+ /// A to add CURIE s to the "curies" link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-8.2
+ ///
+ /// 8.2. Custom link relation types can be represented via "CURIE syntax" links: a set of Link
+ /// Objects under the reserved "curies" link relation, each with a "name" (the CURIE prefix)
+ /// and a templated "href" containing the {rel} token.
+ ///
IResourceCuriesLinkCreationStage AddCuries();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddLinkToResourceStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddLinkToResourceStage.cs
index 73642ef..eea9f81 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddLinkToResourceStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddLinkToResourceStage.cs
@@ -1,6 +1,26 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// A stage of the fluent builder from which a link with an arbitrary relation can be added to the
+/// resource being built
+///
public interface IAddLinkToResourceStage
{
+ ///
+ /// Adds a link with the supplied relation to the resource being built. Repeating a relation
+ /// merges its s into the single link for that relation, because HAL's
+ /// "_links" is a JSON object keyed by relation.
+ ///
+ /// The link relation type of the link. Must not be null or whitespace
+ /// A to add s to the link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.1
+ ///
+ /// 4.1.1. The reserved "_links" property is OPTIONAL.
+ ///
+ /// It is an object whose property names are link relation types (as
+ /// defined by [RFC5988]) and values are either a Link Object or an array
+ /// of Link Objects.
+ ///
IResourceLinkCreationStage AddLink(string rel);
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddSelfLinkToResourceStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddSelfLinkToResourceStage.cs
index f559b36..34afeed 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddSelfLinkToResourceStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IAddSelfLinkToResourceStage.cs
@@ -1,6 +1,22 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// A stage of the fluent builder from which the reserved "self" link relation can be added to the
+/// resource being built
+///
public interface IAddSelfLinkToResourceStage
{
+ ///
+ /// Adds a "self" link to the resource being built. Repeated calls merge into the single
+ /// "self" link.
+ ///
+ /// A to add s to the "self" link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.1
+ ///
+ /// 4.1.1. Each Resource Object SHOULD contain a 'self' link that corresponds
+ /// with the IANA registered 'self' relation (as defined by [RFC5988])
+ /// whose target is the resource's URI.
+ ///
IResourceLinkCreationStage AddSelf();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCreationStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCreationStage.cs
index eca7cd4..bfd8973 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCreationStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCreationStage.cs
@@ -1,5 +1,10 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// The root stage of the fluent builder, returned by the
+/// entry points. From this stage the caller can add a "self" link, links with arbitrary
+/// relations, a "curies" link, or named embedded resource entries, or build the resource.
+///
public interface IResourceCreationStage : IAddCuriesLinkToResourceStage, IAddLinkToResourceStage, IAddSelfLinkToResourceStage, IAddEmbeddedResourceToResourceStage, IBuildResource
{
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCuriesLinkCreationStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCuriesLinkCreationStage.cs
index 8185404..3262e14 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCuriesLinkCreationStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceCuriesLinkCreationStage.cs
@@ -1,7 +1,29 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// A stage of the fluent builder from which CURIE s can be added to the
+/// "curies" link of the resource being built
+///
public interface IResourceCuriesLinkCreationStage
{
+ ///
+ /// Adds a CURIE with the supplied href and name to the "curies" link
+ ///
+ /// The REQUIRED href of the CURIE . A URI Template containing the {rel} token
+ /// The REQUIRED name of the CURIE , used as the prefix of compact link relation types
+ /// A to continue building the
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-8.2
+ ///
+ /// 8.2. Custom link relation types can be represented via "CURIE syntax" links: a set of Link
+ /// Objects under the reserved "curies" link relation, each with a "name" (the CURIE prefix)
+ /// and a templated "href" containing the {rel} token.
+ ///
IResourceLinkObjectPropertiesSelectionStage AddLinkObject(string href, string name);
+ ///
+ /// Forces the "curies" relation to serialize as a JSON array. No-op for curies since 2.0.0:
+ /// the curies relation is array-form by default per HAL section 8.3.
+ ///
+ /// A to continue building the "curies" link
IResourceCuriesLinkCreationStage AsArray();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkCreationStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkCreationStage.cs
index 007da5d..1bd08e6 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkCreationStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkCreationStage.cs
@@ -1,7 +1,37 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// A stage of the fluent builder from which s can be added to the link
+/// being built, or the link's relation forced to serialize as a JSON array
+///
public interface IResourceLinkCreationStage
{
+ ///
+ /// Adds a with the supplied href to the link being built
+ ///
+ /// The REQUIRED href of the . Either a URI or a URI Template
+ /// A to continue building the
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-5.1
+ ///
+ /// 5.1. The "href" property is REQUIRED.
+ ///
+ /// Its value is either a URI [RFC3986] or a URI Template [RFC6570].
+ ///
+ /// If the value is a URI Template then the Link Object SHOULD have a
+ /// "templated" attribute whose value is true.
+ ///
IResourceLinkObjectPropertiesSelectionStage AddLinkObject(string href);
+ ///
+ /// Forces the link relation being built to serialize as a JSON array even when it contains a
+ /// single
+ ///
+ /// A to continue building the link
+ ///
+ /// https://datatracker.ietf.org/doc/html/draft-kelly-json-hal#section-4.1.1
+ ///
+ /// 4.1.1. The reserved "_links" property values are either a Link Object or an
+ /// array of Link Objects.
+ ///
IResourceLinkCreationStage AsArray();
}
diff --git a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkObjectPropertiesSelectionStage.cs b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkObjectPropertiesSelectionStage.cs
index ce546c7..5bc006c 100644
--- a/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkObjectPropertiesSelectionStage.cs
+++ b/src/Chatter.Rest.Hal/Builders/Stages/Resource/IResourceLinkObjectPropertiesSelectionStage.cs
@@ -1,5 +1,9 @@
namespace Chatter.Rest.Hal.Builders.Stages.Resource;
+///
+/// A stage of the fluent builder from which the OPTIONAL properties of the
+/// being built can be set, further links or embedded resources added, or the built
+///
public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCreationStage, IResourceCuriesLinkCreationStage, IAddLinkToResourceStage, IAddSelfLinkToResourceStage, IAddCuriesLinkToResourceStage, IAddEmbeddedResourceToResourceStage, IBuildResource
{
///
@@ -16,6 +20,7 @@ public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCrea
///
/// Its value SHOULD be considered false if it is undefined or any other
/// value than true.
+ ///
IResourceLinkObjectPropertiesSelectionStage Templated();
///
@@ -30,6 +35,7 @@ public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCrea
///
/// Its value is a string used as a hint to indicate the media type
/// expected when dereferencing the target resource.
+ ///
IResourceLinkObjectPropertiesSelectionStage WithType(string type);
///
@@ -51,6 +57,7 @@ public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCrea
/// property.The notification SHOULD include the deprecation property's
/// value so that a client manitainer can easily find information about
/// the deprecation.
+ ///
IResourceLinkObjectPropertiesSelectionStage WithDeprecationUrl(string deprecation);
///
@@ -79,6 +86,7 @@ public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCrea
///
/// Its value is a string which is a URI that hints about the profile(as
/// defined by [I-D.wilde-profile-link]) of the target resource.
+ ///
IResourceLinkObjectPropertiesSelectionStage WithProfileUri(string profile);
///
@@ -93,6 +101,7 @@ public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCrea
///
/// Its value is a string and is intended for labelling the link with a
/// human-readable identifier(as defined by [RFC5988]).
+ ///
IResourceLinkObjectPropertiesSelectionStage WithTitle(string title);
///
@@ -107,6 +116,13 @@ public interface IResourceLinkObjectPropertiesSelectionStage : IResourceLinkCrea
///
/// Its value is a string and is intended for indicating the language of
/// the target resource(as defined by [RFC5988]).
+ ///
IResourceLinkObjectPropertiesSelectionStage WithHreflang(string hreflang);
+ ///
+ /// Forces the link relation being built to serialize as a JSON array even when it contains a
+ /// single . No-op when the relation is "curies", which is array-form
+ /// by default since 2.0.0 per HAL section 8.3
+ ///
+ /// A to continue building the
new IResourceLinkObjectPropertiesSelectionStage AsArray();
}
diff --git a/src/Chatter.Rest.Hal/Chatter.Rest.Hal.csproj b/src/Chatter.Rest.Hal/Chatter.Rest.Hal.csproj
index be507e1..fd856e2 100644
--- a/src/Chatter.Rest.Hal/Chatter.Rest.Hal.csproj
+++ b/src/Chatter.Rest.Hal/Chatter.Rest.Hal.csproj
@@ -4,7 +4,7 @@
net8.0;netstandard2.010.0enable
- 2.1.0
+ 2.1.1Brennan PikeBrennan PikeA dotnet/c# implementation of the Hypertext Application Language (HAL+json) specification for RESTful Web Apis
@@ -14,6 +14,7 @@
.net;Chatter;REST;HAL;hal+json;HATEOASMITtrue
+ truetrue
diff --git a/src/Chatter.Rest.Hal/Converters/ConverterHelpers.cs b/src/Chatter.Rest.Hal/Converters/ConverterHelpers.cs
index 52cef61..0c4fc5c 100644
--- a/src/Chatter.Rest.Hal/Converters/ConverterHelpers.cs
+++ b/src/Chatter.Rest.Hal/Converters/ConverterHelpers.cs
@@ -26,8 +26,9 @@ internal static class ConverterHelpers
/// True when the caller registered a custom converter for in
/// . Options-registered converters take precedence
/// over attribute-wired ones, so the node-walking fast path is only valid when the selected
- /// converter is the built-in one; otherwise the nested part must dispatch through
- /// so the custom converter runs.
+ /// converter is the built-in one; otherwise the nested part must dispatch through the
+ /// extension
+ /// so the custom converter runs.
///
internal static bool HasCustomConverter(JsonSerializerOptions options, Type builtInConverterType)
=> options.GetConverter(typeof(T))?.GetType() != builtInConverterType;
diff --git a/src/Chatter.Rest.Hal/Converters/ResourceConverter.cs b/src/Chatter.Rest.Hal/Converters/ResourceConverter.cs
index 34948c6..f54ab1b 100644
--- a/src/Chatter.Rest.Hal/Converters/ResourceConverter.cs
+++ b/src/Chatter.Rest.Hal/Converters/ResourceConverter.cs
@@ -39,7 +39,7 @@ public sealed class ResourceConverter : JsonConverter
///
/// Materializes a Resource directly from an already-parsed node. Nested converters call this
- /// instead of , which would re-serialize the subtree to UTF-8
+ /// instead of , which would re-serialize the subtree to UTF-8
/// and re-parse it — repeating that once per ancestor turns a deeply nested _embedded
/// chain into O(depth × size) work; walking the existing tree keeps the whole document at O(size).
///
diff --git a/src/Chatter.Rest.Hal/Resource.cs b/src/Chatter.Rest.Hal/Resource.cs
index 0e5fce9..230bc41 100644
--- a/src/Chatter.Rest.Hal/Resource.cs
+++ b/src/Chatter.Rest.Hal/Resource.cs
@@ -72,10 +72,10 @@ internal object? StateObject
}
///
- /// Gets the cached state object, bypassing the Link-guard logic in .
+ /// Gets the cached state object, bypassing the Link-guard logic in .
///
///
- /// contains defensive checks that prevent a -typed
+ /// contains defensive checks that prevent a -typed
/// state from being misinterpreted as a object during deserialization.
/// This property skips those checks and is therefore only safe for the serialization (write) path,
/// where the state is being written out rather than interpreted as a domain type.