Skip to main content

hanzo_client/apis/
world_api.rs

1/*
2 * Hanzo Cloud API
3 *
4 * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator's admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
5 *
6 * The version of the OpenAPI document: v1
7 * 
8 * Generated by: https://openapi-generator.tech
9 */
10
11
12use reqwest;
13use serde::{Deserialize, Serialize, de::Error as _};
14use crate::{apis::ResponseContent, models};
15use super::{Error, configuration, ContentType};
16
17
18/// struct for typed errors of method [`get_world`]
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged)]
21pub enum GetWorldError {
22    UnknownValue(serde_json::Value),
23}
24
25/// struct for typed errors of method [`get_world_limits`]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum GetWorldLimitsError {
29    UnknownValue(serde_json::Value),
30}
31
32/// struct for typed errors of method [`get_world_news`]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum GetWorldNewsError {
36    UnknownValue(serde_json::Value),
37}
38
39/// struct for typed errors of method [`get_world_pipeline`]
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum GetWorldPipelineError {
43    UnknownValue(serde_json::Value),
44}
45
46/// struct for typed errors of method [`get_world_stream`]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(untagged)]
49pub enum GetWorldStreamError {
50    UnknownValue(serde_json::Value),
51}
52
53/// struct for typed errors of method [`put_world_pipeline`]
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum PutWorldPipelineError {
57    UnknownValue(serde_json::Value),
58}
59
60
61/// Answers GET /v1/world — the product's public endpoint, naming every wire this surface answers on.  It exists because two of those wires are INVISIBLE to the generated document. /v1/world/mcp and /v1/world/zap are carved off the cloud catch-all by the ingress and answered by world-gw, so the cloud router never serves them — and openapi.Describe renders prose only for a route the router actually serves, which is the very property that keeps the document from being able to claim an operation nothing answers. Both addresses are real and public, so without this op the only way to learn they exist is to read the ingress config. This is where that fact lives, in the product's own surface.  Public on purpose: discovery precedes credentials. It reports addresses and protocols only — never feed data, and never the caller's plan, which GET /v1/world/limits owns — so there is nothing here to leak.
62pub async fn get_world(configuration: &configuration::Configuration, ) -> Result<models::WorldIndex, Error<GetWorldError>> {
63
64    let uri_str = format!("{}/v1/world", configuration.base_path);
65    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
66
67    if let Some(ref user_agent) = configuration.user_agent {
68        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
69    }
70    if let Some(ref token) = configuration.bearer_access_token {
71        req_builder = req_builder.bearer_auth(token.to_owned());
72    };
73
74    let req = req_builder.build()?;
75    let resp = configuration.client.execute(req).await?;
76
77    let status = resp.status();
78    let content_type = resp
79        .headers()
80        .get("content-type")
81        .and_then(|v| v.to_str().ok())
82        .unwrap_or("application/octet-stream");
83    let content_type = super::ContentType::from(content_type);
84
85    if !status.is_client_error() && !status.is_server_error() {
86        let content = resp.text().await?;
87        match content_type {
88            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
89            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::WorldIndex`"))),
90            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::WorldIndex`")))),
91        }
92    } else {
93        let content = resp.text().await?;
94        let entity: Option<GetWorldError> = serde_json::from_str(&content).ok();
95        Err(Error::ResponseError(ResponseContent { status, content, entity }))
96    }
97}
98
99/// Echoes a World plan's rate limits, alert quota and model-API grant, read straight from the live @hanzo/plans catalog, so agents and dashboards configure themselves against the catalog instead of hardcoding tier numbers.  An empty or unknown plan resolves world-free, and a catalog failure serves that same free floor rather than erroring — so this always answers 200, and it can only ever under-grant. It reports the contract; it does not enforce it.
100pub async fn get_world_limits(configuration: &configuration::Configuration, plan: Option<&str>) -> Result<models::LimitsView, Error<GetWorldLimitsError>> {
101    // add a prefix to parameters to efficiently prevent name collisions
102    let p_plan = plan;
103
104    let uri_str = format!("{}/v1/world/limits", configuration.base_path);
105    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
106
107    if let Some(ref param_value) = p_plan {
108        req_builder = req_builder.query(&[("plan", &param_value.to_string())]);
109    }
110    if let Some(ref user_agent) = configuration.user_agent {
111        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
112    }
113    if let Some(ref token) = configuration.bearer_access_token {
114        req_builder = req_builder.bearer_auth(token.to_owned());
115    };
116
117    let req = req_builder.build()?;
118    let resp = configuration.client.execute(req).await?;
119
120    let status = resp.status();
121    let content_type = resp
122        .headers()
123        .get("content-type")
124        .and_then(|v| v.to_str().ok())
125        .unwrap_or("application/octet-stream");
126    let content_type = super::ContentType::from(content_type);
127
128    if !status.is_client_error() && !status.is_server_error() {
129        let content = resp.text().await?;
130        match content_type {
131            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
132            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::LimitsView`"))),
133            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::LimitsView`")))),
134        }
135    } else {
136        let content = resp.text().await?;
137        let entity: Option<GetWorldLimitsError> = serde_json::from_str(&content).ok();
138        Err(Error::ResponseError(ResponseContent { status, content, entity }))
139    }
140}
141
142/// Returns the caller's merged world-news feed: every source their project's pipeline names — GDELT once per keyword, plus each allowlisted RSS or Atom feed — fetched concurrently, narrowed by the pipeline's keyword/region/source filters, deduplicated by link and sorted freshest first, capped at 50 items.  A project with no stored pipeline gets a sensible default set of world feeds rather than an empty answer. A source that fails or times out is SKIPPED: the feed degrades to honest partial results and never 5xxs because one outlet was down. Reading also publishes the result to the /v1/world/stream subscribers of the same (org, project), so a dashboard's own refresh updates every open tab.
143pub async fn get_world_news(configuration: &configuration::Configuration, ) -> Result<models::NewsResponse, Error<GetWorldNewsError>> {
144
145    let uri_str = format!("{}/v1/world/news", configuration.base_path);
146    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
147
148    if let Some(ref user_agent) = configuration.user_agent {
149        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
150    }
151    if let Some(ref token) = configuration.bearer_access_token {
152        req_builder = req_builder.bearer_auth(token.to_owned());
153    };
154
155    let req = req_builder.build()?;
156    let resp = configuration.client.execute(req).await?;
157
158    let status = resp.status();
159    let content_type = resp
160        .headers()
161        .get("content-type")
162        .and_then(|v| v.to_str().ok())
163        .unwrap_or("application/octet-stream");
164    let content_type = super::ContentType::from(content_type);
165
166    if !status.is_client_error() && !status.is_server_error() {
167        let content = resp.text().await?;
168        match content_type {
169            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
170            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::NewsResponse`"))),
171            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::NewsResponse`")))),
172        }
173    } else {
174        let content = resp.text().await?;
175        let entity: Option<GetWorldNewsError> = serde_json::from_str(&content).ok();
176        Err(Error::ResponseError(ResponseContent { status, content, entity }))
177    }
178}
179
180/// Returns the caller project's news pipeline: which feeds it reads and how the merged result is filtered. A project that has never written one is answered with the built-in world feeds and `default: true`, so a fresh project sees the same feed /v1/world/news would actually serve rather than an empty configuration.
181pub async fn get_world_pipeline(configuration: &configuration::Configuration, ) -> Result<models::PipelineView, Error<GetWorldPipelineError>> {
182
183    let uri_str = format!("{}/v1/world/pipeline", configuration.base_path);
184    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
185
186    if let Some(ref user_agent) = configuration.user_agent {
187        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
188    }
189    if let Some(ref token) = configuration.bearer_access_token {
190        req_builder = req_builder.bearer_auth(token.to_owned());
191    };
192
193    let req = req_builder.build()?;
194    let resp = configuration.client.execute(req).await?;
195
196    let status = resp.status();
197    let content_type = resp
198        .headers()
199        .get("content-type")
200        .and_then(|v| v.to_str().ok())
201        .unwrap_or("application/octet-stream");
202    let content_type = super::ContentType::from(content_type);
203
204    if !status.is_client_error() && !status.is_server_error() {
205        let content = resp.text().await?;
206        match content_type {
207            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
208            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::PipelineView`"))),
209            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::PipelineView`")))),
210        }
211    } else {
212        let content = resp.text().await?;
213        let entity: Option<GetWorldPipelineError> = serde_json::from_str(&content).ok();
214        Err(Error::ResponseError(ResponseContent { status, content, entity }))
215    }
216}
217
218/// Holds the connection open as text/event-stream and pushes a `news` event — the same {items:[…]} body GET /v1/world/news answers — each time the caller's (org, project) feed refreshes, with a `: ping` heartbeat comment every 25s. Delivery is best-effort: a slow consumer is dropped on buffer overrun and reconnects, re-fetching GET /v1/world/news, which stays the source of truth. Requires a validated principal; 403 without one.
219pub async fn get_world_stream(configuration: &configuration::Configuration, ) -> Result<(), Error<GetWorldStreamError>> {
220
221    let uri_str = format!("{}/v1/world/stream", configuration.base_path);
222    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
223
224    if let Some(ref user_agent) = configuration.user_agent {
225        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
226    }
227    if let Some(ref token) = configuration.bearer_access_token {
228        req_builder = req_builder.bearer_auth(token.to_owned());
229    };
230
231    let req = req_builder.build()?;
232    let resp = configuration.client.execute(req).await?;
233
234    let status = resp.status();
235
236    if !status.is_client_error() && !status.is_server_error() {
237        Ok(())
238    } else {
239        let content = resp.text().await?;
240        let entity: Option<GetWorldStreamError> = serde_json::from_str(&content).ok();
241        Err(Error::ResponseError(ResponseContent { status, content, entity }))
242    }
243}
244
245/// Replaces the caller project's news pipeline and returns what was stored. It is a WHOLE replacement, not a patch: a field the request leaves out is stored empty, so sending only feeds clears the filters.  Every feed URL is validated HERE, at the write boundary — http(s) only, and the host must be on the server's allowlist — so a stored pipeline can never name a host the fetcher would later refuse, and the allowlist is one decision in one place rather than a check at each fetch.
246pub async fn put_world_pipeline(configuration: &configuration::Configuration, pipeline_req: models::PipelineReq) -> Result<models::PipelineView, Error<PutWorldPipelineError>> {
247    // add a prefix to parameters to efficiently prevent name collisions
248    let p_pipeline_req = pipeline_req;
249
250    let uri_str = format!("{}/v1/world/pipeline", configuration.base_path);
251    let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str);
252
253    if let Some(ref user_agent) = configuration.user_agent {
254        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
255    }
256    if let Some(ref token) = configuration.bearer_access_token {
257        req_builder = req_builder.bearer_auth(token.to_owned());
258    };
259    req_builder = req_builder.json(&p_pipeline_req);
260
261    let req = req_builder.build()?;
262    let resp = configuration.client.execute(req).await?;
263
264    let status = resp.status();
265    let content_type = resp
266        .headers()
267        .get("content-type")
268        .and_then(|v| v.to_str().ok())
269        .unwrap_or("application/octet-stream");
270    let content_type = super::ContentType::from(content_type);
271
272    if !status.is_client_error() && !status.is_server_error() {
273        let content = resp.text().await?;
274        match content_type {
275            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
276            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::PipelineView`"))),
277            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::PipelineView`")))),
278        }
279    } else {
280        let content = resp.text().await?;
281        let entity: Option<PutWorldPipelineError> = serde_json::from_str(&content).ok();
282        Err(Error::ResponseError(ResponseContent { status, content, entity }))
283    }
284}
285