diff --git a/GuardianClient/GuardianClient.Tests/GetItemAsyncTests.cs b/GuardianClient/GuardianClient.Tests/GetItemAsyncTests.cs
new file mode 100644
index 0000000..d34547c
--- /dev/null
+++ b/GuardianClient/GuardianClient.Tests/GetItemAsyncTests.cs
@@ -0,0 +1,245 @@
+using GuardianClient.Options.Search;
+using Shouldly;
+
+namespace GuardianClient.Tests;
+
+[TestClass]
+public class GetItemAsyncTests : TestBase
+{
+ [TestMethod]
+ public async Task GetItemAsync_WithValidId_ReturnsItem()
+ {
+ // First get a valid item ID from search
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "technology",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 }
+ });
+ searchResult.ShouldNotBeNull("Search should return results");
+ searchResult.Results.Count.ShouldBe(1, "Should return exactly one result");
+
+ var contentItem = searchResult.Results.First();
+ var itemId = contentItem.Id;
+
+ // Now get the specific item
+ var singleItemResult = await ApiClient.GetItemAsync(itemId,
+ new GuardianApiContentAdditionalInformationOptions { ShowFields = [GuardianApiContentShowFieldsOption.Body] });
+
+ singleItemResult.ShouldNotBeNull("GetItem result should not be null");
+ singleItemResult.Status.ShouldBe("ok", "API response status should be 'ok'");
+ singleItemResult.Content.ShouldNotBeNull("Content should not be null");
+ singleItemResult.Content.Id.ShouldBe(itemId, "Returned content ID should match requested ID");
+ singleItemResult.Content.WebTitle.ShouldNotBeNullOrEmpty("Content should have a title");
+ singleItemResult.Content.Fields.ShouldNotBeNull("Fields should be populated when ShowFields is specified");
+
+ Console.WriteLine($"Retrieved item: {singleItemResult.Content.WebTitle}");
+ Console.WriteLine($"Item ID: {singleItemResult.Content.Id}");
+ Console.WriteLine($"Published: {singleItemResult.Content.WebPublicationDate}");
+
+ if (!string.IsNullOrEmpty(singleItemResult.Content.Fields.Body))
+ {
+ Console.WriteLine($"Body length: {singleItemResult.Content.Fields.Body.Length} characters");
+ Console.WriteLine(
+ $"Body preview: {singleItemResult.Content.Fields.Body[..Math.Min(200, singleItemResult.Content.Fields.Body.Length)]}...");
+ }
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithInvalidId_ThrowsException()
+ {
+ var invalidId = "invalid/article/id/that/does/not/exist";
+
+ var exception = await Should.ThrowAsync(async () =>
+ {
+ await ApiClient.GetItemAsync(invalidId);
+ });
+
+ Console.WriteLine($"Expected exception for invalid ID: {exception.Message}");
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithNullId_ThrowsArgumentException()
+ {
+ var exception = await Should.ThrowAsync(async () =>
+ {
+ await ApiClient.GetItemAsync(null!);
+ });
+
+ Console.WriteLine($"Expected exception for null ID: {exception.Message}");
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithEmptyId_ThrowsArgumentException()
+ {
+ var exception = await Should.ThrowAsync(async () =>
+ {
+ await ApiClient.GetItemAsync("");
+ });
+
+ Console.WriteLine($"Expected exception for empty ID: {exception.Message}");
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithShowFields_ReturnsEnhancedContent()
+ {
+ // Get a valid item ID
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "politics",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 }
+ });
+
+ var itemId = searchResult.Results.First().Id;
+
+ // Request with multiple fields
+ var result = await ApiClient.GetItemAsync(itemId,
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields =
+ [
+ GuardianApiContentShowFieldsOption.Headline,
+ GuardianApiContentShowFieldsOption.Body,
+ GuardianApiContentShowFieldsOption.Byline,
+ GuardianApiContentShowFieldsOption.Thumbnail
+ ]
+ });
+
+ result.ShouldNotBeNull();
+ result.Content.Fields.ShouldNotBeNull("Fields should be populated");
+ result.Content.Fields.Headline.ShouldNotBeNullOrEmpty("Headline should be populated");
+
+ Console.WriteLine($"Enhanced content for: {result.Content.WebTitle}");
+ Console.WriteLine($"Headline: {result.Content.Fields.Headline}");
+ Console.WriteLine($"Has body: {!string.IsNullOrEmpty(result.Content.Fields.Body)}");
+ Console.WriteLine($"Has byline: {!string.IsNullOrEmpty(result.Content.Fields.Byline)}");
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithShowTags_ReturnsContentWithTags()
+ {
+ // Get a valid item ID
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "sport",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 }
+ });
+
+ var itemId = searchResult.Results.First().Id;
+
+ // Request with tags
+ var result = await ApiClient.GetItemAsync(itemId,
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowTags = [GuardianApiContentShowTagsOption.Keyword, GuardianApiContentShowTagsOption.Tone, GuardianApiContentShowTagsOption.Type]
+ });
+
+ result.ShouldNotBeNull();
+ result.Content.Tags.ShouldNotBeNull("Tags should be populated");
+ result.Content.Tags.Count.ShouldBeGreaterThan(0, "Should have at least one tag");
+
+ Console.WriteLine($"Content '{result.Content.WebTitle}' has {result.Content.Tags.Count} tags:");
+ foreach (var tag in result.Content.Tags.Take(5)) // Show first 5 tags
+ {
+ Console.WriteLine($" - {tag.WebTitle} ({tag.Type})");
+ }
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithShowElements_ReturnsContentWithElements()
+ {
+ // Get a valid item ID
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "music",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 }
+ });
+
+ var itemId = searchResult.Results.First().Id;
+
+ // Request with elements
+ var result = await ApiClient.GetItemAsync(itemId,
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowElements = [GuardianApiContentShowElementsOption.Image, GuardianApiContentShowElementsOption.Video]
+ });
+
+ result.ShouldNotBeNull();
+ // Elements might be null if the article has no media, so we don't assert their presence
+
+ Console.WriteLine($"Content '{result.Content.WebTitle}' elements:");
+ if (result.Content.Elements != null)
+ {
+ Console.WriteLine($" Has {result.Content.Elements.Count} media elements");
+ }
+ else
+ {
+ Console.WriteLine(" No media elements found");
+ }
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithShowBlocks_ReturnsContentWithBlocks()
+ {
+ // Get a valid item ID
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "news",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 }
+ });
+
+ var itemId = searchResult.Results.First().Id;
+
+ // Request with blocks using string array (as it's still string-based)
+ var result = await ApiClient.GetItemAsync(itemId,
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowBlocks = ["main", "body"]
+ });
+
+ result.ShouldNotBeNull();
+ // Blocks might be null depending on content type
+
+ Console.WriteLine($"Content '{result.Content.WebTitle}' blocks:");
+ if (result.Content.Blocks != null)
+ {
+ Console.WriteLine($" Requested main and body blocks");
+ }
+ else
+ {
+ Console.WriteLine(" No blocks found");
+ }
+ }
+
+ [TestMethod]
+ public async Task GetItemAsync_WithAllOptions_ReturnsFullyEnhancedContent()
+ {
+ // Get a valid item ID
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "culture",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 }
+ });
+
+ var itemId = searchResult.Results.First().Id;
+
+ // Request with all enhancement options
+ var result = await ApiClient.GetItemAsync(itemId,
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields = [GuardianApiContentShowFieldsOption.All],
+ ShowTags = [GuardianApiContentShowTagsOption.All],
+ ShowElements = [GuardianApiContentShowElementsOption.All],
+ ShowBlocks = ["all"]
+ });
+
+ result.ShouldNotBeNull();
+ result.Content.Fields.ShouldNotBeNull("All fields should be populated");
+ result.Content.Tags.ShouldNotBeNull("All tags should be populated");
+
+ Console.WriteLine($"Fully enhanced content: {result.Content.WebTitle}");
+ Console.WriteLine($" Fields populated: Yes");
+ Console.WriteLine($" Tags count: {result.Content.Tags.Count}");
+ Console.WriteLine($" Elements: {(result.Content.Elements?.Count ?? 0)}");
+ Console.WriteLine($" Has blocks: {result.Content.Blocks != null}");
+ }
+}
diff --git a/GuardianClient/GuardianClient.Tests/GuardianApiClientIntegrationTests.cs b/GuardianClient/GuardianClient.Tests/GuardianApiClientIntegrationTests.cs
new file mode 100644
index 0000000..22bcba9
--- /dev/null
+++ b/GuardianClient/GuardianClient.Tests/GuardianApiClientIntegrationTests.cs
@@ -0,0 +1,172 @@
+using GuardianClient.Options.Search;
+using Shouldly;
+
+namespace GuardianClient.Tests;
+
+[TestClass]
+public class GuardianApiClientIntegrationTests : TestBase
+{
+ [TestMethod]
+ public void ApiClient_ShouldNotBeNull()
+ {
+ ApiClient.ShouldNotBeNull("API client should be properly initialized");
+ ApiClient.ShouldBeAssignableTo("API client should implement the interface");
+ }
+
+ [TestMethod]
+ public async Task EndToEnd_SearchAndGetItem_WorksTogether()
+ {
+ // Search for content
+ var searchResult = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "artificial intelligence",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 },
+ FilterOptions = new GuardianApiContentFilterOptions
+ {
+ Section = "technology"
+ }
+ });
+
+ searchResult.ShouldNotBeNull("Search should return results");
+ searchResult.Results.Count.ShouldBe(1, "Should return exactly one result");
+
+ // Get the specific item with enhanced information
+ var contentItem = searchResult.Results.First();
+ var detailedResult = await ApiClient.GetItemAsync(contentItem.Id,
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields = [GuardianApiContentShowFieldsOption.Headline, GuardianApiContentShowFieldsOption.Body],
+ ShowTags = [GuardianApiContentShowTagsOption.Keyword]
+ });
+
+ detailedResult.ShouldNotBeNull("Detailed result should not be null");
+ detailedResult.Content!.Id.ShouldBe(contentItem.Id, "IDs should match");
+ detailedResult.Content.WebTitle.ShouldBe(contentItem.WebTitle, "Titles should match");
+ detailedResult.Content.Fields.ShouldNotBeNull("Detailed fields should be populated");
+ detailedResult.Content.Tags.ShouldNotBeNull("Tags should be populated");
+
+ Console.WriteLine("=== END-TO-END TEST RESULTS ===");
+ Console.WriteLine();
+ Console.WriteLine($"SEARCH RESULT:");
+ Console.WriteLine($" Title: {contentItem.WebTitle}");
+ Console.WriteLine($" Section: {contentItem.SectionName} ({contentItem.SectionId})");
+ Console.WriteLine($" Published: {contentItem.WebPublicationDate}");
+ Console.WriteLine($" URL: {contentItem.WebUrl}");
+ Console.WriteLine();
+
+ Console.WriteLine($"DETAILED CONTENT:");
+ Console.WriteLine($" ID: {detailedResult.Content.Id}");
+ Console.WriteLine($" Headline: {detailedResult.Content.Fields.Headline ?? "N/A"}");
+ Console.WriteLine($" Tags: {detailedResult.Content.Tags.Count} tags");
+
+ if (detailedResult.Content.Tags.Any())
+ {
+ Console.WriteLine($" Tag List:");
+ foreach (var tag in detailedResult.Content.Tags.Take(10)) // Show first 10 tags
+ {
+ Console.WriteLine($" - {tag.WebTitle} ({tag.Type})");
+ }
+ if (detailedResult.Content.Tags.Count > 10)
+ {
+ Console.WriteLine($" ... and {detailedResult.Content.Tags.Count - 10} more tags");
+ }
+ }
+
+ Console.WriteLine();
+ Console.WriteLine($"FULL ARTICLE BODY:");
+ Console.WriteLine("==================");
+ if (!string.IsNullOrEmpty(detailedResult.Content.Fields.Body))
+ {
+ Console.WriteLine(detailedResult.Content.Fields.Body);
+ }
+ else
+ {
+ Console.WriteLine("No body content available");
+ }
+ Console.WriteLine("==================");
+ Console.WriteLine();
+ }
+
+ [TestMethod]
+ public async Task SearchWithComplexOptions_ReturnsExpectedResults()
+ {
+ // Test a complex search with multiple option types
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "climate change",
+ QueryFields = ["body", "headline"],
+ FilterOptions = new GuardianApiContentFilterOptions
+ {
+ Section = "environment"
+ },
+ DateOptions = new GuardianApiContentDateOptions
+ {
+ FromDate = new DateOnly(2023, 1, 1)
+ },
+ PageOptions = new GuardianApiContentPageOptions
+ {
+ Page = 1,
+ PageSize = 5
+ },
+ OrderOptions = new GuardianApiContentOrderOptions
+ {
+ OrderBy = GuardianApiContentOrderBy.Relevance
+ },
+ AdditionalInformationOptions = new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields = [GuardianApiContentShowFieldsOption.Headline, GuardianApiContentShowFieldsOption.Score],
+ ShowTags = [GuardianApiContentShowTagsOption.Tone]
+ }
+ });
+
+ result.ShouldNotBeNull("Complex search should return results");
+ result.Status.ShouldBe("ok", "API should respond successfully");
+ result.Results.Count.ShouldBeLessThanOrEqualTo(5, "Should respect page size");
+
+ // Verify enhanced data is present
+ if (result.Results.Any())
+ {
+ var firstItem = result.Results.First();
+ firstItem.Fields.ShouldNotBeNull("Enhanced fields should be present");
+ firstItem.Tags.ShouldNotBeNull("Tags should be present");
+
+ Console.WriteLine($"Complex search returned {result.Results.Count} results");
+ Console.WriteLine($"First result: {firstItem.WebTitle}");
+ Console.WriteLine($"Relevance score: {firstItem.Fields.Score ?? "N/A"}");
+ }
+ }
+
+ [TestMethod]
+ public async Task TypeSafetyTest_EnumsMapToCorrectApiValues()
+ {
+ // This test ensures our enums map to the correct API values
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "test",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 1 },
+ AdditionalInformationOptions = new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields =
+ [
+ GuardianApiContentShowFieldsOption.Headline,
+ GuardianApiContentShowFieldsOption.TrailText,
+ GuardianApiContentShowFieldsOption.ShowInRelatedContent
+ ],
+ ShowTags = [GuardianApiContentShowTagsOption.Tone, GuardianApiContentShowTagsOption.Type],
+ ShowElements = [GuardianApiContentShowElementsOption.Image]
+ }
+ });
+
+ result.ShouldNotBeNull("Type safety test should work");
+ result.Status.ShouldBe("ok", "API should accept enum-mapped values");
+
+ if (result.Results.Any())
+ {
+ var item = result.Results.First();
+ Console.WriteLine($"Type safety test passed for: {item.WebTitle}");
+ Console.WriteLine($" Fields populated: {item.Fields != null}");
+ Console.WriteLine($" Tags populated: {item.Tags != null}");
+ Console.WriteLine($" Elements populated: {item.Elements != null}");
+ }
+ }
+}
diff --git a/GuardianClient/GuardianClient.Tests/GuardianApiClientTests.cs b/GuardianClient/GuardianClient.Tests/GuardianApiClientTests.cs
deleted file mode 100644
index 357e7c0..0000000
--- a/GuardianClient/GuardianClient.Tests/GuardianApiClientTests.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-namespace GuardianClient.Tests;
-
-using Shouldly;
-
-[TestClass]
-public class GuardianApiClientTests : TestBase
-{
- [TestMethod]
- public async Task SearchAsyncSmokeTest()
- {
- var result = await ApiClient.SearchAsync("climate change", pageSize: 5);
-
- result.ShouldNotBeNull("Search result should not be null");
- result.Status.ShouldBe("ok", "API response status should be 'ok'");
- result.Results.Count.ShouldBeGreaterThan(0, "Should return at least one result");
- result.Results.Count.ShouldBeLessThanOrEqualTo(5, "Should not return more than requested page size");
-
- var firstItem = result.Results.First();
- firstItem.Id.ShouldNotBeNullOrEmpty("Content item should have an ID");
- firstItem.WebTitle.ShouldNotBeNullOrEmpty("Content item should have a title");
- firstItem.WebUrl.ShouldNotBeNullOrEmpty("Content item should have a web URL");
- firstItem.ApiUrl.ShouldNotBeNullOrEmpty("Content item should have an API URL");
-
- Console.WriteLine($"Found {result.Results.Count} articles about climate change");
- Console.WriteLine($"First article: {firstItem.WebTitle}");
- Console.WriteLine($"Published: {firstItem.WebPublicationDate}");
- }
-
- [TestMethod]
- public async Task SearchAsyncWithNoResults()
- {
- var result = await ApiClient.SearchAsync("xyzabc123nonexistentquery456");
-
- result.ShouldNotBeNull("Search result should not be null even with no matches");
- result.Status.ShouldBe("ok", "API response status should be 'ok'");
- result.Results.Count.ShouldBe(0, "Should return zero results for non-existent query");
- }
-
- [TestMethod]
- public async Task GetItemAsyncSmokeTest()
- {
- var searchResult = await ApiClient.SearchAsync("technology", pageSize: 1);
- searchResult.ShouldNotBeNull("Search should return results");
- searchResult.Results.Count.ShouldBe(1, "Should return exactly one result");
-
- var contentItem = searchResult.Results.First();
- var itemId = contentItem.Id;
- var singleItemResult = await ApiClient.GetItemAsync(itemId, new GuardianApiOptions { ShowFields = ["body"] });
-
- singleItemResult.ShouldNotBeNull("GetItem result should not be null");
- singleItemResult.Status.ShouldBe("ok", "API response status should be 'ok'");
- singleItemResult.Content.ShouldNotBeNull("Content should not be null");
- singleItemResult.Content.Id.ShouldBe(itemId, "Returned content ID should match requested ID");
- singleItemResult.Content.WebTitle.ShouldNotBeNullOrEmpty("Content should have a title");
- singleItemResult.Content.Fields.ShouldNotBeNull("Fields should be populated when ShowFields is specified");
-
- Console.WriteLine($"Retrieved item: {singleItemResult.Content.WebTitle}");
- Console.WriteLine($"Item ID: {singleItemResult.Content.Id}");
- Console.WriteLine($"Published: {singleItemResult.Content.WebPublicationDate}");
-
- if (!string.IsNullOrEmpty(singleItemResult.Content.Fields.Body))
- {
- Console.WriteLine($"Body length: {singleItemResult.Content.Fields.Body.Length} characters");
- Console.WriteLine(
- $"Body preview: {singleItemResult.Content.Fields.Body[..Math.Min(200, singleItemResult.Content.Fields.Body.Length)]}...");
- }
- }
-}
diff --git a/GuardianClient/GuardianClient.Tests/SearchAsyncTests.cs b/GuardianClient/GuardianClient.Tests/SearchAsyncTests.cs
new file mode 100644
index 0000000..02bfbb4
--- /dev/null
+++ b/GuardianClient/GuardianClient.Tests/SearchAsyncTests.cs
@@ -0,0 +1,144 @@
+using GuardianClient.Options.Search;
+using Shouldly;
+
+namespace GuardianClient.Tests;
+
+[TestClass]
+public class SearchAsyncTests : TestBase
+{
+ [TestMethod]
+ public async Task SearchAsync_WithQuery_ReturnsResults()
+ {
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "climate change",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 5 }
+ });
+
+ result.ShouldNotBeNull("Search result should not be null");
+ result.Status.ShouldBe("ok", "API response status should be 'ok'");
+ result.Results.Count.ShouldBeGreaterThan(0, "Should return at least one result");
+ result.Results.Count.ShouldBeLessThanOrEqualTo(5, "Should not return more than requested page size");
+
+ var firstItem = result.Results.First();
+ firstItem.Id.ShouldNotBeNullOrEmpty("Content item should have an ID");
+ firstItem.WebTitle.ShouldNotBeNullOrEmpty("Content item should have a title");
+ firstItem.WebUrl.ShouldNotBeNullOrEmpty("Content item should have a web URL");
+ firstItem.ApiUrl.ShouldNotBeNullOrEmpty("Content item should have an API URL");
+
+ Console.WriteLine($"Found {result.Results.Count} articles about climate change");
+ Console.WriteLine($"First article: {firstItem.WebTitle}");
+ Console.WriteLine($"Published: {firstItem.WebPublicationDate}");
+ }
+
+ [TestMethod]
+ public async Task SearchAsync_WithNonExistentQuery_ReturnsNoResults()
+ {
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "xyzabc123nonexistentquery456"
+ });
+
+ result.ShouldNotBeNull("Search result should not be null even with no matches");
+ result.Status.ShouldBe("ok", "API response status should be 'ok'");
+ result.Results.Count.ShouldBe(0, "Should return zero results for non-existent query");
+ }
+
+ [TestMethod]
+ public async Task SearchAsync_WithNoOptions_ReturnsDefaultResults()
+ {
+ var result = await ApiClient.SearchAsync();
+
+ result.ShouldNotBeNull("Search result should not be null");
+ result.Status.ShouldBe("ok", "API response status should be 'ok'");
+ result.Results.Count.ShouldBeGreaterThan(0, "Should return results with default options");
+ result.Results.Count.ShouldBeLessThanOrEqualTo(10, "Default page size should be 10 or less");
+
+ Console.WriteLine($"Default search returned {result.Results.Count} results");
+ }
+
+ [TestMethod]
+ public async Task SearchAsync_WithFilterOptions_ReturnsFilteredResults()
+ {
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "technology",
+ FilterOptions = new GuardianApiContentFilterOptions
+ {
+ Section = "technology"
+ },
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 3 }
+ });
+
+ result.ShouldNotBeNull("Search result should not be null");
+ result.Status.ShouldBe("ok", "API response status should be 'ok'");
+ result.Results.Count.ShouldBeGreaterThan(0, "Should return technology results");
+
+ // Check that results are from technology section
+ foreach (var item in result.Results)
+ {
+ item.SectionId.ShouldBe("technology", "All results should be from technology section");
+ }
+
+ Console.WriteLine($"Found {result.Results.Count} technology articles");
+ }
+
+ [TestMethod]
+ public async Task SearchAsync_WithOrderOptions_ReturnsOrderedResults()
+ {
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "sports",
+ OrderOptions = new GuardianApiContentOrderOptions
+ {
+ OrderBy = GuardianApiContentOrderBy.Oldest
+ },
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 2 }
+ });
+
+ result.ShouldNotBeNull("Search result should not be null");
+ result.Status.ShouldBe("ok", "API response status should be 'ok'");
+ result.Results.Count.ShouldBeGreaterThan(0, "Should return sports results");
+
+ // Oldest first means first result should be older than or equal to second
+ if (result.Results.Count >= 2)
+ {
+ var first = result.Results.First();
+ var second = result.Results.Skip(1).First();
+
+ if (first.WebPublicationDate.HasValue && second.WebPublicationDate.HasValue)
+ {
+ first.WebPublicationDate.Value.ShouldBeLessThanOrEqualTo(second.WebPublicationDate.Value,
+ "Results should be ordered oldest first");
+ }
+ }
+
+ Console.WriteLine($"Found {result.Results.Count} sports articles (oldest first)");
+ }
+
+ [TestMethod]
+ public async Task SearchAsync_WithAdditionalInformation_ReturnsEnhancedResults()
+ {
+ var result = await ApiClient.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "environment",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 2 },
+ AdditionalInformationOptions = new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields = [GuardianApiContentShowFieldsOption.Headline, GuardianApiContentShowFieldsOption.Thumbnail],
+ ShowTags = [GuardianApiContentShowTagsOption.Keyword, GuardianApiContentShowTagsOption.Tone]
+ }
+ });
+
+ result.ShouldNotBeNull("Search result should not be null");
+ result.Status.ShouldBe("ok", "API response status should be 'ok'");
+ result.Results.Count.ShouldBeGreaterThan(0, "Should return environment results");
+
+ var firstItem = result.Results.First();
+ firstItem.Fields.ShouldNotBeNull("Fields should be populated");
+ firstItem.Tags.ShouldNotBeNull("Tags should be populated");
+
+ Console.WriteLine($"Enhanced search returned {result.Results.Count} environment articles");
+ Console.WriteLine($"First article has {firstItem.Tags.Count} tags");
+ }
+}
diff --git a/GuardianClient/GuardianClient.Tests/TestBase.cs b/GuardianClient/GuardianClient.Tests/TestBase.cs
index 5960da5..45d2a50 100644
--- a/GuardianClient/GuardianClient.Tests/TestBase.cs
+++ b/GuardianClient/GuardianClient.Tests/TestBase.cs
@@ -6,7 +6,7 @@ namespace GuardianClient.Tests;
public abstract class TestBase
{
- protected static GuardianApiClient ApiClient { get; }
+ protected static IGuardianApiClient ApiClient { get; }
static TestBase()
{
diff --git a/GuardianClient/GuardianClient/GuardianApiClient.cs b/GuardianClient/GuardianClient/GuardianApiClient.cs
index 204b3ff..92272ff 100644
--- a/GuardianClient/GuardianClient/GuardianApiClient.cs
+++ b/GuardianClient/GuardianClient/GuardianApiClient.cs
@@ -1,17 +1,15 @@
-using System.Reflection;
using System.Text.Json;
+using GuardianClient.Internal;
using GuardianClient.Models;
+using GuardianClient.Options.Search;
namespace GuardianClient;
-public class GuardianApiClient : IDisposable
+public class GuardianApiClient : IGuardianApiClient, IDisposable
{
private readonly HttpClient _httpClient;
-
private readonly string _apiKey;
-
private readonly bool _ownsHttpClient;
-
private bool _disposed;
private const string BaseUrl = "https://content.guardianapis.com";
@@ -54,81 +52,21 @@ public GuardianApiClient(string apiKey)
ConfigureHttpClient();
}
- private void ConfigureHttpClient()
- {
- var packageVersion = GetPackageVersion();
-
- _httpClient.BaseAddress = new Uri(BaseUrl);
- _httpClient.DefaultRequestHeaders.Add("User-Agent", $"GuardianClient.NET/{packageVersion}");
- }
-
- private string GetPackageVersion()
- {
- var packageVersion = Assembly
- .GetExecutingAssembly()
- .GetCustomAttribute()!
- .InformationalVersion;
-
- return packageVersion;
- }
-
- ///
- /// Search for Guardian content
- ///
- /// Search query (supports AND, OR, NOT operators)
- /// Number of results per page (1-50)
- /// Page number for pagination
- /// API options for including additional response data
- /// Cancellation token
- /// Content search results
public async Task SearchAsync(
- string? query = null,
- int? pageSize = null,
- int? page = null,
- GuardianApiOptions? options = null,
- CancellationToken cancellationToken = default)
+ GuardianApiContentSearchOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
{
- var parameters = new List { $"api-key={Uri.EscapeDataString(_apiKey)}" };
-
- if (!string.IsNullOrWhiteSpace(query))
- {
- parameters.Add($"q={Uri.EscapeDataString(query)}");
- }
-
- if (pageSize.HasValue)
- {
- parameters.Add($"page-size={pageSize.Value}");
- }
-
- if (page.HasValue)
- {
- parameters.Add($"page={page.Value}");
- }
-
- if (options?.ShowFields?.Length > 0)
- {
- parameters.Add($"show-fields={string.Join(",", options.ShowFields)}");
- }
-
- if (options?.ShowTags?.Length > 0)
- {
- parameters.Add($"show-tags={string.Join(",", options.ShowTags)}");
- }
-
- if (options?.ShowElements?.Length > 0)
- {
- parameters.Add($"show-elements={string.Join(",", options.ShowElements)}");
- }
+ options ??= new GuardianApiContentSearchOptions();
- if (options?.ShowReferences?.Length > 0)
- {
- parameters.Add($"show-references={string.Join(",", options.ShowReferences)}");
- }
+ var parameters = new List { $"api-key={Uri.EscapeDataString(_apiKey)}" };
- if (options?.ShowBlocks?.Length > 0)
- {
- parameters.Add($"show-blocks={string.Join(",", options.ShowBlocks)}");
- }
+ UrlParameterBuilder.AddQueryParameters(options, parameters);
+ UrlParameterBuilder.AddFilterParameters(options.FilterOptions, parameters);
+ UrlParameterBuilder.AddDateParameters(options.DateOptions, parameters);
+ UrlParameterBuilder.AddPageParameters(options.PageOptions, parameters);
+ UrlParameterBuilder.AddOrderParameters(options.OrderOptions, parameters);
+ UrlParameterBuilder.AddAdditionalInformationParameters(options.AdditionalInformationOptions, parameters);
var url = $"/search?{string.Join("&", parameters)}";
var response = await _httpClient.GetAsync(url, cancellationToken);
@@ -141,46 +79,46 @@ private string GetPackageVersion()
return wrapper?.Response;
}
- ///
- /// Get a single content item by its ID/path
- ///
- /// The content item ID (path from Guardian API)
- /// API options for including additional response data
- /// Cancellation token
- /// Single item response with content details
public async Task GetItemAsync(
string itemId,
- GuardianApiOptions? options = null,
+ GuardianApiContentAdditionalInformationOptions? options = null,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(itemId);
var parameters = new List { $"api-key={Uri.EscapeDataString(_apiKey)}" };
- if (options?.ShowFields?.Length > 0)
- {
- parameters.Add($"show-fields={string.Join(",", options.ShowFields)}");
- }
-
- if (options?.ShowTags?.Length > 0)
- {
- parameters.Add($"show-tags={string.Join(",", options.ShowTags)}");
- }
-
- if (options?.ShowElements?.Length > 0)
- {
- parameters.Add($"show-elements={string.Join(",", options.ShowElements)}");
- }
-
- if (options?.ShowReferences?.Length > 0)
- {
- parameters.Add($"show-references={string.Join(",", options.ShowReferences)}");
- }
-
- if (options?.ShowBlocks?.Length > 0)
- {
- parameters.Add($"show-blocks={string.Join(",", options.ShowBlocks)}");
- }
+ UrlParameterBuilder.AddParameterIfAny(
+ parameters,
+ "show-fields",
+ options?.ShowFields,
+ option => option.ToApiString()
+ );
+
+ UrlParameterBuilder.AddParameterIfAny(
+ parameters,
+ "show-tags",
+ options?.ShowTags,
+ option => option.ToApiString()
+ );
+ UrlParameterBuilder.AddParameterIfAny(
+ parameters,
+ "show-elements",
+ options?.ShowElements,
+ option => option.ToApiString()
+ );
+ UrlParameterBuilder.AddParameterIfAny(
+ parameters,
+ "show-references",
+ options?.ShowReferences,
+ option => option.ToApiString()
+ );
+
+ UrlParameterBuilder.AddParameterIfAny(
+ parameters,
+ "show-blocks",
+ options?.ShowBlocks
+ );
var url = $"/{itemId}?{string.Join("&", parameters)}";
var response = await _httpClient.GetAsync(url, cancellationToken);
@@ -199,6 +137,14 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ private void ConfigureHttpClient()
+ {
+ var packageVersion = AssemblyInfo.GetPackageVersion();
+
+ _httpClient.BaseAddress = new Uri(BaseUrl);
+ _httpClient.DefaultRequestHeaders.Add("User-Agent", $"GuardianClient.NET/{packageVersion}");
+ }
+
///
/// Disposes the if:
/// 1. This instance owns it, and
diff --git a/GuardianClient/GuardianClient/GuardianApiOptions.cs b/GuardianClient/GuardianClient/GuardianApiOptions.cs
deleted file mode 100644
index 505cc6b..0000000
--- a/GuardianClient/GuardianClient/GuardianApiOptions.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace GuardianClient;
-
-public class GuardianApiOptions
-{
- public string[]? ShowFields { get; set; }
- public string[]? ShowTags { get; set; }
- public string[]? ShowElements { get; set; }
- public string[]? ShowReferences { get; set; }
- public string[]? ShowBlocks { get; set; }
-}
diff --git a/GuardianClient/GuardianClient/GuardianClient.csproj b/GuardianClient/GuardianClient/GuardianClient.csproj
index e09c9ec..b385d91 100644
--- a/GuardianClient/GuardianClient/GuardianClient.csproj
+++ b/GuardianClient/GuardianClient/GuardianClient.csproj
@@ -7,13 +7,13 @@
GuardianClient.NET
- 0.3.0-alpha
+ 0.4.0-alpha
Andrew Tarr
A .NET API wrapper for Guardian services
guardian;api;client;wrapper
MIT
https://github.com/tarrball/GuardianClient.NET
- https://github.com/tarrball/GuardianClient.NET.git
+ https://github.com/tarrball/GuardianClient.NET
git
main
README.md
diff --git a/GuardianClient/GuardianClient/IGuardianApiClient.cs b/GuardianClient/GuardianClient/IGuardianApiClient.cs
new file mode 100644
index 0000000..d3a3f6b
--- /dev/null
+++ b/GuardianClient/GuardianClient/IGuardianApiClient.cs
@@ -0,0 +1,53 @@
+using GuardianClient.Models;
+using GuardianClient.Options.Search;
+
+namespace GuardianClient;
+
+///
+/// Interface for the Guardian API client, providing access to Guardian content search and retrieval.
+///
+public interface IGuardianApiClient
+{
+ ///
+ /// Search for Guardian content using comprehensive search options.
+ ///
+ ///
+ /// Search options including query terms, filters, pagination, ordering, date ranges, and additional information to include.
+ /// If null, returns all content with default settings (newest first, page 1, 10 items per page).
+ ///
+ /// Cancellation token to cancel the HTTP request
+ ///
+ /// A containing the search results, pagination info, and metadata.
+ /// Returns null if the API response cannot be deserialized.
+ ///
+ /// Thrown when the API request fails or returns a non-success status code
+ /// Thrown when the request is cancelled via the cancellation token
+ ///
+ /// This method provides access to the full Guardian Content API search functionality, including:
+ ///
+ /// - Free text search with boolean operators (AND, OR, NOT)
+ /// - Filtering by section, tags, references, production office, language, star rating, and more
+ /// - Date range filtering with different date types (published, first-publication, newspaper-edition, last-modified)
+ /// - Pagination and result ordering options
+ /// - Additional fields, tags, elements, references, and blocks in responses
+ ///
+ /// For simple searches, you can create basic options: new GuardianApiContentSearchOptions { Query = "your search terms" }
+ ///
+ Task SearchAsync(
+ GuardianApiContentSearchOptions? options = null,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Get a single content item by its ID/path
+ ///
+ /// The content item ID (path from Guardian API)
+ /// API options for including additional response data
+ /// Cancellation token
+ /// Single item response with content details
+ Task GetItemAsync(
+ string itemId,
+ GuardianApiContentAdditionalInformationOptions? options = null,
+ CancellationToken cancellationToken = default
+ );
+}
diff --git a/GuardianClient/GuardianClient/Internal/AdditionalInformationExtensions.cs b/GuardianClient/GuardianClient/Internal/AdditionalInformationExtensions.cs
new file mode 100644
index 0000000..0327232
--- /dev/null
+++ b/GuardianClient/GuardianClient/Internal/AdditionalInformationExtensions.cs
@@ -0,0 +1,95 @@
+using GuardianClient.Options.Search;
+
+namespace GuardianClient.Internal;
+
+///
+/// Extension methods for converting additional information enums to their API string values.
+///
+internal static class AdditionalInformationExtensions
+{
+ internal static string ToApiString(this GuardianApiContentShowFieldsOption option) => option switch
+ {
+ GuardianApiContentShowFieldsOption.TrailText => "trailText",
+ GuardianApiContentShowFieldsOption.Headline => "headline",
+ GuardianApiContentShowFieldsOption.ShowInRelatedContent => "showInRelatedContent",
+ GuardianApiContentShowFieldsOption.Body => "body",
+ GuardianApiContentShowFieldsOption.LastModified => "lastModified",
+ GuardianApiContentShowFieldsOption.HasStoryPackage => "hasStoryPackage",
+ GuardianApiContentShowFieldsOption.Score => "score",
+ GuardianApiContentShowFieldsOption.Standfirst => "standfirst",
+ GuardianApiContentShowFieldsOption.ShortUrl => "shortUrl",
+ GuardianApiContentShowFieldsOption.Thumbnail => "thumbnail",
+ GuardianApiContentShowFieldsOption.Wordcount => "wordcount",
+ GuardianApiContentShowFieldsOption.Commentable => "commentable",
+ GuardianApiContentShowFieldsOption.IsPremoderated => "isPremoderated",
+ GuardianApiContentShowFieldsOption.AllowUgc => "allowUgc",
+ GuardianApiContentShowFieldsOption.Byline => "byline",
+ GuardianApiContentShowFieldsOption.Publication => "publication",
+ GuardianApiContentShowFieldsOption.InternalPageCode => "internalPageCode",
+ GuardianApiContentShowFieldsOption.ProductionOffice => "productionOffice",
+ GuardianApiContentShowFieldsOption.ShouldHideAdverts => "shouldHideAdverts",
+ GuardianApiContentShowFieldsOption.LiveBloggingNow => "liveBloggingNow",
+ GuardianApiContentShowFieldsOption.CommentCloseDate => "commentCloseDate",
+ GuardianApiContentShowFieldsOption.StarRating => "starRating",
+ GuardianApiContentShowFieldsOption.All => "all",
+ _ => throw new ArgumentOutOfRangeException(nameof(option), option, null)
+ };
+
+ internal static string ToApiString(this GuardianApiContentShowTagsOption option) => option switch
+ {
+ GuardianApiContentShowTagsOption.Blog => "blog",
+ GuardianApiContentShowTagsOption.Contributor => "contributor",
+ GuardianApiContentShowTagsOption.Keyword => "keyword",
+ GuardianApiContentShowTagsOption.NewspaperBook => "newspaper-book",
+ GuardianApiContentShowTagsOption.NewspaperBookSection => "newspaper-book-section",
+ GuardianApiContentShowTagsOption.Publication => "publication",
+ GuardianApiContentShowTagsOption.Series => "series",
+ GuardianApiContentShowTagsOption.Tone => "tone",
+ GuardianApiContentShowTagsOption.Type => "type",
+ GuardianApiContentShowTagsOption.All => "all",
+ _ => throw new ArgumentOutOfRangeException(nameof(option), option, null)
+ };
+
+ internal static string ToApiString(this GuardianApiContentShowElementsOption option) => option switch
+ {
+ GuardianApiContentShowElementsOption.Audio => "audio",
+ GuardianApiContentShowElementsOption.Image => "image",
+ GuardianApiContentShowElementsOption.Video => "video",
+ GuardianApiContentShowElementsOption.All => "all",
+ _ => throw new ArgumentOutOfRangeException(nameof(option), option, null)
+ };
+
+ internal static string ToApiString(this GuardianApiContentShowReferencesOption option) => option switch
+ {
+ GuardianApiContentShowReferencesOption.Author => "author",
+ GuardianApiContentShowReferencesOption.BisacPrefix => "bisac-prefix",
+ GuardianApiContentShowReferencesOption.EsaCricketMatch => "esa-cricket-match",
+ GuardianApiContentShowReferencesOption.EsaFootballMatch => "esa-football-match",
+ GuardianApiContentShowReferencesOption.EsaFootballTeam => "esa-football-team",
+ GuardianApiContentShowReferencesOption.EsaFootballTournament => "esa-football-tournament",
+ GuardianApiContentShowReferencesOption.Isbn => "isbn",
+ GuardianApiContentShowReferencesOption.Imdb => "imdb",
+ GuardianApiContentShowReferencesOption.Musicbrainz => "musicbrainz",
+ GuardianApiContentShowReferencesOption.MusicbrainzGenre => "musicbrainzgenre",
+ GuardianApiContentShowReferencesOption.OptaCricketMatch => "opta-cricket-match",
+ GuardianApiContentShowReferencesOption.OptaFootballMatch => "opta-football-match",
+ GuardianApiContentShowReferencesOption.OptaFootballTeam => "opta-football-team",
+ GuardianApiContentShowReferencesOption.OptaFootballTournament => "opta-football-tournament",
+ GuardianApiContentShowReferencesOption.PaFootballCompetition => "pa-football-competition",
+ GuardianApiContentShowReferencesOption.PaFootballMatch => "pa-football-match",
+ GuardianApiContentShowReferencesOption.PaFootballTeam => "pa-football-team",
+ GuardianApiContentShowReferencesOption.R1Film => "r1-film",
+ GuardianApiContentShowReferencesOption.ReutersIndexRic => "reuters-index-ric",
+ GuardianApiContentShowReferencesOption.ReutersStockRic => "reuters-stock-ric",
+ GuardianApiContentShowReferencesOption.WitnessAssignment => "witness-assignment",
+ _ => throw new ArgumentOutOfRangeException(nameof(option), option, null)
+ };
+
+ internal static string ToApiString(this GuardianApiContentShowRightsOption option) => option switch
+ {
+ GuardianApiContentShowRightsOption.Syndicatable => "syndicatable",
+ GuardianApiContentShowRightsOption.SubscriptionDatabases => "subscription-databases",
+ GuardianApiContentShowRightsOption.All => "all",
+ _ => throw new ArgumentOutOfRangeException(nameof(option), option, null)
+ };
+}
diff --git a/GuardianClient/GuardianClient/Internal/AssemblyInfo.cs b/GuardianClient/GuardianClient/Internal/AssemblyInfo.cs
new file mode 100644
index 0000000..bbb6fe7
--- /dev/null
+++ b/GuardianClient/GuardianClient/Internal/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+using System.Reflection;
+
+namespace GuardianClient.Internal;
+
+internal static class AssemblyInfo
+{
+ internal static string GetPackageVersion()
+ {
+ var packageVersion = Assembly
+ .GetExecutingAssembly()
+ .GetCustomAttribute()!
+ .InformationalVersion;
+
+ return packageVersion;
+ }
+}
\ No newline at end of file
diff --git a/GuardianClient/GuardianClient/Internal/UrlParameterBuilder.cs b/GuardianClient/GuardianClient/Internal/UrlParameterBuilder.cs
new file mode 100644
index 0000000..73981d1
--- /dev/null
+++ b/GuardianClient/GuardianClient/Internal/UrlParameterBuilder.cs
@@ -0,0 +1,125 @@
+using GuardianClient.Options.Search;
+
+namespace GuardianClient.Internal;
+
+internal static class UrlParameterBuilder
+{
+ internal static void AddParameterIfNotEmpty(List parameters, string parameterName, string? value)
+ {
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ parameters.Add($"{parameterName}={Uri.EscapeDataString(value)}");
+ }
+ }
+
+ internal static void AddParameterIfHasValue(List parameters, string parameterName, T? value) where T : struct
+ {
+ if (value.HasValue)
+ {
+ parameters.Add($"{parameterName}={value.Value}");
+ }
+ }
+
+ internal static void AddParameterIfAny(List parameters, string parameterName, string[]? values)
+ {
+ if (values is { Length: > 0 })
+ {
+ parameters.Add($"{parameterName}={string.Join(",", values.Select(Uri.EscapeDataString))}");
+ }
+ }
+
+ internal static void AddParameterIfAny(List parameters, string parameterName, T[]? values, Func converter)
+ {
+ if (values is { Length: > 0 })
+ {
+ parameters.Add($"{parameterName}={string.Join(",", values.Select(v => Uri.EscapeDataString(converter(v))))}");
+ }
+ }
+
+ internal static void AddQueryParameters(GuardianApiContentSearchOptions options, List parameters)
+ {
+ AddParameterIfNotEmpty(parameters, "q", options.Query);
+ AddParameterIfAny(parameters, "query-fields", options.QueryFields);
+ }
+
+ internal static void AddFilterParameters(GuardianApiContentFilterOptions filterOptions, List parameters)
+ {
+ AddParameterIfNotEmpty(parameters, "section", filterOptions.Section);
+ AddParameterIfNotEmpty(parameters, "reference", filterOptions.Reference);
+ AddParameterIfNotEmpty(parameters, "reference-type", filterOptions.ReferenceType);
+ AddParameterIfNotEmpty(parameters, "tag", filterOptions.Tag);
+ AddParameterIfNotEmpty(parameters, "rights", filterOptions.Rights);
+ AddParameterIfNotEmpty(parameters, "ids", filterOptions.Ids);
+ AddParameterIfNotEmpty(parameters, "production-office", filterOptions.ProductionOffice);
+ AddParameterIfNotEmpty(parameters, "lang", filterOptions.Language);
+
+ AddParameterIfHasValue(parameters, "star-rating", filterOptions.StarRating);
+ }
+
+ internal static void AddDateParameters(GuardianApiContentDateOptions dateOptions, List parameters)
+ {
+ if (dateOptions.FromDate != default)
+ {
+ parameters.Add($"from-date={dateOptions.FromDate:yyyy-MM-dd}");
+ }
+
+ if (dateOptions.ToDate != default)
+ {
+ parameters.Add($"to-date={dateOptions.ToDate:yyyy-MM-dd}");
+ }
+
+ AddParameterIfNotEmpty(parameters, "use-date", dateOptions.UseDate);
+ }
+
+ internal static void AddPageParameters(GuardianApiContentPageOptions pageOptions, List parameters)
+ {
+ if (pageOptions.Page > 0)
+ {
+ parameters.Add($"page={pageOptions.Page}");
+ }
+
+ if (pageOptions.PageSize > 0)
+ {
+ parameters.Add($"page-size={pageOptions.PageSize}");
+ }
+ }
+
+ internal static void AddOrderParameters(GuardianApiContentOrderOptions orderOptions, List parameters)
+ {
+ if (orderOptions.OrderBy.HasValue)
+ {
+ var orderByValue = orderOptions.OrderBy.Value switch
+ {
+ GuardianApiContentOrderBy.Newest => "newest",
+ GuardianApiContentOrderBy.Oldest => "oldest",
+ GuardianApiContentOrderBy.Relevance => "relevance",
+ _ => "newest"
+ };
+ parameters.Add($"order-by={orderByValue}");
+ }
+
+ if (orderOptions.OrderDate.HasValue)
+ {
+ var orderDateValue = orderOptions.OrderDate.Value switch
+ {
+ GuardianApiContentOrderDate.Published => "published",
+ GuardianApiContentOrderDate.NewspaperEdition => "newspaper-edition",
+ GuardianApiContentOrderDate.LastModified => "last-modified",
+ _ => "published"
+ };
+ parameters.Add($"order-date={orderDateValue}");
+ }
+ }
+
+ internal static void AddAdditionalInformationParameters(
+ GuardianApiContentAdditionalInformationOptions additionalOptions,
+ List parameters
+ )
+ {
+ AddParameterIfAny(parameters, "show-fields", additionalOptions.ShowFields, f => f.ToApiString());
+ AddParameterIfAny(parameters, "show-tags", additionalOptions.ShowTags, t => t.ToApiString());
+ AddParameterIfAny(parameters, "show-elements", additionalOptions.ShowElements, e => e.ToApiString());
+ AddParameterIfAny(parameters, "show-references", additionalOptions.ShowReferences, r => r.ToApiString());
+ AddParameterIfAny(parameters, "show-blocks", additionalOptions.ShowBlocks);
+ }
+}
\ No newline at end of file
diff --git a/GuardianClient/GuardianClient/Models/BaseResponse.cs b/GuardianClient/GuardianClient/Models/BaseResponse.cs
index 91a091b..2458949 100644
--- a/GuardianClient/GuardianClient/Models/BaseResponse.cs
+++ b/GuardianClient/GuardianClient/Models/BaseResponse.cs
@@ -4,12 +4,21 @@ namespace GuardianClient.Models;
public class BaseResponse
{
+ ///
+ /// The status of the response. It refers to the state of the API. Successful calls will receive an "ok" even if your query did not return any results.
+ ///
[JsonPropertyName("status")]
public string Status { get; set; } = string.Empty;
+ ///
+ /// The user tier for the API account.
+ ///
[JsonPropertyName("userTier")]
public string? UserTier { get; set; }
+ ///
+ /// The number of results available for your search overall.
+ ///
[JsonPropertyName("total")]
public int Total { get; set; }
-}
\ No newline at end of file
+}
diff --git a/GuardianClient/GuardianClient/Models/ContentItem.cs b/GuardianClient/GuardianClient/Models/ContentItem.cs
index c201d81..6c0aab8 100644
--- a/GuardianClient/GuardianClient/Models/ContentItem.cs
+++ b/GuardianClient/GuardianClient/Models/ContentItem.cs
@@ -4,27 +4,45 @@ namespace GuardianClient.Models;
public class ContentItem
{
+ ///
+ /// The path to content.
+ ///
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
+ ///
+ /// The id of the section.
+ ///
[JsonPropertyName("sectionId")]
public string? SectionId { get; set; }
+ ///
+ /// The name of the section.
+ ///
[JsonPropertyName("sectionName")]
public string? SectionName { get; set; }
+ ///
+ /// The combined date and time of publication.
+ ///
[JsonPropertyName("webPublicationDate")]
public DateTime? WebPublicationDate { get; set; }
[JsonPropertyName("webTitle")]
public string WebTitle { get; set; } = string.Empty;
+ ///
+ /// The URL of the html content.
+ ///
[JsonPropertyName("webUrl")]
public string WebUrl { get; set; } = string.Empty;
+ ///
+ /// The URL of the raw content.
+ ///
[JsonPropertyName("apiUrl")]
public string ApiUrl { get; set; } = string.Empty;
@@ -51,4 +69,4 @@ public class ContentItem
[JsonPropertyName("blocks")]
public ContentBlocks? Blocks { get; set; }
-}
\ No newline at end of file
+}
diff --git a/GuardianClient/GuardianClient/Models/ContentSearchResponse.cs b/GuardianClient/GuardianClient/Models/ContentSearchResponse.cs
index 45b4654..399ea7f 100644
--- a/GuardianClient/GuardianClient/Models/ContentSearchResponse.cs
+++ b/GuardianClient/GuardianClient/Models/ContentSearchResponse.cs
@@ -4,21 +4,39 @@ namespace GuardianClient.Models;
public class ContentSearchResponse : BaseResponse
{
+ ///
+ /// The starting index for the current result set.
+ ///
[JsonPropertyName("startIndex")]
public int StartIndex { get; set; }
+ ///
+ /// The number of items returned in this call.
+ ///
[JsonPropertyName("pageSize")]
public int PageSize { get; set; }
+ ///
+ /// The number of the page you are browsing.
+ ///
[JsonPropertyName("currentPage")]
public int CurrentPage { get; set; }
+ ///
+ /// The total amount of pages that are in this call.
+ ///
[JsonPropertyName("pages")]
public int Pages { get; set; }
+ ///
+ /// The sort order used.
+ ///
[JsonPropertyName("orderBy")]
public string? OrderBy { get; set; }
+ ///
+ /// The collection of content items returned by the search.
+ ///
[JsonPropertyName("results")]
- public List Results { get; set; } = new();
-}
\ No newline at end of file
+ public ICollection Results { get; set; } = new HashSet();
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentAdditionalInformationOptions.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentAdditionalInformationOptions.cs
new file mode 100644
index 0000000..f0a4f81
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentAdditionalInformationOptions.cs
@@ -0,0 +1,51 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace GuardianClient.Options.Search;
+
+///
+/// Options for requesting additional information to be included with search results.
+///
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
+public class GuardianApiContentAdditionalInformationOptions
+{
+ ///
+ /// Add fields associated with the content such as headline, body, thumbnail, etc.
+ ///
+ public GuardianApiContentShowFieldsOption[]? ShowFields { get; set; }
+
+ ///
+ /// Add associated metadata tags such as contributor, keyword, tone, etc.
+ ///
+ public GuardianApiContentShowTagsOption[]? ShowTags { get; set; }
+
+ ///
+ /// Add associated media elements such as images, audio, and video.
+ ///
+ public GuardianApiContentShowElementsOption[]? ShowElements { get; set; }
+
+ ///
+ /// Add associated reference data such as ISBNs, IMDB IDs, author references, etc.
+ ///
+ public GuardianApiContentShowReferencesOption[]? ShowReferences { get; set; }
+
+
+ ///
+ /// Add associated blocks (single block for content, one or more for liveblogs).
+ /// Supported values:
+ ///
+ /// - "main" - Main content block
+ /// - "body" - Body content blocks
+ /// - "all" - All blocks
+ /// - "body:latest" - Latest body blocks (default limit: 20)
+ /// - "body:latest:10" - Latest 10 body blocks
+ /// - "body:oldest" - Oldest body blocks
+ /// - "body:oldest:10" - Oldest 10 body blocks
+ /// - "body:<block-id>" - Specific block by ID
+ /// - "body:around:<block-id>" - Block and 20 blocks around it
+ /// - "body:around:<block-id>:10" - Block and 10 blocks around it
+ /// - "body:key-events" - Key event blocks
+ /// - "body:published-since:<timestamp>" - Blocks since timestamp (e.g., "body:published-since:1556529318000")
+ ///
+ ///
+ public string[]? ShowBlocks { get; set; }
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentDateOptions.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentDateOptions.cs
new file mode 100644
index 0000000..0fe11f5
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentDateOptions.cs
@@ -0,0 +1,25 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Options for filtering search results by date ranges.
+///
+public class GuardianApiContentDateOptions
+{
+ ///
+ /// Return only content published on or after that date.
+ /// Example: 2014-02-16.
+ ///
+ public DateOnly FromDate { get; set; }
+
+ ///
+ /// Return only content published on or before that date.
+ /// Example: 2014-02-17.
+ ///
+ public DateOnly ToDate { get; set; }
+
+ ///
+ /// Changes which type of date is used to filter the results using FromDate and ToDate.
+ /// Accepted values: "published" (default - the date the content has been last published), "first-publication" (the date the content has been first published), "newspaper-edition" (the date the content appeared in print), "last-modified" (the date the content was last updated).
+ ///
+ public string? UseDate { get; set; }
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentFilterOptions.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentFilterOptions.cs
new file mode 100644
index 0000000..e838e60
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentFilterOptions.cs
@@ -0,0 +1,64 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace GuardianClient.Options.Search;
+
+///
+/// Options for filtering content search results by various criteria.
+///
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
+public class GuardianApiContentFilterOptions
+{
+ ///
+ /// Return only content in those sections. Supports boolean operators.
+ /// Example: "football".
+ ///
+ public string? Section { get; set; }
+
+ ///
+ /// Return only content with those references. Supports boolean operators.
+ /// Example: "isbn/9780718178949".
+ ///
+ public string? Reference { get; set; }
+
+ ///
+ /// Return only content with references of those types. Supports boolean operators.
+ /// Example: "isbn".
+ ///
+ public string? ReferenceType { get; set; }
+
+ ///
+ /// Return only content with those tags. Supports boolean operators.
+ /// Example: "technology/apple".
+ ///
+ public string? Tag { get; set; }
+
+ ///
+ /// Return only content with those rights. Does not support boolean operators.
+ /// Accepted values: "syndicatable", "subscription-databases".
+ ///
+ public string? Rights { get; set; }
+
+ ///
+ /// Return only content with those IDs. Does not support boolean operators.
+ /// Example: "technology/2014/feb/17/flappy-bird-clones-apple-google".
+ ///
+ public string? Ids { get; set; }
+
+ ///
+ /// Return only content from those production offices. Supports boolean operators.
+ /// Example: "aus".
+ ///
+ public string? ProductionOffice { get; set; }
+
+ ///
+ /// Return only content in those languages. Supports boolean operators.
+ /// Accepts ISO language codes. Examples: "en", "fr".
+ ///
+ public string? Language { get; set; }
+
+ ///
+ /// Return only content with a given star rating. Does not support boolean operators.
+ /// Accepted values: 1 to 5.
+ ///
+ public int? StarRating { get; set; }
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderBy.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderBy.cs
new file mode 100644
index 0000000..b75c022
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderBy.cs
@@ -0,0 +1,22 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Specifies the order in which search results should be returned.
+///
+public enum GuardianApiContentOrderBy
+{
+ ///
+ /// Order by newest content first. Default in all other cases.
+ ///
+ Newest,
+
+ ///
+ /// Order by oldest content first.
+ ///
+ Oldest,
+
+ ///
+ /// Order by relevance to search query. Default where q parameter is specified.
+ ///
+ Relevance
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderDate.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderDate.cs
new file mode 100644
index 0000000..10962a8
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderDate.cs
@@ -0,0 +1,22 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Specifies which type of date is used to order the results.
+///
+public enum GuardianApiContentOrderDate
+{
+ ///
+ /// The date the content appeared on the web. Default.
+ ///
+ Published,
+
+ ///
+ /// The date the content appeared in print.
+ ///
+ NewspaperEdition,
+
+ ///
+ /// The date the content was last updated.
+ ///
+ LastModified
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderOptions.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderOptions.cs
new file mode 100644
index 0000000..606a557
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentOrderOptions.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace GuardianClient.Options.Search;
+
+///
+/// Options for controlling the ordering of search results.
+///
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
+public class GuardianApiContentOrderOptions
+{
+ ///
+ /// Returns results in the specified order. Defaults to Newest in most cases, or Relevance when a query parameter is specified.
+ ///
+ public GuardianApiContentOrderBy? OrderBy { get; set; }
+
+ ///
+ /// Changes which type of date is used to order the results. Defaults to Published.
+ ///
+ public GuardianApiContentOrderDate? OrderDate { get; set; }
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentPageOptions.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentPageOptions.cs
new file mode 100644
index 0000000..04ba1ee
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentPageOptions.cs
@@ -0,0 +1,22 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace GuardianClient.Options.Search;
+
+///
+/// Options for controlling pagination of search results.
+///
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
+public class GuardianApiContentPageOptions
+{
+ ///
+ /// Return only the result set from a particular page.
+ /// Example: 5.
+ ///
+ public int Page { get; set; }
+
+ ///
+ /// Modify the number of items displayed per page.
+ /// Accepted values: 1 to 50.
+ ///
+ public int PageSize { get; set; }
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentSearchOptions.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentSearchOptions.cs
new file mode 100644
index 0000000..01fbf4d
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentSearchOptions.cs
@@ -0,0 +1,47 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace GuardianClient.Options.Search;
+
+///
+/// Options for searching content using the Guardian API.
+///
+[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
+public class GuardianApiContentSearchOptions
+{
+ ///
+ /// Request content containing this free text. Supports AND, OR and NOT operators, and exact phrase queries using double quotes.
+ /// Examples: "sausages", "pork sausages", "sausages AND (mash OR chips)", "sausages AND NOT (saveloy OR battered)".
+ ///
+ public string? Query { get; set; }
+
+ ///
+ /// Specify in which indexed fields query terms should be searched on.
+ /// Examples: ["body"], ["body", "thumbnail"].
+ ///
+ public string[]? QueryFields { get; set; }
+
+ ///
+ /// Options for requesting additional information to be included with search results.
+ ///
+ public GuardianApiContentAdditionalInformationOptions AdditionalInformationOptions { get; set; } = new();
+
+ ///
+ /// Options for filtering search results by various criteria.
+ ///
+ public GuardianApiContentFilterOptions FilterOptions { get; set; } = new();
+
+ ///
+ /// Options for filtering search results by date ranges.
+ ///
+ public GuardianApiContentDateOptions DateOptions { get; set; } = new();
+
+ ///
+ /// Options for controlling pagination of search results.
+ ///
+ public GuardianApiContentPageOptions PageOptions { get; set; } = new();
+
+ ///
+ /// Options for controlling the ordering of search results.
+ ///
+ public GuardianApiContentOrderOptions OrderOptions { get; set; } = new();
+}
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowElementsOption.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowElementsOption.cs
new file mode 100644
index 0000000..275fe03
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowElementsOption.cs
@@ -0,0 +1,12 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Media element types that can be included with content responses.
+///
+public enum GuardianApiContentShowElementsOption
+{
+ Audio,
+ Image,
+ Video,
+ All
+}
\ No newline at end of file
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowFieldsOption.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowFieldsOption.cs
new file mode 100644
index 0000000..32e0251
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowFieldsOption.cs
@@ -0,0 +1,31 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Fields that can be included with content responses.
+///
+public enum GuardianApiContentShowFieldsOption
+{
+ TrailText,
+ Headline,
+ ShowInRelatedContent,
+ Body,
+ LastModified,
+ HasStoryPackage,
+ Score,
+ Standfirst,
+ ShortUrl,
+ Thumbnail,
+ Wordcount,
+ Commentable,
+ IsPremoderated,
+ AllowUgc,
+ Byline,
+ Publication,
+ InternalPageCode,
+ ProductionOffice,
+ ShouldHideAdverts,
+ LiveBloggingNow,
+ CommentCloseDate,
+ StarRating,
+ All
+}
\ No newline at end of file
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowReferencesOption.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowReferencesOption.cs
new file mode 100644
index 0000000..663bb09
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowReferencesOption.cs
@@ -0,0 +1,29 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Reference types that can be included with content responses.
+///
+public enum GuardianApiContentShowReferencesOption
+{
+ Author,
+ BisacPrefix,
+ EsaCricketMatch,
+ EsaFootballMatch,
+ EsaFootballTeam,
+ EsaFootballTournament,
+ Isbn,
+ Imdb,
+ Musicbrainz,
+ MusicbrainzGenre,
+ OptaCricketMatch,
+ OptaFootballMatch,
+ OptaFootballTeam,
+ OptaFootballTournament,
+ PaFootballCompetition,
+ PaFootballMatch,
+ PaFootballTeam,
+ R1Film,
+ ReutersIndexRic,
+ ReutersStockRic,
+ WitnessAssignment
+}
\ No newline at end of file
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowRightsOption.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowRightsOption.cs
new file mode 100644
index 0000000..0a57c62
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowRightsOption.cs
@@ -0,0 +1,11 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Rights types that can be included with content responses.
+///
+public enum GuardianApiContentShowRightsOption
+{
+ Syndicatable,
+ SubscriptionDatabases,
+ All
+}
\ No newline at end of file
diff --git a/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowTagsOption.cs b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowTagsOption.cs
new file mode 100644
index 0000000..d186193
--- /dev/null
+++ b/GuardianClient/GuardianClient/Options/Search/GuardianApiContentShowTagsOption.cs
@@ -0,0 +1,18 @@
+namespace GuardianClient.Options.Search;
+
+///
+/// Tag types that can be included with content responses.
+///
+public enum GuardianApiContentShowTagsOption
+{
+ Blog,
+ Contributor,
+ Keyword,
+ NewspaperBook,
+ NewspaperBookSection,
+ Publication,
+ Series,
+ Tone,
+ Type,
+ All
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index b8a74d2..8f78dc4 100644
--- a/README.md
+++ b/README.md
@@ -6,19 +6,21 @@
-A modern .NET client library for The Guardian's Content API. Provides strongly-typed models and simple methods for
-searching Guardian articles, tags, sections, and more.
+A modern, comprehensive .NET client library for The Guardian's Content API. Provides strongly-typed models, flexible search options, and simple methods for searching Guardian articles with full API feature support.
[](https://www.nuget.org/packages/GuardianClient.NET)
[](https://github.com/tarrball/GuardianClient.NET/actions)
## โจ Features
-- ๐ **Content Search** - Search Guardian articles with full query support
-- ๐ท๏ธ **Strongly Typed** - Complete C# models for all API responses
-- ๐ง **Dependency Injection** - Easy setup with `services.AddGuardianApiClient()`
-- ๐ฆ **HttpClientFactory** - Proper HttpClient lifecycle management
-- โก **Async/Await** - Modern async patterns with cancellation support
+- ๐ **Complete Content Search** - Full Guardian Content API search with all parameters
+- ๐ท๏ธ **Strongly Typed** - Type-safe enums for fields, tags, elements, and references
+- ๐ฏ **Comprehensive Filtering** - Search by section, tags, date ranges, production office, and more
+- ๐ **Rich Content Options** - Include fields, tags, media elements, blocks, and references
+- ๐ง **Interface-Based Design** - Easy mocking and dependency injection with `IGuardianApiClient`
+- ๐ฆ **HttpClientFactory Ready** - Proper HttpClient lifecycle management
+- โก **Modern Async** - Full async/await patterns with cancellation support
+- ๐จ **Clean API** - Organized option classes for maintainable code
## ๐ Quick Start
@@ -28,37 +30,129 @@ searching Guardian articles, tags, sections, and more.
dotnet add package GuardianClient.NET
```
-### Setup with Dependency Injection
+### Setup with Dependency Injection (Recommended)
```csharp
// Program.cs or Startup.cs
builder.Services.AddGuardianApiClient("your-api-key");
```
-### Usage
+### Basic Usage
```csharp
public class NewsService
{
- private readonly GuardianApiClient _client;
+ private readonly IGuardianApiClient _client;
- public NewsService(GuardianApiClient client)
+ public NewsService(IGuardianApiClient client)
{
_client = client;
}
- public async Task GetLatestNews()
+ public async Task GetLatestTechNews()
{
- return await _client.SearchAsync("politics", pageSize: 10);
+ return await _client.SearchAsync(new GuardianApiContentSearchOptions
+ {
+ Query = "artificial intelligence",
+ FilterOptions = new GuardianApiContentFilterOptions
+ {
+ Section = "technology"
+ },
+ PageOptions = new GuardianApiContentPageOptions
+ {
+ PageSize = 10
+ }
+ });
}
}
```
+## ๐ง Advanced Usage
+
+### Comprehensive Search with All Options
+
+```csharp
+var results = await client.SearchAsync(new GuardianApiContentSearchOptions
+{
+ // Search terms with boolean operators
+ Query = "climate change AND (policy OR legislation)",
+ QueryFields = ["body", "headline"],
+
+ // Filtering options
+ FilterOptions = new GuardianApiContentFilterOptions
+ {
+ Section = "environment",
+ Tag = "climate-change",
+ Language = "en"
+ },
+
+ // Date filtering
+ DateOptions = new GuardianApiContentDateOptions
+ {
+ FromDate = new DateOnly(2023, 1, 1),
+ UseDate = "published"
+ },
+
+ // Pagination
+ PageOptions = new GuardianApiContentPageOptions
+ {
+ Page = 1,
+ PageSize = 20
+ },
+
+ // Ordering
+ OrderOptions = new GuardianApiContentOrderOptions
+ {
+ OrderBy = GuardianApiContentOrderBy.Relevance,
+ OrderDate = GuardianApiContentOrderDate.Published
+ },
+
+ // Additional content
+ AdditionalInformationOptions = new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields = [
+ GuardianApiContentShowFieldsOption.Headline,
+ GuardianApiContentShowFieldsOption.Body,
+ GuardianApiContentShowFieldsOption.Thumbnail
+ ],
+ ShowTags = [
+ GuardianApiContentShowTagsOption.Keyword,
+ GuardianApiContentShowTagsOption.Tone
+ ],
+ ShowElements = [GuardianApiContentShowElementsOption.Image]
+ }
+});
+```
+
+### Getting Individual Articles
+
+```csharp
+// Get a specific article by ID
+var article = await client.GetItemAsync("world/2023/oct/15/climate-summit-agreement",
+ new GuardianApiContentAdditionalInformationOptions
+ {
+ ShowFields = [
+ GuardianApiContentShowFieldsOption.Body,
+ GuardianApiContentShowFieldsOption.Byline
+ ],
+ ShowTags = [GuardianApiContentShowTagsOption.All]
+ });
+
+// Access the rich content
+Console.WriteLine(article.Content.WebTitle);
+Console.WriteLine(article.Content.Fields.Body); // Full HTML content
+Console.WriteLine($"Author: {article.Content.Fields.Byline}");
+```
+
### Manual Setup (without DI)
```csharp
using var client = new GuardianApiClient("your-api-key");
-var results = await client.SearchAsync("climate change", pageSize: 5);
+var results = await client.SearchAsync(new GuardianApiContentSearchOptions
+{
+ Query = "sports",
+ PageOptions = new GuardianApiContentPageOptions { PageSize = 5 }
+});
foreach (var article in results.Results)
{
@@ -69,27 +163,69 @@ foreach (var article in results.Results)
## ๐ Getting an API Key
1. Visit [The Guardian Open Platform](https://open-platform.theguardian.com/access/)
-2. Sign up for a free developer account
+2. Sign up for a free developer account
3. Generate your API key
+4. Store it securely (use User Secrets for development)
+
+## ๐๏ธ Available Options
+
+### Filter Options
+- **Section**: Filter by Guardian sections (e.g., "politics", "sport", "culture")
+- **Tags**: Filter by content tags with boolean operators
+- **Date Range**: Filter by publication date with flexible date types
+- **Language**: Filter by content language (ISO codes)
+- **Production Office**: Filter by Guardian production offices
+- **Star Rating**: Filter by review ratings (1-5)
+
+### Additional Content Options
+- **ShowFields**: Include extra fields like body content, thumbnails, bylines
+- **ShowTags**: Include metadata tags (keywords, contributors, tone)
+- **ShowElements**: Include media elements (images, videos, audio)
+- **ShowReferences**: Include reference data (ISBNs, IMDB IDs, etc.)
+- **ShowBlocks**: Include content blocks (useful for live blogs)
+
+### Ordering Options
+- **OrderBy**: Sort by newest, oldest, or relevance
+- **OrderDate**: Choose which date to use for sorting
-## ๐ Current Status
+## ๐ Current Status
-**โ
Implemented:**
+**โ
Fully Implemented:**
+- Complete Content API search with all parameters
+- Individual article retrieval
+- Strongly-typed models and enums for all options
+- Advanced filtering, pagination, and sorting
+- Rich content enhancement options
+- Interface-based design for easy testing
+- Comprehensive documentation and examples
-- Content search with basic parameters
-- Strongly-typed response models
-- Dependency injection support
-- HttpClientFactory integration
+**๐ฏ Feature Complete:** This library now supports the full Guardian Content API specification.
-**๐ In Development:**
+## ๐งช Testing
-- Additional endpoints (tags, sections, editions)
-- Advanced search parameters
-- Deep pagination support
+The library includes comprehensive test coverage:
+
+```bash
+# Run all tests
+dotnet test
+
+# Run with detailed output
+dotnet test --logger "console;verbosity=detailed"
+```
+
+Tests require a Guardian API key stored in user secrets:
+```bash
+dotnet user-secrets set "GuardianApiKey" "your-api-key-here"
+```
## ๐ค Contributing
-This is an early-stage project. Issues and pull requests are welcome!
+Contributions are welcome! Please:
+1. Fork the repository
+2. Create a feature branch
+3. Add tests for new functionality
+4. Ensure all tests pass
+5. Submit a pull request
## ๐ License
@@ -97,4 +233,4 @@ MIT License - see [LICENSE](LICENSE) for details.
---
-*This is an unofficial library and is not affiliated with The Guardian.*
+*This is an unofficial library and is not affiliated with The Guardian.*
\ No newline at end of file
diff --git a/guardian-api-spec.yaml b/guardian-api-spec.yaml
deleted file mode 100644
index c92beec..0000000
--- a/guardian-api-spec.yaml
+++ /dev/null
@@ -1,1148 +0,0 @@
-openapi: 3.0.3
-info:
- title: Guardian Content API
- description: |
- The Guardian Content API provides access to Guardian news content, tags, sections, and editions.
-
- ## Authentication
- All requests require an API key passed as a query parameter: `api-key`
-
- ## Rate Limiting
- The API is rate-limited. For elevated limits, please contact The Guardian.
-
- ## HTTPS Support
- Available at https://content.guardianapis.com/
- version: "1.0.0"
- contact:
- name: Guardian API Support
- url: https://groups.google.com/group/guardian-api-talk/
- license:
- name: Guardian API Terms
- url: https://open-platform.theguardian.com/access/
-
-servers:
- - url: https://content.guardianapis.com
- description: Production server
-
-security:
- - ApiKeyAuth: []
-
-paths:
- /search:
- get:
- summary: Search for content
- description: Returns all pieces of content in the API with optional filtering
- operationId: searchContent
- tags:
- - Content
- parameters:
- - $ref: '#/components/parameters/ApiKey'
- - name: q
- in: query
- description: |
- Search query supporting AND, OR, NOT operators and exact phrase queries using double quotes.
- Examples: "debate", "debate AND economy", "debate AND NOT immigration", "debate AND (economy OR immigration)"
- schema:
- type: string
- example: "debate AND economy"
- - name: query-fields
- in: query
- description: Specify which indexed fields to search in
- schema:
- type: array
- items:
- type: string
- enum: [body, thumbnail]
- style: form
- explode: false
- example: ["body", "thumbnail"]
- - name: section
- in: query
- description: Return only content in specified sections (supports boolean operators)
- schema:
- type: string
- example: "football"
- - name: reference
- in: query
- description: Return only content with specified references
- schema:
- type: string
- example: "isbn/9780718178949"
- - name: reference-type
- in: query
- description: Return only content with references of specified types
- schema:
- type: string
- example: "isbn"
- - name: tag
- in: query
- description: Return only content with specified tags (supports boolean operators)
- schema:
- type: string
- example: "technology/apple"
- - name: rights
- in: query
- description: Return only content with specified rights
- schema:
- type: string
- enum: [syndicatable, subscription-databases]
- - name: ids
- in: query
- description: Return only content with specified IDs
- schema:
- type: string
- example: "technology/2014/feb/17/flappy-bird-clones-apple-google"
- - name: production-office
- in: query
- description: Return only content from specified production offices
- schema:
- type: string
- example: "aus"
- - name: lang
- in: query
- description: Return only content in specified languages (ISO language codes)
- schema:
- type: string
- example: "en"
- - name: star-rating
- in: query
- description: Return only content with specified star rating
- schema:
- type: integer
- minimum: 1
- maximum: 5
- - name: from-date
- in: query
- description: Return only content published on or after this date
- schema:
- type: string
- format: date
- example: "2014-02-16"
- - name: to-date
- in: query
- description: Return only content published on or before this date
- schema:
- type: string
- format: date
- example: "2014-02-17"
- - name: use-date
- in: query
- description: Type of date to use for filtering
- schema:
- type: string
- enum: [published, first-publication, newspaper-edition, last-modified]
- default: published
- - name: page
- in: query
- description: Page number for pagination
- schema:
- type: integer
- minimum: 1
- default: 1
- - name: page-size
- in: query
- description: Number of items per page
- schema:
- type: integer
- minimum: 1
- maximum: 50
- default: 10
- - name: order-by
- in: query
- description: Sort order for results
- schema:
- type: string
- enum: [newest, oldest, relevance]
- default: newest
- - name: order-date
- in: query
- description: Type of date to use for ordering
- schema:
- type: string
- enum: [published, newspaper-edition, last-modified]
- default: published
- - name: show-fields
- in: query
- description: Additional fields to include with content
- schema:
- type: array
- items:
- type: string
- enum: [
- trailText, headline, showInRelatedContent, body, lastModified,
- hasStoryPackage, score, standfirst, shortUrl, thumbnail, wordcount,
- commentable, isPremoderated, allowUgc, byline, publication,
- internalPageCode, productionOffice, shouldHideAdverts, liveBloggingNow,
- commentCloseDate, starRating, all
- ]
- style: form
- explode: false
- - name: show-tags
- in: query
- description: Types of tags to include
- schema:
- type: array
- items:
- type: string
- enum: [
- blog, contributor, keyword, newspaper-book, newspaper-book-section,
- publication, series, tone, type, all
- ]
- style: form
- explode: false
- - name: show-section
- in: query
- description: Include section metadata
- schema:
- type: boolean
- - name: show-blocks
- in: query
- description: |
- Include content blocks. Supports:
- - main, body, all
- - body:latest (default 20), body:latest:10 (limit 10)
- - body:oldest, body:oldest:10
- - body: (specific block)
- - body:around: (block + 20 around it)
- - body:around::10 (block + 10 around it)
- - body:key-events
- - body:published-since:
- schema:
- type: array
- items:
- type: string
- style: form
- explode: false
- - name: show-elements
- in: query
- description: Include media elements
- schema:
- type: array
- items:
- type: string
- enum: [audio, image, video, all]
- style: form
- explode: false
- - name: show-references
- in: query
- description: Include reference data
- schema:
- type: array
- items:
- type: string
- enum: [
- author, bisac-prefix, esa-cricket-match, esa-football-match,
- esa-football-team, esa-football-tournament, isbn, imdb, musicbrainz,
- musicbrainzgenre, opta-cricket-match, opta-football-match,
- opta-football-team, opta-football-tournament, pa-football-competition,
- pa-football-match, pa-football-team, r1-film, reuters-index-ric,
- reuters-stock-ric, witness-assignment
- ]
- style: form
- explode: false
- - name: show-rights
- in: query
- description: Include rights information
- schema:
- type: array
- items:
- type: string
- enum: [syndicatable, subscription-databases, all]
- style: form
- explode: false
- - name: format
- in: query
- description: Response format
- schema:
- type: string
- enum: [json]
- default: json
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ContentSearchResponse'
- '400':
- description: Bad request
- '401':
- description: Unauthorized - invalid API key
- '429':
- description: Rate limit exceeded
-
- /content/{itemId}/next:
- get:
- summary: Get next page of content for deep pagination
- description: |
- For deep pagination beyond standard page limits. Use the ID of the last content item
- from a previous search to get the next set of results.
- operationId: getNextContent
- tags:
- - Content
- parameters:
- - name: itemId
- in: path
- required: true
- description: ID of the last content item from previous results
- schema:
- type: string
- example: "food/2022/jul/04/peperonata-sausages-recipe-rachel-roddy"
- - $ref: '#/components/parameters/ApiKey'
- - name: q
- in: query
- description: Original search query (must match original search)
- schema:
- type: string
- - name: page-size
- in: query
- description: Number of items per page (must match original search)
- schema:
- type: integer
- minimum: 1
- maximum: 50
- default: 10
- - name: order-by
- in: query
- description: Sort order (must match original search)
- schema:
- type: string
- enum: [newest, oldest, relevance]
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ContentSearchResponse'
- '400':
- description: Bad request
- '401':
- description: Unauthorized - invalid API key
- '429':
- description: Rate limit exceeded
-
- /tags:
- get:
- summary: Search for tags
- description: Returns all tags in the API with optional filtering
- operationId: searchTags
- tags:
- - Tags
- parameters:
- - $ref: '#/components/parameters/ApiKey'
- - name: q
- in: query
- description: Return tags containing this free text
- schema:
- type: string
- example: "sausages"
- - name: web-title
- in: query
- description: Return tags starting with this free text
- schema:
- type: string
- example: "sausa"
- - name: type
- in: query
- description: Return only tags of specified type
- schema:
- type: string
- enum: [keyword, series, contributor, tone, type, blog]
- - name: section
- in: query
- description: Return only tags in specified sections (supports boolean operators)
- schema:
- type: string
- example: "football"
- - name: reference
- in: query
- description: Return only tags with specified references
- schema:
- type: string
- example: "isbn/9780349108391"
- - name: reference-type
- in: query
- description: Return only tags with references of specified types
- schema:
- type: string
- example: "isbn"
- - name: page
- in: query
- description: Page number for pagination
- schema:
- type: integer
- minimum: 1
- default: 1
- - name: page-size
- in: query
- description: Number of items per page
- schema:
- type: integer
- minimum: 1
- maximum: 50
- default: 10
- - name: show-references
- in: query
- description: Include reference data
- schema:
- type: array
- items:
- type: string
- enum: [
- author, bisac-prefix, esa-cricket-match, esa-football-match,
- esa-football-team, esa-football-tournament, isbn, imdb, musicbrainz,
- musicbrainzgenre, opta-cricket-match, opta-football-match,
- opta-football-team, opta-football-tournament, pa-football-competition,
- pa-football-match, pa-football-team, r1-film, reuters-index-ric,
- reuters-stock-ric, witness-assignment
- ]
- style: form
- explode: false
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/TagsResponse'
- '400':
- description: Bad request
- '401':
- description: Unauthorized - invalid API key
- '429':
- description: Rate limit exceeded
-
- /sections:
- get:
- summary: Get all sections
- description: Returns all sections in the API
- operationId: getSections
- tags:
- - Sections
- parameters:
- - $ref: '#/components/parameters/ApiKey'
- - name: q
- in: query
- description: Return sections based on query term
- schema:
- type: string
- example: "business"
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SectionsResponse'
- '400':
- description: Bad request
- '401':
- description: Unauthorized - invalid API key
- '429':
- description: Rate limit exceeded
-
- /editions:
- get:
- summary: Get all editions
- description: Returns all editions in the API (regionalized front pages)
- operationId: getEditions
- tags:
- - Editions
- parameters:
- - $ref: '#/components/parameters/ApiKey'
- - name: q
- in: query
- description: Return editions based on query term
- schema:
- type: string
- example: "UK"
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/EditionsResponse'
- '400':
- description: Bad request
- '401':
- description: Unauthorized - invalid API key
- '429':
- description: Rate limit exceeded
-
- /{itemPath}:
- get:
- summary: Get single item by path
- description: |
- Returns all data for a single item (content, tag, or section) by its path.
- The path matches the URL structure on theguardian.com.
- operationId: getSingleItem
- tags:
- - Single Item
- parameters:
- - name: itemPath
- in: path
- required: true
- description: |
- The path to the item. Examples:
- - Content: technology/2014/feb/18/doge-such-questions-very-answered
- - Tag: world/france
- - Section: lifeandstyle
- schema:
- type: string
- example: "technology/2014/feb/18/doge-such-questions-very-answered"
- - $ref: '#/components/parameters/ApiKey'
- - name: show-story-package
- in: query
- description: Display related content in same story package
- schema:
- type: boolean
- - name: show-editors-picks
- in: query
- description: Display editor-chosen content for tags/sections
- schema:
- type: boolean
- - name: show-most-viewed
- in: query
- description: Display most viewed content
- schema:
- type: boolean
- - name: show-related
- in: query
- description: Display related content items
- schema:
- type: boolean
- - name: show-fields
- in: query
- description: Additional fields to include
- schema:
- type: array
- items:
- type: string
- enum: [
- trailText, headline, showInRelatedContent, body, lastModified,
- hasStoryPackage, score, standfirst, shortUrl, thumbnail, wordcount,
- commentable, isPremoderated, allowUgc, byline, publication,
- internalPageCode, productionOffice, shouldHideAdverts, liveBloggingNow,
- commentCloseDate, starRating, all
- ]
- style: form
- explode: false
- - name: show-tags
- in: query
- description: Types of tags to include
- schema:
- type: array
- items:
- type: string
- enum: [
- blog, contributor, keyword, newspaper-book, newspaper-book-section,
- publication, series, tone, type, all
- ]
- style: form
- explode: false
- - name: show-sections
- in: query
- description: Include section metadata
- schema:
- type: boolean
- - name: show-blocks
- in: query
- description: |
- Include content blocks. Supports:
- - main, body, all
- - body:latest (default 20), body:latest:10 (limit 10)
- - body:oldest, body:oldest:10
- - body: (specific block)
- - body:around: (block + 20 around it)
- - body:around::10 (block + 10 around it)
- - body:key-events
- - body:published-since:
- schema:
- type: array
- items:
- type: string
- style: form
- explode: false
- - name: show-elements
- in: query
- description: Include media elements
- schema:
- type: array
- items:
- type: string
- enum: [audio, image, video, all]
- style: form
- explode: false
- - name: show-references
- in: query
- description: Include reference data
- schema:
- type: array
- items:
- type: string
- enum: [
- author, bisac-prefix, esa-cricket-match, esa-football-match,
- esa-football-team, esa-football-tournament, isbn, imdb, musicbrainz,
- musicbrainzgenre, opta-cricket-match, opta-football-match,
- opta-football-team, opta-football-tournament, pa-football-competition,
- pa-football-match, pa-football-team, r1-film, reuters-index-ric,
- reuters-stock-ric, witness-assignment
- ]
- style: form
- explode: false
- responses:
- '200':
- description: Successful response
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/SingleItemResponse'
- '400':
- description: Bad request
- '401':
- description: Unauthorized - invalid API key
- '404':
- description: Item not found
- '429':
- description: Rate limit exceeded
-
-components:
- securitySchemes:
- ApiKeyAuth:
- type: apiKey
- in: query
- name: api-key
- description: API key for authentication
-
- parameters:
- ApiKey:
- name: api-key
- in: query
- required: true
- description: Your Guardian API key
- schema:
- type: string
- example: "test"
-
- schemas:
- BaseResponse:
- type: object
- properties:
- status:
- type: string
- description: API response status
- enum: [ok, error]
- example: "ok"
- userTier:
- type: string
- description: User's API tier
- example: "developer"
- total:
- type: integer
- description: Total number of results available
- example: 1
- required:
- - status
-
- ContentItem:
- type: object
- properties:
- id:
- type: string
- description: Unique identifier/path to the content
- example: "world/2022/oct/21/russia-ukraine-war-latest-what-we-know-on-day-240-of-the-invasion"
- type:
- type: string
- description: Type of content
- example: "article"
- sectionId:
- type: string
- description: ID of the section
- example: "world"
- sectionName:
- type: string
- description: Name of the section
- example: "World news"
- webPublicationDate:
- type: string
- format: date-time
- description: Date and time of publication
- example: "2022-10-21T14:06:14Z"
- webTitle:
- type: string
- description: Title of the content
- example: "Russia-Ukraine war latest: what we know on day 240 of the invasion"
- webUrl:
- type: string
- format: uri
- description: URL of the HTML content
- example: "https://www.theguardian.com/world/2022/oct/21/russia-ukraine-war-latest-what-we-know-on-day-240-of-the-invasion"
- apiUrl:
- type: string
- format: uri
- description: URL of the API content
- example: "https://content.guardianapis.com/world/2022/oct/21/russia-ukraine-war-latest-what-we-know-on-day-240-of-the-invasion"
- isHosted:
- type: boolean
- description: Whether the content is hosted
- example: false
- pillarId:
- type: string
- description: ID of the pillar
- example: "pillar/news"
- pillarName:
- type: string
- description: Name of the pillar
- example: "News"
- fields:
- $ref: '#/components/schemas/ContentFields'
- tags:
- type: array
- items:
- $ref: '#/components/schemas/Tag'
- elements:
- type: array
- items:
- $ref: '#/components/schemas/Element'
- references:
- type: array
- items:
- $ref: '#/components/schemas/Reference'
- blocks:
- $ref: '#/components/schemas/ContentBlocks'
- required:
- - id
- - type
- - webTitle
- - webUrl
- - apiUrl
-
- ContentFields:
- type: object
- description: Additional fields for content (when requested via show-fields)
- properties:
- trailText:
- type: string
- description: Trail text HTML
- headline:
- type: string
- description: Headline HTML
- showInRelatedContent:
- type: string
- description: Whether content can appear in related content (boolean as string)
- body:
- type: string
- description: Full body content HTML
- lastModified:
- type: string
- format: date-time
- description: Last modification date
- hasStoryPackage:
- type: string
- description: Whether content has related story package (boolean as string)
- score:
- type: string
- description: Relevance score (float as string)
- standfirst:
- type: string
- description: Standfirst HTML
- shortUrl:
- type: string
- format: uri
- description: Short URL
- thumbnail:
- type: string
- format: uri
- description: Thumbnail image URL
- wordcount:
- type: string
- description: Word count (integer as string)
- commentable:
- type: string
- description: Whether comments are allowed (boolean as string)
- isPremoderated:
- type: string
- description: Whether comments are premoderated (boolean as string)
- allowUgc:
- type: string
- description: Whether user generated content is allowed (boolean as string)
- byline:
- type: string
- description: Byline HTML
- publication:
- type: string
- description: Publication name
- internalPageCode:
- type: string
- description: Internal page code
- productionOffice:
- type: string
- description: Production office
- shouldHideAdverts:
- type: string
- description: Whether to hide adverts (boolean as string)
- liveBloggingNow:
- type: string
- description: Whether currently live blogging (boolean as string)
- commentCloseDate:
- type: string
- format: date-time
- description: Date when comments were closed
- starRating:
- type: string
- description: Star rating (integer as string)
-
- ContentSearchResponse:
- allOf:
- - $ref: '#/components/schemas/BaseResponse'
- - type: object
- properties:
- startIndex:
- type: integer
- description: Starting index of results
- example: 1
- pageSize:
- type: integer
- description: Number of items per page
- example: 10
- currentPage:
- type: integer
- description: Current page number
- example: 1
- pages:
- type: integer
- description: Total number of pages
- example: 1
- orderBy:
- type: string
- description: Sort order used
- example: "newest"
- results:
- type: array
- items:
- $ref: '#/components/schemas/ContentItem'
- required:
- - results
-
- SingleItemResponse:
- allOf:
- - $ref: '#/components/schemas/BaseResponse'
- - type: object
- properties:
- content:
- $ref: '#/components/schemas/ContentItem'
- leadContent:
- type: array
- description: Key pieces of lead content for tags
- items:
- $ref: '#/components/schemas/ContentItem'
- storyPackage:
- type: array
- description: Related content in same story
- items:
- $ref: '#/components/schemas/ContentItem'
- editorsPicks:
- type: array
- description: Editor-chosen content
- items:
- $ref: '#/components/schemas/ContentItem'
- mostViewed:
- type: array
- description: Most viewed content
- items:
- $ref: '#/components/schemas/ContentItem'
- relatedContent:
- type: array
- description: Related content items
- items:
- $ref: '#/components/schemas/ContentItem'
-
- Tag:
- type: object
- properties:
- id:
- type: string
- description: Tag identifier
- example: "katine/football"
- type:
- type: string
- description: Type of tag
- enum: [keyword, series, contributor, tone, type, blog]
- example: "keyword"
- webTitle:
- type: string
- description: Web title of the tag
- example: "Football"
- webUrl:
- type: string
- format: uri
- description: Web URL of the tag
- example: "http://www.theguardian.com/katine/football"
- apiUrl:
- type: string
- format: uri
- description: API URL of the tag
- example: "http://beta.content.guardianapis.com/katine/football"
- sectionId:
- type: string
- description: Section ID of the tag
- example: "katine"
- sectionName:
- type: string
- description: Section name of the tag
- example: "Katine"
- references:
- type: array
- items:
- $ref: '#/components/schemas/Reference'
- required:
- - id
- - type
- - webTitle
-
- TagsResponse:
- allOf:
- - $ref: '#/components/schemas/BaseResponse'
- - type: object
- properties:
- startIndex:
- type: integer
- description: Starting index of results
- example: 1
- pageSize:
- type: integer
- description: Number of items per page
- example: 10
- currentPage:
- type: integer
- description: Current page number
- example: 1
- pages:
- type: integer
- description: Total number of pages
- example: 7
- results:
- type: array
- items:
- $ref: '#/components/schemas/Tag'
- required:
- - results
-
- Section:
- type: object
- properties:
- id:
- type: string
- description: Section identifier
- example: "football"
- webTitle:
- type: string
- description: Web title of the section
- example: "Football"
- webUrl:
- type: string
- format: uri
- description: Web URL of the section
- example: "https://www.theguardian.com/football"
- apiUrl:
- type: string
- format: uri
- description: API URL of the section
- example: "https://content.guardianapis.com/football"
- editions:
- type: array
- items:
- $ref: '#/components/schemas/Edition'
- required:
- - id
- - webTitle
-
- SectionsResponse:
- allOf:
- - $ref: '#/components/schemas/BaseResponse'
- - type: object
- properties:
- results:
- type: array
- items:
- $ref: '#/components/schemas/Section'
- required:
- - results
-
- Edition:
- type: object
- properties:
- id:
- type: string
- description: Edition identifier
- example: "au"
- path:
- type: string
- description: Path of the edition
- example: "au"
- edition:
- type: string
- description: Edition name
- example: "AU"
- webTitle:
- type: string
- description: Web title of the edition
- example: "new guardian australia front page"
- webUrl:
- type: string
- format: uri
- description: Web URL of the edition
- example: "https://www.theguardian.com/au"
- apiUrl:
- type: string
- format: uri
- description: API URL of the edition
- example: "https://content.guardianapis.com/au"
- code:
- type: string
- description: Edition code
- example: "default"
- required:
- - id
- - edition
-
- EditionsResponse:
- allOf:
- - $ref: '#/components/schemas/BaseResponse'
- - type: object
- properties:
- results:
- type: array
- items:
- $ref: '#/components/schemas/Edition'
- required:
- - results
-
- Element:
- type: object
- description: Media element (image, video, audio)
- properties:
- id:
- type: string
- description: Element identifier
- relation:
- type: string
- description: Relationship to content
- type:
- type: string
- enum: [image, video, audio]
- description: Type of element
- assets:
- type: array
- items:
- $ref: '#/components/schemas/Asset'
-
- Asset:
- type: object
- description: Media asset within an element
- properties:
- type:
- type: string
- description: Asset type
- mimeType:
- type: string
- description: MIME type of the asset
- file:
- type: string
- format: uri
- description: URL of the asset file
- typeData:
- type: object
- description: Additional type-specific data
-
- Reference:
- type: object
- description: Reference data (ISBN, IMDB, etc.)
- properties:
- type:
- type: string
- description: Type of reference
- enum: [
- author, bisac-prefix, esa-cricket-match, esa-football-match,
- esa-football-team, esa-football-tournament, isbn, imdb, musicbrainz,
- musicbrainzgenre, opta-cricket-match, opta-football-match,
- opta-football-team, opta-football-tournament, pa-football-competition,
- pa-football-match, pa-football-team, r1-film, reuters-index-ric,
- reuters-stock-ric, witness-assignment
- ]
- id:
- type: string
- description: Reference identifier
-
- ContentBlocks:
- type: object
- description: Content blocks for articles and live blogs
- properties:
- main:
- $ref: '#/components/schemas/Block'
- body:
- type: array
- items:
- $ref: '#/components/schemas/Block'
-
- Block:
- type: object
- description: Individual content block
- properties:
- id:
- type: string
- description: Block identifier
- bodyHtml:
- type: string
- description: HTML content of the block
- bodyTextSummary:
- type: string
- description: Text summary of the block
- title:
- type: string
- description: Block title
- attributes:
- type: object
- description: Block attributes
- published:
- type: boolean
- description: Whether the block is published
- createdDate:
- type: string
- format: date-time
- description: Block creation date
- firstPublishedDate:
- type: string
- format: date-time
- description: First publication date
- publishedDate:
- type: string
- format: date-time
- description: Publication date
- lastModifiedDate:
- type: string
- format: date-time
- description: Last modification date
- contributors:
- type: array
- items:
- type: string
- description: Block contributors
- elements:
- type: array
- items:
- $ref: '#/components/schemas/Element'
-
-tags:
- - name: Content
- description: Search and retrieve Guardian content
- - name: Tags
- description: Manage content tags and categories
- - name: Sections
- description: Browse content sections
- - name: Editions
- description: Access regional editions
- - name: Single Item
- description: Retrieve individual items by path
\ No newline at end of file