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

  1. Log the raw response body and compare field-by-field against notionPage's struct tags.
  2. Align NotionAPIVersion with the schema notionPage was written for; update struct tags after version upgrades.
  3. Verify the dsID is a genuine data_source ID (from databases/{id}.data_sources or search), not a page/database ID.
  4. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/e82962bdaed7acbd. Report an issue: GitHub.