mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-12-18 22:58:45 +08:00
617 lines
18 KiB
Go
617 lines
18 KiB
Go
package cloudflare
|
|
|
|
import (
|
|
"context"
|
|
rand "crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/textproto"
|
|
|
|
"github.com/goccy/go-json"
|
|
)
|
|
|
|
// WorkerBindingType represents a particular type of binding.
|
|
type WorkerBindingType string
|
|
|
|
func (b WorkerBindingType) String() string {
|
|
return string(b)
|
|
}
|
|
|
|
const (
|
|
// WorkerDurableObjectBindingType is the type for Durable Object bindings.
|
|
WorkerDurableObjectBindingType WorkerBindingType = "durable_object_namespace"
|
|
// WorkerInheritBindingType is the type for inherited bindings.
|
|
WorkerInheritBindingType WorkerBindingType = "inherit"
|
|
// WorkerKvNamespaceBindingType is the type for KV Namespace bindings.
|
|
WorkerKvNamespaceBindingType WorkerBindingType = "kv_namespace"
|
|
// WorkerWebAssemblyBindingType is the type for Web Assembly module bindings.
|
|
WorkerWebAssemblyBindingType WorkerBindingType = "wasm_module"
|
|
// WorkerSecretTextBindingType is the type for secret text bindings.
|
|
WorkerSecretTextBindingType WorkerBindingType = "secret_text"
|
|
// WorkerPlainTextBindingType is the type for plain text bindings.
|
|
WorkerPlainTextBindingType WorkerBindingType = "plain_text"
|
|
// WorkerServiceBindingType is the type for service bindings.
|
|
WorkerServiceBindingType WorkerBindingType = "service"
|
|
// WorkerR2BucketBindingType is the type for R2 bucket bindings.
|
|
WorkerR2BucketBindingType WorkerBindingType = "r2_bucket"
|
|
// WorkerAnalyticsEngineBindingType is the type for Analytics Engine dataset bindings.
|
|
WorkerAnalyticsEngineBindingType WorkerBindingType = "analytics_engine"
|
|
// WorkerQueueBindingType is the type for queue bindings.
|
|
WorkerQueueBindingType WorkerBindingType = "queue"
|
|
// DispatchNamespaceBindingType is the type for WFP namespace bindings.
|
|
DispatchNamespaceBindingType WorkerBindingType = "dispatch_namespace"
|
|
// WorkerD1DataseBindingType is for D1 databases.
|
|
WorkerD1DataseBindingType WorkerBindingType = "d1"
|
|
)
|
|
|
|
type ListWorkerBindingsParams struct {
|
|
ScriptName string
|
|
DispatchNamespace *string
|
|
}
|
|
|
|
// WorkerBindingListItem a struct representing an individual binding in a list of bindings.
|
|
type WorkerBindingListItem struct {
|
|
Name string `json:"name"`
|
|
Binding WorkerBinding
|
|
}
|
|
|
|
// WorkerBindingListResponse wrapper struct for API response to worker binding list API call.
|
|
type WorkerBindingListResponse struct {
|
|
Response
|
|
BindingList []WorkerBindingListItem
|
|
}
|
|
|
|
// Workers supports multiple types of bindings, e.g. KV namespaces or WebAssembly modules, and each type
|
|
// of binding will be represented differently in the upload request body. At a high-level, every binding
|
|
// will specify metadata, which is a JSON object with the properties "name" and "type". Some types of bindings
|
|
// will also have additional metadata properties. For example, KV bindings also specify the KV namespace.
|
|
// In addition to the metadata, some binding types may need to include additional data as part of the
|
|
// multipart form. For example, WebAssembly bindings will include the contents of the WebAssembly module.
|
|
|
|
// WorkerBinding is the generic interface implemented by all of
|
|
// the various binding types.
|
|
type WorkerBinding interface {
|
|
Type() WorkerBindingType
|
|
|
|
// serialize is responsible for returning the binding metadata as well as an optionally
|
|
// returning a function that can modify the multipart form body. For example, this is used
|
|
// by WebAssembly bindings to add a new part containing the WebAssembly module contents.
|
|
serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error)
|
|
}
|
|
|
|
// workerBindingMeta is the metadata portion of the binding.
|
|
type workerBindingMeta = map[string]interface{}
|
|
|
|
// workerBindingBodyWriter allows for a binding to add additional parts to the multipart body.
|
|
type workerBindingBodyWriter func(*multipart.Writer) error
|
|
|
|
// WorkerInheritBinding will just persist whatever binding content was previously uploaded.
|
|
type WorkerInheritBinding struct {
|
|
// Optional parameter that allows for renaming a binding without changing
|
|
// its contents. If `OldName` is empty, the binding name will not be changed.
|
|
OldName string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerInheritBinding) Type() WorkerBindingType {
|
|
return WorkerInheritBindingType
|
|
}
|
|
|
|
func (b WorkerInheritBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
meta := workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
}
|
|
|
|
if b.OldName != "" {
|
|
meta["old_name"] = b.OldName
|
|
}
|
|
|
|
return meta, nil, nil
|
|
}
|
|
|
|
// WorkerKvNamespaceBinding is a binding to a Workers KV Namespace.
|
|
//
|
|
// https://developers.cloudflare.com/workers/archive/api/resource-bindings/kv-namespaces/
|
|
type WorkerKvNamespaceBinding struct {
|
|
NamespaceID string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerKvNamespaceBinding) Type() WorkerBindingType {
|
|
return WorkerKvNamespaceBindingType
|
|
}
|
|
|
|
func (b WorkerKvNamespaceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.NamespaceID == "" {
|
|
return nil, nil, fmt.Errorf(`namespace ID for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"namespace_id": b.NamespaceID,
|
|
}, nil, nil
|
|
}
|
|
|
|
// WorkerDurableObjectBinding is a binding to a Workers Durable Object.
|
|
//
|
|
// https://api.cloudflare.com/#durable-objects-namespace-properties
|
|
type WorkerDurableObjectBinding struct {
|
|
ClassName string
|
|
ScriptName string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerDurableObjectBinding) Type() WorkerBindingType {
|
|
return WorkerDurableObjectBindingType
|
|
}
|
|
|
|
func (b WorkerDurableObjectBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.ClassName == "" {
|
|
return nil, nil, fmt.Errorf(`ClassName for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"class_name": b.ClassName,
|
|
"script_name": b.ScriptName,
|
|
}, nil, nil
|
|
}
|
|
|
|
// WorkerWebAssemblyBinding is a binding to a WebAssembly module.
|
|
//
|
|
// https://developers.cloudflare.com/workers/archive/api/resource-bindings/webassembly-modules/
|
|
type WorkerWebAssemblyBinding struct {
|
|
Module io.Reader
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerWebAssemblyBinding) Type() WorkerBindingType {
|
|
return WorkerWebAssemblyBindingType
|
|
}
|
|
|
|
func (b WorkerWebAssemblyBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
partName := getRandomPartName()
|
|
|
|
bodyWriter := func(mpw *multipart.Writer) error {
|
|
var hdr = textproto.MIMEHeader{}
|
|
hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, partName))
|
|
hdr.Set("content-type", "application/wasm")
|
|
pw, err := mpw.CreatePart(hdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(pw, b.Module)
|
|
return err
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"part": partName,
|
|
}, bodyWriter, nil
|
|
}
|
|
|
|
// WorkerPlainTextBinding is a binding to plain text.
|
|
//
|
|
// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-plain-text-binding
|
|
type WorkerPlainTextBinding struct {
|
|
Text string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerPlainTextBinding) Type() WorkerBindingType {
|
|
return WorkerPlainTextBindingType
|
|
}
|
|
|
|
func (b WorkerPlainTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.Text == "" {
|
|
return nil, nil, fmt.Errorf(`text for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"text": b.Text,
|
|
}, nil, nil
|
|
}
|
|
|
|
// WorkerSecretTextBinding is a binding to secret text.
|
|
//
|
|
// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-secret-text-binding
|
|
type WorkerSecretTextBinding struct {
|
|
Text string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerSecretTextBinding) Type() WorkerBindingType {
|
|
return WorkerSecretTextBindingType
|
|
}
|
|
|
|
func (b WorkerSecretTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.Text == "" {
|
|
return nil, nil, fmt.Errorf(`text for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"text": b.Text,
|
|
}, nil, nil
|
|
}
|
|
|
|
type WorkerServiceBinding struct {
|
|
Service string
|
|
Environment *string
|
|
}
|
|
|
|
func (b WorkerServiceBinding) Type() WorkerBindingType {
|
|
return WorkerServiceBindingType
|
|
}
|
|
|
|
func (b WorkerServiceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.Service == "" {
|
|
return nil, nil, fmt.Errorf(`service for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
meta := workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"service": b.Service,
|
|
}
|
|
|
|
if b.Environment != nil {
|
|
meta["environment"] = *b.Environment
|
|
}
|
|
|
|
return meta, nil, nil
|
|
}
|
|
|
|
// WorkerR2BucketBinding is a binding to an R2 bucket.
|
|
type WorkerR2BucketBinding struct {
|
|
BucketName string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerR2BucketBinding) Type() WorkerBindingType {
|
|
return WorkerR2BucketBindingType
|
|
}
|
|
|
|
func (b WorkerR2BucketBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.BucketName == "" {
|
|
return nil, nil, fmt.Errorf(`BucketName for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"bucket_name": b.BucketName,
|
|
}, nil, nil
|
|
}
|
|
|
|
// WorkerAnalyticsEngineBinding is a binding to an Analytics Engine dataset.
|
|
type WorkerAnalyticsEngineBinding struct {
|
|
Dataset string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerAnalyticsEngineBinding) Type() WorkerBindingType {
|
|
return WorkerAnalyticsEngineBindingType
|
|
}
|
|
|
|
func (b WorkerAnalyticsEngineBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.Dataset == "" {
|
|
return nil, nil, fmt.Errorf(`dataset for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"dataset": b.Dataset,
|
|
}, nil, nil
|
|
}
|
|
|
|
// WorkerQueueBinding is a binding to a Workers Queue.
|
|
//
|
|
// https://developers.cloudflare.com/workers/platform/bindings/#queue-bindings
|
|
type WorkerQueueBinding struct {
|
|
Binding string
|
|
Queue string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerQueueBinding) Type() WorkerBindingType {
|
|
return WorkerQueueBindingType
|
|
}
|
|
|
|
func (b WorkerQueueBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.Binding == "" {
|
|
return nil, nil, fmt.Errorf(`binding name for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
if b.Queue == "" {
|
|
return nil, nil, fmt.Errorf(`queue name for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"type": b.Type(),
|
|
"name": b.Binding,
|
|
"queue_name": b.Queue,
|
|
}, nil, nil
|
|
}
|
|
|
|
// DispatchNamespaceBinding is a binding to a Workers for Platforms namespace
|
|
//
|
|
// https://developers.cloudflare.com/workers/platform/bindings/#dispatch-namespace-bindings-workers-for-platforms
|
|
type DispatchNamespaceBinding struct {
|
|
Binding string
|
|
Namespace string
|
|
Outbound *NamespaceOutboundOptions
|
|
}
|
|
|
|
type NamespaceOutboundOptions struct {
|
|
Worker WorkerReference
|
|
Params []OutboundParamSchema
|
|
}
|
|
|
|
type WorkerReference struct {
|
|
Service string
|
|
Environment *string
|
|
}
|
|
|
|
type OutboundParamSchema struct {
|
|
Name string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b DispatchNamespaceBinding) Type() WorkerBindingType {
|
|
return DispatchNamespaceBindingType
|
|
}
|
|
|
|
func (b DispatchNamespaceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.Binding == "" {
|
|
return nil, nil, fmt.Errorf(`binding name for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
if b.Namespace == "" {
|
|
return nil, nil, fmt.Errorf(`namespace name for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
meta := workerBindingMeta{
|
|
"type": b.Type(),
|
|
"name": b.Binding,
|
|
"namespace": b.Namespace,
|
|
}
|
|
|
|
if b.Outbound != nil {
|
|
if b.Outbound.Worker.Service == "" {
|
|
return nil, nil, fmt.Errorf(`outbound options for binding "%s" must have a service name`, bindingName)
|
|
}
|
|
|
|
var params []map[string]interface{}
|
|
for _, param := range b.Outbound.Params {
|
|
params = append(params, map[string]interface{}{
|
|
"name": param.Name,
|
|
})
|
|
}
|
|
|
|
meta["outbound"] = map[string]interface{}{
|
|
"worker": map[string]interface{}{
|
|
"service": b.Outbound.Worker.Service,
|
|
"environment": b.Outbound.Worker.Environment,
|
|
},
|
|
"params": params,
|
|
}
|
|
}
|
|
|
|
return meta, nil, nil
|
|
}
|
|
|
|
// WorkerD1DatabaseBinding is a binding to a D1 instance.
|
|
type WorkerD1DatabaseBinding struct {
|
|
DatabaseID string
|
|
}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b WorkerD1DatabaseBinding) Type() WorkerBindingType {
|
|
return WorkerD1DataseBindingType
|
|
}
|
|
|
|
func (b WorkerD1DatabaseBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
if b.DatabaseID == "" {
|
|
return nil, nil, fmt.Errorf(`database ID for binding "%s" cannot be empty`, bindingName)
|
|
}
|
|
|
|
return workerBindingMeta{
|
|
"name": bindingName,
|
|
"type": b.Type(),
|
|
"id": b.DatabaseID,
|
|
}, nil, nil
|
|
}
|
|
|
|
// UnsafeBinding is for experimental or deprecated bindings, and allows specifying any binding type or property.
|
|
type UnsafeBinding map[string]interface{}
|
|
|
|
// Type returns the type of the binding.
|
|
func (b UnsafeBinding) Type() WorkerBindingType {
|
|
return ""
|
|
}
|
|
|
|
func (b UnsafeBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) {
|
|
b["name"] = bindingName
|
|
return b, nil, nil
|
|
}
|
|
|
|
// Each binding that adds a part to the multipart form body will need
|
|
// a unique part name so we just generate a random 128bit hex string.
|
|
func getRandomPartName() string {
|
|
randBytes := make([]byte, 16)
|
|
rand.Read(randBytes) //nolint:errcheck
|
|
return hex.EncodeToString(randBytes)
|
|
}
|
|
|
|
// ListWorkerBindings returns all the bindings for a particular worker.
|
|
func (api *API) ListWorkerBindings(ctx context.Context, rc *ResourceContainer, params ListWorkerBindingsParams) (WorkerBindingListResponse, error) {
|
|
if params.ScriptName == "" {
|
|
return WorkerBindingListResponse{}, errors.New("script name is required")
|
|
}
|
|
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerBindingListResponse{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerBindingListResponse{}, ErrMissingAccountID
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings", rc.Identifier, params.ScriptName)
|
|
if params.DispatchNamespace != nil && *params.DispatchNamespace != "" {
|
|
uri = fmt.Sprintf("/accounts/%s/workers/dispatch/namespaces/%s/scripts/%s/bindings", rc.Identifier, *params.DispatchNamespace, params.ScriptName)
|
|
}
|
|
|
|
var jsonRes struct {
|
|
Response
|
|
Bindings []workerBindingMeta `json:"result"`
|
|
}
|
|
var r WorkerBindingListResponse
|
|
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
err = json.Unmarshal(res, &jsonRes)
|
|
if err != nil {
|
|
return r, fmt.Errorf("%s: %w", errUnmarshalError, err)
|
|
}
|
|
|
|
r = WorkerBindingListResponse{
|
|
Response: jsonRes.Response,
|
|
BindingList: make([]WorkerBindingListItem, 0, len(jsonRes.Bindings)),
|
|
}
|
|
for _, jsonBinding := range jsonRes.Bindings {
|
|
name, ok := jsonBinding["name"].(string)
|
|
if !ok {
|
|
return r, fmt.Errorf("binding missing name %v", jsonBinding)
|
|
}
|
|
bType, ok := jsonBinding["type"].(string)
|
|
if !ok {
|
|
return r, fmt.Errorf("binding missing type %v", jsonBinding)
|
|
}
|
|
bindingListItem := WorkerBindingListItem{
|
|
Name: name,
|
|
}
|
|
|
|
switch WorkerBindingType(bType) {
|
|
case WorkerDurableObjectBindingType:
|
|
class_name := jsonBinding["class_name"].(string)
|
|
script_name := jsonBinding["script_name"].(string)
|
|
bindingListItem.Binding = WorkerDurableObjectBinding{
|
|
ClassName: class_name,
|
|
ScriptName: script_name,
|
|
}
|
|
case WorkerKvNamespaceBindingType:
|
|
namespaceID := jsonBinding["namespace_id"].(string)
|
|
bindingListItem.Binding = WorkerKvNamespaceBinding{
|
|
NamespaceID: namespaceID,
|
|
}
|
|
case WorkerQueueBindingType:
|
|
queueName := jsonBinding["queue_name"].(string)
|
|
bindingListItem.Binding = WorkerQueueBinding{
|
|
Binding: name,
|
|
Queue: queueName,
|
|
}
|
|
case WorkerWebAssemblyBindingType:
|
|
bindingListItem.Binding = WorkerWebAssemblyBinding{
|
|
Module: &bindingContentReader{
|
|
api: api,
|
|
ctx: ctx,
|
|
accountID: rc.Identifier,
|
|
params: ¶ms,
|
|
bindingName: name,
|
|
},
|
|
}
|
|
case WorkerPlainTextBindingType:
|
|
text := jsonBinding["text"].(string)
|
|
bindingListItem.Binding = WorkerPlainTextBinding{
|
|
Text: text,
|
|
}
|
|
case WorkerServiceBindingType:
|
|
service := jsonBinding["service"].(string)
|
|
environment := jsonBinding["environment"].(string)
|
|
bindingListItem.Binding = WorkerServiceBinding{
|
|
Service: service,
|
|
Environment: &environment,
|
|
}
|
|
case WorkerSecretTextBindingType:
|
|
bindingListItem.Binding = WorkerSecretTextBinding{}
|
|
case WorkerR2BucketBindingType:
|
|
bucketName := jsonBinding["bucket_name"].(string)
|
|
bindingListItem.Binding = WorkerR2BucketBinding{
|
|
BucketName: bucketName,
|
|
}
|
|
case WorkerAnalyticsEngineBindingType:
|
|
dataset := jsonBinding["dataset"].(string)
|
|
bindingListItem.Binding = WorkerAnalyticsEngineBinding{
|
|
Dataset: dataset,
|
|
}
|
|
case WorkerD1DataseBindingType:
|
|
database_id := jsonBinding["database_id"].(string)
|
|
bindingListItem.Binding = WorkerD1DatabaseBinding{
|
|
DatabaseID: database_id,
|
|
}
|
|
default:
|
|
bindingListItem.Binding = WorkerInheritBinding{}
|
|
}
|
|
r.BindingList = append(r.BindingList, bindingListItem)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// bindingContentReader is an io.Reader that will lazily load the
|
|
// raw bytes for a binding from the API when the Read() method
|
|
// is first called. This is only useful for binding types
|
|
// that store raw bytes, like WebAssembly modules.
|
|
type bindingContentReader struct {
|
|
api *API
|
|
accountID string
|
|
params *ListWorkerBindingsParams
|
|
ctx context.Context
|
|
bindingName string
|
|
content []byte
|
|
position int
|
|
}
|
|
|
|
func (b *bindingContentReader) Read(p []byte) (n int, err error) {
|
|
// Lazily load the content when Read() is first called
|
|
if b.content == nil {
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings/%s/content", b.accountID, b.params.ScriptName, b.bindingName)
|
|
res, err := b.api.makeRequestContext(b.ctx, http.MethodGet, uri, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
b.content = res
|
|
}
|
|
|
|
if b.position >= len(b.content) {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
bytesRemaining := len(b.content) - b.position
|
|
bytesToProcess := 0
|
|
if len(p) < bytesRemaining {
|
|
bytesToProcess = len(p)
|
|
} else {
|
|
bytesToProcess = bytesRemaining
|
|
}
|
|
|
|
for i := 0; i < bytesToProcess; i++ {
|
|
p[i] = b.content[b.position]
|
|
b.position = b.position + 1
|
|
}
|
|
|
|
return bytesToProcess, nil
|
|
}
|