//! Types that represent the various API objects returned by Szurubooru. Many of the `Resource` //! objects have all of their fields as [Option] types because the Server API supports field //! selection. //! //! See [here](https://github.com/rr-/szurubooru/blob/master/doc/API.md#field-selecting) for //! more information. use crate::errors::SzurubooruClientError; use chrono::{DateTime, Utc}; use derive_builder::Builder; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use strum_macros::AsRefStr; #[cfg(feature = "python")] use pyo3::prelude::*; #[cfg(feature = "python")] use serde_pyobject::to_pyobject; #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] /// Enum used to represent something that's either `Left` or `Right` pub enum SzuruEither { /// Enum variant `Left` Left(L), /// Enum variant `Right` Right(R), } #[derive(Debug, Serialize, Deserialize)] /// A result of search operation that doesn't involve paging pub struct UnpagedSearchResult { /// The total list of results pub results: Vec, } impl WithBaseURL for UnpagedSearchResult { fn with_base_url(self, url: &str) -> Self { Self { results: self.results.with_base_url(url), } } } #[derive(Debug, Serialize, Deserialize)] /// A result of search operation that involves paging /// /// Use [offset](crate::SzurubooruRequest::with_offset) and [limit](crate::SzurubooruRequest::with_limit) /// to fetch the next page pub struct PagedSearchResult { /// The original query for the request pub query: String, /// The number of `T` to skip forward pub offset: u32, /// The maximum number of `T` to return pub limit: u32, /// The total number of `T` that match the [query](PagedSearchResult::query) pub total: u32, /// The results themselves pub results: Vec, } impl WithBaseURL for PagedSearchResult { fn with_base_url(self, url: &str) -> Self { Self { results: self.results.with_base_url(url), ..self } } } pub(crate) trait WithBaseURL { fn with_base_url(self, url: &str) -> Self; } impl WithBaseURL for Option { fn with_base_url(self, url: &str) -> Self { self.map(|inner| inner.with_base_url(url)) } } impl WithBaseURL for Vec { fn with_base_url(self, url: &str) -> Self { self.into_iter() .map(|inner| inner.with_base_url(url)) .collect() } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr( all(feature = "python"), pyclass(get_all, eq, module = "szurubooru_client.models") )] /// A [tag resource](TagResource) stripped down to `names`, `category` and `usages` fields. pub struct MicroTagResource { /// The tag names and aliases pub names: Vec, /// The category this tag belongs to pub category: String, /// The number of times this tag has been used pub usages: u32, } #[cfg(feature = "python")] #[cfg_attr(all(feature = "python"), pymethods)] #[doc(hidden)] impl MicroTagResource { /// Function that generates the representation string for this resource fn __repr__(&self) -> String { format!("{:?}", self) } } #[derive(Debug, Clone, Serialize, Deserialize)] /// To prevent problems with concurrent resource modification, Szurubooru implements optimistic /// locks using resource versions. Each modifiable resource has its version returned to the client /// with `GET` requests. At the same time, each `PUT` and `DELETE` request sent by the client /// must present the same version field to the server with value as it was given in `GET`. /// /// For example, given `GET /post/1`, the server responds like this: /// /// ```json /// { /// ..., /// "version": 2 /// } /// ``` /// /// This means the client must then send `{"version": 2}` back too. If the client fails to do so, /// the server will reject the request notifying about missing parameter. If someone has edited the /// post in the meantime, the server will reject the request as well, in which case the client is /// encouraged to notify the user about the situation. pub struct ResourceVersion { /// The version itself pub version: u32, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr( all(feature = "python"), pyclass(get_all, module = "szurubooru_client.models") )] /// A single tag. Tags are used to let users search for posts. pub struct TagResource { /// resource version. See [versioning](ResourceVersion) pub version: u32, /// a list of tag names (aliases). Tagging a post with any name will automatically assign /// the first name from this list. pub names: Option>, /// the name of the category the given tag belongs to pub category: Option, /// a list of implied tags, serialized as micro tag resource. Implied tags are automatically /// appended by the web client on usage. pub implications: Option>, /// a list of suggested tags, serialized as micro tag resource. Suggested tags are shown to /// the user by the web client on usage pub suggestions: Option>, /// time the tag was created pub creation_time: Option>, /// time the tag was edited pub last_edit_time: Option>, /// the number of posts the tag was used in pub usages: Option, /// the tag description (instructions how to use, history etc.) The client should render /// is as Markdown pub description: Option, } #[cfg(feature = "python")] #[cfg_attr(all(feature = "python"), pymethods)] #[doc(hidden)] impl TagResource { /// Generates a representative string of this resource fn __repr__(&self) -> String { format!("{:?}", self) } } /// Creates or updates a tag using specified parameters. Names, suggestions and implications must /// match `tag_name_regex` from server's configuration. Category must exist and is the same as name /// field within [TagCategoryResource] resource. Suggestions and implications are optional. If specified /// implied tags or suggested tags do not exist yet, they will be automatically created. Tags /// created automatically have no implications, no suggestions, one name and their category is set /// to the first tag category found. If there are no tag categories established yet, an error /// will be thrown. /// /// ```no_run /// use szurubooru_client::models::CreateUpdateTagBuilder; /// let cu_tag = CreateUpdateTagBuilder::default() /// .version(1) /// .names(vec!["foo_tag".to_string()]) /// .build() /// .expect("A new tag"); /// ``` #[derive(Debug, Clone, Serialize, Deserialize, Builder, Default)] //#[builder(pattern="owned")] #[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))] pub struct CreateUpdateTag { #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] /// resource version. See [versioning](ResourceVersion) pub version: Option, #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] /// Tag names and aliases, must match `tag_name_regex` from the server's configuration pub names: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] /// Category that this tag belongs to. Must already exist pub category: Option, #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] /// The tag description in Markdown format pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] /// Tags that should be implied when this tag is used pub implications: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] /// Tags that should be suggested when this tag is used pub suggestions: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[cfg_attr( all(feature = "python"), pyclass(get_all, module = "szurubooru_client.models") )] /// A single tag category. The primary purpose of tag categories is to distinguish certain tag /// types (such as characters, media type etc.), which improves user experience. pub struct TagCategoryResource { /// resource version. See [versioning](ResourceVersion) pub version: u32, /// The name of the tag category pub name: Option, /// The display color of the tag category pub color: Option, /// How many tags is the given category used with pub usages: Option, /// The order in which tags with this category are displayed, ascending pub order: Option, /// Whether the tag category is the default one pub default: Option, } #[cfg(feature = "python")] #[cfg_attr(all(feature = "python"), pymethods)] #[doc(hidden)] impl TagCategoryResource { /// Generates a representative string of this resource fn __repr__(&self) -> String { format!("{:?}", self) } } #[derive(Debug, Clone, Serialize, Deserialize, Default, Builder)] #[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))] /// Used for creating or updating a Tag Category pub struct CreateUpdateTagCategory { /// Resource version. See [versioning](ResourceVersion) #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] pub version: Option, /// The name of the category to create #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] pub name: Option, /// The display color to use for the category #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] pub color: Option, /// The order in which tags with this category are displayed, ascending #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] pub order: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Builder)] #[builder(setter(strip_option), build_fn(error = "SzurubooruClientError"))] #[serde(rename_all = "camelCase")] /// Removes source tag and merges all of its usages, suggestions and implications to the target tag. /// Other tag properties such as category and aliases do not get transferred and are discarded. pub struct MergeTags { /// Version of the tag to remove #[serde(rename = "removeVersion")] pub remove_tag_version: u32, /// The name of the tag to remove #[serde(rename = "remove")] pub remove_tag: String, /// The version of the tag to merge TO pub merge_to_version: u32, /// The name of the tag to merge TO #[serde(rename = "mergeTo")] pub merge_to_tag: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr( all(feature = "python"), pyclass(get_all, module = "szurubooru_client.models") )] /// Lists siblings of given tag, e.g. tags that were used in the same posts as the given tag pub struct TagSibling { /// The related tag pub tag: TagResource, /// How many times a given tag appears with the given tag pub occurrences: u32, } #[cfg(feature = "python")] #[cfg_attr(all(feature = "python"), pymethods)] #[doc(hidden)] impl TagSibling { /// Generates a representative string of this resource fn __repr__(&self) -> String { format!("{:?}", self) } } #[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, Eq, PartialEq)] #[cfg_attr( all(feature = "python"), pyclass(eq, eq_int, module = "szurubooru_client.models") )] #[serde(rename_all = "camelCase")] /// The type of post pub enum PostType { /// Image post Image, /// Animated post Animation, /// Alias of [Animation](PostType::Animation) Animated, /// Alias of [Animation](PostType::Animation) Anim, /// Flash animation Flash, /// Alias of [Flash](PostType::Flash) Swf, /// Video post of some type. See the mime type for more information Video, /// Webm container type Webm, } #[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, Eq, PartialEq)] #[cfg_attr( all(feature = "python"), pyclass(eq, eq_int, module = "szurubooru_client.models") )] #[serde(rename_all = "camelCase")] /// How SFW/NSFW the post is pub enum PostSafety { /// Post is SFW Safe, /// Post is possibly NSFW Sketchy, /// Alias of [Sketchy](PostSafety::Sketchy) Questionable, /// Post is NSFW Unsafe, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr( all(feature = "python"), pyclass(get_all, module = "szurubooru_client.models") )] #[serde(rename_all = "camelCase")] /// A post resource stripped down to `id` and `thumbnailUrl` fields. pub struct MicroPostResource { /// The ID of the post pub id: u32, /// The thumbnail URL of the post pub thumbnail_url: String, } #[cfg(feature = "python")] #[cfg_attr(all(feature = "python"), pymethods)] #[doc(hidden)] impl MicroPostResource { /// Generates a representative string of this resource fn __repr__(&self) -> String { format!("{:?}", self) } } impl WithBaseURL for MicroPostResource { fn with_base_url(self, url: &str) -> Self { if !self.thumbnail_url.contains(url) { MicroPostResource { id: self.id, thumbnail_url: format!("{}{}", url, self.thumbnail_url), } } else { self } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[doc(hidden)] pub(crate) struct PostId { pub id: u32, } #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[cfg_attr( all(feature = "python"), pyclass(get_all, module = "szurubooru_client.models") )] #[serde(rename_all = "camelCase")] /// A post resource pub struct PostResource { /// Resource version. See [versioning](ResourceVersion) pub version: Option, /// The post identifier pub id: Option, /// Time the post was created pub creation_time: Option>, /// Time the post was edited pub last_edit_time: Option>, /// Whether the post is safe for work pub safety: Option, #[serde(rename = "type")] /// The type of the post pub post_type: Option, /// Where the post was grabbed form, supplied by the user pub source: Option, /// The SHA1 file checksum. Used in snapshots to signify changes of the post content pub checksum: Option, #[serde(rename = "checksumMD5")] /// The MD5 file checksum pub checksum_md5: Option, /// The original width of the post content. pub canvas_width: Option, /// The original height of the post content. pub canvas_height: Option, /// Where the post content is located pub content_url: Option, /// Where the post thumbnail is located pub thumbnail_url: Option, /// Various flags such as whether the post is looped pub flags: Option>, /// List of tags the post is tagged with pub tags: Option>, /// A list of related posts. pub relations: Option>, /// A list of post annotations pub notes: Option>, /// Who created the post pub user: Option, /// The collective score (+1/-1 rating) of the given post pub score: Option, /// The user's score for this post pub own_score: Option, /// Whether the authenticated user has given post in their favorites pub own_favorite: Option, /// How many tags the post is tagged with pub tag_count: Option, /// How many users have the post in their favorites pub favorite_count: Option, /// How many comments are filed under that post pub comment_count: Option, /// How many notes the post has pub note_count: Option, /// How many times has the post been featured pub feature_count: Option, /// How many posts are related to this post pub relation_count: Option, /// The last time the post was featured pub last_feature_time: Option>, /// List of users who have favorited this post pub favorited_by: Option>, /// Whether the post uses custom thumbnail pub has_custom_thumbnail: Option, /// Subsidiary to [type](PostResource::post_type), used to tell exact content format; /// useful for `