///|
/// Gateway API types for CLI and HTTP server
///|
/// Gateway errors
pub(all) suberror GatewayError {
NotFound(String)
InvalidInput(String)
DimensionMismatch(expected~ : Int, got~ : Int)
CollectionExists(String)
CollectionNotFound(String)
StorageError(String)
}
///|
pub impl Show for GatewayError with fn output(self, logger) {
match self {
NotFound(s) => {
logger.write_string("NotFound(")
Show::output(s, logger)
logger.write_string(")")
}
InvalidInput(s) => {
logger.write_string("InvalidInput(")
Show::output(s, logger)
logger.write_string(")")
}
DimensionMismatch(expected~, got~) => {
logger.write_string("DimensionMismatch(expected: ")
Show::output(expected, logger)
logger.write_string(", got: ")
Show::output(got, logger)
logger.write_string(")")
}
CollectionExists(s) => {
logger.write_string("CollectionExists(")
Show::output(s, logger)
logger.write_string(")")
}
CollectionNotFound(s) => {
logger.write_string("CollectionNotFound(")
Show::output(s, logger)
logger.write_string(")")
}
StorageError(s) => {
logger.write_string("StorageError(")
Show::output(s, logger)
logger.write_string(")")
}
}
}
///|
/// Collection configuration
pub struct CollectionConfig {
name : String
dim : Int
metric : @types.Metric
strategy : @types.Strategy
}
///|
pub impl Show for CollectionConfig with fn output(self, logger) {
logger.write_string("{ name: ")
Show::output(self.name, logger)
logger.write_string(", dim: ")
Show::output(self.dim, logger)
logger.write_string(", metric: ")
Show::output(self.metric, logger)
logger.write_string(", strategy: ")
Show::output(self.strategy, logger)
logger.write_string(" }")
}
///|
/// Create default collection config
pub fn CollectionConfig::default(name : String, dim : Int) -> CollectionConfig {
{
name,
dim,
metric: @types.Cosine,
strategy: @types.Strategy::default_hnsw(),
}
}
///|
/// Parse collection config from JSON
/// Supports both formats:
/// - {"dim": 4, "metric": "Cosine"}
/// - {"vectors": {"size": 4, "distance": "Cosine"}} (Qdrant-compatible)
pub fn CollectionConfig::from_json(
json : Json,
) -> Result[CollectionConfig, String] {
match json {
Object(_) => {
let name = get_string(json, "name").unwrap_or("")
let dim = match parse_collection_dim(json) {
Ok(v) => v
Err(err) => return Err(err)
}
let metric = match parse_collection_metric(json) {
Ok(v) => v
Err(err) => return Err(err)
}
let strategy = match parse_collection_strategy(json) {
Ok(v) => v
Err(err) => return Err(err)
}
Ok({ name, dim, metric, strategy })
}
_ => Err("expected JSON object")
}
}
///|
fn parse_collection_dim(json : Json) -> Result[Int, String] {
match get_object(json, "vectors") {
Some(vectors) =>
match get_int(Json::object(vectors), "size") {
Some(dim) =>
if dim > 0 {
Ok(dim)
} else {
Err("vectors.size must be > 0")
}
None => Err("vectors.size is required")
}
None =>
match get_int(json, "dim") {
Some(dim) => if dim > 0 { Ok(dim) } else { Err("dim must be > 0") }
None => Err("dim is required")
}
}
}
///|
fn parse_collection_metric(json : Json) -> Result[@types.Metric, String] {
let metric_str = match get_object(json, "vectors") {
Some(vectors) => get_string(Json::object(vectors), "distance")
None => get_string(json, "metric")
}
match metric_str {
None => Ok(@types.Cosine)
Some(s) =>
match @types.Metric::from_name(s) {
Some(m) => Ok(m)
None => Err("unsupported metric: " + s)
}
}
}
///|
fn parse_collection_strategy(json : Json) -> Result[@types.Strategy, String] {
match get_string(json, "strategy") {
None => Ok(@types.Strategy::default_hnsw())
Some(s) =>
match @types.Strategy::from_name(s) {
Some(st) => Ok(st)
None => Err("unsupported strategy: " + s)
}
}
}
///|
/// Convert collection config to JSON
pub fn CollectionConfig::to_json(self : CollectionConfig) -> Json {
let metric_str = self.metric.to_name()
let strategy_str = self.strategy.to_name()
json_object([
("name", self.name.to_json()),
("dim", self.dim.to_double().to_json()),
("metric", metric_str.to_json()),
("strategy", strategy_str.to_json()),
])
}
///|
fn vector_id_to_json(id : @types.VectorId) -> Json {
match id {
Int64Id(v) => int64_id_to_json(v)
Bytes16Id(_) => id.to_display_string().to_json()
}
}
///|
/// Single point for upsert
pub struct PointData {
id : Int64
vector : Array[Double]
payload : @types.Attrs
}
///|
pub impl Show for PointData with fn output(self, logger) {
logger.write_string("{ id: ")
Show::output(self.id, logger)
logger.write_string(", vector: ")
Show::output(self.vector, logger)
logger.write_string(", payload: ")
Show::output(self.payload, logger)
logger.write_string(" }")
}
///|
/// Parse point from JSON
pub fn PointData::from_json(json : Json) -> PointData? {
match json {
Object(_) => {
let id = match get_int64_id(json, "id") {
Some(i) => i
None => return None
}
let vector_json = match get_array(json, "vector") {
Some(arr) => arr
None => return None
}
let vector = match json_to_vector(Json::array(vector_json)) {
Some(v) => v
None => return None
}
let payload = match get_object(json, "payload") {
Some(obj) =>
match json_to_attrs(Json::object(obj)) {
Some(attrs) => attrs
None => return None
}
None => @types.empty_attrs()
}
Some({ id, vector, payload })
}
_ => None
}
}
///|
/// Convert point to JSON
pub fn PointData::to_json(self : PointData) -> Json {
json_object([
("id", int64_id_to_json(self.id)),
("vector", vector_to_json(self.vector)),
("payload", attrs_to_json(self.payload)),
])
}
///|
/// Upsert request (batch)
pub struct UpsertRequest {
points : Array[PointData]
}
///|
pub impl Show for UpsertRequest with fn output(self, logger) {
logger.write_string("{ points: ")
Show::output(self.points, logger)
logger.write_string(" }")
}
///|
/// Parse upsert request from JSON
pub fn UpsertRequest::from_json(json : Json) -> UpsertRequest? {
match json {
Object(_) => {
let points_json = match get_array(json, "points") {
Some(arr) => arr
None => return None
}
let points : Array[PointData] = []
for p in points_json {
match PointData::from_json(p) {
Some(point) => points.push(point)
None => return None
}
}
Some({ points, })
}
_ => None
}
}
///|
/// Search request
pub struct SearchRequest {
vector : Array[Double]
limit : Int
filter : Json?
with_payload : Bool
with_vector : Bool
}
///|
pub impl Show for SearchRequest with fn output(self, logger) {
logger.write_string("{ vector: ")
Show::output(self.vector, logger)
logger.write_string(", limit: ")
Show::output(self.limit, logger)
logger.write_string(", filter: ")
Debug::to_repr(self.filter).output(logger)
logger.write_string(", with_payload: ")
Show::output(self.with_payload, logger)
logger.write_string(", with_vector: ")
Show::output(self.with_vector, logger)
logger.write_string(" }")
}
///|
/// Parse search request from JSON
pub fn SearchRequest::from_json(json : Json) -> SearchRequest? {
match json {
Object(_) => {
let vector_json = match get_array(json, "vector") {
Some(arr) => arr
None => return None
}
let vector = match json_to_vector(Json::array(vector_json)) {
Some(v) => v
None => return None
}
let limit = get_int(json, "limit").unwrap_or(10)
let with_payload = get_bool(json, "with_payload").unwrap_or(true)
let with_vector = get_bool(json, "with_vector").unwrap_or(false)
let filter : Json? = match get_object(json, "filter") {
Some(f) => Some(Json::object(f))
None => None
}
Some({ vector, limit, filter, with_payload, with_vector })
}
_ => None
}
}
///|
/// Search hit (result item)
pub struct SearchHitResponse {
id : @types.VectorId
score : Double
payload : @types.Attrs?
vector : Array[Double]?
}
///|
pub impl Show for SearchHitResponse with fn output(self, logger) {
logger.write_string("{ id: ")
Show::output(self.id, logger)
logger.write_string(", score: ")
Show::output(self.score, logger)
logger.write_string(", payload: ")
Debug::to_repr(self.payload).output(logger)
logger.write_string(", vector: ")
Debug::to_repr(self.vector).output(logger)
logger.write_string(" }")
}
///|
/// Convert search hit to JSON
pub fn SearchHitResponse::to_json(self : SearchHitResponse) -> Json {
let parts : Array[(String, Json)] = [
("id", vector_id_to_json(self.id)),
("score", self.score.to_json()),
]
match self.payload {
Some(p) => parts.push(("payload", attrs_to_json(p)))
None => ()
}
match self.vector {
Some(v) => parts.push(("vector", vector_to_json(v)))
None => ()
}
json_object(parts)
}
///|
/// Search response
pub struct SearchResponse {
result : Array[SearchHitResponse]
time_ms : Double
}
///|
pub impl Show for SearchResponse with fn output(self, logger) {
logger.write_string("{ result: ")
Show::output(self.result, logger)
logger.write_string(", time_ms: ")
Show::output(self.time_ms, logger)
logger.write_string(" }")
}
///|
/// Convert search response to JSON
pub fn SearchResponse::to_json(self : SearchResponse) -> Json {
let results = Json::array(self.result.map(fn(h) { h.to_json() }))
json_object([("result", results), ("time", self.time_ms.to_json())])
}
///|
/// Point response (for get operations)
pub struct PointResponse {
id : @types.VectorId
payload : @types.Attrs
vector : Array[Double]
}
///|
pub impl Show for PointResponse with fn output(self, logger) {
logger.write_string("{ id: ")
Show::output(self.id, logger)
logger.write_string(", payload: ")
Show::output(self.payload, logger)
logger.write_string(", vector: ")
Show::output(self.vector, logger)
logger.write_string(" }")
}
///|
/// Convert point response to JSON
pub fn PointResponse::to_json(self : PointResponse) -> Json {
json_object([
("id", vector_id_to_json(self.id)),
("payload", attrs_to_json(self.payload)),
("vector", vector_to_json(self.vector)),
])
}
///|
/// Scroll request, modelled after Qdrant's scroll points API.
pub struct ScrollRequest {
offset : Int64?
limit : Int
with_payload : Bool
with_vector : Bool
}
///|
pub impl Show for ScrollRequest with fn output(self, logger) {
logger.write_string("{ offset: ")
Debug::to_repr(self.offset).output(logger)
logger.write_string(", limit: ")
Show::output(self.limit, logger)
logger.write_string(", with_payload: ")
Show::output(self.with_payload, logger)
logger.write_string(", with_vector: ")
Show::output(self.with_vector, logger)
logger.write_string(" }")
}
///|
pub fn ScrollRequest::from_json(json : Json) -> ScrollRequest? {
match json {
Object(_) =>
Some({
offset: get_int64_id(json, "offset"),
limit: get_int(json, "limit").unwrap_or(10),
with_payload: get_bool(json, "with_payload").unwrap_or(true),
with_vector: get_bool(json, "with_vector").unwrap_or(false),
})
_ => None
}
}
///|
pub struct ScrollPointResponse {
id : @types.VectorId
payload : @types.Attrs?
vector : Array[Double]?
}
///|
pub impl Show for ScrollPointResponse with fn output(self, logger) {
logger.write_string("{ id: ")
Show::output(self.id, logger)
logger.write_string(", payload: ")
Debug::to_repr(self.payload).output(logger)
logger.write_string(", vector: ")
Debug::to_repr(self.vector).output(logger)
logger.write_string(" }")
}
///|
pub fn ScrollPointResponse::to_json(self : ScrollPointResponse) -> Json {
let parts : Array[(String, Json)] = [("id", vector_id_to_json(self.id))]
match self.payload {
Some(payload) => parts.push(("payload", attrs_to_json(payload)))
None => ()
}
match self.vector {
Some(vector) => parts.push(("vector", vector_to_json(vector)))
None => ()
}
json_object(parts)
}
///|
pub struct ScrollResponse {
points : Array[ScrollPointResponse]
next_page_offset : @types.VectorId?
}
///|
pub impl Show for ScrollResponse with fn output(self, logger) {
logger.write_string("{ points: ")
Show::output(self.points, logger)
logger.write_string(", next_page_offset: ")
Debug::to_repr(self.next_page_offset).output(logger)
logger.write_string(" }")
}
///|
pub fn ScrollResponse::to_json(self : ScrollResponse) -> Json {
let parts : Array[(String, Json)] = [
("points", Json::array(self.points.map(fn(point) { point.to_json() }))),
]
match self.next_page_offset {
Some(offset) => parts.push(("next_page_offset", vector_id_to_json(offset)))
None => ()
}
json_object(parts)
}
///|
/// Collection info response
pub struct CollectionInfo {
name : String
vectors_count : Int
dim : Int
metric : String
strategy : String
}
///|
pub impl Show for CollectionInfo with fn output(self, logger) {
logger.write_string("{ name: ")
Show::output(self.name, logger)
logger.write_string(", vectors_count: ")
Show::output(self.vectors_count, logger)
logger.write_string(", dim: ")
Show::output(self.dim, logger)
logger.write_string(", metric: ")
Show::output(self.metric, logger)
logger.write_string(", strategy: ")
Show::output(self.strategy, logger)
logger.write_string(" }")
}
///|
/// Convert collection info to JSON
pub fn CollectionInfo::to_json(self : CollectionInfo) -> Json {
json_object([
("name", self.name.to_json()),
("vectors_count", self.vectors_count.to_double().to_json()),
("dim", self.dim.to_double().to_json()),
("metric", self.metric.to_json()),
("strategy", self.strategy.to_json()),
])
}
///|
/// Collections list response
pub struct CollectionsResponse {
collections : Array[CollectionInfo]
}
///|
pub impl Show for CollectionsResponse with fn output(self, logger) {
logger.write_string("{ collections: ")
Show::output(self.collections, logger)
logger.write_string(" }")
}
///|
/// Convert collections list to JSON
pub fn CollectionsResponse::to_json(self : CollectionsResponse) -> Json {
let collections = Json::array(self.collections.map(fn(c) { c.to_json() }))
json_object([("collections", collections)])
}
///|
/// Generic API response wrapper
pub struct ApiResponse {
status : String
result : Json?
error : String?
}
///|
pub impl Show for ApiResponse with fn output(self, logger) {
logger.write_string("{ status: ")
Show::output(self.status, logger)
logger.write_string(", result: ")
Debug::to_repr(self.result).output(logger)
logger.write_string(", error: ")
Debug::to_repr(self.error).output(logger)
logger.write_string(" }")
}
///|
/// Create success response
pub fn ApiResponse::ok(result : Json) -> ApiResponse {
{ status: "ok", result: Some(result), error: None }
}
///|
/// Create error response
pub fn ApiResponse::err(message : String) -> ApiResponse {
{ status: "error", result: None, error: Some(message) }
}
///|
/// Convert API response to JSON
pub fn ApiResponse::to_json(self : ApiResponse) -> Json {
let parts : Array[(String, Json)] = [("status", self.status.to_json())]
match self.result {
Some(r) => parts.push(("result", r))
None => ()
}
match self.error {
Some(e) => parts.push(("error", e.to_json()))
None => ()
}
json_object(parts)
}