Core/NetworkStorageServerErrors.cs
using System;
using System.Text.Json;

namespace Sandbox;

public static partial class NetworkStorage
{
	private static void LogServerError( string slug, JsonElement json )
	{
		var code = "UNKNOWN";
		var message = "";
		var authFailureKind = "";
		var statusCode = 0;

		if ( json.TryGetProperty( "error", out var err ) )
		{
			if ( err.ValueKind == JsonValueKind.Object )
			{
				code = err.TryGetProperty( "code", out var c ) ? c.GetString() ?? "UNKNOWN" : "UNKNOWN";
				message = err.TryGetProperty( "message", out var m ) ? m.GetString() ?? "" : "";
				authFailureKind = err.TryGetProperty( "authFailureKind", out var authKind )
					? authKind.GetString() ?? ""
					: "";
			}
			else if ( err.ValueKind == JsonValueKind.String )
			{
				code = err.GetString() ?? "UNKNOWN";
			}
		}

		// Top-level message (server copies error.message here for convenience)
		if ( string.IsNullOrEmpty( message ) && json.TryGetProperty( "message", out var topMsg ) )
			message = topMsg.GetString() ?? "";

		if ( json.TryGetProperty( "status", out var statusProp ) && statusProp.ValueKind == JsonValueKind.Number )
			statusProp.TryGetInt32( out statusCode );

		if ( string.Equals( code, "SBOX_AUTH_FAILED", StringComparison.OrdinalIgnoreCase )
			|| string.Equals( authFailureKind, "token_rejected", StringComparison.OrdinalIgnoreCase ) )
		{
			InvalidateCachedAuthToken( $"{slug} {code} {authFailureKind}".Trim() );
		}

		if ( IsSecurityConfigMismatchCode( code ) )
		{
			JsonElement expected = default;
			string expectedConfigVersion = null;
			if ( err.ValueKind == JsonValueKind.Object )
			{
				if ( err.TryGetProperty( "security", out var secObj ) )
				{
					if ( secObj.TryGetProperty( "configVersion", out var cv ) )
						expectedConfigVersion = cv.GetString();
					secObj.TryGetProperty( "expected", out expected );
				}
				if ( expected.ValueKind != JsonValueKind.Object )
					err.TryGetProperty( "expected", out expected );
			}
			if ( expected.ValueKind != JsonValueKind.Object && json.TryGetProperty( "security", out var topSec ) )
			{
				if ( expectedConfigVersion == null && topSec.TryGetProperty( "configVersion", out var cv ) )
					expectedConfigVersion = cv.GetString();
				topSec.TryGetProperty( "expected", out expected );
			}
			ApplyServerExpectedSecurityConfig( code, expected, expectedConfigVersion );
		}

		if ( NetworkStorageLogConfig.LogErrors )
		{
			var logMessage = $"{code}: {message}";
			if ( IsExpectedStorageNotFound( slug, code, statusCode, message ) )
			{
				Log.Info( $"[NetworkStorage] {slug}: {logMessage}" );
				NetLog.Info( slug, logMessage );
			}
			else
			{
				Log.Warning( $"[NetworkStorage] {slug}: {code} — {message}" );
				NetLog.Error( slug, logMessage );
			}
		}
		RecordEndpointError( slug, code, message );
	}

	private static bool IsExpectedStorageNotFound( string slug, string code, int statusCode, string message )
	{
		if ( !string.Equals( slug, "storage", StringComparison.OrdinalIgnoreCase ) )
			return false;

		if ( statusCode == 404 )
			return true;

		if ( string.Equals( code, "NOT_FOUND", StringComparison.OrdinalIgnoreCase )
			|| string.Equals( code, "PROFILE_MISSING", StringComparison.OrdinalIgnoreCase ) )
			return true;

		return !string.IsNullOrWhiteSpace( message )
			&& message.Contains( "404", StringComparison.OrdinalIgnoreCase )
			&& message.Contains( "Not Found", StringComparison.OrdinalIgnoreCase );
	}

	private static bool IsHttpNotFoundException( Exception ex )
	{
		if ( ex is System.Net.Http.HttpRequestException httpEx && httpEx.StatusCode.HasValue && (int)httpEx.StatusCode.Value == 404 )
			return true;

		var message = ex?.Message ?? "";
		return message.Contains( "404", StringComparison.OrdinalIgnoreCase )
			&& message.Contains( "Not Found", StringComparison.OrdinalIgnoreCase );
	}

	private static void RecordStorageNotFound( string details )
	{
		var message = string.IsNullOrWhiteSpace( details ) ? "404 Not Found" : $"404 Not Found: {details}";
		if ( NetworkStorageLogConfig.LogErrors )
		{
			Log.Info( $"[NetworkStorage] storage: {message}" );
			NetLog.Info( "storage", message );
		}
		RecordEndpointError( "storage", "NOT_FOUND", message );
	}
}