Skip to content

v2: Design type-safe Where and WhereDocument filter DSL #83

@oss-amikos

Description

@oss-amikos

Context

PR #80's Where.eq(String key, Object value) accepts any Object — no compile-time type safety. The Go client has typed variants (EqString, EqInt, GtFloat). We need to bring that type safety to Java while keeping the API ergonomic.

Requirements

Where (metadata filtering)

public abstract class Where {
    // Type-safe equality
    static Where eq(String key, String value);
    static Where eq(String key, int value);
    static Where eq(String key, float value);
    static Where eq(String key, boolean value);

    // Type-safe comparison (numeric + string)
    static Where ne(String key, String value);
    static Where ne(String key, int value);
    static Where ne(String key, float value);
    static Where gt(String key, int value);
    static Where gt(String key, float value);
    static Where gte(String key, int value);
    static Where gte(String key, float value);
    static Where lt(String key, int value);
    static Where lt(String key, float value);
    static Where lte(String key, int value);
    static Where lte(String key, float value);

    // Set operations (type-safe)
    static Where in(String key, List<String> values);
    static Where in(String key, int... values);
    static Where in(String key, float... values);
    static Where nin(String key, List<String> values);
    static Where nin(String key, int... values);
    static Where nin(String key, float... values);

    // Logical combinators
    static Where and(Where... conditions);
    static Where or(Where... conditions);

    // Instance chaining (convenience)
    Where and(Where other);
    Where or(Where other);

    // Serialization
    abstract Map<String, Object> toMap();
}

WhereDocument (document content filtering)

public abstract class WhereDocument {
    static WhereDocument contains(String text);
    static WhereDocument notContains(String text);

    static WhereDocument and(WhereDocument... conditions);
    static WhereDocument or(WhereDocument... conditions);

    abstract Map<String, Object> toMap();
}

Usage Examples

// Simple
Where.eq("type", "article")

// Compound
Where.and(
    Where.eq("type", "article"),
    Where.gt("views", 1000),
    Where.in("status", List.of("published", "featured"))
)

// Chaining
Where.eq("type", "article").and(Where.gt("views", 1000))

// Document
WhereDocument.contains("machine learning")

Design Decisions

  1. Overloaded static methods per typeeq(String, String), eq(String, int), eq(String, float) — instead of a single eq(String, Object). This gives compile-time safety.
  2. Abstract class, not interface — allows static factory methods (Java 8 compat) and internal subclasses
  3. Sealed hierarchy (or package-private subclasses) — ComparisonWhere, LogicalWhere, etc. Users only interact via static factories.
  4. toMap() for serialization — the HTTP layer calls toMap() to build the JSON request body
  5. No Where.empty() — just use null or omit the parameter entirely

Acceptance Criteria

  • Where class with all typed static factory methods
  • WhereDocument class with contains/notContains and logical combinators
  • Compile-time rejection of Where.gt("key", "not-a-number") for numeric-only ops
  • toMap() produces correct Chroma filter JSON structure
  • Unit tests for serialization of all filter types
  • Unit tests for nested AND/OR combinations

Supersedes

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-designAPI shape and design decisionsenhancementNew feature or requestv2v2 API support

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions