Skip to content

Commit 3767ef6

Browse files
committed
feat: Implements ARI retrieval service
Adds the ability to retrieve Availability, Rates, and Inventory (ARI) data from the GuestLine service. This includes: - Creation of a new `IServiceOperations` interface and its implementation, `ServiceOperations`, to handle ARI requests. - Introduction of `GuestLineAriRequest` to encapsulate the parameters for ARI requests, including validation. - Definition of an `AriAction` enum to represent the type of ARI data being requested (range or full year). - Updates to `ApiClient` to handle the new ARI service endpoint. - Addition of `GuestLineUriResolver` to determine the correct base URL for services based on environment. - Fixes a date serialization issue using `DateOnlyJsonConverter`.
1 parent 6c15577 commit 3767ef6

File tree

8 files changed

+356
-17
lines changed

8 files changed

+356
-17
lines changed

apps/GuestLineSDK.ConsoleSample/Program.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@
1313
var http = CreateHttpClient();
1414
var api = new GuestLineApiClient(http, settings);
1515

16-
string json = GetARIUpdate();
16+
var ari = await api.Service.GetAriAsync(new GuestLineAriRequest(
17+
PropertyId: "12992",
18+
RoomId: "1299210STANDARD",
19+
RateId: "1299224125",
20+
Action: AriAction.FullYear));
1721

18-
var updateRef = AriUpdateRef.FromJsonString(json);
19-
var update = AriUpdate.FromJsonString(json);
22+
//string json = GetARIUpdate();
23+
24+
//var updateRef = AriUpdateRef.FromJsonString(json);
25+
//var update = AriUpdate.FromJsonString(json);
2026
Console.ReadKey();
2127

2228
GuestLineSettings GetSettings()
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// This work is licensed under the terms of the MIT license.
2+
// For a copy, see <https://opensource.org/licenses/MIT>.
3+
4+
namespace GuestLineSDK.Api;
5+
6+
using System.Text.Json.Serialization;
7+
8+
using FluentValidation;
9+
10+
using GuestLineSDK.Ari;
11+
12+
partial interface IGuestLineApiClient
13+
{
14+
/// <summary>
15+
/// Service Operations
16+
/// </summary>
17+
IServiceOperations Service { get; }
18+
}
19+
20+
partial class GuestLineApiClient
21+
{
22+
Lazy<IServiceOperations>? _service;
23+
public IServiceOperations Service => (_service ??= Defer<IServiceOperations>(
24+
c => new ServiceOperations(c))).Value;
25+
}
26+
27+
public partial interface IServiceOperations
28+
{
29+
Task<GuestLineResponse<AriUpdate>> GetAriAsync(
30+
GuestLineAriRequest request,
31+
CancellationToken cancellationToken = default);
32+
}
33+
34+
public partial class ServiceOperations(ApiClient client) : IServiceOperations
35+
{
36+
readonly ApiClient _client = client;
37+
38+
public async Task<GuestLineResponse<AriUpdate>> GetAriAsync(
39+
GuestLineAriRequest request,
40+
CancellationToken cancellationToken = default)
41+
{
42+
Ensure.IsNotNull(request, nameof(request));
43+
44+
request.ApiKey ??= _client.Settings.ApiKey;
45+
request.Version ??= _client.Settings.Version;
46+
47+
request.Validate();
48+
49+
var req = new GuestLineRequest<GuestLineAriRequest>(
50+
GuestLineService.ARI,
51+
HttpMethod.Post,
52+
PathString.Empty,
53+
request);
54+
55+
return await _client.FetchAsync<GuestLineAriRequest, AriUpdate>(req, cancellationToken)
56+
.ConfigureAwait(false);
57+
}
58+
}
59+
60+
public enum AriAction
61+
{
62+
/// <summary>
63+
/// Request a range of up to 28 days of ARI data.
64+
/// </summary>
65+
Range,
66+
67+
/// <summary>
68+
/// Request a full year of ARI data.
69+
/// </summary>
70+
FullYear
71+
}
72+
73+
/// <summary>
74+
/// Represents a request for GuestLine ARI data.
75+
/// </summary>
76+
/// <param name="action">The ARI action (range or full year)</param>
77+
/// <param name="start">The start date of the range (if provided)</param>
78+
/// <param name="end">The end date of the range (if provided)</param>
79+
public record GuestLineAriRequest(
80+
[property: JsonPropertyName("propertyid")] string PropertyId,
81+
[property: JsonPropertyName("room_id")] string RoomId,
82+
[property: JsonPropertyName("rate_id")] string RateId,
83+
84+
[property: JsonIgnore] AriAction Action = AriAction.FullYear,
85+
[property: JsonPropertyName("from_date")] DateTime? Start = null,
86+
[property: JsonPropertyName("to_date")] DateTime? End = null)
87+
{
88+
[JsonPropertyName("action")]
89+
public string? ActionValue
90+
{
91+
get
92+
{
93+
return Action switch
94+
{
95+
AriAction.FullYear => "year_info_ARR",
96+
AriAction.Range => "ARR_info",
97+
_ => null
98+
};
99+
}
100+
}
101+
102+
[JsonPropertyName("apikey")]
103+
public string? ApiKey { get; set; }
104+
105+
[JsonPropertyName("version")]
106+
public string? Version { get; set; }
107+
108+
/// <summary>
109+
/// Validates the current instance.
110+
/// </summary>
111+
public void Validate()
112+
=> GuestLineAriRequestValidator.Instance.Validate(this);
113+
}
114+
115+
public class GuestLineAriRequestValidator : AbstractValidator<GuestLineAriRequest>
116+
{
117+
public static readonly GuestLineAriRequestValidator Instance = new();
118+
119+
public GuestLineAriRequestValidator()
120+
{
121+
RuleFor(s => s.ActionValue)
122+
.NotEmpty()
123+
.WithMessage("The Action parameter must be a valid action.");
124+
125+
RuleFor(s => s.ApiKey)
126+
.NotEmpty()
127+
.WithMessage("The API key must not be empty.");
128+
129+
RuleFor(s => s.PropertyId)
130+
.NotEmpty()
131+
.WithMessage("The Property ID parameter must not be empty.");
132+
133+
RuleFor(s => s.RoomId)
134+
.NotEmpty()
135+
.WithMessage("The Room ID parameter must not be empty.");
136+
137+
RuleFor(s => s.RateId)
138+
.NotEmpty()
139+
.WithMessage("The Rate ID parameter must not be empty.");
140+
141+
RuleFor(s => s.Version)
142+
.NotEmpty()
143+
.WithMessage("The Version parameter must not be empty.");
144+
145+
When(r => r.Action == AriAction.Range, () =>
146+
{
147+
RuleFor(s => s.Start)
148+
.NotEmpty()
149+
.GreaterThanOrEqualTo(DateTime.Today.Date)
150+
.WithMessage("The start of the ARI range must be today or after");
151+
152+
RuleFor(s => s.End)
153+
.Custom((end, c) =>
154+
{
155+
var start = c.InstanceToValidate.Start;
156+
if (!end.HasValue)
157+
{
158+
c.AddFailure("The end of the ARI range must be on or after the start of the range");
159+
}
160+
else if (!start.HasValue)
161+
{
162+
c.AddFailure("Cannot request a range without a valid start and end date");
163+
}
164+
else
165+
{
166+
var diff = (end.Value - start.Value).Days;
167+
if (diff < 0)
168+
{
169+
c.AddFailure("The end of the range cannot be before the start");
170+
}
171+
else if (diff > 28)
172+
{
173+
c.AddFailure("The range cannot extend beyond 28 days when requesting ARI data.");
174+
}
175+
}
176+
});
177+
});
178+
}
179+
}

libs/GuestLineSDK/ApiClient.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,33 @@ public abstract class ApiClient
2020
readonly GuestLineSettings _settings;
2121
readonly JsonSerializerOptions _serializerOptions = JsonUtility.CreateSerializerOptions();
2222
readonly JsonSerializerOptions _deserializerOptions = JsonUtility.CreateDeserializerOptions();
23-
readonly Uri _baseUrl;
23+
readonly Uri _serviceBaseUrl;
24+
readonly Uri _bookBaseUrl;
2425

2526
protected ApiClient(HttpClient http, GuestLineSettings settings)
2627
{
2728
_http = Ensure.IsNotNull(http, nameof(http));
2829
_settings = Ensure.IsNotNull(settings, nameof(settings));
2930

30-
_baseUrl = new Uri(_settings.BaseUrl);
31+
_serviceBaseUrl = new Uri(GuestLineUriResolver.ResolveServiceBaseUrl(settings));
32+
_bookBaseUrl = new Uri(GuestLineUriResolver.ResolveBookBaseUrl(settings));
3133
}
3234

35+
/// <summary>
36+
/// Gets the book base URL.
37+
/// </summary>
38+
public Uri BookBaseUrl => _bookBaseUrl;
39+
40+
/// <summary>
41+
/// Gets the service base URL.
42+
/// </summary>
43+
public Uri ServiceBaseUrl => _serviceBaseUrl;
44+
45+
/// <summary>
46+
/// Gets the settings.
47+
/// </summary>
48+
public GuestLineSettings Settings => _settings;
49+
3350
#region Send and Fetch
3451
protected internal async Task<GuestLineResponse> SendAsync(
3552
GuestLineRequest request,
@@ -238,7 +255,8 @@ protected internal HttpRequestMessage CreateHttpRequest(
238255

239256
var baseUri = request.Service switch
240257
{
241-
GuestLineService.Reservation => _baseUrl,
258+
GuestLineService.Book => _bookBaseUrl,
259+
GuestLineService.ARI => _serviceBaseUrl,
242260

243261
_ => throw new NotSupportedException(
244262
string.Format(Resources.ApiClient_UnsupportedService, request.Service)

libs/GuestLineSDK/Ari/AriUpdate.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public class AriUpdateData : Model<AriUpdateData>
8686
/// Amount after tax.
8787
/// </summary>
8888
[JsonPropertyName("amountAfterTax")]
89-
public AriUpdateDataPricing? AmountafterTax { get; set; }
89+
public AriUpdateDataPricing? AmountAfterTax { get; set; }
9090

9191
/// <summary>
9292
/// Amount before tax.
@@ -109,7 +109,7 @@ public class AriUpdateData : Model<AriUpdateData>
109109
/// <summary>
110110
/// Date in YYYY-MM-DD format.
111111
/// </summary>
112-
[JsonPropertyName("from_date")]
112+
[JsonPropertyName("from_date"), JsonConverter(typeof(DateOnlyJsonConverter))]
113113
public DateTime? FromDate { get; set; }
114114

115115
/// <summary>
@@ -151,7 +151,7 @@ public class AriUpdateData : Model<AriUpdateData>
151151
/// <summary>
152152
/// Date in YYYY-MM-DD format.
153153
/// </summary>
154-
[JsonPropertyName("to_date")]
154+
[JsonPropertyName("to_date"), JsonConverter(typeof(DateOnlyJsonConverter))]
155155
public DateTime? ToDate { get; set; }
156156
}
157157

libs/GuestLineSDK/GuestLineRequest.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ public class GuestLineRequest<TData>(
7373
public enum GuestLineService
7474
{
7575
/// <summary>
76-
/// The reservation service.
76+
/// The booking service.
7777
/// </summary>
78-
Reservation,
78+
Book,
79+
80+
/// <summary>
81+
/// The ARI service.
82+
/// </summary>
83+
ARI
7984
}

libs/GuestLineSDK/GuestLineSettings.cs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// This work is licensed under the terms of the MIT license.
22
// For a copy, see <https://opensource.org/licenses/MIT>.
33

4+
using System.Security.Cryptography;
5+
46
using FluentValidation;
57

68
using Microsoft.Extensions.Options;
@@ -22,7 +24,12 @@ public class GuestLineSettings
2224
/// <summary>
2325
/// Gets or sets the base URL.
2426
/// </summary>
25-
public string BaseUrl { get; set; } = "";
27+
public string? BookBaseUrl { get; set; }
28+
29+
/// <summary>
30+
/// Gets or sets the GuestLine environment, defaulting to Production.
31+
/// </summary>
32+
public GuestLineEnvironment Environment { get; set; } = GuestLineEnvironment.Production;
2633

2734
/// <summary>
2835
/// Gets or sets whether to capture request content.
@@ -34,6 +41,21 @@ public class GuestLineSettings
3441
/// </summary>
3542
public bool CaptureResponseContent { get; set; }
3643

44+
/// <summary>
45+
/// Gets or sets the partner identifier.
46+
/// </summary>
47+
public string PartnerId { get; set; } = "";
48+
49+
/// <summary>
50+
/// Gets or sets the service base URL.
51+
/// </summary>
52+
public string? ServiceBaseUrl { get; set; }
53+
54+
/// <summary>
55+
/// Gets or sets the default request version.
56+
/// </summary>
57+
public string Version { get; set; } = "2";
58+
3759
/// <summary>
3860
/// Returns the settings as a wrapped options instance.
3961
/// </summary>
@@ -48,6 +70,22 @@ public void Validate()
4870
=> GuestLineSettingsValidator.Instance.Validate(this);
4971
}
5072

73+
/// <summary>
74+
/// Specifies the GuestLine environment.
75+
/// </summary>
76+
public enum GuestLineEnvironment
77+
{
78+
/// <summary>
79+
/// Production environment.
80+
/// </summary>
81+
Production,
82+
83+
/// <summary>
84+
/// Test environment.
85+
/// </summary>
86+
Test
87+
}
88+
5189
/// <summary>
5290
/// Validates instances of <see cref="GuestLineSettings"/>.
5391
/// </summary>
@@ -60,17 +98,34 @@ public GuestLineSettingsValidator()
6098
bool ValidateUri(string value)
6199
=> Uri.TryCreate(value, UriKind.Absolute, out var _);
62100

63-
RuleFor(s => s.BaseUrl)
101+
RuleFor(s => s.ApiKey)
102+
.NotEmpty()
103+
.WithMessage("The API key must not be empty.");
104+
105+
RuleFor(s => s.BookBaseUrl)
64106
.Custom((value, context) =>
65107
{
66-
if (!ValidateUri(value))
108+
if (value is { Length: > 0 } && !ValidateUri(value))
67109
{
68-
context.AddFailure($"'{value}' is not a valid URI.");
110+
context.AddFailure($"'{value}' is not a valid book URI.");
69111
}
70112
});
71113

72-
RuleFor(s => s.ApiKey)
114+
RuleFor(s => s.PartnerId)
73115
.NotEmpty()
74-
.WithMessage("The API key must not be empty.");
116+
.WithMessage("The partner ID must not be empty.");
117+
118+
RuleFor(s => s.ServiceBaseUrl)
119+
.Custom((value, context) =>
120+
{
121+
if (value is { Length: > 0 } && !ValidateUri(value))
122+
{
123+
context.AddFailure($"'{value}' is not a valid service URI.");
124+
}
125+
});
126+
127+
RuleFor(s => s.Version)
128+
.NotEmpty()
129+
.WithMessage("The version must not be empty.");
75130
}
76131
}

0 commit comments

Comments
 (0)