Skip to content

Commit c877fd8

Browse files
Make packable
1 parent e54bbf7 commit c877fd8

2 files changed

Lines changed: 136 additions & 0 deletions

File tree

.github/workflows/nuget.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
dotnet pack src/CarpaNet/CarpaNet.csproj --configuration Release --no-build --output nupkg
3232
dotnet pack src/CarpaNet.OAuth/CarpaNet.OAuth.csproj --configuration Release --no-build --output nupkg
3333
dotnet pack src/CarpaNet.Jetstream/CarpaNet.Jetstream.csproj --configuration Release --no-build --output nupkg
34+
dotnet pack src/CarpaNet.AspNetCore/CarpaNet.AspNetCore.csproj --configuration Release --no-build --output nupkg
3435
3536
- name: Upload Nuget
3637
uses: actions/upload-artifact@v4

src/CarpaNet.AspNetCore/README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# CarpaNet.AspNetCore
2+
3+
[![NuGet Version](https://img.shields.io/nuget/v/CarpaNet.AspNetCore.svg)](https://www.nuget.org/packages/CarpaNet.AspNetCore/) ![License](https://img.shields.io/badge/License-MIT-blue.svg)
4+
5+
ASP.NET Core XRPC server endpoint support for [CarpaNet](https://github.com/drasticactions/carpanet). Generate abstract ATProtocol XRPC controllers from lexicon definitions and implement your own ATProtocol server endpoints.
6+
7+
## Installation
8+
9+
```
10+
dotnet add package CarpaNet.AspNetCore
11+
```
12+
13+
You also need the core `CarpaNet` package (with source generator) to generate the controllers and data model types.
14+
15+
## Quick Start
16+
17+
### 1. Enable XRPC endpoint generation
18+
19+
Set `CarpaNet_EmitXrpcEndpoints` to `true` in your ASP.NET Core project:
20+
21+
```xml
22+
<PropertyGroup>
23+
<CarpaNet_EmitXrpcEndpoints>true</CarpaNet_EmitXrpcEndpoints>
24+
<CarpaNet_LexiconAutoResolve>true</CarpaNet_LexiconAutoResolve>
25+
</PropertyGroup>
26+
27+
<ItemGroup>
28+
<LexiconResolve Include="com.atproto.server.describeServer" />
29+
<LexiconResolve Include="com.atproto.identity.resolveHandle" />
30+
</ItemGroup>
31+
```
32+
33+
This tells the CarpaNet source generator to produce abstract controller classes alongside the usual data model types.
34+
35+
### 2. Implement the generated controllers
36+
37+
The generator creates abstract controllers grouped by NSID namespace. Subclass them and implement the abstract methods:
38+
39+
```csharp
40+
using CarpaNet.AspNetCore;
41+
using Microsoft.AspNetCore.Http.HttpResults;
42+
43+
public class MyServerController : Xrpc.ComAtproto.Server.ServerController
44+
{
45+
public override Task<Results<Ok<ComAtproto.Server.DescribeServerOutput>, ATErrorResult>> DescribeServerAsync(
46+
CancellationToken cancellationToken = default)
47+
{
48+
var output = new ComAtproto.Server.DescribeServerOutput
49+
{
50+
AvailableUserDomains = new List<string> { ".example.com" },
51+
Did = new CarpaNet.ATDid("did:web:example.com"),
52+
};
53+
54+
return Task.FromResult<Results<Ok<ComAtproto.Server.DescribeServerOutput>, ATErrorResult>>(
55+
TypedResults.Ok(output));
56+
}
57+
}
58+
```
59+
60+
### 3. Wire up ASP.NET
61+
62+
```csharp
63+
var builder = WebApplication.CreateBuilder(args);
64+
builder.Services.AddControllers();
65+
66+
var app = builder.Build();
67+
app.MapControllers();
68+
app.Run();
69+
```
70+
71+
Your endpoints are now available at `/xrpc/{nsid}` (e.g., `/xrpc/com.atproto.server.describeServer`).
72+
73+
## How It Works
74+
75+
The CarpaNet source generator reads ATProtocol lexicon JSON files and produces:
76+
77+
- **Data model classes** — Records, objects, input/output types (generated by the existing pipeline)
78+
- **Abstract controllers** — One per NSID namespace group (e.g., `ServerController` for `com.atproto.server.*`)
79+
80+
Lexicon queries map to `[HttpGet]` with `[FromQuery]` parameters. Lexicon procedures map to `[HttpPost]` with `[FromBody]` input.
81+
82+
### Generated Controller Pattern
83+
84+
```
85+
Lexicon: com.atproto.identity.resolveHandle (query)
86+
87+
Xrpc.ComAtproto.Identity.IdentityController (abstract)
88+
→ ResolveHandleAsync([FromQuery] string handle, CancellationToken ct)
89+
→ Returns Task<Results<Ok<ResolveHandleOutput>, ATErrorResult>>
90+
```
91+
92+
```
93+
Lexicon: com.atproto.repo.createRecord (procedure)
94+
95+
Xrpc.ComAtproto.Repo.RepoController (abstract)
96+
→ CreateRecordAsync([FromBody] CreateRecordInput input, CancellationToken ct)
97+
→ Returns Task<Results<Ok<CreateRecordOutput>, ATErrorResult>>
98+
```
99+
100+
## Error Handling
101+
102+
Use `ATErrorResult` factory methods to return ATProtocol-compliant error responses:
103+
104+
```csharp
105+
// Returns { "error": "NotFound", "message": "Record not found" } with HTTP 404
106+
return ATErrorResult.NotFound("Record not found");
107+
108+
// Returns { "error": "AuthMissing", "message": "Authentication Required" } with HTTP 401
109+
return ATErrorResult.Unauthorized();
110+
111+
// Returns { "error": "BadRequest", "message": "Invalid handle format" } with HTTP 400
112+
return ATErrorResult.BadRequest("Invalid handle format");
113+
```
114+
115+
Available factory methods:
116+
117+
| Method | Status Code | Error Type |
118+
|---|---|---|
119+
| `BadRequest()` | 400 | BadRequest |
120+
| `Unauthorized()` | 401 | AuthMissing |
121+
| `Forbidden()` | 403 | Forbidden |
122+
| `NotFound()` | 404 | NotFound |
123+
| `PayloadTooLarge()` | 413 | PayloadTooLarge |
124+
| `TooManyRequests()` | 429 | TooManyRequests |
125+
| `InternalServerError()` | 500 | InternalServerError |
126+
| `NotImplemented()` | 501 | NotImplemented |
127+
| `BadGateway()` | 502 | BadGateway |
128+
| `ServiceUnavailable()` | 503 | ServiceUnavailable |
129+
| `GatewayTimeout()` | 504 | GatewayTimeout |
130+
131+
## MSBuild Properties
132+
133+
| Property | Description | Default |
134+
|---|---|---|
135+
| `CarpaNet_EmitXrpcEndpoints` | Enable XRPC controller generation | `false` |

0 commit comments

Comments
 (0)