Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
dotnet pack src/CarpaNet/CarpaNet.csproj --configuration Release --no-build --output nupkg
dotnet pack src/CarpaNet.OAuth/CarpaNet.OAuth.csproj --configuration Release --no-build --output nupkg
dotnet pack src/CarpaNet.Jetstream/CarpaNet.Jetstream.csproj --configuration Release --no-build --output nupkg
dotnet pack src/CarpaNet.AspNetCore/CarpaNet.AspNetCore.csproj --configuration Release --no-build --output nupkg

- name: Upload Nuget
uses: actions/upload-artifact@v4
Expand Down
2 changes: 2 additions & 0 deletions CarpaNet.Samples.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
<Project Path="src/CarpaNet.OAuth/CarpaNet.OAuth.csproj" />
<Project Path="src/CarpaNet.SourceGen/CarpaNet.SourceGen.csproj" />
<Project Path="src/CarpaNet/CarpaNet.csproj" />
<Project Path="src/CarpaNet.AspNetCore/CarpaNet.AspNetCore.csproj" />
</Folder>
<Folder Name="/samples/">
<Project Path="samples/AuthTest/AuthTest.csproj" />
<Project Path="samples/FirehoseTest/FirehoseTest.csproj" />
<Project Path="samples/JetstreamTest/JetstreamTest.csproj" />
<Project Path="samples/RemoteResolution/RemoteResolution.csproj" />
<Project Path="samples/XrpcServer/XrpcServer.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/CarpaNet.UnitTests/CarpaNet.UnitTests.csproj" />
Expand Down
1 change: 1 addition & 0 deletions CarpaNet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<Project Path="src/CarpaNet.OAuth/CarpaNet.OAuth.csproj" />
<Project Path="src/CarpaNet.SourceGen/CarpaNet.SourceGen.csproj" />
<Project Path="src/CarpaNet/CarpaNet.csproj" />
<Project Path="src/CarpaNet.AspNetCore/CarpaNet.AspNetCore.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/CarpaNet.UnitTests/CarpaNet.UnitTests.csproj" />
Expand Down
30 changes: 30 additions & 0 deletions samples/XrpcServer/Controllers/MyIdentityController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using CarpaNet.AspNetCore;
using Microsoft.AspNetCore.Http.HttpResults;

namespace XrpcServer.Controllers;

/// <summary>
/// Sample implementation of the generated abstract IdentityController.
/// </summary>
public class MyIdentityController : Xrpc.ComAtproto.Identity.IdentityController
{
/// <inheritdoc/>
public override Task<Results<Ok<ComAtproto.Identity.ResolveHandleOutput>, ATErrorResult>> ResolveHandleAsync(
string handle,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(handle))
{
return Task.FromResult<Results<Ok<ComAtproto.Identity.ResolveHandleOutput>, ATErrorResult>>(
ATErrorResult.BadRequest("Handle is required"));
}

var output = new ComAtproto.Identity.ResolveHandleOutput
{
Did = new CarpaNet.ATDid("did:plc:example123"),
};

return Task.FromResult<Results<Ok<ComAtproto.Identity.ResolveHandleOutput>, ATErrorResult>>(
TypedResults.Ok(output));
}
}
25 changes: 25 additions & 0 deletions samples/XrpcServer/Controllers/MyServerController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using CarpaNet.AspNetCore;
using Microsoft.AspNetCore.Http.HttpResults;

namespace XrpcServer.Controllers;

/// <summary>
/// Sample implementation of the generated abstract ServerController.
/// Demonstrates how to implement XRPC server endpoints using CarpaNet-generated controllers.
/// </summary>
public class MyServerController : Xrpc.ComAtproto.Server.ServerController
{
/// <inheritdoc/>
public override Task<Results<Ok<ComAtproto.Server.DescribeServerOutput>, ATErrorResult>> DescribeServerAsync(
CancellationToken cancellationToken = default)
{
var output = new ComAtproto.Server.DescribeServerOutput
{
AvailableUserDomains = new List<string> { ".example.com" },
Did = new CarpaNet.ATDid("did:web:example.com"),
};

return Task.FromResult<Results<Ok<ComAtproto.Server.DescribeServerOutput>, ATErrorResult>>(
TypedResults.Ok(output));
}
}
6 changes: 6 additions & 0 deletions samples/XrpcServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.Run();
3 changes: 3 additions & 0 deletions samples/XrpcServer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# XrpcServer

A sample application for generating [XRPC](https://docs.bsky.app/docs/api/at-protocol-xrpc-api) endpoints via the CarpaNet source generator.
28 changes: 28 additions & 0 deletions samples/XrpcServer/XrpcServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CarpaNet_EmitValidationAttributes>true</CarpaNet_EmitValidationAttributes>
<CarpaNet_EmitXrpcEndpoints>true</CarpaNet_EmitXrpcEndpoints>
<CarpaNet_JsonContextName>ATProtoJsonContext</CarpaNet_JsonContextName>
<CarpaNet_LexiconAutoResolve>true</CarpaNet_LexiconAutoResolve>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../../src/CarpaNet/CarpaNet.csproj" />
<ProjectReference Include="../../src/CarpaNet.AspNetCore/CarpaNet.AspNetCore.csproj" />
<ProjectReference Include="../../src/CarpaNet.SourceGen/CarpaNet.SourceGen.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<Import Project="..\..\src\CarpaNet\build\CarpaNet.targets" />

<ItemGroup>
<LexiconResolve Include="com.atproto.server.describeServer" />
<LexiconResolve Include="com.atproto.identity.resolveHandle" />
</ItemGroup>

</Project>
112 changes: 112 additions & 0 deletions src/CarpaNet.AspNetCore/ATErrorResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// <auto-generated />
#nullable enable

using Microsoft.AspNetCore.Http;

namespace CarpaNet.AspNetCore;

/// <summary>
/// ATProtocol XRPC error result implementing <see cref="IResult"/>.
/// </summary>
public sealed class ATErrorResult : IResult
{
/// <summary>
/// Gets the HTTP status code.
/// </summary>
public int StatusCode { get; private init; }

/// <summary>
/// Gets the error type identifier.
/// </summary>
public string? Error { get; private init; }

/// <summary>
/// Gets the human-readable error message.
/// </summary>
public string? Message { get; private init; }

/// <summary>
/// Creates a Bad Request (400) error.
/// </summary>
public static ATErrorResult BadRequest(string? message = null)
=> new() { StatusCode = 400, Error = "BadRequest", Message = message ?? "Bad Request" };

/// <summary>
/// Creates an Unauthorized (401) error.
/// </summary>
public static ATErrorResult Unauthorized(string? message = null)
=> new() { StatusCode = 401, Error = "AuthMissing", Message = message ?? "Authentication Required" };

/// <summary>
/// Creates a Forbidden (403) error.
/// </summary>
public static ATErrorResult Forbidden(string? message = null)
=> new() { StatusCode = 403, Error = "Forbidden", Message = message ?? "Forbidden" };

/// <summary>
/// Creates a Not Found (404) error.
/// </summary>
public static ATErrorResult NotFound(string? message = null)
=> new() { StatusCode = 404, Error = "NotFound", Message = message ?? "Not Found" };

/// <summary>
/// Creates a Payload Too Large (413) error.
/// </summary>
public static ATErrorResult PayloadTooLarge(string? message = null)
=> new() { StatusCode = 413, Error = "PayloadTooLarge", Message = message ?? "Payload Too Large" };

/// <summary>
/// Creates a Too Many Requests (429) error.
/// </summary>
public static ATErrorResult TooManyRequests(string? message = null)
=> new() { StatusCode = 429, Error = "TooManyRequests", Message = message ?? "Too Many Requests" };

/// <summary>
/// Creates an Internal Server Error (500) error.
/// </summary>
public static ATErrorResult InternalServerError(string? message = null)
=> new() { StatusCode = 500, Error = "InternalServerError", Message = message ?? "Internal Server Error" };

/// <summary>
/// Creates an Internal Server Error (500) from an exception.
/// </summary>
public static ATErrorResult InternalServerError(Exception exception)
=> new() { StatusCode = 500, Error = "InternalServerError", Message = exception.Message };

/// <summary>
/// Creates a Not Implemented (501) error.
/// </summary>
public static ATErrorResult NotImplemented(string? message = null)
=> new() { StatusCode = 501, Error = "NotImplemented", Message = message ?? "Not Implemented" };

/// <summary>
/// Creates a Bad Gateway (502) error.
/// </summary>
public static ATErrorResult BadGateway(string? message = null)
=> new() { StatusCode = 502, Error = "BadGateway", Message = message ?? "Bad Gateway" };

/// <summary>
/// Creates a Service Unavailable (503) error.
/// </summary>
public static ATErrorResult ServiceUnavailable(string? message = null)
=> new() { StatusCode = 503, Error = "ServiceUnavailable", Message = message ?? "Service Unavailable" };

/// <summary>
/// Creates a Gateway Timeout (504) error.
/// </summary>
public static ATErrorResult GatewayTimeout(string? message = null)
=> new() { StatusCode = 504, Error = "GatewayTimeout", Message = message ?? "Gateway Timeout" };

/// <inheritdoc/>
public Task ExecuteAsync(HttpContext httpContext)
{
httpContext.Response.StatusCode = StatusCode;
httpContext.Response.ContentType = "application/json";
var error = new XrpcError
{
Error = this.Error,
Message = this.Message,
};
return httpContext.Response.WriteAsync(error.ToJson());
}
}
24 changes: 24 additions & 0 deletions src/CarpaNet.AspNetCore/CarpaNet.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<IsAotCompatible>true</IsAotCompatible>
<PackageId>CarpaNet.AspNetCore</PackageId>
<Description>ASP.NET Core XRPC endpoint support for CarpaNet ATProtocol library.</Description>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<ProjectReference Include="..\CarpaNet\CarpaNet.csproj" />
</ItemGroup>

<ItemGroup Condition=" ('$(IsPackable)' == 'true') or ('$(PackAsTool)' == 'true') ">
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath=""
Visible="false" />
</ItemGroup>

<Import Project="..\..\Version.props" />
</Project>
Loading
Loading