feat(api): enum-typed visibility/data_type/kind + open-map fields in OpenAPI (#24 #29)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 20:14:30 +02:00
parent d3c33a6c5d
commit 5a72f85989
5 changed files with 39 additions and 11 deletions
-1
View File
@@ -32,7 +32,6 @@ pub(crate) struct UserView {
/// Desired visibility for a publish/unpublish request. /// Desired visibility for a publish/unpublish request.
#[derive(Deserialize, ToSchema)] #[derive(Deserialize, ToSchema)]
pub(crate) struct VisibilityRequest { pub(crate) struct VisibilityRequest {
#[schema(value_type = String)]
pub visibility: Visibility, pub visibility: Visibility,
} }
+1
View File
@@ -20,6 +20,7 @@ use crate::{
#[derive(Serialize, ToSchema)] #[derive(Serialize, ToSchema)]
pub(crate) struct AuthorityView { pub(crate) struct AuthorityView {
pub id: String, pub id: String,
#[schema(value_type = domain::AuthorityKind)]
pub kind: String, pub kind: String,
pub external_uri: Option<String>, pub external_uri: Option<String>,
pub labels: Vec<LabelView>, pub labels: Vec<LabelView>,
+4 -2
View File
@@ -40,9 +40,10 @@ pub(crate) struct AdminObjectView {
/// `YYYY-MM-DD` or null. /// `YYYY-MM-DD` or null.
pub recording_date: Option<String>, pub recording_date: Option<String>,
/// "draft" | "internal" | "public". /// "draft" | "internal" | "public".
#[schema(value_type = domain::Visibility)]
pub visibility: String, pub visibility: String,
/// Flexible field values (key -> value). /// Flexible field values (key -> value).
#[schema(value_type = Object)] #[schema(value_type = std::collections::HashMap<String, serde_json::Value>)]
pub fields: serde_json::Value, pub fields: serde_json::Value,
} }
@@ -162,7 +163,6 @@ pub(crate) struct ObjectCreateRequest {
pub recorder: Option<String>, pub recorder: Option<String>,
pub recording_date: Option<String>, pub recording_date: Option<String>,
/// "draft" | "internal" (public is rejected — publish via the visibility endpoint). /// "draft" | "internal" (public is rejected — publish via the visibility endpoint).
#[schema(value_type = String)]
pub visibility: Visibility, pub visibility: Visibility,
} }
@@ -360,8 +360,10 @@ pub(crate) async fn delete_object(
pub(crate) struct FieldDefinitionView { pub(crate) struct FieldDefinitionView {
pub key: String, pub key: String,
/// "text" | "localized_text" | "integer" | "date" | "boolean" | "term" | "authority". /// "text" | "localized_text" | "integer" | "date" | "boolean" | "term" | "authority".
#[schema(value_type = domain::DataType)]
pub data_type: String, pub data_type: String,
pub vocabulary_id: Option<String>, pub vocabulary_id: Option<String>,
#[schema(value_type = Option<domain::AuthorityKind>)]
pub authority_kind: Option<String>, pub authority_kind: Option<String>,
pub required: bool, pub required: bool,
pub group: Option<String>, pub group: Option<String>,
+4 -1
View File
@@ -59,7 +59,10 @@ use crate::{
admin_search::SearchHitView, admin_search::SearchHitView,
admin_search::SearchResultsView, admin_search::SearchResultsView,
admin_authorities::AuthorityView, admin_authorities::AuthorityView,
admin_authorities::NewAuthorityRequest admin_authorities::NewAuthorityRequest,
domain::Visibility,
domain::AuthorityKind,
domain::DataType
)), )),
info(title = "Collection Management System", version = "0.0.0") info(title = "Collection Management System", version = "0.0.0")
)] )]
+30 -7
View File
@@ -326,7 +326,9 @@ export interface components {
current_location?: string | null; current_location?: string | null;
current_owner?: string | null; current_owner?: string | null;
/** @description Flexible field values (key -> value). */ /** @description Flexible field values (key -> value). */
fields: Record<string, never>; fields: {
[key: string]: unknown;
};
id: string; id: string;
/** Format: int32 */ /** Format: int32 */
number_of_objects: number; number_of_objects: number;
@@ -336,12 +338,21 @@ export interface components {
/** @description `YYYY-MM-DD` or null. */ /** @description `YYYY-MM-DD` or null. */
recording_date?: string | null; recording_date?: string | null;
/** @description "draft" | "internal" | "public". */ /** @description "draft" | "internal" | "public". */
visibility: string; visibility: components["schemas"]["Visibility"];
}; };
/**
* @description The kind of authority record.
*
* NOTE: kept in sync by hand with the
* `CHECK (kind IN ('person', 'organisation', 'place'))` constraint in
* `crates/db/migrations/0002_vocabularies_authorities.sql` — add a variant in both places.
* @enum {string}
*/
AuthorityKind: "person" | "organisation" | "place";
AuthorityView: { AuthorityView: {
external_uri?: string | null; external_uri?: string | null;
id: string; id: string;
kind: string; kind: components["schemas"]["AuthorityKind"];
labels: components["schemas"]["LabelView"][]; labels: components["schemas"]["LabelView"][];
}; };
CreatedField: { CreatedField: {
@@ -354,11 +365,18 @@ export interface components {
CreatedObject: { CreatedObject: {
id: string; id: string;
}; };
/**
* @description The stored `data_type` discriminant of a field definition — mirrors the strings from
* [`FieldType::kind_str`]. Exists so the OpenAPI schema can describe `data_type` as a
* closed string enum (consumed by the typed web client). Keep in sync with `kind_str`.
* @enum {string}
*/
DataType: "text" | "localized_text" | "integer" | "date" | "boolean" | "term" | "authority";
/** @description Field-definition descriptor for the UI to render forms. */ /** @description Field-definition descriptor for the UI to render forms. */
FieldDefinitionView: { FieldDefinitionView: {
authority_kind?: string | null; authority_kind?: null | components["schemas"]["AuthorityKind"];
/** @description "text" | "localized_text" | "integer" | "date" | "boolean" | "term" | "authority". */ /** @description "text" | "localized_text" | "integer" | "date" | "boolean" | "term" | "authority". */
data_type: string; data_type: components["schemas"]["DataType"];
group?: string | null; group?: string | null;
key: string; key: string;
labels: components["schemas"]["LabelView"][]; labels: components["schemas"]["LabelView"][];
@@ -419,7 +437,7 @@ export interface components {
recorder?: string | null; recorder?: string | null;
recording_date?: string | null; recording_date?: string | null;
/** @description "draft" | "internal" (public is rejected — publish via the visibility endpoint). */ /** @description "draft" | "internal" (public is rejected — publish via the visibility endpoint). */
visibility: string; visibility: components["schemas"]["Visibility"];
}; };
/** /**
* @description Inventory-minimum fields for update. Visibility is intentionally absent — it changes * @description Inventory-minimum fields for update. Visibility is intentionally absent — it changes
@@ -488,9 +506,14 @@ export interface components {
id: string; id: string;
role: string; role: string;
}; };
/**
* @description Publication state of a catalogue record.
* @enum {string}
*/
Visibility: "draft" | "internal" | "public";
/** @description Desired visibility for a publish/unpublish request. */ /** @description Desired visibility for a publish/unpublish request. */
VisibilityRequest: { VisibilityRequest: {
visibility: string; visibility: components["schemas"]["Visibility"];
}; };
VocabularyView: { VocabularyView: {
id: string; id: string;