use crate::Client;
use crate::error::Result;
use crate::resource::Resource;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateTagRequest {
pub name: String,
pub slug: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_types: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateTagRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slug: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_types: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateCustomFieldChoiceSetRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_choices: Option<String>,
pub extra_choices: Vec<Vec<Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_alphabetically: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateCustomFieldChoiceSetRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_choices: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra_choices: Option<Vec<Vec<Value>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_alphabetically: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateCustomFieldRequest {
pub object_types: Vec<String>,
pub r#type: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_object_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unique: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_logic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ui_visible: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ui_editable: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_cloneable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_object_filter: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_minimum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_maximum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_regex: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub choice_set: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateCustomFieldRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub object_types: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_object_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unique: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_logic: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ui_visible: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ui_editable: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_cloneable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_object_filter: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_minimum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_maximum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_regex: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub choice_set: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateConfigContextRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub regions: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub site_groups: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sites: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locations: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_types: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub platforms: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cluster_types: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cluster_groups: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clusters: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_groups: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenants: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateConfigContextRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub regions: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub site_groups: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sites: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locations: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_types: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roles: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub platforms: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cluster_types: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cluster_groups: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clusters: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant_groups: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenants: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateConfigContextProfileRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateConfigContextProfileRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateConfigTemplateRequest {
pub name: String,
pub template_code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment_params: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_extension: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub as_attachment: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateConfigTemplateRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment_params: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_extension: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub as_attachment: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWebhookRequest {
pub name: String,
pub payload_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_content_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_headers: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secret: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_verification: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ca_file_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_fields: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateWebhookRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_content_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_headers: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secret: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_verification: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ca_file_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_fields: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
pub type Bookmark = crate::models::Bookmark;
pub type ConfigContextProfile = crate::models::ConfigContextProfile;
pub type ConfigContext = crate::models::ConfigContext;
pub type ConfigTemplate = crate::models::ConfigTemplate;
pub type CustomFieldChoiceSet = crate::models::CustomFieldChoiceSet;
pub type CustomField = crate::models::CustomField;
pub type CustomLink = crate::models::CustomLink;
pub type Dashboard = crate::models::Dashboard;
pub type DashboardRequest = crate::models::DashboardRequest;
pub type PatchedDashboardRequest = crate::models::PatchedDashboardRequest;
pub type EventRule = crate::models::EventRule;
pub type ExportTemplate = crate::models::ExportTemplate;
pub type ImageAttachment = crate::models::ImageAttachment;
pub type JournalEntry = crate::models::JournalEntry;
pub type NotificationGroup = crate::models::NotificationGroup;
pub type Notification = crate::models::Notification;
pub type ObjectType = crate::models::ObjectType;
pub type SavedFilter = crate::models::SavedFilter;
pub type Script = crate::models::Script;
pub type Subscription = crate::models::Subscription;
pub type TableConfig = crate::models::TableConfig;
pub type TaggedItem = crate::models::TaggedItem;
pub type Tag = crate::models::Tag;
pub type Webhook = crate::models::Webhook;
pub type BookmarksApi = Resource<crate::models::Bookmark>;
pub type ConfigContextProfilesApi = Resource<crate::models::ConfigContextProfile>;
pub type ConfigContextsApi = Resource<crate::models::ConfigContext>;
pub type ConfigTemplatesApi = Resource<crate::models::ConfigTemplate>;
pub type CustomFieldChoiceSetsApi = Resource<crate::models::CustomFieldChoiceSet>;
pub type CustomFieldsApi = Resource<crate::models::CustomField>;
pub type CustomLinksApi = Resource<crate::models::CustomLink>;
pub type EventRulesApi = Resource<crate::models::EventRule>;
pub type ExportTemplatesApi = Resource<crate::models::ExportTemplate>;
pub type ImageAttachmentsApi = Resource<crate::models::ImageAttachment>;
pub type JournalEntriesApi = Resource<crate::models::JournalEntry>;
pub type NotificationGroupsApi = Resource<crate::models::NotificationGroup>;
pub type NotificationsApi = Resource<crate::models::Notification>;
pub type ObjectTypesApi = Resource<crate::models::ObjectType>;
pub type SavedFiltersApi = Resource<crate::models::SavedFilter>;
pub type ScriptsApi = Resource<crate::models::Script>;
pub type SubscriptionsApi = Resource<crate::models::Subscription>;
pub type TableConfigsApi = Resource<crate::models::TableConfig>;
pub type TaggedObjectsApi = Resource<crate::models::TaggedItem>;
pub type TagsApi = Resource<crate::models::Tag>;
pub type WebhooksApi = Resource<crate::models::Webhook>;
#[derive(Clone)]
pub struct ExtrasApi {
client: Client,
}
impl ExtrasApi {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
pub fn bookmarks(&self) -> BookmarksApi {
Resource::new(self.client.clone(), "extras/bookmarks/")
}
pub fn config_context_profiles(&self) -> ConfigContextProfilesApi {
Resource::new(self.client.clone(), "extras/config-context-profiles/")
}
pub fn config_contexts(&self) -> ConfigContextsApi {
Resource::new(self.client.clone(), "extras/config-contexts/")
}
pub fn config_templates(&self) -> ConfigTemplatesApi {
Resource::new(self.client.clone(), "extras/config-templates/")
}
pub fn custom_field_choice_sets(&self) -> CustomFieldChoiceSetsApi {
Resource::new(self.client.clone(), "extras/custom-field-choice-sets/")
}
pub fn custom_fields(&self) -> CustomFieldsApi {
Resource::new(self.client.clone(), "extras/custom-fields/")
}
pub fn custom_links(&self) -> CustomLinksApi {
Resource::new(self.client.clone(), "extras/custom-links/")
}
pub async fn dashboard(&self) -> Result<Dashboard> {
self.client.get("extras/dashboard/").await
}
pub async fn update_dashboard(&self, body: &DashboardRequest) -> Result<Dashboard> {
self.client.put("extras/dashboard/", body).await
}
pub async fn patch_dashboard(&self, body: &PatchedDashboardRequest) -> Result<Dashboard> {
self.client.patch("extras/dashboard/", body).await
}
pub async fn delete_dashboard(&self) -> Result<()> {
self.client.delete("extras/dashboard/").await
}
pub fn event_rules(&self) -> EventRulesApi {
Resource::new(self.client.clone(), "extras/event-rules/")
}
pub fn export_templates(&self) -> ExportTemplatesApi {
Resource::new(self.client.clone(), "extras/export-templates/")
}
pub fn image_attachments(&self) -> ImageAttachmentsApi {
Resource::new(self.client.clone(), "extras/image-attachments/")
}
pub fn journal_entries(&self) -> JournalEntriesApi {
Resource::new(self.client.clone(), "extras/journal-entries/")
}
pub fn notification_groups(&self) -> NotificationGroupsApi {
Resource::new(self.client.clone(), "extras/notification-groups/")
}
pub fn notifications(&self) -> NotificationsApi {
Resource::new(self.client.clone(), "extras/notifications/")
}
pub fn object_types(&self) -> ObjectTypesApi {
Resource::new(self.client.clone(), "extras/object-types/")
}
pub fn saved_filters(&self) -> SavedFiltersApi {
Resource::new(self.client.clone(), "extras/saved-filters/")
}
pub fn scripts(&self) -> ScriptsApi {
Resource::new(self.client.clone(), "extras/scripts/")
}
pub fn subscriptions(&self) -> SubscriptionsApi {
Resource::new(self.client.clone(), "extras/subscriptions/")
}
pub fn table_configs(&self) -> TableConfigsApi {
Resource::new(self.client.clone(), "extras/table-configs/")
}
pub fn tagged_objects(&self) -> TaggedObjectsApi {
Resource::new(self.client.clone(), "extras/tagged-objects/")
}
pub fn tags(&self) -> TagsApi {
Resource::new(self.client.clone(), "extras/tags/")
}
pub fn webhooks(&self) -> WebhooksApi {
Resource::new(self.client.clone(), "extras/webhooks/")
}
pub async fn sync_config_context(&self, id: u64) -> Result<ConfigContext> {
self.client
.post(&format!("extras/config-contexts/{}/sync/", id), &())
.await
}
pub async fn sync_config_context_profile(&self, id: u64) -> Result<ConfigContextProfile> {
self.client
.post(&format!("extras/config-context-profiles/{}/sync/", id), &())
.await
}
pub async fn sync_config_template(&self, id: u64) -> Result<ConfigTemplate> {
self.client
.post(&format!("extras/config-templates/{}/sync/", id), &())
.await
}
pub async fn render_config_template(&self, id: u64) -> Result<String> {
self.client
.post(&format!("extras/config-templates/{}/render/", id), &())
.await
}
pub async fn sync_export_template(&self, id: u64) -> Result<ExportTemplate> {
self.client
.post(&format!("extras/export-templates/{}/sync/", id), &())
.await
}
pub async fn custom_field_choices(&self, id: u64) -> Result<CustomFieldChoiceSet> {
self.client
.get(&format!("extras/custom-field-choice-sets/{}/choices/", id))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ClientConfig;
use httpmock::prelude::*;
use serde_json::json;
fn test_client() -> Client {
let config = ClientConfig::new("https://netbox.example.com", "token");
Client::new(config).unwrap()
}
fn mock_client(server: &MockServer) -> Client {
let config = ClientConfig::new(server.base_url(), "test-token");
Client::new(config).unwrap()
}
fn assert_path<T>(resource: Resource<T>, expected: &str)
where
T: serde::de::DeserializeOwned,
{
let paginator = resource.paginate(None).unwrap();
assert_eq!(paginator.next_url(), Some(expected));
}
fn assert_missing(value: &serde_json::Value, key: &str) {
assert!(value.get(key).is_none(), "expected {} to be omitted", key);
}
#[test]
fn extras_accessors_return_expected_paths() {
let api = ExtrasApi::new(test_client());
assert_path(api.bookmarks(), "extras/bookmarks/");
assert_path(
api.config_context_profiles(),
"extras/config-context-profiles/",
);
assert_path(api.config_contexts(), "extras/config-contexts/");
assert_path(api.config_templates(), "extras/config-templates/");
assert_path(
api.custom_field_choice_sets(),
"extras/custom-field-choice-sets/",
);
assert_path(api.custom_fields(), "extras/custom-fields/");
assert_path(api.custom_links(), "extras/custom-links/");
assert_path(api.event_rules(), "extras/event-rules/");
assert_path(api.export_templates(), "extras/export-templates/");
assert_path(api.image_attachments(), "extras/image-attachments/");
assert_path(api.journal_entries(), "extras/journal-entries/");
assert_path(api.notification_groups(), "extras/notification-groups/");
assert_path(api.notifications(), "extras/notifications/");
assert_path(api.object_types(), "extras/object-types/");
assert_path(api.saved_filters(), "extras/saved-filters/");
assert_path(api.scripts(), "extras/scripts/");
assert_path(api.subscriptions(), "extras/subscriptions/");
assert_path(api.table_configs(), "extras/table-configs/");
assert_path(api.tagged_objects(), "extras/tagged-objects/");
assert_path(api.tags(), "extras/tags/");
assert_path(api.webhooks(), "extras/webhooks/");
}
#[test]
fn serialize_tag_requests() {
let create = CreateTagRequest {
name: "critical".to_string(),
slug: "critical".to_string(),
color: Some("ff0000".to_string()),
description: None,
weight: None,
object_types: Some(vec!["dcim.device".to_string()]),
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "critical");
assert_eq!(value["slug"], "critical");
assert_eq!(value["color"], "ff0000");
assert_eq!(value["object_types"], json!(["dcim.device"]));
assert_missing(&value, "description");
let update = UpdateTagRequest {
name: None,
slug: Some("critical-updated".to_string()),
color: None,
description: Some("Updated".to_string()),
weight: Some(10),
object_types: None,
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["slug"], "critical-updated");
assert_eq!(value["description"], "Updated");
assert_eq!(value["weight"], 10);
assert_missing(&value, "name");
}
#[test]
fn serialize_custom_field_choice_set_requests() {
let create = CreateCustomFieldChoiceSetRequest {
name: "regions".to_string(),
description: None,
base_choices: Some("ISO_3166".to_string()),
extra_choices: vec![vec![json!("NA"), json!("North America")]],
order_alphabetically: Some(true),
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "regions");
assert_eq!(value["base_choices"], "ISO_3166");
assert_eq!(value["order_alphabetically"], true);
assert_missing(&value, "description");
let update = UpdateCustomFieldChoiceSetRequest {
name: None,
description: Some("Updated".to_string()),
base_choices: None,
extra_choices: None,
order_alphabetically: None,
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["description"], "Updated");
assert_missing(&value, "name");
}
#[test]
fn serialize_custom_field_requests() {
let create = CreateCustomFieldRequest {
object_types: vec!["dcim.device".to_string()],
r#type: "text".to_string(),
name: "asset_code".to_string(),
related_object_type: None,
label: Some("Asset Code".to_string()),
group_name: None,
description: None,
required: Some(true),
unique: None,
search_weight: None,
filter_logic: None,
ui_visible: None,
ui_editable: None,
is_cloneable: None,
default: None,
related_object_filter: None,
weight: None,
validation_minimum: None,
validation_maximum: None,
validation_regex: None,
choice_set: Some(3),
comments: None,
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["object_types"], json!(["dcim.device"]));
assert_eq!(value["type"], "text");
assert_eq!(value["name"], "asset_code");
assert_eq!(value["label"], "Asset Code");
assert_eq!(value["required"], true);
assert_eq!(value["choice_set"], 3);
assert_missing(&value, "group_name");
let update = UpdateCustomFieldRequest {
object_types: None,
r#type: None,
name: None,
related_object_type: None,
label: None,
group_name: Some("Ops".to_string()),
description: Some("Updated".to_string()),
required: None,
unique: None,
search_weight: None,
filter_logic: Some("exact".to_string()),
ui_visible: None,
ui_editable: None,
is_cloneable: None,
default: None,
related_object_filter: None,
weight: None,
validation_minimum: None,
validation_maximum: None,
validation_regex: None,
choice_set: None,
comments: None,
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["group_name"], "Ops");
assert_eq!(value["description"], "Updated");
assert_eq!(value["filter_logic"], "exact");
assert_missing(&value, "name");
}
#[test]
fn serialize_config_context_requests() {
let create = CreateConfigContextRequest {
name: "base".to_string(),
weight: None,
profile: None,
description: None,
is_active: Some(true),
regions: Some(vec![1, 2]),
site_groups: None,
sites: None,
locations: None,
device_types: None,
roles: None,
platforms: None,
cluster_types: None,
cluster_groups: None,
clusters: None,
tenant_groups: None,
tenants: None,
tags: Some(vec!["codex-smoke".to_string()]),
data_source: Some(5),
data: Some(json!({"env": "prod"})),
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "base");
assert_eq!(value["is_active"], true);
assert_eq!(value["regions"], json!([1, 2]));
assert_eq!(value["tags"], json!(["codex-smoke"]));
assert_eq!(value["data_source"], 5);
assert_eq!(value["data"]["env"], "prod");
assert_missing(&value, "profile");
let update = UpdateConfigContextRequest {
name: None,
weight: Some(100),
profile: Some(2),
description: Some("Updated".to_string()),
is_active: None,
regions: None,
site_groups: None,
sites: None,
locations: None,
device_types: None,
roles: None,
platforms: None,
cluster_types: None,
cluster_groups: None,
clusters: None,
tenant_groups: None,
tenants: None,
tags: None,
data_source: None,
data: None,
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["weight"], 100);
assert_eq!(value["profile"], 2);
assert_eq!(value["description"], "Updated");
assert_missing(&value, "name");
}
#[test]
fn serialize_config_context_profile_requests() {
let create = CreateConfigContextProfileRequest {
name: "defaults".to_string(),
description: None,
schema: Some(json!({"type": "object"})),
tags: Some(vec!["codex-smoke".to_string(), "ops".to_string()]),
comments: None,
data_source: None,
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "defaults");
assert_eq!(value["schema"]["type"], "object");
assert_eq!(value["tags"], json!(["codex-smoke", "ops"]));
assert_missing(&value, "description");
let update = UpdateConfigContextProfileRequest {
name: None,
description: Some("Updated".to_string()),
schema: None,
tags: None,
comments: None,
data_source: Some(7),
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["description"], "Updated");
assert_eq!(value["data_source"], 7);
assert_missing(&value, "name");
}
#[test]
fn serialize_config_template_requests() {
let create = CreateConfigTemplateRequest {
name: "basic".to_string(),
template_code: "hostname {{ device.name }}".to_string(),
description: None,
environment_params: Some(json!({"trim_blocks": true})),
mime_type: None,
file_name: None,
file_extension: None,
as_attachment: Some(true),
data_source: Some(4),
tags: None,
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "basic");
assert_eq!(value["template_code"], "hostname {{ device.name }}");
assert_eq!(value["environment_params"]["trim_blocks"], true);
assert_eq!(value["as_attachment"], true);
assert_eq!(value["data_source"], 4);
assert_missing(&value, "mime_type");
let update = UpdateConfigTemplateRequest {
name: None,
template_code: None,
description: Some("Updated".to_string()),
environment_params: None,
mime_type: None,
file_name: None,
file_extension: Some("cfg".to_string()),
as_attachment: None,
data_source: None,
tags: Some(vec![3]),
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["description"], "Updated");
assert_eq!(value["file_extension"], "cfg");
assert_eq!(value["tags"], json!([3]));
assert_missing(&value, "name");
}
#[test]
fn serialize_webhook_requests() {
let mut custom_fields = HashMap::new();
custom_fields.insert("service".to_string(), json!("billing"));
let create = CreateWebhookRequest {
name: "audit".to_string(),
payload_url: "https://hooks.example.com/audit".to_string(),
description: None,
http_method: Some("POST".to_string()),
http_content_type: Some("application/json".to_string()),
additional_headers: None,
body_template: None,
secret: None,
ssl_verification: Some(true),
ca_file_path: None,
custom_fields: Some(custom_fields),
tags: Some(vec![1]),
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "audit");
assert_eq!(value["payload_url"], "https://hooks.example.com/audit");
assert_eq!(value["http_method"], "POST");
assert_eq!(value["http_content_type"], "application/json");
assert_eq!(value["ssl_verification"], true);
assert_eq!(value["custom_fields"]["service"], "billing");
assert_eq!(value["tags"], json!([1]));
let update = UpdateWebhookRequest {
name: None,
payload_url: None,
description: Some("Updated".to_string()),
http_method: None,
http_content_type: None,
additional_headers: None,
body_template: None,
secret: None,
ssl_verification: Some(false),
ca_file_path: Some("/etc/ssl/ca.pem".to_string()),
custom_fields: None,
tags: None,
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["description"], "Updated");
assert_eq!(value["ssl_verification"], false);
assert_eq!(value["ca_file_path"], "/etc/ssl/ca.pem");
assert_missing(&value, "name");
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn sync_and_render_actions_use_expected_paths() {
let server = MockServer::start();
let client = mock_client(&server);
let mock1 = server.mock(|when, then| {
when.method(POST)
.path("/api/extras/config-contexts/5/sync/");
then.status(200).json_body(json!({}));
});
let _ = client.extras().sync_config_context(5).await;
mock1.assert();
let mock2 = server.mock(|when, then| {
when.method(POST)
.path("/api/extras/config-context-profiles/3/sync/");
then.status(200).json_body(json!({}));
});
let _ = client.extras().sync_config_context_profile(3).await;
mock2.assert();
let mock3 = server.mock(|when, then| {
when.method(POST)
.path("/api/extras/config-templates/2/sync/");
then.status(200).json_body(json!({}));
});
let _ = client.extras().sync_config_template(2).await;
mock3.assert();
let mock4 = server.mock(|when, then| {
when.method(POST)
.path("/api/extras/config-templates/2/render/");
then.status(200).json_body(json!("hostname switch-01"));
});
let result = client.extras().render_config_template(2).await;
assert!(result.is_ok());
mock4.assert();
let mock5 = server.mock(|when, then| {
when.method(POST)
.path("/api/extras/export-templates/4/sync/");
then.status(200).json_body(json!({}));
});
let _ = client.extras().sync_export_template(4).await;
mock5.assert();
let mock6 = server.mock(|when, then| {
when.method(GET)
.path("/api/extras/custom-field-choice-sets/1/choices/");
then.status(200).json_body(json!({}));
});
let _ = client.extras().custom_field_choices(1).await;
mock6.assert();
}
}