pub trait Resource: Send + Sync {
Show 39 methods
// Required methods
fn resource_name(&self) -> &'static str;
fn base_path(&self) -> &'static str;
fn table_name(&self) -> &'static str;
fn clone_box(&self) -> Box<dyn Resource>;
// Provided methods
fn primary_key(&self) -> &'static str { ... }
fn menu_group(&self) -> Option<&'static str> { ... }
fn menu(&self) -> &'static str { ... }
fn allowed_roles(&self) -> Vec<String> { ... }
fn allowed_actions(&self) -> Option<Vec<MenuAction>> { ... }
fn custom_actions(&self) -> Vec<CustomAction> { ... }
fn permit_keys(&self) -> Vec<&'static str> { ... }
fn readonly_keys(&self) -> Vec<&'static str> { ... }
fn soft_delete(&self) -> bool { ... }
fn form_structure(&self) -> Option<Value> { ... }
fn filterable_fields(&self) -> Vec<FilterField> { ... }
fn file_fields(&self) -> Vec<FileField> { ... }
fn search_fields(&self) -> Vec<&'static str> { ... }
fn list<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn get<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn create<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn delete<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn form_fields(&self) -> Vec<Value> { ... }
fn list_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn new_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn edit_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn view_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn attach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait { ... }
fn serve_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait { ... }
fn detach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait { ... }
fn history_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn create_form<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn update_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn delete_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn run_action<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
name: &'life2 str,
id: String,
body: Value,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn export<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
format: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
fn authorize(&self, ctx: &ReqCtx, action: Action<'_>) -> bool { ... }
fn filter_writable(
&self,
body: Value,
) -> Result<Map<String, Value>, ApiResponse> { ... }
fn generate_menu(&self) -> Option<MenuItem> { ... }
}Required Methods§
fn resource_name(&self) -> &'static str
fn base_path(&self) -> &'static str
Sourcefn table_name(&self) -> &'static str
fn table_name(&self) -> &'static str
Backing table (SQL) or collection (Mongo) name.
fn clone_box(&self) -> Box<dyn Resource>
Provided Methods§
fn primary_key(&self) -> &'static str
fn allowed_roles(&self) -> Vec<String>
fn allowed_actions(&self) -> Option<Vec<MenuAction>>
Sourcefn custom_actions(&self) -> Vec<CustomAction>
fn custom_actions(&self) -> Vec<CustomAction>
Extra id-scoped operations beyond CRUD, exposed at
POST /{base}/{id}/action/{name} and as buttons on the detail page.
Sourcefn permit_keys(&self) -> Vec<&'static str>
fn permit_keys(&self) -> Vec<&'static str>
Mass-assignment allow-list for create/update.
Sourcefn readonly_keys(&self) -> Vec<&'static str>
fn readonly_keys(&self) -> Vec<&'static str>
Columns that must never be client-set.
Sourcefn soft_delete(&self) -> bool
fn soft_delete(&self) -> bool
Whether delete should soft-delete (set deleted = true).
Sourcefn form_structure(&self) -> Option<Value>
fn form_structure(&self) -> Option<Value>
Custom form layout for create/edit pages. Return None to derive fields
from permit_keys(). Shape: { "groups": [{ "fields": [...] }] }.
Sourcefn filterable_fields(&self) -> Vec<FilterField>
fn filterable_fields(&self) -> Vec<FilterField>
Columns exposed as filters on the list page. Empty (the default) means no
filter bar is shown. Build entries with FilterField::text/select/boolean.
Sourcefn file_fields(&self) -> Vec<FileField>
fn file_fields(&self) -> Vec<FileField>
File attachments this resource accepts. Empty (the default) means no
upload widgets are shown. Each field renders an <input type=file> on the
detail page, backed by the attach/serve/detach routes. Requires an
attachment backend (adminx-storage) to be registered; without one the
widgets are hidden.
Sourcefn search_fields(&self) -> Vec<&'static str>
fn search_fields(&self) -> Vec<&'static str>
Columns included in this resource’s full-text search document. Empty (the
default) means the resource is not searchable — no search box, no
indexing. When non-empty and a search backend (adminx-search) is
registered, create/update keep the index in sync, delete removes the
record, and the list page grows a ?q= search box.
fn list<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Sourcefn create<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn create<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Create. The body lives in crud::create so an
overriding impl can delegate to it and keep the audit recording; see that
module for why.
Sourcefn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Update. Body in crud::update.
Sourcefn delete<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn delete<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Delete. Body in crud::delete.
Sourcefn form_fields(&self) -> Vec<Value>
fn form_fields(&self) -> Vec<Value>
The form fields to render on create/edit. Derived from an explicit
form_structure() when present, otherwise from permit_keys().
fn list_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn new_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn edit_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn view_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Sourcefn attach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn attach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Store an uploaded file against this record under field. Called by the
web adapter after it parses the multipart body; redirects back to the
detail page on success. Gated on Update — attaching a file changes the
record.
Sourcefn serve_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn serve_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Stream a stored attachment back. Gated on Read — seeing the file is a
read of the record it belongs to. Returns the bytes with the stored
content type and a download-friendly filename.
Sourcefn detach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn detach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Remove one field’s attachment. Gated on Update; redirects back to the
detail page.
Sourcefn history_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn history_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
The recorded history of one record. Reads through the audit seam, so with no auditor registered it renders an empty log rather than 404 — the route exists either way and the page explains itself.
Sourcefn create_form<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn create_form<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Handle a submitted create form; redirects to the list on success.
Sourcefn update_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn update_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Handle a submitted edit form; redirects to the item view on success.
Sourcefn delete_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn delete_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Handle a delete from the list UI; redirects back to the list. csrf is
the submitted hidden field, checked against the cookie before anything
is removed.
Sourcefn run_action<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
name: &'life2 str,
id: String,
body: Value,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn run_action<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
name: &'life2 str,
id: String,
body: Value,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Look up a custom action by name and run it (after auth + CSRF checks).
csrf is the submitted hidden field; the action button posts a form, so
it’s guarded like the other mutating form handlers.
Sourcefn export<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
format: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn export<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
format: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Export the resource’s rows as json or csv (used by ?download=).
Whether the principal in ctx may perform action on this resource.
Delegates to the authorization seam: always allowed when auth is not
configured; a registered Authorizer decides
per action; otherwise the principal must hold one of allowed_roles().
Sourcefn filter_writable(
&self,
body: Value,
) -> Result<Map<String, Value>, ApiResponse>
fn filter_writable( &self, body: Value, ) -> Result<Map<String, Value>, ApiResponse>
Apply the permit/readonly/primary-key rules to an incoming JSON body, returning the writable column map or a ready-made error response.
Trait Implementations§
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".