MishkaCmsCore.Runtime.ErrorPage (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Manages custom error page configurations in the MishkaCMS runtime system.

This resource handles the creation and management of error page templates that are dynamically compiled and rendered when HTTP errors occur. Each error page configuration can define multiple status code handlers (404, 500, etc.) with custom templates and layouts, allowing sites to provide branded and informative error experiences. Error pages are strictly site-specific, ensuring proper isolation in multi-tenant environments.

One more status sits beside the codes: :offline, the page the site shows its visitors while it is switched offline. MishkaCms.Plugs.MaintenanceGate answers it as a 503 at the address asked for, and draws its built-in page when the site has no such entry. See MishkaCmsCore.Runtime.ErrorStatus.

The resource supports priority-based path matching for flexible error handling strategies, helper functions for dynamic content generation, and optional precompilation for improved performance. It implements soft deletion through archival, allowing error configurations to be temporarily disabled and later restored. Database-level constraints ensure data integrity, including prevention of empty error page arrays and modifications to archived records.

Error page configurations automatically trigger background compilation jobs when modified, integrating with the runtime compiler to generate optimized error handling modules. The resource provides both standard CRUD operations and specialized API actions for external integrations, with comprehensive tenant isolation and permission-based access control.

The two style attributes in the codes column

Everything else the admin renders paints through classes so the policy in MishkaCmsCore.Runtime.Plugs.SecurityHeaders can close style-src-attr — a directive no nonce can reopen, because CSP has no mechanism for an attribute. The error-codes tooltip is the exception: it is a CSS-anchor-positioned popover, and anchor-name / position-anchor both carry the RECORD's id. A per-record name is the whole point — one shared name would make every row's tooltip resolve to the same anchor — so the value is unbounded, which is exactly what a Tailwind class cannot be: the scanner reads source text, and no @source inline can declare a uuid that does not exist yet. Closing these two means giving the popover its anchor through the HTML anchor attribute instead, which needs verifying in a browser rather than assuming.

Summary

Functions

Apply the full GuardedStruct pipeline (sanitize → validate → derive → main_validator) to attrs and return either {:ok, transformed_attrs} or {:error, errors}. Wire this into an Ash.Resource.Change to plug guardedstruct rules into Ash's changeset pipeline.

O(1) lookup of a field's compile-time metadata by name. Returns nil for unknown fields.

Compile-time-baked MapSet of every field name owned by the guardedstruct block. Used by GuardedStruct.AshResource.Change to decide, in O(1), whether a key in changeset.atomics belongs to the pipeline.

Format → badge colours and labels, in the shape the Gervaz :badge column type expects. Shared by the Format table column and the error-page detail modal so both draw the same badge from one definition.

Unguessable token for the shareable preview URL (generated on create).

Validates that the keys in the provided input are valid for at least one action on the resource.

Same as input/1, except restricts the keys to values accepted by the action provided.

Types

t()

@type t() :: %MishkaCmsCore.Runtime.ErrorPage{
  __lateral_join_source__: term(),
  __meta__: term(),
  __metadata__: term(),
  __order__: term(),
  active: term(),
  aggregates: term(),
  archived_at: term(),
  calculations: term(),
  configured_statuses: term(),
  error_module: term(),
  error_pages: term(),
  extra: term(),
  format: term(),
  helpers: term(),
  id: term(),
  inserted_at: term(),
  latest_version: term(),
  name: term(),
  paper_trail_versions: term(),
  path: term(),
  precompile: term(),
  preview_token: term(),
  priority: term(),
  recent_versions: term(),
  root_layout_template: term(),
  site: term(),
  site_id: term(),
  sorted_versions: term(),
  updated_at: term(),
  version_count: term()
}

Functions

__guarded_change__(attrs, error? \\ false)

Apply the full GuardedStruct pipeline (sanitize → validate → derive → main_validator) to attrs and return either {:ok, transformed_attrs} or {:error, errors}. Wire this into an Ash.Resource.Change to plug guardedstruct rules into Ash's changeset pipeline.

The function is named __guarded_change__ because it does more than validate — it can also transform values (trim, downcase, slugify, auto-fill, etc.) before they reach the data layer.

__guarded_field_meta__(name)

O(1) lookup of a field's compile-time metadata by name. Returns nil for unknown fields.

__guarded_field_name_set__()

Compile-time-baked MapSet of every field name owned by the guardedstruct block. Used by GuardedStruct.AshResource.Change to decide, in O(1), whether a key in changeset.atomics belongs to the pipeline.

default_short_name()

format_badge_config()

Format → badge colours and labels, in the shape the Gervaz :badge column type expects. Shared by the Format table column and the error-page detail modal so both draw the same badge from one definition.

generate_token()

Unguessable token for the shareable preview URL (generated on create).

input(opts)

@spec input(values :: map() | Keyword.t()) :: map() | no_return()

Validates that the keys in the provided input are valid for at least one action on the resource.

Raises a KeyError error at compile time if not. This exists because generally a struct should only ever be created by Ash as a result of a successful action. You should not be creating records manually in code, e.g %MyResource{value: 1, value: 2}. Generally that is fine, but often with embedded resources it is nice to be able to validate the keys that are being provided, e.g

Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()

input(opts, action)

@spec input(values :: map() | Keyword.t(), action :: atom()) :: map() | no_return()

Same as input/1, except restricts the keys to values accepted by the action provided.

json_api_match_route(_, _)

primary_key_matches?(left, right)