dkapi

package module
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 18 Imported by: 0

README

developerknowledge-go

Go helpers for the Google Developer Knowledge API.

This module provides shared primitives used by Developer Knowledge API clients:

  • API key and ADC authentication helpers
  • quota project handling for local ADC (including CLOUDSDK_CONFIG)
  • Google API error parsing with bounded error-body reads
  • rate limit error handling and Retry-After parsing
  • context-aware HTTP request helpers
  • typed v1 AnswerQuery support, including citations and document references
  • typed GetDocument with DocumentView selection, so metadata-only requests report contentLengthBytes without downloading content
  • documents:batchGet chunking and positional partial results, including metadata-only DocumentView requests
  • shared Document and DocumentChunk response types
  • conservative batch bisection error classification

See pkg.go.dev for API documentation. This module complements the official generated client at google.golang.org/api/developerknowledge/v1; see repository issues for the long-term direction.

Install

go get github.com/apstndb/developerknowledge-go

Example

import (
    "context"

    dkapi "github.com/apstndb/developerknowledge-go"
)

ctx := context.Background()
client, apiKey, err := dkapi.NewAuthenticatedHTTPClient(ctx, dkapi.AuthConfig{
    Mode:    dkapi.AuthPreferAPIKey,
    Timeout: dkapi.DefaultHTTPTimeout,
})
if err != nil {
    return err
}

dkClient := &dkapi.Client{
    BaseURL:    dkapi.DefaultV1BaseURL,
    APIKey:     apiKey,
    HTTPClient: client,
}

docs, err := dkClient.BatchGetDocuments(ctx, []string{
    "documents/developers.google.com/knowledge/api",
})

Documentation

Overview

Package dkapi provides shared primitives for Google Developer Knowledge API clients.

This module complements (rather than replaces) the official generated client at google.golang.org/api/developerknowledge/v1. It focuses on auth-mode selection, quota-project handling for local ADC, rate-limit retry, AnswerQuery, document retrieval with view selection, batch bisection helpers, and document-name normalization used by dkcli, gcp-docs-mirror-tools, and spanner-mycli.

Authentication supports API keys (DEVELOPERKNOWLEDGE_API_KEY or GOOGLE_API_KEY) and Application Default Credentials. When CLOUDSDK_CONFIG is set, its ADC file provides both token and quota-project metadata when present. If that optional file is absent, standard ADC discovery continues; other path or read errors are returned.

Partial batch retrieval

BatchGetDocumentsPartial chunks large input lists and bisects document-specific failures while preserving input order and duplicate names. WithDocumentView can request metadata-only results. A fatal batch-level error stops later requests but returns the positional results completed before it.

Document views

GetDocument retrieves one document, and WithDocumentView selects how much of it the API returns. DOCUMENT_VIEW_BASIC omits Content while still reporting ContentLengthBytes and UpdateTime, so size and freshness checks do not have to download full content. The same option applies to BatchGetDocumentsPartial.

Index

Examples

Constants

View Source
const (
	CloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
	DefaultV1BaseURL   = "https://developerknowledge.googleapis.com/v1"
	DefaultHTTPTimeout = time.Minute
	// MaxBatchGetDocuments is the maximum number of document names accepted by
	// documents:batchGet. The live v1 and v1alpha APIs rejected 21 names as
	// INVALID_ARGUMENT on 2026-07-18, despite the how-to page stating 100.
	// Documents are returned in the same order as names.
	MaxBatchGetDocuments = 20
)

Variables

This section is empty.

Functions

func APIKeyFromEnv

func APIKeyFromEnv() string

func CheckResponse

func CheckResponse(resp *http.Response) ([]byte, error)

func DefaultADCCredentialsPath

func DefaultADCCredentialsPath(goos, homeDir, appData string) string

func DefaultCredentialsPath

func DefaultCredentialsPath() string

func IsBisectableDocumentError

func IsBisectableDocumentError(err error) bool

func NewADCHTTPClient

func NewADCHTTPClient(ctx context.Context, cfg AuthConfig) (*http.Client, error)

NewADCHTTPClient constructs an OAuth-authenticated HTTP client. Initial requests are restricted to AuthConfig.AllowedOrigin, which defaults to DefaultV1BaseURL, and redirects must remain on that origin.

func NewAuthenticatedHTTPClient

func NewAuthenticatedHTTPClient(ctx context.Context, cfg AuthConfig) (*http.Client, string, error)

NewAuthenticatedHTTPClient prefers an environment API key unless ADC is required. Initial requests are restricted to AuthConfig.AllowedOrigin, which defaults to DefaultV1BaseURL, and redirects must remain on that origin.

func NormalizeDocName

func NormalizeDocName(name string) string

NormalizeDocName converts a pasted URL or short document path into a Developer Knowledge API resource name (documents/...). Query strings, fragments, and trailing slashes are stripped. URL-like inputs must be hierarchical ASCII HTTP(S) URLs without userinfo; empty or invalid inputs return "".

func ParseRetryAfter

func ParseRetryAfter(resp *http.Response) time.Duration

func SleepContext

func SleepContext(ctx context.Context, wait time.Duration) error

Types

type ADCCredentialsMetadata

type ADCCredentialsMetadata struct {
	Type           string `json:"type"`
	QuotaProjectID string `json:"quota_project_id"`
}

func LoadADCCredentialsMetadata

func LoadADCCredentialsMetadata(credentialsPath func() string) ADCCredentialsMetadata

func ResolveQuotaProjectID

func ResolveQuotaProjectID(credentialsPath func() string) (string, ADCCredentialsMetadata)

type APIError

type APIError struct {
	Code    int
	Status  string
	Message string
}

func (*APIError) Error

func (e *APIError) Error() string

type Answer added in v0.3.1

type Answer struct {
	AnswerText string            `json:"answerText" yaml:"answer_text"`
	Citations  []AnswerCitation  `json:"citations,omitempty" yaml:"citations,omitempty"`
	References []AnswerReference `json:"references,omitempty" yaml:"references,omitempty"`
}

Answer is a generated answer and its supporting citations and references.

type AnswerCitation added in v0.3.1

type AnswerCitation struct {
	// StartIndex is the inclusive UTF-8 byte offset of the cited segment.
	StartIndex int64 `json:"startIndex" yaml:"start_index"`
	// EndIndex is the exclusive UTF-8 byte offset of the cited segment.
	EndIndex int64 `json:"endIndex" yaml:"end_index"`
	// Sources identify entries in Answer.References.
	Sources []CitationSource `json:"sources,omitempty" yaml:"sources,omitempty"`
}

AnswerCitation describes a segment of Answer.AnswerText and its sources.

type AnswerQueryRequest added in v0.3.1

type AnswerQueryRequest struct {
	// Query is the question to answer.
	Query string `json:"query" yaml:"query"`
}

AnswerQueryRequest is the request body for AnswerQuery.

type AnswerQueryResponse added in v0.3.1

type AnswerQueryResponse struct {
	// Answer is nil if the service omits the answer object.
	Answer *Answer `json:"answer,omitempty" yaml:"answer,omitempty"`
}

AnswerQueryResponse is the response from AnswerQuery.

type AnswerReference added in v0.3.1

type AnswerReference struct {
	DocumentReference *DocumentReference `json:"documentReference,omitempty" yaml:"document_reference,omitempty"`
}

AnswerReference represents a source used to generate an answer.

type AuthConfig

type AuthConfig struct {
	Mode    AuthMode
	Timeout time.Duration
	// AllowedOrigin is the hierarchical ASCII HTTP(S) origin accepted by
	// constructor-created clients. It defaults to DefaultV1BaseURL.
	AllowedOrigin string
	// TokenSource overrides ADC discovery. When set, CredentialsPath is ignored.
	TokenSource TokenSourceFunc
	// QuotaProjectID explicitly sets the x-goog-user-project header for ADC
	// clients. It takes precedence over GOOGLE_CLOUD_QUOTA_PROJECT and
	// credentials-file metadata.
	QuotaProjectID string
	// CredentialsPath returns an explicit ADC file path. When set, the path is
	// evaluated once and must be readable; ADC discovery does not fall back.
	CredentialsPath func() string
}

type AuthMode

type AuthMode int
const (
	AuthPreferAPIKey AuthMode = iota
	AuthRequireADC
)

type BatchGetDocumentResult added in v0.3.1

type BatchGetDocumentResult struct {
	Name     string
	Document *Document
	Err      error
}

BatchGetDocumentResult pairs one input occurrence with its outcome. Name is always populated. Document is set for a returned document, and Err is set for a document-specific failure. Both are nil when a fatal error stopped processing or the API omitted the document from a successful response.

type BatchGetOption added in v0.3.1

type BatchGetOption = DocumentOption

BatchGetOption is an alias of DocumentOption. It is retained because BatchGetDocumentsPartial introduced the option type under this name.

type BatchGetResponse

type BatchGetResponse struct {
	Documents []Document `json:"documents" yaml:"documents"`
}

type CitationSource added in v0.3.1

type CitationSource struct {
	ReferenceIndex int64 `json:"referenceIndex" yaml:"reference_index"`
}

CitationSource identifies a supporting entry in Answer.References.

type Client

type Client struct {
	BaseURL       string
	APIKey        string
	HTTPClient    *http.Client
	Limiter       Waiter
	Verbose       bool
	VerboseWriter io.Writer
	// MaxRetries is the number of additional attempts after the first request.
	MaxRetries int
}

func (*Client) AnswerQuery added in v0.3.1

func (c *Client) AnswerQuery(ctx context.Context, req *AnswerQueryRequest) (*AnswerQueryResponse, error)

AnswerQuery answers a natural-language query using Developer Knowledge content. As of google.golang.org/api v0.289.0, the official generated v1 Go client does not expose this GA operation. See issue #1 for the long-term relationship with that client.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	dkapi "github.com/apstndb/developerknowledge-go"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = fmt.Fprint(w, `{"answer":{"answerText":"Use documents:batchGet."}}`)
	}))
	defer server.Close()

	client := &dkapi.Client{BaseURL: server.URL + "/v1", HTTPClient: server.Client()}
	resp, err := client.AnswerQuery(context.Background(), &dkapi.AnswerQueryRequest{
		Query: "How can I fetch multiple documents?",
	})
	if err != nil {
		panic(err)
	}
	if resp.Answer == nil {
		fmt.Println("No answer returned.")
		return
	}
	fmt.Println(resp.Answer.AnswerText)
}
Output:
Use documents:batchGet.

func (*Client) BatchGetDocuments

func (c *Client) BatchGetDocuments(ctx context.Context, names []string) ([]Document, error)

func (*Client) BatchGetDocumentsAll added in v0.2.0

func (c *Client) BatchGetDocumentsAll(ctx context.Context, names []string) ([]Document, error)

BatchGetDocumentsAll fetches documents in chunks of MaxBatchGetDocuments while preserving the order of names. Invalid names fail the whole batch for that chunk.

func (*Client) BatchGetDocumentsPartial added in v0.3.1

func (c *Client) BatchGetDocumentsPartial(
	ctx context.Context,
	names []string,
	opts ...DocumentOption,
) ([]BatchGetDocumentResult, error)

BatchGetDocumentsPartial fetches names in chunks of MaxBatchGetDocuments and bisects document-specific failures. Results preserve input order and duplicates: len(results) equals len(names), and results[i].Name is names[i].

Document-specific errors are stored in the corresponding result and do not make the method return an error. A non-bisectable error stops processing and is returned with all results completed before the failure. If a successful response contains fewer documents than requested, unmatched results remain nil; response documents that match no remaining input occurrence are ignored.

Example
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"

	dkapi "github.com/apstndb/developerknowledge-go"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		names := r.URL.Query()["names"]
		for _, name := range names {
			if name == "documents/example.com/missing" {
				w.WriteHeader(http.StatusNotFound)
				_, _ = w.Write([]byte(
					`{"error":{"code":404,"status":"NOT_FOUND","message":"missing"}}`,
				))
				return
			}
		}

		docs := make([]dkapi.Document, 0, len(names))
		for _, name := range names {
			docs = append(docs, dkapi.Document{
				Name:               name,
				View:               string(dkapi.DocumentViewBasic),
				ContentLengthBytes: 42,
			})
		}
		_ = json.NewEncoder(w).Encode(dkapi.BatchGetResponse{Documents: docs})
	}))
	defer server.Close()

	client := &dkapi.Client{
		BaseURL:    server.URL + "/v1",
		HTTPClient: server.Client(),
	}
	results, err := client.BatchGetDocumentsPartial(
		context.Background(),
		[]string{
			"documents/example.com/guide",
			"documents/example.com/missing",
		},
		dkapi.WithDocumentView(dkapi.DocumentViewBasic),
	)
	if err != nil {
		panic(err)
	}
	for _, result := range results {
		switch {
		case result.Err != nil:
			fmt.Printf("%s: unavailable\n", result.Name)
		case result.Document == nil:
			fmt.Printf("%s: omitted\n", result.Name)
		default:
			fmt.Printf("%s: %d bytes\n", result.Name, result.Document.ContentLengthBytes)
		}
	}

}
Output:
documents/example.com/guide: 42 bytes
documents/example.com/missing: unavailable

func (*Client) DoAPIRequest

func (c *Client) DoAPIRequest(ctx context.Context, method, reqURL string, body []byte, contentType string) ([]byte, error)

DoAPIRequest sends an authenticated API request. reqURL must be absolute and share an origin with BaseURL; redirects to another origin are rejected.

func (*Client) DoGet

func (c *Client) DoGet(ctx context.Context, reqURL string) ([]byte, error)

func (*Client) DoJSONPost

func (c *Client) DoJSONPost(ctx context.Context, reqURL string, body []byte) ([]byte, error)

func (*Client) GetDocument added in v0.3.2

func (c *Client) GetDocument(ctx context.Context, name string, opts ...DocumentOption) (*Document, error)

GetDocument retrieves a single document by resource name, for example "documents/docs.cloud.google.com/storage/docs/creating-buckets". Use NormalizeDocName to convert a pasted URL into that form; a name with a leading slash, a query string, or a fragment is rejected locally.

WithDocumentView requests a metadata-only document. DOCUMENT_VIEW_BASIC omits Content while still reporting ContentLengthBytes, which makes size and freshness checks much cheaper than fetching full content.

Example (MetadataOnly)
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"

	dkapi "github.com/apstndb/developerknowledge-go"
)

// WithDocumentView must stay usable where the v0.3.1 option type is expected.
var _ dkapi.BatchGetOption = dkapi.WithDocumentView(dkapi.DocumentViewBasic)

// BatchGetDocumentsPartial must retain its v0.3.1 source-level method shape.
var _ func(context.Context, []string, ...dkapi.BatchGetOption) ([]dkapi.BatchGetDocumentResult, error) = (&dkapi.Client{}).BatchGetDocumentsPartial

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// The BASIC view answers without the content field.
		_ = json.NewEncoder(w).Encode(dkapi.Document{
			Name:               "documents/example.com/guide",
			URI:                "https://example.com/guide",
			View:               r.URL.Query().Get("view"),
			UpdateTime:         "2026-07-18T00:00:00Z",
			ContentLengthBytes: 31940,
		})
	}))
	defer server.Close()

	client := &dkapi.Client{BaseURL: server.URL + "/v1", HTTPClient: server.Client()}
	doc, err := client.GetDocument(
		context.Background(),
		dkapi.NormalizeDocName("https://example.com/guide"),
		dkapi.WithDocumentView(dkapi.DocumentViewBasic),
	)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s view=%s bytes=%d content=%q\n",
		doc.Name, doc.View, doc.ContentLengthBytes, doc.Content)

}
Output:
documents/example.com/guide view=DOCUMENT_VIEW_BASIC bytes=31940 content=""

type Document

type Document struct {
	Name               string `json:"name" yaml:"name"`
	URI                string `json:"uri" yaml:"uri"`
	Content            string `json:"content,omitempty" yaml:"content,omitempty"`
	Description        string `json:"description,omitempty" yaml:"description,omitempty"`
	DataSource         string `json:"dataSource,omitempty" yaml:"data_source,omitempty"`
	Title              string `json:"title,omitempty" yaml:"title,omitempty"`
	UpdateTime         string `json:"updateTime,omitempty" yaml:"update_time,omitempty"`
	View               string `json:"view,omitempty" yaml:"view,omitempty"`
	ContentLengthBytes int64  `json:"contentLengthBytes,omitempty" yaml:"content_length_bytes,omitempty"`
}

type DocumentChunk

type DocumentChunk struct {
	Parent   string    `json:"parent" yaml:"parent"`
	ID       string    `json:"id" yaml:"id"`
	Content  string    `json:"content" yaml:"content"`
	Document *Document `json:"document,omitempty" yaml:"document,omitempty"`
}

type DocumentOption added in v0.3.2

type DocumentOption func(*documentRequestConfig)

DocumentOption configures a document retrieval request. The same options apply to GetDocument and BatchGetDocumentsPartial.

func WithDocumentView added in v0.3.1

func WithDocumentView(view DocumentView) DocumentOption

WithDocumentView sets the document view for every request the call makes. Without this option, the server default applies: DOCUMENT_VIEW_CONTENT for GetDocument and batchGet.

type DocumentReference added in v0.3.1

type DocumentReference struct {
	// DocumentChunk contains the source chunk. The API leaves its ID field empty.
	DocumentChunk *DocumentChunk `json:"documentChunk,omitempty" yaml:"document_chunk,omitempty"`
}

DocumentReference represents a document source.

type DocumentView added in v0.3.1

type DocumentView string

DocumentView selects how much of each Document the API returns.

const (
	DocumentViewUnspecified DocumentView = "DOCUMENT_VIEW_UNSPECIFIED"
	DocumentViewBasic       DocumentView = "DOCUMENT_VIEW_BASIC"
	DocumentViewFull        DocumentView = "DOCUMENT_VIEW_FULL"
	DocumentViewContent     DocumentView = "DOCUMENT_VIEW_CONTENT"
)

type QuotaProjectTransport

type QuotaProjectTransport struct {
	Base    http.RoundTripper
	Project string
}

func (*QuotaProjectTransport) CloseIdleConnections added in v0.3.0

func (t *QuotaProjectTransport) CloseIdleConnections()

func (*QuotaProjectTransport) RoundTrip

func (t *QuotaProjectTransport) RoundTrip(req *http.Request) (*http.Response, error)

type RateLimitError

type RateLimitError struct {
	RetryAfter time.Duration
}

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

type TokenSourceFunc

type TokenSourceFunc func(context.Context, ...string) (oauth2.TokenSource, error)
var DefaultTokenSource TokenSourceFunc = func(ctx context.Context, scopes ...string) (oauth2.TokenSource, error) {
	return google.DefaultTokenSource(ctx, scopes...)
}

type Waiter

type Waiter interface {
	Wait(context.Context) error
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL