feat: add msglib display entity extraction
This commit is contained in:
@@ -16,6 +16,12 @@ import (
|
||||
const (
|
||||
Protocol = "isphere.msglib.v1"
|
||||
DefaultPassword = "123"
|
||||
|
||||
EntityTypeContacts = "contacts"
|
||||
EntityTypeGroups = "groups"
|
||||
|
||||
DefaultDisplayEntityLimit = 25
|
||||
MaxDisplayEntityLimit = 100
|
||||
)
|
||||
|
||||
var requestCounter uint64
|
||||
@@ -81,6 +87,21 @@ type DisplaySource struct {
|
||||
PresentColumns []string `json:"present_columns"`
|
||||
}
|
||||
|
||||
type DisplayEntitiesOptions struct {
|
||||
EntityType string
|
||||
Query string
|
||||
Limit int
|
||||
}
|
||||
|
||||
type DisplayEntity struct {
|
||||
EntityType string `json:"entity_type"`
|
||||
SourceTable string `json:"source_table"`
|
||||
JID string `json:"jid"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
MatchedColumns []string `json:"matched_columns"`
|
||||
}
|
||||
|
||||
type SidecarError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
@@ -118,10 +139,45 @@ func (c *Client) DisplaySources(ctx context.Context) ([]DisplaySource, error) {
|
||||
return out.DisplaySources, nil
|
||||
}
|
||||
|
||||
func (c *Client) DisplayEntities(ctx context.Context, opts DisplayEntitiesOptions) ([]DisplayEntity, error) {
|
||||
entityType := strings.TrimSpace(opts.EntityType)
|
||||
if entityType != EntityTypeContacts && entityType != EntityTypeGroups {
|
||||
return nil, fmt.Errorf("entity_type must be %q or %q", EntityTypeContacts, EntityTypeGroups)
|
||||
}
|
||||
limit := opts.Limit
|
||||
if limit <= 0 {
|
||||
limit = DefaultDisplayEntityLimit
|
||||
}
|
||||
if limit > MaxDisplayEntityLimit {
|
||||
limit = MaxDisplayEntityLimit
|
||||
}
|
||||
|
||||
args := map[string]any{
|
||||
"sqlite_dll_path": c.config.SQLiteDLLPath,
|
||||
"db_path": c.config.DBPath,
|
||||
"password": c.config.Password,
|
||||
"entity_type": entityType,
|
||||
"limit": limit,
|
||||
}
|
||||
if strings.TrimSpace(opts.Query) != "" {
|
||||
args["query"] = strings.TrimSpace(opts.Query)
|
||||
}
|
||||
|
||||
var out displayEntitiesData
|
||||
if err := c.call(ctx, "display_entities", args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out.Entities, nil
|
||||
}
|
||||
|
||||
type displaySourcesData struct {
|
||||
DisplaySources []DisplaySource `json:"display_sources"`
|
||||
}
|
||||
|
||||
type displayEntitiesData struct {
|
||||
Entities []DisplayEntity `json:"entities"`
|
||||
}
|
||||
|
||||
type requestEnvelope struct {
|
||||
Protocol string `json:"protocol"`
|
||||
RequestID string `json:"request_id"`
|
||||
|
||||
@@ -55,6 +55,44 @@ func TestDisplaySourcesPassesConfiguredPathsAndDecodesMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayEntitiesPassesBoundedArgsAndDecodesMetadata(t *testing.T) {
|
||||
cfg := Config{
|
||||
SQLiteDLLPath: `C:\fixture\System.Data.SQLite.dll`,
|
||||
DBPath: `C:\fixture\MsgLib.db`,
|
||||
Password: "123",
|
||||
}
|
||||
client := newFakeClient(t, "display_entities_ok", cfg)
|
||||
|
||||
got, err := client.DisplayEntities(context.Background(), DisplayEntitiesOptions{EntityType: "contacts", Query: "alice", Limit: 2})
|
||||
if err != nil {
|
||||
t.Fatalf("DisplayEntities returned error: %v", err)
|
||||
}
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("len(DisplayEntities) = %d, want 2", len(got))
|
||||
}
|
||||
if got[0].EntityType != "contacts" || got[0].SourceTable != "TD_Roster" || got[0].JID != "alice@example" || got[0].DisplayName != "Alice" {
|
||||
t.Fatalf("unexpected first entity: %+v", got[0])
|
||||
}
|
||||
if got[0].Confidence <= 0 || strings.Join(got[0].MatchedColumns, ",") != "JId,Nick" {
|
||||
t.Fatalf("unexpected first entity metadata: %+v", got[0])
|
||||
}
|
||||
if got[1].SourceTable != "tblRecent" || got[1].JID != "recent@example" || got[1].DisplayName != "Recent Alice" {
|
||||
t.Fatalf("unexpected second entity: %+v", got[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayEntitiesRejectsUnsupportedEntityType(t *testing.T) {
|
||||
client := newFakeClient(t, "display_entities_ok", Config{})
|
||||
|
||||
_, err := client.DisplayEntities(context.Background(), DisplayEntitiesOptions{EntityType: "messages", Limit: 5})
|
||||
if err == nil {
|
||||
t.Fatal("DisplayEntities returned nil error, want unsupported entity_type error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "entity_type") {
|
||||
t.Fatalf("DisplayEntities error = %v, want entity_type context", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientReturnsStructuredSidecarError(t *testing.T) {
|
||||
client := newFakeClient(t, "sidecar_error", Config{})
|
||||
|
||||
@@ -202,6 +240,31 @@ func fakeSidecarMain() int {
|
||||
"error": nil,
|
||||
"warnings": []any{},
|
||||
})
|
||||
case "display_entities":
|
||||
args, _ := req["args"].(map[string]any)
|
||||
if args["sqlite_dll_path"] != `C:\fixture\System.Data.SQLite.dll` || args["db_path"] != `C:\fixture\MsgLib.db` || args["password"] != "123" || args["entity_type"] != "contacts" || args["query"] != "alice" || args["limit"] != float64(2) {
|
||||
fmt.Fprintf(os.Stderr, "bad display_entities args: %#v\n", args)
|
||||
return 2
|
||||
}
|
||||
return writeFakeResponse(map[string]any{
|
||||
"protocol": Protocol,
|
||||
"request_id": requestID,
|
||||
"op": op,
|
||||
"ok": true,
|
||||
"data": map[string]any{
|
||||
"metadata_only": true,
|
||||
"read_only": true,
|
||||
"entity_type": "contacts",
|
||||
"limit": 2,
|
||||
"result_count": 2,
|
||||
"entities": []any{
|
||||
map[string]any{"entity_type": "contacts", "source_table": "TD_Roster", "jid": "alice@example", "display_name": "Alice", "confidence": 0.95, "matched_columns": []any{"JId", "Nick"}},
|
||||
map[string]any{"entity_type": "contacts", "source_table": "tblRecent", "jid": "recent@example", "display_name": "Recent Alice", "confidence": 0.70, "matched_columns": []any{"PersonId", "PersonName"}},
|
||||
},
|
||||
},
|
||||
"error": nil,
|
||||
"warnings": []any{},
|
||||
})
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unexpected op: %s\n", op)
|
||||
return 2
|
||||
|
||||
Reference in New Issue
Block a user