diff --git a/src/AnthropicClient/Json/ContentConverter.cs b/src/AnthropicClient/Json/ContentConverter.cs index e0183d4..d7fbd14 100644 --- a/src/AnthropicClient/Json/ContentConverter.cs +++ b/src/AnthropicClient/Json/ContentConverter.cs @@ -16,6 +16,7 @@ class ContentConverter : JsonConverter { ContentType.Text => JsonSerializer.Deserialize(root.GetRawText(), options)!, ContentType.Image => JsonSerializer.Deserialize(root.GetRawText(), options)!, + ContentType.Document => JsonSerializer.Deserialize(root.GetRawText(), options)!, ContentType.ToolUse => JsonSerializer.Deserialize(root.GetRawText(), options)!, ContentType.ToolResult => JsonSerializer.Deserialize(root.GetRawText(), options)!, _ => throw new JsonException($"Unknown content type: {type}") diff --git a/src/AnthropicClient/Models/DocumentContent.cs b/src/AnthropicClient/Models/DocumentContent.cs new file mode 100644 index 0000000..1389d84 --- /dev/null +++ b/src/AnthropicClient/Models/DocumentContent.cs @@ -0,0 +1,56 @@ +using System.Text.Json.Serialization; + +using AnthropicClient.Utils; + +namespace AnthropicClient.Models; + +/// +/// Represents content from a document that is part of a message. +/// +public class DocumentContent : Content +{ + /// + /// Gets the source of the document. + /// + public DocumentSource Source { get; init; } = new(); + + [JsonConstructor] + internal DocumentContent() + { + } + + private void Validate(string mediaType, string data) + { + ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType)); + ArgumentValidator.ThrowIfNull(data, nameof(data)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The media type of the document. + /// The data of the document. + /// Thrown when the media type or data is null. + /// A new instance of the class. + public DocumentContent(string mediaType, string data) : base(ContentType.Document) + { + Validate(mediaType, data); + + Source = new(mediaType, data); + } + + /// + /// Initializes a new instance of the class. + /// + /// The media type of the document. + /// The data of the document. + /// The cache control to be used for the content. + /// A new instance of the class. + /// Thrown when the media type, data, or cache control is null. + public DocumentContent(string mediaType, string data, CacheControl cacheControl) : base(ContentType.Document, cacheControl) + { + Validate(mediaType, data); + + Source = new(mediaType, data); + } +} \ No newline at end of file diff --git a/src/AnthropicClient/Models/DocumentSource.cs b/src/AnthropicClient/Models/DocumentSource.cs new file mode 100644 index 0000000..e18f978 --- /dev/null +++ b/src/AnthropicClient/Models/DocumentSource.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +using AnthropicClient.Utils; + +namespace AnthropicClient.Models; + +/// +/// Represents a document source. +/// +public class DocumentSource +{ + /// + /// Gets the media type of the document. + /// + [JsonPropertyName("media_type")] + public string MediaType { get; init; } = string.Empty; + + /// + /// Gets the data of the document. + /// + public string Data { get; init; } = string.Empty; + + /// + /// Gets the type of encoding of the document data. + /// + public string Type { get; init; } = "base64"; + + [JsonConstructor] + internal DocumentSource() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The media type of the document. + /// The data of the document. + /// Thrown when the media type is invalid. + /// Thrown when the media type or data is null. + /// A new instance of the class. + public DocumentSource(string mediaType, string data) + { + ArgumentValidator.ThrowIfNull(mediaType, nameof(mediaType)); + ArgumentValidator.ThrowIfNull(data, nameof(data)); + + MediaType = mediaType; + Data = data; + } +} \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Unit/Models/DocumentContentTests.cs b/tests/AnthropicClient.Tests/Unit/Models/DocumentContentTests.cs new file mode 100644 index 0000000..cec42f1 --- /dev/null +++ b/tests/AnthropicClient.Tests/Unit/Models/DocumentContentTests.cs @@ -0,0 +1,119 @@ +namespace AnthropicClient.Tests.Unit.Models; + +public class DocumentContentTests : SerializationTest +{ + private readonly string _testJson = @"{ + ""source"": { + ""media_type"": ""application/pdf"", + ""data"": ""data"", + ""type"": ""base64"" + }, + ""type"": ""document"" + }"; + + private readonly string _testJsonWithCacheControl = @"{ + ""source"": { + ""media_type"": ""application/pdf"", + ""data"": ""data"", + ""type"": ""base64"" + }, + ""cache_control"": { ""type"": ""ephemeral"" }, + ""type"": ""document"" + }"; + + [Fact] + public void Constructor_WhenCalled_ItShouldInitializeSource() + { + var expectedMediaType = "application/pdf"; + var expectedData = "data"; + + var result = new DocumentContent(expectedMediaType, expectedData); + + result.Source.Should().BeEquivalentTo(new DocumentSource(expectedMediaType, expectedData)); + } + + [Fact] + public void Constructor_WhenCalledAndMediaTypeIsNull_ItShouldThrowArgumentNullException() + { + var expectedData = "data"; + + var action = () => new DocumentContent(null!, expectedData); + + action.Should().Throw(); + } + + [Fact] + public void Constructor_WhenCalledAndDataIsNull_ItShouldThrowArgumentNullException() + { + var expectedMediaType = "application/pdf"; + + var action = () => new DocumentContent(expectedMediaType, null!); + + action.Should().Throw(); + } + + [Fact] + public void Constructor_WhenCalledWithCacheControl_ItShouldInitializeSourceAndCacheControl() + { + var expectedMediaType = "application/pdf"; + var expectedData = "data"; + var cacheControl = new EphemeralCacheControl(); + + var result = new DocumentContent(expectedMediaType, expectedData, cacheControl); + + result.Source.Should().BeEquivalentTo(new DocumentSource(expectedMediaType, expectedData)); + result.CacheControl.Should().BeSameAs(cacheControl); + } + + [Fact] + public void Constructor_WhenCalledWithCacheControlAndMediaTypeIsNull_ItShouldThrowArgumentNullException() + { + var expectedData = "data"; + var cacheControl = new EphemeralCacheControl(); + + var action = () => new DocumentContent(null!, expectedData, cacheControl); + + action.Should().Throw(); + } + + [Fact] + public void Constructor_WhenCalledWithCacheControlAndDataIsNull_ItShouldThrowArgumentNullException() + { + var expectedMediaType = "application/pdf"; + var cacheControl = new EphemeralCacheControl(); + + var action = () => new DocumentContent(expectedMediaType, null!, cacheControl); + + action.Should().Throw(); + } + + [Fact] + public void JsonSerialization_WhenSerialized_ItShouldHaveExpectedShape() + { + var content = new DocumentContent("application/pdf", "data"); + + var actual = Serialize(content); + + JsonAssert.Equal(_testJson, actual); + } + + [Fact] + public void JsonSerialization_WhenSerializedWithCacheControl_ItShouldHaveExpectedShape() + { + var content = new DocumentContent("application/pdf", "data", new EphemeralCacheControl()); + + var actual = Serialize(content); + + JsonAssert.Equal(_testJsonWithCacheControl, actual); + } + + [Fact] + public void JsonDeserialization_WhenDeserialized_ItShouldHaveExpectedShape() + { + var expected = new DocumentContent("application/pdf", "data"); + + var actual = Deserialize(_testJson); + + actual.Should().BeEquivalentTo(expected); + } +} \ No newline at end of file diff --git a/tests/AnthropicClient.Tests/Unit/Models/DocumentSourceTests.cs b/tests/AnthropicClient.Tests/Unit/Models/DocumentSourceTests.cs new file mode 100644 index 0000000..60e2ec3 --- /dev/null +++ b/tests/AnthropicClient.Tests/Unit/Models/DocumentSourceTests.cs @@ -0,0 +1,49 @@ +namespace AnthropicClient.Tests.Unit.Models; + +public class DocumentSourceTests +{ + [Fact] + public void Constructor_WhenCalledWithValidArguments_ItShouldSetProperties() + { + var mediaType = "application/pdf"; + var data = "base64data"; + + var source = new DocumentSource(mediaType, data); + + source.MediaType.Should().Be(mediaType); + source.Data.Should().Be(data); + } + + [Fact] + public void Constructor_WhenCalledWithNullMediaType_ItShouldThrowArgumentNullException() + { + string? mediaType = null; + var data = "base64data"; + + var action = () => new DocumentSource(mediaType!, data); + + action.Should().Throw(); + } + + [Fact] + public void Constructor_WhenCalledWithNullData_ItShouldThrowArgumentNullException() + { + var mediaType = "application/pdf"; + string? data = null; + + var action = () => new DocumentSource(mediaType, data!); + + action.Should().Throw(); + } + + [Fact] + public void Constructor_WhenCalled_ItShouldHaveTypePropertySetToBase64() + { + var mediaType = "application/pdf"; + var data = "base64data"; + + var source = new DocumentSource(mediaType, data); + + source.Type.Should().Be("base64"); + } +} \ No newline at end of file