@@ -53,18 +53,12 @@ public function __construct(
5353 if ($ secretKey === null ) {
5454 $ env = getenv ('SNAPAUTH_SECRET_KEY ' );
5555 if ($ env === false ) {
56- throw new ApiError (
57- 'Secret key missing. It can be explictly provided, or it ' .
58- 'can be auto-detected from the SNAPAUTH_SECRET_KEY ' .
59- 'environment variable. ' ,
60- );
56+ throw new Exception \MissingSecretKey ();
6157 }
6258 $ secretKey = $ env ;
6359 }
6460 if (!str_starts_with ($ secretKey , 'secret_ ' )) {
65- throw new ApiError (
66- 'Invalid secret key. Please verify you copied the full value from the SnapAuth dashboard. ' ,
67- );
61+ throw new Exception \InvalidSecretKey ();
6862 }
6963
7064 $ this ->secretKey = $ secretKey ;
@@ -136,31 +130,52 @@ public function makeApiCall(string $route, array $params): array
136130 $ code = curl_getinfo ($ ch , CURLINFO_RESPONSE_CODE );
137131
138132 if ($ response === false || $ errno !== CURLE_OK ) {
139- $ this -> error ( );
133+ throw new Exception \ Network ( $ errno );
140134 }
135+ } finally {
136+ curl_close ($ ch );
137+ }
141138
142- if ($ code >= 300 ) {
143- $ this ->error ();
144- }
145- // Handle non-200s, non-JSON (severe upstream error)
146- assert (is_string ($ response ));
139+ assert (is_string ($ response ), 'No response body despite CURLOPT_RETURNTRANSFER ' );
140+ try {
147141 $ decoded = json_decode ($ response , true , flags: JSON_THROW_ON_ERROR );
148- assert (is_array ($ decoded ));
149- return $ decoded ['result ' ];
150142 } catch (JsonException ) {
151- $ this ->error ();
152- } finally {
153- curl_close ($ ch );
143+ // Received non-JSON response - wrap and rethrow
144+ throw new Exception \MalformedResponse ('Received non-JSON response ' , $ code );
154145 }
155- }
156146
157- /**
158- * TODO: specific error info!
159- */
160- private function error (): never
161- {
162- throw new ApiError ();
163- // TODO: also make this more specific
147+ if (!is_array ($ decoded ) || !array_key_exists ('result ' , $ decoded )) {
148+ // Received JSON response in an unexpected format
149+ throw new Exception \MalformedResponse ('Received JSON in an unexpected format ' , $ code );
150+ }
151+
152+ // Success!
153+ if ($ decoded ['result ' ] !== null ) {
154+ assert ($ code >= 200 && $ code < 300 , 'Got a result with a non-2xx response ' );
155+ return $ decoded ['result ' ];
156+ }
157+
158+ // The `null` result indicated an error. Parse out the response shape
159+ // more and throw an appropriate ApiError.
160+ if (!array_key_exists ('errors ' , $ decoded )) {
161+ throw new Exception \MalformedResponse ('Error details missing ' , $ code );
162+ }
163+ $ errors = $ decoded ['errors ' ];
164+ if (!is_array ($ errors ) || !array_is_list ($ errors ) || count ($ errors ) === 0 ) {
165+ throw new Exception \MalformedResponse ('Error details are invalid or empty ' , $ code );
166+ }
167+
168+ $ primaryError = $ errors [0 ];
169+ if (
170+ !is_array ($ primaryError )
171+ || !array_key_exists ('code ' , $ primaryError )
172+ || !array_key_exists ('message ' , $ primaryError )
173+ ) {
174+ throw new Exception \MalformedResponse ('Error details are invalid or empty ' , $ code );
175+ }
176+
177+ // Finally, the error details are known to be in the correct shape.
178+ throw new Exception \CodedError ($ primaryError ['message ' ], $ primaryError ['code ' ], $ code );
164179 }
165180
166181 public function __debugInfo (): array
0 commit comments