diff --git a/seq-api.sln.DotSettings b/seq-api.sln.DotSettings
index 969cc30..6e98e3d 100644
--- a/seq-api.sln.DotSettings
+++ b/seq-api.sln.DotSettings
@@ -1,11 +1,16 @@
AD
True
+ True
True
+ True
+ True
True
True
True
True
+ True
True
+ True
True
diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs
index e313df4..6de445c 100644
--- a/src/Seq.Api/Client/SeqApiClient.cs
+++ b/src/Seq.Api/Client/SeqApiClient.cs
@@ -1,4 +1,18 @@
-using System;
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -19,6 +33,9 @@
namespace Seq.Api.Client
{
+ ///
+ /// A low-level client that provides navigation over the linked resource structure of the Seq HTTP API.
+ ///
public class SeqApiClient : IDisposable
{
readonly string _apiKey;
@@ -35,6 +52,14 @@ public class SeqApiClient : IDisposable
Converters = { new StringEnumConverter(), new LinkCollectionConverter() }
});
+ ///
+ /// Construct a .
+ ///
+ /// The base URL of the Seq server.
+ /// An API key to use when making requests to the server, if required.
+ /// Whether default credentials will be sent with HTTP requests; the default is true.
+ /// The time to wait before canceling long-running HTTP requests; the default (null) is to use the
+ /// system request timeout, normally 100 seconds. To disable timing out at the client, pass .
public SeqApiClient(string serverUrl, string apiKey = null, bool useDefaultCredentials = true, TimeSpan? requestTimeout = null)
{
ServerUrl = serverUrl ?? throw new ArgumentNullException(nameof(serverUrl));
@@ -53,45 +78,110 @@ public SeqApiClient(string serverUrl, string apiKey = null, bool useDefaultCrede
baseAddress += "/";
HttpClient = new HttpClient(handler) { BaseAddress = new Uri(baseAddress) };
+ HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV7MediaType));
+
+ if (_apiKey != null)
+ HttpClient.DefaultRequestHeaders.Add("X-Seq-ApiKey", _apiKey);
+
if (requestTimeout != null)
HttpClient.Timeout = requestTimeout.Value;
}
+ ///
+ /// The base URL of the Seq server.
+ ///
public string ServerUrl { get; }
+ ///
+ /// The HTTP client used when making requests to the Seq server. The HTTP client is configured with the server's base address, collects any cookies
+ /// sent with responses from the API, and will send the appropriate Accept and X-Seq-ApiKey headers by default.
+ ///
public HttpClient HttpClient { get; }
+ ///
+ /// Get the root entity describing the server and providing links to other resources available from the API.
+ ///
+ /// A supporting cancellation.
+ /// The root entity.
public Task GetRootAsync(CancellationToken cancellationToken = default)
{
return HttpGetAsync("api", cancellationToken);
}
+ ///
+ /// Issue a GET request returning a by following the from .
+ ///
+ /// The entity type into which the response should be deserialized.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// A supporting cancellation.
+ /// The response entity.
public Task GetAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
return HttpGetAsync(linkUri, cancellationToken);
}
+ ///
+ /// Issue a GET request returning a string by following the from .
+ ///
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// A supporting cancellation.
+ /// The response as a string.
public Task GetStringAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
return HttpGetStringAsync(linkUri, cancellationToken);
}
+ ///
+ /// Issue a GET request returning a list of by following the from .
+ ///
+ /// The entity type into which the response items should be deserialized.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// A supporting cancellation.
+ /// The response entities.
public Task> ListAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
return HttpGetAsync>(linkUri, cancellationToken);
}
+ ///
+ /// Issue a POST request accepting a serialized to a from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// A task that signals request completion.
public async Task PostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
- new StreamReader(stream).ReadToEnd();
+ using (var reader = new StreamReader(stream))
+ reader.ReadToEnd();
}
+ ///
+ /// Issue a POST request accepting a serialized and returning a by following from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// The entity type into which the response should be deserialized.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// The response entity.
public async Task PostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
@@ -100,14 +190,35 @@ public async Task PostAsync(ILinked entity, strin
return _serializer.Deserialize(new JsonTextReader(new StreamReader(stream)));
}
+ ///
+ /// Issue a POST request accepting a serialized and returning a string by following from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// The response string.
public async Task PostReadStringAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
- return await new StreamReader(stream).ReadToEndAsync();
+ using (var reader = new StreamReader(stream))
+ return await reader.ReadToEndAsync();
}
+ ///
+ /// Issue a POST request accepting a serialized and returning a stream by following from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// The response stream.
public async Task PostReadStreamAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
@@ -115,22 +226,55 @@ public async Task PostReadStreamAsync(ILinked entity, string li
return await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
}
+ ///
+ /// Issue a PUT request accepting a serialized to a from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// A task that signals request completion.
public async Task PutAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Put, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
- new StreamReader(stream).ReadToEnd();
+ using (var reader = new StreamReader(stream))
+ reader.ReadToEnd();
}
+ ///
+ /// Issue a DELETE request accepting a serialized to a from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// A task that signals request completion.
public async Task DeleteAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Delete, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
- new StreamReader(stream).ReadToEnd();
+ using (var reader = new StreamReader(stream))
+ reader.ReadToEnd();
}
+ ///
+ /// Issue a DELETE request accepting a serialized to a from .
+ ///
+ /// The entity type that will be serialized into the request payload.
+ /// The entity type into which the response should be deserialized.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// The request content.
+ /// A supporting cancellation.
+ /// The response entity.
public async Task DeleteAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
@@ -139,11 +283,28 @@ public async Task DeleteAsync(ILinked entity, str
return _serializer.Deserialize(new JsonTextReader(new StreamReader(stream)));
}
+ ///
+ /// Connect to a websocket at the address specified by following from .
+ ///
+ /// The type of the values received over the websocket.
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// A supporting cancellation.
+ /// A stream of values from the websocket.
public async Task> StreamAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
return await WebSocketStreamAsync(entity, link, parameters, reader => _serializer.Deserialize(new JsonTextReader(reader)), cancellationToken);
}
+ ///
+ /// Connect to a websocket at the address specified by following from .
+ ///
+ /// An entity previously retrieved from the API.
+ /// The name of the outbound link template present in 's collection.
+ /// Named parameters to substitute into the link template, if required.
+ /// A supporting cancellation.
+ /// A stream of raw messages from the websocket.
public async Task> StreamTextAsync(ILinked entity, string link, IDictionary parameters = null, CancellationToken cancellationToken = default)
{
return await WebSocketStreamAsync(entity, link, parameters, reader => reader.ReadToEnd(), cancellationToken);
@@ -174,16 +335,12 @@ async Task HttpGetStringAsync(string url, CancellationToken cancellation
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
- return await new StreamReader(stream).ReadToEndAsync();
+ using (var reader = new StreamReader(stream))
+ return await reader.ReadToEndAsync();
}
async Task HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
- if (_apiKey != null)
- request.Headers.Add("X-Seq-ApiKey", _apiKey);
-
- request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV7MediaType));
-
var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
@@ -213,31 +370,13 @@ HttpContent MakeJsonContent(object content)
static string ResolveLink(ILinked entity, string link, IDictionary parameters = null)
{
- Link linkItem;
- if (!entity.Links.TryGetValue(link, out linkItem))
+ if (!entity.Links.TryGetValue(link, out var linkItem))
throw new NotSupportedException($"The requested link `{link}` isn't available on entity `{entity}`.");
- var expression = linkItem.GetUri();
- var template = new UriTemplate(expression);
- if (parameters != null)
- {
- var missing = parameters.Select(p => p.Key).Except(template.GetParameterNames()).ToArray();
- if (missing.Any())
- throw new ArgumentException($"The URI template `{expression}` does not contain parameter: `{string.Join("`, `", missing)}`.");
-
- foreach (var parameter in parameters)
- {
- var value = parameter.Value is DateTime time
- ? time.ToString("O")
- : parameter.Value;
-
- template.SetParameter(parameter.Key, value);
- }
- }
-
- return template.Resolve();
+ return linkItem.GetUri(parameters);
}
+ /// >
public void Dispose()
{
HttpClient.Dispose();
diff --git a/src/Seq.Api/Client/SeqApiException.cs b/src/Seq.Api/Client/SeqApiException.cs
index 71d5966..fc53da9 100644
--- a/src/Seq.Api/Client/SeqApiException.cs
+++ b/src/Seq.Api/Client/SeqApiException.cs
@@ -1,4 +1,18 @@
-using System;
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
using System.Net;
namespace Seq.Api.Client
diff --git a/src/Seq.Api/Model/Entity.cs b/src/Seq.Api/Model/Entity.cs
index b31f7c3..7d3a98f 100644
--- a/src/Seq.Api/Model/Entity.cs
+++ b/src/Seq.Api/Model/Entity.cs
@@ -1,14 +1,42 @@
-namespace Seq.Api.Model
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Seq.Api.Model
{
+ ///
+ /// A uniquely-identifiable resource available from the Seq HTTP API.
+ ///
public abstract class Entity : ILinked
{
+ ///
+ /// Construct an .
+ ///
protected Entity()
{
Links = new LinkCollection();
}
+ ///
+ /// The entity's unique identifier. This will be null if a newly-instantiated
+ /// has not yet been sent to the server.
+ ///
public string Id { get; set; }
+ ///
+ /// A collection of outbound links from the entity. This will be empty if the entity
+ /// was instantiated locally and not received from the API.
+ ///
public LinkCollection Links { get; set; }
}
}
diff --git a/src/Seq.Api/Model/ILinked.cs b/src/Seq.Api/Model/ILinked.cs
index df7dc62..ae30187 100644
--- a/src/Seq.Api/Model/ILinked.cs
+++ b/src/Seq.Api/Model/ILinked.cs
@@ -1,7 +1,27 @@
-namespace Seq.Api.Model
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Seq.Api.Model
{
+ ///
+ /// A resource that carries outbound links.
+ ///
public interface ILinked
{
+ ///
+ /// Links associated with the resource.
+ ///
LinkCollection Links { get; }
}
}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Link.cs b/src/Seq.Api/Model/Link.cs
index b3a77d2..0513d68 100644
--- a/src/Seq.Api/Model/Link.cs
+++ b/src/Seq.Api/Model/Link.cs
@@ -1,50 +1,81 @@
-using System;
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using Tavis.UriTemplates;
+// ReSharper disable PossibleMultipleEnumeration
+
namespace Seq.Api.Model
{
- public class Link
+ ///
+ /// A hypermedia link. A link is an RFC 6570 URI template that can be
+ /// parameterized in order to produce a complete URI (if the template contains no
+ /// parameters then it may also be a literal URI).
+ ///
+ public struct Link
{
- readonly bool _isLiteral;
- readonly string _href;
- readonly IDictionary _parameters;
-
- public Link(string href, bool isLiteral = true)
- : this(href, new Dictionary())
- {
- _isLiteral = isLiteral;
- }
+ ///
+ /// An empty link.
+ ///
+ public static Link Empty { get; } = default;
- public Link(string href, IDictionary parameters)
+ ///
+ /// Construct a link.
+ ///
+ /// The URI template.
+ public Link(string template)
{
- if (href == null) throw new ArgumentNullException(nameof(href));
- if (parameters == null) throw new ArgumentNullException(nameof(parameters));
- _href = href;
- _parameters = parameters;
+ Template = template ?? throw new ArgumentNullException(nameof(template));
}
- public Link(string href, object parameters)
- : this(href, parameters
- .GetType()
- .GetTypeInfo()
- .DeclaredProperties
- .ToDictionary(p => p.Name, p => p.GetValue(parameters)))
- {
- }
+ ///
+ /// Get the unprocessed URI template.
+ ///
+ public string Template { get; }
- public string GetUri()
+ ///
+ /// Parameterize the link to construct a URI.
+ ///
+ /// Parameters to substitute into the template, if any.
+ /// A constructed URI.
+ /// This method ensures that templates containing parameters cannot be accidentally
+ /// used as URIs.
+ public string GetUri(IDictionary parameters = null)
{
- if (_isLiteral) return _href;
+ if (Template == null) throw new InvalidOperationException("Attempted to process an empty URI template.");
- var template = new UriTemplate(_href);
- foreach (var parameter in _parameters)
+ var template = new UriTemplate(Template);
+ if (parameters != null)
{
- template.SetParameter(parameter.Key, parameter.Value);
+ var missing = parameters.Select(p => p.Key).Except(template.GetParameterNames());
+ if (missing.Any())
+ throw new ArgumentException($"The URI template `{Template}` does not contain parameter: `{string.Join("`, `", missing)}`.");
+
+ foreach (var parameter in parameters)
+ {
+ var value = parameter.Value is DateTime time
+ ? time.ToString("O")
+ : parameter.Value;
+
+ template.SetParameter(parameter.Key, value);
+ }
}
+
return template.Resolve();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Seq.Api/Model/LinkCollection.cs b/src/Seq.Api/Model/LinkCollection.cs
index e4afbf3..aebab05 100644
--- a/src/Seq.Api/Model/LinkCollection.cs
+++ b/src/Seq.Api/Model/LinkCollection.cs
@@ -1,18 +1,30 @@
-using System;
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Seq.Api.Serialization;
namespace Seq.Api.Model
{
+ ///
+ /// A collection of s indexed by case-insensitive name.
+ ///
[JsonConverter(typeof(LinkCollectionConverter))]
public class LinkCollection : Dictionary
{
public LinkCollection() : base(StringComparer.OrdinalIgnoreCase) { }
-
- public string GetUri(string name)
- {
- return this[name].GetUri();
- }
}
}
diff --git a/src/Seq.Api/Model/ResourceGroup.cs b/src/Seq.Api/Model/ResourceGroup.cs
index f634ddd..3837ca8 100644
--- a/src/Seq.Api/Model/ResourceGroup.cs
+++ b/src/Seq.Api/Model/ResourceGroup.cs
@@ -1,12 +1,34 @@
-namespace Seq.Api.Model
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Seq.Api.Model
{
+ ///
+ /// A resource group is a logical partitioning of the API, usually
+ /// associated with a particular type of entity.
+ ///
public class ResourceGroup : ILinked
{
+ ///
+ /// Construct a .
+ ///
public ResourceGroup()
{
Links = new LinkCollection();
}
+ ///
public LinkCollection Links { get; set; }
}
}
diff --git a/src/Seq.Api/SeqConnection.cs b/src/Seq.Api/SeqConnection.cs
index cc5aa28..3c03704 100644
--- a/src/Seq.Api/SeqConnection.cs
+++ b/src/Seq.Api/SeqConnection.cs
@@ -1,4 +1,18 @@
-using System;
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
diff --git a/src/Seq.Api/Serialization/LinkCollectionConverter.cs b/src/Seq.Api/Serialization/LinkCollectionConverter.cs
index 01dc797..c560bed 100644
--- a/src/Seq.Api/Serialization/LinkCollectionConverter.cs
+++ b/src/Seq.Api/Serialization/LinkCollectionConverter.cs
@@ -1,4 +1,18 @@
-using System;
+// Copyright 2014-2019 Datalust and contributors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
@@ -6,12 +20,12 @@
namespace Seq.Api.Serialization
{
- public class LinkCollectionConverter : JsonConverter
+ class LinkCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var lc = (LinkCollection)value;
- var dictionary = lc.ToDictionary(kv => kv.Key, kv => kv.Value.GetUri());
+ var dictionary = lc.ToDictionary(kv => kv.Key, kv => kv.Value.Template);
serializer.Serialize(writer, dictionary);
}
diff --git a/src/Seq.Api/Streams/ObservableStream.Unsubscriber.cs b/src/Seq.Api/Streams/ObservableStream.Unsubscriber.cs
index e4380e3..f7f510b 100644
--- a/src/Seq.Api/Streams/ObservableStream.Unsubscriber.cs
+++ b/src/Seq.Api/Streams/ObservableStream.Unsubscriber.cs
@@ -12,6 +12,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+
using System;
namespace Seq.Api.Streams
@@ -25,10 +26,8 @@ sealed class Unsubscriber : IDisposable
public Unsubscriber(ObservableStream sink, IObserver observer)
{
- if (sink == null) throw new ArgumentNullException(nameof(sink));
- if (observer == null) throw new ArgumentNullException(nameof(observer));
- _stream = sink;
- _observer = observer;
+ _stream = sink ?? throw new ArgumentNullException(nameof(sink));
+ _observer = observer ?? throw new ArgumentNullException(nameof(observer));
}
public void Dispose()
diff --git a/src/Seq.Api/Streams/ObservableStream.cs b/src/Seq.Api/Streams/ObservableStream.cs
index 0ddc419..2af4853 100644
--- a/src/Seq.Api/Streams/ObservableStream.cs
+++ b/src/Seq.Api/Streams/ObservableStream.cs
@@ -24,8 +24,10 @@
namespace Seq.Api.Streams
{
- // Some questionable synchronization in this one; probably better to take the Rx dependency
- // and use the Rx support classes here.
+ ///
+ /// A stream of values read from a .
+ ///
+ /// The type of the values.
public sealed partial class ObservableStream : IObservable, IDisposable
{
readonly object _syncRoot = new object();
@@ -42,6 +44,7 @@ internal ObservableStream(ClientWebSocket socket, Func deserializ
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
}
+ ///
public IDisposable Subscribe(IObserver observer)
{
if (observer == null) throw new ArgumentNullException(nameof(observer));
@@ -182,6 +185,7 @@ static void OnError(IEnumerable exceptions)
throw new AggregateException("At least one observer failed to accept the event", exceptions);
}
+ ///
public void Dispose()
{
lock (_syncRoot)
diff --git a/test/Seq.Api.Tests/LinkTests.cs b/test/Seq.Api.Tests/LinkTests.cs
new file mode 100644
index 0000000..ae6a636
--- /dev/null
+++ b/test/Seq.Api.Tests/LinkTests.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using Seq.Api.Model;
+using Xunit;
+
+namespace Seq.Api.Tests
+{
+ public class LinkTests
+ {
+ [Fact]
+ public void ALinkWithNoParametersIsLiteral()
+ {
+ const string uri = "https://example.com";
+ var link = new Link(uri);
+ var constructed = link.GetUri();
+ Assert.Equal(uri, constructed);
+ }
+
+ [Fact]
+ public void AParameterizedLinkCanBeConstructed()
+ {
+ const string template = "https://example.com/{name}";
+ var link = new Link(template);
+ var constructed = link.GetUri(new Dictionary {["name"] = "test"});
+ Assert.Equal("https://example.com/test", constructed);
+ }
+
+ [Fact]
+ public void InvalidParametersAreDetected()
+ {
+ const string template = "https://example.com";
+ var link = new Link(template);
+ Assert.Throws(() => link.GetUri(new Dictionary {["name"] = "test"}));
+ }
+ }
+}