Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions Core/Attributes/MapToAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Supabase.Core.Attributes
{
Expand All @@ -14,7 +12,7 @@ public class MapToAttribute : Attribute
/// The externally specified target value.
/// </summary>
public string Mapping { get; set; }

/// <summary>
/// A formatter to be passed into the <see cref="String.ToString()" /> method.
/// </summary>
Expand All @@ -29,8 +27,8 @@ public class MapToAttribute : Attribute
/// <param name="formatter"></param>
public MapToAttribute(string mapping, string? formatter = null)
{
Mapping = mapping;
Formatter = formatter;
this.Mapping = mapping;
this.Formatter = formatter;
}
}
}
93 changes: 50 additions & 43 deletions Core/Diagnostics/ActivityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,60 +14,67 @@ namespace Supabase.Core.Diagnostics
/// </summary>
public static class ActivityExtensions
{
/// <summary>
/// Tags an outgoing HTTP request following OpenTelemetry HTTP client conventions.
/// The URL is sanitized to scheme/host/port/path; the query string is never recorded.
/// </summary>
/// <param name="activity">The activity to tag, or null when nothing is listening.</param>
/// <param name="method">The HTTP method, e.g. <c>POST</c>.</param>
/// <param name="uri">The request URI, sanitized before tagging.</param>
public static Activity? SetHttpRequestTags(this Activity? activity, string method, Uri uri)
extension(Activity? activity)
{
if (activity == null)
return null;
/// <summary>
/// Tags an outgoing HTTP request following OpenTelemetry HTTP client conventions.
/// The URL is sanitized to scheme/host/port/path; the query string is never recorded.
/// </summary>
/// <param name="method">The HTTP method, e.g. <c>POST</c>.</param>
/// <param name="uri">The request URI, sanitized before tagging.</param>
public Activity? SetHttpRequestTags(string method, Uri uri)
{
if (activity == null)
return null;

activity.SetTag("http.request.method", method);
activity.SetTag("server.address", uri.Host);
if (!uri.IsDefaultPort)
activity.SetTag("server.port", uri.Port);
activity.SetTag("http.request.method", method);
activity.SetTag("server.address", uri.Host);
if (!uri.IsDefaultPort)
activity.SetTag("server.port", uri.Port);

activity.SetTag("url.full", UrlSanitizer.Sanitize(uri));
return activity;
}
activity.SetTag("url.full", UrlSanitizer.Sanitize(uri));
return activity;
}

/// <summary>
/// Tags the response status code, marking the activity as failed for 4xx/5xx responses.
/// </summary>
/// <param name="activity">The activity to tag, or null when nothing is listening.</param>
/// <param name="statusCode">The HTTP response status code.</param>
public static Activity? SetHttpResponseTags(this Activity? activity, int statusCode)
{
if (activity == null)
return null;
/// <summary>
/// Tags the response status code, marking the activity as failed for 4xx/5xx responses.
/// </summary>
/// <param name="statusCode">The HTTP response status code.</param>
public Activity? SetHttpResponseTags(int statusCode)
{
if (activity == null)
return null;

activity.SetTag("http.response.status_code", statusCode);
return statusCode >= 400 ? activity.SetErrorTags( statusCode) : activity;
}

activity.SetTag("http.response.status_code", statusCode);
if (statusCode >= 400)
/// <summary>
/// Marks the activity as failed with the exception type as <c>error.type</c>.
/// </summary>
/// <param name="exception">The exception whose type and message describe the failure.</param>
public Activity? SetFailure(Exception exception)
{
activity.SetTag("error.type", statusCode.ToString());
activity.SetStatus(ActivityStatusCode.Error);
if (activity == null)
return null;

activity.SetTag("error.type", exception.GetType().FullName);
activity.SetStatus(ActivityStatusCode.Error, exception.Message);
return activity;
}

return activity;
}

/// <summary>
/// Marks the activity as failed with the exception type as <c>error.type</c>.
/// </summary>
/// <param name="activity">The activity to tag, or null when nothing is listening.</param>
/// <param name="exception">The exception whose type and message describe the failure.</param>
public static Activity? SetFailure(this Activity? activity, Exception exception)
{
if (activity == null)
return null;
private Activity? SetErrorTags( int statusCode)
{
if (activity == null)
return null;

activity.SetTag("error.type", exception.GetType().FullName);
activity.SetStatus(ActivityStatusCode.Error, exception.Message);
return activity;
activity.SetTag("error.type", statusCode.ToString());
activity.SetStatus(ActivityStatusCode.Error);
return activity;
}
}

}
}
7 changes: 2 additions & 5 deletions Core/Diagnostics/Instrumentation.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Reflection;
Expand All @@ -21,17 +20,15 @@ public static class Instrumentation
/// </summary>
/// <param name="assembly">The assembly whose version identifies the emitting library.</param>
/// <param name="name">The source name, e.g. <c>Supabase.Gotrue</c>.</param>
public static ActivitySource CreateActivitySource(Assembly assembly, string name) =>
new ActivitySource(name, GetVersion(assembly));
public static ActivitySource CreateActivitySource(Assembly assembly, string name) => new(name, GetVersion(assembly));

/// <summary>
/// Creates the <see cref="Meter"/> for a Supabase client library, versioned from the
/// library's assembly rather than a hardcoded literal.
/// </summary>
/// <param name="assembly">The assembly whose version identifies the emitting library.</param>
/// <param name="name">The meter name, e.g. <c>Supabase.Gotrue</c>.</param>
public static Meter CreateMeter(Assembly assembly, string name) =>
new Meter(name, GetVersion(assembly));
public static Meter CreateMeter(Assembly assembly, string name) => new(name, GetVersion(assembly));

/// <summary>
/// Resolves the version of an assembly, preferring the informational (package) version
Expand Down
2 changes: 1 addition & 1 deletion Core/Diagnostics/UrlSanitizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static string Sanitize(string url) =>

private static string StripAfterPath(string url)
{
var delimiterIndex = url.IndexOfAny(new[] { '?', '#' });
var delimiterIndex = url.IndexOfAny(['?', '#']);
return delimiterIndex < 0 ? url : url.Substring(0, delimiterIndex);
}
}
Expand Down
30 changes: 11 additions & 19 deletions Core/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Supabase.Core.Extensions
{
Expand All @@ -12,32 +10,26 @@ public static class DictionaryExtensions
{
/// <summary>
/// Merges two dictionaries, allowing overwrite priorities leftward.
///
///
/// Works in C#3/VS2008:
/// Returns a new dictionary of this ... others merged leftward.
/// Keeps the type of 'this', which must be default-instantiable.
/// Example:
/// Example:
/// result = map.MergeLeft(other1, other2, ...)
/// From: https://stackoverflow.com/a/2679857/3629438
/// </summary>
/// <param name="me"></param>
/// <param name="others"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
public static T MergeLeft<T, K, V>(this T me, params IDictionary<K, V>[] others)
where T : IDictionary<K, V>, new()
{
T newMap = new T();
foreach (IDictionary<K, V> src in (new List<IDictionary<K, V>> { me }).Concat(others))
public static T MergeLeft<T, TKey, TValue>(this T me, params IDictionary<TKey, TValue>[] others)
where T : IDictionary<TKey, TValue>, new() =>
others.Prepend(me).SelectMany(pairs => pairs).Aggregate(new T(), (newMap, pair) =>
{
foreach (KeyValuePair<K, V> p in src)
{
newMap[p.Key] = p.Value;
}
}
return newMap;
}
newMap[pair.Key] = pair.Value;
return newMap;
});
}
}
7 changes: 2 additions & 5 deletions Core/Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using Supabase.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Supabase.Core
{
Expand All @@ -19,15 +17,15 @@ public static class Helpers
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T GetPropertyValue<T>(object obj, string propName) => (T)obj.GetType().GetProperty(propName).GetValue(obj, null);

/// <summary>
/// Returns a cast Custom Attribute from a given object.
/// </summary>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T GetCustomAttribute<T>(object obj) where T : Attribute => (T)Attribute.GetCustomAttribute(obj.GetType(), typeof(T));

/// <summary>
/// Returns a cast Custom Attribute from a given type.
/// </summary>
Expand All @@ -45,7 +43,6 @@ public static MapToAttribute GetMappedToAttr(Enum obj)
{
var type = obj.GetType();
var name = Enum.GetName(type, obj);

return type.GetField(name).GetCustomAttributes(false).OfType<MapToAttribute>().SingleOrDefault();
}
}
Expand Down
1 change: 0 additions & 1 deletion Core/Interfaces/IGettableHeaders.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Supabase.Core.Interfaces
{
Expand Down
18 changes: 9 additions & 9 deletions Core/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static string GetAssemblyVersion(Type clientType) =>

// Testability seam: the platform and framework probes read process-wide ambient state
// (RuntimeInformation, the loaded assembly set) that a host cannot vary at runtime. This
// overload takes those reads as parameters so every branch is reachable hermetically; the
// overload takes those reads as parameters, so every branch is reachable hermetically; the
// public entry point above supplies the real values. Internal — not part of the public API.
internal static string GetAssemblyVersion(Type clientType, string osDescription, Func<OSPlatform, bool> isOsPlatform, IReadOnlyCollection<Assembly> loadedAssemblies) =>
$"{GetClientName(clientType)}-csharp/{GetClientVersion(clientType)}{BuildMetadata(osDescription, isOsPlatform, loadedAssemblies)}";
Expand All @@ -51,11 +51,11 @@ internal MetadataEntry(string key, string value, string? version = null)
this.version = version;
}

public override string ToString() => string.IsNullOrEmpty(version)
? $"; {key}={value}"
: $"; {key}={value}; {key}-version={version}";
internal static MetadataEntry Unknown(string key) => new MetadataEntry(key, "unknown");
public override string ToString() => string.IsNullOrEmpty(this.version)
? $"; {this.key}={this.value}"
: $"; {this.key}={this.value}; {this.key}-version={this.version}";

internal static MetadataEntry Unknown(string key) => new(key, "unknown");
}

private static string GetPlatform(string osDescription, Func<OSPlatform, bool> isOsPlatform)
Expand All @@ -69,9 +69,9 @@ private static string GetPlatform(string osDescription, Func<OSPlatform, bool> i
return osDescription;
}

private static MetadataEntry GetPlatformInfo(string osDescription, Func<OSPlatform, bool> isOsPlatform) => new MetadataEntry("platform", GetPlatform(osDescription, isOsPlatform), Environment.OSVersion.Version.ToString());
private static MetadataEntry GetPlatformInfo(string osDescription, Func<OSPlatform, bool> isOsPlatform) => new("platform", GetPlatform(osDescription, isOsPlatform), Environment.OSVersion.Version.ToString());

private static MetadataEntry GetRuntimeInfo() => new MetadataEntry("runtime", "dotnet", Environment.Version.ToString());
private static MetadataEntry GetRuntimeInfo() => new("runtime", "dotnet", Environment.Version.ToString());

// Priority is explicit: MAUI wins over Blazor in hybrid apps where both assemblies are present.
// Unity version uses GetCustomAttributesData() rather than member reflection — safe under IL2CPP.
Expand All @@ -96,7 +96,7 @@ private static MetadataEntry GetFrameworkInfo(IReadOnlyCollection<Assembly> load
private static CustomAttributeData[] SafeGetCustomAttributesData(Assembly assembly)
{
try { return assembly.GetCustomAttributesData().ToArray(); }
catch { return Array.Empty<CustomAttributeData>(); }
catch { return []; }
}

private static string? GetUnityVersion(IReadOnlyCollection<Assembly> loadedAssemblies)
Expand Down
Loading
Loading