systemprompt_cli/commands/admin/config/rate_limits/
diff.rs1use anyhow::{Context, Result, bail};
7use std::fs;
8use std::path::Path;
9use systemprompt_config::ProfileBootstrap;
10use systemprompt_logging::CliService;
11use systemprompt_models::profile::{RateLimitsConfig, TierMultipliers};
12
13use super::DiffArgs;
14use crate::CliConfig;
15use crate::cli_settings::OutputFormat;
16use crate::shared::{CommandOutput, render_result};
17
18use super::super::types::{DiffEntry, DiffOutput};
19
20pub(super) fn execute_diff(args: &DiffArgs, config: &CliConfig) -> Result<()> {
21 let profile = ProfileBootstrap::get()?;
22 let current = &profile.rate_limits;
23
24 let (compare_with, source) = if args.defaults {
25 (RateLimitsConfig::default(), "defaults".to_owned())
26 } else if let Some(file_path) = &args.file {
27 let content = fs::read_to_string(file_path)
28 .with_context(|| format!("Failed to read file: {}", file_path))?;
29
30 let is_json = Path::new(file_path)
31 .extension()
32 .is_some_and(|ext| ext.eq_ignore_ascii_case("json"));
33
34 let limits: RateLimitsConfig = if is_json {
35 serde_json::from_str(&content)
36 .with_context(|| format!("Failed to parse JSON from: {}", file_path))?
37 } else {
38 serde_yaml::from_str(&content)
39 .with_context(|| format!("Failed to parse YAML from: {}", file_path))?
40 };
41
42 (limits, file_path.clone())
43 } else {
44 bail!("Must specify --defaults or --file");
45 };
46
47 let differences = collect_differences(current, &compare_with);
48 let count = differences.len();
49
50 let output = DiffOutput {
51 source,
52 identical: differences.is_empty(),
53 differences,
54 };
55
56 render_result(
57 &CommandOutput::table_of(vec!["field", "current", "other"], &output.differences)
58 .with_title("Rate Limits Diff"),
59 config,
60 );
61
62 if config.output_format() == OutputFormat::Table {
63 if count == 0 {
64 CliService::success("No differences found");
65 } else {
66 CliService::info(&format!("{count} difference(s) found"));
67 }
68 }
69
70 Ok(())
71}
72
73pub fn collect_differences(
74 current: &RateLimitsConfig,
75 compare_with: &RateLimitsConfig,
76) -> Vec<DiffEntry> {
77 let mut differences: Vec<DiffEntry> = Vec::new();
78
79 add_diff_if_different(
80 &mut differences,
81 "disabled",
82 ¤t.disabled,
83 &compare_with.disabled,
84 );
85 collect_rate_differences(current, compare_with, &mut differences);
86 add_diff_if_different(
87 &mut differences,
88 "burst_multiplier",
89 ¤t.burst_multiplier,
90 &compare_with.burst_multiplier,
91 );
92 collect_tier_differences(
93 ¤t.tier_multipliers,
94 &compare_with.tier_multipliers,
95 &mut differences,
96 );
97
98 differences
99}
100
101fn collect_rate_differences(
102 current: &RateLimitsConfig,
103 compare_with: &RateLimitsConfig,
104 diffs: &mut Vec<DiffEntry>,
105) {
106 let rates = [
107 (
108 "oauth_public_per_second",
109 current.oauth_public_per_second,
110 compare_with.oauth_public_per_second,
111 ),
112 (
113 "oauth_auth_per_second",
114 current.oauth_auth_per_second,
115 compare_with.oauth_auth_per_second,
116 ),
117 (
118 "contexts_per_second",
119 current.contexts_per_second,
120 compare_with.contexts_per_second,
121 ),
122 (
123 "tasks_per_second",
124 current.tasks_per_second,
125 compare_with.tasks_per_second,
126 ),
127 (
128 "artifacts_per_second",
129 current.artifacts_per_second,
130 compare_with.artifacts_per_second,
131 ),
132 (
133 "agent_registry_per_second",
134 current.agent_registry_per_second,
135 compare_with.agent_registry_per_second,
136 ),
137 (
138 "agents_per_second",
139 current.agents_per_second,
140 compare_with.agents_per_second,
141 ),
142 (
143 "mcp_registry_per_second",
144 current.mcp_registry_per_second,
145 compare_with.mcp_registry_per_second,
146 ),
147 (
148 "mcp_per_second",
149 current.mcp_per_second,
150 compare_with.mcp_per_second,
151 ),
152 (
153 "stream_per_second",
154 current.stream_per_second,
155 compare_with.stream_per_second,
156 ),
157 (
158 "content_per_second",
159 current.content_per_second,
160 compare_with.content_per_second,
161 ),
162 ];
163
164 for (field, current_val, other_val) in rates {
165 add_diff_if_different(diffs, field, ¤t_val, &other_val);
166 }
167}
168
169fn collect_tier_differences(
170 current: &TierMultipliers,
171 compare_with: &TierMultipliers,
172 diffs: &mut Vec<DiffEntry>,
173) {
174 let tiers = [
175 ("tier_multipliers.admin", current.admin, compare_with.admin),
176 ("tier_multipliers.user", current.user, compare_with.user),
177 ("tier_multipliers.a2a", current.a2a, compare_with.a2a),
178 ("tier_multipliers.mcp", current.mcp, compare_with.mcp),
179 (
180 "tier_multipliers.service",
181 current.service,
182 compare_with.service,
183 ),
184 ("tier_multipliers.anon", current.anon, compare_with.anon),
185 ];
186
187 for (field, current_val, other_val) in tiers {
188 add_diff_if_different_f64(diffs, field, current_val, other_val);
189 }
190}
191
192fn add_diff_if_different<T: std::fmt::Display + PartialEq>(
193 diffs: &mut Vec<DiffEntry>,
194 field: &str,
195 current: &T,
196 compare: &T,
197) {
198 if current != compare {
199 diffs.push(DiffEntry {
200 field: field.to_owned(),
201 current: current.to_string(),
202 other: compare.to_string(),
203 });
204 }
205}
206
207fn add_diff_if_different_f64(diffs: &mut Vec<DiffEntry>, field: &str, current: f64, compare: f64) {
208 if (current - compare).abs() > f64::EPSILON {
209 diffs.push(DiffEntry {
210 field: field.to_owned(),
211 current: format!("{:.1}", current),
212 other: format!("{:.1}", compare),
213 });
214 }
215}