From c427777dca199bba7dcdbb32c1b964a0fb691391 Mon Sep 17 00:00:00 2001 From: Jericho Date: Sat, 2 Nov 2024 13:45:48 -0400 Subject: [PATCH 01/16] Fix integration test --- Source/ZoomNet.IntegrationTests/Tests/Meetings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs b/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs index 211c0903..7ea128a7 100644 --- a/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs +++ b/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs @@ -85,7 +85,7 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie // Scheduled meeting var start = DateTime.UtcNow.AddMonths(1); var duration = 30; - var newScheduledMeeting = await client.Meetings.CreateScheduledMeetingAsync(myUser.Id, "ZoomNet Integration Testing: scheduled meeting", "The agenda", start, duration, TimeZones.UTC, "pass@word!", settings, trackingFields, null, true, false, cancellationToken).ConfigureAwait(false); + var newScheduledMeeting = await client.Meetings.CreateScheduledMeetingAsync(myUser.Id, "ZoomNet Integration Testing: scheduled meeting", "The agenda", start, duration, TimeZones.UTC, null, settings, trackingFields, null, true, false, cancellationToken).ConfigureAwait(false); await log.WriteLineAsync($"Scheduled meeting {newScheduledMeeting.Id} created").ConfigureAwait(false); var updatedSettings = new MeetingSettings() { Audio = AudioType.Voip }; From 2cf486b747441aec541d37d50685c071f21e5b3d Mon Sep 17 00:00:00 2001 From: Jericho Date: Tue, 5 Nov 2024 10:38:06 -0500 Subject: [PATCH 02/16] Fix typo in comment --- Source/ZoomNet/Resources/Chat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet/Resources/Chat.cs b/Source/ZoomNet/Resources/Chat.cs index b7eec5f6..a735bd6e 100644 --- a/Source/ZoomNet/Resources/Chat.cs +++ b/Source/ZoomNet/Resources/Chat.cs @@ -83,7 +83,7 @@ public Task UpdateAccountChannelAsync(string userId, string channelId, string na // additional properties and Zoom does not allow us to update these additional // properties (e.g.: "allow_to_add_external_users"). // - // If we include the addtional properties, the Zoom API responds with a misleading + // If we include the additional properties, the Zoom API responds with a misleading // error message: {"code":300,"message":"Request Body should be a valid JSON object."} // // Contrary to what the error message says, the body contains a valid JSON object but From d990b84d36f150e2ab9a4a54324b71771cb99fa6 Mon Sep 17 00:00:00 2001 From: pvgritsenko-ansible <165666478+pvgritsenko-ansible@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:16:19 +0300 Subject: [PATCH 03/16] Update call handling settings and get user presence status (#375) * Get presence status endpoint added * UpdateCallHandlingSettingsAsync method implemented * Add PresenceStatusResponse to the ZoomNetJsonSerializerContext * PresenceStatusResponse model nullable properties * Remove extra validation in UpdateCallHandlingSettingsAsync method; Typo fix --------- Co-authored-by: P. Gritsenko Co-authored-by: Jericho <112710+Jericho@users.noreply.github.com> --- .../Json/ZoomNetJsonSerializerContext.cs | 36 ++ .../BusyOnAnotherCallActionType.cs | 63 ++++ .../CallDistributionSettings.cs | 38 +++ .../CallForwardingChildSubsettings.cs | 40 +++ .../CallForwardingSubsettings.cs | 31 ++ .../CallHandlingSettingType.cs | 28 ++ .../CallHandlingSubsettings.cs | 322 ++++++++++++++++++ .../CallHandlingSubsettingsBase.cs | 16 + .../CallHandlingSubsettingsType.cs | 34 ++ .../CallNotAnswerActionType.cs | 104 ++++++ .../CustomHoursChildSubsettings.cs | 35 ++ .../CustomHoursSubsettings.cs | 38 +++ .../CallHandlingSettings/CustomHoursType.cs | 23 ++ .../CallHandlingSettings/ExternalContact.cs | 16 + .../HolidaySubsettings.cs | 41 +++ .../CallHandlingSettings/RingModeType.cs | 34 ++ Source/ZoomNet/Models/PresenceStatus.cs | 20 +- .../ZoomNet/Models/PresenceStatusResponse.cs | 31 ++ Source/ZoomNet/Resources/IPhone.cs | 22 ++ Source/ZoomNet/Resources/IUsers.cs | 9 + Source/ZoomNet/Resources/Phone.cs | 22 ++ Source/ZoomNet/Resources/Users.cs | 9 + 22 files changed, 1008 insertions(+), 4 deletions(-) create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/BusyOnAnotherCallActionType.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallDistributionSettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallForwardingChildSubsettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallForwardingSubsettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSettingType.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsType.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CallNotAnswerActionType.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CustomHoursChildSubsettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CustomHoursSubsettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/CustomHoursType.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/ExternalContact.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/HolidaySubsettings.cs create mode 100644 Source/ZoomNet/Models/CallHandlingSettings/RingModeType.cs create mode 100644 Source/ZoomNet/Models/PresenceStatusResponse.cs diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs index 0c70f0cb..8a1614fe 100644 --- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs +++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs @@ -21,6 +21,20 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrant))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrantInfo))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType))] [JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlan))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlanType))] @@ -166,6 +180,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.PollStatus))] [JsonSerializable(typeof(ZoomNet.Models.PollType))] [JsonSerializable(typeof(ZoomNet.Models.PresenceStatus))] + [JsonSerializable(typeof(ZoomNet.Models.PresenceStatusResponse))] [JsonSerializable(typeof(ZoomNet.Models.PronounDisplayType))] [JsonSerializable(typeof(ZoomNet.Models.PurchasingTimeFrame))] [JsonSerializable(typeof(ZoomNet.Models.QualityOfService.CpuUsage), TypeInfoPropertyName = "QualityOfServiceCpuUsage")] @@ -322,6 +337,20 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType[]))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrant[]))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrantInfo[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType[]))] [JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType[]))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlan[]))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlanType[]))] @@ -469,6 +498,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.PollStatus[]))] [JsonSerializable(typeof(ZoomNet.Models.PollType[]))] [JsonSerializable(typeof(ZoomNet.Models.PresenceStatus[]))] + [JsonSerializable(typeof(ZoomNet.Models.PresenceStatusResponse[]))] [JsonSerializable(typeof(ZoomNet.Models.PronounDisplayType[]))] [JsonSerializable(typeof(ZoomNet.Models.PurchasingTimeFrame[]))] [JsonSerializable(typeof(ZoomNet.Models.QualityOfService.CpuUsage[]), TypeInfoPropertyName = "QualityOfServiceCpuUsageArray")] @@ -615,6 +645,12 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.AudioType?))] [JsonSerializable(typeof(ZoomNet.Models.AuthenticationType?))] [JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType?))] [JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType?))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlanType?))] [JsonSerializable(typeof(ZoomNet.Models.CallLogCalleeNumberType?))] diff --git a/Source/ZoomNet/Models/CallHandlingSettings/BusyOnAnotherCallActionType.cs b/Source/ZoomNet/Models/CallHandlingSettings/BusyOnAnotherCallActionType.cs new file mode 100644 index 00000000..908e54ef --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/BusyOnAnotherCallActionType.cs @@ -0,0 +1,63 @@ +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// The action to take when the user is busy on another call. + /// + public enum BusyOnAnotherCallActionType + { + /// + /// Forward to a Voicemail. + /// + ForwardToVoicemail = 1, + + /// + /// Forward to the User. + /// + ForwardToUser = 2, + + /// + /// Forward to the Common Area. + /// + ForwardToCommonArea = 4, + + /// + /// Forward to the Auto Receptionist. + /// + ForwardToAutoReceptionist = 6, + + /// + /// Forward to a Call Queue. + /// + ForwardToCallQueue = 7, + + /// + /// Forward to a Shared Line Group. + /// + ForwardToSharedLineGroup = 8, + + /// + /// Forward to an External Contact. + /// + ForwardToExternalContact = 9, + + /// + /// Forward to an external Phone Number. + /// + ForwardToPhoneNumber = 10, + + /// + /// Play a message, then disconnect. + /// + PlayMessageThenDisconnect = 12, + + /// + /// Call waiting. + /// + CallWaiting = 21, + + /// + /// Play a busy signal. + /// + PlayBusySignal = 22 + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallDistributionSettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallDistributionSettings.cs new file mode 100644 index 00000000..52955fa9 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallDistributionSettings.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Call distribution model for settings. + /// + public class CallDistributionSettings + { + /// + /// Gets or sets a value indicating whether the maximum number of calls that can be handled simultaneously is less than half of the total amount of available call queue members. + /// Note that the first incoming call may not be answered first. + /// + [JsonPropertyName("handle_multiple_calls")] + public bool? HandleMultipleCalls { get; set; } + + /// + /// Gets or sets the ringing duration for each member. Allowed values: 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60. + /// + [JsonPropertyName("ring_duration")] + public int? RingDuration { get; set; } + + /// + /// Gets or sets the call distribution ring mode. + /// + [JsonPropertyName("ring_mode")] + public RingModeType? RingMode { get; set; } + + /// + /// Gets or sets the value indicating whether: + /// 1. Devices with Zoom app or client not launched and mobile phone with screen locked will be skipped; + /// 2. Phone numbers added to user's call handling settings will be skipped. + /// Required except for simultaneous ring mode. + /// + [JsonPropertyName("skip_offline_device_phone_number")] + public bool? SkipOfflineDevicePhoneNumber { get; set; } + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallForwardingChildSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallForwardingChildSubsettings.cs new file mode 100644 index 00000000..b296bc25 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallForwardingChildSubsettings.cs @@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// The call forwarding route settings. It's only required for the . + /// + public class CallForwardingChildSubsettings + { + /// + /// Gets or sets the external phone number's description. + /// + [JsonPropertyName("description")] + public string Description { get; set; } + + /// + /// Gets or sets a value indicating whether to receive a call. + /// + [JsonPropertyName("enable")] + public bool? Enable { get; set; } + + /// + /// Gets or sets the call forwarding's ID. + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Gets or sets the external phone number in E.164 format format. + /// + [JsonPropertyName("phone_number")] + public string PhoneNumber { get; set; } + + /// + /// Gets or sets the external contact to where the call will be forwarded. + /// + [JsonPropertyName("external_contact")] + public ExternalContact ExternalContact { get; set; } + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallForwardingSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallForwardingSubsettings.cs new file mode 100644 index 00000000..27c24dc4 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallForwardingSubsettings.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Call forwarding subsettings. + /// + public class CallForwardingSubsettings : CallHandlingSubsettingsBase + { + /// + /// Gets or sets a value indicating whether the user must press "1" before the call connects, when a call is forwarded to a personal phone number. + /// Enable this option to ensure missed calls do not reach to your personal voicemail. + /// Press 1 is always enabled and is required for callQueue type extension calls. + /// + [JsonPropertyName("sub_setting_type")] + public bool? RequirePress1BeforeConnecting { get; set; } + + /// + /// Gets or sets the list of call forwarding settings. + /// + [JsonPropertyName("call_forwarding_settings")] + public List CallForwardingSettings { get; set; } + + /// + /// Gets the type of sub-setting. + /// + [JsonIgnore] + public override CallHandlingSubsettingsType SubsettingType => CallHandlingSubsettingsType.CallForwarding; + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSettingType.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSettingType.cs new file mode 100644 index 00000000..a88a26a1 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSettingType.cs @@ -0,0 +1,28 @@ +using System.Runtime.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Call handling settings type. + /// + public enum CallHandlingSettingType + { + /// + /// Business hours. + /// + [EnumMember(Value = "business_hours")] + BusinessHours, + + /// + /// Closed hours. + /// + [EnumMember(Value = "closed_hours")] + ClosedHours, + + /// + /// Holidays hours. + /// + [EnumMember(Value = "holiday_hours")] + HolidayHours + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs new file mode 100644 index 00000000..36274d67 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs @@ -0,0 +1,322 @@ +// Ignore Spelling: Voicemail + +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// The model of call handling subsettings. + /// + public class CallHandlingSubsettings : CallHandlingSubsettingsBase + { + /// + /// Gets or sets a value indicating whether to allow the callers to check voicemails. + /// This field is only available in the following scenarios: + /// - The is set to 1 (Forward to a voicemail). + /// - The is set to 1 (Forward to a voicemail).(Only applicable to the User) + /// + [JsonPropertyName("allow_callers_check_voicemail")] + public bool? AllowCallersCheckVoicemail { get; set; } + + /// + /// Gets or sets a value indicating whether the queue members to set their own business hours. + /// This field allows queue members' business Hours to override the default hours of the call queue. + /// + [JsonPropertyName("allow_members_to_reset")] + public bool? AllowMembersToReset { get; set; } + + /// + /// Gets or sets the audio while connecting the prompt ID. + /// This option can select the audio played for the inbound callers when they are waiting to be routed to the next available call queue member. + /// Options: + /// - empty char - default; + /// - 0 - disable. + /// + [JsonPropertyName("audio_while_connecting_id")] + public string AudioWhileConnectingId { get; set; } + + /// + /// Gets or sets the action to take when a call is not answered. + /// + [JsonPropertyName("call_not_answer_action")] + public CallNotAnswerActionType? CallNotAnswerAction { get; set; } + + /// + /// Gets or sets the action to take when the user is busy on another call. + /// + [JsonPropertyName("busy_on_another_call_action")] + public BusyOnAnotherCallActionType? BusyOnAnotherCallAction { get; set; } + + /// + /// Gets or sets this option to distribute incoming calls. + /// If Sequential or Rotating ring mode is selected , + /// calls will ring for a specific time before trying the next available queue member. + /// + [JsonPropertyName("busy_on_another_call_action")] + public CallDistributionSettings CallDistribution { get; set; } + + /// + /// Gets or sets the value indicating whether the receiver needs to press 1 before connecting the call for it to be forwarded to an external contact or a number, + /// when one is busy on another call. + /// This option ensures that forwarded calls won't reach the voicemail box for the external contact or a number. + /// + [JsonPropertyName("busy_require_press_1_before_connecting")] + public bool? BusyRequirePress1BeforeConnecting { get; set; } + + /// + /// Gets or sets the value indicating whether press 1 before connecting the call to forward to an external contact or a number, + /// when a call is unanswered. + /// This option ensures that forwarded calls won't reach the voicemail box for the external contact or a number. + /// + /// This field is only available if the is set to: + /// - 9(Forward to an External Contact); + /// - 10(Forward to a Phone Number). + /// + [JsonPropertyName("un_answered_require_press_1_before_connecting")] + public bool? UnAnsweredRequirePress1BeforeConnecting { get; set; } + + /// + /// Gets or sets a value indicating whether to play callee's voicemail greeting when the caller reaches the end of the forwarding sequence. + /// This field is only available if the setting is set to: + /// - , + /// - , + /// - , + /// - , + /// - , + /// - , + /// - . + /// + [JsonPropertyName("overflow_play_callee_voicemail_greeting")] + public bool? OverflowPlayCalleeVoicemailGreeting { get; set; } + + /// + /// Gets or sets a value indicating whether to play callee's voicemail greeting when the caller reaches the end of forwarding sequence. + /// This field is only available in the following scenarios: + /// + /// The is set to and the is true. + /// The is set to and the is true. + /// + /// + [JsonPropertyName("play_callee_voicemail_greeting")] + public bool? PlayCalleeVoicemailGreeting { get; set; } + + /// + /// Gets or sets a value indicating whether to allow callers to reach an operator. + /// This field is only available in the following scenarios: + /// + /// The is set to . + /// The is set to . + /// + /// + [JsonPropertyName("connect_to_operator ")] + public bool? ConnectToOperator { get; set; } + + /// + /// Gets or sets a value indicating whether to play callee's voicemail greeting when the caller reaches the end of the forwarding sequence. + /// It displays when is set to one of the following values: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + [JsonPropertyName("busy_play_callee_voicemail_greeting")] + public bool? BusyPlayCalleeVoicemailGreeting { get; set; } + + /// + /// Gets or sets the extension's phone number or forward to an external number in E.164 format. + /// This field is only available if is set to . + /// + [JsonPropertyName("phone_number")] + public string PhoneNumber { get; set; } + + /// + /// Gets or sets description of the phone number to which the call is forwarded. + /// This field is only available if is set to . + /// + [JsonPropertyName("description")] + public string Description { get; set; } + + /// + /// Gets or sets the extension's phone number or forward to an external number in E.164 format. + /// It sets when action is set to . + /// + [JsonPropertyName("busy_phone_number")] + public string BusyPhoneNumber { get; set; } + + /// + /// Gets or sets description of the phone number to which the call is forwarded. + /// It sets when action is set to . + /// + [JsonPropertyName("busy_description")] + public string BusyDescription { get; set; } + + /// + /// Gets or sets the forwarding extension ID. + /// This field is only available in the following scenarios: + /// + /// + /// When is set to for unanswered calls, + /// this field is used to set the specific extension to which voicemails are forwarded. This scenario applies to Auto Receptionist and Call Queue. + /// + /// + /// When is set to one of the following values: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + [JsonPropertyName("forward_to_extension_id")] + public string ForwardToExtensionId { get; set; } + + /// + /// Gets or sets the forwarding extension ID that's required only when setting is set to: + /// + /// + /// + /// + /// + /// + /// + /// + /// + [JsonPropertyName("busy_forward_to_extension_id")] + public string BusyForwardToExtensionId { get; set; } + + /// + /// Gets or sets the greeting audio prompt ID. + /// Options: empty char - default and 0 - disable + /// This is only required for the Call Queue or Auto Receptionist call_handling sub-setting. + /// + [JsonPropertyName("greeting_prompt_id")] + public string GreetingPromptId { get; set; } + + /// + /// Gets or sets the maximum number of calls in queue. Specify the maximum number of callers to place in the queue. + /// When this number is exceeded, callers will be routed based on the overflow option. Up to 60. + /// It's required for the Call Queue call_handling sub-setting. + /// + [JsonPropertyName("max_call_in_queue")] + public int? MaxCallInQueue { get; set; } + + /// + /// Gets or sets the maximum wait time, in seconds, for ring mode + /// or the ring duration for each device for ring mode. + /// to check allowed values. + /// + [JsonPropertyName("max_wait_time")] + public int? MaxWaitTime { get; set; } + + /// + /// Gets or sets the music on hold prompt ID. This field is an option to choose music for inbound callers when they're placed on hold by a call queue member. + /// Options: empty char - default and 0 - disable. + /// Only required for the Call Queue call_handling sub-setting. + /// + [JsonPropertyName("music_on_hold_id")] + public string MusicOnHoldId { get; set; } + + /// + /// Gets or sets the extension ID of the operator to whom the call is being forwarded. + /// This field is only available in the following scenarios: + /// + /// is set to and is true. + /// is set to and is true. (Only applicable to the User) + /// + /// + [JsonPropertyName("operator_extension_id")] + public string OperatorExtensionId { get; set; } + + /// + /// Gets or sets a value indicating whether call queue members can receive new incoming calls notification even on the call. + /// This field receives calls while on a call. + /// It's required for the Call Queue call handling sub-setting. + /// + [JsonPropertyName("receive_call")] + public bool? ReceiveCall { get; set; } + + /// + /// Gets or sets the call handling ring mode, only allowed values: + /// + /// + /// + /// + /// + [JsonPropertyName("ring_mode")] + public RingModeType? RingMode { get; set; } + + /// + /// Gets or sets the voicemail greeting prompt ID. + /// This field is only available in the following scenarios: + /// + /// is set to + /// is set to (Only applicable to the User) + /// + /// + [JsonPropertyName("voicemail_greeting_id")] + public string VoicemailGreetingId { get; set; } + + /// + /// Gets or sets the voicemail leaving instruction prompt ID. + /// This field is only available in the following scenarios: + /// + /// is set to and either or is set to true + /// is set to (Only applicable to the User), and either or is set to true + /// + /// + [JsonPropertyName("voicemail_leaving_instruction_id")] + public string VoicemailLeavingInstructionId { get; set; } + + /// + /// Gets or sets the message greeting prompt ID. + /// This field is only available if is set to . + /// + [JsonPropertyName("message_greeting_id")] + public string MessageGreetingId { get; set; } + + /// + /// Gets or sets the Zoom Contact Center phone number, in E.164 format, to which the call is forwarded. + /// This field is only available if is set to . + /// + [JsonPropertyName("forward_to_zcc_phone_number")] + public string ForwardToZccPhoneNumber { get; set; } + + /// + /// Gets or sets the Partner Contact Center Setting ID to which the call is forwarded. + /// This field is only available if is set to . + /// + [JsonPropertyName("forward_to_partner_contact_center_id")] + public string ForwardToPartnerContactCenterId { get; set; } + + /// + /// Gets or sets the Microsoft Teams Voice App ID to which the call is forwarded. + /// This field is only available if is set to . + /// + [JsonPropertyName("forward_to_teams_id")] + public string ForwardToTeamsId { get; set; } + + /// + /// Gets or sets the wrap up time in seconds. Specify the duration before the next queue call is routed to a member in call queue. + /// See the to check allowed values. + /// + [JsonPropertyName("wrap_up_time")] + public int? WrapUpTime { get; set; } + + /// + /// Gets the type of sub-setting. + /// + [JsonIgnore] + public override CallHandlingSubsettingsType SubsettingType => CallHandlingSubsettingsType.CallHandling; + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs new file mode 100644 index 00000000..4f99bb92 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Base call handling sub-settings model. + /// + public abstract class CallHandlingSubsettingsBase + { + /// + /// Gets the type of sub-setting. + /// + [JsonIgnore] + public abstract CallHandlingSubsettingsType SubsettingType { get; } + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsType.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsType.cs new file mode 100644 index 00000000..c8c6683f --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsType.cs @@ -0,0 +1,34 @@ +using System.Runtime.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Call handling subsetting type. + /// + public enum CallHandlingSubsettingsType + { + /// + /// Call forwarding hours. + /// + [EnumMember(Value = "call_forwarding")] + CallForwarding, + + /// + /// Holiday. + /// + [EnumMember(Value = "holiday")] + Holiday, + + /// + /// Custom hours. + /// + [EnumMember(Value = "custom_hours")] + CustomHours, + + /// + /// Call handling. + /// + [EnumMember(Value = "call_handling")] + CallHandling + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallNotAnswerActionType.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallNotAnswerActionType.cs new file mode 100644 index 00000000..5e140e7c --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallNotAnswerActionType.cs @@ -0,0 +1,104 @@ +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// The types of action to take when a call is not answered. + /// + public enum CallNotAnswerActionType + { + /// + /// Forward to a Voicemail. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToVoicemail = 1, + + /// + /// Forward to the User. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToUser = 2, + + /// + /// Forward to the Zoom Room. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToZoomRoom = 3, + + /// + /// Forward to the Common Area. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToCommonArea = 4, + + /// + /// Forward to the Cisco/Polycom Room. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToCiscoOrPolycomRoom = 5, + + /// + /// Forward to the Auto Receptionist. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToAutoReceptionist = 6, + + /// + /// Forward to a Call Queue. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToCallQueue = 7, + + /// + /// Forward to a Shared Line Group. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToSharedLineGroup = 8, + + /// + /// Forward to an External Contact. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToExternalContact = 9, + + /// + /// Forward to a Phone Number. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToPhoneNumber = 10, + + /// + /// Disconnect. + /// Applicable to User, Call Queue, or Shared Line Group. + /// + Disconnect = 11, + + /// + /// Play a message, then disconnect. + /// Applicable to User, Call Queue, Auto Receptionist, or Shared Line Group. + /// + PlayMessageThenDisconnect = 12, + + /// + /// Forward to an Interactive Voice Response (IVR). + /// Applicable to Auto Receptionist. + /// + ForwardToIvr = 14, + + /// + /// Forward to a Partner Contact Center. + /// Applicable to Auto Receptionist. + /// + ForwardToPartnerContactCenter = 15, + + /// + /// Forward to Microsoft Teams Resource Account. Required the license of Zoom Phone for Microsoft Teams. + /// Applicable to Call queue, Auto Receptionist, or Shared Line group. + /// + ForwardToMicrosoftTeamsResourceAccount = 18, + + /// + /// Forward to a Zoom Contact Center. Required Zoom Contact Center license. + /// Applicable to Call Queue, Auto Receptionist, or Shared Line Group. + /// + ForwardToZoomContactCenter = 19 + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursChildSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursChildSubsettings.cs new file mode 100644 index 00000000..a1518eb1 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursChildSubsettings.cs @@ -0,0 +1,35 @@ +using System; +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Custom hours child subsettings. + /// + public class CustomHoursChildSubsettings + { + /// + /// Gets or sets custom hours start time in HH:mm format. + /// + [JsonPropertyName("from")] + public string From { get; set; } + + /// + /// Gets or sets custom hours end time in HH:mm format. + /// + [JsonPropertyName("to")] + public string To { get; set; } + + /// + /// Gets or sets the type of custom hours. + /// + [JsonPropertyName("type")] + public CustomHoursType? Type { get; set; } + + /// + /// Gets or sets the day of the week. + /// + [JsonPropertyName("weekday")] + public DayOfWeek? Weekday { get; set; } + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursSubsettings.cs new file mode 100644 index 00000000..ac929ef8 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursSubsettings.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Call handling custom hours subsettings. + /// + public class CustomHoursSubsettings : CallHandlingSubsettingsBase + { + /// + /// Gets or sets a value indicating whether the queue members are able to set their own business hours. + /// This field allows queue members' business hours to override the default hours of the call queue. + /// + [JsonPropertyName("allow_members_to_reset")] + public bool? AllowMembersToReset { get; set; } + + /// + /// Gets or sets the type of custom hours. + /// 1 — 24 hours, 7 days a week. + /// 2 — Custom hours. + /// + [JsonPropertyName("type")] + public byte? CustomHoursType { get; set; } + + /// + /// Gets or sets the custom hours settings. + /// + [JsonPropertyName("custom_hours_settings")] + public List CustomHoursSettings { get; set; } + + /// + /// Gets the type of sub-setting. + /// + [JsonIgnore] + public override CallHandlingSubsettingsType SubsettingType => CallHandlingSubsettingsType.CustomHours; + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursType.cs b/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursType.cs new file mode 100644 index 00000000..2e52c38d --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/CustomHoursType.cs @@ -0,0 +1,23 @@ +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// The type of custom hours. + /// + public enum CustomHoursType + { + /// + /// Disabled. + /// + Disabled = 0, + + /// + /// 24 hours. + /// + FullDay = 1, + + /// + /// Customized hours. + /// + CustomizedHours = 2 + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/ExternalContact.cs b/Source/ZoomNet/Models/CallHandlingSettings/ExternalContact.cs new file mode 100644 index 00000000..8ea29e15 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/ExternalContact.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// External contact object. It's only required for . + /// + public class ExternalContact + { + /// + /// Gets or sets the external contact's ID. + /// + [JsonPropertyName("external_contact_id")] + public string ExternalContactId { get; set; } + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/HolidaySubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/HolidaySubsettings.cs new file mode 100644 index 00000000..d98e3530 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/HolidaySubsettings.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// Call handling holiday subsettings. + /// + public class HolidaySubsettings : CallHandlingSubsettingsBase + { + /// + /// Gets or sets the holiday's start date and time in yyyy-MM-dd'T'HH:mm:ss'Z' format. + /// + [JsonPropertyName("from")] + public DateTime? From { get; set; } + + /// + /// Gets or sets the holiday's end date and time in yyyy-MM-dd'T'HH:mm:ss'Z' format. + /// + [JsonPropertyName("to")] + public DateTime? To { get; set; } + + /// + /// Gets or sets the name of the holiday. + /// + [JsonPropertyName("name")] + public string Name { get; set; } + + /// + /// Gets or sets the holiday's ID. + /// + [JsonPropertyName("holiday_id")] + public string HolidayId { get; set; } + + /// + /// Gets the type of sub-setting. + /// + [JsonIgnore] + public override CallHandlingSubsettingsType SubsettingType => CallHandlingSubsettingsType.Holiday; + } +} diff --git a/Source/ZoomNet/Models/CallHandlingSettings/RingModeType.cs b/Source/ZoomNet/Models/CallHandlingSettings/RingModeType.cs new file mode 100644 index 00000000..4f91bf88 --- /dev/null +++ b/Source/ZoomNet/Models/CallHandlingSettings/RingModeType.cs @@ -0,0 +1,34 @@ +using System.Runtime.Serialization; + +namespace ZoomNet.Models.CallHandlingSettings +{ + /// + /// The ring mode types. + /// + public enum RingModeType + { + /// + /// Simultaneous mode. + /// + [EnumMember(Value = "simultaneous")] + Simultaneous, + + /// + /// Sequential mode. + /// + [EnumMember(Value = "sequential")] + Sequential, + + /// + /// Rotating mode. + /// + [EnumMember(Value = "rotating")] + Rotating, + + /// + /// Longest idle mode. + /// + [EnumMember(Value = "longest_idle")] + LongestIdle, + } +} diff --git a/Source/ZoomNet/Models/PresenceStatus.cs b/Source/ZoomNet/Models/PresenceStatus.cs index abf9b5d4..464979b7 100644 --- a/Source/ZoomNet/Models/PresenceStatus.cs +++ b/Source/ZoomNet/Models/PresenceStatus.cs @@ -38,27 +38,39 @@ public enum PresenceStatus Offline, /// - /// Offline. + /// In calendar event. /// [EnumMember(Value = "In_Calendar_Event")] InEvent, /// - /// Offline. + /// Presenting. /// [EnumMember(Value = "Presenting")] Presenting, /// - /// Offline. + /// In a Zoom meeting. /// [EnumMember(Value = "In_A_Zoom_Meeting")] InMeeting, /// - /// Offline. + /// On a call. /// [EnumMember(Value = "On_A_Call")] OnCall, + + /// + /// Out of office. + /// + [EnumMember(Value = "Out_of_Office")] + OutOfOffice, + + /// + /// Busy. + /// + [EnumMember(Value = "Busy")] + Busy } } diff --git a/Source/ZoomNet/Models/PresenceStatusResponse.cs b/Source/ZoomNet/Models/PresenceStatusResponse.cs new file mode 100644 index 00000000..eff823a2 --- /dev/null +++ b/Source/ZoomNet/Models/PresenceStatusResponse.cs @@ -0,0 +1,31 @@ +using System; +using System.Text.Json.Serialization; + +namespace ZoomNet.Models +{ + /// + /// Presence status model. + /// + public class PresenceStatusResponse + { + /// + /// Gets or sets presence status. + /// + [JsonPropertyName("status")] + public PresenceStatus Status { get; set; } + + /// + /// Gets or sets end time. + /// Visible only in case that the user queries for self and presence status is . + /// + [JsonPropertyName("end_time")] + public DateTime? EndTime { get; set; } + + /// + /// Gets or sets remaining time. + /// Visible only in case that the user queries for self and presence status is . + /// + [JsonPropertyName("remaining_time")] + public int? RemainingTime { get; set; } + } +} diff --git a/Source/ZoomNet/Resources/IPhone.cs b/Source/ZoomNet/Resources/IPhone.cs index 14702d94..f6a426f7 100644 --- a/Source/ZoomNet/Resources/IPhone.cs +++ b/Source/ZoomNet/Resources/IPhone.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using ZoomNet.Models; +using ZoomNet.Models.CallHandlingSettings; namespace ZoomNet.Resources { @@ -88,6 +89,27 @@ Task> ListPhoneUsersAsync( string keyword = null, CancellationToken cancellationToken = default); + /// + /// Updates a Zoom Phone's call handling setting. + /// Call handling settings allow you to control how your system routes calls during business, closed, or holiday hours. + /// + /// Extension id. + /// Call handling setting type. + /// Call handling subsettings. Allowed subsettings: + /// + /// + /// + /// + /// + /// + /// A cancellation token that can be used to cancel the asynchronous operation. + /// A Task representing the asynchronous operation. + Task UpdateCallHandlingSettingsAsync( + string extensionId, + CallHandlingSettingType settingType, + CallHandlingSubsettingsBase subsettins, + CancellationToken cancellationToken); + #endregion } } diff --git a/Source/ZoomNet/Resources/IUsers.cs b/Source/ZoomNet/Resources/IUsers.cs index b2dcd77c..f71aebb6 100644 --- a/Source/ZoomNet/Resources/IUsers.cs +++ b/Source/ZoomNet/Resources/IUsers.cs @@ -416,5 +416,14 @@ public interface IUsers /// - The maximum value for duration is 1,440 minutes which represents 24 hours (i.e.: 60 * 24 hours = 1,440). /// Task UpdatePresenceStatusAsync(string userId, PresenceStatus status, int? duration = null, CancellationToken cancellationToken = default); + + /// + /// Returns a user's current status. + /// + /// The user Id/member Id or email address. + /// The cancellation token. + /// Current user's status. + /// If the presence status is Do not disturb, end_time and remaining_time properties are only visible if the user being queried is also the current user. + Task GetPresenceStatusAsync(string userId, CancellationToken cancellationToken = default); } } diff --git a/Source/ZoomNet/Resources/Phone.cs b/Source/ZoomNet/Resources/Phone.cs index f5fdade5..96fd07ef 100644 --- a/Source/ZoomNet/Resources/Phone.cs +++ b/Source/ZoomNet/Resources/Phone.cs @@ -1,8 +1,10 @@ using Pathoschild.Http.Client; using System; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using ZoomNet.Models; +using ZoomNet.Models.CallHandlingSettings; namespace ZoomNet.Resources { @@ -94,6 +96,26 @@ public Task> ListPhoneUsersAsync( .AsPaginatedResponseWithToken("users"); } + /// + public Task UpdateCallHandlingSettingsAsync( + string extensionId, + CallHandlingSettingType settingType, + CallHandlingSubsettingsBase subsettings, + CancellationToken cancellationToken) + { + var data = new JsonObject + { + { "settings", subsettings }, + { "sub_setting_type", subsettings?.SubsettingType } + }; + + return _client + .PatchAsync($"/phone/extension/{extensionId}/call_handling/settings/{settingType.ToEnumString()}") + .WithJsonBody(data) + .WithCancellationToken(cancellationToken) + .AsMessage(); + } + #endregion } } diff --git a/Source/ZoomNet/Resources/Users.cs b/Source/ZoomNet/Resources/Users.cs index e3a2e504..a0df4b90 100644 --- a/Source/ZoomNet/Resources/Users.cs +++ b/Source/ZoomNet/Resources/Users.cs @@ -504,5 +504,14 @@ public Task UpdatePresenceStatusAsync(string userId, PresenceStatus status, int? .WithCancellationToken(cancellationToken) .AsMessage(); } + + /// + public Task GetPresenceStatusAsync(string userId, CancellationToken cancellationToken = default) + { + return _client + .GetAsync($"users/{userId}/presence_status") + .WithCancellationToken(cancellationToken) + .AsObject(); + } } } From 8ee30c29b21afdb8770fe57ea4d09f938b2115d5 Mon Sep 17 00:00:00 2001 From: Jericho Date: Wed, 6 Nov 2024 12:09:20 -0500 Subject: [PATCH 04/16] Throw when undefine enum value Resolves #376 --- .../Extensions/InternalTests.cs | 35 +++++++++++++++---- Source/ZoomNet/Extensions/Internal.cs | 11 +++++- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs index 1f923b14..a2f07779 100644 --- a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs +++ b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs @@ -327,18 +327,18 @@ public class Enumconversion public enum MyEnum { [EnumMember(Value = "One")] - First, + First = 1, [MultipleValuesEnumMember(DefaultValue = "Two", OtherValues = new[] { "Second", "Alternative" })] - Second, + Second = 2, [JsonPropertyName("Three")] - Third, + Third = 3, [Description("Four")] - Fourth, + Fourth = 4, - Fifth + Fifth = 5 } [Theory] @@ -373,8 +373,31 @@ public void ToEnumString(MyEnum value, string expected) result.ShouldBe(expected); } - } + [Fact] + public void ThrowsWhenUndefinedValue() + { + // Arrange + var myInvalidEnumValue = (MyEnum)9999; + + // Act + Should.Throw(() => myInvalidEnumValue.TryToEnumString(out string stringValue, true)); + } + + [Fact] + public void UndefinedValueCanBeIgnored() + { + // Arrange + var myInvalidEnumValue = (MyEnum)9999; + + // Act + var result = myInvalidEnumValue.TryToEnumString(out string stringValue, false); + + // Asert + result.ShouldBeFalse(); + stringValue.ShouldBeNull(); + } + } public class GetErrorMessageAsync { diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index 07848aa5..2e749d8f 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -801,9 +801,18 @@ internal static string ToEnumString(this T enumValue) return enumValue.ToString(); } - internal static bool TryToEnumString(this T enumValue, out string stringValue) + internal static bool TryToEnumString(this T enumValue, out string stringValue, bool throwWhenUndefined = true) where T : Enum { + if (throwWhenUndefined) + { + var typeOfT = typeof(T); + if (!Enum.IsDefined(typeOfT, enumValue)) + { + throw new ArgumentException($"{enumValue} is not a valid value for {typeOfT.Name}", nameof(enumValue)); + } + } + var multipleValuesEnumMemberAttribute = enumValue.GetAttributeOfType(); if (multipleValuesEnumMemberAttribute != null) { From f555245040d969c75b8960296a4293cc1807425e Mon Sep 17 00:00:00 2001 From: Jericho Date: Thu, 7 Nov 2024 13:09:26 -0500 Subject: [PATCH 05/16] Fix SA1629 A section of the XML header documentation for a C# element does not end with a period. --- .../Models/CallHandlingSettings/CallHandlingSubsettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs index 36274d67..7f05cb4e 100644 --- a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs @@ -13,7 +13,7 @@ public class CallHandlingSubsettings : CallHandlingSubsettingsBase /// Gets or sets a value indicating whether to allow the callers to check voicemails. /// This field is only available in the following scenarios: /// - The is set to 1 (Forward to a voicemail). - /// - The is set to 1 (Forward to a voicemail).(Only applicable to the User) + /// - The is set to 1 (Forward to a voicemail).(Only applicable to the User). /// [JsonPropertyName("allow_callers_check_voicemail")] public bool? AllowCallersCheckVoicemail { get; set; } From da559d874deb5e6cf7ebe011cc0b915f430b15b3 Mon Sep 17 00:00:00 2001 From: Jericho Date: Sat, 9 Nov 2024 20:12:11 -0500 Subject: [PATCH 06/16] Sort the scopes (for convenience) --- Source/ZoomNet/Utilities/OAuthTokenHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/ZoomNet/Utilities/OAuthTokenHandler.cs b/Source/ZoomNet/Utilities/OAuthTokenHandler.cs index fbed88c3..3f3888d5 100644 --- a/Source/ZoomNet/Utilities/OAuthTokenHandler.cs +++ b/Source/ZoomNet/Utilities/OAuthTokenHandler.cs @@ -127,9 +127,10 @@ public string RefreshTokenIfNecessary(bool forceRefresh) .Select(x => x.Split(new[] { ':' }, 2)) .Select(x => new KeyValuePair(x[0], x.Skip(1).ToArray())) .GroupBy(x => x.Key) + .OrderBy(x => x.Key) .ToDictionary( x => x.Key, - x => x.SelectMany(c => c.Value).ToArray())); + x => x.SelectMany(c => c.Value).OrderBy(c => c).ToArray())); // Please note that Server-to-Server OAuth does not use the refresh token. // Therefore change the grant type to 'RefreshToken' only when the response includes a refresh token. From 2a35741036c828a1c88112132bba9621dfe96379 Mon Sep 17 00:00:00 2001 From: Jericho Date: Sun, 10 Nov 2024 11:53:14 -0500 Subject: [PATCH 07/16] Formatting --- Source/ZoomNet/Resources/Accounts.cs | 4 ++-- Source/ZoomNet/Resources/CallLogs.cs | 4 ++-- Source/ZoomNet/Resources/Chat.cs | 4 ++-- Source/ZoomNet/Resources/CloudRecordings.cs | 4 ++-- Source/ZoomNet/Resources/Contacts.cs | 4 ++-- Source/ZoomNet/Resources/DataCompliance.cs | 4 ++-- Source/ZoomNet/Resources/Meetings.cs | 4 ++-- Source/ZoomNet/Resources/PastMeetings.cs | 4 ++-- Source/ZoomNet/Resources/PastWebinars.cs | 4 ++-- Source/ZoomNet/Resources/Roles.cs | 4 ++-- Source/ZoomNet/Resources/Users.cs | 4 ++-- Source/ZoomNet/Resources/Webinars.cs | 4 ++-- Source/ZoomNet/Utilities/DiagnosticHandler.cs | 2 +- Source/ZoomNet/Utilities/Http429RetryStrategy.cs | 2 +- Source/ZoomNet/Utilities/JwtTokenHandler.cs | 2 +- Source/ZoomNet/Utilities/OAuthTokenHandler.cs | 2 +- Source/ZoomNet/Utilities/ZoomErrorHandler.cs | 2 +- Source/ZoomNet/ZoomClient.cs | 2 +- 18 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Source/ZoomNet/Resources/Accounts.cs b/Source/ZoomNet/Resources/Accounts.cs index 1f8bb601..634c7911 100644 --- a/Source/ZoomNet/Resources/Accounts.cs +++ b/Source/ZoomNet/Resources/Accounts.cs @@ -12,13 +12,13 @@ namespace ZoomNet.Resources /// public class Accounts : IAccounts { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Accounts(Pathoschild.Http.Client.IClient client) + internal Accounts(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/CallLogs.cs b/Source/ZoomNet/Resources/CallLogs.cs index b851c47d..bcd75ce6 100644 --- a/Source/ZoomNet/Resources/CallLogs.cs +++ b/Source/ZoomNet/Resources/CallLogs.cs @@ -9,13 +9,13 @@ namespace ZoomNet.Resources /// public class CallLogs : ICallLogs { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal CallLogs(Pathoschild.Http.Client.IClient client) + internal CallLogs(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/Chat.cs b/Source/ZoomNet/Resources/Chat.cs index a735bd6e..9c220844 100644 --- a/Source/ZoomNet/Resources/Chat.cs +++ b/Source/ZoomNet/Resources/Chat.cs @@ -15,13 +15,13 @@ namespace ZoomNet.Resources /// public class Chat : IChat { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Chat(Pathoschild.Http.Client.IClient client) + internal Chat(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/CloudRecordings.cs b/Source/ZoomNet/Resources/CloudRecordings.cs index 155018d5..d264ce54 100644 --- a/Source/ZoomNet/Resources/CloudRecordings.cs +++ b/Source/ZoomNet/Resources/CloudRecordings.cs @@ -16,13 +16,13 @@ namespace ZoomNet.Resources /// public class CloudRecordings : ICloudRecordings { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal CloudRecordings(Pathoschild.Http.Client.IClient client) + internal CloudRecordings(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/Contacts.cs b/Source/ZoomNet/Resources/Contacts.cs index d641d2d6..8d470c5f 100644 --- a/Source/ZoomNet/Resources/Contacts.cs +++ b/Source/ZoomNet/Resources/Contacts.cs @@ -9,13 +9,13 @@ namespace ZoomNet.Resources /// public class Contacts : IContacts { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Contacts(Pathoschild.Http.Client.IClient client) + internal Contacts(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/DataCompliance.cs b/Source/ZoomNet/Resources/DataCompliance.cs index 1fa2d19c..c4d98e02 100644 --- a/Source/ZoomNet/Resources/DataCompliance.cs +++ b/Source/ZoomNet/Resources/DataCompliance.cs @@ -13,13 +13,13 @@ namespace ZoomNet.Resources [Obsolete("The Data Compliance API is deprecated")] public class DataCompliance : IDataCompliance { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal DataCompliance(Pathoschild.Http.Client.IClient client) + internal DataCompliance(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/Meetings.cs b/Source/ZoomNet/Resources/Meetings.cs index 78087b86..f1a3215b 100644 --- a/Source/ZoomNet/Resources/Meetings.cs +++ b/Source/ZoomNet/Resources/Meetings.cs @@ -12,13 +12,13 @@ namespace ZoomNet.Resources /// public class Meetings : IMeetings { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Meetings(Pathoschild.Http.Client.IClient client) + internal Meetings(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/PastMeetings.cs b/Source/ZoomNet/Resources/PastMeetings.cs index 97c1bd53..6f6b5541 100644 --- a/Source/ZoomNet/Resources/PastMeetings.cs +++ b/Source/ZoomNet/Resources/PastMeetings.cs @@ -9,13 +9,13 @@ namespace ZoomNet.Resources /// public class PastMeetings : IPastMeetings { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal PastMeetings(Pathoschild.Http.Client.IClient client) + internal PastMeetings(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/PastWebinars.cs b/Source/ZoomNet/Resources/PastWebinars.cs index e321a0e3..bc39289a 100644 --- a/Source/ZoomNet/Resources/PastWebinars.cs +++ b/Source/ZoomNet/Resources/PastWebinars.cs @@ -9,13 +9,13 @@ namespace ZoomNet.Resources /// public class PastWebinars : IPastWebinars { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal PastWebinars(Pathoschild.Http.Client.IClient client) + internal PastWebinars(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/Roles.cs b/Source/ZoomNet/Resources/Roles.cs index 485d1570..fdb78d0e 100644 --- a/Source/ZoomNet/Resources/Roles.cs +++ b/Source/ZoomNet/Resources/Roles.cs @@ -13,13 +13,13 @@ namespace ZoomNet.Resources /// public class Roles : IRoles { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Roles(Pathoschild.Http.Client.IClient client) + internal Roles(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/Users.cs b/Source/ZoomNet/Resources/Users.cs index a0df4b90..40e1f84e 100644 --- a/Source/ZoomNet/Resources/Users.cs +++ b/Source/ZoomNet/Resources/Users.cs @@ -14,13 +14,13 @@ namespace ZoomNet.Resources /// public class Users : IUsers { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Users(Pathoschild.Http.Client.IClient client) + internal Users(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Resources/Webinars.cs b/Source/ZoomNet/Resources/Webinars.cs index 0673d17f..5e10c3a9 100644 --- a/Source/ZoomNet/Resources/Webinars.cs +++ b/Source/ZoomNet/Resources/Webinars.cs @@ -12,13 +12,13 @@ namespace ZoomNet.Resources /// public class Webinars : IWebinars { - private readonly Pathoschild.Http.Client.IClient _client; + private readonly IClient _client; /// /// Initializes a new instance of the class. /// /// The HTTP client. - internal Webinars(Pathoschild.Http.Client.IClient client) + internal Webinars(IClient client) { _client = client; } diff --git a/Source/ZoomNet/Utilities/DiagnosticHandler.cs b/Source/ZoomNet/Utilities/DiagnosticHandler.cs index 50e898a5..02893c2c 100644 --- a/Source/ZoomNet/Utilities/DiagnosticHandler.cs +++ b/Source/ZoomNet/Utilities/DiagnosticHandler.cs @@ -13,7 +13,7 @@ namespace ZoomNet.Utilities /// /// Diagnostic handler for requests dispatched to the Zoom API. /// - /// + /// internal class DiagnosticHandler : IHttpFilter { #region FIELDS diff --git a/Source/ZoomNet/Utilities/Http429RetryStrategy.cs b/Source/ZoomNet/Utilities/Http429RetryStrategy.cs index 1af21e70..47c9a52c 100644 --- a/Source/ZoomNet/Utilities/Http429RetryStrategy.cs +++ b/Source/ZoomNet/Utilities/Http429RetryStrategy.cs @@ -11,7 +11,7 @@ namespace ZoomNet.Utilities /// "Retry-After" response header. The value in this header contains the date /// and time when the next attempt can take place. /// - /// + /// internal class Http429RetryStrategy : IRetryConfig { #region FIELDS diff --git a/Source/ZoomNet/Utilities/JwtTokenHandler.cs b/Source/ZoomNet/Utilities/JwtTokenHandler.cs index 041a13f0..4ceddacc 100644 --- a/Source/ZoomNet/Utilities/JwtTokenHandler.cs +++ b/Source/ZoomNet/Utilities/JwtTokenHandler.cs @@ -11,7 +11,7 @@ namespace ZoomNet.Utilities /// /// Handler to ensure requests to the Zoom API include a valid JWT token. /// - /// + /// /// internal class JwtTokenHandler : IHttpFilter, ITokenHandler { diff --git a/Source/ZoomNet/Utilities/OAuthTokenHandler.cs b/Source/ZoomNet/Utilities/OAuthTokenHandler.cs index 3f3888d5..189ba8c7 100644 --- a/Source/ZoomNet/Utilities/OAuthTokenHandler.cs +++ b/Source/ZoomNet/Utilities/OAuthTokenHandler.cs @@ -16,7 +16,7 @@ namespace ZoomNet.Utilities /// /// Handler to ensure requests to the Zoom API include a valid OAuth token. /// - /// + /// internal class OAuthTokenHandler : IHttpFilter, ITokenHandler { public string Token diff --git a/Source/ZoomNet/Utilities/ZoomErrorHandler.cs b/Source/ZoomNet/Utilities/ZoomErrorHandler.cs index 7adb46e2..3f7d6e50 100644 --- a/Source/ZoomNet/Utilities/ZoomErrorHandler.cs +++ b/Source/ZoomNet/Utilities/ZoomErrorHandler.cs @@ -7,7 +7,7 @@ namespace ZoomNet.Utilities /// /// Error handler for requests dispatched to the Zoom API. /// - /// + /// internal class ZoomErrorHandler : IHttpFilter { private const string DEFAULT_HTTP_200_EXCEPTION_MESSAGE = "The Zoom API returned a status code that indicates that your request was unseccessful, without providing an explanation. Typically this means that you either lack the necessary permissions or that a paid account is required and you have a free account."; diff --git a/Source/ZoomNet/ZoomClient.cs b/Source/ZoomNet/ZoomClient.cs index 46bd7213..f91d70ff 100644 --- a/Source/ZoomNet/ZoomClient.cs +++ b/Source/ZoomNet/ZoomClient.cs @@ -36,7 +36,7 @@ public class ZoomClient : IZoomClient, IDisposable private readonly ILogger _logger; private HttpClient _httpClient; - private Pathoschild.Http.Client.IClient _fluentClient; + private IClient _fluentClient; #endregion From 255a8aa4ca44c9754c03d937a0a4364262553bc9 Mon Sep 17 00:00:00 2001 From: Jericho Date: Mon, 11 Nov 2024 10:44:23 -0500 Subject: [PATCH 08/16] Add the HasPermissions method to the ZoomClient class Resolves #379 --- .../Tests/Accounts.cs | 25 +++++++++++-------- Source/ZoomNet/Extensions/Public.cs | 17 +++++++++++++ Source/ZoomNet/IZoomClient.cs | 14 +++++++++++ Source/ZoomNet/OAuthConnectionInfo.cs | 4 +-- Source/ZoomNet/Utilities/OAuthTokenHandler.cs | 11 +------- Source/ZoomNet/ZoomClient.cs | 21 ++++++++++++++++ 6 files changed, 69 insertions(+), 23 deletions(-) diff --git a/Source/ZoomNet.IntegrationTests/Tests/Accounts.cs b/Source/ZoomNet.IntegrationTests/Tests/Accounts.cs index 3e6240ed..4328b1b2 100644 --- a/Source/ZoomNet.IntegrationTests/Tests/Accounts.cs +++ b/Source/ZoomNet.IntegrationTests/Tests/Accounts.cs @@ -14,20 +14,23 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie await log.WriteLineAsync("\n***** ACCOUNTS *****\n").ConfigureAwait(false); - // GET ALL THE ACCOUNTS - var paginatedAccounts = await client.Accounts.GetAllAsync(100, null, cancellationToken).ConfigureAwait(false); - await log.WriteLineAsync($"There are {paginatedAccounts.TotalRecords} sub accounts under the main account").ConfigureAwait(false); - - // GET SETTINGS - if (paginatedAccounts.Records.Any()) + if (client.HasPermission("account:read:list_sub_accounts:master")) { - var accountId = paginatedAccounts.Records.First().Id; + // GET ALL THE ACCOUNTS + var paginatedAccounts = await client.Accounts.GetAllAsync(100, null, cancellationToken).ConfigureAwait(false); + await log.WriteLineAsync($"There are {paginatedAccounts.TotalRecords} sub accounts under the main account").ConfigureAwait(false); + + // GET SETTINGS + if (paginatedAccounts.Records.Any()) + { + var accountId = paginatedAccounts.Records.First().Id; - var meetingAuthenticationSettings = await client.Accounts.GetMeetingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false); - await log.WriteLineAsync("Meeting authentication settings retrieved").ConfigureAwait(false); + var meetingAuthenticationSettings = await client.Accounts.GetMeetingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false); + await log.WriteLineAsync("Meeting authentication settings retrieved").ConfigureAwait(false); - var recordingAuthenticationSettings = await client.Accounts.GetRecordingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false); - await log.WriteLineAsync("Recording authentication settings retrieved").ConfigureAwait(false); + var recordingAuthenticationSettings = await client.Accounts.GetRecordingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false); + await log.WriteLineAsync("Recording authentication settings retrieved").ConfigureAwait(false); + } } } } diff --git a/Source/ZoomNet/Extensions/Public.cs b/Source/ZoomNet/Extensions/Public.cs index 60faab06..6991b196 100644 --- a/Source/ZoomNet/Extensions/Public.cs +++ b/Source/ZoomNet/Extensions/Public.cs @@ -368,5 +368,22 @@ public static async Task AddUserToGroupAsync(this IGroups groupsResource // We added a single member to a group therefore the array returned from the Zoom API contains a single element return result.Single(); } + + /// + /// Determines if the specified scope has been granted. + /// + /// The ZoomNet client. + /// The name of the scope. + /// True if the scope has been granted, False otherwise. + /// + /// The concept of "scopes" only applies to OAuth connections. + /// Therefore an exeption will be thrown if you invoke this method while using + /// a JWT connection (you shouldn't be using JWT in the first place since this + /// type of connection has been deprecated in the Zoom API since September 2023). + /// + public static bool HasPermission(this IZoomClient client, string scope) + { + return client.HasPermissions(new[] { scope }); + } } } diff --git a/Source/ZoomNet/IZoomClient.cs b/Source/ZoomNet/IZoomClient.cs index d38f20cc..4bba5eb2 100644 --- a/Source/ZoomNet/IZoomClient.cs +++ b/Source/ZoomNet/IZoomClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using ZoomNet.Resources; namespace ZoomNet @@ -105,5 +106,18 @@ public interface IZoomClient /// Gets the resource which allows you to manage webinars. /// IWebinars Webinars { get; } + + /// + /// Determines if the specified scopes have been granted. + /// + /// The name of the scopes. + /// True if all the scopes have been granted, False otherwise. + /// + /// The concept of "scopes" only applies to OAuth connections. + /// Therefore an exeption will be thrown if you invoke this method while using + /// a JWT connection (you shouldn't be using JWT in the first place since this + /// type of connection has been deprecated in the Zoom API since September 2023). + /// + bool HasPermissions(IEnumerable scopes); } } diff --git a/Source/ZoomNet/OAuthConnectionInfo.cs b/Source/ZoomNet/OAuthConnectionInfo.cs index e3a07585..cd95b81a 100644 --- a/Source/ZoomNet/OAuthConnectionInfo.cs +++ b/Source/ZoomNet/OAuthConnectionInfo.cs @@ -53,9 +53,9 @@ public class OAuthConnectionInfo : IConnectionInfo public string AccessToken { get; internal set; } /// - /// Gets the token scope. + /// Gets the scopes. /// - public IReadOnlyDictionary TokenScope { get; internal set; } + public IReadOnlyList Scopes { get; internal set; } /// /// Gets the token expiration time. diff --git a/Source/ZoomNet/Utilities/OAuthTokenHandler.cs b/Source/ZoomNet/Utilities/OAuthTokenHandler.cs index 189ba8c7..1ab05467 100644 --- a/Source/ZoomNet/Utilities/OAuthTokenHandler.cs +++ b/Source/ZoomNet/Utilities/OAuthTokenHandler.cs @@ -121,16 +121,7 @@ public string RefreshTokenIfNecessary(bool forceRefresh) _connectionInfo.RefreshToken = jsonResponse.GetPropertyValue("refresh_token", string.Empty); _connectionInfo.AccessToken = jsonResponse.GetPropertyValue("access_token", string.Empty); _connectionInfo.TokenExpiration = requestTime.AddSeconds(jsonResponse.GetPropertyValue("expires_in", 60 * 60)); - _connectionInfo.TokenScope = new ReadOnlyDictionary( - jsonResponse.GetPropertyValue("scope", string.Empty) - .Split(' ') - .Select(x => x.Split(new[] { ':' }, 2)) - .Select(x => new KeyValuePair(x[0], x.Skip(1).ToArray())) - .GroupBy(x => x.Key) - .OrderBy(x => x.Key) - .ToDictionary( - x => x.Key, - x => x.SelectMany(c => c.Value).OrderBy(c => c).ToArray())); + _connectionInfo.Scopes = new ReadOnlyCollection(jsonResponse.GetPropertyValue("scope", string.Empty).Split(' ').OrderBy(x => x).ToList()); // Please note that Server-to-Server OAuth does not use the refresh token. // Therefore change the grant type to 'RefreshToken' only when the response includes a refresh token. diff --git a/Source/ZoomNet/ZoomClient.cs b/Source/ZoomNet/ZoomClient.cs index f91d70ff..3f8812d6 100644 --- a/Source/ZoomNet/ZoomClient.cs +++ b/Source/ZoomNet/ZoomClient.cs @@ -3,6 +3,8 @@ using Pathoschild.Http.Client; using Pathoschild.Http.Client.Extensibility; using System; +using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; @@ -244,6 +246,25 @@ private ZoomClient(IConnectionInfo connectionInfo, HttpClient httpClient, bool d #region PUBLIC METHODS + /// + public bool HasPermissions(IEnumerable scopes) + { + var tokenHandler = _fluentClient.Filters.OfType().SingleOrDefault(); + if (tokenHandler == null) throw new Exception("The concept of scopes only applies when using an OAuth connection."); + + // Ensure the token (and by extension the scopes) is not expired + tokenHandler.RefreshTokenIfNecessary(false); + + // The list of scopes can be empty if a previously issued token was specified when the OAuthConnectionInfo was instantiated. + // I am not aware of any way to fetch the list of scopes which would enable me to populate the list of scopes in the OAuthConnectionInfo. + // Therefore in this scenario the only workaround I can think of is to force the token to be refreshed. + var oAuthConnectionInfo = (OAuthConnectionInfo)tokenHandler.ConnectionInfo; + if (oAuthConnectionInfo.Scopes == null) tokenHandler.RefreshTokenIfNecessary(true); // Force the token to be refreshed wich will have the side-effect of populating the '.Scopes' + + var missingScopes = scopes.Except(((OAuthConnectionInfo)tokenHandler.ConnectionInfo).Scopes).ToArray(); + return !missingScopes.Any(); + } + /// public void Dispose() { From 8d9fcd9a362dcdb6a02b1bd503ef9f52b84675cd Mon Sep 17 00:00:00 2001 From: Jericho Date: Mon, 11 Nov 2024 11:00:25 -0500 Subject: [PATCH 09/16] Sort integration tests alphabetically --- Source/ZoomNet.IntegrationTests/TestSuites/ApiTestSuite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet.IntegrationTests/TestSuites/ApiTestSuite.cs b/Source/ZoomNet.IntegrationTests/TestSuites/ApiTestSuite.cs index 26829f35..61dba1ec 100644 --- a/Source/ZoomNet.IntegrationTests/TestSuites/ApiTestSuite.cs +++ b/Source/ZoomNet.IntegrationTests/TestSuites/ApiTestSuite.cs @@ -16,10 +16,10 @@ internal class ApiTestSuite : TestSuite typeof(Contacts), typeof(Dashboards), typeof(Meetings), + typeof(Reports), typeof(Roles), typeof(Users), typeof(Webinars), - typeof(Reports), }; public ApiTestSuite(IConnectionInfo connectionInfo, IWebProxy proxy, ILoggerFactory loggerFactory) : From 6ddaf752948c3ef30c00e8b5e132b140d1457276 Mon Sep 17 00:00:00 2001 From: Jericho Date: Wed, 13 Nov 2024 12:12:55 -0500 Subject: [PATCH 10/16] Upgrade to Cake 5.0.0 and .NET SDK 9. Also refresh build script --- .config/dotnet-tools.json | 2 +- build.cake | 4 ++-- global.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 1f95b27b..43e6f74c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "cake.tool": { - "version": "4.2.0", + "version": "5.0.0", "commands": [ "dotnet-cake" ] diff --git a/build.cake b/build.cake index f2ca8ae3..a7ce8fcf 100644 --- a/build.cake +++ b/build.cake @@ -1,8 +1,8 @@ // Install tools. -#tool dotnet:?package=GitVersion.Tool&version=6.0.4 +#tool dotnet:?package=GitVersion.Tool&version=6.0.5 #tool dotnet:?package=coveralls.net&version=4.0.1 #tool nuget:https://f.feedz.io/jericho/jericho/nuget/?package=GitReleaseManager&version=0.17.0-collaborators0008 -#tool nuget:?package=ReportGenerator&version=5.3.11 +#tool nuget:?package=ReportGenerator&version=5.4.0 #tool nuget:?package=xunit.runner.console&version=2.9.2 #tool nuget:?package=CodecovUploader&version=0.8.0 diff --git a/global.json b/global.json index a323b039..11db103e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.403", + "version": "9.0.100", "rollForward": "patch", "allowPrerelease": false } From 3f1428fa2cacdbea29701c79858154807b51212d Mon Sep 17 00:00:00 2001 From: Jericho Date: Wed, 13 Nov 2024 12:43:23 -0500 Subject: [PATCH 11/16] Fix typo --- Source/ZoomNet/Json/WebhookEventConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet/Json/WebhookEventConverter.cs b/Source/ZoomNet/Json/WebhookEventConverter.cs index a881c475..b0b6ca54 100644 --- a/Source/ZoomNet/Json/WebhookEventConverter.cs +++ b/Source/ZoomNet/Json/WebhookEventConverter.cs @@ -306,7 +306,7 @@ private static KeyValuePair ConvertJsonPropertyToKeyValuePair(Js if (value.TryGetInt64(out var longValue)) return new KeyValuePair(key, longValue); if (value.TryGetInt32(out var intValue)) return new KeyValuePair(key, intValue); if (value.TryGetInt16(out var shortValue)) return new KeyValuePair(key, shortValue); - throw new JsonException($"Property {key} appears to contain a numerical value but we are unable to determine to exact type"); + throw new JsonException($"Property {key} appears to contain a numerical value but we are unable to determine the exact type"); default: return new KeyValuePair(key, value.GetRawText()); } } From 5c494f3797504e201f59c655ff1b90271f05347a Mon Sep 17 00:00:00 2001 From: Jericho Date: Fri, 15 Nov 2024 09:41:46 -0500 Subject: [PATCH 12/16] Fix incorrect JsonPropertyName in CallHandlingSettings --- .../Models/CallHandlingSettings/CallHandlingSubsettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs index 7f05cb4e..a02de1f4 100644 --- a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettings.cs @@ -52,7 +52,7 @@ public class CallHandlingSubsettings : CallHandlingSubsettingsBase /// If Sequential or Rotating ring mode is selected , /// calls will ring for a specific time before trying the next available queue member. /// - [JsonPropertyName("busy_on_another_call_action")] + [JsonPropertyName("call_distribution")] public CallDistributionSettings CallDistribution { get; set; } /// From 3b613695e36f74ad8807a50c2f22e02c9ff54033 Mon Sep 17 00:00:00 2001 From: Jericho Date: Fri, 15 Nov 2024 09:43:27 -0500 Subject: [PATCH 13/16] Remove extra '/' in URL --- Source/ZoomNet/Resources/Phone.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet/Resources/Phone.cs b/Source/ZoomNet/Resources/Phone.cs index 96fd07ef..3289ff0c 100644 --- a/Source/ZoomNet/Resources/Phone.cs +++ b/Source/ZoomNet/Resources/Phone.cs @@ -110,7 +110,7 @@ public Task UpdateCallHandlingSettingsAsync( }; return _client - .PatchAsync($"/phone/extension/{extensionId}/call_handling/settings/{settingType.ToEnumString()}") + .PatchAsync($"phone/extension/{extensionId}/call_handling/settings/{settingType.ToEnumString()}") .WithJsonBody(data) .WithCancellationToken(cancellationToken) .AsMessage(); From e51cd08333603aaa5f67951df56a4953b79e3af9 Mon Sep 17 00:00:00 2001 From: pvgritsenko-ansible <165666478+pvgritsenko-ansible@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:47:12 +0300 Subject: [PATCH 14/16] Call-handling settings issues (#380) * Fix call-handling issues * remove extra jsonConverter fix call handling settings update url --------- Co-authored-by: P. Gritsenko Co-authored-by: Jericho <112710+Jericho@users.noreply.github.com> --- Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs | 2 ++ .../CallHandlingSettings/CallHandlingSubsettingsBase.cs | 4 ++++ Source/ZoomNet/Resources/IPhone.cs | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs index 8a1614fe..07980371 100644 --- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs +++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs @@ -27,6 +27,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings))] @@ -343,6 +344,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings[]))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType[]))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase[]))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType[]))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType[]))] [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings[]))] diff --git a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs index 4f99bb92..a878465b 100644 --- a/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs +++ b/Source/ZoomNet/Models/CallHandlingSettings/CallHandlingSubsettingsBase.cs @@ -5,6 +5,10 @@ namespace ZoomNet.Models.CallHandlingSettings /// /// Base call handling sub-settings model. /// + [JsonDerivedType(typeof(CallForwardingSubsettings))] + [JsonDerivedType(typeof(CallHandlingSubsettings))] + [JsonDerivedType(typeof(CustomHoursSubsettings))] + [JsonDerivedType(typeof(HolidaySubsettings))] public abstract class CallHandlingSubsettingsBase { /// diff --git a/Source/ZoomNet/Resources/IPhone.cs b/Source/ZoomNet/Resources/IPhone.cs index f6a426f7..66bc7558 100644 --- a/Source/ZoomNet/Resources/IPhone.cs +++ b/Source/ZoomNet/Resources/IPhone.cs @@ -95,7 +95,7 @@ Task> ListPhoneUsersAsync( /// /// Extension id. /// Call handling setting type. - /// Call handling subsettings. Allowed subsettings: + /// Call handling subsettings. Allowed subsettings: /// /// /// @@ -107,7 +107,7 @@ Task> ListPhoneUsersAsync( Task UpdateCallHandlingSettingsAsync( string extensionId, CallHandlingSettingType settingType, - CallHandlingSubsettingsBase subsettins, + CallHandlingSubsettingsBase subsettings, CancellationToken cancellationToken); #endregion From daf63a495e824247ae03fd06986b4e6d9d4fe4d7 Mon Sep 17 00:00:00 2001 From: Jericho Date: Fri, 15 Nov 2024 09:50:04 -0500 Subject: [PATCH 15/16] Synchronize JSON serializer context --- .../Json/ZoomNetJsonSerializerContext.cs | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs index 07980371..4d32b395 100644 --- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs +++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs @@ -21,22 +21,22 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrant))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrantInfo))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType))] [JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType), TypeInfoPropertyName = "CallHandlingSettingsBusyOnAnotherCallActionType")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings), TypeInfoPropertyName = "CallHandlingSettingsCallDistributionSettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingChildSubsettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingSubsettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSettingType")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsBase")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsType")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType), TypeInfoPropertyName = "CallHandlingSettingsCallNotAnswerActionType")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursChildSubsettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursSubsettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursType")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact), TypeInfoPropertyName = "CallHandlingSettingsExternalContact")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings), TypeInfoPropertyName = "CallHandlingSettingsHolidaySubsettings")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType), TypeInfoPropertyName = "CallHandlingSettingsRingModeType")] [JsonSerializable(typeof(ZoomNet.Models.CallingPlan))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlanType))] [JsonSerializable(typeof(ZoomNet.Models.CallLog))] @@ -99,6 +99,8 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.CrcPortsHourUsage))] [JsonSerializable(typeof(ZoomNet.Models.CrcPortsUsage))] [JsonSerializable(typeof(ZoomNet.Models.CustomAttribute))] + [JsonSerializable(typeof(ZoomNet.Models.DailyUsageReport))] + [JsonSerializable(typeof(ZoomNet.Models.DailyUsageSummary))] [JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetrics))] [JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetricsPaginationObject))] [JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingParticipant))] @@ -338,22 +340,22 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType[]))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrant[]))] [JsonSerializable(typeof(ZoomNet.Models.BatchRegistrantInfo[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings[]))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType[]))] [JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType[]))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType[]), TypeInfoPropertyName = "CallHandlingSettingsBusyOnAnotherCallActionTypeArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallDistributionSettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingChildSubsettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingSubsettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSettingTypeArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsBaseArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsTypeArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType[]), TypeInfoPropertyName = "CallHandlingSettingsCallNotAnswerActionTypeArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursChildSubsettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursSubsettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType[]), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursTypeArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact[]), TypeInfoPropertyName = "CallHandlingSettingsExternalContactArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsHolidaySubsettingsArray")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType[]), TypeInfoPropertyName = "CallHandlingSettingsRingModeTypeArray")] [JsonSerializable(typeof(ZoomNet.Models.CallingPlan[]))] [JsonSerializable(typeof(ZoomNet.Models.CallingPlanType[]))] [JsonSerializable(typeof(ZoomNet.Models.CallLog[]))] @@ -417,6 +419,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.CrcPortsUsage[]))] [JsonSerializable(typeof(ZoomNet.Models.CustomAttribute[]))] [JsonSerializable(typeof(ZoomNet.Models.DailyUsageReport[]))] + [JsonSerializable(typeof(ZoomNet.Models.DailyUsageSummary[]))] [JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetrics[]))] [JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetricsPaginationObject[]))] [JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingParticipant[]))] @@ -426,7 +429,6 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.DashboardParticipant[]))] [JsonSerializable(typeof(ZoomNet.Models.DashboardParticipantQos[]))] [JsonSerializable(typeof(ZoomNet.Models.DataCenterRegion[]))] - [JsonSerializable(typeof(ZoomNet.Models.DailyUsageSummary[]))] [JsonSerializable(typeof(ZoomNet.Models.EmailNotificationUserSettings[]))] [JsonSerializable(typeof(ZoomNet.Models.EmergencyAddress[]))] [JsonSerializable(typeof(ZoomNet.Models.EncryptionType[]))] @@ -647,13 +649,13 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.AudioType?))] [JsonSerializable(typeof(ZoomNet.Models.AuthenticationType?))] [JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType?))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType?))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType?))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType?))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType?))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType?))] - [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType?))] [JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType?))] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType?), TypeInfoPropertyName = "CallHandlingSettingsBusyOnAnotherCallActionTypeNullable")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType?), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSettingTypeNullable")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType?), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsTypeNullable")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType?), TypeInfoPropertyName = "CallHandlingSettingsCallNotAnswerActionTypeNullable")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType?), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursTypeNullable")] + [JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType?), TypeInfoPropertyName = "CallHandlingSettingsRingModeTypeNullable")] [JsonSerializable(typeof(ZoomNet.Models.CallingPlanType?))] [JsonSerializable(typeof(ZoomNet.Models.CallLogCalleeNumberType?))] [JsonSerializable(typeof(ZoomNet.Models.CallLogCallerNumberType?))] From fe69be536b261f595b131eef6e619cacabeb87b1 Mon Sep 17 00:00:00 2001 From: Jericho Date: Fri, 15 Nov 2024 10:09:42 -0500 Subject: [PATCH 16/16] Improve error message to include field name when available. Resolves #378 --- .../Extensions/InternalTests.cs | 17 +++++++++++++++++ Source/ZoomNet/Extensions/Internal.cs | 7 ++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs index a2f07779..a58e1ec9 100644 --- a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs +++ b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs @@ -417,6 +417,23 @@ public async Task CanHandleUnescapedDoubleQuotesInErrorMessage() errorMessage.ShouldStartWith("Invalid access token, does not contain scopes"); errorCode.ShouldBe(104); } + + [Fact] + public async Task IncludesFieldNameInErrorMessage() + { + // Arrange + const string responseContent = @"{""code"":300,""message"":""Validation Failed."",""errors"":[{""field"":""settings.jbh_time"",""message"":""Invalid parameter: jbh_time.""}]}"; + var message = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(responseContent) }; + var response = new MockFluentHttpResponse(message, null, CancellationToken.None); + + // Act + var (isError, errorMessage, errorCode) = await response.Message.GetErrorMessageAsync(); + + // Assert + isError.ShouldBeTrue(); + errorMessage.ShouldBe("Validation Failed. settings.jbh_time Invalid parameter: jbh_time."); + errorCode.ShouldBe(300); + } } } } diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index 2e749d8f..3a88b984 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -748,7 +748,12 @@ internal static DiagnosticInfo GetDiagnosticInfo(this IResponse response) " ", jsonErrorDetails .EnumerateArray() - .Select(jsonErrorDetail => jsonErrorDetail.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : string.Empty) + .Select(jsonErrorDetail => + { + var field = jsonErrorDetail.TryGetProperty("field", out JsonElement jsonField) ? jsonField.GetString() : string.Empty; + var message = jsonErrorDetail.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : string.Empty; + return $"{field} {message}".Trim(); + }) .Where(message => !string.IsNullOrEmpty(message))); if (!string.IsNullOrEmpty(errorDetails)) errorMessage += $" {errorDetails}";