shopware

package module
v0.0.0-...-7e1ca8c Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 17 Imported by: 0

README

go-shopware-http-client

CI Go Reference

A standalone Go client for the Shopware Admin API, with a typed Data Abstraction Layer (DAL) on top.

It is self-contained — outside the standard library (which provides the uuid package) it depends only on golang.org/x/sync and github.com/shyim/go-version. Everything application-specific (HTTP transport, instrumentation, SSRF protection, extra headers, token persistence) is injected rather than imported, so the package can be vendored or split into its own module without dragging the rest of an application along.

import "github.com/shopwareLabs/go-shopware-http-client"

The import path ends in go-shopware-http-client, but the package is named shopware, so it is used as shopware.NewClient(...).

Quick start

ctx := context.Background()

client := shopware.NewClient(shopware.Config{
	BaseURL:     "https://my-shop.example.com",
	Credentials: shopware.NewIntegrationCredentials("CLIENT_ID", "CLIENT_SECRET"),
})

type Product struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

products := shopware.NewRepository[Product](client, "product")

result, err := products.Search(ctx,
	shopware.NewCriteria().
		SetLimit(10).
		AddFilter(shopware.Equals("active", true)),
)
if err != nil {
	panic(err)
}

fmt.Printf("found %d products\n", result.Total)
for _, p := range result.Data {
	fmt.Println(p.ID, p.Name)
}

The client authenticates lazily on the first request, caches the OAuth token, and refreshes it transparently — you never manage tokens by hand.

Documentation

Full guides live in docs/:

Runnable, compile-checked snippets are in example_test.go. A full PKCE browser-login CLI demo lives in examples/pkce-cli.

Documentation

Overview

Package shopware provides a standalone HTTP client and Data Abstraction Layer (DAL) helpers for talking to the Shopware Admin API.

The package is self-contained: it depends only on the standard library and golang.org/x/sync/singleflight, so it can be vendored or extracted into its own module without dragging application-specific code along. Anything application-specific (custom HTTP transports, extra request headers, SSRF protection) is injected via Config rather than imported.

The high-level entry points are:

  • Client — authenticated HTTP client with OAuth token caching.
  • Criteria — fluent builder for DAL search/aggregation payloads.
  • EntityRepository — typed CRUD/search over a single entity.

Index

Examples

Constants

View Source
const (
	// DefaultTimeout is applied to the internal http.Client when the caller
	// does not provide one.
	DefaultTimeout = 30 * time.Second

	// DefaultUserAgent is sent as the User-Agent header on every request
	// (token requests included) unless overridden via Config.UserAgent,
	// Config.Headers, or per-request extraHeaders.
	DefaultUserAgent = "go-shopware-http-client"
)
View Source
const (
	DefaultSystemLanguageID              = "2fbb5fe2e29a4d70aa5854ce7ce3e20b"
	DefaultLiveVersionID                 = "0fa91ce3e96a4bc2be4bd9ce752c3425"
	DefaultSystemCurrencyID              = "b7d2554b0ce847cd82f3ac9bd1c0dfca"
	DefaultSalesChannelTypeAPI           = "f183ee5650cf4bdb8a774337575067a6"
	DefaultSalesChannelTypeStorefront    = "8a243080f92e4c719546314b577cf82b"
	DefaultSalesChannelTypeProductExport = "ed535e5722134ac1aa6524f73e26881b"
)

Well-known default IDs used throughout Shopware.

Variables

This section is empty.

Functions

func DefaultFileTokenStorageDir

func DefaultFileTokenStorageDir() (string, error)

DefaultFileTokenStorageDir returns a user-private default directory for file token storage: <os.UserCacheDir()>/go-shopware-http-client/tokens. Callers typically pass it to NewFileTokenStorage.

func UUID

func UUID() string

UUID returns a new Shopware-style UUID: a random UUID with the dashes stripped.

Types

type APIError

type APIError struct {
	StatusCode int
	Body       string
	Detail     string
}

APIError describes a non-2xx response from the API. When the body matches the Shopware errors envelope, Detail holds the first error's detail message.

Example
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	ctx := context.Background()
	client := shopware.NewClient(shopware.Config{BaseURL: "https://x", ClientID: "i", ClientSecret: "s"})

	_, err := client.Get(ctx, "/search/does-not-exist")
	var apiErr *shopware.APIError
	if errors.As(err, &apiErr) {
		fmt.Println(apiErr.StatusCode, apiErr.Detail)
	}
}

func (*APIError) Error

func (e *APIError) Error() string

type Aggregation

type Aggregation map[string]any

Aggregation is a single DAL aggregation. Construct with the helpers (TermsAggregation, SumAggregation, ...).

func AvgAggregation

func AvgAggregation(name, field string) Aggregation

AvgAggregation computes the average of field.

func CountAggregation

func CountAggregation(name, field string) Aggregation

CountAggregation counts non-null values of field.

func EntityAggregation

func EntityAggregation(name, field, definition string) Aggregation

EntityAggregation buckets documents by an associated entity.

func FilterAggregation

func FilterAggregation(name string, filter []Filter, nested Aggregation) Aggregation

FilterAggregation applies filters before computing the nested aggregation.

func HistogramAggregation

func HistogramAggregation(name, field, interval, format string, nested Aggregation, timeZone string) Aggregation

HistogramAggregation buckets documents into date intervals. Empty/nil optional arguments are omitted.

func MaxAggregation

func MaxAggregation(name, field string) Aggregation

MaxAggregation computes the maximum of field.

func MinAggregation

func MinAggregation(name, field string) Aggregation

MinAggregation computes the minimum of field.

func StatsAggregation

func StatsAggregation(name, field string) Aggregation

StatsAggregation computes sum/max/min/avg/count of field at once.

func SumAggregation

func SumAggregation(name, field string) Aggregation

SumAggregation computes the sum of field.

func TermsAggregation

func TermsAggregation(name, field string, limit *int, sort *Sorting, nested Aggregation) Aggregation

TermsAggregation buckets documents by the distinct values of field. A nil nested aggregation or sorting is omitted.

type AggregationResults

type AggregationResults map[string]jsontext.Value

AggregationResults is the decoded "aggregations" object: a map from each aggregation's name to its raw result. Decode a named entry with the Get* helpers, or with Decode for a custom type.

func (AggregationResults) Decode

func (a AggregationResults) Decode(name string, target any) error

Decode unmarshals the named aggregation into target. It returns an error if no aggregation with that name exists.

func (AggregationResults) GetAvg

func (a AggregationResults) GetAvg(name string) (*AvgResult, error)

GetAvg decodes the named aggregation as an avg result.

func (AggregationResults) GetCount

func (a AggregationResults) GetCount(name string) (*CountResult, error)

GetCount decodes the named aggregation as a count result.

func (AggregationResults) GetDateHistogram

func (a AggregationResults) GetDateHistogram(name string) (*DateHistogramResult, error)

GetDateHistogram decodes the named aggregation as a date histogram result.

func (AggregationResults) GetEntity

func (a AggregationResults) GetEntity(name string) (*EntityResult, error)

GetEntity decodes the named aggregation as an entity result.

func (AggregationResults) GetMax

func (a AggregationResults) GetMax(name string) (*MaxResult, error)

GetMax decodes the named aggregation as a max result.

func (AggregationResults) GetMin

func (a AggregationResults) GetMin(name string) (*MinResult, error)

GetMin decodes the named aggregation as a min result.

func (AggregationResults) GetStats

func (a AggregationResults) GetStats(name string) (*StatsResult, error)

GetStats decodes the named aggregation as a stats result.

func (AggregationResults) GetSum

func (a AggregationResults) GetSum(name string) (*SumResult, error)

GetSum decodes the named aggregation as a sum result.

func (AggregationResults) GetTerms

func (a AggregationResults) GetTerms(name string) (*TermsResult, error)

GetTerms decodes the named aggregation as a terms result.

func (AggregationResults) Has

func (a AggregationResults) Has(name string) bool

Has reports whether an aggregation with the given name is present.

func (AggregationResults) Names

func (a AggregationResults) Names() []string

Names returns the names of all aggregations present.

type AvgResult

type AvgResult struct {
	Name string  `json:"name"`
	Avg  float64 `json:"avg"`
}

AvgResult is the result of an avg aggregation.

type Bucket

type Bucket struct {
	Key   string `json:"key"`
	Count int    `json:"count"`

	// Nested is the sub-aggregation result merged into the bucket under its
	// own name. It is nil when the bucket has no nested aggregation.
	Nested jsontext.Value `json:"-"`
}

Bucket is one bucket of a terms or histogram aggregation. Key identifies the bucket, Count is the number of documents in it, and Nested holds the nested sub-aggregation result (if the criteria requested one) keyed by its name — decode it with NestedAs.

func (*Bucket) NestedAs

func (b *Bucket) NestedAs(target any) error

NestedAs decodes a bucket's nested sub-aggregation result into target.

func (*Bucket) UnmarshalJSON

func (b *Bucket) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the fixed bucket fields and captures any extra key as the nested aggregation result.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is an authenticated HTTP client for the Shopware Admin API. It caches the OAuth token, collapses concurrent token fetches into a single request, and transparently retries once on a 401 with a fresh token.

A Client is safe for concurrent use.

func NewClient

func NewClient(config Config) *Client

NewClient creates a new Client from the given Config.

Example (CustomHTTPClientAndStorage)
package main

import (
	"net/http"
	"time"

	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	_ = shopware.NewClient(shopware.Config{
		BaseURL:      "https://my-shop.example.com",
		ClientID:     "CLIENT_ID",
		ClientSecret: "CLIENT_SECRET",
		HTTPClient:   &http.Client{Timeout: 30 * time.Second},
		TokenStorage: shopware.NewInMemoryTokenStorage(),
		Headers:      map[string]string{"x-internal-proxy-token": "secret"},
	})
}
Example (FileTokenStorage)
package main

import (
	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	dir, err := shopware.DefaultFileTokenStorageDir()
	if err != nil {
		return
	}
	store, err := shopware.NewFileTokenStorage(dir)
	if err != nil {
		return
	}

	shopURL := "https://my-shop.example.com"
	_ = shopware.NewClient(shopware.Config{
		BaseURL:      shopURL,
		ClientID:     "CLIENT_ID",
		ClientSecret: "CLIENT_SECRET",
		// Scope by shop so one shared directory stays correct if the same
		// principal is ever used against multiple shops.
		TokenStorage: shopware.NewScopedTokenStorage(store, shopURL),
	})
}
Example (Integration)
package main

import (
	"context"

	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	client := shopware.NewClient(shopware.Config{
		BaseURL:     "https://my-shop.example.com",
		Credentials: shopware.NewIntegrationCredentials("CLIENT_ID", "CLIENT_SECRET"),
	})
	_ = client.Authenticate(context.Background())
}
Example (Password)
package main

import (
	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	_ = shopware.NewClient(shopware.Config{
		BaseURL:     "https://my-shop.example.com",
		Credentials: shopware.NewPasswordCredentials("admin", "shopware"),
	})
}
Example (PkceLogin)
package main

import (
	"context"
	"fmt"

	"github.com/shopwareLabs/go-shopware-http-client"
	"github.com/shopwareLabs/go-shopware-http-client/pkce"
)

type exampleProduct struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	ctx := context.Background()

	store, err := pkce.NewFileStore("/path/to/sessions")
	if err != nil {
		return
	}

	// Reuses the saved session for the shop, or opens the browser to log in.
	// Every token change, including rotated refresh tokens, is saved to store.
	client, err := pkce.NewClient(ctx, pkce.Config{
		BaseURL: "https://my-shop.example.com",
	}, store)
	if err != nil {
		return
	}

	products := shopware.NewRepository[exampleProduct](client, "product")
	result, err := products.Search(ctx, shopware.NewCriteria().SetLimit(10))
	if err != nil {
		return
	}
	fmt.Println(result.Total)
}

func (*Client) AccessToken

func (c *Client) AccessToken(ctx context.Context) (string, error)

AccessToken returns a valid OAuth access token, fetching or refreshing it when the cached one is missing or about to expire. Use it to hand the token to external tools (curl, a browser session, ...); regular requests through the Client authenticate on their own.

func (*Client) Authenticate

func (c *Client) Authenticate(ctx context.Context) error

Authenticate verifies the credentials by fetching an access token. It returns nil on success. The context bounds the token request.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, path string, body any) (*Response, error)

Delete makes a DELETE request to /api+path with an optional JSON body.

func (*Client) Get

func (c *Client) Get(ctx context.Context, path string) (*Response, error)

Get makes a GET request to /api+path.

func (*Client) Info

func (c *Client) Info(ctx context.Context) (*Info, error)

Info returns the shop's /api/_info/config, fetching it once and caching the result for the lifetime of the Client. Concurrent callers share a single fetch.

func (*Client) Patch

func (c *Client) Patch(ctx context.Context, path string, body any) (*Response, error)

Patch makes a PATCH request to /api+path with a JSON body.

func (*Client) Post

func (c *Client) Post(ctx context.Context, path string, body any) (*Response, error)

Post makes a POST request to /api+path with a JSON body.

func (*Client) Put

func (c *Client) Put(ctx context.Context, path string, body any) (*Response, error)

Put makes a PUT request to /api+path with a JSON body.

func (*Client) Request

func (c *Client) Request(ctx context.Context, method, path string, body any, extraHeaders map[string]string) (*Response, error)

Request makes an authenticated request to /api+path with a JSON body. extraHeaders are merged on top of the per-request defaults (e.g. DAL context headers).

func (*Client) RequestRaw

func (c *Client) RequestRaw(ctx context.Context, method, path string, bodyFactory func() (io.Reader, error), extraHeaders map[string]string) (*Response, error)

RequestRaw makes an authenticated request to /api+path sending the body produced by bodyFactory verbatim, without JSON marshaling. The caller sets the Content-Type (and any other headers) via extraHeaders. Use it for file uploads (multipart/form-data) or other non-JSON payloads.

bodyFactory is a factory, not a reader, because the request may be retried once after a 401 — the body must be reproducible. Return (nil, nil) for an empty body.

func (*Client) SetAccessToken

func (c *Client) SetAccessToken(ctx context.Context, token string, expiry time.Time) error

SetAccessToken seeds the token cache with a pre-obtained access token and its expiry. Use it after an interactive PKCE login (via the pkce sub-package) to avoid an immediate refresh_token round-trip.

func (*Client) Version

func (c *Client) Version(ctx context.Context) (string, error)

Version returns the shop's Shopware version (e.g. "6.7.0.0"). It is read from the cached Info, so /api/_info/config is fetched at most once. Endpoint helpers use it to gate version-specific behavior.

type Config

type Config struct {
	// BaseURL is the shop URL without a trailing "/api" (e.g.
	// "https://shop.example.com"). A trailing slash is trimmed.
	BaseURL string

	// Credentials selects the OAuth grant used to authenticate. Use
	// NewIntegrationCredentials (client_credentials) or NewPasswordCredentials
	// (admin user login), or supply a custom Credentials implementation.
	//
	// If nil, ClientID/ClientSecret are used as IntegrationCredentials.
	Credentials Credentials

	// ClientID and ClientSecret are a shorthand for integration credentials and
	// are only consulted when Credentials is nil.
	ClientID     string
	ClientSecret string

	// HTTPClient performs the actual requests. If nil, a default client with
	// DefaultTimeout is used. Inject your own to add instrumentation, SSRF
	// protection, or custom transports.
	HTTPClient *http.Client

	// Headers are attached to every request (token requests included). Use this
	// for deployment-specific headers such as a reverse-proxy auth token.
	// A "User-Agent" entry here overrides UserAgent/DefaultUserAgent.
	Headers map[string]string

	// UserAgent overrides DefaultUserAgent. If empty, DefaultUserAgent is
	// sent. It can still be overridden per deployment via Headers or
	// per request via extraHeaders (Request/RequestRaw).
	UserAgent string

	// TokenStorage caches OAuth tokens. If nil, a process-local
	// InMemoryTokenStorage is used, caching the token for the lifetime of the
	// Client. Provide a distributed store (Redis, ...) to share tokens across
	// instances, NewFileTokenStorage() to persist tokens on disk across
	// restarts and processes, or NewNoOpTokenStorage() to disable caching.
	TokenStorage TokenStorage

	// TokenStorageKey is the key under which this Client's token is stored. If
	// empty, a key derived from the credentials' identity is used. Set this when
	// several Clients share a TokenStorage but must not share tokens (e.g. the
	// same principal against different shops).
	TokenStorageKey string
}

Config configures a Client. Only BaseURL and the OAuth credentials are required; everything else has sensible defaults.

type CountResult

type CountResult struct {
	Name  string `json:"name"`
	Count int    `json:"count"`
}

CountResult is the result of a count aggregation.

type Credentials

type Credentials interface {
	// contains filtered or unexported methods
}

Credentials produces the OAuth token request for a Client. The Shopware Admin API supports two grant types, each modeled by an implementation here:

  • IntegrationCredentials — client_credentials grant (integration / app).
  • PasswordCredentials — password grant (admin user login).

Custom implementations may supply any other body the /api/oauth/token endpoint accepts.

type Criteria

type Criteria struct {
	// contains filtered or unexported fields
}

Criteria is a fluent builder for Shopware DAL search and aggregation payloads. The zero value is not ready for use; create one with NewCriteria.

All mutating methods return the receiver so calls can be chained:

c := NewCriteria().
	SetLimit(10).
	AddFilter(Equals("active", true)).
	AddSorting(Sort("createdAt", "DESC")).
	AddAssociation("media")
Example (NestedAssociations)
package main

import (
	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	c := shopware.NewCriteria()
	c.AddAssociation("categories")
	c.GetAssociation("manufacturer.media").
		AddFilter(shopware.Equals("private", false))
	_ = c
}

func NewCriteria

func NewCriteria(ids ...string) *Criteria

NewCriteria creates an empty Criteria, optionally pre-filled with ids.

func (*Criteria) AddAggregation

func (c *Criteria) AddAggregation(aggregation Aggregation) *Criteria

AddAggregation appends an aggregation.

func (*Criteria) AddAssociation

func (c *Criteria) AddAssociation(path string) *Criteria

AddAssociation ensures a sub-criteria exists for each segment of the dot-separated path and returns the receiver for chaining.

func (*Criteria) AddFields

func (c *Criteria) AddFields(fields ...string) *Criteria

AddFields restricts the result to partial fields.

func (*Criteria) AddFilter

func (c *Criteria) AddFilter(filter Filter) *Criteria

AddFilter appends a filter applied to both documents and aggregations.

func (*Criteria) AddGrouping

func (c *Criteria) AddGrouping(field string) *Criteria

AddGrouping groups the result by the given field.

func (*Criteria) AddIncludes

func (c *Criteria) AddIncludes(includes map[string][]string) *Criteria

AddIncludes merges the given include map (entity name -> fields) into the criteria's includes.

func (*Criteria) AddPostFilter

func (c *Criteria) AddPostFilter(filter Filter) *Criteria

AddPostFilter appends a filter applied to documents but not aggregations.

func (*Criteria) AddQuery

func (c *Criteria) AddQuery(filter Filter, score float64, scoreField ...string) *Criteria

AddQuery appends a scored query for relevance ranking.

func (*Criteria) AddSorting

func (c *Criteria) AddSorting(sorting Sorting) *Criteria

AddSorting appends a sort instruction.

func (*Criteria) GetAssociation

func (c *Criteria) GetAssociation(path string) *Criteria

GetAssociation ensures a sub-criteria exists for each segment of the dot-separated path and returns the Criteria of the last segment, so it can be further refined (filters, sortings, nested associations).

func (*Criteria) GetLimit

func (c *Criteria) GetLimit() int

GetLimit returns the configured limit, or 0 if unset.

func (*Criteria) GetPage

func (c *Criteria) GetPage() int

GetPage returns the configured page, or 0 if unset.

func (*Criteria) HasAssociation

func (c *Criteria) HasAssociation(name string) bool

HasAssociation reports whether a direct association with the given name exists.

func (*Criteria) MarshalJSON

func (c *Criteria) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler so a Criteria can be passed directly as a request body.

func (*Criteria) ResetSorting

func (c *Criteria) ResetSorting() *Criteria

ResetSorting removes all sort instructions.

func (*Criteria) SetIDs

func (c *Criteria) SetIDs(ids []string) *Criteria

SetIDs replaces the id filter.

func (*Criteria) SetLimit

func (c *Criteria) SetLimit(limit int) *Criteria

SetLimit sets the maximum number of results.

func (*Criteria) SetPage

func (c *Criteria) SetPage(page int) *Criteria

SetPage sets the 1-based result page.

func (*Criteria) SetTerm

func (c *Criteria) SetTerm(term string) *Criteria

SetTerm sets a full-text search term.

func (*Criteria) SetTitle

func (c *Criteria) SetTitle(title string) *Criteria

SetTitle sets a title shown in the search request URL, useful for debugging.

func (*Criteria) SetTotalCountMode

func (c *Criteria) SetTotalCountMode(mode TotalCountMode) *Criteria

SetTotalCountMode configures how the total count is computed.

func (*Criteria) ToPayload

func (c *Criteria) ToPayload() map[string]any

ToPayload renders the criteria into the request body expected by the DAL search endpoints.

type DateHistogramResult

type DateHistogramResult struct {
	Name    string   `json:"name"`
	Buckets []Bucket `json:"buckets"`
}

DateHistogramResult is the result of a histogram (date histogram) aggregation. Bucket keys are formatted timestamps (e.g. "2026-06-01 00:00:00").

type EntityRepository

type EntityRepository[T any] struct {
	// contains filtered or unexported fields
}

EntityRepository provides typed search and write access to a single entity. Create one with NewRepository:

products := NewRepository[Product](client, "product")
res, err := products.Search(ctx, NewCriteria().SetLimit(10))

func NewRepository

func NewRepository[T any](client *Client, entityName string) *EntityRepository[T]

NewRepository returns a repository for the given entity (e.g. "product", "sales_channel"). The entity name uses snake_case as in the DAL.

func (*EntityRepository[T]) Aggregate

func (r *EntityRepository[T]) Aggregate(ctx context.Context, criteria *Criteria, opts ...RequestOption) (AggregationResults, error)

Aggregate executes the criteria for its aggregations only, forcing limit=1 so no documents are loaded. It returns the aggregations as an AggregationResults map; decode a named entry with its Get* helpers (GetTerms, GetStats, ...).

For a fully custom result shape, use the generic AggregateAs instead.

Example
package main

import (
	"context"
	"fmt"

	"github.com/shopwareLabs/go-shopware-http-client"
)

type exampleProduct struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	ctx := context.Background()
	client := shopware.NewClient(shopware.Config{BaseURL: "https://x", ClientID: "i", ClientSecret: "s"})
	products := shopware.NewRepository[exampleProduct](client, "product")

	aggs, err := products.Aggregate(ctx,
		shopware.NewCriteria().
			AddAggregation(shopware.TermsAggregation("by_active", "active", nil, nil, nil)).
			AddAggregation(shopware.StatsAggregation("price_stats", "price")))
	if err != nil {
		return
	}

	terms, _ := aggs.GetTerms("by_active")
	for _, b := range terms.Buckets {
		fmt.Println(b.Key, b.Count)
	}

	stats, _ := aggs.GetStats("price_stats")
	fmt.Println(stats.Min.String(), stats.Avg)
}

func (*EntityRepository[T]) AggregateAs

func (r *EntityRepository[T]) AggregateAs[A any](ctx context.Context, criteria *Criteria, opts ...RequestOption) (*A, error)

AggregateAs executes the criteria for its aggregations only and decodes the aggregation payload into A. The shape of A depends on the aggregations added to the criteria, not on the entity, so the type parameter lives on the method instead of the repository:

type Aggs struct {
	ByActive struct {
		Buckets []struct {
			Key   string `json:"key"`
			Count int    `json:"count"`
		} `json:"buckets"`
	} `json:"by_active"`
}
aggs, err := products.AggregateAs[Aggs](ctx, criteria)
Example
package main

import (
	"context"
	"fmt"

	"github.com/shopwareLabs/go-shopware-http-client"
)

type exampleProduct struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	ctx := context.Background()
	client := shopware.NewClient(shopware.Config{BaseURL: "https://x", ClientID: "i", ClientSecret: "s"})
	products := shopware.NewRepository[exampleProduct](client, "product")

	type aggs struct {
		ByActive shopware.TermsResult `json:"by_active"`
	}

	got, err := products.AggregateAs[aggs](ctx,
		shopware.NewCriteria().AddAggregation(shopware.TermsAggregation("by_active", "active", nil, nil, nil)))
	if err != nil {
		return
	}
	fmt.Println(len(got.ByActive.Buckets))
}

func (*EntityRepository[T]) Delete

func (r *EntityRepository[T]) Delete(ctx context.Context, payload []map[string]any, opts ...RequestOption) error

Delete removes the given entities. Each element typically carries at least an "id" field.

func (*EntityRepository[T]) DeleteByFilters

func (r *EntityRepository[T]) DeleteByFilters(ctx context.Context, filters []Filter, opts ...RequestOption) error

DeleteByFilters removes all entities matching the given filters.

func (*EntityRepository[T]) Search

func (r *EntityRepository[T]) Search(ctx context.Context, criteria *Criteria, opts ...RequestOption) (*SearchResult[T], error)

Search executes the criteria and returns the matching entities.

Example
package main

import (
	"context"
	"fmt"

	"github.com/shopwareLabs/go-shopware-http-client"
)

type exampleProduct struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	ctx := context.Background()
	client := shopware.NewClient(shopware.Config{
		BaseURL:     "https://my-shop.example.com",
		Credentials: shopware.NewIntegrationCredentials("id", "secret"),
	})

	products := shopware.NewRepository[exampleProduct](client, "product")

	result, err := products.Search(ctx,
		shopware.NewCriteria().
			SetLimit(10).
			AddFilter(shopware.Equals("active", true)).
			AddSorting(shopware.Sort("createdAt", "DESC")),
	)
	if err != nil {
		return
	}
	fmt.Println(result.Total, result.First())
}

func (*EntityRepository[T]) SearchIDs

func (r *EntityRepository[T]) SearchIDs(ctx context.Context, criteria *Criteria, opts ...RequestOption) ([]string, error)

SearchIDs executes the criteria and returns only the matching entity IDs.

This is the convenience form for entities with a single-column primary key (the common case), where /search-ids returns a flat list of id strings. For mapping entities (m:n join tables such as product_category), whose primary key is composite, /search-ids returns objects instead — decode those into a struct with SearchIDsAs.

func (*EntityRepository[T]) SearchIDsAs

func (r *EntityRepository[T]) SearchIDsAs[ID any](ctx context.Context, criteria *Criteria, opts ...RequestOption) ([]ID, error)

SearchIDsAs executes the criteria and returns the matching primary keys decoded as ID. Use it for mapping entities whose composite key /search-ids returns as objects:

type ProductCategory struct {
	ProductID  string `json:"productId"`
	CategoryID string `json:"categoryId"`
}
pairs, err := mapping.SearchIDsAs[ProductCategory](ctx, criteria)

For single-column keys, ID is string and SearchIDs is the shorter spelling.

Example
package main

import (
	"context"
	"fmt"

	"github.com/shopwareLabs/go-shopware-http-client"
)

func main() {
	ctx := context.Background()
	client := shopware.NewClient(shopware.Config{BaseURL: "https://x", ClientID: "i", ClientSecret: "s"})

	type productCategory struct {
		ProductID  string `json:"productId"`
		CategoryID string `json:"categoryId"`
	}
	mapping := shopware.NewRepository[productCategory](client, "product_category")

	pairs, err := mapping.SearchIDsAs[productCategory](ctx, shopware.NewCriteria())
	if err != nil {
		return
	}
	fmt.Println(len(pairs))
}

func (*EntityRepository[T]) Upsert

func (r *EntityRepository[T]) Upsert(ctx context.Context, payload []T, opts ...RequestOption) error

Upsert creates or updates the given entities in a single sync operation.

Example
package main

import (
	"context"

	"github.com/shopwareLabs/go-shopware-http-client"
)

type exampleProduct struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	ctx := context.Background()
	client := shopware.NewClient(shopware.Config{BaseURL: "https://x", ClientID: "i", ClientSecret: "s"})
	products := shopware.NewRepository[exampleProduct](client, "product")

	_ = products.Upsert(ctx, []exampleProduct{
		{ID: shopware.UUID(), Name: "New Product"},
	})

	_ = products.DeleteByFilters(ctx, []shopware.Filter{
		shopware.Equals("active", false),
	})
}

type EntityResult

type EntityResult struct {
	Name     string           `json:"name"`
	Entities []jsontext.Value `json:"entities"`
}

EntityResult is the result of an entity aggregation. Entities holds the raw entity payloads; decode them into your entity type.

type FileTokenStorage

type FileTokenStorage struct {
	// contains filtered or unexported fields
}

FileTokenStorage is a TokenStorage that persists OAuth tokens on disk, one file per storage key. The filename is the hex-encoded SHA-256 hash of the key (plus a ".json" suffix), so neither the client ID, username, nor any other key material ever appears in the directory listing, and keys with characters that are unsafe for filenames (or path separators) cannot escape the directory.

Because the key is hashed, distinct principals never share a file — the same guarantee InMemoryTokenStorage gives. But note the default storage key only identifies the principal (e.g. "integration:<client-id>"), not the shop. If a single directory is shared by clients talking to *different* shops with the same principal, scope each client with NewScopedTokenStorage (or set Config.TokenStorageKey explicitly); otherwise they will share one file and one token.

Tokens survive process restarts and are shared between processes on the same machine. Writes are atomic (write-temp-then-rename) and files are created with mode 0600. The directory is created with mode 0700 when missing. Like all TokenStorage implementations, expired tokens are treated as absent.

A FileTokenStorage is safe for concurrent use.

func NewFileTokenStorage

func NewFileTokenStorage(dir string) (*FileTokenStorage, error)

NewFileTokenStorage creates a FileTokenStorage rooted at dir, creating the directory (with mode 0700) when it does not exist. It returns an error when dir is empty or the directory cannot be created.

func (*FileTokenStorage) Delete

func (s *FileTokenStorage) Delete(ctx context.Context, key string) error

Delete removes the token for key. A missing file is not an error.

func (*FileTokenStorage) Dir

func (s *FileTokenStorage) Dir() string

Dir returns the directory tokens are stored in.

func (*FileTokenStorage) Get

func (s *FileTokenStorage) Get(ctx context.Context, key string) (string, time.Time, error)

Get returns the cached token for key, or an empty token when no valid (unexpired) token exists. Missing, expired, and unreadable/corrupt files all report a miss; corrupt and expired files are removed on a best-effort basis so the next request fetches (and stores) a fresh token.

func (*FileTokenStorage) PathForKey

func (s *FileTokenStorage) PathForKey(key string) string

PathForKey returns the file backing key. It is exposed for debugging and tooling (e.g. listing or pruning cached tokens); callers should otherwise treat the layout as opaque.

func (*FileTokenStorage) Set

func (s *FileTokenStorage) Set(ctx context.Context, key, token string, expiry time.Time) error

Set stores token for key with its expiry, atomically and with mode 0600.

type Filter

type Filter map[string]any

Filter is a single DAL filter (equals, range, multi, ...). Construct filters with the package-level helpers (Equals, EqualsAny, Contains, Range, ...).

func Contains

func Contains(field, value string) Filter

Contains filters documents where field LIKE %value%.

func Equals

func Equals(field string, value any) Filter

Equals filters documents where field == value.

func EqualsAny

func EqualsAny(field string, value []any) Filter

EqualsAny filters documents where field is one of value.

func Multi

func Multi(operator string, queries []Filter) Filter

Multi combines the given queries with the operator ("AND"/"OR").

func Not

func Not(operator string, queries []Filter) Filter

Not negates the given queries combined with the operator ("AND"/"OR").

func Prefix

func Prefix(field, value string) Filter

Prefix filters documents where field LIKE value%.

func Range

func Range(field string, parameters map[string]any) Filter

Range filters documents where field falls within the given bounds. Valid parameter keys are "gt", "gte", "lt", "lte".

func Suffix

func Suffix(field, value string) Filter

Suffix filters documents where field LIKE %value.

type InMemoryTokenStorage

type InMemoryTokenStorage struct {
	// contains filtered or unexported fields
}

InMemoryTokenStorage is a process-local TokenStorage backed by a map. It is the default for the Client. Tokens are lost on restart and not shared across processes; for multi-instance deployments back the Client with a distributed store instead.

func NewInMemoryTokenStorage

func NewInMemoryTokenStorage() *InMemoryTokenStorage

NewInMemoryTokenStorage creates an empty in-memory token storage.

func (*InMemoryTokenStorage) Delete

func (s *InMemoryTokenStorage) Delete(_ context.Context, key string) error

Delete removes the token for key.

func (*InMemoryTokenStorage) Get

Get returns the cached token for key, or an empty token if absent or expired.

func (*InMemoryTokenStorage) Set

func (s *InMemoryTokenStorage) Set(_ context.Context, key, token string, expiry time.Time) error

Set stores token for key with its expiry.

type Info

type Info struct {
	Version         string                `json:"version"`
	VersionRevision string                `json:"versionRevision"`
	Bundles         map[string]InfoBundle `json:"bundles"`
}

Info is the payload of GET /api/_info/config.

func (*Info) HasBundle

func (i *Info) HasBundle(name string) bool

HasBundle reports whether the shop has the given administration bundle.

type InfoBundle

type InfoBundle struct {
	CSS []string `json:"css"`
	JS  []string `json:"js"`
}

InfoBundle lists the administration assets a bundle ships.

type IntegrationCredentials

type IntegrationCredentials struct {
	ClientID     string
	ClientSecret string
}

IntegrationCredentials authenticates as a Shopware integration using the client_credentials grant.

func NewIntegrationCredentials

func NewIntegrationCredentials(clientID, clientSecret string) IntegrationCredentials

NewIntegrationCredentials creates IntegrationCredentials.

type MaxResult

type MaxResult struct {
	Name string  `json:"name"`
	Max  Numeric `json:"max"`
}

MaxResult is the result of a max aggregation. The value may be numeric or a numeric string.

type MinResult

type MinResult struct {
	Name string  `json:"name"`
	Min  Numeric `json:"min"`
}

MinResult is the result of a min aggregation. The value may be numeric or a numeric string.

type NoOpTokenStorage

type NoOpTokenStorage struct{}

NoOpTokenStorage is a TokenStorage that never caches. Every request triggers a fresh token fetch. Suitable for tests or callers that manage tokens elsewhere.

func NewNoOpTokenStorage

func NewNoOpTokenStorage() *NoOpTokenStorage

NewNoOpTokenStorage creates a NoOpTokenStorage.

func (*NoOpTokenStorage) Delete

Delete is a no-op.

func (*NoOpTokenStorage) Get

Get always reports no cached token.

func (*NoOpTokenStorage) Set

Set discards the token.

type Numeric

type Numeric struct {
	// contains filtered or unexported fields
}

Numeric holds a metric value that Shopware may serialize as either a number or a string (decimal columns come back as strings, e.g. "1.5400"), matching the PHP `mixed`/`float|int|string|null` types on min/max.

func (Numeric) Float

func (n Numeric) Float() (float64, error)

Float returns the value as a float64, parsing it whether it arrived as a JSON number or a numeric string.

func (Numeric) IsNull

func (n Numeric) IsNull() bool

IsNull reports whether the value is absent or JSON null.

func (Numeric) MarshalJSON

func (n Numeric) MarshalJSON() ([]byte, error)

MarshalJSON renders the original value.

func (Numeric) String

func (n Numeric) String() string

String returns the value as a string, unquoting it if it was a JSON string.

func (*Numeric) UnmarshalJSON

func (n *Numeric) UnmarshalJSON(data []byte) error

UnmarshalJSON stores the raw value for later, lossless interpretation.

type PasswordCredentials

type PasswordCredentials struct {
	Username string
	Password string
}

PasswordCredentials authenticates as an admin user using the password grant. The client_id is fixed to "administration", matching the Shopware admin SPA.

func NewPasswordCredentials

func NewPasswordCredentials(username, password string) PasswordCredentials

NewPasswordCredentials creates PasswordCredentials.

type Query

type Query struct {
	Score      float64 `json:"score"`
	Query      Filter  `json:"query"`
	ScoreField string  `json:"scoreField,omitempty"`
}

Query is a scored query used for relevance ranking.

type RefreshTokenCredentials

type RefreshTokenCredentials struct {
	ClientID string

	// OnRotate, if set, is called with the new refresh token whenever the
	// server rotates it, before the new access token is used. An error is
	// returned from the request that triggered the refresh. Set it before the
	// credentials are first used.
	OnRotate func(ctx context.Context, refreshToken string) error
	// contains filtered or unexported fields
}

RefreshTokenCredentials authenticates using the refresh_token grant. It is the bridge from an interactive PKCE login (via the pkce sub-package) into ongoing Client use: the initial access + refresh tokens are obtained by browser-based authorization, then stored here so the Client can transparently refresh the access token without further user interaction.

The refresh token is mutable (supports rotation) and thread-safe.

Shopware revokes the previous refresh token on every refresh, so a rotated token that is not persisted logs the user out on the next run. Set OnRotate to persist it, or use pkce.NewClient, which does this for you.

func NewRefreshTokenCredentials

func NewRefreshTokenCredentials(clientID, refreshToken string) *RefreshTokenCredentials

NewRefreshTokenCredentials creates RefreshTokenCredentials with the given client ID and initial refresh token. Use the client ID that was registered as a public OAuth client in Shopware (e.g. "shopware-cli").

func (*RefreshTokenCredentials) RefreshToken

func (c *RefreshTokenCredentials) RefreshToken() string

RefreshToken returns the current refresh token (for inspection/persistence).

func (*RefreshTokenCredentials) SetRefreshToken

func (c *RefreshTokenCredentials) SetRefreshToken(refreshToken string)

SetRefreshToken updates the stored refresh token. Call it after a PKCE exchange or when the server returns a rotated refresh token.

type RequestOption

type RequestOption func(*requestOptions)

RequestOption configures the DAL context (language, version, inheritance, indexing behaviour) for a single repository operation. Pass any number to the repository methods; with none, no context headers are sent.

repo.Search(ctx, criteria)                       // no context
repo.Search(ctx, criteria, WithLanguage(langID)) // with a language

func WithHeader

func WithHeader(key, value string) RequestOption

WithHeader sets an arbitrary request header, an escape hatch for headers not covered by a dedicated option.

func WithIndexingBehaviour

func WithIndexingBehaviour(value string) RequestOption

WithIndexingBehaviour sets the indexing-behavior header.

func WithIndexingSkip

func WithIndexingSkip(value string) RequestOption

WithIndexingSkip sets the indexing-skip header (a comma-separated list of indexers to skip).

func WithInheritance

func WithInheritance(inherit bool) RequestOption

WithInheritance toggles the sw-inheritance header.

func WithLanguage

func WithLanguage(languageID string) RequestOption

WithLanguage sets the sw-language-id header.

func WithSkipTriggerFlows

func WithSkipTriggerFlows(skip bool) RequestOption

WithSkipTriggerFlows toggles the sw-skip-trigger-flow header.

func WithVersion

func WithVersion(versionID string) RequestOption

WithVersion sets the sw-version-id header.

type Response

type Response struct {
	StatusCode int
	Body       []byte
	Headers    http.Header
}

Response represents a response from the Shopware API.

func (*Response) JSON

func (r *Response) JSON(target any) error

JSON unmarshals the response body into target.

type ScopedTokenStorage

type ScopedTokenStorage struct {
	// contains filtered or unexported fields
}

ScopedTokenStorage prefixes every key with a fixed scope before delegating to its parent storage. Use it to namespace one shared backend — e.g. a FileTokenStorage directory — per shop:

fileStore, err := shopware.NewFileTokenStorage(dir)
storage := shopware.NewScopedTokenStorage(fileStore, "https://shop.example.com")
client := shopware.NewClient(shopware.Config{
	BaseURL:      "https://shop.example.com",
	Credentials:  shopware.NewIntegrationCredentials(id, secret),
	TokenStorage: storage,
})

The default storage key only identifies the principal ("integration:<client-id>", "password:<username>", "pkce:<client-id>"), so without scoping, clients with the same principal against different shops would share one cached token. ScopedTokenStorage makes the effective key "<scope>\x00<key>", which FileTokenStorage then hashes into the filename — effectively a hash of shop URL + client-id/username.

A ScopedTokenStorage is safe for concurrent use when its parent is.

func NewScopedTokenStorage

func NewScopedTokenStorage(parent TokenStorage, scope string) *ScopedTokenStorage

NewScopedTokenStorage wraps parent so every key is namespaced under scope. Scope is typically the shop's BaseURL (any non-empty string works).

func (*ScopedTokenStorage) Delete

func (s *ScopedTokenStorage) Delete(ctx context.Context, key string) error

Delete removes the token stored under the scoped key.

func (*ScopedTokenStorage) Get

Get retrieves the token for the scoped key.

func (*ScopedTokenStorage) Scope

func (s *ScopedTokenStorage) Scope() string

Scope returns the configured scope.

func (*ScopedTokenStorage) Set

func (s *ScopedTokenStorage) Set(ctx context.Context, key, token string, expiry time.Time) error

Set stores the token under the scoped key.

func (*ScopedTokenStorage) Unwrap

func (s *ScopedTokenStorage) Unwrap() TokenStorage

Unwrap returns the parent storage.

type SearchResult

type SearchResult[T any] struct {
	Total        int                `json:"total"`
	Data         []T                `json:"data"`
	Aggregations AggregationResults `json:"aggregations"`
}

SearchResult is the typed result of a DAL search. Aggregations exposes the typed Get* helpers (GetTerms, GetStats, ...) for any aggregations requested.

func (*SearchResult[T]) First

func (r *SearchResult[T]) First() *T

First returns a pointer to the first result, or nil if there are none.

type Sorting

type Sorting struct {
	Field          string `json:"field"`
	Order          string `json:"order"`
	NaturalSorting bool   `json:"naturalSorting"`
	Type           string `json:"type,omitempty"`
}

Sorting describes a single sort instruction.

func CountSort

func CountSort(field, order string) Sorting

CountSort sorts by counting associations via field (ORDER BY COUNT(field)).

func NaturalSort

func NaturalSort(field, order string) Sorting

NaturalSort sorts by field using natural ordering.

func Sort

func Sort(field, order string) Sorting

Sort sorts by field. order defaults to "ASC" when empty.

type StatsResult

type StatsResult struct {
	Name string   `json:"name"`
	Min  Numeric  `json:"min"`
	Max  Numeric  `json:"max"`
	Avg  *float64 `json:"avg"`
	Sum  *float64 `json:"sum"`
}

StatsResult is the result of a stats aggregation. Min and Max may be numeric or numeric strings; Avg and Sum are nullable numbers.

type SumResult

type SumResult struct {
	Name string  `json:"name"`
	Sum  float64 `json:"sum"`
}

SumResult is the result of a sum aggregation.

type SyncOperation

type SyncOperation struct {
	Key      string   `json:"key"`
	Entity   string   `json:"entity"`
	Action   string   `json:"action"`
	Payload  []any    `json:"payload"`
	Criteria []Filter `json:"criteria,omitempty"`
}

SyncOperation is a single operation within a sync request.

func NewSyncOperation

func NewSyncOperation(key, entity, action string, payload []any, criteria []Filter) SyncOperation

NewSyncOperation builds a SyncOperation. action is "upsert" or "delete".

type SyncService

type SyncService struct {
	// contains filtered or unexported fields
}

SyncService performs DAL sync operations against the /_action/sync endpoint.

func NewSyncService

func NewSyncService(client *Client) *SyncService

NewSyncService returns a SyncService bound to the client.

func (*SyncService) Sync

func (s *SyncService) Sync(ctx context.Context, operations []SyncOperation, opts ...RequestOption) error

Sync sends the operations to the /_action/sync endpoint.

type TermsResult

type TermsResult struct {
	Name    string   `json:"name"`
	Buckets []Bucket `json:"buckets"`
}

TermsResult is the result of a terms aggregation.

type TokenStorage

type TokenStorage interface {
	// Get retrieves the token for key. It returns an empty string and the zero
	// time when no valid (unexpired) token exists.
	Get(ctx context.Context, key string) (token string, expiry time.Time, err error)

	// Set stores token for key with the given expiry time.
	Set(ctx context.Context, key string, token string, expiry time.Time) error

	// Delete removes the token for key.
	Delete(ctx context.Context, key string) error
}

TokenStorage stores and retrieves OAuth access tokens for a Client.

Implementations must treat expired tokens as absent: Get returns an empty token once the stored expiry has passed, prompting the Client to fetch a fresh one. Implementations must be safe for concurrent use.

Tokens are keyed by an opaque string chosen by the Client (its credentials' identity by default), so a single backend can serve many independent shops.

type TotalCountMode

type TotalCountMode int

TotalCountMode controls how the total count of a search result is computed.

const (
	// NoTotalCount selects no total count. Use when no pagination is required
	// (fastest).
	NoTotalCount TotalCountMode = 0
	// ExactTotalCount selects the exact total count. Use when exact pagination
	// is required (slow).
	ExactTotalCount TotalCountMode = 1
	// PaginationTotalCount fetches limit*5+1. Use when pagination only needs a
	// "next page exists" hint (fast).
	PaginationTotalCount TotalCountMode = 2
)

Directories

Path Synopsis
examples
pkce-cli command
pkce-cli is a minimal CLI that demonstrates the PKCE browser login against a Shopware instance and then calls /_info/me.
pkce-cli is a minimal CLI that demonstrates the PKCE browser login against a Shopware instance and then calls /_info/me.
Package extension provides a typed helper over the Shopware /api/_action/extension/* endpoints: listing, lifecycle (install, update, activate, ...), and uploading extension zips.
Package extension provides a typed helper over the Shopware /api/_action/extension/* endpoints: listing, lifecycle (install, update, activate, ...), and uploading extension zips.
Package instance provides helpers for operations on the shop as a whole rather than on a single entity, such as clearing its caches.
Package instance provides helpers for operations on the shop as a whole rather than on a single entity, such as clearing its caches.
internal
assert
Package assert provides the small testify-style subset of test helpers this module's tests need, built on the standard library only.
Package assert provides the small testify-style subset of test helpers this module's tests need, built on the standard library only.
Package media provides helpers for working with Shopware media: uploading files (by binary content or by URL) and resolving/creating media folders.
Package media provides helpers for working with Shopware media: uploading files (by binary content or by URL) and resolving/creating media folders.
Package pkce provides an end-to-end OAuth 2.0 authorization-code flow with PKCE (Proof Key for Code Exchange) for public clients of the Shopware Admin API.
Package pkce provides an end-to-end OAuth 2.0 authorization-code flow with PKCE (Proof Key for Code Exchange) for public clients of the Shopware Admin API.

Jump to

Keyboard shortcuts

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