Skip to main content

resourcespace_client/api/
metadata.rs

1use serde::{Serialize, Serializer};
2use serde_with::json::JsonString;
3use serde_with::{serde_as, skip_serializing_none};
4
5use crate::client::Client;
6use crate::error::RsError;
7
8use super::List;
9
10#[derive(Debug)]
11pub struct MetadataApi<'a> {
12    client: &'a Client,
13}
14
15/// Sub-API for metadata endpoints.
16impl<'a> MetadataApi<'a> {
17    pub(crate) fn new(client: &'a Client) -> Self {
18        Self { client }
19    }
20
21    /// For a given field, return all the available tags (nodes) or selectable options.
22    ///
23    /// ## Arguments
24    /// * `request` - Parameters built via [`GetFieldOptionsRequest`]
25    ///
26    /// ## TODO: Returns
27    ///
28    /// ## TODO: Errors
29    ///
30    /// ## TODO: Examples
31    pub async fn get_field_options(
32        &self,
33        request: GetFieldOptionsRequest,
34    ) -> Result<serde_json::Value, RsError> {
35        self.client
36            .send_request("get_field_options", reqwest::Method::GET, request)
37            .await
38    }
39
40    /// Find a node ID (entry in a fixed tag field) given the name of the node.
41    ///
42    /// ## Arguments
43    /// * `request` - Parameters built via [`GetNodeIdRequest`]
44    ///
45    /// ## TODO: Returns
46    ///
47    /// ## TODO: Errors
48    ///
49    /// ## TODO: Examples
50    pub async fn get_node_id(
51        &self,
52        request: GetNodeIdRequest,
53    ) -> Result<serde_json::Value, RsError> {
54        self.client
55            .send_request("get_node_id", reqwest::Method::GET, request)
56            .await
57    }
58
59    /// Get all nodes (fixed keywords) from database for a specific metadata field or parent.
60    ///
61    /// ## Arguments
62    /// * `request` - Parameters built via [`GetNodesRequest`]
63    ///
64    /// ## TODO: Returns
65    ///
66    /// ## TODO: Errors
67    ///
68    /// ## TODO: Examples
69    pub async fn get_nodes(&self, request: GetNodesRequest) -> Result<serde_json::Value, RsError> {
70        self.client
71            .send_request("get_nodes", reqwest::Method::GET, request)
72            .await
73    }
74
75    /// Add all node IDs (field options) in the list to a resource.
76    ///
77    /// ## Arguments
78    /// * `request` - Parameters built via [`AddResourceNodesRequest`]
79    ///
80    /// ## TODO: Returns
81    ///
82    /// ## TODO: Errors
83    ///
84    /// ## TODO: Examples
85    pub async fn add_resource_nodes(
86        &self,
87        request: AddResourceNodesRequest,
88    ) -> Result<serde_json::Value, RsError> {
89        self.client
90            .send_request("add_resource_nodes", reqwest::Method::POST, request)
91            .await
92    }
93
94    /// Add all node IDs (field options) in the list to the resources specified.
95    ///
96    /// ## Arguments
97    /// * `request` - Parameters built via [`AddResourceNodesMultiRequest`]
98    ///
99    /// ## TODO: Returns
100    ///
101    /// ## TODO: Errors
102    ///
103    /// ## TODO: Examples
104    pub async fn add_resource_nodes_multi(
105        &self,
106        request: AddResourceNodesMultiRequest,
107    ) -> Result<serde_json::Value, RsError> {
108        self.client
109            .send_request("add_resource_nodes_multi", reqwest::Method::POST, request)
110            .await
111    }
112
113    /// Create a new node (option for a fixed list field).
114    ///
115    /// ## Arguments
116    /// * `request` - Parameters built via [`SetNodeRequest`]
117    ///
118    /// ## TODO: Returns
119    ///
120    /// ## TODO: Errors
121    ///
122    /// ## TODO: Examples
123    pub async fn set_node(&self, request: SetNodeRequest) -> Result<serde_json::Value, RsError> {
124        self.client
125            .send_request("set_node", reqwest::Method::POST, request)
126            .await
127    }
128
129    /// Get metadata field information for all (matching) fields.
130    ///
131    /// Available from RS version 10.3+ and requires permission `a`.
132    ///
133    /// ## Arguments
134    /// * `request` - Parameters built via [`GetResourceTypeFieldsRequest`]
135    ///
136    /// ## TODO: Returns
137    ///
138    /// ## TODO: Errors
139    ///
140    /// ## TODO: Examples
141    pub async fn get_resource_type_fields(
142        &self,
143        request: GetResourceTypeFieldsRequest,
144    ) -> Result<serde_json::Value, RsError> {
145        self.client
146            .send_request("get_resource_type_fields", reqwest::Method::GET, request)
147            .await
148    }
149
150    /// Create a metadata field.
151    ///
152    /// Available from RS version 10.3+ and requires permission `a`.
153    ///
154    /// ## Arguments
155    /// * `request` - Parameters built via [`CreateResourceTypeFieldRequest`]
156    ///
157    /// ## TODO: Returns
158    ///
159    /// ## TODO: Errors
160    ///
161    /// ## TODO: Examples
162    pub async fn create_resource_type_field(
163        &self,
164        request: CreateResourceTypeFieldRequest,
165    ) -> Result<serde_json::Value, RsError> {
166        self.client
167            .send_request("create_resource_type_field", reqwest::Method::POST, request)
168            .await
169    }
170
171    /// Toggle nodes' active state.
172    ///
173    /// Available from RS version 10.4+ and requires permission `k`.
174    ///
175    /// ## Arguments
176    /// * `request` - Parameters built via [`ToggleActiveStatesForNodesRequest`]
177    ///
178    /// ## TODO: Returns
179    ///
180    /// ## TODO: Errors
181    ///
182    /// ## TODO: Examples
183    pub async fn toggle_active_state_for_nodes(
184        &self,
185        request: ToggleActiveStatesForNodesRequest,
186    ) -> Result<serde_json::Value, RsError> {
187        self.client
188            .send_request(
189                "toggle_active_state_for_nodes",
190                reqwest::Method::POST,
191                request,
192            )
193            .await
194    }
195
196    /// Set the value of a metadata field.
197    ///
198    /// ## Arguments
199    /// * `request` - Parameters built via [`UpdateFieldRequest`]
200    ///
201    /// ## TODO: Returns
202    ///
203    /// ## TODO: Errors
204    ///
205    /// ## TODO: Examples
206    pub async fn update_field(
207        &self,
208        request: UpdateFieldRequest,
209    ) -> Result<serde_json::Value, RsError> {
210        self.client
211            .send_request("update_field", reqwest::Method::POST, request)
212            .await
213    }
214}
215
216/// A metadata field identifier, either a numeric ID or a shortname.
217///
218/// Accepts a `u32` field ID or a string shortname via [`Into`] conversions,
219/// making it ergonomic to reference fields at call sites:
220///
221/// ```no_run
222/// FieldIdentifier::from(72)       // numeric ID
223/// FieldIdentifier::from("title")  // shortname
224/// ```
225#[derive(Clone, Debug, PartialEq)]
226pub enum FieldIdentifier {
227    Id(u32),
228    Shortname(String),
229}
230
231impl From<u32> for FieldIdentifier {
232    fn from(id: u32) -> Self {
233        Self::Id(id)
234    }
235}
236
237impl From<String> for FieldIdentifier {
238    fn from(name: String) -> Self {
239        Self::Shortname(name)
240    }
241}
242
243impl From<&str> for FieldIdentifier {
244    fn from(name: &str) -> Self {
245        Self::Shortname(name.to_string())
246    }
247}
248
249impl Serialize for FieldIdentifier {
250    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
251        match self {
252            Self::Id(id) => id.serialize(serializer),
253            Self::Shortname(name) => name.serialize(serializer),
254        }
255    }
256}
257
258#[skip_serializing_none]
259#[derive(Clone, Debug, PartialEq, Serialize)]
260pub struct GetFieldOptionsRequest {
261    /// The ID or shortname of the metadata field to retrieve options for.
262    #[serde(rename = "ref")]
263    pub r#ref: FieldIdentifier,
264    /// If set, returns additional node information alongside each option.
265    pub nodeinfo: Option<bool>,
266}
267
268impl GetFieldOptionsRequest {
269    pub fn new(r#ref: impl Into<FieldIdentifier>) -> Self {
270        Self {
271            r#ref: r#ref.into(),
272            nodeinfo: None,
273        }
274    }
275
276    pub fn nodeinfo(mut self, nodeinfo: bool) -> Self {
277        self.nodeinfo = Some(nodeinfo);
278        self
279    }
280}
281
282#[derive(Clone, Debug, PartialEq, Serialize)]
283pub struct GetNodeIdRequest {
284    /// The name of the node to look up.
285    pub value: String,
286    /// The ID of the resource type field the node belongs to.
287    pub resource_type_field: u32,
288}
289
290impl GetNodeIdRequest {
291    pub fn new(value: impl Into<String>, resource_type_field: u32) -> Self {
292        Self {
293            value: value.into(),
294            resource_type_field,
295        }
296    }
297}
298
299#[skip_serializing_none]
300#[derive(Clone, Debug, PartialEq, Serialize)]
301pub struct GetNodesRequest {
302    /// The ID of the metadata field to retrieve nodes from.
303    #[serde(rename = "ref")]
304    pub r#ref: u32,
305    /// Restrict results to children of this parent node ID.
306    pub parent: Option<u32>,
307    /// If set, retrieves all descendant nodes recursively.
308    pub recursive: Option<bool>,
309    /// Number of nodes to skip, used for pagination.
310    pub offset: Option<u32>,
311    /// Maximum number of nodes to return.
312    pub rows: Option<u32>,
313    /// Filter nodes by name (partial match).
314    pub name: Option<String>,
315    /// If set, includes the number of resources using each node.
316    pub use_count: Option<bool>,
317    /// If set, orders results by the translated node name.
318    pub order_by_translated_name: Option<bool>,
319}
320
321impl GetNodesRequest {
322    pub fn new(r#ref: u32) -> Self {
323        Self {
324            r#ref,
325            parent: None,
326            recursive: None,
327            offset: None,
328            rows: None,
329            name: None,
330            use_count: None,
331            order_by_translated_name: None,
332        }
333    }
334
335    pub fn parent(mut self, parent: u32) -> Self {
336        self.parent = Some(parent);
337        self
338    }
339
340    pub fn recursive(mut self, recursive: bool) -> Self {
341        self.recursive = Some(recursive);
342        self
343    }
344
345    pub fn offset(mut self, offset: u32) -> Self {
346        self.offset = Some(offset);
347        self
348    }
349
350    pub fn rows(mut self, rows: u32) -> Self {
351        self.rows = Some(rows);
352        self
353    }
354
355    pub fn name(mut self, name: impl Into<String>) -> Self {
356        self.name = Some(name.into());
357        self
358    }
359
360    pub fn use_count(mut self, use_count: bool) -> Self {
361        self.use_count = Some(use_count);
362        self
363    }
364
365    pub fn order_by_translated_name(mut self, order_by_translated_name: bool) -> Self {
366        self.order_by_translated_name = Some(order_by_translated_name);
367        self
368    }
369}
370
371#[derive(Clone, Debug, PartialEq, Serialize)]
372pub struct AddResourceNodesRequest {
373    /// The ID of the resource to add nodes to.
374    pub resource: u32,
375    /// Comma-separated list of node IDs to add to the resource.
376    pub nodestring: List<u32>,
377}
378
379impl AddResourceNodesRequest {
380    pub fn new(resource: u32, nodestring: impl Into<List<u32>>) -> Self {
381        Self {
382            resource,
383            nodestring: nodestring.into(),
384        }
385    }
386}
387
388#[derive(Clone, Debug, PartialEq, Serialize)]
389pub struct AddResourceNodesMultiRequest {
390    /// Comma-separated list of resource IDs to add nodes to.
391    pub resourceid: List<u32>,
392    /// Comma-separated list of node IDs to add to each resource.
393    pub nodes: List<u32>,
394}
395
396impl AddResourceNodesMultiRequest {
397    pub fn new(resourceid: impl Into<List<u32>>, nodes: impl Into<List<u32>>) -> Self {
398        Self {
399            resourceid: resourceid.into(),
400            nodes: nodes.into(),
401        }
402    }
403}
404
405#[skip_serializing_none]
406#[derive(Clone, Debug, PartialEq, Serialize)]
407pub struct SetNodeRequest {
408    /// The ID of an existing node to update, or 0 to create a new one.
409    #[serde(rename = "ref")]
410    pub r#ref: u32,
411    /// The ID of the resource type field this node belongs to.
412    pub resource_type_field: u32,
413    /// The name of the node.
414    pub name: String,
415    /// The ID of the parent node, if this is a child node.
416    pub parent: Option<String>,
417    /// Position used to order this node relative to siblings.
418    pub order_by: Option<u32>,
419    /// If set, returns the existing node instead of creating a duplicate.
420    pub returnexisting: Option<bool>,
421}
422
423impl SetNodeRequest {
424    pub fn new(r#ref: u32, resource_type_field: u32, name: impl Into<String>) -> Self {
425        Self {
426            r#ref,
427            resource_type_field,
428            name: name.into(),
429            parent: None,
430            order_by: None,
431            returnexisting: None,
432        }
433    }
434    pub fn parent(mut self, parent: impl Into<String>) -> Self {
435        self.parent = Some(parent.into());
436        self
437    }
438
439    pub fn order_by(mut self, order_by: u32) -> Self {
440        self.order_by = Some(order_by);
441        self
442    }
443
444    pub fn returnexisting(mut self, returnexisting: bool) -> Self {
445        self.returnexisting = Some(returnexisting);
446        self
447    }
448}
449
450#[skip_serializing_none]
451#[derive(Clone, Debug, Default, PartialEq, Serialize)]
452pub struct GetResourceTypeFieldsRequest {
453    /// Comma-separated list of resource type IDs to filter fields by.
454    pub by_resource_types: Option<List<u32>>,
455    /// Search string to filter fields by name.
456    pub find: Option<String>,
457    /// Comma-separated list of field type IDs to filter by.
458    pub by_types: Option<List<u32>>,
459}
460
461impl GetResourceTypeFieldsRequest {
462    pub fn new() -> Self {
463        Self::default()
464    }
465
466    pub fn by_resource_types(mut self, by_resource_types: impl Into<List<u32>>) -> Self {
467        self.by_resource_types = Some(by_resource_types.into());
468        self
469    }
470
471    pub fn find(mut self, find: impl Into<String>) -> Self {
472        self.find = Some(find.into());
473        self
474    }
475
476    pub fn by_types(mut self, by_types: impl Into<List<u32>>) -> Self {
477        self.by_types = Some(by_types.into());
478        self
479    }
480}
481
482#[derive(Clone, Debug, PartialEq, Serialize)]
483pub struct CreateResourceTypeFieldRequest {
484    /// The name of the new metadata field.
485    pub name: String,
486    /// Comma-separated list of resource type IDs this field should apply to.
487    pub resource_types: List<u32>,
488    /// The field type, for values see the FIELD_TYPE_* constants.
489    pub r#type: String,
490}
491
492impl CreateResourceTypeFieldRequest {
493    pub fn new(
494        name: impl Into<String>,
495        resource_types: impl Into<List<u32>>,
496        r#type: impl Into<String>,
497    ) -> Self {
498        Self {
499            name: name.into(),
500            resource_types: resource_types.into(),
501            r#type: r#type.into(),
502        }
503    }
504}
505
506#[serde_as]
507#[derive(Clone, Debug, PartialEq, Serialize)]
508pub struct ToggleActiveStatesForNodesRequest {
509    /// JSON-encoded array of node IDs whose active states should be toggled.
510    #[serde_as(as = "JsonString")]
511    pub refs: Vec<u32>,
512}
513
514impl ToggleActiveStatesForNodesRequest {
515    pub fn new(refs: impl Into<List<u32>>) -> Self {
516        Self {
517            refs: refs.into().0,
518        }
519    }
520}
521
522#[derive(Clone, Debug, PartialEq, Serialize)]
523pub struct UpdateFieldRequest {
524    /// The ID of the resource to update.
525    pub resource: u32,
526    /// The ID or shortname of the metadata field to set a value on.
527    pub field: FieldIdentifier,
528    /// The new value to assign to the field.
529    /// This can be a comma separated list for fixed list option fields.
530    #[serde(flatten)]
531    pub value: FieldValue,
532}
533
534impl UpdateFieldRequest {
535    pub fn new(
536        resource: u32,
537        field: impl Into<FieldIdentifier>,
538        value: impl Into<FieldValue>,
539    ) -> Self {
540        Self {
541            resource,
542            field: field.into(),
543            value: value.into(),
544        }
545    }
546}
547
548/// The value to set for a metadata field.
549///
550/// Accepts plain text or a list of node IDs via named constructors:
551///
552/// ```no_run
553/// FieldValue::from("hello")          // plain text
554/// FieldValue::from("red")            // single option
555/// FieldValue::from(42u32)            // single node ID
556/// FieldValue::from([1u32, 2, 3])     // multiple node IDs
557/// ```
558///
559/// When constructed from node IDs, the `nodevalues` parameter is
560/// automatically set to `true`.
561#[derive(Clone, Debug, PartialEq)]
562pub enum FieldValue {
563    /// A plain text value e.g. "hello"
564    Text(String),
565    /// A list of node IDs, sets nodevalues = true automatically
566    Nodes(List<u32>),
567}
568
569impl From<&str> for FieldValue {
570    fn from(val: &str) -> Self {
571        Self::Text(val.to_string())
572    }
573}
574
575impl From<String> for FieldValue {
576    fn from(val: String) -> Self {
577        Self::Text(val)
578    }
579}
580
581impl From<u32> for FieldValue {
582    fn from(val: u32) -> Self {
583        Self::Nodes(List::from(val))
584    }
585}
586
587impl From<Vec<u32>> for FieldValue {
588    fn from(val: Vec<u32>) -> Self {
589        Self::Nodes(List::from(val))
590    }
591}
592
593impl<const N: usize> From<[u32; N]> for FieldValue {
594    fn from(val: [u32; N]) -> Self {
595        Self::Nodes(List::from(val))
596    }
597}
598
599impl Serialize for FieldValue {
600    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
601        use serde::ser::SerializeMap;
602
603        let mut map = serializer.serialize_map(None)?;
604        match self {
605            Self::Text(text) => {
606                map.serialize_entry("value", text)?;
607            }
608            Self::Nodes(nodes) => {
609                map.serialize_entry("value", nodes)?;
610                map.serialize_entry("nodevalues", &true)?;
611            }
612        }
613        map.end()
614    }
615}