Update request

#!/bin/bash perform_update_request() { local access_id="$1" local access_code="$2" local updates="$3" local timeout=30 # === Input Validation === if [[ -z "$access_id" ]]; then echo "Error: access_id must be a non-empty string" >&2 return 1 fi if [[ -z "$access_code" ]]; then echo "Error: access_code must be a non-empty string" >&2 return 1 fi # Validate updates: must be integer between 1 and 50 if ! [[ "$updates" =~ ^[0-9]+$ ]] || (( updates < 1 || updates > 50 )); then echo "Error: updates must be an integer between 1 and 50 (hard limit)" >&2 return 1 fi local url="https://www.serversnug.com/api/public/v1/update/" # === Build JSON payload === local payload payload=$(jq -n \ --arg access_id "$access_id" \ --arg access_code "$access_code" \ --argjson updates "$updates" \ '{access_id: $access_id, access_code: $access_code, updates: $updates}') || { echo "Error: Failed to construct JSON payload" >&2 return 1 } # === Make the HTTP request === local tmpfile tmpfile=$(mktemp) || { echo "Error: Failed to create temporary file" >&2 return 1 } local http_code http_code=$(curl -sS --max-time "$timeout" \ -w "%{http_code}" \ -o "$tmpfile" \ -X POST \ -H "Content-Type: application/json" \ -d "$payload" \ "$url") local curl_exit=$? local response_body response_body=$(<"$tmpfile") rm -f "$tmpfile" # Handle curl failures (timeout or connection issues) if [[ $curl_exit -ne 0 ]]; then if [[ $curl_exit -eq 28 ]]; then echo "Error: Request timed out while contacting ServerSnug API" >&2 else echo "Error: Failed to connect to ServerSnug API (network issue)" >&2 fi return 1 fi # === Validate JSON === if ! echo "$response_body" | jq empty > /dev/null 2>&1; then echo "Error: Invalid JSON response from server. Status code: $http_code" >&2 return 1 fi # === Handle HTTP status codes === case "$http_code" in 403) echo "Error: 403 Forbidden - Credentials not recognized" >&2 return 1 ;; 500) echo "Error: 500 Internal Server Error - ServerSnug service issue" >&2 return 1 ;; 502) echo "Error: 502 Bad Gateway - ServerSnug service issue" >&2 return 1 ;; 503) echo "Error: 503 Service Unavailable - ServerSnug under maintenance" >&2 return 1 ;; 422) local err err=$(echo "$response_body" | jq -r '.error // "Unknown error"') echo "Error: 422 Processing Error: $err" >&2 return 1 ;; esac if [[ "$http_code" != "200" ]]; then echo "Error: Unexpected HTTP status: $http_code" >&2 return 1 fi # === Check for API-level errors (can occur even on HTTP 200) === local api_error api_error=$(echo "$response_body" | jq -r '.error // empty') if [[ -n "$api_error" ]]; then case "$api_error" in "Allowance exceeded") echo "Error: Allowance exceeded - fewer results returned than requested" >&2 ;; "Malformed request") echo "Error: Malformed request - check JSON format" >&2 ;; *) echo "Error: API error: $api_error" >&2 ;; esac return 1 fi # === Validate response structure === if ! echo "$response_body" | jq -e 'has("count") and has("response")' > /dev/null; then echo "Error: Response missing required fields ('count' or 'response')" >&2 return 1 fi # Success → print JSON to stdout echo "$response_body" return 0 }
import requests import json from typing import Dict, Any def perform_update_request( access_id: str, access_code: str, updates: int, timeout: int = 30 ) -> Dict[str, Any]: """ Performs a ServerSnug 'update' request with full error handling. Returns the parsed JSON response on success. Raises detailed exceptions for any failure. """ # Input validation if not access_id or not isinstance(access_id, str): raise ValueError("access_id must be a non-empty string") if not access_code or not isinstance(access_code, str): raise ValueError("access_code must be a non-empty string") if not isinstance(updates, int) or updates < 1 or updates > 50: raise ValueError("updates must be an integer between 1 and 50 (hard limit)") url = "https://www.serversnug.com/api/public/v1/update/" payload = { "access_id": access_id, "access_code": access_code, "updates": updates } try: response = requests.post( url, json=payload, timeout=timeout, allow_redirects=False ) except requests.exceptions.Timeout: raise ConnectionError("Request timed out while contacting ServerSnug API") except requests.exceptions.ConnectionError: raise ConnectionError("Failed to connect to ServerSnug API (network issue)") except requests.exceptions.RequestException as e: raise ConnectionError(f"Request failed: {e}") # Always attempt to parse JSON try: data: Dict[str, Any] = response.json() except json.JSONDecodeError: raise ValueError(f"Invalid JSON response from server. Status code: {response.status_code}") except Exception as e: raise ValueError(f"Failed to parse response: {e}") # Handle HTTP-level errors if response.status_code == 403: raise PermissionError("403 Forbidden - Credentials not recognized") if response.status_code == 500: raise RuntimeError("500 Internal Server Error - ServerSnug service issue") if response.status_code == 502: raise RuntimeError("502 Internal Server Error - ServerSnug service issue") if response.status_code == 503: raise RuntimeError("503 Service Unavailable - ServerSnug under maintenance") if response.status_code == 422: error_msg = data.get("error") # Error is Missing updates raise RuntimeError("422 Processing Error: {error_msg}") if response.status_code != 200: raise RuntimeError(f"Unexpected HTTP status: {response.status_code}") # Check for API-level error message (returned with 200) error_msg = data.get("error") if error_msg: if error_msg == "Allowance exceeded": raise RuntimeError("Allowance exceeded - fewer results returned than requested") if error_msg == "Malformed request": raise ValueError("Malformed request - check JSON format") raise RuntimeError(f"API error: {error_msg}") # Validate expected success structure if "count" not in data or "response" not in data: raise ValueError("Response missing required fields ('count' or 'response')") return data
<?php /** * Performs a ServerSnug 'update' request with full error handling (PHP equivalent). * Returns the parsed JSON response on success. * Throws detailed exceptions for any failure. */ function perform_update_request( string $access_id, string $access_code, int $updates, int $timeout = 30 ): array { // Input validation if (empty($access_id) || !is_string($access_id)) { throw new InvalidArgumentException("access_id must be a non-empty string"); } if (empty($access_code) || !is_string($access_code)) { throw new InvalidArgumentException("access_code must be a non-empty string"); } if ($updates < 1 || $updates > 50) { throw new InvalidArgumentException("updates must be an integer between 1 and 50 (hard limit)"); } $url = "https://www.serversnug.com/api/public/v1/update/"; $payload = [ "access_id" => $access_id, "access_code" => $access_code, "updates" => $updates ]; $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Defensive: same as Python version curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); $responseBody = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); // Network / cURL errors if ($responseBody === false) { throw new RuntimeException("cURL request failed: " . $curlError); } if ($httpCode === 0) { throw new RuntimeException("Failed to connect to ServerSnug API (network issue)"); } // Always attempt to parse JSON even on error status codes $data = json_decode($responseBody, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new RuntimeException("Invalid JSON response from server. Status code: $httpCode"); } // Handle HTTP-level errors if ($httpCode === 403) { throw new RuntimeException("403 Forbidden - Credentials not recognized"); } if ($httpCode === 500) { throw new RuntimeException("500 Internal Server Error - ServerSnug service issue"); } if ($httpCode !== 200) { throw new RuntimeException("Unexpected HTTP status: $httpCode"); } // Check for API-level error message (returned with 200) if (isset($data['error'])) { $errorMsg = $data['error']; if ($errorMsg === "Allowance exceeded") { throw new RuntimeException("Allowance exceeded - fewer results returned than requested"); } if ($errorMsg === "Malformed request") { throw new InvalidArgumentException("Malformed request - check JSON format"); } if ($errorMsg === "Missing updates") { throw new InvalidArgumentException("Missing 'updates' field in request"); } throw new RuntimeException("API error: $errorMsg"); } // Validate expected success structure if (!isset($data['count']) || !isset($data['response'])) { throw new RuntimeException("Response missing required fields ('count' or 'response')"); } return $data; } ?>
/** * Performs a ServerSnug 'update' request with full error handling (Node.js equivalent). * Requires Node.js v18+ (native fetch). Returns the parsed JSON response on success. * Throws detailed errors for any failure — identical logic to the Python/PHP versions. */ async function performUpdateRequest(accessId, accessCode, updates, timeout = 30) { // === Input validation === if (!accessId || typeof accessId !== 'string') { throw new TypeError("accessId must be a non-empty string"); } if (!accessCode || typeof accessCode !== 'string') { throw new TypeError("accessCode must be a non-empty string"); } if (typeof updates !== 'number' || !Number.isInteger(updates) || updates < 1 || updates > 50) { throw new RangeError("updates must be an integer between 1 and 50 (hard limit)"); } const url = "https://www.serversnug.com/api/public/v1/update/"; const payload = { access_id: accessId, access_code: accessCode, updates: updates }; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(timeout * 1000) // Clean timeout handling }); let data; try { data = await response.json(); } catch (jsonError) { throw new Error(`Invalid JSON response from server. Status code: ${response.status}`); } // === HTTP status code handling === if (response.status === 403) { throw new Error("403 Forbidden - Credentials not recognized"); } if ([500, 502].includes(response.status)) { throw new Error(`${response.status} Internal Server Error - ServerSnug service issue`); } if (response.status === 503) { throw new Error("503 Service Unavailable - ServerSnug under maintenance"); } if (response.status === 422) { const errorMsg = data?.error || "Unknown error"; throw new Error(`422 Processing Error: ${errorMsg}`); } if (response.status !== 200) { throw new Error(`Unexpected HTTP status: ${response.status}`); } // === API-level error messages (even with HTTP 200) === if (data?.error) { const errorMsg = data.error; if (errorMsg === "Allowance exceeded") { throw new Error("Allowance exceeded - fewer results returned than requested"); } if (errorMsg === "Malformed request") { throw new TypeError("Malformed request - check JSON format"); } throw new Error(`API error: ${errorMsg}`); } // === Validate expected response structure === if (typeof data?.count === 'undefined' || typeof data?.response === 'undefined') { throw new Error("Response missing required fields ('count' or 'response')"); } return data; } catch (error) { // Handle timeout specifically if (error.name === 'TimeoutError' || error.name === 'AbortError') { throw new Error("Request timed out while contacting ServerSnug API"); } // Handle network/connection errors if (error instanceof TypeError && error.message.includes('fetch')) { throw new Error("Failed to connect to ServerSnug API (network issue)"); } // Re-throw all other errors (our custom ones) throw error; } } export { performUpdateRequest };