gproxy_protocol/adapt/video/reverse/
state.rs1use super::*;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum ReverseVideoKind {
6 Native,
7 OpenRouter,
8}
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ReverseVideoBinding {
13 pub operation_name: String,
14 pub origin: String,
15 pub model: String,
16 pub kind: ReverseVideoKind,
17 pub api_origin_url: String,
18 pub create_path: String,
19 pub query_prefix: String,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum ReverseVideoRequest {
24 Native(o::NativeCreateVideoRequestBody),
25 OpenRouter(o::CreateVideoRequestBody),
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub enum ReverseVideoResult {
30 Native(o::NativeVideo),
31 OpenRouter(o::VideoGenerationResponseBody),
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct ReverseVideoChild {
36 pub instance_index: usize,
37 pub sample_index: usize,
38 pub source: g::PredictLongRunningRequestBody,
39 pub request: Option<ReverseVideoRequest>,
40 pub started: bool,
42 pub result: Option<ReverseVideoResult>,
43 pub published: Option<g::VideoOperation>,
44}
45
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct ReverseVideoState {
48 pub schema: u32,
49 pub native_defaults: Option<video::NativeVideoDefaults>,
50 pub expires_at: SystemTime,
51 pub binding: ReverseVideoBinding,
52 pub original: g::PredictLongRunningRequestBody,
53 pub children: Vec<ReverseVideoChild>,
54}
55
56#[derive(Debug, Default)]
57pub struct ReverseVideoProgress {
58 pub state: Option<ReverseVideoState>,
59 pub version: Option<Version>,
60 pub child_index: Option<usize>,
61 pub send_started: bool,
62 pub raw_response: Option<WireResponse<bytes::Bytes>>,
63 pub result: Option<ReverseVideoResult>,
64 pub publication_ids: Vec<String>,
65 pub publications: Vec<ResourceReference>,
66}
67
68pub(super) fn conflict(path: &str) -> TransformError {
69 TransformError::new(
70 TransformErrorKind::Conflict,
71 path,
72 "saved creation requires reconciliation; do not repeat POST",
73 )
74}
75
76impl ReverseVideoBinding {
77 pub(super) fn key(&self) -> String {
78 format!(
79 "video/reverse/{}:{}",
80 self.operation_name.len(),
81 self.operation_name
82 )
83 }
84
85 pub(super) fn query_path(&self, id: &str) -> Result<String, TransformError> {
86 if id.is_empty() || id.chars().any(char::is_control) {
87 return Err(TransformError::shape(
88 "video.id",
89 "nonempty control-free target identity required",
90 ));
91 }
92 let mut encoded = String::new();
93 for byte in id.bytes() {
94 if byte.is_ascii_alphanumeric()
95 || matches!(byte, b'-' | b'_' | b'~')
96 || byte == b'.' && !matches!(id, "." | "..")
97 {
98 encoded.push(char::from(byte));
99 } else {
100 use std::fmt::Write;
101 write!(&mut encoded, "%{byte:02X}").expect("String write");
102 }
103 }
104 Ok(format!("{}{encoded}", self.query_prefix))
105 }
106}
107
108impl ReverseVideoResult {
109 pub(super) fn id(&self) -> &str {
110 match self {
111 Self::Native(v) => &v.id,
112 Self::OpenRouter(v) => &v.id,
113 }
114 }
115 pub(super) fn same_projection(&self, other: &Self) -> bool {
116 match (self, other) {
117 (Self::Native(a), Self::Native(b)) => {
118 a.id == b.id && a.status == b.status && a.error == b.error
119 }
120 (Self::OpenRouter(a), Self::OpenRouter(b)) => {
121 a.id == b.id
122 && a.status == b.status
123 && a.error == b.error
124 && a.unsigned_urls == b.unsigned_urls
125 }
126 _ => false,
127 }
128 }
129 pub(super) fn clean(self) -> Self {
130 match self {
131 Self::Native(v) => Self::Native(v.into_declared()),
132 Self::OpenRouter(v) => Self::OpenRouter(v.into_declared()),
133 }
134 }
135}
136
137pub(super) async fn save<S: StateStore>(
138 store: &S,
139 scope: &S::Scope,
140 state: &ReverseVideoState,
141 progress: &mut ReverseVideoProgress,
142 limits: VideoLimits,
143) -> Result<(), TransformError> {
144 progress.state = Some(state.clone());
146 let version = super::super::state::save_payload(
147 store,
148 scope,
149 &state.binding.key(),
150 state,
151 progress.version.clone(),
152 super::super::state::state_expiry(Some(state.expires_at)),
153 limits,
154 )
155 .await?;
156 progress.version = Some(version);
157 Ok(())
158}
159
160pub(super) async fn load<S: StateStore>(
161 store: &S,
162 scope: &S::Scope,
163 binding: &ReverseVideoBinding,
164 limits: VideoLimits,
165) -> Result<(ReverseVideoState, Version), TransformError> {
166 let entry = store
167 .get(scope, &binding.key())
168 .await?
169 .ok_or_else(|| TransformError::missing_metadata("video.reverse.state"))?;
170 if entry.payload.len() as u64 > store.limits().read_bytes {
171 return Err(super::super::resources::limit("video.state"));
172 }
173 let mut state: ReverseVideoState = crate::codec::decode_json(&entry.payload, limits.codec)
174 .map_err(|e| TransformError::invalid_result("video.state", e.to_string()))?;
175 if state.schema != 1 || &state.binding != binding || state.children.is_empty() {
176 return Err(TransformError::shape(
177 "video.state.binding",
178 "invalid schema, binding or child count",
179 ));
180 }
181 if state.expires_at <= SystemTime::now() {
182 return Err(TransformError::missing_metadata(
183 "video.state.unexpired_publications",
184 ));
185 }
186 state.original = state.original.into_declared();
187 for child in &mut state.children {
188 child.source = std::mem::replace(
189 &mut child.source,
190 g::PredictLongRunningRequestBody::builder(vec![]).build(),
191 )
192 .into_declared();
193 child.request = child.request.take().map(|v| match v {
194 ReverseVideoRequest::Native(v) => ReverseVideoRequest::Native(v.into_declared()),
195 ReverseVideoRequest::OpenRouter(v) => {
196 ReverseVideoRequest::OpenRouter(v.into_declared())
197 }
198 });
199 child.result = child.result.take().map(ReverseVideoResult::clean);
200 child.published = child.published.take().map(DeclaredFields::into_declared);
201 }
202 Ok((state, entry.version))
203}