gproxy_transform/transform/models/list/
query.rs1use crate::protocol::{claude, gemini};
9use crate::transform::{TransformContext, TransformPair};
10
11pub fn request_query(pair: TransformPair, ctx: &TransformContext) -> Option<String> {
16 use TransformPair as P;
17 if ctx.source.operation != crate::protocol::Operation::ListModels {
18 return None;
19 }
20 match pair {
21 P::ClaudeToGeminiModels => {
22 write_gemini(super::claude_to_gemini::request(parse_claude(ctx), ctx))
23 }
24 P::GeminiToClaudeModels => {
25 write_claude(super::gemini_to_claude::request(parse_gemini(ctx), ctx))
26 }
27 _ => None,
28 }
29}
30
31fn pairs(ctx: &TransformContext) -> impl Iterator<Item = (&str, &str)> {
32 ctx.query
33 .as_deref()
34 .unwrap_or_default()
35 .split('&')
36 .filter_map(|pair| {
37 let (k, v) = pair.split_once('=').unwrap_or((pair, ""));
38 (!k.is_empty() && !v.is_empty()).then_some((k, v))
39 })
40}
41
42fn parse_claude(ctx: &TransformContext) -> claude::ListModelsQuery {
43 let mut q = claude::ListModelsQuery {
44 after_id: None,
45 before_id: None,
46 limit: None,
47 extra: Default::default(),
48 };
49 for (k, v) in pairs(ctx) {
50 match k {
51 "after_id" => q.after_id = Some(v.to_owned()),
52 "before_id" => q.before_id = Some(v.to_owned()),
53 "limit" => q.limit = v.parse().ok(),
54 _ => {}
55 }
56 }
57 q
58}
59
60fn parse_gemini(ctx: &TransformContext) -> gemini::ListModelsRequest {
61 let mut q = gemini::ListModelsRequest::default();
62 for (k, v) in pairs(ctx) {
63 match k {
64 "pageSize" => q.page_size = v.parse().ok(),
65 "pageToken" => q.page_token = Some(v.to_owned()),
66 _ => {}
67 }
68 }
69 q
70}
71
72fn write_claude(q: claude::ListModelsQuery) -> Option<String> {
73 let mut out = Vec::new();
74 if let Some(limit) = q.limit {
75 out.push(format!("limit={limit}"));
76 }
77 if let Some(after_id) = q.after_id {
78 out.push(format!("after_id={after_id}"));
79 }
80 if let Some(before_id) = q.before_id {
81 out.push(format!("before_id={before_id}"));
82 }
83 (!out.is_empty()).then(|| out.join("&"))
84}
85
86fn write_gemini(q: gemini::ListModelsRequest) -> Option<String> {
87 let mut out = Vec::new();
88 if let Some(page_size) = q.page_size {
89 out.push(format!("pageSize={page_size}"));
90 }
91 if let Some(page_token) = q.page_token {
92 out.push(format!("pageToken={page_token}"));
93 }
94 (!out.is_empty()).then(|| out.join("&"))
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::protocol::{Operation, OperationKey, Provider};
101
102 fn ctx(source: Provider, target: Provider, query: &str) -> TransformContext {
103 TransformContext::new(
104 OperationKey::provider(Operation::ListModels, source),
105 OperationKey::provider(Operation::ListModels, target),
106 )
107 .with_request("/v1/models", Some(query))
108 }
109
110 #[test]
111 fn claude_and_gemini_pagination_round_trips() {
112 let out = request_query(
113 TransformPair::ClaudeToGeminiModels,
114 &ctx(
115 Provider::Claude,
116 Provider::Gemini,
117 "limit=25&after_id=m1&x=1",
118 ),
119 );
120 assert_eq!(out.as_deref(), Some("pageSize=25&pageToken=m1"));
121
122 let out = request_query(
123 TransformPair::GeminiToClaudeModels,
124 &ctx(
125 Provider::Gemini,
126 Provider::Claude,
127 "pageSize=25&pageToken=tok",
128 ),
129 );
130 assert_eq!(out.as_deref(), Some("limit=25&after_id=tok"));
131
132 let out = request_query(
134 TransformPair::ClaudeToOpenAiModels,
135 &ctx(Provider::Claude, Provider::OpenAi, "limit=25"),
136 );
137 assert_eq!(out, None);
138 }
139}