diff --git a/Vonage.Test/Conversations/GetConversations/E2ETest.cs b/Vonage.Test/Conversations/GetConversations/E2ETest.cs index d78d0f7b5..d41a9449e 100644 --- a/Vonage.Test/Conversations/GetConversations/E2ETest.cs +++ b/Vonage.Test/Conversations/GetConversations/E2ETest.cs @@ -16,7 +16,7 @@ public class E2ETest : E2EBase public E2ETest() : base(typeof(E2ETest).Namespace) { } - + [Fact] public async Task GetConversations() { @@ -40,7 +40,7 @@ await this.Helper.VonageClient.ConversationsClient .Should() .BeSuccessAsync(SerializationTest.VerifyExpectedResponse); } - + [Fact] public async Task GetConversationsFromHalLink() { @@ -56,13 +56,13 @@ public async Task GetConversationsFromHalLink() .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserialize200)))); await this.Helper.VonageClient.ConversationsClient - .GetConversationsAsync(new GetConversationsHalLink(new Uri( - "https://api.nexmo.com/v1/users?order=desc&page_size=50&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D&date_start=2023-12-18T09%3A56%3A08Z&date_end=2023-12-18T10%3A56%3A08Z")) + .GetConversationsAsync(new GetMembersHalLink(new Uri( + "https://api.nexmo.com/v1/conversations?order=desc&page_size=50&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D&date_start=2023-12-18T09%3A56%3A08Z&date_end=2023-12-18T10%3A56%3A08Z")) .BuildRequest()) .Should() .BeSuccessAsync(SerializationTest.VerifyExpectedResponse); } - + [Fact] public async Task GetConversationsWithDefaultRequest() { diff --git a/Vonage.Test/Conversations/GetConversations/GetConversationsHalLinkTest.cs b/Vonage.Test/Conversations/GetConversations/GetConversationsHalLinkTest.cs index 3a79e2486..beacdefcc 100644 --- a/Vonage.Test/Conversations/GetConversations/GetConversationsHalLinkTest.cs +++ b/Vonage.Test/Conversations/GetConversations/GetConversationsHalLinkTest.cs @@ -9,11 +9,11 @@ namespace Vonage.Test.Conversations.GetConversations; [Trait("Category", "Request")] -public class GetConversationsHalLinkTest +public class GetMembersHalLinkTest { [Fact] public void BuildRequestForPrevious_ShouldReturnSuccess() => - new GetConversationsHalLink(new Uri("https://api.nexmo.com/v1/users?order=desc&page_size=10")) + new GetMembersHalLink(new Uri("https://api.nexmo.com/v1/users?order=desc&page_size=10")) .BuildRequest() .Should() .BeSuccess(new GetConversationsRequest @@ -24,28 +24,28 @@ public void BuildRequestForPrevious_ShouldReturnSuccess() => PageSize = 10, Order = FetchOrder.Descending, }); - + [Fact] public void BuildRequestForPrevious_ShouldReturnSuccess_WithCursor() => - new GetConversationsHalLink(new Uri( + new GetMembersHalLink(new Uri( "https://api.nexmo.com/v1/users?order=desc&page_size=10&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D")) .BuildRequest() .Map(request => request.Cursor) .Should() .BeSuccess("7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg="); - + [Fact] public void BuildRequestForPrevious_ShouldReturnSuccess_WithEndDate() => - new GetConversationsHalLink(new Uri( + new GetMembersHalLink(new Uri( "https://api.nexmo.com/v1/users?order=desc&page_size=10&date_end=2023-12-18T10%3A56%3A08Z")) .BuildRequest() .Map(request => request.EndDate) .Should() .BeSuccess(DateTimeOffset.Parse("2023-12-18T10:56:08Z", CultureInfo.InvariantCulture)); - + [Fact] public void BuildRequestForPrevious_ShouldReturnSuccess_WithStartDate() => - new GetConversationsHalLink(new Uri( + new GetMembersHalLink(new Uri( "https://api.nexmo.com/v1/users?order=desc&page_size=10&date_start=2023-12-18T09%3A56%3A08Z")) .BuildRequest() .Map(request => request.StartDate) diff --git a/Vonage.Test/Conversations/GetConversations/RequestBuilderTest.cs b/Vonage.Test/Conversations/GetConversations/RequestBuilderTest.cs index 1d884817e..5feced386 100644 --- a/Vonage.Test/Conversations/GetConversations/RequestBuilderTest.cs +++ b/Vonage.Test/Conversations/GetConversations/RequestBuilderTest.cs @@ -19,7 +19,7 @@ public void Build_ShouldHaveDefaultOrder() => .Map(request => request.Order) .Should() .BeSuccess(FetchOrder.Ascending); - + [Fact] public void Build_ShouldHaveDefaultPageSize() => GetConversationsRequest @@ -28,7 +28,7 @@ public void Build_ShouldHaveDefaultPageSize() => .Map(request => request.PageSize) .Should() .BeSuccess(10); - + [Fact] public void Build_ShouldHaveNoDefaultCursor() => GetConversationsRequest @@ -37,7 +37,7 @@ public void Build_ShouldHaveNoDefaultCursor() => .Map(request => request.Cursor) .Should() .BeSuccess(Maybe.None); - + [Fact] public void Build_ShouldHaveNoDefaultEndDate() => GetConversationsRequest @@ -46,7 +46,7 @@ public void Build_ShouldHaveNoDefaultEndDate() => .Map(request => request.EndDate) .Should() .BeSuccess(Maybe.None); - + [Fact] public void Build_ShouldHaveNoDefaultStartDate() => GetConversationsRequest @@ -55,7 +55,7 @@ public void Build_ShouldHaveNoDefaultStartDate() => .Map(request => request.StartDate) .Should() .BeSuccess(Maybe.None); - + [Fact] public void Build_ShouldReturnFailure_GivenPageSizeIsHigherThanOneHundred() => GetConversationsRequest @@ -64,7 +64,7 @@ public void Build_ShouldReturnFailure_GivenPageSizeIsHigherThanOneHundred() => .Create() .Should() .BeParsingFailure("PageSize cannot be higher than 100."); - + [Fact] public void Build_ShouldReturnFailure_GivenPageSizeIsLowerThanOne() => GetConversationsRequest @@ -73,7 +73,7 @@ public void Build_ShouldReturnFailure_GivenPageSizeIsLowerThanOne() => .Create() .Should() .BeParsingFailure("PageSize cannot be lower than 1."); - + [Fact] public void Build_ShouldSetEndDate() => GetConversationsRequest @@ -83,7 +83,7 @@ public void Build_ShouldSetEndDate() => .Map(request => request.EndDate) .Should() .BeSuccess(DateTimeOffset.Parse("2018-01-01 10:00:00", CultureInfo.InvariantCulture)); - + [Fact] public void Build_ShouldSetOrder() => GetConversationsRequest @@ -93,17 +93,20 @@ public void Build_ShouldSetOrder() => .Map(request => request.Order) .Should() .BeSuccess(FetchOrder.Descending); - - [Fact] - public void Build_ShouldSetPageSize() => + + [Theory] + [InlineData(1)] + [InlineData(50)] + [InlineData(100)] + public void Build_ShouldSetPageSize(int pageSize) => GetConversationsRequest .Build() - .WithPageSize(50) + .WithPageSize(pageSize) .Create() .Map(request => request.PageSize) .Should() - .BeSuccess(50); - + .BeSuccess(pageSize); + [Fact] public void Build_ShouldSetStartDate() => GetConversationsRequest diff --git a/Vonage.Test/Conversations/GetMembers/Data/ShouldDeserialize200-response.json b/Vonage.Test/Conversations/GetMembers/Data/ShouldDeserialize200-response.json new file mode 100644 index 000000000..4e1847441 --- /dev/null +++ b/Vonage.Test/Conversations/GetMembers/Data/ShouldDeserialize200-response.json @@ -0,0 +1,40 @@ +{ + "page_size": 10, + "_embedded": { + "members": [ + { + "id": "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + "state": "JOINED", + "_embedded": { + "user": { + "id": "USR-82e028d9-5201-4f1e-8188-604b2d3471ec", + "name": "my_user_name", + "display_name": "My User Name", + "_links": { + "self": { + "href": "https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec" + } + } + } + }, + "_links": { + "href": "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391" + } + } + ] + }, + "_links": { + "first": { + "href": "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10" + }, + "self": { + "href": "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10&cursor=88b395c167da4d94e929705cbd63b82973771e7d390d274a58e301386d5762600a3ffd799bfb3fc5190c5a0d124cdd0fc72fe6e450506b18e4e2edf9fe84c7a0" + }, + "next": { + "href": "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10&cursor=88b395c167da4d94e929705cbd63b829a650e69a39197bfd4c949f4243f60dc4babb696afa404d2f44e7775e32b967f2a1a0bb8fb259c0999ba5a4e501eaab55" + }, + "prev": { + "href": "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10&cursor=069626a3de11d2ec900dff5042197bd75f1ce41dafc3f2b2481eb9151086e59aae9dba3e3a8858dc355232d499c310fbfbec43923ff657c0de8d49ffed9f7edb" + } + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMembers/E2ETest.cs b/Vonage.Test/Conversations/GetMembers/E2ETest.cs new file mode 100644 index 000000000..cf3412100 --- /dev/null +++ b/Vonage.Test/Conversations/GetMembers/E2ETest.cs @@ -0,0 +1,59 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Vonage.Conversations; +using Vonage.Conversations.GetMembers; +using Vonage.Test.Common.Extensions; +using WireMock.ResponseBuilders; +using Xunit; + +namespace Vonage.Test.Conversations.GetMembers; + +[Trait("Category", "E2E")] +public class E2ETest : E2EBase +{ + public E2ETest() : base(typeof(E2ETest).Namespace) + { + } + + [Fact] + public async Task GetMembers() + { + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/v1/conversations/CON-123/members") + .WithParam("page_size", "50") + .WithParam("order", "desc") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .UsingGet()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserialize200)))); + await this.Helper.VonageClient.ConversationsClient + .GetMembersAsync(GetMembersRequest.Build() + .WithConversationId("CON-123") + .WithPageSize(50) + .WithOrder(FetchOrder.Descending) + .Create()) + .Should() + .BeSuccessAsync(SerializationTest.VerifyExpectedResponse); + } + + [Fact] + public async Task GetMembersFromHalLink() + { + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/v1/conversations/CON-123/members") + .WithParam("page_size", "50") + .WithParam("order", "desc") + .WithParam("cursor", "7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg=") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .UsingGet()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserialize200)))); + await this.Helper.VonageClient.ConversationsClient + .GetMembersAsync(new GetMembersHalLink(new Uri( + "https://api.nexmo.com/v1/conversations/CON-123/members?order=desc&page_size=50&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D")) + .BuildRequest()) + .Should() + .BeSuccessAsync(SerializationTest.VerifyExpectedResponse); + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMembers/RequestBuilderTest.cs b/Vonage.Test/Conversations/GetMembers/RequestBuilderTest.cs new file mode 100644 index 000000000..39eae8610 --- /dev/null +++ b/Vonage.Test/Conversations/GetMembers/RequestBuilderTest.cs @@ -0,0 +1,87 @@ +using Vonage.Common.Monads; +using Vonage.Conversations; +using Vonage.Conversations.GetMembers; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.Conversations.GetMembers; + +public class RequestBuilderTest +{ + private const string ConversationId = "CON-123"; + + [Fact] + public void Build_ShouldHaveDefaultOrder() => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .Create() + .Map(request => request.Order) + .Should() + .BeSuccess(FetchOrder.Ascending); + + [Fact] + public void Build_ShouldHaveDefaultPageSize() => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .Create() + .Map(request => request.PageSize) + .Should() + .BeSuccess(10); + + [Fact] + public void Build_ShouldHaveNoDefaultCursor() => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .Create() + .Map(request => request.Cursor) + .Should() + .BeSuccess(Maybe.None); + + [Fact] + public void Build_ShouldReturnFailure_GivenPageSizeIsHigherThanOneHundred() => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .WithPageSize(101) + .Create() + .Should() + .BeParsingFailure("PageSize cannot be higher than 100."); + + [Fact] + public void Build_ShouldReturnFailure_GivenPageSizeIsLowerThanOne() => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .WithPageSize(0) + .Create() + .Should() + .BeParsingFailure("PageSize cannot be lower than 1."); + + [Fact] + public void Build_ShouldSetOrder() => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .WithOrder(FetchOrder.Descending) + .Create() + .Map(request => request.Order) + .Should() + .BeSuccess(FetchOrder.Descending); + + [Theory] + [InlineData(1)] + [InlineData(50)] + [InlineData(100)] + public void Build_ShouldSetPageSize(int pageSize) => + GetMembersRequest + .Build() + .WithConversationId(ConversationId) + .WithPageSize(pageSize) + .Create() + .Map(request => request.PageSize) + .Should() + .BeSuccess(pageSize); +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMembers/RequestTest.cs b/Vonage.Test/Conversations/GetMembers/RequestTest.cs new file mode 100644 index 000000000..357766bd4 --- /dev/null +++ b/Vonage.Test/Conversations/GetMembers/RequestTest.cs @@ -0,0 +1,30 @@ +using Vonage.Conversations; +using Vonage.Conversations.GetMembers; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.Conversations.GetMembers; + +public class RequestTest +{ + [Theory] + [InlineData(null, null, "/v1/conversations/CON-123/members?page_size=10&order=asc")] + [InlineData(50, null, "/v1/conversations/CON-123/members?page_size=50&order=asc")] + [InlineData(null, FetchOrder.Descending, "/v1/conversations/CON-123/members?page_size=10&order=desc")] + [InlineData(50, FetchOrder.Descending, "/v1/conversations/CON-123/members?page_size=50&order=desc")] + public void GetEndpointPath_ShouldReturnApiEndpoint(int? pageSize, FetchOrder? order, string expectedEndpoint) + { + var builder = GetMembersRequest.Build().WithConversationId("CON-123"); + if (pageSize.HasValue) + { + builder = builder.WithPageSize(pageSize.Value); + } + + if (order.HasValue) + { + builder = builder.WithOrder(order.Value); + } + + builder.Create().Map(request => request.GetEndpointPath()).Should().BeSuccess(expectedEndpoint); + } +} \ No newline at end of file diff --git a/Vonage.Test/Conversations/GetMembers/SerializationTest.cs b/Vonage.Test/Conversations/GetMembers/SerializationTest.cs new file mode 100644 index 000000000..93997aea6 --- /dev/null +++ b/Vonage.Test/Conversations/GetMembers/SerializationTest.cs @@ -0,0 +1,63 @@ +using System; +using FluentAssertions; +using Vonage.Common; +using Vonage.Conversations; +using Vonage.Conversations.GetMembers; +using Vonage.Serialization; +using Vonage.Test.Common; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.Conversations.GetMembers; + +[Trait("Category", "Serialization")] +public class SerializationTest +{ + private readonly SerializationTestHelper helper = new SerializationTestHelper( + typeof(SerializationTest).Namespace, + JsonSerializerBuilder.BuildWithSnakeCase()); + + [Fact] + public void ShouldDeserialize200() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(VerifyExpectedResponse); + + internal static void VerifyExpectedResponse(GetMembersResponse response) + { + response.PageSize.Should().Be(10); + response.Embedded.Members.Should().BeEquivalentTo(new[] + { + new Member( + "MEM-63f61863-4a51-4f6b-86e1-46edebio0391", + null, + "JOINED", + null, + null, + null, + new MemberEmbedded(new MemberEmbeddedUser("USR-82e028d9-5201-4f1e-8188-604b2d3471ec", "my_user_name", + "My User Name", new HalLinks + { + Self = new HalLink( + new Uri("https://api.nexmo.com/v1/users/USR-82e028d9-5201-4f1e-8188-604b2d3471ec")), + })), + null, + null, + null, + new HalLink(new Uri( + "https://api.nexmo.com/v1/conversations/CON-63f61863-4a51-4f6b-86e1-46edebio0391/members/MEM-63f61863-4a51-4f6b-86e1-46edebio0391"))), + }); + response.Links.First.Href.Should() + .Be(new Uri( + "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10")); + response.Links.Self.Href.Should() + .Be(new Uri( + "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10&cursor=88b395c167da4d94e929705cbd63b82973771e7d390d274a58e301386d5762600a3ffd799bfb3fc5190c5a0d124cdd0fc72fe6e450506b18e4e2edf9fe84c7a0")); + response.Links.Next.Href.Should() + .Be(new Uri( + "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10&cursor=88b395c167da4d94e929705cbd63b829a650e69a39197bfd4c949f4243f60dc4babb696afa404d2f44e7775e32b967f2a1a0bb8fb259c0999ba5a4e501eaab55")); + response.Links.Previous.Href.Should() + .Be(new Uri( + "https://api.nexmo.com/v1/conversations/CON-d66d47de-5bcb-4300-94f0-0c9d4b948e9a/members?order=desc&page_size=10&cursor=069626a3de11d2ec900dff5042197bd75f1ce41dafc3f2b2481eb9151086e59aae9dba3e3a8858dc355232d499c310fbfbec43923ff657c0de8d49ffed9f7edb")); + } +} \ No newline at end of file diff --git a/Vonage.Test/Vonage.Test.csproj b/Vonage.Test/Vonage.Test.csproj index ef40fa0b3..55275e48e 100644 --- a/Vonage.Test/Vonage.Test.csproj +++ b/Vonage.Test/Vonage.Test.csproj @@ -1158,6 +1158,9 @@ PreserveNewest + + PreserveNewest + diff --git a/Vonage/Conversations/ConversationsClient.cs b/Vonage/Conversations/ConversationsClient.cs index b897322eb..c3ccacfb5 100644 --- a/Vonage/Conversations/ConversationsClient.cs +++ b/Vonage/Conversations/ConversationsClient.cs @@ -6,6 +6,7 @@ using Vonage.Conversations.GetConversation; using Vonage.Conversations.GetConversations; using Vonage.Conversations.GetMember; +using Vonage.Conversations.GetMembers; using Vonage.Conversations.GetUserConversations; using Vonage.Conversations.UpdateConversation; using Vonage.Serialization; @@ -44,6 +45,10 @@ public Task> GetMemberAsync(Result request) => public Task> GetConversationsAsync(Result request) => this.vonageClient.SendWithResponseAsync(request); + /// + public Task> GetMembersAsync(Result request) => + this.vonageClient.SendWithResponseAsync(request); + /// public Task> GetUserConversationsAsync( Result request) => diff --git a/Vonage/Conversations/GetConversations/GetConversationsResponse.cs b/Vonage/Conversations/GetConversations/GetConversationsResponse.cs index c3b3e66c3..13e759e22 100644 --- a/Vonage/Conversations/GetConversations/GetConversationsResponse.cs +++ b/Vonage/Conversations/GetConversations/GetConversationsResponse.cs @@ -22,7 +22,7 @@ public record GetConversationsResponse( EmbeddedConversations Embedded, [property: JsonPropertyName("_links")] [property: JsonPropertyOrder(2)] - HalLinks Links); + HalLinks Links); /// /// Represents a list of conversations. @@ -34,7 +34,7 @@ public record EmbeddedConversations(Conversation[] Conversations); /// Represents a link to another resource. /// /// Hyperlink reference. -public record GetConversationsHalLink(Uri Href) +public record GetMembersHalLink(Uri Href) { /// /// Transforms the link into a GetUsersRequest using the cursor pagination. @@ -50,15 +50,15 @@ public Result BuildRequest() builder = ApplyOptionalEndDate(parameters, builder); return builder.Create(); } - + private static IBuilderForOptional ApplyOptionalStartDate(QueryParameters parameters, IBuilderForOptional builder) => parameters.StartDate.Match(builder.WithStartDate, () => builder); - + private static IBuilderForOptional ApplyOptionalEndDate(QueryParameters parameters, IBuilderForOptional builder) => parameters.EndDate.Match(builder.WithEndDate, () => builder); - + private static QueryParameters ExtractQueryParameters(Uri uri) { var queryParameters = HttpUtility.ParseQueryString(uri.Query); @@ -71,7 +71,7 @@ private static QueryParameters ExtractQueryParameters(Uri uri) startDate.Map(value => DateTimeOffset.Parse(value, CultureInfo.InvariantCulture)), endDate.Map(value => DateTimeOffset.Parse(value, CultureInfo.InvariantCulture))); } - + private record QueryParameters( Maybe Cursor, int PageSize, diff --git a/Vonage/Conversations/GetMembers/GetMembersRequest.cs b/Vonage/Conversations/GetMembers/GetMembersRequest.cs new file mode 100644 index 000000000..b50288450 --- /dev/null +++ b/Vonage/Conversations/GetMembers/GetMembersRequest.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Net.Http; +using EnumsNET; +using Vonage.Common; +using Vonage.Common.Client; +using Vonage.Common.Monads; + +namespace Vonage.Conversations.GetMembers; + +/// +public readonly struct GetMembersRequest : IVonageRequest +{ + /// + /// The cursor to start returning results from. You are not expected to provide this manually, but to follow the url + /// provided in _links.next.href or _links.prev.href in the response which contains a cursor value. + /// + public Maybe Cursor { get; internal init; } + + /// + /// Defines the data ordering. + /// + public FetchOrder Order { get; internal init; } + + /// + /// Number of results per page. + /// + public int PageSize { get; internal init; } + + /// + /// + public string ConversationId { get; internal init; } + + /// + public HttpRequestMessage BuildRequestMessage() => VonageRequestBuilder + .Initialize(HttpMethod.Get, this.GetEndpointPath()) + .Build(); + + /// + public string GetEndpointPath() => UriHelpers.BuildUri($"/v1/conversations/{this.ConversationId}/members", + this.GetQueryStringParameters()); + + /// + /// Initializes a builder. + /// + /// The builder. + public static IBuilderForConversationId Build() => new GetMembersRequestBuilder(Maybe.None); + + private Dictionary GetQueryStringParameters() + { + var parameters = new Dictionary + { + {"page_size", this.PageSize.ToString()}, + {"order", this.Order.AsString(EnumFormat.Description)}, + }; + this.Cursor.IfSome(value => parameters.Add("cursor", value)); + return parameters; + } +} \ No newline at end of file diff --git a/Vonage/Conversations/GetMembers/GetMembersRequestBuilder.cs b/Vonage/Conversations/GetMembers/GetMembersRequestBuilder.cs new file mode 100644 index 000000000..2c02cd7d1 --- /dev/null +++ b/Vonage/Conversations/GetMembers/GetMembersRequestBuilder.cs @@ -0,0 +1,97 @@ +using Vonage.Common.Client; +using Vonage.Common.Monads; +using Vonage.Common.Validation; + +namespace Vonage.Conversations.GetMembers; + +internal class GetMembersRequestBuilder : IBuilderForConversationId, IBuilderForOptional +{ + private const int MaximumPageSize = 100; + private const int MinimumPageSize = 1; + private readonly Maybe cursor; + private string conversationId; + private FetchOrder fetchOrder = FetchOrder.Ascending; + private int pageSize = 10; + + internal GetMembersRequestBuilder(Maybe cursor) => this.cursor = cursor; + + /// + public IBuilderForOptional WithConversationId(string value) => + new GetMembersRequestBuilder(this.cursor) + { + pageSize = this.pageSize, + fetchOrder = this.fetchOrder, + conversationId = value, + }; + + /// + public Result Create() => Result.FromSuccess(new GetMembersRequest + { + PageSize = this.pageSize, + Cursor = this.cursor, + ConversationId = this.conversationId, + Order = this.fetchOrder, + }) + .Map(InputEvaluation.Evaluate) + .Bind(evaluation => evaluation.WithRules(VerifyConversationId, VerifyMinimumPageSize, VerifyMaximumPageSize)); + + /// + public IBuilderForOptional WithOrder(FetchOrder value) => + new GetMembersRequestBuilder(this.cursor) + { + pageSize = this.pageSize, + fetchOrder = value, + conversationId = this.conversationId, + }; + + /// + public IBuilderForOptional WithPageSize(int value) => + new GetMembersRequestBuilder(this.cursor) + { + pageSize = value, + fetchOrder = this.fetchOrder, + conversationId = this.conversationId, + }; + + private static Result VerifyConversationId(GetMembersRequest request) => + InputValidation.VerifyNotEmpty(request, request.ConversationId, nameof(request.ConversationId)); + + private static Result VerifyMaximumPageSize(GetMembersRequest request) => + InputValidation.VerifyLowerOrEqualThan(request, request.PageSize, MaximumPageSize, nameof(request.PageSize)); + + private static Result VerifyMinimumPageSize(GetMembersRequest request) => + InputValidation.VerifyHigherOrEqualThan(request, request.PageSize, MinimumPageSize, nameof(request.PageSize)); +} + +/// +/// Represents a builder for optional values. +/// +public interface IBuilderForOptional : IVonageRequestBuilder +{ + /// + /// Sets the order on the builder. + /// + /// The order. + /// The builder. + IBuilderForOptional WithOrder(FetchOrder value); + + /// + /// Sets the page size on the builder. + /// + /// The page size. + /// The builder. + IBuilderForOptional WithPageSize(int value); +} + +/// +/// Represents a builder for ConversationId. +/// +public interface IBuilderForConversationId +{ + /// + /// Sets the ConversationId on the builder. + /// + /// The conversation Id. + /// The builder. + IBuilderForOptional WithConversationId(string value); +} \ No newline at end of file diff --git a/Vonage/Conversations/GetMembers/GetMembersResponse.cs b/Vonage/Conversations/GetMembers/GetMembersResponse.cs new file mode 100644 index 000000000..3e10b7c5e --- /dev/null +++ b/Vonage/Conversations/GetMembers/GetMembersResponse.cs @@ -0,0 +1,64 @@ +using System; +using System.Text.Json.Serialization; +using System.Web; +using EnumsNET; +using Vonage.Common; +using Vonage.Common.Monads; + +namespace Vonage.Conversations.GetMembers; + +public record GetMembersResponse( + [property: JsonPropertyName("page_size")] + [property: JsonPropertyOrder(0)] + int PageSize, + [property: JsonPropertyName("_embedded")] + [property: JsonPropertyOrder(1)] + EmbeddedMembers Embedded, + [property: JsonPropertyName("_links")] + [property: JsonPropertyOrder(2)] + HalLinks Links); + +/// +/// Represents a list of conversations. +/// +/// List of conversations matching the provided filter. +public record EmbeddedMembers(Member[] Members); + +/// +/// Represents a link to another resource. +/// +/// Hyperlink reference. +public record GetMembersHalLink(Uri Href) +{ + /// + /// Transforms the link into a GetUsersRequest using the cursor pagination. + /// + /// + public Result BuildRequest() + { + var parameters = ExtractQueryParameters(this.Href); + return new GetMembersRequestBuilder(parameters.Cursor) + .WithConversationId(parameters.ConversationId) + .WithPageSize(parameters.PageSize) + .WithOrder(parameters.Order).Create(); + } + + private static QueryParameters ExtractQueryParameters(Uri uri) + { + var queryParameters = HttpUtility.ParseQueryString(uri.Query); + return new QueryParameters( + queryParameters["cursor"], + ExtractConversationId(uri), + int.Parse(queryParameters["page_size"]), + Enums.Parse(queryParameters["order"], false, EnumFormat.Description)); + } + + private static string ExtractConversationId(Uri uri) => uri.AbsolutePath.Replace("/v1/conversations/", string.Empty) + .Replace("/members", string.Empty); + + private record QueryParameters( + Maybe Cursor, + string ConversationId, + int PageSize, + FetchOrder Order); +} \ No newline at end of file diff --git a/Vonage/Conversations/GetUserConversations/GetUserConversationsResponse.cs b/Vonage/Conversations/GetUserConversations/GetUserConversationsResponse.cs index b609b727d..6d55c5048 100644 --- a/Vonage/Conversations/GetUserConversations/GetUserConversationsResponse.cs +++ b/Vonage/Conversations/GetUserConversations/GetUserConversationsResponse.cs @@ -18,7 +18,7 @@ public record GetUserConversationsResponse( EmbeddedConversations Embedded, [property: JsonPropertyName("_links")] [property: JsonPropertyOrder(2)] - HalLinks Links); + HalLinks Links); /// /// Represents a list of conversations. @@ -49,21 +49,21 @@ public Result BuildRequest() builder = ApplyOptionalIncludeCustomData(parameters, builder); return builder.Create(); } - + private static IBuilderForOptional ApplyOptionalIncludeCustomData(QueryParameters parameters, IBuilderForOptional builder) => parameters.IncludeCustomData.IfNone(false) ? builder.IncludeCustomData() : builder; - + private static IBuilderForOptional ApplyOptionalState(QueryParameters parameters, IBuilderForOptional builder) => parameters.State.Match(builder.WithState, () => builder); - + private static IBuilderForOptional ApplyOptionalStartDate(QueryParameters parameters, IBuilderForOptional builder) => parameters.StartDate.Match(builder.WithStartDate, () => builder); - + private static IBuilderForOptional ApplyOptionalOrderBy(QueryParameters parameters, IBuilderForOptional builder) => parameters.OrderBy.Match(builder.WithOrderBy, () => builder); - + private static QueryParameters ExtractQueryParameters(Uri uri) { var queryParameters = HttpUtility.ParseQueryString(uri.Query); @@ -81,7 +81,7 @@ private static QueryParameters ExtractQueryParameters(Uri uri) includeCustomData.Match(bool.Parse, () => false), state.Map(value => Enums.Parse(value, false, EnumFormat.Description))); } - + private record QueryParameters( string UserId, Maybe Cursor, diff --git a/Vonage/Conversations/IConversationsClient.cs b/Vonage/Conversations/IConversationsClient.cs index f79e48455..f54d04a03 100644 --- a/Vonage/Conversations/IConversationsClient.cs +++ b/Vonage/Conversations/IConversationsClient.cs @@ -5,6 +5,7 @@ using Vonage.Conversations.GetConversation; using Vonage.Conversations.GetConversations; using Vonage.Conversations.GetMember; +using Vonage.Conversations.GetMembers; using Vonage.Conversations.GetUserConversations; using Vonage.Conversations.UpdateConversation; @@ -50,6 +51,13 @@ public interface IConversationsClient /// Success or Failure. Task> GetConversationsAsync(Result request); + /// + /// Retrieves members. + /// + /// The request. + /// Success or Failure. + Task> GetMembersAsync(Result request); + /// /// Retrieves conversations for a user. ///