Skip to content

Commit 1167b86

Browse files
committed
Add ResendClient.Create and tool to check API examples
1 parent c3d4efd commit 1167b86

File tree

6 files changed

+280
-5
lines changed

6 files changed

+280
-5
lines changed

resend-dotnet.sln

+7
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderLiquid", "examples\Re
6060
EndProject
6161
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebControllerApi", "examples\WebControllerApi\WebControllerApi.csproj", "{64049053-1C1E-43D6-A23B-5A609354934B}"
6262
EndProject
63+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resend.DocsCheck", "tools\Resend.DocsCheck\Resend.DocsCheck.csproj", "{ED268270-FD7F-4111-8B54-020C81F8D683}"
64+
EndProject
6365
Global
6466
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6567
Debug|Any CPU = Debug|Any CPU
@@ -130,6 +132,10 @@ Global
130132
{64049053-1C1E-43D6-A23B-5A609354934B}.Debug|Any CPU.Build.0 = Debug|Any CPU
131133
{64049053-1C1E-43D6-A23B-5A609354934B}.Release|Any CPU.ActiveCfg = Release|Any CPU
132134
{64049053-1C1E-43D6-A23B-5A609354934B}.Release|Any CPU.Build.0 = Release|Any CPU
135+
{ED268270-FD7F-4111-8B54-020C81F8D683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
136+
{ED268270-FD7F-4111-8B54-020C81F8D683}.Debug|Any CPU.Build.0 = Debug|Any CPU
137+
{ED268270-FD7F-4111-8B54-020C81F8D683}.Release|Any CPU.ActiveCfg = Release|Any CPU
138+
{ED268270-FD7F-4111-8B54-020C81F8D683}.Release|Any CPU.Build.0 = Release|Any CPU
133139
EndGlobalSection
134140
GlobalSection(SolutionProperties) = preSolution
135141
HideSolutionNode = FALSE
@@ -151,6 +157,7 @@ Global
151157
{C33A6269-8657-4487-8CDD-588210E20B65} = {121B647E-18F4-41CB-AC00-49D5F06D5320}
152158
{FD4407E3-551D-48F8-9FFB-63E409F4C4BB} = {121B647E-18F4-41CB-AC00-49D5F06D5320}
153159
{64049053-1C1E-43D6-A23B-5A609354934B} = {121B647E-18F4-41CB-AC00-49D5F06D5320}
160+
{ED268270-FD7F-4111-8B54-020C81F8D683} = {CAC9B80A-B362-4135-AC30-8013D0DB6124}
154161
EndGlobalSection
155162
GlobalSection(ExtensibilityGlobals) = postSolution
156163
SolutionGuid = {15EEB6F3-A067-45F5-987C-824BD8FDEAF9}

src/Resend.Webhooks/WebApplicationExtensions.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using Microsoft.AspNetCore.Http;
2-
using Microsoft.AspNetCore.Mvc;
3-
using Microsoft.Extensions.DependencyInjection;
1+
using Microsoft.Extensions.DependencyInjection;
42
using Resend.Webhooks;
53

64
namespace Microsoft.AspNetCore.Builder;
@@ -12,6 +10,7 @@ public static class WebApplicationExtensions
1210
///
1311
/// </summary>
1412
/// <param name="services"></param>
13+
/// <param name="configureOptions"></param>
1514
/// <returns></returns>
1615
public static IServiceCollection AddResendWebhooks( this IServiceCollection services, Action<WebhookValidatorOptions>? configureOptions = null )
1716
{

src/Resend/IResend.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public interface IResend
110110
/// Domain.
111111
/// </returns>
112112
/// <see href="https://resend.com/docs/api-reference/domains/create-domain"/>
113-
Task<ResendResponse<Domain>> DomainAddAsync( string domainName, DeliveryRegion? region, CancellationToken cancellationToken = default );
113+
Task<ResendResponse<Domain>> DomainAddAsync( string domainName, DeliveryRegion? region = null, CancellationToken cancellationToken = default );
114114

115115

116116
/// <summary>

src/Resend/ResendClient.cs

+39-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public ResendClient( IOptions<ResendClientOptions> options, HttpClient httpClien
113113

114114

115115
/// <inheritdoc />
116-
public async Task<ResendResponse<Domain>> DomainAddAsync( string domainName, DeliveryRegion? region, CancellationToken cancellationToken = default )
116+
public async Task<ResendResponse<Domain>> DomainAddAsync( string domainName, DeliveryRegion? region = null, CancellationToken cancellationToken = default )
117117
{
118118
var path = $"/domains";
119119
var req = new HttpRequestMessage( HttpMethod.Post, path );
@@ -653,4 +653,42 @@ private ResendRateLimit FromHeaders( HttpResponseHeaders headers )
653653

654654
return (T) Convert.ChangeType( v, typeof( T ) );
655655
}
656+
657+
658+
/// <summary>
659+
/// Creates an instance of Resend client with the given client
660+
/// options.
661+
/// </summary>
662+
/// <param name="options">Resend client options.</param>
663+
/// <returns>Instance of Resend client.</returns>
664+
/// <remarks>
665+
/// Utility method for examples/one-off apps. For most use-cases it is
666+
/// preferable to use dependency injection to configure/inject `IResend`
667+
/// instances.
668+
/// </remarks>
669+
public static IResend Create( ResendClientOptions options )
670+
{
671+
var opt = Options.Create( options );
672+
673+
return new ResendClient( opt, new HttpClient() );
674+
}
675+
676+
677+
/// <summary>
678+
/// Creates an instance of Resend with the given API token.
679+
/// </summary>
680+
/// <param name="apiToken">API token</param>
681+
/// <returns>Instance of Resend client.</returns>
682+
/// <remarks>
683+
/// Utility method for examples/one-off apps. For most use-cases it is
684+
/// preferable to use dependency injection to configure/inject `IResend`
685+
/// instances.
686+
/// </remarks>
687+
public static IResend Create( string apiToken )
688+
{
689+
var opt = new ResendClientOptions();
690+
opt.ApiToken = apiToken;
691+
692+
return Create( opt );
693+
}
656694
}

tools/Resend.DocsCheck/Program.cs

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using McMaster.Extensions.CommandLineUtils;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.Emit;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using System.ComponentModel.DataAnnotations;
7+
using System.Reflection;
8+
using System.Text;
9+
10+
namespace Resend.DocsCheck;
11+
12+
/// <summary />
13+
public class Program
14+
{
15+
/// <summary />
16+
public static int Main( string[] args )
17+
{
18+
/*
19+
*
20+
*/
21+
var app = new CommandLineApplication<Program>();
22+
23+
var svc = new ServiceCollection();
24+
25+
var sp = svc.BuildServiceProvider();
26+
27+
28+
/*
29+
*
30+
*/
31+
try
32+
{
33+
app.Conventions
34+
.UseDefaultConventions()
35+
.UseConstructorInjection( sp );
36+
}
37+
catch ( Exception ex )
38+
{
39+
Console.WriteLine( "ftl: unhandled exception during setup" );
40+
Console.WriteLine( ex.ToString() );
41+
42+
return 2;
43+
}
44+
45+
46+
/*
47+
*
48+
*/
49+
try
50+
{
51+
return app.Execute( args );
52+
}
53+
catch ( UnrecognizedCommandParsingException ex )
54+
{
55+
Console.WriteLine( "err: " + ex.Message );
56+
57+
return 2;
58+
}
59+
catch ( Exception ex )
60+
{
61+
Console.WriteLine( "ftl: unhandled exception during execution" );
62+
Console.WriteLine( ex.ToString() );
63+
64+
return 2;
65+
}
66+
}
67+
68+
69+
/// <summary />
70+
[Option( "-r|--root", Description = "" )]
71+
[DirectoryExists]
72+
[Required]
73+
public string? RootFolder { get; set; }
74+
75+
76+
/// <summary />
77+
public async Task<int> OnExecute()
78+
{
79+
var dir = new DirectoryInfo( this.RootFolder! );
80+
81+
foreach ( var f in dir.GetFiles( "*.mdx", SearchOption.AllDirectories ) )
82+
{
83+
await FileCheck( dir, f );
84+
}
85+
86+
return 0;
87+
}
88+
89+
90+
/// <summary />
91+
private async Task FileCheck( DirectoryInfo root, FileInfo f )
92+
{
93+
var rel = GetRelativePath( root, f );
94+
var mdx = await File.ReadAllTextAsync( f.FullName );
95+
96+
97+
/*
98+
*
99+
*/
100+
var startIx = mdx.IndexOf( "```csharp" );
101+
102+
if ( startIx < 0 )
103+
return;
104+
105+
var endIx = mdx.IndexOf( "```", startIx + 4 );
106+
107+
108+
var fragment = mdx.Substring( startIx, endIx - startIx + 3 );
109+
110+
111+
/*
112+
*
113+
*/
114+
var lines = fragment.Split( "\n" );
115+
116+
var sb = new StringBuilder();
117+
sb.AppendLine( "using System;" );
118+
sb.AppendLine( "using System.Threading.Tasks;" );
119+
120+
foreach ( var l in lines )
121+
if ( l.StartsWith( "using " ) == true )
122+
sb.AppendLine( l );
123+
124+
sb.AppendLine();
125+
sb.AppendLine( "public class Program {" );
126+
sb.AppendLine( "public static async Task<int> Main( string[] args ) {" );
127+
128+
foreach ( var l in lines.Skip( 1 ).Take( lines.Count() - 2 ) )
129+
{
130+
if ( l.StartsWith( "using " ) == true )
131+
continue;
132+
133+
sb.AppendLine( l );
134+
}
135+
136+
sb.AppendLine( "return 0; } }" );
137+
138+
var csharp = sb.ToString();
139+
140+
141+
/*
142+
*
143+
*/
144+
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText( csharp );
145+
146+
var assemblyName = Path.GetRandomFileName();
147+
var references = new MetadataReference[]
148+
{
149+
MetadataReference.CreateFromFile( typeof( Object ).Assembly.Location ),
150+
MetadataReference.CreateFromFile( Assembly.Load( "System.Console" ).Location ),
151+
MetadataReference.CreateFromFile( Assembly.Load( "System.Runtime" ).Location ),
152+
MetadataReference.CreateFromFile( Assembly.Load( "System.Collections" ).Location ),
153+
MetadataReference.CreateFromFile( typeof( List<> ).Assembly.Location ),
154+
MetadataReference.CreateFromFile( typeof( IResend ).Assembly.Location ),
155+
};
156+
157+
CSharpCompilation compilation = CSharpCompilation.Create(
158+
assemblyName,
159+
syntaxTrees: new[] { syntaxTree },
160+
references: references,
161+
options: new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary ) );
162+
163+
using ( var ms = new MemoryStream() )
164+
{
165+
EmitResult result = compilation.Emit( ms );
166+
167+
if ( result.Success == false )
168+
{
169+
// handle exceptions
170+
IEnumerable<Diagnostic> failures = result.Diagnostics.Where( diagnostic =>
171+
diagnostic.IsWarningAsError ||
172+
diagnostic.Severity == DiagnosticSeverity.Error );
173+
174+
var fg = Console.ForegroundColor;
175+
Console.ForegroundColor = ConsoleColor.Red;
176+
Console.Write( "err" );
177+
Console.ForegroundColor = fg;
178+
179+
Console.Write( " " );
180+
Console.WriteLine( rel );
181+
182+
foreach ( Diagnostic diagnostic in failures )
183+
{
184+
Console.Error.WriteLine( "{0}: {1}", diagnostic.Id, diagnostic.GetMessage() );
185+
}
186+
}
187+
else
188+
{
189+
var fg = Console.ForegroundColor;
190+
Console.ForegroundColor = ConsoleColor.Green;
191+
Console.Write( "aok" );
192+
Console.ForegroundColor = fg;
193+
194+
Console.Write( " " );
195+
Console.WriteLine( rel );
196+
}
197+
}
198+
}
199+
200+
201+
/// <summary />
202+
private static string GetRelativePath( DirectoryInfo directoryInfo, FileInfo fileInfo )
203+
{
204+
Uri directoryUri = new Uri( directoryInfo.FullName + Path.DirectorySeparatorChar );
205+
Uri fileUri = new Uri( fileInfo.FullName );
206+
Uri relativeUri = directoryUri.MakeRelativeUri( fileUri );
207+
string relativePath = Uri.UnescapeDataString( relativeUri.ToString() );
208+
209+
return relativePath.Replace( '/', Path.DirectorySeparatorChar );
210+
}
211+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
12+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
13+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\src\Resend\Resend.csproj" />
18+
</ItemGroup>
19+
20+
</Project>

0 commit comments

Comments
 (0)