Skip to content

Commit d6fb82d

Browse files
committed
feat: Processes multiple reservations
Updates reservation processing to handle multiple reservations within a single request. This change enables the API to accept and process an array of reservations, improving efficiency and reducing the need for multiple individual requests. It introduces a `ReservationRefSet` to encapsulate the array of `ReservationRef` objects and related tracking information. It also includes error handling for individual reservation failures within the set.
1 parent c4e00dc commit d6fb82d

File tree

3 files changed

+241
-5
lines changed

3 files changed

+241
-5
lines changed

apps/GuestLineSDK.ConsoleSample/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
RateId: "1299224125",
2222
AriAction: AriAction.Range,
2323
Start: new(2026, 02, 15),
24-
End: new(2026, 02, 15)
24+
End: new(2026, 02, 16)
2525
));
2626

2727
var res = new ReservationRequest(
@@ -61,7 +61,7 @@
6161
var reservationResult = await api.Reservation.ProcessReservationAsync(
6262
new GuestLineReservationRequest(
6363
"12992",
64-
[res]));
64+
[res, res]));
6565

6666
//string json = GetARIUpdate();
6767

libs/GuestLineSDK/Api/ReservationOperations.cs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ partial class GuestLineApiClient
2727

2828
public partial interface IReservationOperations
2929
{
30-
Task<GuestLineResponse<ReservationRef>> ProcessReservationAsync(
30+
Task<GuestLineResponse<ReservationRefSet>> ProcessReservationAsync(
3131
GuestLineReservationRequest request,
3232
CancellationToken cancellationToken = default);
3333
}
@@ -36,7 +36,7 @@ public class ReservationOperations(ApiClient client) : IReservationOperations
3636
{
3737
readonly ApiClient _client = client;
3838

39-
public async Task<GuestLineResponse<ReservationRef>> ProcessReservationAsync(GuestLineReservationRequest request, CancellationToken cancellationToken = default)
39+
public async Task<GuestLineResponse<ReservationRefSet>> ProcessReservationAsync(GuestLineReservationRequest request, CancellationToken cancellationToken = default)
4040
{
4141

4242
Ensure.IsNotNull(request, nameof(request));
@@ -52,8 +52,63 @@ public async Task<GuestLineResponse<ReservationRef>> ProcessReservationAsync(Gue
5252
PathString.Empty,
5353
request);
5454

55-
return await _client.FetchAsync<GuestLineReservationRequest, ReservationRef>(req, cancellationToken)
55+
var (res, data) = await _client.FetchRawAsync<GuestLineReservationRequest, ReservationRef[]>(req, cancellationToken)
5656
.ConfigureAwait(false);
57+
58+
if (!res.IsSuccess)
59+
{
60+
return new GuestLineResponse<ReservationRefSet>(
61+
res.RequestMethod,
62+
res.RequestUri,
63+
res.IsSuccess,
64+
res.StatusCode,
65+
error: res.Error);
66+
}
67+
68+
string? trackingId = null;
69+
List<ReservationRef> refs = new();
70+
List<string> errors = new();
71+
bool success = true;
72+
73+
if (data != null)
74+
{
75+
var tracker = data.FirstOrDefault(x => !string.IsNullOrEmpty(x.TrackingId));
76+
trackingId = tracker?.TrackingId;
77+
78+
for (int i = 0; i < data.Length; i++)
79+
{
80+
var @ref = data[i];
81+
if (@ref == tracker)
82+
{
83+
continue;
84+
}
85+
86+
if (@ref.Error is { Length: >0 } || string.Equals("fail", @ref.Status, StringComparison.OrdinalIgnoreCase))
87+
{
88+
success = false;
89+
if (@ref.Error is { Length: >0 })
90+
{
91+
errors.Add(@ref.Error);
92+
}
93+
}
94+
95+
@ref.TrackingId = tracker?.TrackingId;
96+
refs.Add(@ref);
97+
}
98+
}
99+
100+
return new GuestLineResponse<ReservationRefSet>(
101+
res.RequestMethod,
102+
res.RequestUri,
103+
success,
104+
res.StatusCode,
105+
data: new ReservationRefSet()
106+
{
107+
Reservations = refs.ToArray(),
108+
TrackingId = trackingId
109+
},
110+
error: res.Error ?? new ErrorResponse() { Error = string.Join("\n", errors), Status = "Fail", TrackingId = trackingId }
111+
);
57112
}
58113
}
59114

@@ -93,6 +148,12 @@ public class ReservationRef : Result<ReservationRef>
93148
public string? BookingId { get; set; }
94149
}
95150

151+
public class ReservationRefSet : Result<ReservationRefSet>
152+
{
153+
[JsonPropertyName("reservations")]
154+
public ReservationRef[]? Reservations { get; set; }
155+
}
156+
96157
public record ReservationRequest(
97158
[property: JsonPropertyName("reservation_datetime"), JsonConverter(typeof(DateTimeJsonConverter))] DateTime ReservationDateTime,
98159
[property: JsonPropertyName("reservation_id")] string ReservationId,

libs/GuestLineSDK/ApiClient.cs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,71 @@ protected internal async Task<GuestLineResponse<TResponse>> FetchAsync<TRequest,
269269
}
270270
}
271271
}
272+
273+
protected internal async Task<(GuestLineResponse, TResponse?)> FetchRawAsync<TRequest, TResponse>(
274+
GuestLineRequest<TRequest> request,
275+
CancellationToken cancellationToken = default)
276+
where TRequest : notnull
277+
where TResponse : class
278+
{
279+
Ensure.IsNotNull(request, nameof(request));
280+
var (httpReq, capturedRequestContent) = CreateHttpRequest(request);
281+
HttpResponseMessage? httpResp = null;
282+
283+
try
284+
{
285+
httpResp = await _http.SendAsync(httpReq, cancellationToken)
286+
.ConfigureAwait(false);
287+
288+
var (transformedResponse, data, capturedResponseContent) = await TransformRawResponse<TResponse>(
289+
httpReq.Method,
290+
httpReq.RequestUri,
291+
httpResp)
292+
.ConfigureAwait(false); ;
293+
294+
if (_settings.CaptureRequestContent)
295+
{
296+
transformedResponse.RequestContent = capturedRequestContent;
297+
}
298+
299+
if (_settings.CaptureResponseContent || !httpResp.IsSuccessStatusCode)
300+
{
301+
transformedResponse.ResponseContent = capturedResponseContent;
302+
}
303+
304+
return (transformedResponse, data);
305+
}
306+
catch (Exception ex)
307+
{
308+
ex = ex.Demystify();
309+
310+
var response = new GuestLineResponse(
311+
httpReq.Method,
312+
httpReq.RequestUri,
313+
false,
314+
(HttpStatusCode)0,
315+
error: new ErrorResponse
316+
{
317+
Error = ex.Message,
318+
Exception = ex,
319+
ErrorSource = ErrorSource.SDK
320+
});
321+
322+
return (response, null);
323+
}
324+
finally
325+
{
326+
if (httpReq is not null)
327+
{
328+
httpReq.Dispose();
329+
}
330+
331+
if (httpResp is not null)
332+
{
333+
httpResp.Dispose();
334+
}
335+
}
336+
}
272337
#endregion
273338

274339
#region Preprocessing
@@ -487,6 +552,116 @@ protected internal (HttpRequestMessage, string?) CreateHttpRequest<TRequest>(
487552
), stringContent);
488553
}
489554
}
555+
556+
protected internal async Task<(GuestLineResponse, TResponse?, string?)> TransformRawResponse<TResponse>(
557+
HttpMethod method,
558+
Uri uri,
559+
HttpResponseMessage response,
560+
CancellationToken cancellationToken = default)
561+
where TResponse : class
562+
{
563+
Stream? content = null;
564+
string? stringContent = null;
565+
if (response.Content is not null)
566+
{
567+
if (_settings.CaptureResponseContent || !response.IsSuccessStatusCode)
568+
{
569+
stringContent = await response.Content.ReadAsStringAsync()
570+
.ConfigureAwait(false);
571+
}
572+
else
573+
{
574+
content = await response.Content.ReadAsStreamAsync()
575+
.ConfigureAwait(false);
576+
}
577+
}
578+
579+
if (response.IsSuccessStatusCode)
580+
{
581+
TResponse? data = default;
582+
if (content is not null || stringContent is { Length: > 0 })
583+
{
584+
try
585+
{
586+
data = stringContent is { Length: > 0 }
587+
? JsonSerializer.Deserialize<TResponse>(stringContent, _deserializerOptions)
588+
: await JsonSerializer.DeserializeAsync<TResponse>(content!, _deserializerOptions).ConfigureAwait(false);
589+
590+
return (new GuestLineResponse(
591+
method,
592+
uri,
593+
response.IsSuccessStatusCode,
594+
response.StatusCode), data, stringContent);
595+
}
596+
catch (Exception ex)
597+
{
598+
ex = ex.Demystify();
599+
600+
return new(
601+
new GuestLineResponse(
602+
method,
603+
uri,
604+
response.IsSuccessStatusCode,
605+
response.StatusCode,
606+
error: new ErrorResponse
607+
{
608+
Error = ex.Message,
609+
Exception = ex,
610+
ErrorSource = ErrorSource.SDK
611+
}
612+
), null, stringContent);
613+
}
614+
}
615+
616+
return (new GuestLineResponse(
617+
method,
618+
uri,
619+
false,
620+
response.StatusCode,
621+
error: new ErrorResponse
622+
{
623+
Error = Resources.ApiClient_UnknownResponse
624+
}
625+
), null, stringContent);
626+
}
627+
else
628+
{
629+
ErrorResponse? error;
630+
if (stringContent is { Length: > 0 })
631+
{
632+
var result = JsonSerializer.Deserialize<ErrorResponse>(stringContent, _deserializerOptions);
633+
634+
if (result?.Error is not { Length: > 0 })
635+
{
636+
error = new ErrorResponse
637+
{
638+
Error = Resources.ApiClient_UnknownResponse,
639+
Status = result?.Status,
640+
TrackingId = result?.TrackingId
641+
};
642+
}
643+
else
644+
{
645+
error = result;
646+
}
647+
}
648+
else
649+
{
650+
error = new ErrorResponse
651+
{
652+
Error = Resources.ApiClient_NoErrorMessage
653+
};
654+
}
655+
656+
return (new GuestLineResponse(
657+
method,
658+
uri,
659+
false,
660+
response.StatusCode,
661+
error: error
662+
), null, stringContent);
663+
}
664+
}
490665
#endregion
491666

492667
protected internal Lazy<TOperations> Defer<TOperations>(Func<ApiClient, TOperations> factory)

0 commit comments

Comments
 (0)