الأخطاء
كل إخفاق يرفع خطأً. صنف للرفض، وصنف لغياب الرد، ومعرّف طلب على كل خطأ من API.
التقاط خطأ
message = {from: "[email protected]", to: "[email protected]", subject: "Your September invoice", text: "Invoice attached."} begin client.emails.send(message)rescue OpenEmail::ApiError => error warn "#{error.code} #{error.param} #{error.message}" if error.validation? warn client.addresses.list_all.addresses.inspect if error.permission? warn "try again in #{error.retry_after_seconds} seconds" if error.rate_limited? warn "#{error.status} #{error.request_id}" raiserescue OpenEmail::NetworkError => error warn "no answer in time" if error.timeout? raiseendالخطأ permission_error عند الإرسال يعود عادةً إلى نطاق الإرسال الخاص بالمفتاح، أي نطاق أو عنوان لم يُمنح له، لا إلى مساحة العمل، ولهذا يطبع المثال ما يقوله addresses.list_all عن العناوين التي يجوز لهذا المفتاح الإرسال باسمها.
لكل نوع من الرفض صنف فرعي خاص به، فيستطيع rescue أن يختار حسب الصنف الأنواع التي يعالجها ويترك الباقي يصعد إلى الأعلى.
message = {from: "[email protected]", to: "[email protected]", subject: "Your September invoice", text: "Invoice attached."} begin client.emails.send(message)rescue OpenEmail::ValidationError => error warn "#{error.param}: #{error.message}"rescue OpenEmail::AuthenticationError, OpenEmail::PermissionError => error warn "the key cannot do this: #{error.code}" raiserescue OpenEmail::Error => error warn "#{error.class}: #{error.message}" raiseendالأصناف
| الصنف | متى |
|---|---|
| OpenEmail::Error | أساس كل خطأ يعرّفه الـ gem، فيلتقطها rescue OpenEmail::Error كلها. ولا يلتقط ArgumentError. |
| OpenEmail::ApiError | أجابت API، ولكن ليس بنجاح. يحمل status وtype وcode وparam وdoc_url وrequest_id وretry_after_seconds وfields وbody. ويُرفع بصنفه هذا حين تكون قيمة type هي api_error، كما في أعطال الخادم، وبالصنف الفرعي المقابل لقيمة type فيما عدا ذلك. |
| OpenEmail::InvalidRequestError وAuthenticationError وPermissionError وNotFoundError وConflictError وValidationError وRateLimitError | أصناف فرعية من ApiError، واحد لكل type: invalid_request_error وauthentication_error وpermission_error وnot_found_error وconflict_error وvalidation_error وrate_limit_error. |
| OpenEmail::NetworkError | لم تصل أي استجابة: DNS، أو TLS، أو اتصال مرفوض أو منقطع، أو انتهاء المهلة. يحمل original، وهو الاستثناء الأصلي الذي هو أيضًا cause الخاص به، وتكون قيمة timeout? هي true حين يكون انتهاء المهلة هو السبب. |
| OpenEmail::WebhookSignatureError | رفض OpenEmail.verify_webhook_signature عملية تسليم. |
| ArgumentError | يُرفع قبل إرسال أي شيء: مفتاح مفقود أو مشوّه، أو base_url: غير صالح للاستخدام، أو معرّف فارغ. وهو صنف Ruby العادي، لا OpenEmail::Error، لأنه يعني أن الاستدعاء نفسه خاطئ. |
ما يحمله ApiError
messageString- الجملة التي تكتبها API نفسها، مكتوبة لشخص، وتسمّي القيمة المخالفة إن وُجدت. ليست معرّفًا ثابتًا، فاعتمد على `code` في التفرّع.
statusInteger- حالة HTTP للرد.
typeString- واحدة من القيم الثماني في `OpenEmail::ERROR_TYPES`، وهي مجموعة مجمَّدة لن تكبر. وحين لا يسمّي المتن أيًّا منها، تُستنتج من الحالة.
codeString- الإخفاق المحدد، مثل `from_address_forbidden` أو `invalid_email_address`. مجموعة مفتوحة تتوسع بالإضافة، فعامل الرمز الذي لا تعرفه وفق `type` الخاص به. وتكون القيمة `unrecognised_response` حين لا يكون المتن غلاف أخطاء API.
paramString or nil- الحقل الذي رُفض، كمسار منقوط مثل `to.0`، حين يسمّي الإخفاق حقلًا.
doc_urlString or nil- صفحة عن هذا الإخفاق، حين تسمّي API صفحة.
request_idString or nil- المعرّف الذي سجّل الخادم الطلب تحته، من المتن أو من الترويسة `x-request-id`.
retry_after_secondsInteger, Float or nil- مدة الانتظار التي طلبها الخادم في `Retry-After`، بالثواني، سواء أرسل رقمًا أم تاريخًا. nil حين لا يرسل شيئًا.
fieldsArray<Hash> or nil- Hash واحد لكل مشكلة، لكل منها `key` و`error`، مثل `{key: "email", error: "email"}` حين يرفض `forms.subscribe` الإجابات بخطأ 422 `invalid_form_submission`. nil حين لا يسرد الخطأ أي مشكلة.
bodyHash or nil- استجابة الخطأ كاملة، محلَّلة، بمفاتيح من نوع Symbol. nil حين تكون فارغة أو ليست JSON.
| التابع المنطقي | تكون true عندما |
|---|---|
| auth? | تكون type بقيمة authentication_error، أي 401: لا مفتاح، أو نوع اعتماد خاطئ، أو مفتاح لم نُصدره. |
| permission? | permission_error، أي 403: مفتاح حقيقي ينقصه النطاق أو عنوان From الذي يحتاجه. |
| scope_missing? | تكون code بقيمة insufficient_scope، وهو الخطأ 403 الذي يسمّي نطاقًا مفقودًا. |
| invalid_request? | invalid_request_error، أي 400: طلب تعذّر فهمه. والرسالة التي تتجاوز الحد الأقصى للحجم تعود بالخطأ 422 message_too_large، فالتابع المنطقي الذي يلتقطها هو validation?. |
| validation? | validation_error، أي 422: رفضه المخطط، وparam تسمّي الحقل. |
| not_found? | not_found_error، أي 404: لا وجود لهذا المورد. |
| conflict? | conflict_error، أي 409: تجاوز المورد النقطة التي كان يمكن عندها تنفيذ هذا عليه. |
| rate_limited? | rate_limit_error، أي 429. ويحمل retry_after_seconds مدة الانتظار حين يحددها الخادم. |
| server_error? | قيمة status هي 500 أو أكثر. اذكر request_id إن تواصلت مع الدعم. |
| retryable? | تكون status بقيمة 408 أو 429 أو 500 أو 502 أو 503 أو 504. |
| step_up_required? | قيمة code هي step_up_required، أي الخطأ 403 الذي يتلقاه رمز وصول OAuth قبل تغيير حساس إلى أن يتحقق الشخص من رمز. |
تقرأ معظم التوابع المنطقية type، أي النصف المجمَّد من الغلاف، ويمثّل كل صنف فرعي قيمة type واحدة. ويبقى code من نوع String، لأن API تضمن أنه مفتوح ويتوسع بالإضافة، فعامل الرمز الذي لا تعرفه وفق type الخاص به. فالقائمة المغلقة كانت ستجعل ترقية الـ gem ثمن قراءة نمط إخفاق جديد.
المتن الذي ليس غلاف أخطاء API يرفع مع ذلك OpenEmail::ApiError، مع استنتاج type من الحالة وضبط code على unrecognised_response. والاستجابة الناجحة التي متنها ليس JSON ترفعه أيضًا.
يصف retryable? الحالة، لا استدعاءك. فالاستدعاء الذي يمكن تكراره بأمان تكون محاولته قد أُعيدت بالفعل قبل أن يرفع الخطأ، والخطأ 429 مثل send_quota_exceeded أو ai_quota_exceeded يفشل بالطريقة نفسها إلى أن تتجدد حصته، فاعرضه على شخص بدل تكرار المحاولة في حلقة.
الاستثناء الذي يرفعه محوّلك يصبح OpenEmail::NetworkError بعد استنفاد أي محاولات يسمح بها الاستدعاء، مع الاستثناء الأصلي في original وcause. أما NameError وTypeError وArgumentError فهي الاستثناءات من هذه القاعدة: إذ تعني خطأً برمجيًا في المحوّل، فتُرفع دون تغيير ولا تُعاد محاولتها أبدًا.
request_id
يحمل كل OpenEmail::ApiError معرّف الطلب الذي أرسله الخادم، من متن الخطأ أو من الترويسة x-request-id، وهو الشيء الوحيد الذي يربط إخفاقك بسطر في سجل الخادم. أما الاستجابة الناجحة فتعيد المتن المحلَّل وحده، فلا يوجد فيها معرّف طلب لقراءته.