Added 'DnsRecords' extensions

This commit is contained in:
2025-07-16 20:09:27 +02:00
parent 6a3b2d93c6
commit 3614d0344b
54 changed files with 6165 additions and 12 deletions

View File

@@ -29,5 +29,11 @@
/// </summary>
[JsonProperty("total_count")]
public int? TotalCount { get; set; }
/// <summary>
/// Total number of pages of results.
/// </summary>
[JsonProperty("total_pages")]
public int? TotalPages { get; set; }
}
}

View File

@@ -15,7 +15,8 @@
</PropertyGroup>
<!-- Only build package for tagged releases or Debug on CI (only dev NuGet feed) -->
<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(CI_COMMIT_TAG)', '^dns\/v[0-9.]+')) or ('$(Configuration)' == 'Debug' and '$(GITLAB_CI)' == 'true')">
<!--<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(CI_COMMIT_TAG)', '^dns\/v[0-9.]+')) or ('$(Configuration)' == 'Debug' and '$(GITLAB_CI)' == 'true')">-->
<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(CI_COMMIT_TAG)', '^dns\/v[0-9.]+')) or '$(Configuration)' == 'Debug'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

View File

@@ -0,0 +1,638 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AMWD.Net.Api.Cloudflare.Dns.Internals;
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Extensions for <see href="https://developers.cloudflare.com/api/resources/dns/subresources/records/">DNS Records</see>.
/// </summary>
public static class DnsRecordsExtensions
{
private static readonly IReadOnlyCollection<DnsRecordType> _dataComponentTypes = [
DnsRecordType.CAA,
DnsRecordType.CERT,
DnsRecordType.DNSKEY,
DnsRecordType.DS,
DnsRecordType.HTTPS,
DnsRecordType.LOC,
DnsRecordType.NAPTR,
DnsRecordType.SMIMEA,
DnsRecordType.SRV,
DnsRecordType.SSHFP,
DnsRecordType.SVCB,
DnsRecordType.TLSA,
DnsRecordType.URI,
];
private static readonly IReadOnlyCollection<DnsRecordType> _priorityTypes = [
DnsRecordType.MX,
DnsRecordType.SRV,
DnsRecordType.URI,
];
/// <summary>
/// Send a Batch of DNS Record API calls to be executed together.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>
/// Although Cloudflare will execute the batched operations in a single database
/// transaction, Cloudflare's distributed KV store must treat each record change
/// as a single key-value pair. This means that the propagation of changes is not
/// atomic. See
/// <see href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/batch-record-changes/">the documentation</see>
/// for more information.
/// </item>
/// <item>
/// The operations you specify within the /batch request body are always executed
/// in the following order:
/// <list type="number">
/// <item>Deletes (DELETE)</item>
/// <item>Updates (PATCH)</item>
/// <item>Overwrites (PUT)</item>
/// <item>Creates (POST)</item>
/// </list>
/// </item>
/// </list>
/// </remarks>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<BatchDnsRecordsResponse>> BatchDnsRecords(this ICloudflareClient client, BatchDnsRecordsRequest request, CancellationToken cancellationToken = default)
{
request.ZoneId.ValidateCloudflareId();
// Deletes (DELETE)
var deletes = new List<Identifier>();
foreach (string delete in request.Deletes ?? [])
{
delete.ValidateCloudflareId();
deletes.Add(new Identifier { Id = delete });
}
// Updates (PATCH)
var patches = new List<InternalBatchUpdateRequest>();
foreach (var patch in request.Updates ?? [])
{
patch.Id.ValidateCloudflareId();
var req = ValidateRequest(patch);
req.Id = patch.Id;
patches.Add(req);
}
// Creates (POST)
var posts = new List<InternalDnsRecordRequest>();
foreach (var post in request.Creates ?? [])
{
var req = (InternalDnsRecordRequest)ValidateRequest(post);
posts.Add(req);
}
// Overwrites (PUT)
var puts = new List<InternalBatchUpdateRequest>();
foreach (var put in request.Overwrites ?? [])
{
put.Id.ValidateCloudflareId();
var req = ValidateRequest(put);
req.Id = put.Id;
puts.Add(req);
}
var batchReq = new InternalBatchRequest();
if (deletes.Count > 0)
batchReq.Deletes = deletes;
if (patches.Count > 0)
batchReq.Patches = patches;
if (posts.Count > 0)
batchReq.Posts = posts;
if (puts.Count > 0)
batchReq.Puts = puts;
return client.PostAsync<BatchDnsRecordsResponse, InternalBatchRequest>($"/zones/{request.ZoneId}/dns_records/batch", batchReq, null, cancellationToken);
}
/// <summary>
/// Create a new DNS record for a zone.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>A/AAAA records cannot exist on the same name as CNAME records.</item>
/// <item>NS records cannot exist on the same name as any other record type.</item>
/// <item>Domain names are always represented in Punycode, even if Unicode characters were used when creating the record.</item>
/// </list>
/// </remarks>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<DnsRecord>> CreateDnsRecord(this ICloudflareClient client, CreateDnsRecordRequest request, CancellationToken cancellationToken = default)
{
request.ZoneId.ValidateCloudflareId();
var req = ValidateRequest(request);
return client.PostAsync<DnsRecord, InternalDnsRecordRequest>($"/zones/{request.ZoneId}/dns_records", req, null, cancellationToken);
}
/// <summary>
/// Delete DNS Record.
/// </summary>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="recordId">The record identifier.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<Identifier>> DeleteDnsRecord(this ICloudflareClient client, string zoneId, string recordId, CancellationToken cancellationToken = default)
{
zoneId.ValidateCloudflareId();
recordId.ValidateCloudflareId();
return client.DeleteAsync<Identifier>($"/zones/{zoneId}/dns_records/{recordId}", null, cancellationToken);
}
/// <summary>
/// Update an existing DNS record.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>A/AAAA records cannot exist on the same name as CNAME records.</item>
/// <item>NS records cannot exist on the same name as any other record type.</item>
/// <item>Domain names are always represented in Punycode, even if Unicode characters were used when creating the record.</item>
/// </list>
/// </remarks>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<DnsRecord>> UpdateDnsRecord(this ICloudflareClient client, UpdateDnsRecordRequest request, CancellationToken cancellationToken = default)
{
request.ZoneId.ValidateCloudflareId();
request.RecordId.ValidateCloudflareId();
var req = ValidateRequest(request);
return client.PatchAsync<DnsRecord, InternalDnsRecordRequest>($"/zones/{request.ZoneId}/dns_records/{request.RecordId}", req, cancellationToken);
}
/// <summary>
/// You can export your <see href="https://en.wikipedia.org/wiki/Zone_file">BIND config</see> through this endpoint.
/// </summary>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<string>> ExportDnsRecords(this ICloudflareClient client, string zoneId, CancellationToken cancellationToken = default)
{
zoneId.ValidateCloudflareId();
return client.GetAsync<string>($"/zones/{zoneId}/dns_records/export", null, cancellationToken);
}
/// <summary>
/// DNS Record Details.
/// </summary>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="recordId">The record identifier.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<DnsRecord>> DnsRecordDetails(this ICloudflareClient client, string zoneId, string recordId, CancellationToken cancellationToken = default)
{
zoneId.ValidateCloudflareId();
recordId.ValidateCloudflareId();
return client.GetAsync<DnsRecord>($"/zones/{zoneId}/dns_records/{recordId}", null, cancellationToken);
}
/// <summary>
/// You can upload your <see href="https://en.wikipedia.org/wiki/Zone_file">BIND config</see> through this endpoint.
/// It assumes that cURL is called from a location with bind_config.txt (valid BIND config) present.
/// </summary>
/// <remarks>
/// See <see href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/import-and-export/">the documentation</see> for more information.
/// </remarks>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<RecordImportResponse>> ImportDnsRecords(this ICloudflareClient client, ImportDnsRecordsRequest request, CancellationToken cancellationToken = default)
{
request.ZoneId.ValidateCloudflareId();
if (string.IsNullOrWhiteSpace(request.File))
throw new ArgumentNullException(nameof(request.File));
var req = new MultipartFormDataContent();
if (request.Proxied.HasValue)
req.Add(new StringContent(request.Proxied.Value.ToString().ToLowerInvariant()), "proxied");
byte[] fileBytes = File.Exists(request.File)
? File.ReadAllBytes(request.File)
: Encoding.UTF8.GetBytes(request.File);
req.Add(new ByteArrayContent(fileBytes), "file");
return client.PostAsync<RecordImportResponse, MultipartFormDataContent>($"/zones/{request.ZoneId}/dns_records/import", req, null, cancellationToken);
}
/// <summary>
/// List, search, sort, and filter a zones' DNS records.
/// </summary>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="options">Filter options (optional).</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<IReadOnlyCollection<DnsRecord>>> ListDnsRecords(this ICloudflareClient client, string zoneId, ListDnsRecordsFilter? options = null, CancellationToken cancellationToken = default)
{
zoneId.ValidateCloudflareId();
return client.GetAsync<IReadOnlyCollection<DnsRecord>>($"/zones/{zoneId}/dns_records", options, cancellationToken);
}
/// <summary>
/// Scan for common DNS records on your domain and automatically add them to your zone.
/// Useful if you haven't updated your nameservers yet.
/// </summary>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<RecordScanResponse>> ScanDnsRecords(this ICloudflareClient client, string zoneId, CancellationToken cancellationToken = default)
{
zoneId.ValidateCloudflareId();
return client.PostAsync<RecordScanResponse, object>($"/zones/{zoneId}/dns_records/scan", null, null, cancellationToken);
}
/// <summary>
/// Overwrite an existing DNS record.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>A/AAAA records cannot exist on the same name as CNAME records.</item>
/// <item>NS records cannot exist on the same name as any other record type.</item>
/// <item>Domain names are always represented in Punycode, even if Unicode characters were used when creating the record.</item>
/// </list>
/// </remarks>
/// <param name="client">The <see cref="ICloudflareClient"/> instance.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">A cancellation token used to propagate notification that this operation should be canceled.</param>
public static Task<CloudflareResponse<DnsRecord>> OverwriteDnsRecord(this ICloudflareClient client, OverwriteDnsRecordRequest request, CancellationToken cancellationToken = default)
{
request.ZoneId.ValidateCloudflareId();
request.RecordId.ValidateCloudflareId();
var req = ValidateRequest(request);
return client.PutAsync<DnsRecord, InternalDnsRecordRequest>($"/zones/{request.ZoneId}/dns_records/{request.RecordId}", req, cancellationToken);
}
private static InternalDnsRecordRequest ValidateRequest(CreateDnsRecordRequest request)
{
if (string.IsNullOrWhiteSpace(request.Name))
throw new ArgumentNullException(nameof(request.Name));
if (!Enum.IsDefined(typeof(DnsRecordType), request.Type))
throw new ArgumentOutOfRangeException(nameof(request.Type), request.Type, $"The value '{request.Type}' is not valid. Valid value are in {nameof(DnsRecordType)}.");
var req = new InternalDnsRecordRequest
{
Name = request.Name.Trim(),
Type = request.Type,
Comment = request.Comment?.Trim(),
Proxied = request.Proxied,
Tags = request.Tags?
.Where(t => !string.IsNullOrWhiteSpace(t))
.Select(t => t.Trim())
.ToList()
};
if (!_dataComponentTypes.Contains(request.Type) && string.IsNullOrWhiteSpace(request.Content))
{
throw new ArgumentNullException(nameof(request.Content));
}
else
{
req.Content = request.Content?.Trim();
}
if (request.Type == DnsRecordType.CNAME && request.Settings != null && request.Settings is CNAMERecordSettings cnameSettings)
req.Settings = cnameSettings;
if (_dataComponentTypes.Contains(request.Type) && request.Data == null)
throw new ArgumentNullException(nameof(request.Data));
if (_priorityTypes.Contains(request.Type))
{
if (!request.Priority.HasValue)
throw new ArgumentNullException(nameof(request.Priority));
req.Priority = request.Priority.Value;
}
if (request.TimeToLive.HasValue)
{
if (request.TimeToLive == 1)
{
req.Ttl = 1;
}
else if (request.TimeToLive < 30 || 86400 < request.TimeToLive)
{
throw new ArgumentOutOfRangeException(nameof(request.TimeToLive), request.TimeToLive, $"The value '{request.TimeToLive}' is not valid. Valid value are between 60 (30 for Enterprise) and 86400.");
}
else
{
req.Ttl = request.TimeToLive.Value;
}
}
switch (request.Type)
{
case DnsRecordType.CAA:
if (request.Data is CAARecordData caaData)
{
if (string.IsNullOrWhiteSpace(caaData.Tag))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(caaData.Tag)}");
if (string.IsNullOrWhiteSpace(caaData.Value))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(caaData.Value)}");
req.Data = caaData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(CAARecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.CERT:
if (request.Data is CERTRecordData certData)
{
if (string.IsNullOrWhiteSpace(certData.Certificate))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(certData.Certificate)}");
req.Data = certData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(CAARecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.DNSKEY:
if (request.Data is DNSKEYRecordData dnskeyData)
{
if (string.IsNullOrWhiteSpace(dnskeyData.PublicKey))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(dnskeyData.PublicKey)}");
req.Data = dnskeyData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(DNSKEYRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.DS:
if (request.Data is DSRecordData dsData)
{
if (string.IsNullOrWhiteSpace(dsData.Digest))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(dsData.Digest)}");
req.Data = dsData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(DSRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.HTTPS:
if (request.Data is HTTPSRecordData httpsData)
{
if (string.IsNullOrWhiteSpace(httpsData.Target))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(httpsData.Target)}");
if (string.IsNullOrWhiteSpace(httpsData.Value))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(httpsData.Value)}");
req.Data = httpsData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(HTTPSRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.LOC:
if (request.Data is LOCRecordData locData)
{
if (locData.LatitudeDegrees.HasValue && (locData.LatitudeDegrees < 0 || 90 < locData.LatitudeDegrees))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LatitudeDegrees)}", locData.LatitudeDegrees, $"The value '{locData.LatitudeDegrees}' is not valid. Valid value are between {0} and {90}.");
if (locData.LatitudeMinutes.HasValue && (locData.LatitudeMinutes < 0 || 59 < locData.LatitudeMinutes))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LatitudeMinutes)}", locData.LatitudeMinutes, $"The value '{locData.LatitudeMinutes}' is not valid. Valid value are between {0} and {59}.");
if (locData.LatitudeSeconds.HasValue && (locData.LatitudeSeconds < 0 || 59.999 < locData.LatitudeSeconds))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LatitudeSeconds)}", locData.LatitudeSeconds, $"The value '{locData.LatitudeSeconds}' is not valid. Valid value are between {0} and {59.999}.");
if (locData.LatitudeDirection.HasValue && !Enum.IsDefined(typeof(LOCRecordLatitudeDirection), locData.LatitudeDirection))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LatitudeDirection)}", locData.LatitudeDirection, $"The value '{locData.LatitudeDirection}' is not valid.");
if (locData.LongitudeDegrees.HasValue && (locData.LongitudeDegrees < 0 || 180 < locData.LongitudeDegrees))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LongitudeDegrees)}", locData.LongitudeDegrees, $"The value '{locData.LongitudeDegrees}' is not valid. Valid value are between {0} and {180}.");
if (locData.LongitudeMinutes.HasValue && (locData.LongitudeMinutes < 0 || 59 < locData.LongitudeMinutes))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LongitudeMinutes)}", locData.LongitudeMinutes, $"The value '{locData.LongitudeMinutes}' is not valid. Valid value are between {0} and {59}.");
if (locData.LongitudeSeconds.HasValue && (locData.LongitudeSeconds < 0 || 59.999 < locData.LongitudeSeconds))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LongitudeSeconds)}", locData.LongitudeSeconds, $"The value '{locData.LongitudeSeconds}' is not valid. Valid value are between {0} and {59.999}.");
if (locData.LongitudeDirection.HasValue && (!Enum.IsDefined(typeof(LOCRecordLongitudeDirection), locData.LongitudeDirection)))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.LongitudeDirection)}", locData.LongitudeDirection, $"The value '{locData.LongitudeDirection}' is not valid.");
if (locData.Altitude.HasValue && (locData.Altitude < -100_000 || 42_849_672.95 < locData.Altitude))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.Altitude)}", locData.Size, $"The value '{locData.Altitude}' is not valid. Valid value are between {-100_000} and {42_849_672.95}.");
if (locData.Size.HasValue && (locData.Size < 0 || 90_000_000 < locData.Size))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.Size)}", locData.Size, $"The value '{locData.Size}' is not valid. Valid value are between {0} and {90_000_000}.");
if (locData.PrecisionHorizontal.HasValue && (locData.PrecisionHorizontal < 0 || 90_000_000 < locData.PrecisionHorizontal))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.PrecisionHorizontal)}", locData.Size, $"The value '{locData.PrecisionHorizontal}' is not valid. Valid value are between {0} and {90_000_000}.");
if (locData.PrecisionVertical.HasValue && (locData.PrecisionVertical < 0 || 90_000_000 < locData.PrecisionVertical))
throw new ArgumentOutOfRangeException($"{nameof(request.Data)}.{nameof(locData.PrecisionVertical)}", locData.PrecisionVertical, $"The value '{locData.PrecisionVertical}' is not valid. Valid value are between {0} and {90_000_000}.");
if (locData.LatitudeSeconds.HasValue)
locData.LatitudeSeconds = Math.Floor(locData.LatitudeSeconds.Value * 1000) / 1000; // Truncate to 3 decimal places
if (locData.LongitudeSeconds.HasValue)
locData.LongitudeSeconds = Math.Floor(locData.LongitudeSeconds.Value * 1000) / 1000; // Truncate to 3 decimal places
if (locData.Altitude.HasValue)
locData.Altitude = Math.Floor(locData.Altitude.Value * 100) / 100; // Truncate to 2 decimal places
req.Data = locData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(LOCRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.NAPTR:
if (request.Data is NAPTRRecordData naptrData)
{
if (string.IsNullOrWhiteSpace(naptrData.Flags))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(naptrData.Flags)}");
if (string.IsNullOrWhiteSpace(naptrData.Regex))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(naptrData.Regex)}");
if (string.IsNullOrWhiteSpace(naptrData.Replacement))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(naptrData.Replacement)}");
if (string.IsNullOrWhiteSpace(naptrData.Service))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(naptrData.Service)}");
req.Data = naptrData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(NAPTRRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.SMIMEA:
if (request.Data is SMIMEARecordData smimeaData)
{
if (string.IsNullOrWhiteSpace(smimeaData.Certificate))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(smimeaData.Certificate)}");
req.Data = smimeaData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(SMIMEARecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.SRV:
if (request.Data is SRVRecordData srvData)
{
if (string.IsNullOrWhiteSpace(srvData.Target))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(srvData.Target)}");
req.Data = srvData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(SRVRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.SSHFP:
if (request.Data is SSHFPRecordData sshfpData)
{
if (string.IsNullOrWhiteSpace(sshfpData.Fingerprint))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(sshfpData.Fingerprint)}");
req.Data = sshfpData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(SSHFPRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.SVCB:
if (request.Data is SVCBRecordData svcbData)
{
if (string.IsNullOrWhiteSpace(svcbData.Target))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(svcbData.Target)}");
if (string.IsNullOrWhiteSpace(svcbData.Value))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(svcbData.Value)}");
req.Data = svcbData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(SVCBRecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.TLSA:
if (request.Data is TLSARecordData tlsaData)
{
if (string.IsNullOrWhiteSpace(tlsaData.Certificate))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(tlsaData.Certificate)}");
req.Data = tlsaData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(TLSARecordData)}'", nameof(request.Data));
}
break;
case DnsRecordType.URI:
if (request.Data is URIRecordData uriData)
{
if (string.IsNullOrWhiteSpace(uriData.Target))
throw new ArgumentNullException($"{nameof(request.Data)}.{nameof(uriData.Target)}");
req.Data = uriData;
}
else
{
throw new ArgumentException($"The type of the value '{nameof(request.Data)}' has to be '{nameof(URIRecordData)}'", nameof(request.Data));
}
break;
}
return req;
}
private static InternalBatchUpdateRequest ValidateRequest(BatchDnsRecordsRequest.Post request)
{
var req = ValidateRequest(new CreateDnsRecordRequest("", request.Name)
{
Comment = request.Comment,
Content = request.Content,
Data = request.Data,
Priority = request.Priority,
Proxied = request.Proxied,
Settings = request.Settings,
Tags = request.Tags,
TimeToLive = request.TimeToLive,
Type = request.Type
});
return new InternalBatchUpdateRequest
{
Comment = req.Comment,
Content = req.Content,
Data = req.Data,
Name = req.Name,
Priority = req.Priority,
Proxied = req.Proxied,
Settings = req.Settings,
Tags = req.Tags,
Ttl = req.Ttl,
Type = req.Type
};
}
}
}

View File

@@ -0,0 +1,277 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// DNS record types.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/dns/records.ts">Source</see>
/// </summary>
/// <remarks>
/// A list with short description can be found <see href="https://en.wikipedia.org/wiki/List_of_DNS_record_types">@wikipedia</see>.
/// </remarks>
[JsonConverter(typeof(StringEnumConverter))]
public enum DnsRecordType : int
{
/// <summary>
/// Address record.
/// </summary>
/// <remarks>
/// Returns a 32-bit IPv4 address, most commonly used to map hostnames to an IP address of the host, but it is also used for DNSBLs, storing subnet masks in RFC 1101, etc.
/// <code>
/// example.com. 3600 IN A 96.7.128.175
/// </code>
/// </remarks>
[EnumMember(Value = "A")]
A = 1,
/// <summary>
/// IPv6 address record.
/// </summary>
/// <remarks>
/// Returns a 128-bit IPv6 address, most commonly used to map hostnames to an IP address of the host.
/// <code>
/// example.com. 3600 IN AAAA 2600:1408:ec00:36::1736:7f31
/// </code>
/// </remarks>
[EnumMember(Value = "AAAA")]
AAAA = 28,
/// <summary>
/// Certification Authority Authorization.
/// </summary>
/// <remarks>
/// DNS Certification Authority Authorization, constraining acceptable CAs for a host/domain.
/// <code>
/// example.com. 604800 IN CAA 0 issue "letsencrypt.org"
/// </code>
/// </remarks>
[EnumMember(Value = "CAA")]
CAA = 257,
/// <summary>
/// Certificate record.
/// </summary>
/// <remarks>
/// Stores PKIX, SPKI, PGP, etc.
/// <code>
/// example.com. 86400 IN CERT 2 77 2 TUlJQ1l6Q0NBY3lnQXdJQkFnSUJBREFOQmdrcWh
/// </code>
/// </remarks>
[EnumMember(Value = "CERT")]
CERT = 37,
/// <summary>
/// Canonical name record.
/// </summary>
/// <remarks>
/// Alias of one name to another: the DNS lookup will continue by retrying the lookup with the new name.
/// <code>
/// autodiscover.example.com. 86400 IN CNAME mail.example.com.
/// </code>
/// </remarks>
[EnumMember(Value = "CNAME")]
CNAME = 5,
/// <summary>
/// DNS Key record.
/// </summary>
/// <remarks>
/// The key record used in DNSSEC. Uses the same format as the KEY record.
/// <code>
/// example.com. 3600 IN DNSKEY 256 3 13 OtuN/SL9sE+SDQ0tOLeezr1KzUNi77FflTjxQylUhm3V7m13Vz9tYQuc SGK0pyxISo9CQsszubAwJSypq3li3g==
/// </code>
/// </remarks>
[EnumMember(Value = "DNSKEY")]
DNSKEY = 48,
/// <summary>
/// Delegation signer.
/// </summary>
/// <remarks>
/// The record used to identify the DNSSEC signing key of a delegated zone.
/// <code>
/// example.com. 86400 IN DS 370 13 2 BE74359954660069D5C63D200C39F5603827D7DD02B56F120EE9F3A8 6764247C
/// </code>
/// </remarks>
[EnumMember(Value = "DS")]
DS = 43,
/// <summary>
/// HTTPS Binding.
/// </summary>
/// <remarks>
/// RR that improves performance for clients that need to resolve many resources to access a domain.
/// <code>
/// example.com. 3600 IN HTTPS 1 svc.example.com. alpn=h2
/// </code>
/// </remarks>
[EnumMember(Value = "HTTPS")]
HTTPS = 65,
/// <summary>
/// Location record.
/// </summary>
/// <remarks>
/// Specifies a geographical location associated with a domain name.
/// <code>
/// SW1A2AA.find.me.uk. 2592000 IN LOC 51 30 12.748 N 0 7 39.611 W 0.00m 0.00m 0.00m 0.00m
/// </code>
/// </remarks>
[EnumMember(Value = "LOC")]
LOC = 29,
/// <summary>
/// Mail exchange record.
/// </summary>
/// <remarks>
/// List of mail exchange servers that accept email for a domain.
/// <code>
/// example.com. 43200 IN MX 0 mail.example.com.
/// </code>
/// </remarks>
[EnumMember(Value = "MX")]
MX = 15,
/// <summary>
/// Naming Authority Pointer.
/// </summary>
/// <remarks>
/// Allows regular-expression-based rewriting of domain names which can then be used as URIs, further domain names to lookups, etc.
/// <code>
/// example.com. 86400 IN NAPTR 100 10 "S" "SIP+D2T" "" _sip._tcp.example.com.
/// </code>
/// </remarks>
[EnumMember(Value = "NAPTR")]
NAPTR = 35,
/// <summary>
/// Name server record.
/// </summary>
/// <remarks>
/// Delegates a DNS zone to use the given authoritative name servers.
/// <code>
/// example.com. 86400 IN NS a.iana-servers.net.
/// </code>
/// </remarks>
[EnumMember(Value = "NS")]
NS = 2,
/// <summary>
/// OpenPGP public key record.
/// </summary>
/// <remarks>
/// A DNS-based Authentication of Named Entities (DANE) method for publishing and locating OpenPGP
/// public keys in DNS for a specific email address using an OPENPGPKEY DNS resource record.
/// <code>
/// 00d8d3f11739d2f3537099982b4674c29fc59a8fda350fca1379613a._openpgpkey.example.com. 3600 IN OPENPGPKEY a2V5S0VZMTIzNGtleUtFWQ==
/// </code>
/// </remarks>
[EnumMember(Value = "OPENPGPKEY")]
OPENPGPKEY = 61,
/// <summary>
/// PTR Resource Record.
/// </summary>
/// <remarks>
/// Pointer to a canonical name.
/// Unlike a CNAME, DNS processing stops and just the name is returned.
/// The most common use is for implementing reverse DNS lookups, but other uses include such things as DNS-SD.
/// <code>
/// 14.215.184.93.in-addr.arpa. 86400 IN PTR example.com.
/// </code>
/// </remarks>
[EnumMember(Value = "PTR")]
PTR = 12,
/// <summary>
/// S/MIME cert association.
/// </summary>
/// <remarks>
/// Associates an S/MIME certificate with a domain name for sender authentication.
/// <code>
/// example.com. 3600 IN SMIMEA 0 0 0 keyKEY1234keyKEY
/// </code>
/// </remarks>
[EnumMember(Value = "SMIMEA")]
SMIMEA = 53,
/// <summary>
/// Service locator.
/// </summary>
/// <remarks>
/// Generalized service location record, used for newer protocols instead of creating protocol-specific records such as MX.
/// <code>
/// _autodiscover._tcp.example.com. 604800 IN SRV 1 0 443 mail.example.com.
/// </code>
/// </remarks>
[EnumMember(Value = "SRV")]
SRV = 33,
/// <summary>
/// SSH Public Key Fingerprint.
/// </summary>
/// <remarks>
/// Resource record for publishing SSH public host key fingerprints in the DNS, in order to aid in verifying the authenticity of the host.
/// RFC 6594 defines ECC SSH keys and SHA-256 hashes. See the IANA SSHFP RR parameters registry for details.
/// <code>
/// example.com. 600 IN SSHFP 2 1 123456789abcdef67890123456789abcdef67890
/// </code>
/// </remarks>
[EnumMember(Value = "SSHFP")]
SSHFP = 44,
/// <summary>
/// Service Binding.
/// </summary>
/// <remarks>
/// RR that improves performance for clients that need to resolve many resources to access a domain.
/// <code>
/// example.com. 3600 IN SVCB 1 . alpn="h2,http/1.1"
/// </code>
/// </remarks>
[EnumMember(Value = "SVCB")]
SVCB = 64,
/// <summary>
/// TLSA certificate association.
/// </summary>
/// <remarks>
/// A record for DANE. RFC 6698 defines "The TLSA DNS resource record is used to associate a TLS server certificate
/// or public key with the domain name where the record is found, thus forming a 'TLSA certificate association'".
/// <code>
/// _443._tcp.example.com. 3600 IN TLSA 3 0 18cb0fc6c527506a053f4f14c8464bebbd6dede2738d11468dd953d7d6a3021f1
/// </code>
/// </remarks>
[EnumMember(Value = "TLSA")]
TLSA = 52,
/// <summary>
/// Text record.
/// </summary>
/// <remarks>
/// Originally for arbitrary human-readable text in a DNS record.
/// Since the early 1990s, however, this record more often carries machine-readable data, such as specified by RFC 1464,
/// opportunistic encryption, Sender Policy Framework, DKIM, DMARC, DNS-SD, etc.
/// <br/>
/// More information about TXT records on <see href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/">Cloudflare</see>.
/// <code>
/// example.com. 86400 IN TXT "v=spf1 -all"
/// </code>
/// </remarks>
[EnumMember(Value = "TXT")]
TXT = 16,
/// <summary>
/// Uniform Resource Identifier.
/// </summary>
/// <remarks>
/// Can be used for publishing mappings from hostnames to URIs.
/// <code>
/// _ftp._tcp.example.com. 3600 IN URI 10 1 "ftp://ftp.example.com/public"
/// </code>
/// </remarks>
[EnumMember(Value = "URI")]
URI = 256,
}
}

View File

@@ -0,0 +1,24 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Options how to match the query filter.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum FilterMatchType
{
/// <summary>
/// Match any rule.
/// </summary>
[EnumMember(Value = "any")]
Any = 1,
/// <summary>
/// Match all rules.
/// </summary>
[EnumMember(Value = "all")]
All = 2
}
}

View File

@@ -0,0 +1,439 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Filter for listing DNS records.
/// </summary>
public class ListDnsRecordsFilter : IQueryParameterFilter
{
#region Comment
/// <summary>
/// Exact value of the DNS record comment.
/// This is a convenience alias for <see cref="CommentExact"/>.
/// </summary>
public string? Comment { get; set; }
/// <summary>
/// If this parameter is present, only records <em>without</em> a comment are returned.
/// </summary>
public bool? CommentAbsent { get; set; }
/// <summary>
/// Substring of the DNS record comment.
/// Comment filters are case-insensitive.
/// </summary>
public string? CommentContains { get; set; }
/// <summary>
/// Suffix of the DNS record comment.
/// Comment filters are case-insensitive.
/// </summary>
public string? CommentEndsWith { get; set; }
/// <summary>
/// Exact value of the DNS record comment.
/// Comment filters are case-insensitive.
/// </summary>
public string? CommentExact { get; set; }
/// <summary>
/// If this parameter is present, only records <em>with</em> a comment are returned.
/// </summary>
public bool? CommentPresent { get; set; }
/// <summary>
/// Prefix of the DNS record comment.
/// Comment filters are case-insensitive.
/// </summary>
public string? CommentStartsWith { get; set; }
#endregion Comment
#region Content
/// <summary>
/// Exact value of the DNS record Content.
/// This is a convenience alias for <see cref="ContentExact"/>.
/// </summary>
public string? Content { get; set; }
/// <summary>
/// Substring of the DNS record Content.
/// Content filters are case-insensitive.
/// </summary>
public string? ContentContains { get; set; }
/// <summary>
/// Suffix of the DNS record Content.
/// Content filters are case-insensitive.
/// </summary>
public string? ContentEndsWith { get; set; }
/// <summary>
/// Exact value of the DNS record Content.
/// Content filters are case-insensitive.
/// </summary>
public string? ContentExact { get; set; }
/// <summary>
/// Prefix of the DNS record Content.
/// Content filters are case-insensitive.
/// </summary>
public string? ContentStartsWith { get; set; }
#endregion Content
/// <summary>
/// Direction to order DNS records in.
/// </summary>
public SortDirection? Direction { get; set; }
/// <summary>
/// Whether to match all search requirements or at least one (any).
/// </summary>
/// <remarks>
/// <para>
/// If set to <see cref="FilterMatchType.All"/>, acts like a logical AND between filters.
/// <br/>
/// If set to <see cref="FilterMatchType.Any"/>, acts like a logical OR instead.
/// </para>
/// <para>
/// Note that the interaction between tag filters is controlled by the <see cref="TagMatch"/> parameter instead.
/// </para>
/// </remarks>
public FilterMatchType? Match { get; set; }
#region Name
/// <summary>
/// Exact value of the DNS record Name.
/// This is a convenience alias for <see cref="NameExact"/>.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Substring of the DNS record Name.
/// Name filters are case-insensitive.
/// </summary>
public string? NameContains { get; set; }
/// <summary>
/// Suffix of the DNS record Name.
/// Name filters are case-insensitive.
/// </summary>
public string? NameEndsWith { get; set; }
/// <summary>
/// Exact value of the DNS record Name.
/// Name filters are case-insensitive.
/// </summary>
public string? NameExact { get; set; }
/// <summary>
/// Prefix of the DNS record Name.
/// Name filters are case-insensitive.
/// </summary>
public string? NameStartsWith { get; set; }
#endregion Name
/// <summary>
/// Field to order DNS records by.
/// </summary>
public DnsRecordsOrderBy? OrderBy { get; set; }
/// <summary>
/// Page number of paginated results.
/// </summary>
/// <value>1 &lt;= X</value>
public int? Page { get; set; }
/// <summary>
/// Number of DNS records per page.
/// </summary>
/// <value>1 &lt;= X &lt;= 5,000,000</value>
public int? PerPage { get; set; }
/// <summary>
/// Whether the record is receiving the performance and security benefits of Cloudflare.
/// </summary>
public bool? Proxied { get; set; }
/// <summary>
/// Allows searching in multiple properties of a DNS record simultaneously.
/// </summary>
/// <remarks>
/// <para>
/// This parameter is intended for human users, not automation.
/// Its exact behavior is intentionally left unspecified and is subject to change in the future.
/// </para>
/// <para>
/// <em>
/// This parameter works independently of the <see cref="Match"/> setting.
/// <br/>
/// For automated searches, please use the other available parameters.
/// </em>
/// </para>
/// </remarks>
public string? Search { get; set; }
#region Tag
/// <summary>
/// Condition on the DNS record tag.
/// </summary>
/// <remarks>
/// <para>
/// Parameter values can be of the form <c>&lt;tag-name&gt;:&lt;tag-value&gt;</c> to search for an exact name:value pair,
/// or just <c>&lt;tag-name&gt;</c> to search for records with a specific tag name regardless of its value.
/// </para>
/// <para>
/// This is a convenience shorthand for the more powerful <c>tag.&lt;predicate&gt;</c> parameters.
/// <br/>
/// Examples:
/// <list type="bullet">
/// <item><c>tag=important</c> is equivalent to <c>tag.present=important</c></item>
/// <item><c>tag=team:DNS</c> is equivalent to <c>tag.exact=team:DNS</c></item>
/// </list>
/// </para>
/// </remarks>
public string? Tag { get; set; }
/// <summary>
/// Name of a tag which must <em>not</em> be present on the DNS record.
/// Tag filters are case-insensitive.
/// </summary>
public string? TagAbsent { get; set; }
/// <summary>
/// A tag and value, of the form <c>&lt;tag-name&gt;:&lt;tag-value&gt;</c>.
/// Tag filters are case-insensitive.
/// </summary>
/// <remarks>
/// The API will only return DNS records that have a tag named <c>&lt;tag-name&gt;</c> whose value contains <c>&lt;tag-value&gt;</c>.
/// </remarks>
public string? TagContains { get; set; }
/// <summary>
/// A tag and value, of the form <c>&lt;tag-name&gt;:&lt;tag-value&gt;</c>.
/// Tag filters are case-insensitive.
/// </summary>
/// <remarks>
/// The API will only return DNS records that have a tag named <c>&lt;tag-name&gt;</c> whose value ends with <c>&lt;tag-value&gt;</c>.
/// </remarks>
public string? TagEndsWith { get; set; }
/// <summary>
/// A tag and value, of the form <c>&lt;tag-name&gt;:&lt;tag-value&gt;</c>.
/// Tag filters are case-insensitive.
/// </summary>
/// <remarks>
/// The API will only return DNS records that have a tag named <c>&lt;tag-name&gt;</c> whose value is <c>&lt;tag-value&gt;</c>.
/// </remarks>
public string? TagExact { get; set; }
/// <summary>
/// Name of a tag which must be present on the DNS record.
/// Tag filters are case-insensitive.
/// </summary>
public string? TagPresent { get; set; }
/// <summary>
/// A tag and value, of the form <c>&lt;tag-name&gt;:&lt;tag-value&gt;</c>.
/// Tag filters are case-insensitive.
/// </summary>
/// <remarks>
/// The API will only return DNS records that have a tag named <c>&lt;tag-name&gt;</c> whose value starts with <c>&lt;tag-value&gt;</c>.
/// </remarks>
public string? TagStartsWith { get; set; }
#endregion Tag
/// <summary>
/// Whether to match all tag search requirements or at least one (any).
/// </summary>
/// <remarks>
/// <para>
/// If set to <see cref="FilterMatchType.All"/>, acts like a logical AND between filters.
/// <br/>
/// If set to <see cref="FilterMatchType.Any"/>, acts like a logical OR instead.
/// </para>
/// <para>
/// Note that the regular <see cref="Match"/> parameter is still used to combine the resulting condition with other filters that aren't related to tags.
/// </para>
/// </remarks>
public FilterMatchType? TagMatch { get; set; }
/// <summary>
/// Record type.
/// </summary>
public DnsRecordType? Type { get; set; }
/// <inheritdoc />
public IDictionary<string, string> GetQueryParameters()
{
var dict = new Dictionary<string, string>();
#pragma warning disable CS8602, CS8604 // There will be no null value below.
#region Comment
if (!string.IsNullOrWhiteSpace(Comment))
dict.Add("comment", Comment.Trim());
if (CommentAbsent.HasValue && CommentAbsent.Value)
dict.Add("comment.absent", "true");
if (!string.IsNullOrWhiteSpace(CommentContains))
dict.Add("comment.contains", CommentContains.Trim());
if (!string.IsNullOrWhiteSpace(CommentEndsWith))
dict.Add("comment.endswith", CommentEndsWith.Trim());
if (!string.IsNullOrWhiteSpace(CommentExact))
dict.Add("comment.exact", CommentExact.Trim());
if (CommentPresent.HasValue && CommentPresent.Value)
dict.Add("comment.present", "true");
if (!string.IsNullOrWhiteSpace(CommentStartsWith))
dict.Add("comment.startswith", CommentStartsWith.Trim());
#endregion Comment
#region Content
if (!string.IsNullOrWhiteSpace(Content))
dict.Add("content", Content.Trim());
if (!string.IsNullOrWhiteSpace(ContentContains))
dict.Add("content.contains", ContentContains.Trim());
if (!string.IsNullOrWhiteSpace(ContentEndsWith))
dict.Add("content.endswith", ContentEndsWith.Trim());
if (!string.IsNullOrWhiteSpace(ContentExact))
dict.Add("content.exact", ContentExact.Trim());
if (!string.IsNullOrWhiteSpace(ContentStartsWith))
dict.Add("content.startswith", ContentStartsWith.Trim());
#endregion Content
if (Direction.HasValue && Enum.IsDefined(typeof(SortDirection), Direction.Value))
dict.Add("direction", Direction.Value.GetEnumMemberValue());
if (Match.HasValue && Enum.IsDefined(typeof(FilterMatchType), Match.Value))
dict.Add("match", Match.Value.GetEnumMemberValue());
#region Name
if (!string.IsNullOrWhiteSpace(Name))
dict.Add("name", Name.Trim());
if (!string.IsNullOrWhiteSpace(NameContains))
dict.Add("name.contains", NameContains.Trim());
if (!string.IsNullOrWhiteSpace(NameEndsWith))
dict.Add("name.endswith", NameEndsWith.Trim());
if (!string.IsNullOrWhiteSpace(NameExact))
dict.Add("name.exact", NameExact.Trim());
if (!string.IsNullOrWhiteSpace(NameStartsWith))
dict.Add("name.startswith", NameStartsWith.Trim());
#endregion Name
if (OrderBy.HasValue && Enum.IsDefined(typeof(DnsRecordsOrderBy), OrderBy.Value))
dict.Add("order", OrderBy.Value.GetEnumMemberValue());
if (Page.HasValue && Page.Value >= 1)
dict.Add("page", Page.Value.ToString());
if (PerPage.HasValue && PerPage.Value >= 1 && PerPage.Value <= 5_000_000)
dict.Add("per_page", PerPage.Value.ToString());
if (Proxied.HasValue)
dict.Add("proxied", Proxied.Value.ToString().ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(Search))
dict.Add("search", Search.Trim());
#region Tag
if (!string.IsNullOrWhiteSpace(Tag))
dict.Add("tag", Tag.Trim());
if (!string.IsNullOrWhiteSpace(TagAbsent))
dict.Add("tag.absent", TagAbsent.Trim());
if (!string.IsNullOrWhiteSpace(TagContains))
dict.Add("tag.contains", TagContains.Trim());
if (!string.IsNullOrWhiteSpace(TagEndsWith))
dict.Add("tag.endswith", TagEndsWith.Trim());
if (!string.IsNullOrWhiteSpace(TagExact))
dict.Add("tag.exact", TagExact.Trim());
if (!string.IsNullOrWhiteSpace(TagPresent))
dict.Add("tag.present", TagPresent.Trim());
if (!string.IsNullOrWhiteSpace(TagStartsWith))
dict.Add("tag.startswith", TagStartsWith.Trim());
#endregion Tag
if (TagMatch.HasValue && Enum.IsDefined(typeof(FilterMatchType), TagMatch.Value))
dict.Add("tag_match", TagMatch.Value.GetEnumMemberValue());
if (Type.HasValue && Enum.IsDefined(typeof(DnsRecordType), Type.Value))
dict.Add("type", Type.Value.GetEnumMemberValue());
#pragma warning restore CS8602, CS8604
return dict;
}
}
/// <summary>
/// Possible fields to order DNS records by.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum DnsRecordsOrderBy
{
/// <summary>
/// Order by record type.
/// </summary>
[EnumMember(Value = "type")]
Type = 1,
/// <summary>
/// Order by record name.
/// </summary>
[EnumMember(Value = "name")]
Name = 2,
/// <summary>
/// Order by record content.
/// </summary>
[EnumMember(Value = "content")]
Content = 3,
/// <summary>
/// Order by record TTL.
/// </summary>
[EnumMember(Value = "ttl")]
Ttl = 4,
/// <summary>
/// Order by record proxied.
/// </summary>
[EnumMember(Value = "proxied")]
Proxied = 5
}
}

View File

@@ -0,0 +1,23 @@
namespace AMWD.Net.Api.Cloudflare.Dns.Internals
{
internal class InternalBatchRequest
{
[JsonProperty("deletes")]
public IReadOnlyCollection<Identifier>? Deletes { get; set; }
[JsonProperty("patches")]
public IReadOnlyCollection<InternalBatchUpdateRequest>? Patches { get; set; }
[JsonProperty("posts")]
public IReadOnlyCollection<InternalDnsRecordRequest>? Posts { get; set; }
[JsonProperty("puts")]
public IReadOnlyCollection<InternalBatchUpdateRequest>? Puts { get; set; }
}
internal class InternalBatchUpdateRequest : InternalDnsRecordRequest
{
[JsonProperty("id")]
public string? Id { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
namespace AMWD.Net.Api.Cloudflare.Dns.Internals
{
internal class InternalDnsRecordRequest
{
[JsonProperty("comment")]
public string? Comment { get; set; }
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("proxied")]
public bool? Proxied { get; set; }
[JsonProperty("settings")]
public object? Settings { get; set; }
[JsonProperty("tags")]
public IReadOnlyCollection<string>? Tags { get; set; }
[JsonProperty("ttl")]
public int? Ttl { get; set; }
[JsonProperty("content")]
public string? Content { get; set; }
[JsonProperty("type")]
public DnsRecordType Type { get; set; }
[JsonProperty("priority")]
public int? Priority { get; set; }
[JsonProperty("data")]
public object? Data { get; set; }
}
}

View File

@@ -0,0 +1,122 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare.Dns
{
internal class DNSAnalyticsQuery
{
/// <summary>
/// Array of dimension names.
/// </summary>
[JsonProperty("dimensions")]
public IReadOnlyCollection<string>? Dimensions { get; set; }
/// <summary>
/// Limit number of returned metrics.
/// </summary>
[JsonProperty("limit")]
public int? Limit { get; set; }
/// <summary>
/// Array of metric names.
/// </summary>
[JsonProperty("metrics")]
public IReadOnlyCollection<string>? Metrics { get; set; }
/// <summary>
/// Start date and time of requesting data period.
/// </summary>
[JsonProperty("since")]
public DateTime? Since { get; set; }
/// <summary>
/// Unit of time to group data by.
/// </summary>
[JsonProperty("time_delta")]
public TimeDeltaUnit? TimeDelta { get; set; }
[JsonProperty("until")]
public DateTime? Until { get; set; }
/// <summary>
/// Segmentation filter in 'attribute operator value' format.
/// </summary>
[JsonProperty("filters")]
public string? Filters { get; set; }
/// <summary>
/// Array of dimensions to sort by, where each dimension may be prefixed
/// by <c>-</c> (descending) or <c>+</c> (ascending).
/// </summary>
[JsonProperty("sort")]
public IReadOnlyCollection<string>? Sort { get; set; }
}
/// <summary>
/// Time delta units.
/// <see href="https://github.com/cloudflare/cloudflare-typescript/blob/v4.4.1/src/resources/dns/dns.ts#L103">Source</see>
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum TimeDeltaUnit
{
/// <summary>
/// All time.
/// </summary>
[EnumMember(Value = "all")]
All = 1,
/// <summary>
/// Auto.
/// </summary>
[EnumMember(Value = "auto")]
Auto = 2,
/// <summary>
/// Year.
/// </summary>
[EnumMember(Value = "year")]
Year = 3,
/// <summary>
/// Quarter.
/// </summary>
[EnumMember(Value = "quarter")]
Quarter = 4,
/// <summary>
/// Month.
/// </summary>
[EnumMember(Value = "month")]
Month = 5,
/// <summary>
/// Week.
/// </summary>
[EnumMember(Value = "week")]
Week = 6,
/// <summary>
/// Day.
/// </summary>
[EnumMember(Value = "day")]
Day = 7,
/// <summary>
/// Hour.
/// </summary>
[EnumMember(Value = "hour")]
Hour = 8,
/// <summary>
/// Dekaminute.
/// </summary>
[EnumMember(Value = "dekaminute")]
DekaMinute = 9,
/// <summary>
/// Minute.
/// </summary>
[EnumMember(Value = "minute")]
Minute = 10
}
}

View File

@@ -0,0 +1,120 @@
using Newtonsoft.Json.Linq;
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// A DNS record.
/// </summary>
public abstract class DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="DnsRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
protected DnsRecord(string name)
{
Name = name;
}
/// <summary>
/// DNS record name (or @ for the zone apex) in Punycode.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Record type.
/// </summary>
[JsonProperty("type")]
public DnsRecordType Type { get; protected set; }
/// <summary>
/// Comments or notes about the DNS record.
/// This field has no effect on DNS responses.
/// </summary>
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>
/// DNS record content.
/// </summary>
[JsonProperty("content")]
public string? Content { get; set; }
/// <summary>
/// Whether the record is receiving the performance and security benefits of
/// Cloudflare.
/// </summary>
[JsonProperty("proxied")]
public bool? Proxied { get; set; }
/// <summary>
/// Settings for the DNS record.
/// </summary>
[JsonProperty("settings")]
public DnsRecordSettings? Settings { get; set; }
/// <summary>
/// Custom tags for the DNS record.
/// This field has no effect on DNS responses.
/// </summary>
[JsonProperty("tags")]
public IReadOnlyCollection<string>? Tags { get; set; }
/// <summary>
/// Time To Live (TTL) of the DNS record in seconds.
/// </summary>
/// <remarks>
/// Setting to <c>1</c> means 'automatic'. Value must be between <c>60</c> and <c>86400</c>, with the
/// minimum reduced to <c>30</c> for Enterprise zones.
/// </remarks>
[JsonProperty("ttl")]
public int? TimeToLive { get; set; }
#region Response
/// <summary>
/// Identifier.
/// </summary>
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// When the record was created.
/// </summary>
[JsonProperty("created_on")]
public DateTime CreatedOn { get; set; }
/// <summary>
/// Extra Cloudflare-specific information about the record.
/// </summary>
[JsonProperty("meta")]
public JToken? Meta { get; set; }
/// <summary>
/// When the record was last modified.
/// </summary>
[JsonProperty("modified_on")]
public DateTime ModifiedOn { get; set; }
/// <summary>
/// Whether the record can be proxied by Cloudflare or not.
/// </summary>
[JsonProperty("proxiable")]
public bool Proxiable { get; set; }
/// <summary>
/// When the record comment was last modified. Omitted if there is no comment.
/// </summary>
[JsonProperty("comment_modified_on")]
public DateTime? CommentModifiedOn { get; set; }
/// <summary>
/// When the record tags were last modified. Omitted if there are no tags.
/// </summary>
[JsonProperty("tags_modified_on")]
public DateTime? TagsModifiedOn { get; set; }
#endregion Response
}
}

View File

@@ -0,0 +1,26 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// IP Settings for the DNS record.
/// </summary>
public class DnsRecordSettings
{
/// <summary>
/// When enabled, only A records will be generated, and AAAA records will not be
/// created. This setting is intended for exceptional cases. Note that this option
/// only applies to proxied records and it has no effect on whether Cloudflare
/// communicates with the origin using IPv4 or IPv6.
/// </summary>
[JsonProperty("ipv4_only")]
public bool? IPv4Only { get; set; }
/// <summary>
/// When enabled, only AAAA records will be generated, and A records will not be
/// created. This setting is intended for exceptional cases. Note that this option
/// only applies to proxied records and it has no effect on whether Cloudflare
/// communicates with the origin using IPv4 or IPv6.
/// </summary>
[JsonProperty("ipv6_only")]
public bool? IPv6Only { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// IPv6 address record.
/// </summary>
public class AAAARecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="ARecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public AAAARecord(string name)
: base(name)
{
Type = DnsRecordType.AAAA;
}
}
}

View File

@@ -0,0 +1,18 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Address record.
/// </summary>
public class ARecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="ARecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public ARecord(string name)
: base(name)
{
Type = DnsRecordType.A;
}
}
}

View File

@@ -0,0 +1,48 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Certification Authority Authorization record.
/// </summary>
public class CAARecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="CAARecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public CAARecord(string name)
: base(name)
{
Type = DnsRecordType.CAA;
}
/// <summary>
/// Components of a CAA record.
/// </summary>
[JsonProperty("data")]
public CAARecordData? Data { get; set; }
}
/// <summary>
/// Components of a CAA record.
/// </summary>
public class CAARecordData
{
/// <summary>
/// Flags for the CAA record.
/// </summary>
[JsonProperty("flags")]
public int? Flags { get; set; }
/// <summary>
/// Name of the property controlled by this record (e.g.: issue, issuewild, iodef).
/// </summary>
[JsonProperty("tag")]
public string? Tag { get; set; }
/// <summary>
/// Value of the record. This field's semantics depend on the chosen tag.
/// </summary>
[JsonProperty("value")]
public string? Value { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Certificate record.
/// </summary>
public class CERTRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="CERTRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public CERTRecord(string name)
: base(name)
{
Type = DnsRecordType.CERT;
}
/// <summary>
/// Components of a CERT record.
/// </summary>
[JsonProperty("data")]
public CERTRecordData? Data { get; set; }
}
/// <summary>
/// Components of a CERT record.
/// </summary>
public class CERTRecordData
{
/// <summary>
/// Algorithm.
/// </summary>
[JsonProperty("algorithm")]
public int? Algorithm { get; set; }
/// <summary>
/// Certificate.
/// </summary>
[JsonProperty("certificate")]
public string? Certificate { get; set; }
/// <summary>
/// Key tag.
/// </summary>
[JsonProperty("key_tag")]
public int? KeyTag { get; set; }
/// <summary>
/// Type.
/// </summary>
[JsonProperty("type")]
public int? Type { get; set; }
}
}

View File

@@ -0,0 +1,41 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Canonical Name record.
/// </summary>
public class CNAMERecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="CNAMERecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public CNAMERecord(string name)
: base(name)
{
Type = DnsRecordType.CNAME;
}
/// <summary>
/// Settings for the DNS record.
/// </summary>
[JsonProperty("settings")]
public new CNAMERecordSettings? Settings { get; set; }
}
/// <summary>
/// Settings for the DNS record.
/// </summary>
public class CNAMERecordSettings : DnsRecordSettings
{
/// <summary>
/// If enabled, causes the CNAME record to be resolved externally and the resulting
/// address records (e.g., A and AAAA) to be returned instead of the CNAME record
/// itself.
/// </summary>
/// <remarks>
/// This setting is unavailable for proxied records, since they are always flattened.
/// </remarks>
[JsonProperty("flatten_cname")]
public bool? FlattenCname { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// DNS Key record.
/// </summary>
public class DNSKEYRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="DNSKEYRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public DNSKEYRecord(string name)
: base(name)
{
Type = DnsRecordType.DNSKEY;
}
/// <summary>
/// Components of a DNSKEY record.
/// </summary>
[JsonProperty("data")]
public DNSKEYRecordData? Data { get; set; }
}
/// <summary>
/// Components of a DNSKEY record.
/// </summary>
public class DNSKEYRecordData
{
/// <summary>
/// Algorithm.
/// </summary>
[JsonProperty("algorithm")]
public int? Algorithm { get; set; }
/// <summary>
/// Flags.
/// </summary>
[JsonProperty("flags")]
public int? Flags { get; set; }
/// <summary>
/// Protocol.
/// </summary>
[JsonProperty("protocol")]
public int? Protocol { get; set; }
/// <summary>
/// Public key.
/// </summary>
[JsonProperty("public_key")]
public string? PublicKey { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Delegate Signer record.
/// </summary>
public class DSRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="DSRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public DSRecord(string name)
: base(name)
{
Type = DnsRecordType.DS;
}
/// <summary>
/// Components of a DS record.
/// </summary>
[JsonProperty("data")]
public DSRecordData? Data { get; set; }
}
/// <summary>
/// Components of a DS record.
/// </summary>
public class DSRecordData
{
/// <summary>
/// Algorithm.
/// </summary>
[JsonProperty("algorithm")]
public int? Algorithm { get; set; }
/// <summary>
/// Digest.
/// </summary>
[JsonProperty("digest")]
public string? Digest { get; set; }
/// <summary>
/// Digest type.
/// </summary>
[JsonProperty("digest_type")]
public int? DigestType { get; set; }
/// <summary>
/// Key tag.
/// </summary>
[JsonProperty("key_tag")]
public int? KeyTag { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// HTTPS binding record.
/// </summary>
public class HTTPSRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="HTTPSRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public HTTPSRecord(string name)
: base(name)
{
Type = DnsRecordType.HTTPS;
}
/// <summary>
/// Components of a HTTPS record.
/// </summary>
[JsonProperty("data")]
public HTTPSRecordData? Data { get; set; }
}
/// <summary>
/// Components of a HTTPS record.
/// </summary>
public class HTTPSRecordData
{
/// <summary>
/// Priority.
/// </summary>
[JsonProperty("priority")]
public int? Priority { get; set; }
/// <summary>
/// Target.
/// </summary>
[JsonProperty("target")]
public string? Target { get; set; }
/// <summary>
/// Value.
/// </summary>
[JsonProperty("value")]
public string? Value { get; set; }
}
}

View File

@@ -0,0 +1,143 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Converters;
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Location record.
/// </summary>
public class LOCRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="LOCRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public LOCRecord(string name)
: base(name)
{
Type = DnsRecordType.LOC;
}
/// <summary>
/// Components of a LOC record.
/// </summary>
[JsonProperty("data")]
public LOCRecordData? Data { get; set; }
}
/// <summary>
/// Components of a LOC record.
/// </summary>
public class LOCRecordData
{
/// <summary>
/// Altitude of location in meters.
/// </summary>
[JsonProperty("altitude")]
public double? Altitude { get; set; }
/// <summary>
/// Degrees of latitude.
/// </summary>
[JsonProperty("lat_degrees")]
public double? LatitudeDegrees { get; set; }
/// <summary>
/// Latitude direction.
/// </summary>
[JsonProperty("lat_direction")]
public LOCRecordLatitudeDirection? LatitudeDirection { get; set; }
/// <summary>
/// Minutes of latitude.
/// </summary>
[JsonProperty("lat_minutes")]
public double? LatitudeMinutes { get; set; }
/// <summary>
/// Seconds of latitude.
/// </summary>
[JsonProperty("lat_seconds")]
public double? LatitudeSeconds { get; set; }
/// <summary>
/// Degrees of longitude.
/// </summary>
[JsonProperty("long_degrees")]
public double? LongitudeDegrees { get; set; }
/// <summary>
/// Longitude direction.
/// </summary>
[JsonProperty("long_direction")]
public LOCRecordLongitudeDirection? LongitudeDirection { get; set; }
/// <summary>
/// Minutes of longitude.
/// </summary>
[JsonProperty("long_minutes")]
public double? LongitudeMinutes { get; set; }
/// <summary>
/// Seconds of longitude.
/// </summary>
[JsonProperty("long_seconds")]
public double? LongitudeSeconds { get; set; }
/// <summary>
/// Horizontal precision of location.
/// </summary>
[JsonProperty("precision_horz")]
public double? PrecisionHorizontal { get; set; }
/// <summary>
/// Vertical precision of location.
/// </summary>
[JsonProperty("precision_vert")]
public double? PrecisionVertical { get; set; }
/// <summary>
/// Size of location in meters.
/// </summary>
[JsonProperty("size")]
public double? Size { get; set; }
}
/// <summary>
/// Location record latitude direction.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum LOCRecordLatitudeDirection
{
/// <summary>
/// North.
/// </summary>
[EnumMember(Value = "N")]
North = 1,
/// <summary>
/// South.
/// </summary>
[EnumMember(Value = "S")]
South = 2
}
/// <summary>
/// Location record longitude direction.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum LOCRecordLongitudeDirection
{
/// <summary>
/// East.
/// </summary>
[EnumMember(Value = "E")]
East = 1,
/// <summary>
/// West.
/// </summary>
[EnumMember(Value = "W")]
West = 2
}
}

View File

@@ -0,0 +1,25 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Mail Exchange record.
/// </summary>
public class MXRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="MXRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public MXRecord(string name)
: base(name)
{
Type = DnsRecordType.MX;
}
/// <summary>
/// Required for MX, SRV and URI records; unused by other record types.
/// Records with lower priorities are preferred.
/// </summary>
[JsonProperty("priority")]
public int? Priority { get; set; }
}
}

View File

@@ -0,0 +1,66 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Naming authority pointer record.
/// </summary>
public class NAPTRRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="NAPTRRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public NAPTRRecord(string name)
: base(name)
{
Type = DnsRecordType.NAPTR;
}
/// <summary>
/// Components of a NAPTR record.
/// </summary>
[JsonProperty("data")]
public NAPTRRecordData? Data { get; set; }
}
/// <summary>
/// Components of a NAPTR record.
/// </summary>
public class NAPTRRecordData
{
/// <summary>
/// Flags.
/// </summary>
[JsonProperty("flags")]
public string? Flags { get; set; }
/// <summary>
/// Order.
/// </summary>
[JsonProperty("order")]
public int? Order { get; set; }
/// <summary>
/// Preference.
/// </summary>
[JsonProperty("preference")]
public int? Preference { get; set; }
/// <summary>
/// Regular expression.
/// </summary>
[JsonProperty("regex")]
public string? Regex { get; set; }
/// <summary>
/// Replacement.
/// </summary>
[JsonProperty("replacement")]
public string? Replacement { get; set; }
/// <summary>
/// Service.
/// </summary>
[JsonProperty("service")]
public string? Service { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Name Server record.
/// </summary>
public class NSRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="NSRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public NSRecord(string name)
: base(name)
{
Type = DnsRecordType.NS;
}
}
}

View File

@@ -0,0 +1,24 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// OpenPGP Publi Key record.
/// </summary>
public class OPENPGPKEYRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="OPENPGPKEYRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public OPENPGPKEYRecord(string name)
: base(name)
{
Type = DnsRecordType.OPENPGPKEY;
}
/// <summary>
/// A single Base64-encoded OpenPGP Transferable Public Key (RFC 4880 Section 11.1).
/// </summary>
[JsonProperty("content")]
public new string? Content { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// PTR resource record.
/// </summary>
public class PTRRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="PTRRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public PTRRecord(string name)
: base(name)
{
Type = DnsRecordType.PTR;
}
}
}

View File

@@ -0,0 +1,54 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// S/MIME cert association record.
/// </summary>
public class SMIMEARecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="SMIMEARecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public SMIMEARecord(string name)
: base(name)
{
Type = DnsRecordType.SMIMEA;
}
/// <summary>
/// Components of a SMIMEA record.
/// </summary>
[JsonProperty("data")]
public SMIMEARecordData? Data { get; set; }
}
/// <summary>
/// Components of a SMIMEA record.
/// </summary>
public class SMIMEARecordData
{
/// <summary>
/// Certificate.
/// </summary>
[JsonProperty("certificate")]
public string? Certificate { get; set; }
/// <summary>
/// Matching type.
/// </summary>
[JsonProperty("matching_type")]
public int? MatchingType { get; set; }
/// <summary>
/// Selector.
/// </summary>
[JsonProperty("selector")]
public int? Selector { get; set; }
/// <summary>
/// Usage.
/// </summary>
[JsonProperty("usage")]
public int? Usage { get; set; }
}
}

View File

@@ -0,0 +1,55 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Service locator record.
/// </summary>
public class SRVRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="SRVRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public SRVRecord(string name)
: base(name)
{
Type = DnsRecordType.SRV;
}
/// <summary>
/// Components of a SRV record.
/// </summary>
[JsonProperty("data")]
public SRVRecordData? Data { get; set; }
}
/// <summary>
/// Components of a SRV record.
/// </summary>
public class SRVRecordData
{
/// <summary>
/// The port of the service.
/// </summary>
[JsonProperty("port")]
public int? Port { get; set; }
/// <summary>
/// Required for MX, SRV and URI records; unused by other record types.
/// Records with lower priorities are preferred.
/// </summary>
[JsonProperty("priority")]
public int? Priority { get; set; }
/// <summary>
/// A valid hostname.
/// </summary>
[JsonProperty("target")]
public string? Target { get; set; }
/// <summary>
/// The record weight.
/// </summary>
[JsonProperty("weight")]
public int? Weight { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// SSH Public Key Fingerprint record.
/// </summary>
public class SSHFPRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="SSHFPRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public SSHFPRecord(string name)
: base(name)
{
Type = DnsRecordType.SSHFP;
}
/// <summary>
/// Components of a SSHFP record.
/// </summary>
[JsonProperty("data")]
public SSHFPRecordData? Data { get; set; }
}
/// <summary>
/// Components of a SSHFP record.
/// </summary>
public class SSHFPRecordData
{
/// <summary>
/// Algorithm.
/// </summary>
[JsonProperty("algorithm")]
public int? Algorithm { get; set; }
/// <summary>
/// Fingerprint.
/// </summary>
[JsonProperty("fingerprint")]
public string? Fingerprint { get; set; }
/// <summary>
/// Type.
/// </summary>
[JsonProperty("type")]
public int? Type { get; set; }
}
}

View File

@@ -0,0 +1,48 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Service Binding record.
/// </summary>
public class SVCBRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="SVCBRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public SVCBRecord(string name)
: base(name)
{
Type = DnsRecordType.SVCB;
}
/// <summary>
/// Components of a SVCB record.
/// </summary>
[JsonProperty("data")]
public SVCBRecordData? Data { get; set; }
}
/// <summary>
/// Components of a SVCB record.
/// </summary>
public class SVCBRecordData
{
/// <summary>
/// Priority.
/// </summary>
[JsonProperty("priority")]
public int? Priority { get; set; }
/// <summary>
/// Target.
/// </summary>
[JsonProperty("target")]
public string? Target { get; set; }
/// <summary>
/// Value.
/// </summary>
[JsonProperty("value")]
public string? Value { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// TLSA certificate association record.
/// </summary>
public class TLSARecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="TLSARecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public TLSARecord(string name)
: base(name)
{
Type = DnsRecordType.TLSA;
}
/// <summary>
/// Components of a TLSA record.
/// </summary>
[JsonProperty("data")]
public TLSARecordData? Data { get; set; }
}
/// <summary>
/// Components of a TLSA record.
/// </summary>
public class TLSARecordData
{
/// <summary>
/// Certificate.
/// </summary>
[JsonProperty("certificate")]
public string? Certificate { get; set; }
/// <summary>
/// Matching type.
/// </summary>
[JsonProperty("matching_type")]
public int? MatchingType { get; set; }
/// <summary>
/// Selector.
/// </summary>
[JsonProperty("selector")]
public int? Selector { get; set; }
/// <summary>
/// Usage.
/// </summary>
[JsonProperty("usage")]
public int? Usage { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Text record.
/// </summary>
public class TXTRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="TXTRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public TXTRecord(string name)
: base(name)
{
Type = DnsRecordType.TXT;
}
/// <summary>
/// Text content for the record. The content must consist of quoted "character
/// strings" (RFC 1035), each with a length of up to 255 bytes. Strings exceeding
/// this allowed maximum length are automatically split.
/// </summary>
/// <remarks>
/// Learn more at <see href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/"/>.
/// </remarks>
[JsonProperty("content")]
public new string? Content { get; set; }
}
}

View File

@@ -0,0 +1,49 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Uniform Resource Identifier record.
/// </summary>
public class URIRecord : DnsRecord
{
/// <summary>
/// Initializes a new instance of the <see cref="URIRecord"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public URIRecord(string name)
: base(name)
{
Type = DnsRecordType.URI;
}
/// <summary>
/// Required for MX, SRV and URI records; unused by other record types.
/// Records with lower priorities are preferred.
/// </summary>
[JsonProperty("priority")]
public int? Priority { get; set; }
/// <summary>
/// Components of a URI record.
/// </summary>
[JsonProperty("data")]
public URIRecordData? Data { get; set; }
}
/// <summary>
/// Components of a URI record.
/// </summary>
public class URIRecordData
{
/// <summary>
/// The record content.
/// </summary>
[JsonProperty("target")]
public string? Target { get; set; }
/// <summary>
/// The record weight.
/// </summary>
[JsonProperty("weight")]
public int? Weight { get; set; }
}
}

View File

@@ -11,6 +11,21 @@ This package contains the feature set of the _DNS_ section of the Cloudflare API
- [List Account Custom Nameservers](https://developers.cloudflare.com/api/resources/custom_nameservers/methods/get/)
### [DNS]
#### [Records]
- [Batch DNS Records](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/batch/)
- [Create DNS Record](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/)
- [Delete DNS Record](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/delete/)
- [Update DNS Record](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/)
- [Export DNS Records](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/export/)
- [DNS Record Details](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/get/)
- [Import DNS Records](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/import/)
- [List DNS Records](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/list/)
- [Scan DNS Records](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/scan/)
- [Overwrite DNS Record](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/update/)
---
@@ -21,3 +36,6 @@ Published under MIT License (see [choose a license])
[choose a license]: https://choosealicense.com/licenses/mit/
[Account Custom Nameservers]: https://developers.cloudflare.com/api/resources/custom_nameservers/
[DNS]: https://developers.cloudflare.com/api/resources/dns/
[Records]: https://developers.cloudflare.com/api/resources/dns/subresources/records/

View File

@@ -0,0 +1,159 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Represents a request to manipulate DNS records.
/// </summary>
public class BatchDnsRecordsRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="BatchDnsRecordsRequest"/> class.
/// </summary>
/// <param name="zoneId">The zone identifier.</param>
public BatchDnsRecordsRequest(string zoneId)
{
ZoneId = zoneId;
}
/// <summary>
/// The zone identifier.
/// </summary>
public string ZoneId { get; set; }
/// <summary>
/// The DNS records to delete.
/// </summary>
public IReadOnlyCollection<string>? Deletes { get; set; }
/// <summary>
/// The DNS records to update.
/// </summary>
public IReadOnlyCollection<Patch>? Updates { get; set; }
/// <summary>
/// The DNS records to create.
/// </summary>
public IReadOnlyCollection<Post>? Creates { get; set; }
/// <summary>
/// The DNS records to overwrite.
/// </summary>
public IReadOnlyCollection<Put>? Overwrites { get; set; }
/// <summary>
/// Represents a request to update a DNS record.
/// </summary>
public class Patch : Post
{
/// <summary>
/// Initializes a new instance of the <see cref="Patch"/> class.
/// </summary>
/// <param name="id">The DNS record identifier.</param>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public Patch(string id, string name)
: base(name)
{
Id = id;
}
/// <summary>
/// The DNS record identifier.
/// </summary>
public string Id { get; set; }
}
/// <summary>
/// Represents a request to create a DNS record.
/// </summary>
public class Post
{
/// <summary>
/// Initializes a new instance of the <see cref="Post"/> class.
/// </summary>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public Post(string name)
{
Name = name;
}
/// <summary>
/// DNS record name (or @ for the zone apex) in Punycode.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Time To Live (TTL) of the DNS record in seconds.
/// </summary>
/// <remarks>
/// Setting to <c>1</c> means 'automatic'. Value must be between <c>60</c> and <c>86400</c>, with the
/// minimum reduced to <c>30</c> for Enterprise zones.
/// </remarks>
public int? TimeToLive { get; set; }
/// <summary>
/// DNS record type.
/// </summary>
public DnsRecordType Type { get; set; }
/// <summary>
/// Comments or notes about the DNS record.
/// This field has no effect on DNS responses.
/// </summary>
public string? Comment { get; set; }
/// <summary>
/// The content of the DNS record.
/// </summary>
public string? Content { get; set; }
/// <summary>
/// Components of a record.
/// </summary>
public object? Data { get; set; }
/// <summary>
/// Required for MX, SRV and URI records; unused by other record types.
/// Records with lower priorities are preferred.
/// </summary>
public int? Priority { get; set; }
/// <summary>
/// Whether the record is receiving the performance and security benefits of
/// Cloudflare.
/// </summary>
public bool? Proxied { get; set; }
/// <summary>
/// Settings for the DNS record.
/// </summary>
public DnsRecordSettings? Settings { get; set; }
/// <summary>
/// Custom tags for the DNS record.
/// This field has no effect on DNS responses.
/// </summary>
public IReadOnlyCollection<string>? Tags { get; set; }
}
/// <summary>
/// Represents a request to overwrite a DNS record.
/// </summary>
public class Put : Post
{
/// <summary>
/// Initializes a new instance of the <see cref="Put"/> class.
/// </summary>
/// <param name="id">The DNS record identifier.</param>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public Put(string id, string name)
: base(name)
{
Id = id;
}
/// <summary>
/// The DNS record identifier.
/// </summary>
public string Id { get; set; }
}
}
}

View File

@@ -0,0 +1,82 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Represents a request to create a DNS record.
/// </summary>
public class CreateDnsRecordRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="CreateDnsRecordRequest"/> class.
/// </summary>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public CreateDnsRecordRequest(string zoneId, string name)
{
ZoneId = zoneId;
Name = name;
}
/// <summary>
/// The zone identifier.
/// </summary>
public string ZoneId { get; set; }
/// <summary>
/// DNS record name (or @ for the zone apex) in Punycode.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Time To Live (TTL) of the DNS record in seconds.
/// </summary>
/// <remarks>
/// Setting to <c>1</c> means 'automatic'. Value must be between <c>60</c> and <c>86400</c>, with the
/// minimum reduced to <c>30</c> for Enterprise zones.
/// </remarks>
public int? TimeToLive { get; set; }
/// <summary>
/// DNS record type.
/// </summary>
public DnsRecordType Type { get; set; }
/// <summary>
/// Comments or notes about the DNS record.
/// This field has no effect on DNS responses.
/// </summary>
public string? Comment { get; set; }
/// <summary>
/// The content of the DNS record.
/// </summary>
public string? Content { get; set; }
/// <summary>
/// Components of a record.
/// </summary>
public object? Data { get; set; }
/// <summary>
/// Required for MX, SRV and URI records; unused by other record types.
/// Records with lower priorities are preferred.
/// </summary>
public int? Priority { get; set; }
/// <summary>
/// Whether the record is receiving the performance and security benefits of
/// Cloudflare.
/// </summary>
public bool? Proxied { get; set; }
/// <summary>
/// Settings for the DNS record.
/// </summary>
public DnsRecordSettings? Settings { get; set; }
/// <summary>
/// Custom tags for the DNS record.
/// This field has no effect on DNS responses.
/// </summary>
public IReadOnlyCollection<string>? Tags { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Represents a request to delete a DNS record.
/// </summary>
public class DeleteDnsRecordRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="DeleteDnsRecordRequest"/> class.
/// </summary>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="recordId">The DNS record identifier.</param>
public DeleteDnsRecordRequest(string zoneId, string recordId)
{
ZoneId = zoneId;
RecordId = recordId;
}
/// <summary>
/// The zone identifier.
/// </summary>
public string ZoneId { get; set; }
/// <summary>
/// The DNS record identifier.
/// </summary>
public string RecordId { get; set; }
}
}

View File

@@ -0,0 +1,33 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Represents a request to import DNS records.
/// </summary>
public class ImportDnsRecordsRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="ImportDnsRecordsRequest"/> class.
/// </summary>
/// <param name="zoneId">The zone identifier.</param>
public ImportDnsRecordsRequest(string zoneId)
{
ZoneId = zoneId;
}
/// <summary>
/// The zone identifier.
/// </summary>
public string ZoneId { get; set; }
/// <summary>
/// BIND config to import.
/// </summary>
public string? File { get; set; }
/// <summary>
/// Whether or not proxiable records should receive the performance and
/// security benefits of Cloudflare.
/// </summary>
public bool? Proxied { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Represents a request to overwrite a DNS record.
/// </summary>
public class OverwriteDnsRecordRequest : CreateDnsRecordRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="OverwriteDnsRecordRequest"/> class.
/// </summary>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="recordId">The DNS record identifier.</param>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public OverwriteDnsRecordRequest(string zoneId, string recordId, string name)
: base(zoneId, name)
{
RecordId = recordId;
}
/// <summary>
/// The DNS record identifier.
/// </summary>
public string RecordId { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// Represents a request to update a DNS record.
/// </summary>
public class UpdateDnsRecordRequest : CreateDnsRecordRequest
{
/// <summary>
/// Initializes a new instance of the <see cref="UpdateDnsRecordRequest"/> class.
/// </summary>
/// <param name="zoneId">The zone identifier.</param>
/// <param name="recordId">The DNS record identifier.</param>
/// <param name="name">DNS record name (or @ for the zone apex) in Punycode.</param>
public UpdateDnsRecordRequest(string zoneId, string recordId, string name)
: base(zoneId, name)
{
RecordId = recordId;
}
/// <summary>
/// The DNS record identifier.
/// </summary>
public string RecordId { get; set; }
}
}

View File

@@ -0,0 +1,32 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// The response for a batch update request.
/// </summary>
public class BatchDnsRecordsResponse
{
/// <summary>
/// The records that were deleted (DELETE).
/// </summary>
[JsonProperty("deletes")]
public IReadOnlyCollection<DnsRecord>? Deletes { get; set; }
/// <summary>
/// The records that were updated (PATCH).
/// </summary>
[JsonProperty("patches")]
public IReadOnlyCollection<DnsRecord>? Updates { get; set; }
/// <summary>
/// The records that were created (POST).
/// </summary>
[JsonProperty("posts")]
public IReadOnlyCollection<DnsRecord>? Creates { get; set; }
/// <summary>
/// The records that were overwritten (PUT).
/// </summary>
[JsonProperty("puts")]
public IReadOnlyCollection<DnsRecord>? Overwrites { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// The response for a DNS record import.
/// </summary>
public class RecordImportResponse
{
/// <summary>
/// Number of DNS records added.
/// </summary>
[JsonProperty("recs_added")]
public int? RecordsAdded { get; set; }
/// <summary>
/// Total number of DNS records parsed.
/// </summary>
[JsonProperty("total_records_parsed")]
public int? TotalRecordsParsed { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
namespace AMWD.Net.Api.Cloudflare.Dns
{
/// <summary>
/// The response for a DNS record scan.
/// </summary>
public class RecordScanResponse
{
/// <summary>
/// Number of DNS records added.
/// </summary>
[JsonProperty("recs_added")]
public int? RecordsAdded { get; set; }
/// <summary>
/// Total number of DNS records parsed.
/// </summary>
[JsonProperty("total_records_parsed")]
public int? TotalRecordsParsed { get; set; }
}
}

View File

@@ -20,12 +20,12 @@ This package contains the feature set of the _Domain/Zone Management_ section of
- [List Zones](https://developers.cloudflare.com/api/resources/zones/methods/list/)
##### [Activation Check]
#### [Activation Check]
- [Rerun The Activation Check](https://developers.cloudflare.com/api/resources/zones/subresources/activation_check/methods/trigger/)
##### [Holds]
#### [Holds]
- [Create Zone Hold](https://developers.cloudflare.com/api/resources/zones/subresources/holds/methods/create/)
- [Remove Zone Hold](https://developers.cloudflare.com/api/resources/zones/subresources/holds/methods/delete/)
@@ -33,18 +33,18 @@ This package contains the feature set of the _Domain/Zone Management_ section of
- [Get Zone Hold](https://developers.cloudflare.com/api/resources/zones/subresources/holds/methods/get/)
##### [Plans]
#### [Plans]
- [Available Plan Details](https://developers.cloudflare.com/api/resources/zones/subresources/plans/methods/get/)
- [List Available Plans](https://developers.cloudflare.com/api/resources/zones/subresources/plans/methods/list/)
##### [Rate Plans]
#### [Rate Plans]
- [List Available Rate Plans](https://developers.cloudflare.com/api/resources/zones/subresources/rate_plans/methods/get/)
##### [Settings]
#### [Settings]
- **DEPRECATED** [Edit Multiple Zone Settings](https://developers.cloudflare.com/api/resources/zones/subresources/settings/methods/bulk_edit/)
- [Edit Zone Setting](https://developers.cloudflare.com/api/resources/zones/subresources/settings/methods/edit/)
@@ -52,7 +52,7 @@ This package contains the feature set of the _Domain/Zone Management_ section of
- **DEPRECATED** [Get All Zone Settings](https://developers.cloudflare.com/api/resources/zones/subresources/settings/methods/list/)
##### [Subscriptions]
#### [Subscriptions]
- [Create Zone Subscription](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/create/)
- [Zone Subscription Details](https://developers.cloudflare.com/api/resources/zones/subresources/subscriptions/methods/get/)