Tencent/WeKnora · error
unmarshal data_source: %w
Error message
unmarshal data_source: %w
What it means
GetDataSourceInfo fetched /v1/data_sources/{dsID} successfully (2xx) but json.Unmarshal into notionPage failed. The data_source object response does not match the notionPage struct. Since data_source objects are a newer API concept (2025-09-03+), this is the most version-sensitive unmarshal in the client.
Source
Thrown at internal/datasource/connector/notion/client.go:228
if len(dsResult.DataSources) > 0 {
dsID = dsResult.DataSources[0].ID
}
return &databaseInfo{Page: db, DataSourceID: dsID}, nil
}
// GetDataSourceInfo retrieves a data source by ID, returning its metadata.
// In API 2025-09-03+, data_source objects hold the schema/properties and are
// the target for record queries. The response includes database_parent to
// locate the database in the workspace hierarchy.
func (c *notionClient) GetDataSourceInfo(ctx context.Context, dsID string) (*notionPage, error) {
respBody, err := c.doRequest(ctx, http.MethodGet, "/v1/data_sources/"+dsID, nil)
if err != nil {
return nil, err
}
var ds notionPage
if err := json.Unmarshal(respBody, &ds); err != nil {
return nil, fmt.Errorf("unmarshal data_source: %w", err)
}
ds.Title = extractTitle(&ds)
return &ds, nil
}
// GetBlockChildrenFlat fetches only the direct children of a block (no recursion).
// Used by discoverPages to quickly scan for child_page/child_database without
// fetching the full block tree content.
func (c *notionClient) GetBlockChildrenFlat(ctx context.Context, blockID string) ([]notionBlock, error) {
var allBlocks []notionBlock
var startCursor string
for {
path := fmt.Sprintf("/v1/blocks/%s/children", blockID)
if startCursor != "" {
path += "?start_cursor=" + startCursor
}
View on GitHub (pinned to 988cbb0330)
Solutions
- Log the raw response body and compare field-by-field against notionPage's struct tags.
- Align NotionAPIVersion with the schema notionPage was written for; update struct tags after version upgrades.
- Verify the dsID is a genuine data_source ID (from databases/{id}.data_sources or search), not a page/database ID.
- Consider decoding into a lenient structure (json.RawMessage for volatile fields) to be resilient to additive API changes.
Defensive patterns
Strategy: type-guard
Type guard
func isLikelyJSON(b []byte) bool {
t := bytes.TrimSpace(b)
return len(t) > 0 && (t[0] == '{' || t[0] == '[')
} Try / catch
ds, err := client.GetDataSourceInfo(ctx, dsID)
if err != nil && strings.HasPrefix(err.Error(), "unmarshal data_source:") {
// API version drift is the usual cause; log body and pin matching version
return fmt.Errorf("data_source schema mismatch (%s): %w", dsID, err)
} Prevention
- Pin Notion-Version to the version the structs were written against
- Only pass real data_source IDs (from data_sources[] or search) into this call
- Use json.RawMessage for volatile fields to absorb additive schema changes
- Re-run integration tests against the live API after every version bump
When it happens
Trigger: GET /v1/data_sources/{id} returning JSON whose shape breaks notionPage decoding — typically after a Notion API version bump changing data_source fields, or a 200 non-JSON body from an intercepting proxy. Called from getDatabaseOrDataSourceInfo during discovery.
Common situations: API contract drift: upgrading Notion-Version changes data_source schema (e.g. properties moved/renamed) while notionPage expects old field types; test fixtures outdated; proxy returning 200 HTML error pages.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- unmarshal page: %w
- unmarshal database: %w
- unmarshal data_sources: %w
- unmarshal block children response: %w
- unmarshal page results: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/e82962bdaed7acbd.
Report an issue: GitHub.