Class: AtlasRb::Middleware::RaiseOnResourceError
- Inherits:
-
Faraday::Middleware
- Object
- Faraday::Middleware
- AtlasRb::Middleware::RaiseOnResourceError
- Defined in:
- lib/atlas_rb/middleware/raise_on_resource_error.rb
Overview
Translates Atlas's structured re-parent / linked-member rejections into typed Ruby exceptions, so the resource bindings don't silently swallow the error envelope.
The re-parent and linked-member bindings unwrap their success payload by
a fixed key (["collection"] / ["work"] / ["community"], or the
bare linked-member array). On a 4xx that key is absent, so the binding
would return nil and discard Atlas's machine-readable error /
message. This middleware keys on the request path + status and
raises a typed error carrying the envelope through, parallel to
RaiseOnStaleResource.
It is intentionally narrow — it only fires on the re-parent
(.../parent) and linked-member (.../linked_members...) write paths,
the Compilation surface (/compilations...), the derivative-permissions
write (.../derivative_permissions), the container-create endpoints
(CREATE_PATHS), and binary uploads (/files..., /file_sets...), and
only on 403 / 422 bodies carrying an error discriminator. The upload
branch is further gated on a fixity discriminator (FIXITY_CODES), so a
422 on those paths with any other error (or 403s on uploads, which
stay raw) passes through untouched.
Everything else (other paths, other statuses, a 422 whose body uses a
different discriminator such as tombstone's code: "has_live_children")
passes through untouched, so atlas_rb stays a thin Faraday binding that
translates only the wire signals callers genuinely need to discriminate.
The ACL branch is keyed on the error code (PERMISSIONS_CODES) rather
than a path, because the write it guards is the plain resource PATCH
whose other rejections (tombstone's has_live_children) must keep passing
through — a path rule couldn't tell them apart.
Mapping:
403on a re-parent/linked/Compilation/derivative-permissions/create path → ForbiddenError422on.../parent→ ReparentError (error/resource_id)422on.../linked_members...→ LinkedMemberError422on/compilations...→ CompilationError422on.../derivative_permissions→ DerivativePermissionsError422+ an ACL discriminator anywhere → PermissionsError422+ a fixity discriminator on/files...//file_sets...→ FixityMismatchError
Constant Summary collapse
- FIXITY_CODES =
Upload-path
422discriminators this middleware translates; any othererroron those paths passes through (Atlas owns these as a wire contract). %w[fixity_mismatch unsupported_digest_algorithm].freeze
- PERMISSIONS_CODES =
ACL-invariant
422discriminators, translated wherever they appear: Atlas raises them from the shared metadata-PATCH funnel, so they can arrive on any resource'sPATCH /{type}/:id. %w[visibility_exceeds_parent].freeze
- CREATE_PATHS =
The container-create endpoints, matched exactly (no trailing id) and only on
POST, so neither a member action under the same prefix (/collections/:id/tombstone, whose422must pass through) nor the indexGETon the same path can be caught by mistake. A403here means the caller holds no edit rights on the destination container. %w[/works /collections /communities].freeze
Instance Method Summary collapse
Instance Method Details
#on_complete(env) ⇒ void
This method returns an undefined value.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/atlas_rb/middleware/raise_on_resource_error.rb', line 71 def on_complete(env) return unless [403, 422].include?(env.status) path = env.url&.path.to_s reparent = path.end_with?("/parent") linked = path.include?("/linked_members") compilation = path.start_with?("/compilations") deriv_perms = path.end_with?("/derivative_permissions") create = env.method.to_s == "post" && CREATE_PATHS.include?(path.chomp("/")) upload = path.start_with?("/files") || path.start_with?("/file_sets") body = parse_json(env.body) return unless body.is_a?(Hash) && body["error"] # Path-independent: the ACL invariants ride the shared metadata PATCH, # so the code is the only reliable signal. if env.status == 422 && PERMISSIONS_CODES.include?(body["error"]) raise AtlasRb::PermissionsError.new( body["message"] || "Atlas rejected the permissions write", code: body["error"], resource_id: body["resource_id"] ) end return unless reparent || linked || compilation || deriv_perms || create || upload if env.status == 403 # 403s on upload paths stay raw — acting-as/authz isn't an upload concern here. return unless reparent || linked || compilation || deriv_perms || create raise AtlasRb::ForbiddenError.new( body["message"] || "Atlas refused the request", code: body["error"], action: body["action"], subject: body["subject"] ) elsif reparent raise AtlasRb::ReparentError.new( body["message"] || "Atlas rejected the re-parent", code: body["error"], resource_id: body["resource_id"] ) elsif linked raise AtlasRb::LinkedMemberError.new( body["message"] || "Atlas rejected the linked-member write", code: body["error"], resource_id: body["resource_id"] ) elsif compilation raise AtlasRb::CompilationError.new( body["message"] || "Atlas rejected the compilation write", code: body["error"], resource_id: body["resource_id"] ) elsif deriv_perms raise AtlasRb::DerivativePermissionsError.new( body["message"] || "Atlas rejected the derivative-permissions policy", code: body["error"], resource_id: body["resource_id"] ) elsif FIXITY_CODES.include?(body["error"]) raise AtlasRb::FixityMismatchError.new( body["message"] || "Atlas rejected the upload (fixity)", code: body["error"], resource_id: body["resource_id"] ) end end |