blob: 93bdff29062c0b70a7fea8998b1332c2e40cc43e [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{ConfigError, Result};
use maka_agent_facts::configuration::*;
use serde_json::{Value, json};
/// Produces a 128-item / 128 KiB wire page; the cursor identifies
/// the next item, and only belongs to the revision that produced it.
pub fn project(
snapshot: &ConnectionCatalogSnapshot,
input: &ConnectionCatalogQueryInput,
resolve: impl Fn(&ConnectionCatalogEntry, Option<&str>) -> Result<Vec<ModelCatalogEntry>>,
) -> Result<Value> {
if let ConnectionCatalogQueryInput::Continue { revision, .. } = input {
validation::revision(*revision, false).map_err(ConfigError::Invalid)?;
if *revision != snapshot.revision {
return Ok(
json!({"kind":"revision_changed", "expectedRevision":revision,
"actualRevision":snapshot.revision}),
);
}
}
let mut items = Vec::new();
for (connection_index, row) in snapshot.connections.iter().enumerate() {
let default = snapshot
.default_target
.as_ref()
.filter(|target| target.connection_id == row.connection_id)
.map(|target| target.model_id.as_str());
let entries = resolve(row, default)?;
for entry in &entries {
entry.validate().map_err(ConfigError::Invalid)?;
}
let mut header = json!({"kind":"connection", "connectionIndex":connection_index,
"connectionId":row.connection_id,"revision":row.revision,"slug":row.slug,
"name":row.name,"provider":row.provider,"configuration":row.configuration,"enabled":row.enabled,
"enabledModelIdCount":row.enabled_model_ids.len(),"modelCount":row.models.len(),
"catalogEntryCount":entries.len()});
for (key, value) in [
("modelSource", serde_json::to_value(row.model_source)?),
("lastTest", serde_json::to_value(&row.last_test)?),
(
"requestBodyOverlay",
serde_json::to_value(&row.request_body_overlay)?,
),
] {
if !value.is_null() {
header[key] = value;
}
}
items.push(header);
for (item_index, id) in row.enabled_model_ids.iter().enumerate() {
let item = json!({"kind":"enabled_model_id", "connectionIndex":connection_index,
"itemIndex":item_index,"modelId":id});
items.push(item);
}
for (item_index, model) in row.models.iter().enumerate() {
model.validate().map_err(ConfigError::Invalid)?;
items.push(json!({"kind":"model","connectionIndex":connection_index,
"itemIndex":item_index,"model":model}));
}
for (item_index, entry) in entries.into_iter().enumerate() {
let profile = row.model_overrides.as_ref().and_then(|p| p.get(&entry.id));
let mut item = json!({"kind":"catalog_entry","connectionIndex":connection_index,
"itemIndex":item_index,"entry":entry});
if let Some(profile) = profile {
item["modelOverride"] = serde_json::to_value(profile)?;
}
items.push(item);
}
}
let offset = match input {
ConnectionCatalogQueryInput::Start => 0,
ConnectionCatalogQueryInput::Continue { cursor, .. } => {
let cursor = serde_json::to_value(cursor)?;
items
.iter()
.position(|item| cursor_for(item) == cursor)
.ok_or_else(|| ConfigError::Invalid("catalog cursor has no matching item".into()))?
}
};
let page = |end: usize| {
json!({"kind":"page","revision":snapshot.revision,
"defaultTarget":snapshot.default_target,"connectionCount":snapshot.connections.len(),
"items":&items[offset..end],"nextCursor":items.get(end).map(cursor_for)})
};
// Count each item once. Re-encoding every growing prefix blocks the Host's
// connection task on large provider inventories, delaying unrelated control RPCs.
let mut envelope = page(offset);
envelope["nextCursor"] = Value::Null;
let envelope_bytes = serde_json::to_vec(&envelope)?.len() - 4; // replace JSON null
let mut item_bytes = 0;
let mut end = offset;
while end < items.len().min(offset + 128) {
let next_item_bytes = serde_json::to_vec(&items[end])?.len() + usize::from(end > offset);
let cursor_bytes = serde_json::to_vec(&items.get(end + 1).map(cursor_for))?.len();
if end > offset && envelope_bytes + item_bytes + next_item_bytes + cursor_bytes > 128 * 1024
{
break;
}
item_bytes += next_item_bytes;
end += 1;
}
Ok(page(end))
}
fn cursor_for(item: &Value) -> Value {
let mut cursor = json!({"part":item["kind"],"connectionIndex":item["connectionIndex"]});
if let Some(index) = item.get("itemIndex") {
cursor["itemIndex"] = index.clone();
}
cursor
}