Documentation
¶
Overview ¶
Package traverse provides a production-grade OData v2/v4 client library for Go.
Traverse is designed for high-performance querying and manipulation of OData services, with special optimizations for SAP systems. It offers:
- Streaming-first architecture for processing large datasets without memory overhead
- Ultra-low memory allocations (-81% vs baseline) through object pooling and zero-allocation patterns - Fluent query builder API for ergonomic OData query construction - Support for OData batch operations, delta queries, functions, and actions - Thread-safe client with concurrent goroutine support - Extensive metadata caching and query hooks for extensibility
Quick Start:
import "github.com/jhonsferg/traverse"
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithODataVersion(traverse.ODataV4),
)
defer client.Close()
results, _ := client.From("Products").
Filter("Price gt 100").
OrderBy("Name").
Collect(context.Background())
For streaming large result sets:
stream := client.From("Orders").
Stream(context.Background())
for result := range stream {
processOrder(result.Value)
}
The Client type is the main entry point and is safe for concurrent use across multiple goroutines. Use From() to create a QueryBuilder for constructing queries.
Package traverse provides a production-grade OData v2/v4 client for Go, built on top of github.com/jhonsferg/relay.
traverse is designed to consume OData services with millions of records without running out of memory, using server-side pagination, streaming JSON decoding, and Go channels for backpressure.
It is compatible with SAP OData services (both classic ABAP Gateway using OData v2 and S/4HANA using OData v4).
Quick start:
client, err := traverse.New(
traverse.WithBaseURL("https://sap.example.com/sap/opu/odata/sap/MY_SRV"),
traverse.WithBasicAuth("user", "pass"),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
for result := range client.From("MaterialSet").Stream(ctx) {
if result.Err != nil {
log.Fatal(result.Err)
}
fmt.Println(result.Value) // map[string]any for each record
}
See https://github.com/jhonsferg/traverse for full documentation.
Index ¶
- Constants
- Variables
- func Cast(args ...string) string
- func ClearGlobalCache()
- func CollectAs[T any](qb *QueryBuilder, ctx context.Context) ([]T, error)
- func CollectJsonAs[T any](qb *QueryBuilder, ctx context.Context) ([]T, error)
- func CollectXmlAs[T any](qb *QueryBuilder, ctx context.Context) ([]T, error)
- func ComputeExpr(parts ...string) string
- func CreateAtomXmlAs[T any](c *Client, ctx context.Context, entitySet string, data interface{}) (T, error)
- func CreateJsonAs[T any](c *Client, ctx context.Context, entitySet string, data interface{}) (T, error)
- func CreateRawAs(c *Client, ctx context.Context, entitySet string, data interface{}) ([]byte, error)
- func CreateXmlAs[T any](c *Client, ctx context.Context, entitySet string, data interface{}) (T, error)
- func DateTimeOffsetValue(t time.Time) string
- func DateTimeValue(t time.Time) string
- func DateTimeValueBytes(t time.Time) []byte
- func DecimalValue(v float64) string
- func DecimalValueBytes(v float64) []byte
- func ExecuteActionAs[T any](a *ActionBuilder, ctx context.Context) (T, error)
- func ExecuteActionJsonAs[T any](a *ActionBuilder, ctx context.Context) (T, error)
- func ExecuteActionXmlAs[T any](a *ActionBuilder, ctx context.Context) (T, error)
- func ExecuteFunctionAs[T any](f *FunctionBuilder, ctx context.Context) (T, error)
- func ExecuteFunctionImportAs[T any](f *FunctionImportBuilder, ctx context.Context) (T, error)
- func ExecuteFunctionImportJsonAs[T any](f *FunctionImportBuilder, ctx context.Context) (T, error)
- func ExecuteFunctionImportXmlAs[T any](f *FunctionImportBuilder, ctx context.Context) (T, error)
- func ExecuteFunctionJsonAs[T any](f *FunctionBuilder, ctx context.Context) (T, error)
- func ExecuteFunctionXmlAs[T any](f *FunctionBuilder, ctx context.Context) (T, error)
- func FetchPropertyAs[T any](qb *QueryBuilder, ctx context.Context, property string) (T, error)
- func FindByKeyAs[T any](qb *QueryBuilder, ctx context.Context, key interface{}) (T, error)
- func FindByKeyJsonAs[T any](qb *QueryBuilder, ctx context.Context, key interface{}) (T, error)
- func FindByKeyXmlAs[T any](qb *QueryBuilder, ctx context.Context, key interface{}) (T, error)
- func FirstAs[T any](qb *QueryBuilder, ctx context.Context) (T, error)
- func FirstJsonAs[T any](qb *QueryBuilder, ctx context.Context) (T, error)
- func FirstXmlAs[T any](qb *QueryBuilder, ctx context.Context) (T, error)
- func GeoDistanceFilter(property string, point GeographyPoint, operator string, maxDistance float64) string
- func GeoDistanceFilterGeom(property string, point GeometryPoint, operator string, maxDistance float64) string
- func GeoIntersectsFilter(property string, polygon GeographyPolygon) string
- func GeoIntersectsFilterGeom(property string, polygon GeometryPolygon) string
- func GeoLengthFilter(property string, operator string, threshold float64) string
- func GlobalCacheSize() int
- func GuidValue(id string) string
- func InternString(s string) string
- func IsAtomContentType(contentType string) bool
- func IsConcurrencyConflict(err error) bool
- func IsEntityNotFound(err error) bool
- func IsOf(args ...string) string
- func ParseAtomFeed(r io.Reader, page *Page) error
- func ReleaseGeographyPoint(p *GeographyPoint)
- func ReleaseGeometryPoint(p *GeometryPoint)
- func SingletonAs[T any](c *Client, name string) (*T, error)
- func SingletonAsCtx[T any](c *Client, ctx context.Context, name string) (*T, error)
- func StreamAs[T any](qb *QueryBuilder, ctx context.Context, bufferSize ...int) <-chan Result[T]
- func StreamJsonAs[T any](qb *QueryBuilder, ctx context.Context, bufferSize ...int) <-chan Result[T]
- func StreamXmlAs[T any](qb *QueryBuilder, ctx context.Context, bufferSize ...int) <-chan Result[T]
- func UpdateAs[T any](c *Client, ctx context.Context, entitySet string, key interface{}, ...) error
- func ValidateFilter(expr string) error
- func XmlBytesToStruct[T any](xmlData []byte) (T, error)
- type ActionBuilder
- type ActionInfo
- type AnalyticsVocabulary
- type AnnotatedEntity
- type Association
- type AssociationEnd
- type AsyncOpPoller
- type AsyncOpStatus
- type AsyncResult
- type AuthorizationVocabulary
- type BatchOperation
- type BatchRequest
- func (b *BatchRequest) BeginChangeset(id string) *BatchRequest
- func (b *BatchRequest) Create(entitySet string, data interface{}) *BatchRequest
- func (b *BatchRequest) Delete(entitySet string, key interface{}) *BatchRequest
- func (b *BatchRequest) EndChangeset() *BatchRequest
- func (b *BatchRequest) Execute(ctx context.Context) (*BatchResponse, error)
- func (b *BatchRequest) ExecuteJSON(ctx context.Context) (*BatchResponse, error)
- func (b *BatchRequest) ExecuteJSONStream(ctx context.Context) <-chan BatchResult
- func (b *BatchRequest) ExecuteStream(ctx context.Context) <-chan BatchResult
- func (b *BatchRequest) Get(entitySet string, key interface{}) *BatchRequest
- func (b *BatchRequest) Update(entitySet string, key interface{}, data interface{}) *BatchRequest
- type BatchResponse
- type BatchResult
- type Binary
- type CacheStore
- type CapabilitiesRegistry
- type CapabilityError
- type Client
- func (c *Client) Action(name string) *ActionBuilder
- func (c *Client) BaseURL() string
- func (c *Client) Batch() *BatchRequest
- func (c *Client) CircuitBreakerState() relay.CircuitBreakerState
- func (c *Client) Close() error
- func (c *Client) Create(ctx context.Context, entitySet string, data interface{}) (map[string]interface{}, error)
- func (c *Client) CrossJoin(entitySets ...string) *CrossJoinBuilder
- func (c *Client) Delete(ctx context.Context, entitySet string, key interface{}) error
- func (c *Client) DeleteWithETag(ctx context.Context, entitySet string, key interface{}, etag ETag) error
- func (c *Client) DeleteWithOptions(ctx context.Context, entitySet string, key interface{}, opts ...DeleteOption) error
- func (c *Client) FetchPageAt(ctx context.Context, rawURL string) (*Page, error)
- func (c *Client) From(entitySet string) *QueryBuilder
- func (c *Client) Function(name string) *FunctionBuilder
- func (c *Client) FunctionImport(name string) *FunctionImportBuilder
- func (c *Client) Metadata(ctx context.Context) (*Metadata, error)
- func (c *Client) NewAsyncPoller(statusURL string) *AsyncOpPoller
- func (c *Client) NewDeltaSync(entitySet string) *DeltaSync
- func (c *Client) PageSize() int
- func (c *Client) ReadWithETag(ctx context.Context, entitySet string, key interface{}) (*EntityWithETag, error)
- func (c *Client) RelayClient() *relay.Client
- func (c *Client) Replace(ctx context.Context, entitySet string, key interface{}, data interface{}) error
- func (c *Client) ReplaceWithETag(ctx context.Context, entitySet string, key interface{}, data interface{}, ...) error
- func (c *Client) ResetCircuitBreaker()
- func (c *Client) Service(ctx context.Context) (*ServiceDocument, error)
- func (c *Client) Singleton(name string) *QueryBuilder
- func (c *Client) Update(ctx context.Context, entitySet string, key interface{}, data interface{}) error
- func (c *Client) UpdateWithETag(ctx context.Context, entitySet string, key interface{}, data interface{}, ...) error
- func (c *Client) Upsert(ctx context.Context, entitySet string, key interface{}, data interface{}) error
- func (c *Client) Version() ODataVersion
- type ComplexType
- type CoreVocabulary
- type CrossJoinBuilder
- func (b *CrossJoinBuilder) Collect(ctx context.Context) ([]CrossJoinResult, error)
- func (b *CrossJoinBuilder) Expand(navProps ...string) *CrossJoinBuilder
- func (b *CrossJoinBuilder) Filter(expr string) *CrossJoinBuilder
- func (b *CrossJoinBuilder) Param(key, value string) *CrossJoinBuilder
- func (b *CrossJoinBuilder) Select(fields ...string) *CrossJoinBuilder
- type CrossJoinResult
- type DateTime
- type DateTimeOffset
- type Decimal
- type DeepInsertOptions
- type DeepUpdateOptions
- type DeleteOption
- type DeleteOptions
- type DeltaResult
- type DeltaResultAs
- type DeltaSync
- func (d *DeltaSync) Full(ctx context.Context, bufferSize ...int) (<-chan Result[map[string]interface{}], string, error)
- func (d *DeltaSync) Incremental(ctx context.Context, token string, bufferSize ...int) (<-chan DeltaResult, string, error)
- func (d *DeltaSync) SetToken(token string)
- func (d *DeltaSync) Token() string
- type DeltaSyncAs
- type DeltaSyncJsonAs
- type DeltaSyncXmlAs
- type ETag
- type EntityCapabilities
- type EntitySchema
- type EntitySetInfo
- type EntitySetReference
- type EntityType
- type EntityWithETag
- type EnumMember
- type EnumType
- type ExpandBuilder
- func (b *ExpandBuilder) Done() *QueryBuilder
- func (b *ExpandBuilder) Expand(property string) *ExpandBuilder
- func (b *ExpandBuilder) Filter(expr string) *ExpandBuilder
- func (b *ExpandBuilder) OrderBy(expr string) *ExpandBuilder
- func (b *ExpandBuilder) Select(fields ...string) *ExpandBuilder
- func (b *ExpandBuilder) Top(n int) *ExpandBuilder
- type ExpandOption
- func WithExpandFilter(expr string) ExpandOption
- func WithExpandLevels(n int) ExpandOption
- func WithExpandOrderBy(field string) ExpandOption
- func WithExpandOrderByDesc(field string) ExpandOption
- func WithExpandSelect(fields ...string) ExpandOption
- func WithExpandSkip(n int) ExpandOption
- func WithExpandTop(n int) ExpandOption
- type FilterBuilder
- func (f *FilterBuilder) Contains(substr string) *QueryBuilder
- func (f *FilterBuilder) EndsWith(suffix string) *QueryBuilder
- func (f *FilterBuilder) Eq(value interface{}) *QueryBuilder
- func (f *FilterBuilder) Ge(value interface{}) *QueryBuilder
- func (f *FilterBuilder) Gt(value interface{}) *QueryBuilder
- func (f *FilterBuilder) In(values ...interface{}) *QueryBuilder
- func (f *FilterBuilder) Le(value interface{}) *QueryBuilder
- func (f *FilterBuilder) Lt(value interface{}) *QueryBuilder
- func (f *FilterBuilder) Ne(value interface{}) *QueryBuilder
- func (f *FilterBuilder) StartsWith(prefix string) *QueryBuilder
- type FilterExpr
- func (f *FilterExpr) Build() string
- func (f *FilterExpr) Contains(value string) *FilterExpr
- func (f *FilterExpr) EndsWith(value string) *FilterExpr
- func (f *FilterExpr) Eq(value interface{}) *FilterExpr
- func (f *FilterExpr) Ge(value interface{}) *FilterExpr
- func (f *FilterExpr) Gt(value interface{}) *FilterExpr
- func (f *FilterExpr) Le(value interface{}) *FilterExpr
- func (f *FilterExpr) Lt(value interface{}) *FilterExpr
- func (f *FilterExpr) Ne(value interface{}) *FilterExpr
- func (f *FilterExpr) Reset() *FilterExpr
- func (f *FilterExpr) StartsWith(value string) *FilterExpr
- func (f *FilterExpr) String() string
- type FunctionBuilder
- type FunctionImportBuilder
- func (f *FunctionImportBuilder) Execute(ctx context.Context) (map[string]interface{}, error)
- func (f *FunctionImportBuilder) Invoke(ctx context.Context, result any) error
- func (f *FunctionImportBuilder) InvokeCollection(ctx context.Context, results any) error
- func (f *FunctionImportBuilder) Method(m string) *FunctionImportBuilder
- func (f *FunctionImportBuilder) Param(key string, value interface{}) *FunctionImportBuilder
- type FunctionImportInfo
- type FunctionInfo
- type FunctionParameter
- type GeographyLineString
- type GeographyMultiPoint
- type GeographyPoint
- type GeographyPolygon
- type GeometryLineString
- type GeometryMultiPoint
- type GeometryPoint
- type GeometryPolygon
- type GraphConfig
- type GraphError
- type Guid
- type LambdaBuilder
- type LambdaCondition
- func (c *LambdaCondition) Contains(s string) *LambdaBuilder
- func (c *LambdaCondition) EndsWith(s string) *LambdaBuilder
- func (c *LambdaCondition) Eq(v any) *LambdaBuilder
- func (c *LambdaCondition) Ge(v any) *LambdaBuilder
- func (c *LambdaCondition) Gt(v any) *LambdaBuilder
- func (c *LambdaCondition) Le(v any) *LambdaBuilder
- func (c *LambdaCondition) Lt(v any) *LambdaBuilder
- func (c *LambdaCondition) Ne(v any) *LambdaBuilder
- func (c *LambdaCondition) StartsWith(s string) *LambdaBuilder
- type MeasuresVocabulary
- type MemoryCache
- type Metadata
- type NavigationBinding
- type NavigationProperty
- type NoOpCache
- type ODataError
- type ODataErrorDetail
- type ODataVersion
- type Option
- func WithAPIKey(header, value string) Option
- func WithAfterExecute(hook func(*QueryBuilder) error) Option
- func WithBaseURL(url string) Option
- func WithBasicAuth(user, pass string) Option
- func WithBearerToken(token string) Option
- func WithBeforeQuery(hook func(*QueryBuilder) error) Option
- func WithCapabilitiesValidation(registry *CapabilitiesRegistry) Option
- func WithCircuitBreaker(cfg *relay.CircuitBreakerConfig) Option
- func WithConnectTimeout(d time.Duration) Option
- func WithCookieJar(jar http.CookieJar) Option
- func WithFormat(f ResponseFormat) Option
- func WithHTTPOption(opt relay.Option) Option
- func WithHeader(key, value string) Option
- func WithLogger(l relay.Logger) Option
- func WithMaxPages(n int) Option
- func WithMaxRedirects(n int) Option
- func WithMetadataCache(cache CacheStore) Option
- func WithODataErrors() Option
- func WithODataVersion(v ODataVersion) Option
- func WithPageSize(n int) Option
- func WithProxy(proxyURL string) Option
- func WithRateLimit(rps float64, burst int) Option
- func WithRelayClient(rc *relay.Client) Option
- func WithRequestHook(fn func(context.Context, *relay.Request) error) Option
- func WithResponseCache(cache ResponseCache) Option
- func WithResponseHook(fn func(context.Context, *relay.Response) error) Option
- func WithRetry(rc *relay.RetryConfig) Option
- func WithSchemaVersion(version string) Option
- func WithSigner(s relay.RequestSigner) Option
- func WithTLSConfig(cfg *tls.Config) Option
- func WithTimeout(d time.Duration) Option
- type Page
- type Paginator
- type Property
- type PropertyRef
- type QueryBuilder
- func (q *QueryBuilder) Apply(expr string) *QueryBuilder
- func (q *QueryBuilder) AsType(typeName string) *QueryBuilder
- func (q *QueryBuilder) BoundAction(name string) *ActionBuilder
- func (q *QueryBuilder) BoundFunction(name string) *FunctionBuilder
- func (q *QueryBuilder) BulkDelete(ctx context.Context) error
- func (q *QueryBuilder) BulkUpdate(ctx context.Context, data interface{}) error
- func (q *QueryBuilder) Collect(ctx context.Context) ([]map[string]interface{}, error)
- func (q *QueryBuilder) Compute(expressions ...string) *QueryBuilder
- func (q *QueryBuilder) Count(ctx context.Context) (int64, error)
- func (q *QueryBuilder) CreateDeep(ctx context.Context, entity any) (*relay.Response, error)
- func (q *QueryBuilder) CreateDeepWithPrefer(ctx context.Context, entity any, prefer string) (*relay.Response, error)
- func (q *QueryBuilder) DeleteLink(ctx context.Context, navProperty string, relatedKey any) error
- func (q *QueryBuilder) DeleteLinks(ctx context.Context, navProperty string) error
- func (q *QueryBuilder) Expand(navProp string, opts ...ExpandOption) *QueryBuilder
- func (q *QueryBuilder) ExpandNested(property string) *ExpandBuilder
- func (q *QueryBuilder) Filter(expr string) *QueryBuilder
- func (q *QueryBuilder) FilterBy(expr *FilterExpr) *QueryBuilder
- func (q *QueryBuilder) FilterLambda(expression string) *QueryBuilder
- func (q *QueryBuilder) FindByCompositeKey(ctx context.Context, keys map[string]interface{}) (map[string]interface{}, error)
- func (q *QueryBuilder) FindByKey(ctx context.Context, key interface{}) (map[string]interface{}, error)
- func (q *QueryBuilder) First(ctx context.Context) (map[string]interface{}, error)
- func (q *QueryBuilder) GeoDistance(property string, point GeographyPoint, operator string, distance float64) *QueryBuilder
- func (q *QueryBuilder) GeoDistanceGeom(property string, point GeometryPoint, operator string, distance float64) *QueryBuilder
- func (q *QueryBuilder) GeoIntersects(property string, polygon GeographyPolygon) *QueryBuilder
- func (q *QueryBuilder) GeoIntersectsGeom(property string, polygon GeometryPolygon) *QueryBuilder
- func (q *QueryBuilder) GeoLength(property string, operator string, threshold float64) *QueryBuilder
- func (q *QueryBuilder) IfMatch(etag string) *QueryBuilder
- func (q *QueryBuilder) IfModifiedSince(t time.Time) *QueryBuilder
- func (q *QueryBuilder) IfNoneMatch(etag string) *QueryBuilder
- func (q *QueryBuilder) IfUnmodifiedSince(t time.Time) *QueryBuilder
- func (q *QueryBuilder) Key(key any) *QueryBuilder
- func (q *QueryBuilder) LambdaAll(collectionField string, fn func(*LambdaBuilder)) *QueryBuilder
- func (q *QueryBuilder) LambdaAny(collectionField string, fn func(*LambdaBuilder)) *QueryBuilder
- func (q *QueryBuilder) LinkTo(ctx context.Context, key any, navProperty string, targetEntitySet string, ...) error
- func (q *QueryBuilder) NoCache() *QueryBuilder
- func (q *QueryBuilder) OrderBy(field string) *QueryBuilder
- func (q *QueryBuilder) OrderByDesc(field string) *QueryBuilder
- func (q *QueryBuilder) Page(ctx context.Context) (*Page, error)
- func (q *QueryBuilder) Param(key, value string) *QueryBuilder
- func (q *QueryBuilder) Search(expr SearchExpression) *QueryBuilder
- func (q *QueryBuilder) Select(fields ...string) *QueryBuilder
- func (q *QueryBuilder) Skip(n int) *QueryBuilder
- func (q *QueryBuilder) Stream(ctx context.Context, bufferSize ...int) <-chan Result[map[string]interface{}]
- func (q *QueryBuilder) StreamAs(ctx context.Context, bufferSize ...int) <-chan Result[map[string]interface{}]
- func (q *QueryBuilder) StreamProperty(ctx context.Context, propertyName string) (io.ReadCloser, error)
- func (q *QueryBuilder) StreamPropertySize(ctx context.Context, propertyName string) (int64, error)
- func (q *QueryBuilder) Top(n int) *QueryBuilder
- func (q *QueryBuilder) UnlinkFrom(ctx context.Context, key any, navProperty string, targetKey ...any) error
- func (q *QueryBuilder) UpdateDeep(ctx context.Context, entity any) (*relay.Response, error)
- func (q *QueryBuilder) UpdateDeepWithPrefer(ctx context.Context, entity any, prefer string) (*relay.Response, error)
- func (q *QueryBuilder) Where(field string) *FilterBuilder
- func (q *QueryBuilder) WithCache(ttl time.Duration) *QueryBuilder
- func (q *QueryBuilder) WithCount() *QueryBuilder
- func (q *QueryBuilder) WithDeltaToken(token string) *QueryBuilder
- func (q *QueryBuilder) WithNoPrefetch() *QueryBuilder
- func (q *QueryBuilder) WithPrefer(pref string) *QueryBuilder
- func (q *QueryBuilder) WithPrefetch(bufferPages int) *QueryBuilder
- func (q *QueryBuilder) WithSchema(schema *EntitySchema) *QueryBuilder
- func (q *QueryBuilder) WithSchemaVersion(version string) *QueryBuilder
- type RawResult
- type ResponseCache
- type ResponseCacheEntry
- type ResponseFormat
- type Result
- type SAPAnnotations
- type SAPError
- type SAPErrorType
- type SchemaValidationError
- type SearchExpression
- func Search(s string) SearchExpression
- func SearchAnd(exprs ...SearchExpression) SearchExpression
- func SearchNot(expr SearchExpression) SearchExpression
- func SearchOr(exprs ...SearchExpression) SearchExpression
- func SearchPhrase(phrase string) SearchExpression
- func SearchWord(word string) SearchExpression
- type ServiceDocument
- type StringInterning
- type TrackedEntity
- func (t *TrackedEntity) Changes() map[string]interface{}
- func (t *TrackedEntity) DirtyFields() []string
- func (t *TrackedEntity) Discard()
- func (t *TrackedEntity) Get(field string) (interface{}, bool)
- func (t *TrackedEntity) IsDirty() bool
- func (t *TrackedEntity) MarshalJSON() ([]byte, error)
- func (t *TrackedEntity) Original() map[string]interface{}
- func (t *TrackedEntity) Reset()
- func (t *TrackedEntity) SaveChanges(ctx context.Context, client *Client, entitySet string, key interface{}) error
- func (t *TrackedEntity) Set(field string, value interface{})
- type ValidationVocabulary
Constants ¶
const ( // PreferHandlingStrict instructs the server to fail if it encounters unknown // query options (handling=strict). PreferHandlingStrict = "handling=strict" // PreferHandlingLenient instructs the server to silently ignore unknown // query options (handling=lenient). PreferHandlingLenient = "handling=lenient" // PreferReturnRepresentation instructs the server to return the full entity // in the response body after a create or update operation (return=representation). PreferReturnRepresentation = "return=representation" // PreferReturnMinimal instructs the server to return 204 No Content after a // create or update operation instead of the full entity (return=minimal). PreferReturnMinimal = "return=minimal" // PreferTrackChanges requests that the server include a deltaLink in the // response, enabling incremental sync on the next request. PreferTrackChanges = "odata.track-changes" // PreferRespondAsync requests that the server process the request asynchronously // and return a 202 Accepted with a status monitor URL. PreferRespondAsync = "respond-async" )
Prefer header constants for OData Prefer request header values. See OData v4.0 spec section 8.2.8.
const DefaultMaxPolls = 60
DefaultMaxPolls is the default maximum number of poll attempts.
const DefaultPollInterval = 5 * time.Second
DefaultPollInterval is the default interval between async operation polls.
const LevelsMax = -1
LevelsMax is a sentinel value for WithExpandLevels that instructs the server to expand recursively to the maximum supported depth ($levels=max).
Variables ¶
var ( // ErrEntityNotFound is returned when a requested entity is not found (HTTP 404). ErrEntityNotFound = errors.New("traverse: entity not found (404)") // ErrConcurrencyConflict is returned when an ETag mismatch occurs (HTTP 412). // This indicates the entity was modified by another client. ErrConcurrencyConflict = errors.New("traverse: ETag mismatch (412)") ErrServiceUnavailable = errors.New("traverse: OData service unavailable") // ErrCSRFTokenRequired is returned when a CSRF token is required (SAP-specific). // This is common in SAP systems; include a CSRF token in requests. ErrCSRFTokenRequired = errors.New("traverse: CSRF token required (SAP)") // ErrInvalidFilter is returned when a $filter expression is invalid. ErrInvalidFilter = errors.New("traverse: invalid $filter expression") // ErrMetadataInvalid is returned when $metadata parsing fails. ErrMetadataInvalid = errors.New("traverse: failed to parse $metadata") // ErrPageSizeExceeded is returned when server rejects $top value. ErrPageSizeExceeded = errors.New("traverse: server rejected $top value") // ErrStreamClosed is returned when stream is closed or context cancelled. ErrStreamClosed = errors.New("traverse: stream closed or context cancelled") // ErrClientClosed is returned when client is closed. ErrClientClosed = errors.New("traverse: client closed") ErrUnauthorized = errors.New("traverse: authentication required (401)") // ErrForbidden is returned when authorization fails (HTTP 403). ErrForbidden = errors.New("traverse: authorization denied (403)") // ErrBatchFailed is returned when a batch request fails. ErrBatchFailed = errors.New("traverse: batch request failed") )
Sentinel errors define common error conditions in OData operations.
These are error values that can be compared directly with errors.Is() for categorizing failures in OData interactions.
var ErrAsyncOpFailed = errors.New("traverse: async operation failed")
ErrAsyncOpFailed is returned when the async operation completes with a failure status.
var ErrAsyncOpTimeout = errors.New("traverse: async operation timed out after max polls")
ErrAsyncOpTimeout is returned when maxPolls is exhausted before the operation completes.
Functions ¶
func Cast ¶ added in v0.6.0
Cast returns an OData filter expression string that casts a value to the given type (cast() function).
Cast implements the OData v4.0 type system function defined in spec section 5.1.1.6.3. Two forms are supported:
- cast(TypeName) - casts the entity itself to the given type
- cast(expression, TypeName) - casts an expression to the given type
The result can be used in filter expressions, typically with a comparison operator.
Example:
// $filter=cast(Budget,Edm.Decimal) gt 1000
client.From("Projects").
Filter(traverse.Cast("Budget", "Edm.Decimal") + " gt 1000").
Collect(ctx)
func ClearGlobalCache ¶
func ClearGlobalCache()
ClearGlobalCache clears the global string interning cache.
ClearGlobalCache removes all cached strings and reinitializes the cache. Useful for testing or when you want to reset memory state in long-running processes.
func CollectAs ¶
func CollectAs[T any](qb *QueryBuilder, ctx context.Context) ([]T, error)
CollectAs iterates through the query result stream, converts each result to type T, and collects them into a slice. All results are loaded into memory before returning.
⚠️ Warning: For large datasets (millions of records), this method loads all results into memory at once, which can cause significant memory pressure and GC overhead. For large result sets, prefer StreamAs which processes results incrementally without materializing the entire collection.
The buffer size parameter is passed to the underlying QueryBuilder.Stream call to control the buffering of results. Default is adaptive buffering (see [Stream]).
Returns a slice of T with all query results, or an error if streaming or conversion fails.
Example:
type Product struct {
ID int `json:"id"`
Price float64 `json:"price"`
}
// Load all products (⚠️ use with caution for large datasets)
products, err := CollectAs[Product](qb, ctx)
CollectAs is an alias for CollectJsonAs for backward compatibility. Deprecated: Use CollectJsonAs or CollectXmlAs instead.
func CollectJsonAs ¶ added in v0.16.0
func CollectJsonAs[T any](qb *QueryBuilder, ctx context.Context) ([]T, error)
CollectJsonAs materializes all results from the query into a slice of type T using JSON.
CollectJsonAs iterates through the query result stream, converts each result to type T, and collects them into a slice. All results are loaded into memory before returning.
⚠️ Warning: For large datasets (millions of records), this method loads all results into memory at once, which can cause significant memory pressure and GC overhead. For large result sets, prefer StreamJsonAs which processes results incrementally without materializing the entire collection.
Returns a slice of T with all query results, or an error if streaming or conversion fails.
Example:
type Product struct {
ID int `json:"id"`
Price float64 `json:"price"`
}
// Load all products (⚠️ use with caution for large datasets)
products, err := CollectJsonAs[Product](qb, ctx)
func CollectXmlAs ¶ added in v0.16.0
func CollectXmlAs[T any](qb *QueryBuilder, ctx context.Context) ([]T, error)
CollectXmlAs materializes all results from the query into a slice of type T with XML struct tags.
CollectXmlAs is the XML-tag variant of CollectAs. It iterates through the query result stream, converts each result to type T, and collects them into a slice. All results are loaded into memory before returning.
The "XmlAs" suffix indicates that the target struct T has xml:"..." tags for field mapping, not that the server returns XML responses. The server response is always JSON.
⚠️ Warning: For large datasets (millions of records), this method loads all results into memory at once, which can cause significant memory pressure and GC overhead. For large result sets, prefer StreamXmlAs which processes results incrementally without materializing the entire collection.
Returns a slice of T with all query results, or an error if streaming or conversion fails.
Example:
type Product struct {
ID int `xml:"id"`
Price float64 `xml:"price"`
}
// Load all products (⚠️ use with caution for large datasets)
products, err := CollectXmlAs[Product](qb, ctx)
func ComputeExpr ¶ added in v0.2.27
ComputeExpr joins the provided parts with spaces to form a $compute expression.
This is a convenience helper for constructing compute expressions from individual tokens without manual string concatenation.
Example:
ComputeExpr("Price", "mul", "Quantity", "as", "Total") // "Price mul Quantity as Total"
func CreateAtomXmlAs ¶ added in v0.21.3
func CreateAtomXmlAs[T any](c *Client, ctx context.Context, entitySet string, data interface{}) (T, error)
CreateAtomXmlAs creates a new entity and unmarshals the OData v2 Atom XML response directly to type T.
CreateAtomXmlAs is specialized for SAP OData v2 services that return Atom+XML format (RFC 5023). Unlike CreateXmlAs which converts through a map[string]interface{}, this method:
- Sends Accept: application/atom+xml header to request Atom format
- Gets raw XML bytes from the response
- Unmarshals directly to the target struct T using xml.Unmarshal
- Preserves the full Atom structure including namespaces and metadata
The target struct T must have xml:"..." tags with proper namespace handling for Atom elements. For OData v2 Atom, the response contains an <entry> element with <content><m:properties> children.
This is the preferred method for SAP OData v2 integrations when you need true XML unmarshaling without intermediate JSON conversion.
Returns the created entity as type T, or an error if creation fails.
Example:
type SAPNotification struct {
XMLName xml.Name `xml:"entry"`
ID string `xml:"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata id"`
Content struct {
Properties struct {
NotifID string `xml:"NotificationID"`
} `xml:"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata properties"`
} `xml:"http://www.w3.org/2005/Atom content"`
}
notif, err := CreateAtomXmlAs[SAPNotification](client, ctx, "MaintenanceNotifications", newNotif)
func CreateJsonAs ¶ added in v0.16.0
func CreateJsonAs[T any](c *Client, ctx context.Context, entitySet string, data interface{}) (T, error)
CreateJsonAs creates a new entity using JSON payload and decodes the response to type T.
CreateJsonAs is the generic version of Client.Create. It sends a POST request to create a new entity in the specified entity set, then unmarshals the response into a typed value of type T.
The created entity data is marshaled to JSON automatically. The server response typically contains the new entity with generated fields (ID, timestamps, etc.).
Returns the created entity (with server-assigned fields) as type T, or an error if creation fails.
Example:
type Order struct {
ID int `json:"id"`
Total float64 `json:"total"`
}
newOrder := map[string]interface{}{"total": 99.99}
order, err := CreateJsonAs[Order](client, ctx, "Orders", newOrder)
// order.ID is now set by the server
func CreateRawAs ¶ added in v0.20.0
func CreateRawAs(c *Client, ctx context.Context, entitySet string, data interface{}) ([]byte, error)
CreateRawAs creates a new entity and returns the raw response bytes.
CreateRawAs sends a POST request to create a new entity in the specified entity set and returns the raw response body as bytes. This is useful for debugging, testing, or handling responses that don't fit standard patterns (e.g., custom XML, mixed formats).
Returns the raw response bytes, or an error if creation fails. The caller is responsible for parsing or validating the response content.
Example:
rawData, err := CreateRawAs(client, ctx, "Orders", newOrder)
if err != nil {
// handle error
}
fmt.Println(string(rawData)) // Print raw response
This is commonly used for:
- Testing and debugging OData responses
- Working with non-standard response formats
- Validating SAP backend behavior
- Capturing both JSON and XML responses transparently
func CreateXmlAs ¶ added in v0.16.0
func CreateXmlAs[T any](c *Client, ctx context.Context, entitySet string, data interface{}) (T, error)
CreateXmlAs creates a new entity and decodes the response to type T with XML struct tags.
CreateXmlAs is the generic version of Client.Create. It sends a POST request to create a new entity in the specified entity set, then unmarshals the response into a typed value of type T.
The target struct T must have xml:"..." tags for proper field mapping. The method handles responses that are returned as JSON (converted via mapToXmlStruct) or for direct XML unmarshaling, use the lower-level Client methods with rawMessageToXmlStruct.
Returns the created entity (with server-assigned fields) as type T, or an error if creation fails.
Example:
type Order struct {
ID int `xml:"id"`
Total float64 `xml:"total"`
}
newOrder := map[string]interface{}{"total": 99.99}
order, err := CreateXmlAs[Order](client, ctx, "Orders", newOrder)
// order.ID is now set by the server
func DateTimeOffsetValue ¶
DateTimeOffsetValue produces an OData DateTimeOffset literal for use in filter expressions.
DateTimeOffsetValue generates the OData v4 DateTimeOffset format (ISO 8601/RFC 3339) suitable for $filter expressions on DateTimeOffset fields.
Returns string in RFC 3339 format: 2024-01-01T00:00:00Z or 2024-01-01T00:00:00+01:00
Example (used in filters):
qb.Filter(fmt.Sprintf("CreatedAt ge %s", DateTimeOffsetValue(startDate)))
func DateTimeValue ¶
DateTimeValue produces an OData DateTime literal for use in filter expressions.
DateTimeValue generates the OData v2 DateTime format suitable for $filter expressions. It's a wrapper around DateTimeValueBytes for convenience when a string is needed.
Returns string in the format: datetime'2024-01-01T00:00:00'
Example (used in filters):
qb.Filter(fmt.Sprintf("CreatedAt ge %s", DateTimeValue(startDate)))
func DateTimeValueBytes ¶
DateTimeValueBytes produces an OData DateTime literal for use in filter expressions as bytes.
DateTimeValueBytes generates the OData v2 DateTime format suitable for use in $filter expressions. It pre-allocates a buffer and appends the formatted datetime to minimize allocations compared to string concatenation.
Returns bytes in the format: datetime'2024-01-01T00:00:00'
Example:
filter := string(DateTimeValueBytes(time.Now())) // filter = "datetime'2024-01-01T12:34:56'"
func DecimalValue ¶
DecimalValue produces an OData Decimal literal for filters. Returns: 3.14M This is a wrapper around DecimalValueBytes for backward compatibility.
func DecimalValueBytes ¶
DecimalValueBytes produces an OData Decimal literal for filters as bytes. Returns: []byte("3.14M") This is the optimized version that avoids string allocations.
func ExecuteActionAs ¶
func ExecuteActionAs[T any](a *ActionBuilder, ctx context.Context) (T, error)
ExecuteActionAs is an alias for ExecuteActionJsonAs for backward compatibility. Deprecated: Use ExecuteActionJsonAs or ExecuteActionXmlAs instead.
func ExecuteActionJsonAs ¶ added in v0.16.0
func ExecuteActionJsonAs[T any](a *ActionBuilder, ctx context.Context) (T, error)
ExecuteActionJsonAs is the JSON-format generic version of ActionBuilder.Execute.
ExecuteActionJsonAs calls the action and unmarshals the response result to type T using JSON. It uses [mapToJsonStruct] for type conversion, supporting all Go types with JSON marshaling.
Returns the action result as type T, or an error if the call fails or type conversion fails.
Example:
type ApprovalResult struct {
Approved bool `json:"approved"`
Message string `json:"message"`
}
result, err := ExecuteActionJsonAs[ApprovalResult](
client.Action("ApproveOrder").WithBody(approvalData),
ctx,
)
func ExecuteActionXmlAs ¶ added in v0.16.0
func ExecuteActionXmlAs[T any](a *ActionBuilder, ctx context.Context) (T, error)
ExecuteActionXmlAs is the XML-format generic version of ActionBuilder.Execute.
ExecuteActionXmlAs calls the action and unmarshals the response result to type T with XML struct tags. It uses [mapToXmlStruct] for conversion with struct tags in xml:"..." format.
Returns the action result as type T, or an error if the call fails or type conversion fails.
Example:
type ApprovalResult struct {
Approved bool `xml:"approved"`
Message string `xml:"message"`
}
result, err := ExecuteActionXmlAs[ApprovalResult](
client.Action("ApproveOrder").WithBody(approvalData),
ctx,
)
func ExecuteFunctionAs ¶
func ExecuteFunctionAs[T any](f *FunctionBuilder, ctx context.Context) (T, error)
ExecuteFunctionAs is an alias for ExecuteFunctionJsonAs for backward compatibility. Deprecated: Use ExecuteFunctionJsonAs or ExecuteFunctionXmlAs instead.
func ExecuteFunctionImportAs ¶
func ExecuteFunctionImportAs[T any](f *FunctionImportBuilder, ctx context.Context) (T, error)
ExecuteFunctionImportAs is an alias for ExecuteFunctionImportJsonAs for backward compatibility. Deprecated: Use ExecuteFunctionImportJsonAs or ExecuteFunctionImportXmlAs instead.
func ExecuteFunctionImportJsonAs ¶ added in v0.16.0
func ExecuteFunctionImportJsonAs[T any](f *FunctionImportBuilder, ctx context.Context) (T, error)
ExecuteFunctionImportJsonAs is the JSON-format generic version of FunctionImportBuilder.Execute.
func ExecuteFunctionImportXmlAs ¶ added in v0.16.0
func ExecuteFunctionImportXmlAs[T any](f *FunctionImportBuilder, ctx context.Context) (T, error)
ExecuteFunctionImportXmlAs is the XML-format generic version of FunctionImportBuilder.Execute.
func ExecuteFunctionJsonAs ¶ added in v0.16.0
func ExecuteFunctionJsonAs[T any](f *FunctionBuilder, ctx context.Context) (T, error)
ExecuteFunctionJsonAs is the JSON-format generic version of FunctionBuilder.Execute.
ExecuteFunctionJsonAs calls the function and unmarshals the response result to type T using JSON. It uses [mapToJsonStruct] for type conversion, supporting all Go types with JSON marshaling.
Returns the function result as type T, or an error if the call fails or type conversion fails.
Example:
type TopProducts struct {
Products []Product `json:"products"`
}
result, err := ExecuteFunctionJsonAs[TopProducts](
client.Function("GetTopProducts").Param("count", 10),
ctx,
)
func ExecuteFunctionXmlAs ¶ added in v0.16.0
func ExecuteFunctionXmlAs[T any](f *FunctionBuilder, ctx context.Context) (T, error)
ExecuteFunctionXmlAs is the XML-format generic version of FunctionBuilder.Execute.
ExecuteFunctionXmlAs calls the function and unmarshals the response result to type T with XML struct tags. It uses [mapToXmlStruct] for conversion with struct tags in xml:"..." format.
Returns the function result as type T, or an error if the call fails or type conversion fails.
Example:
type TopProducts struct {
Products []Product `xml:"products"`
}
result, err := ExecuteFunctionXmlAs[TopProducts](
client.Function("GetTopProducts").Param("count", 10),
ctx,
)
func FetchPropertyAs ¶ added in v0.4.0
FetchPropertyAs retrieves a single scalar or object property from an OData entity using the standard OData property path pattern:
GET /EntitySet(Key)/PropertyName
This is the idiomatic way to fetch one field from a known entity without downloading the full record. Useful for large entities where only a single field (e.g. a price, a flag, or a blob link) is needed.
The qb parameter must already point to the full entity including its key, e.g. built via:
qb := client.From("/sap/opu/odata/sap/UI_PRODUCTLIST/ProductList(Product='3001008',Plant='1010',ValuationType='')")
price, err := traverse.FetchPropertyAs[string](qb, ctx, "PriceUnitQty")
Returns the zero value of T and an error if the property is not found or cannot be decoded.
func FindByKeyAs ¶
func FindByKeyAs[T any](qb *QueryBuilder, ctx context.Context, key interface{}) (T, error)
FindByKeyAs is an alias for FindByKeyJsonAs for backward compatibility. Deprecated: Use FindByKeyJsonAs or FindByKeyXmlAs instead.
func FindByKeyJsonAs ¶ added in v0.16.0
func FindByKeyJsonAs[T any](qb *QueryBuilder, ctx context.Context, key interface{}) (T, error)
FindByKeyJsonAs retrieves a single entity by its key and decodes it to type T using JSON.
FindByKeyJsonAs is the JSON-format variant of QueryBuilder.FindByKey. It constructs a single-entity query using the provided key and returns the entity as type T.
The key can be a single value (for single-part keys) or a composite key using a map (for entities with compound keys).
Returns the entity as type T, or an error if not found or query fails.
Example:
type Customer struct {
ID int `json:"id"`
Name string `json:"name"`
}
customer, err := FindByKeyJsonAs[Customer](qb, ctx, 42)
// or with composite key:
customer, err := FindByKeyJsonAs[Customer](qb, ctx, map[string]interface{}{"CompanyID": 1, "CustomerID": 42})
func FindByKeyXmlAs ¶ added in v0.16.0
func FindByKeyXmlAs[T any](qb *QueryBuilder, ctx context.Context, key interface{}) (T, error)
FindByKeyXmlAs retrieves a single entity by its key and decodes it to type T with XML struct tags.
FindByKeyXmlAs is the XML-tag variant of QueryBuilder.FindByKey. It constructs a single-entity query using the provided key and returns the entity as type T.
The "XmlAs" suffix indicates that the target struct T has xml:"..." tags for field mapping, not that the server response is XML. The server response is always JSON, which is unmarshaled into structs with XML tags.
The key can be a single value (for single-part keys) or a composite key using a map (for entities with compound keys).
Returns the entity as type T, or an error if not found or query fails.
Example:
type Customer struct {
ID int `xml:"id"`
Name string `xml:"name"`
}
customer, err := FindByKeyXmlAs[Customer](qb, ctx, 42)
// or with composite key:
customer, err := FindByKeyXmlAs[Customer](qb, ctx, map[string]interface{}{"CompanyID": 1, "CustomerID": 42})
func FirstAs ¶
func FirstAs[T any](qb *QueryBuilder, ctx context.Context) (T, error)
FirstAs is an alias for FirstJsonAs for backward compatibility. Deprecated: Use FirstJsonAs or FirstXmlAs instead.
func FirstJsonAs ¶ added in v0.16.0
func FirstJsonAs[T any](qb *QueryBuilder, ctx context.Context) (T, error)
FirstJsonAs retrieves the first result from the query and decodes it to type T using JSON.
FirstJsonAs is the JSON-format version of QueryBuilder.First. It executes the query with the $top=1 modifier to retrieve only the first matching entity, then unmarshals it to type T.
This is efficient for single-item lookups and is equivalent to:
result, _ := FirstJsonAs[T](qb, ctx) // vs item, _ := qb.Top(1).First(ctx) item, _ := mapToJsonStruct[T](item)
Returns the first entity as type T, or an error if the query fails or no results match.
Example:
type Customer struct {
ID int `json:"id"`
Name string `json:"name"`
}
customer, err := FirstJsonAs[Customer](qb.Filter("Name eq 'Alice'"), ctx)
func FirstXmlAs ¶ added in v0.16.0
func FirstXmlAs[T any](qb *QueryBuilder, ctx context.Context) (T, error)
FirstXmlAs retrieves the first result from the query and decodes it to type T with XML struct tags.
FirstXmlAs is the XML-tag version of QueryBuilder.First. It executes the query with the $top=1 modifier to retrieve only the first matching entity, then unmarshals it to type T.
The "XmlAs" suffix indicates that the target struct T has xml:"..." tags for field mapping, not that the server response is XML. The server response is always JSON.
Returns the first entity as type T, or an error if the query fails or no results match.
Example:
type Customer struct {
ID int `xml:"id"`
Name string `xml:"name"`
}
customer, err := FirstXmlAs[Customer](qb.Filter("Name eq 'Alice'"), ctx)
func GeoDistanceFilter ¶ added in v0.10.0
func GeoDistanceFilter(property string, point GeographyPoint, operator string, maxDistance float64) string
GeoDistanceFilter returns an OData geo.distance() filter expression string.
The generated expression can be used directly with Filter():
client.From("Shops").Filter(traverse.GeoDistanceFilter("Location", traverse.GeographyPoint{13.408, 52.518}, "le", 1000))
// $filter=geo.distance(Location,geography'SRID=4326;POINT(13.408 52.518)') le 1000
The operator must be one of: lt, le, gt, ge, eq, ne. The maxDistance is the threshold value (in the same unit as the CRS - typically metres).
func GeoDistanceFilterGeom ¶ added in v0.10.0
func GeoDistanceFilterGeom(property string, point GeometryPoint, operator string, maxDistance float64) string
GeoDistanceFilterGeom returns an OData geo.distance() filter expression for geometry coordinates.
func GeoIntersectsFilter ¶ added in v0.10.0
func GeoIntersectsFilter(property string, polygon GeographyPolygon) string
GeoIntersectsFilter returns an OData geo.intersects() filter expression string.
geo.intersects(property, polygon) returns true when the point property falls within the given polygon boundary.
client.From("POI").Filter(traverse.GeoIntersectsFilter("Coordinates", myPolygon))
// $filter=geo.intersects(Coordinates,geography'SRID=4326;POLYGON(...)')
func GeoIntersectsFilterGeom ¶ added in v0.10.0
func GeoIntersectsFilterGeom(property string, polygon GeometryPolygon) string
GeoIntersectsFilterGeom returns an OData geo.intersects() filter expression for geometry.
func GeoLengthFilter ¶ added in v0.10.0
GeoLengthFilter returns an OData geo.length() filter expression string.
geo.length(linestring_property) returns the length of the LineString in the same unit as the coordinate reference system.
client.From("Routes").Filter(traverse.GeoLengthFilter("Path", "le", 50000))
// $filter=geo.length(Path) le 50000
func GlobalCacheSize ¶
func GlobalCacheSize() int
GlobalCacheSize returns the current size of the global string interning cache.
GlobalCacheSize returns the number of unique interned strings in the global cache. Use this to monitor cache growth and detect potential memory leaks.
func GuidValue ¶
GuidValue produces an OData Guid literal for use in filter expressions.
GuidValue wraps the provided GUID string in the OData Guid format suitable for $filter expressions on Guid fields.
Returns string in the format: guid'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
Example (used in filters):
qb.Filter(fmt.Sprintf("ID eq %s", GuidValue("550e8400-e29b-41d4-a716-446655440000")))
func InternString ¶
InternString interns a string using the global interning cache.
InternString should be used for frequently repeated strings like entity names and property names that appear across millions of records.
The global cache is automatically initialized and can be managed with ClearGlobalCache and GlobalCacheSize.
Example:
propName := traverse.InternString("CustomerName") // first call allocates
propName = traverse.InternString("CustomerName") // second call returns cached ref
func IsAtomContentType ¶ added in v0.8.0
IsAtomContentType returns true when the Content-Type header indicates an Atom/XML feed.
func IsConcurrencyConflict ¶
IsConcurrencyConflict returns true if err is or wraps ErrConcurrencyConflict.
IsConcurrencyConflict indicates whether the error is due to an ETag mismatch (concurrent modification by another client). This typically requires the caller to retry with a fresh entity version.
func IsEntityNotFound ¶
IsEntityNotFound returns true if err is or wraps ErrEntityNotFound.
IsEntityNotFound provides a convenient way to check if a specific error is a "not found" error without type assertion.
func IsOf ¶ added in v0.6.0
IsOf returns an OData filter expression string that tests whether entities or a property value are of the given type (isof() function).
IsOf implements the OData v4.0 type system function defined in spec section 5.1.1.6.3. Two forms are supported:
- isof(TypeName) - tests whether the entity itself is of the given type
- isof(propertyPath, TypeName) - tests whether a property is of the given type
The result can be used directly in QueryBuilder.Filter.
Example:
// $filter=isof(Model.Manager)
client.From("Employees").Filter(traverse.IsOf("Model.Manager")).Collect(ctx)
// $filter=isof(Address, Model.CnAddress)
client.From("People").Filter(traverse.IsOf("Address", "Model.CnAddress")).Collect(ctx)
func ParseAtomFeed ¶ added in v0.8.0
atomFeedParser implements token-by-token streaming of an OData v2 Atom/XML feed.
The Atom format for OData v2 uses the following structure:
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:count>100</m:count>
<link rel="next" href="..."/>
<entry>
<id>...</id>
<content type="application/xml">
<m:properties>
<d:OrderID m:type="Edm.Int32">1</d:OrderID>
<d:Status>Active</d:Status>
</m:properties>
</content>
</entry>
</feed>
ParseAtomFeed reads directly from the io.Reader and populates the Page.
func ReleaseGeographyPoint ¶ added in v0.10.0
func ReleaseGeographyPoint(p *GeographyPoint)
ReleaseGeographyPoint returns the point to the pool.
func ReleaseGeometryPoint ¶ added in v0.10.0
func ReleaseGeometryPoint(p *GeometryPoint)
ReleaseGeometryPoint returns the point to the pool.
func SingletonAs ¶ added in v0.6.0
SingletonAs fetches a singleton resource and decodes it into the provided type T.
SingletonAs is a convenience wrapper around Client.Singleton that handles the common pattern of fetching a singleton and decoding it into a typed struct.
Example:
type Me struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
Mail string `json:"mail"`
}
me, err := traverse.SingletonAs[Me](client, "me")
func SingletonAsCtx ¶ added in v0.6.0
SingletonAsCtx fetches a singleton resource and decodes it into the provided type T, using the provided context for cancellation and deadline control.
Example:
me, err := traverse.SingletonAsCtx[Me](client, ctx, "me")
func StreamAs ¶
StreamAs is an alias for StreamJsonAs for backward compatibility. Deprecated: Use StreamJsonAs or StreamXmlAs instead.
func StreamJsonAs ¶ added in v0.16.0
StreamJsonAs is the JSON-format streaming method for type T.
StreamJsonAs returns a channel of Result items typed to T, enabling incremental processing of large result sets without materializing all data in memory. Each result can be of type T or contain an error.
The method is optimized to use [QueryBuilder.streamRaw] for direct JSON unmarshaling, avoiding the intermediate map[string]interface{} allocation required by CollectJsonAs. This makes it significantly faster for streaming large datasets.
The bufferSize parameter controls the capacity of the result channel (default 256). For large record sizes or high network latency, increase this value to reduce blocking. For small records, the default is usually optimal.
Results include pagination information (Page, Index) for tracking position within large result sets.
Returns a receive-only channel that yields Result items as they become available. The channel is closed when all results have been processed or an error occurs.
Example:
type Product struct {
ID int `json:"id"`
Price float64 `json:"price"`
}
// Stream 1 million products incrementally
results := StreamJsonAs[Product](qb.Filter("Price gt 50"), ctx, 512)
for result := range results {
if result.Err != nil {
log.Println("Error:", result.Err)
continue
}
product := result.Value
fmt.Printf("Product %d (page %d): $%.2f\n", product.ID, result.Page, product.Price)
}
func StreamXmlAs ¶ added in v0.16.0
StreamXmlAs is the XML-tag streaming method for type T.
StreamXmlAs returns a channel of Result items typed to T, enabling incremental processing of large result sets without materializing all data in memory. Each result can be of type T or contain an error.
The "XmlAs" suffix indicates that the target struct T has xml:"..." tags for field mapping, not that the server returns XML responses. The server response is always JSON, which is unmarshaled into structs with XML tags.
The bufferSize parameter controls the capacity of the result channel (default 256). For large record sizes or high network latency, increase this value to reduce blocking. For small records, the default is usually optimal.
Results include pagination information (Page, Index) for tracking position within large result sets.
Returns a receive-only channel that yields Result items as they become available. The channel is closed when all results have been processed or an error occurs.
Example:
type Product struct {
ID int `xml:"id"`
Price float64 `xml:"price"`
}
// Stream 1 million products
results := StreamXmlAs[Product](qb.Filter("Price gt 50"), ctx, 512)
for result := range results {
if result.Err != nil {
log.Println("Error:", result.Err)
continue
}
product := result.Value
fmt.Printf("Product %d (page %d): $%.2f\n", product.ID, result.Page, product.Price)
}
func UpdateAs ¶
func UpdateAs[T any](c *Client, ctx context.Context, entitySet string, key interface{}, data interface{}) error
UpdateAs is the generic version of Client.Update.
UpdateAs updates an existing entity identified by its key. The update data is marshaled to JSON and sent as a PATCH request.
Note: OData PATCH requests typically do not return an entity body in the response, so this is primarily a type-safe wrapper around Client.Update. The generic type parameter T is included for API consistency but does not affect the response.
Returns an error if the update fails (entity not found, invalid data, etc.).
Example:
err := UpdateAs[Order](client, ctx, "Orders", 123, map[string]interface{}{"total": 150.00})
func ValidateFilter ¶
ValidateFilter performs basic OData filter syntax validation.
ValidateFilter checks the filter expression for common syntax errors such as:
- Unbalanced parentheses
- Unbalanced quotes
- Invalid operator usage
- Unknown function names
ValidateFilter is a permissive validator that only catches obvious errors, not all possible invalid syntax. An empty filter expression is considered valid.
This is typically called before executing a query to provide early error detection, avoiding unnecessary server round-trips for invalid filters.
func XmlBytesToStruct ¶ added in v0.21.3
XmlBytesToStruct converts raw XML bytes directly to a typed value T with XML struct tags.
XmlBytesToStruct performs true XML unmarshaling using xml.Unmarshal, which respects xml:"..." struct tags. This is the correct method for handling native XML responses from OData v2 services that return Atom+XML format (e.g., SAP systems).
This method is used internally by the XML decoding path when Content-Type is XML or the response body starts with '<'.
Example:
type Order struct {
ID int `xml:"id"`
Total float64 `xml:"total"`
}
rawXML := []byte(`<Order><id>123</id><total>99.99</total></Order>`)
order, err := XmlBytesToStruct[Order](rawXML)
Types ¶
type ActionBuilder ¶
type ActionBuilder struct {
// contains filtered or unexported fields
}
ActionBuilder provides a fluent API for calling OData Actions (v4).
ActionBuilder enables execution of OData Action calls, which may perform side effects (create, update, delete). Actions use HTTP POST and can include both body data and parameters.
Typical usage:
result, err := client.Action("ApproveOrder").
WithBody(orderData).
Param("approverID", 42).
Execute(ctx)
func (*ActionBuilder) Execute ¶
func (a *ActionBuilder) Execute(ctx context.Context) (map[string]interface{}, error)
Execute calls the action and returns the result as a map.
Execute sends an HTTP POST request to the action with optional body and parameter data. The request body is marshaled to JSON automatically. The response is parsed into a map.
If ActionBuilder.WithBody was called, that data is sent as the primary body. Otherwise, if parameters were added via ActionBuilder.Param, they are sent as JSON. If neither, an empty POST is sent.
Returns a map containing the action response, or an error if the call fails.
Example:
result, err := client.Action("ApproveOrder").
WithBody(approvalData).
Execute(ctx)
func (*ActionBuilder) Invoke ¶ added in v0.2.11
func (a *ActionBuilder) Invoke(ctx context.Context, result any) error
Invoke calls the action and unmarshals the response into result.
Invoke is the result-receiver variant of ActionBuilder.Execute. It sends an HTTP POST request to the OData action URL and unmarshals the response body into the value pointed to by result. result may be nil when no response body is expected.
If ActionBuilder.WithBody was called, that data forms the request body. Otherwise parameters added via ActionBuilder.Param are sent as JSON.
Returns an error if the HTTP call fails, the response status is not 2xx, or unmarshaling fails.
Example:
type ApprovalResult struct {
Approved bool `json:"approved"`
Message string `json:"message"`
}
var res ApprovalResult
err := client.Action("ApproveOrder").Param("orderID", 42).Invoke(ctx, &res)
func (*ActionBuilder) Param ¶
func (a *ActionBuilder) Param(key string, value interface{}) *ActionBuilder
Param adds a parameter to the action call.
Param appends a key-value parameter for the action. Parameters can be sent in the request body as JSON.
Returns the receiver for method chaining.
func (*ActionBuilder) WithBody ¶
func (a *ActionBuilder) WithBody(data interface{}) *ActionBuilder
WithBody sets the request body for the action.
WithBody sets the main request body data for the action. The data is automatically marshaled to JSON.
Returns the receiver for method chaining.
Example:
action.WithBody(map[string]interface{}{
"orderID": 12345,
"approvedBy": "Manager",
})
type ActionInfo ¶
type ActionInfo struct {
Name string
Parameters []FunctionParameter
ReturnType string
}
ActionInfo represents an OData action (v4).
type AnalyticsVocabulary ¶ added in v0.8.0
type AnalyticsVocabulary struct {
// AggregationMethod is the aggregation function to apply to this property.
// Common values: "sum", "min", "max", "average", "count", "countdistinct".
// Corresponds to Org.OData.Aggregation.V1.default.
AggregationMethod string
// IsDimension is true when the property is an analytical dimension.
// Corresponds to Org.OData.Aggregation.V1.Dimensionality="Dimension".
IsDimension bool
// IsMeasure is true when the property is an analytical measure.
// Corresponds to Org.OData.Aggregation.V1.Dimensionality="Measure".
IsMeasure bool
// RollupLevels specifies the hierarchy levels for dimension rollup.
// Corresponds to Org.OData.Aggregation.V1.RollupLevels.
RollupLevels int
// ReferencedProperties lists properties that this aggregation references.
// Corresponds to Org.OData.Aggregation.V1.ReferencedProperties.
ReferencedProperties []string
// GroupableProperties lists the properties that can be used in $apply groupby().
// Corresponds to Org.OData.Aggregation.V1.GroupableProperties.
GroupableProperties []string
// AggregatableProperties lists properties that can be aggregated via $apply aggregate().
// Corresponds to Org.OData.Aggregation.V1.AggregatableProperties.
AggregatableProperties []string
}
AnalyticsVocabulary defines Org.OData.Aggregation.V1 annotation terms for analytics.
The Aggregation (Analytics) vocabulary annotates entity types and properties with analytical semantics, indicating which properties are dimensions, measures, or aggregation methods. These annotations are defined in the OASIS OData Aggregation vocabulary (namespace: Org.OData.Aggregation.V1), and also appear in the SAP Analytics vocabulary (namespace: com.sap.vocabularies.Analytics.v1).
func ParseAnalyticsVocabulary ¶ added in v0.8.0
func ParseAnalyticsVocabulary(annotations map[string]string) AnalyticsVocabulary
ParseAnalyticsVocabulary extracts Org.OData.Aggregation.V1 (and SAP Analytics v1) annotation terms from a raw annotation map.
Both the OASIS aggregation namespace and the SAP-specific analytics namespace are recognised, so annotations from either source populate the same struct.
Example annotations map:
map[string]string{
"Org.OData.Aggregation.V1.default": "sum",
"Org.OData.Aggregation.V1.RollupLevels": "3",
"com.sap.vocabularies.Analytics.v1.Dimension": "true",
"com.sap.vocabularies.Analytics.v1.Measure": "false",
}
type AnnotatedEntity ¶ added in v0.2.27
type AnnotatedEntity[T any] struct { // Entity holds the decoded entity value. Entity T // Annotations holds all '@'-prefixed annotation properties as raw JSON. Annotations map[string]json.RawMessage }
AnnotatedEntity wraps a decoded entity and exposes its OData instance annotations.
OData responses may include annotation properties alongside entity data. Annotations are any property whose key begins with '@', such as '@odata.etag', '@odata.count', or custom vocabulary terms like '@Custom.Score'.
Example usage:
result, err := DecodeAnnotated[Product](data)
if err != nil { ... }
fmt.Println(result.Entity.Name)
var etag string
_ = result.GetAnnotation("@odata.etag", &etag)
func DecodeAnnotated ¶ added in v0.2.27
func DecodeAnnotated[T any](data []byte) (AnnotatedEntity[T], error)
DecodeAnnotated decodes an OData JSON object, separating '@'-prefixed annotation properties from the regular entity fields.
The raw JSON object is first unmarshalled into a map. Keys beginning with '@' are stored as Annotations; the remaining keys are re-encoded and decoded into T.
func (*AnnotatedEntity[T]) GetAnnotation ¶ added in v0.2.27
func (a *AnnotatedEntity[T]) GetAnnotation(name string, target any) error
GetAnnotation retrieves a typed annotation value by name and unmarshals it into target.
Returns an error if the annotation is not present or cannot be decoded into target.
Example:
var etag string
if err := result.GetAnnotation("@odata.etag", &etag); err != nil { ... }
type Association ¶
type Association struct {
// Name is the name of the association.
Name string
// From is the source end of the association.
From AssociationEnd
// To is the target end of the association.
To AssociationEnd
}
Association represents an OData association between entity types.
type AssociationEnd ¶
type AssociationEnd struct {
// EntityType is the fully qualified entity type name at this end.
EntityType string
// Multiplicity describes the multiplicity (0..1, 1, *).
Multiplicity string
}
AssociationEnd represents one end of an OData association.
type AsyncOpPoller ¶ added in v0.2.6
type AsyncOpPoller struct {
// contains filtered or unexported fields
}
AsyncOpPoller polls an OData async operation status URL until completion.
Typical OData async flow:
- POST/DELETE/PATCH with "Prefer: respond-async" header
- Server responds 202 Accepted + Location header (status URL)
- Poll Location URL until 200/204 (done) or 4xx/5xx (failed)
Usage:
resp, err := client.Execute(...) // initial request returns 202
if resp.StatusCode == 202 {
poller := client.NewAsyncPoller(resp.Headers.Get("Location"))
result, err := poller.Wait(ctx)
}
func (*AsyncOpPoller) Wait ¶ added in v0.2.6
func (p *AsyncOpPoller) Wait(ctx context.Context) (*AsyncResult, error)
Wait polls the status URL until the operation completes or ctx is cancelled. Returns ErrAsyncOpFailed if the server signals failure, ErrAsyncOpTimeout if maxPolls is exhausted before completion.
func (*AsyncOpPoller) WithMaxPolls ¶ added in v0.2.6
func (p *AsyncOpPoller) WithMaxPolls(n int) *AsyncOpPoller
WithMaxPolls sets the maximum number of poll attempts before giving up. Use 0 for unlimited (rely on ctx cancellation).
func (*AsyncOpPoller) WithPollInterval ¶ added in v0.2.6
func (p *AsyncOpPoller) WithPollInterval(d time.Duration) *AsyncOpPoller
WithPollInterval sets the interval between poll attempts.
type AsyncOpStatus ¶ added in v0.2.6
type AsyncOpStatus int
AsyncOpStatus represents the state of a long-running OData async operation.
const ( // AsyncOpRunning indicates the operation is still in progress. AsyncOpRunning AsyncOpStatus = iota // AsyncOpSucceeded indicates the operation completed successfully. AsyncOpSucceeded // AsyncOpFailed indicates the operation failed. AsyncOpFailed // AsyncOpCancelled indicates the operation was cancelled by the server. AsyncOpCancelled )
func (AsyncOpStatus) String ¶ added in v0.2.6
func (s AsyncOpStatus) String() string
String returns a human-readable name for the status.
type AsyncResult ¶ added in v0.2.6
type AsyncResult struct {
// Status is the final resolved status.
Status AsyncOpStatus
// StatusCode is the HTTP status code of the final poll response.
StatusCode int
// Body is the raw response body (may be nil for 204 No Content).
Body []byte
}
AsyncResult holds the outcome of a completed async OData operation.
type AuthorizationVocabulary ¶ added in v0.8.0
type AuthorizationVocabulary struct {
// Authorizations is the list of named authorization schemes that apply.
// Corresponds to Org.OData.Authorization.V1.Authorizations.
// Each entry is the Name of a SecurityScheme defined on the service.
Authorizations []string
// RequiredScopes is the list of OAuth2/OpenID scopes required.
// Corresponds to Org.OData.Authorization.V1.RequiredScopes.
RequiredScopes []string
// SecuritySchemeType identifies the type of scheme: "ApiKey", "Http", "OAuth2", "OpenIDConnect".
// Set on service-level annotations that define the security scheme itself.
SecuritySchemeType string
// KeyName is the name of the API key parameter (for ApiKey schemes).
// Corresponds to Org.OData.Authorization.V1.KeyName.
KeyName string
// KeyLocation is where the API key is sent: "header", "query", "cookie" (for ApiKey schemes).
// Corresponds to Org.OData.Authorization.V1.Location.
KeyLocation string
// Scheme is the HTTP authentication scheme (for Http type), e.g. "bearer", "basic".
// Corresponds to Org.OData.Authorization.V1.Scheme.
Scheme string
// BearerFormat describes the bearer token format (for Http+bearer), e.g. "JWT".
// Corresponds to Org.OData.Authorization.V1.BearerFormat.
BearerFormat string
// AuthorizationURL is the OAuth2 authorization endpoint URL.
// Corresponds to Org.OData.Authorization.V1.AuthorizationURL.
AuthorizationURL string
// TokenURL is the OAuth2 token endpoint URL.
// Corresponds to Org.OData.Authorization.V1.TokenURL.
TokenURL string
// OpenIDConnectURL is the OpenID Connect discovery document URL.
// Corresponds to Org.OData.Authorization.V1.OpenIDConnectUrl.
OpenIDConnectURL string
}
AuthorizationVocabulary defines Org.OData.Authorization.V1 annotation terms.
The Authorization vocabulary annotates services and operations with the security schemes required to access them. These annotations are defined in the OASIS OData Authorization vocabulary (namespace: Org.OData.Authorization.V1).
func ParseAuthorizationVocabulary ¶ added in v0.8.0
func ParseAuthorizationVocabulary(annotations map[string]string) AuthorizationVocabulary
ParseAuthorizationVocabulary extracts Org.OData.Authorization.V1 annotation terms from a raw annotation map. The map keys are fully-qualified term names (e.g. "Org.OData.Authorization.V1.Authorizations").
Example annotations map:
map[string]string{
"Org.OData.Authorization.V1.Authorizations": "OAuth2Implicit",
"Org.OData.Authorization.V1.RequiredScopes": "read:data,write:data",
"Org.OData.Authorization.V1.Scheme": "bearer",
"Org.OData.Authorization.V1.BearerFormat": "JWT",
}
type BatchOperation ¶
type BatchOperation struct {
// Method is the HTTP method (GET, POST, PATCH, DELETE)
Method string
// URL is the entity set or entity reference
URL string
// Headers are operation-specific HTTP headers
Headers map[string]string
// Body is the request body as raw JSON (for POST/PATCH)
Body json.RawMessage
// ChangesetID identifies the changeset this operation belongs to
ChangesetID string
}
BatchOperation represents a single operation within a batch request.
BatchOperation encapsulates a single OData operation (GET, POST, PATCH, DELETE) with its method, URL, headers, and optional body data.
func (*BatchOperation) SetBody ¶
func (op *BatchOperation) SetBody(data interface{}) error
SetBody sets the Body field, marshaling data if needed.
SetBody converts the provided data to JSON and stores it as a json.RawMessage in the Body field. Returns an error if JSON marshaling fails. Nil data results in a nil Body.
This helper maintains API compatibility while optimizing allocations by using raw JSON instead of unmarshaling on the server side.
type BatchRequest ¶
type BatchRequest struct {
// contains filtered or unexported fields
}
BatchRequest represents a collection of OData operations to execute as a batch.
BatchRequest builds a $batch request combining multiple operations (GET, POST, PATCH, DELETE) into a single HTTP request. Operations can be grouped into changesets for atomic transactions.
Typical usage:
resp, err := client.Batch().
Get("Customers", 1).
Create("Orders", orderData).
Execute(ctx)
For transactional operations, use BeginChangeset/EndChangeset:
resp, err := client.Batch().
BeginChangeset("tx1").
Create("Orders", order).
Update("Customers", 1, updateData).
EndChangeset().
Execute(ctx)
func (*BatchRequest) BeginChangeset ¶
func (b *BatchRequest) BeginChangeset(id string) *BatchRequest
BeginChangeset starts a changeset (atomic transaction group).
BeginChangeset marks the beginning of a set of operations that should be executed atomically. All operations added after this call (until EndChangeset) belong to this changeset. Multiple changesets can exist in a single batch.
The id parameter is used to group related operations. If a changeset was already open, it is closed first.
Returns the receiver for method chaining.
Example:
batch.BeginChangeset("group-1").
Create("Orders", order).
Create("OrderItems", item1).
EndChangeset()
func (*BatchRequest) Create ¶
func (b *BatchRequest) Create(entitySet string, data interface{}) *BatchRequest
Create adds a POST (create) operation to the batch.
Create inserts a new entity into the specified entity set. The data is marshaled to JSON automatically. If Create is called within a changeset (between BeginChangeset and EndChangeset), the operation becomes part of that transaction.
Returns the receiver for method chaining.
Example:
batch.Create("Orders", map[string]interface{}{"OrderID": 123, "Total": 99.99})
func (*BatchRequest) Delete ¶
func (b *BatchRequest) Delete(entitySet string, key interface{}) *BatchRequest
Delete adds a DELETE operation to the batch.
Delete removes an entity identified by its key. If called within a changeset, the deletion becomes part of that atomic transaction.
Returns the receiver for method chaining.
Example:
batch.Delete("Orders", 123)
func (*BatchRequest) EndChangeset ¶
func (b *BatchRequest) EndChangeset() *BatchRequest
EndChangeset ends the current changeset.
EndChangeset closes the active changeset, storing it for later execution. All subsequent operations will be standalone batch operations (not in a changeset) until another BeginChangeset is called.
Returns the receiver for method chaining.
Example:
batch.BeginChangeset("tx1").
Create("Orders", orderData).
EndChangeset().
Get("Customers", 1)
func (*BatchRequest) Execute ¶
func (b *BatchRequest) Execute(ctx context.Context) (*BatchResponse, error)
Execute sends the batch request and returns all results at once.
Execute constructs a multipart/mixed HTTP request containing all batch operations and changesets, sends it to the OData service, and parses the response. The operation automatically closes any open changeset before execution.
All results are materialized into memory before returning. For large batches, consider using BatchRequest.ExecuteStream for memory-efficient incremental processing.
On error, returns a non-nil error. The returned BatchResponse contains individual results for each operation, even if some operations failed.
Example:
resp, err := batch.Execute(ctx)
if err != nil {
log.Fatal(err)
}
for _, result := range resp.Results {
fmt.Println(result.Status, result.Data)
}
func (*BatchRequest) ExecuteJSON ¶ added in v0.13.0
func (b *BatchRequest) ExecuteJSON(ctx context.Context) (*BatchResponse, error)
ExecuteJSON sends the batch request using the OData 4.01 JSON batch format.
ExecuteJSON is an alternative to BatchRequest.Execute that uses JSON encoding instead of multipart/mixed. The OData 4.01 JSON batch format is more compact, easier to debug, and supported by modern OData services.
OData 4.01 spec reference: section 18 (Batch Requests and Responses).
Operations within changesets are grouped using the "atomicityGroup" property. All operations in a changeset must succeed or all will be rolled back.
Example:
resp, err := client.Batch().
Get("Products", 1).
Get("Products", 2).
ExecuteJSON(ctx)
if err != nil {
log.Fatal(err)
}
for _, result := range resp.Results {
fmt.Println(result.StatusCode, string(result.Body))
}
func (*BatchRequest) ExecuteJSONStream ¶ added in v0.13.0
func (b *BatchRequest) ExecuteJSONStream(ctx context.Context) <-chan BatchResult
ExecuteJSONStream sends the batch using JSON format and streams results via a channel.
ExecuteJSONStream is the streaming variant of BatchRequest.ExecuteJSON. It reads the entire JSON response but emits results to the channel one by one to allow concurrent processing.
Example:
for result := range batch.ExecuteJSONStream(ctx) {
if result.Err != nil {
log.Println("Operation failed:", result.Err)
continue
}
fmt.Println(result.StatusCode, string(result.Body))
}
func (*BatchRequest) ExecuteStream ¶
func (b *BatchRequest) ExecuteStream(ctx context.Context) <-chan BatchResult
ExecuteStream sends the batch request and streams results incrementally via a channel.
ExecuteStream is more memory-efficient than BatchRequest.Execute for large batches. Results are parsed and sent to the returned channel as they arrive, allowing processing to begin before the entire response is received. The operation automatically closes any open changeset before execution.
The returned channel is buffered with capacity 8 and will be closed when all results have been sent or an error occurs. Errors are sent as BatchResult items with non-nil Err field.
Example:
results := batch.ExecuteStream(ctx)
for result := range results {
if result.Err != nil {
log.Println("Operation failed:", result.Err)
continue
}
fmt.Println(result.Status, string(result.Body))
}
func (*BatchRequest) Get ¶
func (b *BatchRequest) Get(entitySet string, key interface{}) *BatchRequest
Get adds a GET operation to the batch.
Get retrieves a single entity by its key and adds the operation to the batch. The operation is read-only and cannot be part of a changeset.
Returns the receiver for method chaining.
Example:
batch.Get("Customers", 1)
func (*BatchRequest) Update ¶
func (b *BatchRequest) Update(entitySet string, key interface{}, data interface{}) *BatchRequest
Update adds a PATCH (update) operation to the batch.
Update modifies an existing entity identified by its key. The data is marshaled to JSON and sent as PATCH. If called within a changeset, the operation becomes part of that atomic transaction.
Returns the receiver for method chaining.
Example:
batch.Update("Orders", 123, map[string]interface{}{"Total": 150.00})
type BatchResponse ¶
type BatchResponse struct {
// Results is the slice of results, one per operation
Results []BatchResult
}
BatchResponse contains the results of all operations in a batch request, with one BatchResult entry per operation in the same order as submitted.
type BatchResult ¶
type BatchResult struct {
// StatusCode is the HTTP status code of the operation
StatusCode int
// Headers are the response headers
Headers map[string]string
// Body is the response body
Body []byte
// Err is an error if one occurred
Err error
}
BatchResult represents the result of a single operation in the batch.
BatchResult contains the HTTP response status, headers, and body from one operation within a batch request.
type Binary ¶
type Binary []byte
Binary represents an OData Edm.Binary value (base64 encoded binary data).
Binary is a byte slice with custom JSON marshaling/unmarshaling for base64 encoded binary data. This is used for BLOB/binary fields in OData services.
Example formats in JSON responses:
"aGVsbG8gd29ybGQ=" // base64 encoded: "hello world"
Example:
type Document struct {
Content traverse.Binary `json:"content"`
}
json.Unmarshal([]byte(`{"content":"aGVsbG8="}`), &doc)
fmt.Println(string(doc.Content)) // "hello"
func (Binary) MarshalJSON ¶
MarshalJSON encodes binary data as a base64 JSON string.
MarshalJSON converts the raw binary bytes to a standard base64-encoded string (RFC 4648 §4) and wraps it in JSON quotes, matching the OData v4 Edm.Binary wire format.
func (*Binary) UnmarshalJSON ¶
UnmarshalJSON decodes base64 encoded binary data.
UnmarshalJSON accepts a JSON string containing standard or URL-safe base64 encoded binary data and decodes it to the internal byte slice. OData v4 uses standard base64 (RFC 4648 §4); some SAP services use URL-safe base64 (RFC 4648 §5). Both are tried in order.
Returns an error if the input is not a valid JSON string or cannot be decoded as base64.
type CacheStore ¶
type CacheStore interface {
// Get retrieves cached metadata by key.
// Returns nil and false if key is not found.
Get(key string) (*Metadata, bool)
// Set stores metadata in the cache with the given key.
Set(key string, metadata *Metadata)
// Clear removes all entries from the cache.
Clear()
}
CacheStore is an interface for caching OData metadata.
CacheStore provides a contract for caching Metadata retrieved from OData services. Different implementations can use in-memory storage, Redis, file-based caching, or other strategies.
This interface allows the client to optimize metadata fetches by caching the service schema across requests, avoiding repeated $metadata calls for the same service.
Example custom implementation:
type RedisCache struct {
client *redis.Client
}
func (c *RedisCache) Get(key string) (*Metadata, bool) { ... }
func (c *RedisCache) Set(key string, metadata *Metadata) { ... }
func (c *RedisCache) Clear() { ... }
type CapabilitiesRegistry ¶ added in v0.2.21
type CapabilitiesRegistry struct {
// contains filtered or unexported fields
}
CapabilitiesRegistry holds parsed capabilities for all entity sets.
func NewCapabilitiesRegistry ¶ added in v0.2.21
func NewCapabilitiesRegistry() *CapabilitiesRegistry
NewCapabilitiesRegistry creates a new empty registry.
func ParseCapabilities ¶ added in v0.2.21
func ParseCapabilities(edmxXML []byte) (*CapabilitiesRegistry, error)
ParseCapabilities parses an OData v4 EDMX metadata document.
func (*CapabilitiesRegistry) Get ¶ added in v0.2.21
func (r *CapabilitiesRegistry) Get(entitySet string) EntityCapabilities
Get returns the capabilities for the named entity set.
type CapabilityError ¶ added in v0.2.21
CapabilityError is returned when a requested operation is not supported.
func (*CapabilityError) Error ¶ added in v0.2.21
func (e *CapabilityError) Error() string
Error implements the error interface.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
func New ¶
New creates a new Client with the provided options. At minimum, WithBaseURL must be provided unless a pre-configured relay client is passed via WithRelayClient, in which case the base URL is inherited from the relay client automatically. Returns an error if required options are missing or invalid.
Example:
client, err := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v2"),
traverse.WithBasicAuth("user", "pass"),
traverse.WithPageSize(5000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
func NewGraphClient ¶ added in v0.2.15
func NewGraphClient(relayClient *relay.Client, cfg GraphConfig) *Client
func (*Client) Action ¶
func (c *Client) Action(name string) *ActionBuilder
Action starts building a call to an OData Action (v4).
Action creates a new ActionBuilder for calling OData Actions. Actions may perform side effects and can include both request bodies and parameters.
Returns an ActionBuilder ready for body/parameter configuration and execution.
Example:
result, err := client.Action("ApproveOrder").
WithBody(approvalData).
Param("approverID", emp123).
Execute(ctx)
func (*Client) BaseURL ¶
BaseURL returns the base URL of the OData service that this Client is connected to.
BaseURL returns the configured service root URL, which serves as the base for all OData entity set operations. This is the URL passed to New() via WithBaseURL().
The returned URL typically has the form: https://odata.example.com/v4 or for SAP systems: https://sap.example.com/sap/opu/odata/sap/ZESX_PRODUCT/
Returns:
- The base URL string (always non-empty for a valid client)
Example:
url := client.BaseURL() // Returns: "https://odata.example.com/v4"
func (*Client) Batch ¶
func (c *Client) Batch() *BatchRequest
Batch starts building a batch request.
Batch returns a new BatchRequest associated with the client, ready to add operations (Get, Create, Update, Delete) or changesets.
Example:
batch := client.Batch().
Get("Customers", 1).
Create("Orders", orderData)
resp, err := batch.Execute(ctx)
func (*Client) CircuitBreakerState ¶ added in v0.2.0
func (c *Client) CircuitBreakerState() relay.CircuitBreakerState
breaker: Closed (healthy), Open (failing, requests rejected), or Half-Open (probing for recovery).
Returns relay.StateClosed if the circuit breaker is not configured.
Example:
if client.CircuitBreakerState() == relay.StateOpen {
log.Println("OData service unavailable - circuit is open")
}
func (*Client) Close ¶
Close closes the client and releases all associated resources.
Close gracefully shuts down the underlying HTTP client (relay.Client), terminating any active connections and releasing network resources. This should always be called when the client is no longer needed, preferably via defer for cleanup guarantee.
After Close is called, the client must not be reused. Attempting to make requests after Close will result in errors.
Returns an error if the shutdown process fails, though in most cases this is safe to ignore. However, it's still good practice to check and log errors:
if err := client.Close(); err != nil {
log.Printf("error closing client: %v", err)
}
Typical usage with defer:
client, _ := traverse.New(traverse.WithBaseURL("..."))
defer client.Close() // Ensures cleanup even if panic occurs
// Use client...
func (*Client) Create ¶
func (c *Client) Create(ctx context.Context, entitySet string, data interface{}) (map[string]interface{}, error)
Create creates a new entity in the specified entity set.
Create sends an HTTP POST request with the provided entity data to the service, creating a new record and returning the complete entity as created by the server. This includes any server-generated fields such as identity columns, default values, and computed fields.
Parameters:
- ctx: Context for request cancellation and timeout
- entitySet: Name of the entity set (e.g., "Products", "Orders")
- data: Entity data as map[string]interface{} or struct
Response: The method returns the complete created entity including server-generated fields. The response format differs by OData version:
- OData v2: Entity wrapped in {"d": {...}} response
- OData v4: Entity returned directly in response body
Error Cases:
- HTTP 400: Invalid data or constraint violations
- HTTP 409: Duplicate key or concurrency conflict
- Network errors: Connection failures
HTTP Status: 201 Created (success)
Example with Map:
newProduct := map[string]interface{}{
"Name": "Premium Widget",
"Price": 29.99,
"Category": "Electronics",
}
created, err := client.Create(ctx, "Products", newProduct)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created product with ID: %v\n", created["ID"])
fmt.Printf("Server name: %v\n", created["Name"]) // May differ if server applies defaults
Example with Struct:
type Product struct {
Name string `json:"Name"`
Price float64 `json:"Price"`
}
product := Product{Name: "Widget", Price: 9.99}
created, err := client.Create(ctx, "Products", product)
func (*Client) CrossJoin ¶ added in v0.2.23
func (c *Client) CrossJoin(entitySets ...string) *CrossJoinBuilder
CrossJoin creates a CrossJoinBuilder for the given entity sets.
At least two entity set names are required; the method panics if fewer than two are provided to surface the configuration error at init time rather than silently producing an invalid URL.
Example:
client.CrossJoin("Products", "Categories").
Filter("Products/CategoryID eq Categories/ID")
func (*Client) Delete ¶
Delete deletes an entity from the specified entity set.
Delete sends an HTTP DELETE request to remove the entity identified by the provided key. Once deleted, the entity can no longer be accessed through normal queries; it is permanently removed from the data store (subject to any database retention policies).
Key Encoding:
- String keys: Automatically quoted and escaped for OData URL encoding ('MAT001')
- Numeric keys: Converted to string representation (123)
- Invalid keys: Returns error during encoding
Concurrency Control:
- Returns ErrConcurrencyConflict if entity was modified elsewhere (ETag mismatch)
- The service may return HTTP 409 if concurrent modification is detected
Parameters:
- ctx: Context for request cancellation and timeout
- entitySet: Name of the entity set (e.g., "Products", "Orders")
- key: Entity key for identification (int or string)
HTTP Method: DELETE Response: HTTP 204 No Content on success (no body returned)
Error Cases:
- HTTP 404: Entity not found
- HTTP 409: Concurrency conflict (entity modified elsewhere)
- Network errors: Connection failures
Important Notes:
- Deletion is permanent; deleted data cannot be recovered
- Some services may use soft deletes (marking as deleted) instead of hard deletion
- Deleting a parent may cascade to child entities (depending on foreign key constraints)
Example - Delete by Numeric ID:
err := client.Delete(ctx, "Products", 123)
if err != nil {
if errors.Is(err, traverse.ErrConcurrencyConflict) {
log.Println("Product was modified by another user")
} else {
log.Fatal(err)
}
}
log.Println("Product deleted successfully")
Example - Delete by String ID:
err := client.Delete(ctx, "Materials", "MAT001")
if err != nil {
log.Fatal(err)
}
func (*Client) DeleteWithETag ¶ added in v0.2.5
func (c *Client) DeleteWithETag(ctx context.Context, entitySet string, key interface{}, etag ETag) error
DeleteWithETag deletes an entity guarded by an ETag. If etag is non-empty, an If-Match header is sent. HTTP 412 is translated to ErrConcurrencyConflict.
func (*Client) DeleteWithOptions ¶ added in v0.5.0
func (c *Client) DeleteWithOptions(ctx context.Context, entitySet string, key interface{}, opts ...DeleteOption) error
DeleteWithOptions deletes an entity, applying the provided DeleteOption values.
DeleteWithOptions extends the base Delete method with support for cascade delete, ETag conditions, and return-representation semantics.
Parameters:
- ctx: Request context for cancellation and timeout
- entitySet: Name of the entity set (e.g. "Orders")
- key: Entity key (int or string)
- opts: Zero or more DeleteOption values
Example:
err := client.DeleteWithOptions(ctx, "Orders", 1,
traverse.WithDeleteCascade(),
traverse.WithDeleteIfMatch(`W/"abc"`),
)
func (*Client) FetchPageAt ¶ added in v0.2.5
FetchPageAt fetches and parses an OData page from an arbitrary URL. It is primarily used by Paginator to follow @odata.nextLink (or __next) URLs returned by the server between pages.
The rawURL must be a fully-qualified URL (absolute) or a path relative to the client base URL that the server returned.
func (*Client) From ¶
func (c *Client) From(entitySet string) *QueryBuilder
From creates a new QueryBuilder for querying the specified entity set.
From starts the construction of an OData query for the given entity set name. The returned QueryBuilder supports a fluent API for adding query parameters (filtering, selection, ordering, expansion, pagination) before execution.
The QueryBuilder provides:
- Filter(): Add OData $filter expressions
- Select(): Choose specific properties ($select)
- OrderBy(): Sort results ($orderby)
- Expand(): Include related entities ($expand)
- Top(): Limit result count ($top)
- Skip(): Skip records for pagination ($skip)
- Collect(): Execute and collect all results
- Stream(): Execute and stream results without buffering
The QueryBuilder uses method chaining for ergonomic query construction. All methods are optional-you can execute with just From() for a basic query.
Query Execution:
- Call Collect() to fetch all results at once
- Call Stream() for memory-efficient streaming of large datasets
- Call Count() to get the record count with $count
- Call Single() for a single-record query with error if 0 or >1 result
Thread Safety: Each QueryBuilder is independent and not thread-safe for concurrent modifications. Create separate QueryBuilders for concurrent queries.
Example:
qb := client.From("Products").
Filter("Price gt 100").
Select("ProductID", "Name", "Price").
OrderBy("Name asc").
Top(50)
results, _ := qb.Collect(context.Background())
Or for streaming:
stream := client.From("LargeDataSet").Stream(context.Background())
for result := range stream {
processRecord(result.Value)
}
func (*Client) Function ¶
func (c *Client) Function(name string) *FunctionBuilder
Function starts building a call to an OData Function (v4).
Function creates a new FunctionBuilder for calling OData Functions. Functions are read-only operations that may take parameters and return data.
Returns a FunctionBuilder ready for parameter addition and execution.
Example:
result, err := client.Function("GetProductsByCategory").
Param("category", "Electronics").
Execute(ctx)
func (*Client) FunctionImport ¶
func (c *Client) FunctionImport(name string) *FunctionImportBuilder
FunctionImport starts building a call to an OData Function Import (v2).
FunctionImport creates a new FunctionImportBuilder for calling OData v2 Function Imports. Function Imports are similar to v4 Functions and use HTTP GET with URL-encoded parameters.
Returns a FunctionImportBuilder ready for parameter addition and execution.
Example:
result, err := client.FunctionImport("GetTop10Orders").Execute(ctx)
func (*Client) Metadata ¶
Metadata returns the OData $metadata service document, which contains the complete Entity Data Model (EDM) describing all entity types, properties, relationships, and operations.
Metadata fetches and caches the $metadata document, which defines:
- All entity types and their properties
- Navigation properties (relationships between entities)
- Associations and multiplicities
- Complex types and enumerations
- Function imports and action definitions
- Sap annotations and extensions for SAP systems
Caching Behavior:
The result is cached using sync.Once to ensure metadata is fetched only once per client lifetime, even with concurrent calls. Subsequent calls return the cached metadata without network requests, providing significant performance benefits for services with large metadata documents.
The cache key includes the base URL to support scenarios with multiple clients to different services. Custom cache implementations can be provided via WithMetadataCache().
Use Cases:
- Query validation: Check entity types before constructing queries
- Dynamic query building: Inspect available properties and relationships
- Type information: Determine property types for serialization/deserialization
- Navigation: Discover related entities through association mappings
Returns:
- *Metadata: Parsed EDM model describing the service schema
- error: Returns ErrMetadataInvalid if metadata cannot be parsed
- error: Returns network/HTTP errors if fetch fails
Example:
meta, err := client.Metadata(ctx)
if err != nil {
log.Fatal(err)
}
// Find entity type definition
productType := meta.FindEntityType("Product")
for _, prop := range productType.Properties {
fmt.Printf("Property: %s (Type: %s)\n", prop.Name, prop.Type)
}
// Use discovered metadata for dynamic query building
for _, navProp := range productType.NavigationProperties {
fmt.Printf("Related: %s\n", navProp.Name)
}
Thread Safety: Metadata is thread-safe. Multiple goroutines can safely call Metadata() concurrently; only the first call fetches the metadata, while others wait for the result.
func (*Client) NewAsyncPoller ¶ added in v0.2.6
func (c *Client) NewAsyncPoller(statusURL string) *AsyncOpPoller
NewAsyncPoller creates an AsyncOpPoller for the given status URL. Default: 5s poll interval, 60 max polls (5 minutes total).
func (*Client) NewDeltaSync ¶
NewDeltaSync creates a new delta sync handler for an entity set.
NewDeltaSync initializes a DeltaSync for incremental synchronization of the specified entity set. The delta token is initially empty; call DeltaSync.Full first to obtain an initial token.
Returns a DeltaSync instance ready for use.
Example:
ds := client.NewDeltaSync("Customers")
func (*Client) PageSize ¶
PageSize returns the configured page size for pagination.
PageSize returns the number of records fetched per HTTP request when using $top-based pagination. This is the default page size for all queries unless overridden with QueryBuilder.Top().
Pagination Details:
- Controls the default $top parameter for OData queries
- Reduces per-request latency by controlling chunk size
- Larger values: Fewer requests but higher memory per request
- Smaller values: More requests but lower memory per request
- Typical value: 1000-5000 records (default: 1000)
This is configured via WithPageSize() when creating the client.
Returns:
- The page size in records (default: 1000)
Example:
pageSize := client.PageSize() // Default returns 1000 // Can be overridden per query with QueryBuilder.Top()
func (*Client) ReadWithETag ¶ added in v0.2.5
func (c *Client) ReadWithETag(ctx context.Context, entitySet string, key interface{}) (*EntityWithETag, error)
ReadWithETag fetches a single entity by key and returns it together with the server ETag. The ETag can be passed to Client.UpdateWithETag, Client.ReplaceWithETag, or Client.DeleteWithETag to guard against concurrent modifications.
The ETag is extracted from the response ETag header. If the server does not return an ETag header, EntityWithETag.ETag.IsEmpty() returns true - the entity is still returned normally.
entity, err := client.ReadWithETag(ctx, "Products", 42)
if err != nil { ... }
// modify entity.Entity ...
err = client.UpdateWithETag(ctx, "Products", 42, changes, entity.ETag)
func (*Client) RelayClient ¶ added in v0.2.20
RelayClient returns the underlying relay.Client used for HTTP transport.
This provides access to relay's full request API for advanced use cases such as extension modules that need to issue raw HTTP calls (e.g. OData webhook subscription management) while reusing the same configured transport, auth, retry and circuit-breaker settings as the parent traverse.Client.
The returned client must not be closed independently - use Client.Close to shut down both the traverse and relay clients together.
func (*Client) Replace ¶
func (c *Client) Replace(ctx context.Context, entitySet string, key interface{}, data interface{}) error
Replace replaces an entire entity with new data using HTTP PUT.
Replace sends an HTTP PUT request with the complete new entity state to replace the existing entity. Unlike Update() which performs a partial merge (PATCH), Replace completely replaces all properties of the entity. Properties not explicitly provided in the data parameter are typically set to their default values or NULL, which can result in data loss if not all properties are included.
Use Cases:
- Complete entity replacement: When you have the full new state
- Wholesale updates: Replacing entire records from batch operations
- Consistency: Ensuring a known complete state after update
Compare with Update():
- Update (PATCH): Partial update, only changed fields
- Replace (PUT): Complete replacement, unspecified fields set to defaults
Parameters:
- ctx: Context for request cancellation and timeout
- entitySet: Name of the entity set (e.g., "Products")
- key: Entity key for identification (ID or primary key)
- data: Complete new entity state (all properties that should exist)
HTTP Method: PUT (HTTP 201-204 on success)
Response: Returns nil on success with HTTP 204 No Content. Returns ErrConcurrencyConflict if an ETag mismatch occurs (HTTP 409).
Warning: Replace can result in data loss if the data parameter doesn't include all required fields. Prefer Update() for partial updates unless you specifically need complete replacement.
Example - Complete Replacement:
newProduct := map[string]interface{}{
"ID": 123, // Must include key
"Name": "Completely New Name",
"Price": 49.99,
"Stock": 50,
"Active": true,
}
err := client.Replace(ctx, "Products", 123, newProduct)
if err != nil {
log.Fatal(err)
}
log.Println("Product completely replaced")
func (*Client) ReplaceWithETag ¶ added in v0.2.5
func (c *Client) ReplaceWithETag(ctx context.Context, entitySet string, key interface{}, data interface{}, etag ETag) error
ReplaceWithETag performs a full replacement (PUT) guarded by an ETag. If etag is non-empty, an If-Match header is sent to guard against concurrent modifications (HTTP 412 -> ErrConcurrencyConflict).
func (*Client) ResetCircuitBreaker ¶ added in v0.2.0
func (c *Client) ResetCircuitBreaker()
ResetCircuitBreaker resets the circuit breaker to the Closed state, allowing requests to flow again immediately. This is useful after a manual intervention or during testing.
No-op if the circuit breaker is not configured.
func (*Client) Service ¶
func (c *Client) Service(ctx context.Context) (*ServiceDocument, error)
Service fetches the OData service document, which lists all available entity sets and operations.
Service makes an HTTP GET request to the service root URL (typically /odata/v4 or /sap/opu/odata/...) and returns a ServiceDocument containing all entity sets exposed by the service.
The Service Document:
- Lists all available entity sets that can be queried
- Includes singleton entities and function imports
- Is version-aware: Parsed differently for OData v2 vs v4
- Is useful for dynamic service discovery without prior knowledge of entity set names
Response Format:
- OData v2: {"d": {"EntitySets": [{...}, ...]}} with "d" wrapper for CSRF protection
- OData v4: {"value": [{...}, ...]} at the root
Use Case:
This is typically called once during service initialization to discover available entity sets, or when the service schema is not known in advance. For known schemas, use From() directly.
Returns:
- *ServiceDocument: Contains all entity sets available in the service
- error: Network error, HTTP error, or parsing error
Example:
doc, err := client.Service(ctx)
if err != nil {
log.Fatal(err)
}
for _, es := range doc.EntitySets {
fmt.Printf("Entity Set: %s (URL: %s)\n", es.Name, es.URL)
}
// Now you can query known entity sets
results, _ := client.From(doc.EntitySets[0].Name).Collect(ctx)
func (*Client) Singleton ¶ added in v0.6.0
func (c *Client) Singleton(name string) *QueryBuilder
Singleton returns a QueryBuilder targeting a named singleton resource at the service root.
A singleton is a single-entity resource addressable directly by name, without a key predicate. It is defined in OData v4.0 spec section 11.2.4.
Microsoft Graph uses singletons extensively (e.g., "/me", "/organization"). SAP uses them for session-level resources.
The returned QueryBuilder supports the full query API including $select, $expand, $filter, and navigation to related collections via [From].
Example:
// Fetch the "me" singleton
me, err := client.Singleton("me").Page(ctx)
// Expand a navigation property from a singleton
result, err := client.Singleton("me").Expand("manager").Page(ctx)
// Navigate to a related collection on a singleton
for r := range client.Singleton("me").From("messages").Stream(ctx) {
// process r.Value
}
func (*Client) Update ¶
func (c *Client) Update(ctx context.Context, entitySet string, key interface{}, data interface{}) error
Update updates an existing entity using a partial update (PATCH/MERGE operation).
Update sends an HTTP PATCH request (or HTTP MERGE for OData v2 compatibility) with the entity data. This performs a partial update where only provided properties are modified; omitted properties remain unchanged.
This is the standard way to update entities-use Replace() for complete entity replacement instead.
Parameters:
- ctx: Context for request cancellation and timeout
- entitySet: Name of the entity set (e.g., "Products", "Orders")
- key: Entity key for identification (typically an ID or primary key)
- data: Properties to update as map[string]interface{} or struct (only these fields change)
Key Formats:
- Single key: numeric ID (123) or string ID ('MAT001')
- Composite key: Not directly supported; use URL encoding if needed
HTTP Method:
- PATCH (standard) for OData v4
- MERGE (legacy) for OData v2
Response: Returns nil on success with HTTP 200 OK (with updated entity) or HTTP 204 No Content (standard). The method doesn't parse the response body for 204 responses.
Concurrency Control: Returns ErrConcurrencyConflict if:
- ETag mismatch (entity was modified elsewhere)
- HTTP 409 Conflict response
Example - Partial Update:
err := client.Update(ctx, "Products", 123, map[string]interface{}{
"Price": 19.99,
"LastModified": time.Now(),
})
if err != nil {
if err == traverse.ErrConcurrencyConflict {
log.Println("Entity was modified by another user")
} else {
log.Fatal(err)
}
}
log.Println("Product updated successfully")
Example - Update Multiple Fields:
updates := map[string]interface{}{
"Status": "Active",
"Priority": 1,
"Description": "Updated description",
}
err := client.Update(ctx, "Tasks", "TASK-001", updates)
func (*Client) UpdateWithETag ¶ added in v0.2.5
func (c *Client) UpdateWithETag(ctx context.Context, entitySet string, key interface{}, data interface{}, etag ETag) error
UpdateWithETag performs a partial update (PATCH/MERGE) guarded by an ETag. If etag is non-empty, an If-Match header is sent. The server will reject the update with HTTP 412 Precondition Failed if the entity was modified since the ETag was fetched; this is translated to ErrConcurrencyConflict.
Pass a zero ETag (or ETag{}) to skip the concurrency check.
entity, err := client.ReadWithETag(ctx, "Products", 42)
// ...
err = client.UpdateWithETag(ctx, "Products", 42, map[string]any{"Price": 9.99}, entity.ETag)
func (*Client) Upsert ¶ added in v0.2.5
func (c *Client) Upsert(ctx context.Context, entitySet string, key interface{}, data interface{}) error
Upsert creates a new entity or replaces an existing one using OData upsert semantics (PUT with If-None-Match: * header for create-or-replace).
Upsert behaviour depends on whether the entity already exists:
- If the entity does NOT exist: the server creates it (HTTP 201 Created).
- If the entity already exists: the server replaces it (HTTP 200/204).
This uses the OData upsert pattern (If-None-Match: *) as described in OData 4.01 section 11.4.4. Not all servers support this pattern; SAP OData services typically do via the PUT endpoint.
err := client.Upsert(ctx, "Products", 42, map[string]any{
"ID": 42,
"Name": "Widget",
"Price": 9.99,
})
func (*Client) Version ¶
func (c *Client) Version() ODataVersion
Version returns the OData protocol version (v2 or v4) configured for this Client.
Version returns the OData protocol version that the client is using for requests and response parsing. This is configured via WithODataVersion() when creating the client.
OData Version Differences:
- ODataV2: Legacy SAP standard, uses {"d": {"results": [...]}} response structure
- ODataV4: Current OASIS standard, uses {"value": [...]} response structure
The version affects:
- JSON response parsing (d.results vs value array)
- DateTime format handling
- Query operator availability
- Metadata structure interpretation
Returns:
- ODataV2 (value 2) or ODataV4 (value 4)
Example:
version := client.Version()
if version == traverse.ODataV4 {
// Use OData v4-specific features
}
type ComplexType ¶ added in v0.2.28
type ComplexType struct {
// Name is the name of the complex type.
Name string
// Properties is the list of all properties defined for this complex type.
Properties []Property
}
ComplexType represents an OData complex type definition. Complex types are structured types without a key (not independently addressable).
type CoreVocabulary ¶ added in v0.2.29
type CoreVocabulary struct {
// Description corresponds to Org.OData.Core.V1.Description.
Description string
// LongDescription corresponds to Org.OData.Core.V1.LongDescription.
LongDescription string
// IsLanguageDependent corresponds to Org.OData.Core.V1.IsLanguageDependent.
IsLanguageDependent bool
// Immutable corresponds to Org.OData.Core.V1.Immutable.
Immutable bool
// Computed corresponds to Org.OData.Core.V1.Computed.
Computed bool
// Permissions corresponds to Org.OData.Core.V1.Permissions (e.g., Read, Write).
Permissions []string
// Example corresponds to Org.OData.Core.V1.Example (for documentation purposes).
Example string
}
CoreVocabulary defines commonly used Org.OData.Core.V1 annotation terms. These match the official Org.OData.Core.V1 namespace.
func ParseCoreVocabulary ¶ added in v0.2.29
func ParseCoreVocabulary(annotations map[string]string) CoreVocabulary
ParseCoreVocabulary extracts Org.OData.Core.V1 annotation terms from a raw annotation map. The map keys are fully-qualified term names (e.g. "Org.OData.Core.V1.Description").
type CrossJoinBuilder ¶ added in v0.2.23
type CrossJoinBuilder struct {
// contains filtered or unexported fields
}
CrossJoinBuilder constructs an OData $crossjoin query against two or more entity sets, returning tuples of related entities.
$crossjoin is an OData v4 feature (section 11.2.10) that produces the Cartesian product of the specified entity sets, optionally narrowed by a $filter expression. The response is an array of objects where each key is the entity set name and the value is a single entity instance.
Build a cross-join with Client.CrossJoin, then call CrossJoinBuilder.Filter, CrossJoinBuilder.Select, and CrossJoinBuilder.Expand to refine the query. Execute with CrossJoinBuilder.Collect or [CrossJoinBuilder.Stream].
Example:
pairs, err := client.CrossJoin("Customers", "Orders").
Filter("Customers/ID eq Orders/CustomerID").
Collect(ctx)
func (*CrossJoinBuilder) Collect ¶ added in v0.2.23
func (b *CrossJoinBuilder) Collect(ctx context.Context) ([]CrossJoinResult, error)
Collect executes the $crossjoin query and returns all result rows.
Collect follows @odata.nextLink pagination automatically, accumulating all pages before returning. For very large result sets, prefer iterating over pages manually with repeated Collect calls using CrossJoinBuilder.Param.
Example:
rows, err := client.CrossJoin("Products", "Categories").
Filter("Products/CategoryID eq Categories/ID").
Collect(ctx)
if err != nil {
return err
}
for _, row := range rows {
var p Product
var c Category
_ = row.Decode("Products", &p)
_ = row.Decode("Categories", &c)
}
func (*CrossJoinBuilder) Expand ¶ added in v0.2.23
func (b *CrossJoinBuilder) Expand(navProps ...string) *CrossJoinBuilder
Expand requests inline expansion of navigation properties.
Each value must use the qualified form "EntitySetName/NavigationProperty".
Example:
.Expand("Products/Supplier")
func (*CrossJoinBuilder) Filter ¶ added in v0.2.23
func (b *CrossJoinBuilder) Filter(expr string) *CrossJoinBuilder
Filter adds an OData $filter expression to narrow the cross-join result.
The filter can reference properties from any of the joined entity sets using the qualified form "EntitySetName/PropertyName".
Example:
.Filter("Products/CategoryID eq Categories/ID and Products/Price gt 100.0")
func (*CrossJoinBuilder) Param ¶ added in v0.2.23
func (b *CrossJoinBuilder) Param(key, value string) *CrossJoinBuilder
Param adds a custom query parameter.
Use this for service-specific OData parameters not covered by the builder (e.g., SAP sap-client or service-defined preference hints).
func (*CrossJoinBuilder) Select ¶ added in v0.2.23
func (b *CrossJoinBuilder) Select(fields ...string) *CrossJoinBuilder
Select limits the properties returned for each entity set in the result.
Each selector must use the qualified form "EntitySetName/PropertyName".
Example:
.Select("Products/Name", "Products/Price", "Categories/Name")
type CrossJoinResult ¶ added in v0.2.23
type CrossJoinResult map[string]json.RawMessage
CrossJoinResult is a single row returned from a $crossjoin query. Each key is an entity set name; the value is the raw JSON of one entity.
Use CrossJoinResult.Decode to unmarshal a specific entity set's data into a typed struct.
type DateTime ¶
DateTime represents an OData Edm.DateTime value (OData v2).
DateTime wraps time.Time to provide custom JSON marshaling/unmarshaling for the OData v2 DateTime format: /Date(milliseconds)/ or /Date(milliseconds+offset)/. This format is common in SAP NetWeaver Gateway and legacy OData v2 services.
Example formats in JSON responses:
"/Date(1704067200000)/" // 2024-01-01 UTC "/Date(1704067200000+0100)/" // With timezone offset
Internally stored as UTC time.Time for consistency. Use DateTime.Time to convert to time.Time for standard Go operations.
Example:
type Order struct {
CreatedAt traverse.DateTime `json:"createdAt"`
}
json.Unmarshal([]byte(`{"createdAt":"/Date(1704067200000)/"}`), &order)
t := order.CreatedAt.Time() // Convert to time.Time
func (DateTime) MarshalJSON ¶
MarshalJSON encodes DateTime to OData v2 format: /Date(milliseconds)/.
MarshalJSON converts the internal time.Time to milliseconds since epoch and formats it as /Date(milliseconds)/.
func (DateTime) String ¶
String returns the time.Time string representation.
String converts DateTime to time.Time and returns its standard string format.
func (DateTime) Time ¶
Time converts DateTime to time.Time.
Time returns the underlying time.Time value for use with standard Go time operations.
func (*DateTime) UnmarshalJSON ¶
UnmarshalJSON decodes OData DateTime format: /Date(milliseconds)/ or /Date(milliseconds±offset)/.
UnmarshalJSON parses the OData v2 DateTime format and extracts milliseconds since epoch. The timezone offset (if present) is ignored; the result is always in UTC.
Returns an error if the format is invalid or milliseconds cannot be parsed.
type DateTimeOffset ¶
DateTimeOffset represents an OData Edm.DateTimeOffset value (OData v4).
DateTimeOffset wraps time.Time to provide custom JSON marshaling/unmarshaling for the OData v4 DateTimeOffset format: ISO 8601 (2024-01-01T00:00:00Z or 2024-01-01T00:00:00+01:00). This format is standard in modern OData v4 services like SAP S/4HANA.
Example formats in JSON responses:
"2024-01-01T00:00:00Z" // UTC (RFC 3339) "2024-01-01T00:00:00+01:00" // With timezone offset "2024-01-01T00:00:00" // Without timezone (treated as UTC)
Use DateTimeOffset.Time to convert to time.Time for standard Go operations.
Example:
type Order struct {
CreatedAt traverse.DateTimeOffset `json:"createdAt"`
}
json.Unmarshal([]byte(`{"createdAt":"2024-01-01T00:00:00Z"}`), &order)
t := order.CreatedAt.Time() // Convert to time.Time
func (DateTimeOffset) MarshalJSON ¶
func (d DateTimeOffset) MarshalJSON() ([]byte, error)
MarshalJSON encodes to ISO 8601 (RFC 3339).
MarshalJSON converts DateTimeOffset to RFC 3339 format for JSON output.
func (DateTimeOffset) Time ¶
func (d DateTimeOffset) Time() time.Time
Time converts DateTimeOffset to time.Time.
Time returns the underlying time.Time value for use with standard Go time operations.
func (*DateTimeOffset) UnmarshalJSON ¶
func (d *DateTimeOffset) UnmarshalJSON(b []byte) error
UnmarshalJSON decodes ISO 8601 format (RFC 3339).
UnmarshalJSON parses ISO 8601 formatted datetime strings. Supports both full timestamps with timezone (RFC 3339) and timestamps without timezone (treated as UTC).
Returns an error if the format cannot be parsed.
type Decimal ¶
type Decimal struct {
// contains filtered or unexported fields
}
Decimal represents an OData Edm.Decimal value (arbitrary precision decimal).
Decimal uses math/big.Float internally to store arbitrary precision decimal numbers. This is essential for financial calculations and other high-precision scenarios where standard float64 cannot represent the exact value.
The underlying value is stored as a pointer to big.Float to enable nil representation for SQL NULL equivalents.
Example formats in JSON responses:
"123.456789012345678901234567890" 123.45 (numeric literal)
Example:
type Product struct {
Price traverse.Decimal `json:"price"`
}
json.Unmarshal([]byte(`{"price":"19.99"}`), &product)
fmt.Println(product.Price.String()) // "19.99" with full precision
func (Decimal) MarshalJSON ¶
MarshalJSON encodes decimal to JSON string (preserves precision).
MarshalJSON returns the decimal as a JSON string to preserve arbitrary precision during round-tripping. Nil values are encoded as null.
func (Decimal) String ¶
String returns the decimal string representation.
String returns the full precision string representation of the decimal. Returns "0" for nil values.
func (*Decimal) UnmarshalJSON ¶
UnmarshalJSON decodes decimal values from JSON (string or numeric).
UnmarshalJSON accepts both string and numeric representations of decimal values. Strings are preferred for preserving arbitrary precision. Numeric literals are converted to string first for parsing.
Returns an error if the value cannot be parsed as a valid decimal number.
type DeepInsertOptions ¶ added in v0.2.8
type DeepInsertOptions struct {
// ReturnRepresentation instructs the server to return the created entity
// (adds "return=representation" to the Prefer header).
ReturnRepresentation bool
// ContinueOnError instructs the server to continue on partial failures
// (adds "odata.continue-on-error" to the Prefer header).
ContinueOnError bool
}
DeepInsertOptions configures the Prefer header for a deep insert request.
DeepInsertOptions allows control over server behavior during a deep insert:
- ReturnRepresentation: ask the server to return the created entity
- ContinueOnError: ask the server to continue processing remaining operations on error
func (DeepInsertOptions) PreferHeader ¶ added in v0.2.8
func (o DeepInsertOptions) PreferHeader() string
PreferHeader generates the Prefer header value from the options. Returns an empty string if no options are set.
type DeepUpdateOptions ¶ added in v0.7.0
type DeepUpdateOptions struct {
// ReturnRepresentation instructs the server to return the updated entity
// (adds "return=representation" to the Prefer header).
ReturnRepresentation bool
// ContinueOnError instructs the server to continue on partial failures
// (adds "odata.continue-on-error" to the Prefer header).
ContinueOnError bool
}
DeepUpdateOptions configures the Prefer header for a deep update request.
DeepUpdateOptions allows control over server behavior during a deep update:
- ReturnRepresentation: ask the server to return the updated entity
- ContinueOnError: ask the server to continue processing remaining operations on error
func (DeepUpdateOptions) PreferHeader ¶ added in v0.7.0
func (o DeepUpdateOptions) PreferHeader() string
PreferHeader generates the Prefer header value from the options. Returns an empty string if no options are set.
type DeleteOption ¶ added in v0.5.0
type DeleteOption func(*DeleteOptions)
DeleteOption is a functional option for configuring delete behaviour.
func WithDeleteCascade ¶ added in v0.5.0
func WithDeleteCascade() DeleteOption
WithDeleteCascade enables cascade delete on related navigation properties. The Prefer: odata.cascade-delete header is sent with the DELETE request.
Example:
err := client.Delete(ctx, "Orders", 1, traverse.WithDeleteCascade())
func WithDeleteIfMatch ¶ added in v0.5.0
func WithDeleteIfMatch(etag string) DeleteOption
WithDeleteIfMatch adds an If-Match ETag condition to the delete request. The server will only delete the entity if its current ETag matches. Returns ErrConcurrencyConflict if the ETag does not match (HTTP 412).
Example:
err := client.Delete(ctx, "Products", 42, traverse.WithDeleteIfMatch(`W/"abc123"`))
func WithDeleteReturnRepresentation ¶ added in v0.5.0
func WithDeleteReturnRepresentation() DeleteOption
WithDeleteReturnRepresentation requests that the deleted entity be returned in the response. Sends header: Prefer: return=representation
Note: Most OData services return HTTP 204 No Content for deletes; this option is only useful with services that support return=representation on deletes.
Example:
err := client.Delete(ctx, "Orders", 1, traverse.WithDeleteReturnRepresentation())
type DeleteOptions ¶ added in v0.5.0
type DeleteOptions struct {
// Sends header: Prefer: odata.cascade-delete
CascadeNavigationProperties bool
// IfMatch sets the ETag condition for the delete.
IfMatch string
// ReturnRepresentation requests the deleted entity in the response.
// Sends header: Prefer: return=representation
ReturnRepresentation bool
}
DeleteOptions configures a delete operation.
type DeltaResult ¶
type DeltaResult struct {
// Value is the entity record (nil for deleted records)
Value map[string]interface{}
// Removed is true if the record was deleted
Removed bool
// Reason is the deletion reason if available
Reason string
// Err is an error if one occurred
Err error
}
DeltaResult represents a record from a delta sync operation.
DeltaResult is used with delta queries to track both added/modified records (in Value) and deleted records (indicated by Removed=true).
Fields:
- Value: the entity record (for non-deleted records)
- Removed: true if the record was deleted
- Reason: optional deletion reason (if provided by server)
- Err: error if one occurred during parsing
See QueryBuilder.WithDeltaToken for incremental sync usage.
type DeltaResultAs ¶
DeltaResultAs is the typed version of delta result.
type DeltaSync ¶
type DeltaSync struct {
// contains filtered or unexported fields
}
DeltaSync manages incremental synchronization using OData delta links (OData v4).
DeltaSync enables efficient synchronization of large datasets by supporting incremental updates. On the first sync, all records are read. On subsequent syncs, only changes (modifications and deletions) are returned using a delta token.
Delta sync uses the $deltatoken query parameter to mark a point in time. The server returns only records that have changed since that token was issued. Deleted records are marked with the @removed annotation.
Typical workflow:
- Initialize: ds := client.NewDeltaSync("Customers")
- Full sync: records, token, err := ds.Full(ctx) // Read all records
- Save token for later
- Incremental: changes, newToken, err := ds.Incremental(ctx, token) // Only changes
- Process changes, save new token, repeat step 4
This approach significantly reduces bandwidth and latency for large datasets that change infrequently.
func (*DeltaSync) Full ¶
func (d *DeltaSync) Full(ctx context.Context, bufferSize ...int) (<-chan Result[map[string]interface{}], string, error)
Full performs a complete (initial) synchronization, returning all records.
Full returns a channel streaming all records from the entity set and automatically extracts the delta token from the server response metadata (DeltaLink). This token can be used for subsequent incremental syncs via DeltaSync.Incremental.
The method also stores the token internally for convenience, so you can call DeltaSync.Incremental without providing the token explicitly.
The bufferSize parameter controls the result channel capacity (default 256). For large record sizes or high network latency, increase this value to reduce blocking.
Returns:
- A receive-only channel of Result items containing all records
- The delta token for use in subsequent incremental syncs
- An error if the query fails
Example:
records, token, err := ds.Full(ctx)
if err != nil {
log.Fatal(err)
}
for result := range records {
if result.Err != nil {
log.Println("Error:", result.Err)
continue
}
processRecord(result.Value)
}
// Save token for next sync: ds.Incremental(ctx, token)
func (*DeltaSync) Incremental ¶
func (d *DeltaSync) Incremental(ctx context.Context, token string, bufferSize ...int) (<-chan DeltaResult, string, error)
Incremental performs an incremental sync using a delta token.
Incremental returns only records that have changed or been deleted since the provided delta token was issued. Changes include both new records and modifications to existing records. Deleted records are marked with Removed=true and include a Reason (typically "changed" or "deleted").
The token parameter specifies which delta point to start from. If empty, the internally stored token (from previous DeltaSync.Full or DeltaSync.Incremental) is used. Returns an error if no token is available.
A new delta token is extracted from the response and can be used for the next incremental sync. It's automatically stored internally as well.
The bufferSize parameter controls the result channel capacity (default 256).
Returns:
- A receive-only channel of DeltaResult items containing changed records
- The new delta token for use in subsequent incremental syncs
- An error if the query fails or no token is available
Example:
// First, get initial token from Full sync
_, token, _ := ds.Full(ctx)
// Later, sync only changes
for result := range ds.Incremental(ctx, token) {
if result.Removed {
deleteRecord(result.Value)
} else {
updateRecord(result.Value)
}
}
type DeltaSyncAs ¶
type DeltaSyncAs[T any] struct { // contains filtered or unexported fields }
DeltaSyncAs is an alias for DeltaSyncJsonAs for backward compatibility. Deprecated: Use DeltaSyncJsonAs or DeltaSyncXmlAs instead.
func NewDeltaSyncAs ¶
func NewDeltaSyncAs[T any](c *Client, entitySet string) *DeltaSyncAs[T]
NewDeltaSyncAs is an alias for NewDeltaSyncJsonAs for backward compatibility. Deprecated: Use NewDeltaSyncJsonAs or NewDeltaSyncXmlAs instead.
func (*DeltaSyncAs[T]) Full ¶
func (d *DeltaSyncAs[T]) Full(ctx context.Context, bufferSize ...int) (<-chan Result[T], string, error)
Full is an alias for the JSON-format Full method. Deprecated: Use DeltaSyncJsonAs.Full or DeltaSyncXmlAs.Full instead.
func (*DeltaSyncAs[T]) Incremental ¶
func (d *DeltaSyncAs[T]) Incremental(ctx context.Context, token string, bufferSize ...int) (<-chan DeltaResultAs[T], string, error)
Incremental is an alias for the JSON-format Incremental method. Deprecated: Use DeltaSyncJsonAs.Incremental or DeltaSyncXmlAs.Incremental instead.
type DeltaSyncJsonAs ¶ added in v0.16.0
type DeltaSyncJsonAs[T any] struct { // contains filtered or unexported fields }
DeltaSyncJsonAs is the JSON-format generic version of delta sync.
func NewDeltaSyncJsonAs ¶ added in v0.16.0
func NewDeltaSyncJsonAs[T any](c *Client, entitySet string) *DeltaSyncJsonAs[T]
NewDeltaSyncJsonAs creates a typed delta sync handler for JSON format.
func (*DeltaSyncJsonAs[T]) Full ¶ added in v0.16.0
func (d *DeltaSyncJsonAs[T]) Full(ctx context.Context, bufferSize ...int) (<-chan Result[T], string, error)
Full performs a complete sync with type T using JSON format.
func (*DeltaSyncJsonAs[T]) Incremental ¶ added in v0.16.0
func (d *DeltaSyncJsonAs[T]) Incremental(ctx context.Context, token string, bufferSize ...int) (<-chan DeltaResultAs[T], string, error)
Incremental performs an incremental sync with type T using JSON format.
type DeltaSyncXmlAs ¶ added in v0.16.0
type DeltaSyncXmlAs[T any] struct { // contains filtered or unexported fields }
DeltaSyncXmlAs is the XML-format generic version of delta sync.
func NewDeltaSyncXmlAs ¶ added in v0.16.0
func NewDeltaSyncXmlAs[T any](c *Client, entitySet string) *DeltaSyncXmlAs[T]
NewDeltaSyncXmlAs creates a typed delta sync handler for XML format.
func (*DeltaSyncXmlAs[T]) Full ¶ added in v0.16.0
func (d *DeltaSyncXmlAs[T]) Full(ctx context.Context, bufferSize ...int) (<-chan Result[T], string, error)
Full performs a complete sync with type T using XML format.
func (*DeltaSyncXmlAs[T]) Incremental ¶ added in v0.16.0
func (d *DeltaSyncXmlAs[T]) Incremental(ctx context.Context, token string, bufferSize ...int) (<-chan DeltaResultAs[T], string, error)
Incremental performs an incremental sync with type T using XML format.
type ETag ¶ added in v0.2.5
type ETag struct {
// Value is the raw ETag string as returned by the server in the ETag header
// (e.g. W/"123", "abc456").
Value string
}
ETag wraps an entity value together with its HTTP ETag for concurrency control. Obtain an ETag value via Client.ReadWithETag and pass it back to Client.UpdateWithETag, Client.ReplaceWithETag, or Client.DeleteWithETag to detect concurrent modifications.
type EntityCapabilities ¶ added in v0.2.21
type EntityCapabilities struct {
Filterable bool
NonFilterableProperties []string
Sortable bool
NonSortableProperties []string
Insertable bool
Updatable bool
Deletable bool
}
EntityCapabilities describes what operations are supported for an entity set.
type EntitySchema ¶ added in v0.2.12
type EntitySchema struct {
// Properties maps property name -> property type.
// Valid types: "string", "int", "float", "bool", "datetime", "guid"
Properties map[string]string
}
EntitySchema defines the known properties of an entity type for filter validation.
EntitySchema provides a declarative way to specify the properties of an OData entity type so that filter and orderby expressions can be validated before sending to the service. This catches typos in field names and type mismatches at build time rather than at runtime when the service rejects the request.
The Properties map associates property names with their types, allowing validation of filter expressions to ensure they reference only valid properties with appropriate types.
Example:
schema := EntitySchema{
Properties: map[string]string{
"ID": "int",
"Name": "string",
"Email": "string",
"Age": "int",
"Birthdate": "datetime",
"IsActive": "bool",
},
}
query := client.From("Users").WithSchema(schema)
type EntitySetInfo ¶
type EntitySetInfo struct {
// Name is the name of the entity set.
Name string
// EntityTypeName is the fully qualified name of the entity type.
EntityTypeName string
NavigationBindings []NavigationBinding
// SAP contains SAP-specific entity-set-level annotations (sap:creatable, sap:deletable, etc.).
SAP SAPAnnotations
}
EntitySetInfo represents an OData entity set definition. An entity set is a collection of entities of a specific type.
type EntitySetReference ¶
type EntitySetReference struct {
// Name is the name of the entity set.
Name string
// URL is the URL path relative to the service root.
URL string
}
EntitySetReference represents a reference to an entity set in the service document. It provides the name and URL path for accessing the entity set.
type EntityType ¶
type EntityType struct {
// Name is the name of the entity type.
Name string
// Abstract indicates whether this entity type is abstract (cannot be instantiated directly).
Abstract bool
// BaseType is the fully qualified name of the parent entity type (e.g. "Namespace.ParentType").
BaseType string
// Key is the list of properties that form the primary key.
Key []PropertyRef
// Properties is the list of all properties defined for this entity type.
Properties []Property
NavigationProperties []NavigationProperty
}
EntityType represents an OData entity type definition. It describes the structure of entities of this type, including their properties and relationships.
type EntityWithETag ¶ added in v0.2.5
type EntityWithETag struct {
// Entity is the entity properties as returned by the server.
Entity map[string]interface{}
// ETag is the concurrency token for this entity revision.
ETag ETag
}
EntityWithETag pairs a fetched entity map with its server ETag.
type EnumMember ¶ added in v0.2.28
type EnumMember struct {
// Name is the name of the enum member.
Name string
// Value is the integer value of the enum member.
Value int
}
EnumMember represents a single member of an OData enum type.
type EnumType ¶ added in v0.2.28
type EnumType struct {
// Name is the name of the enum type.
Name string
// Members is the list of enum members.
Members []EnumMember
// IsFlags indicates whether multiple members can be combined.
IsFlags bool
// UnderlyingType is the underlying integer type (e.g., "Edm.Int32").
UnderlyingType string
}
EnumType represents an OData enum type definition.
type ExpandBuilder ¶ added in v0.2.6
type ExpandBuilder struct {
// contains filtered or unexported fields
}
ExpandBuilder configures a nested expand expression with optional sub-query options.
ExpandBuilder is obtained via QueryBuilder.ExpandNested and allows specifying per-navigation-property $select, $filter, $orderby, $top, and further nested $expand options.
func (*ExpandBuilder) Done ¶ added in v0.2.6
func (b *ExpandBuilder) Done() *QueryBuilder
Done finalizes the nested expand and returns the parent QueryBuilder.
func (*ExpandBuilder) Expand ¶ added in v0.2.6
func (b *ExpandBuilder) Expand(property string) *ExpandBuilder
Expand adds a further nested expand within this navigation property.
func (*ExpandBuilder) Filter ¶ added in v0.2.6
func (b *ExpandBuilder) Filter(expr string) *ExpandBuilder
Filter applies a filter expression to the expanded entities.
func (*ExpandBuilder) OrderBy ¶ added in v0.2.6
func (b *ExpandBuilder) OrderBy(expr string) *ExpandBuilder
OrderBy sets the sort order for expanded entities.
func (*ExpandBuilder) Select ¶ added in v0.2.6
func (b *ExpandBuilder) Select(fields ...string) *ExpandBuilder
Select limits the properties returned for the expanded entity.
func (*ExpandBuilder) Top ¶ added in v0.2.6
func (b *ExpandBuilder) Top(n int) *ExpandBuilder
Top limits the number of expanded entities returned.
type ExpandOption ¶
type ExpandOption func(*expandConfig)
ExpandOption is a functional option for configuring entity expansion.
ExpandOption functions configure which fields, filters, ordering, and limits apply to related entities when using QueryBuilder.Expand. Multiple options can be passed to Expand to build complex nested queries.
See WithExpandSelect, WithExpandFilter, WithExpandOrderBy, etc. for available option constructors.
Example:
query.Expand("Orders",
WithExpandSelect("OrderID", "Amount"),
WithExpandFilter("Status eq 'Completed'"),
WithExpandTop(5),
)
func WithExpandFilter ¶
func WithExpandFilter(expr string) ExpandOption
WithExpandFilter specifies a filter for related entities.
WithExpandFilter adds a $filter clause to the expand, returning only related entities that match the filter expression.
Example:
Expand("Orders", WithExpandFilter("Status eq 'Completed'"))
func WithExpandLevels ¶ added in v0.6.0
func WithExpandLevels(n int) ExpandOption
WithExpandLevels sets the $levels query option on a nested expand, enabling recursive expansion of a navigation property.
Pass a positive integer for a fixed recursion depth, or LevelsMax to expand to the maximum depth the server supports.
This implements OData v4 spec section 11.2.5.2.1. Only OData v4 services support $levels; the option is silently ignored on v2 services.
Example:
// Expand recursively to maximum depth
Expand("Children", WithExpandLevels(traverse.LevelsMax))
// Expand exactly 3 levels deep
Expand("Children", WithExpandLevels(3))
func WithExpandOrderBy ¶
func WithExpandOrderBy(field string) ExpandOption
WithExpandOrderBy specifies ascending order for related entities.
WithExpandOrderBy adds an $orderby clause (ascending) to the expand. Multiple calls accumulate order expressions.
Example:
Expand("Orders", WithExpandOrderBy("OrderDate"))
func WithExpandOrderByDesc ¶
func WithExpandOrderByDesc(field string) ExpandOption
WithExpandOrderByDesc specifies descending order for related entities.
WithExpandOrderByDesc adds an $orderby clause (descending) to the expand. Multiple calls accumulate order expressions.
Example:
Expand("Orders", WithExpandOrderByDesc("OrderDate"))
func WithExpandSelect ¶
func WithExpandSelect(fields ...string) ExpandOption
WithExpandSelect specifies which fields to include from related entities.
WithExpandSelect adds a $select clause to the expand, limiting related entity results to only the specified fields. This reduces response size and improves performance.
Example:
Expand("Orders", WithExpandSelect("OrderID", "Amount", "OrderDate"))
func WithExpandSkip ¶
func WithExpandSkip(n int) ExpandOption
WithExpandSkip skips a number of related entities.
WithExpandSkip adds a $skip clause to the expand, skipping the specified number of related entities before returning results. Typically used with WithExpandTop.
Example:
Expand("Orders", WithExpandSkip(5).WithExpandTop(10))
func WithExpandTop ¶
func WithExpandTop(n int) ExpandOption
WithExpandTop limits the number of related entities to return.
WithExpandTop adds a $top clause to the expand, limiting the number of related entities to the specified count.
Example:
Expand("Orders", WithExpandTop(10))
type FilterBuilder ¶
type FilterBuilder struct {
// contains filtered or unexported fields
}
FilterBuilder provides a type-safe, chainable interface for building OData filter expressions.
FilterBuilder is obtained from QueryBuilder.Where(fieldName) and allows constructing filter conditions without writing raw OData syntax. Each comparison method returns *QueryBuilder to enable method chaining directly back to query building.
Lifecycle and Pooling: FilterBuilder instances are managed by an internal sync.Pool to reduce allocations during filtering operations. Instances obtained from Where() should NOT be retained after the method returns-they may be reused for subsequent Where() calls.
Object Pooling Benefits: - -1 allocation per Where() call (amortized) - Efficient for queries with many filters - Transparent to users; no manual pool management required
Chaining Pattern: Where() methods return *QueryBuilder, enabling seamless method chaining:
query.Where("Field1").Eq(value1).
Where("Field2").Gt(value2).
Where("Field3").StartsWith("prefix")
Type Safety: Comparison methods accept interface{} and automatically handle type conversion:
- int, int64: Converted to decimal notation
- string: Quoted with escape handling ('value' with ” for internal quotes)
- bool: Converted to "true" or "false"
- time.Time: Formatted per OData version (v2: /Date(ms)/, v4: RFC3339)
- nil: Converted to "null"
Available Comparisons: Numeric: Eq, Ne, Gt, Ge, Lt, Le String: Eq, Ne, StartsWith, EndsWith, Contains Boolean/Null: Eq, Ne Date/Time: All numeric comparisons (Gt, Lt, etc.)
String Functions vs Operators: String comparisons can use either:
- Operators: Eq() for exact match
- Functions: StartsWith(), Contains(), EndsWith() for partial matching
Examples:
Single filter:
query.Where("Status").Eq("Active")
// OData: $filter=Status eq 'Active'
Multiple filters (AND):
query.Where("Status").Eq("Active").
Where("Age").Gt(30)
// OData: $filter=Status eq 'Active' and Age gt 30
String operations:
query.Where("Name").StartsWith("John")
// OData: $filter=startswith(Name, 'John')
Mixed types:
query.Where("OrderDate").Gt(time.Now()).
Where("TotalAmount").Lt(1000).
Where("Status").Ne("Cancelled")
// OData: $filter=OrderDate gt <timestamp> and TotalAmount lt 1000 and Status ne 'Cancelled'
func (*FilterBuilder) Contains ¶
func (f *FilterBuilder) Contains(substr string) *QueryBuilder
Contains creates a substring filter using the OData substringof function.
Contains filters for records where the field value contains the specified substring anywhere within the value (case-sensitive per OData spec).
Behavior: Matches partial strings at any position:
- "John Smith" contains "Smith" ✓
- "Smith, John" contains "Smith" ✓
- "smith" contains "Smith" ✗ (case-sensitive)
OData Implementation: Contains uses the substringof() function (OData standard):
OData: substringof('substring', FieldName)
Performance Note: Substring searches typically require full table scans without indexes. For large datasets, ensure database indexes exist or use more specific filters.
Chaining: Contains returns *QueryBuilder to combine with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Basic substring match:
query.Where("Name").Contains("John")
// OData: $filter=substringof('John', Name)
// Matches: "John", "John Smith", "Johnny", "Johanna", "Adjourn"
Email domain search:
query.Where("Email").Contains("@example.com")
// OData: $filter=substringof('@example.com', Email)
// Matches: "user@example.com", "admin@example.com"
Combined with other filters:
query.Where("Company").Contains("Inc").
Where("Status").Eq("Active")
// OData: $filter=substringof('Inc', Company) and Status eq 'Active'
// Active companies with "Inc" in name
Search functionality:
query.Where("Description").Contains("urgent").
Where("Priority").Gt(2)
// OData: $filter=substringof('urgent', Description) and Priority gt 2
// High-priority items with "urgent" in description
func (*FilterBuilder) EndsWith ¶
func (f *FilterBuilder) EndsWith(suffix string) *QueryBuilder
EndsWith creates a suffix match filter using the OData endswith function.
EndsWith filters for records where the field value ends with the specified suffix string (case-sensitive per OData spec).
Behavior: Matches only at the end of the field value:
- "john.smith@example.com" endswith "@example.com" ✓
- "example.com" endswith "example.com" ✓
- "john.smith@example.com" endswith "@example.COM" ✗ (case-sensitive)
Efficiency: EndsWith may require full table scans unless database provides suffix indexes. More expensive than StartsWith for large datasets.
Use Cases:
- Email domains: EndsWith("@example.com")
- File extensions: EndsWith(".pdf"), EndsWith(".jpg")
- Phone number suffixes: EndsWith("-1234")
- URL patterns: EndsWith(".com"), EndsWith(".org")
- TLDs: EndsWith(".co.uk")
OData Implementation: EndsWith generates: endswith(FieldName, 'suffix')
Chaining: EndsWith returns *QueryBuilder to combine with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Email domain filter:
query.Where("Email").EndsWith("@company.com")
// OData: $filter=endswith(Email, '@company.com')
// Matches: "user@company.com", "admin@company.com"
File type filtering:
query.Where("Filename").EndsWith(".pdf")
// OData: $filter=endswith(Filename, '.pdf')
// Matches: "document.pdf", "report_2024.pdf"
Phone number area code:
query.Where("Phone").EndsWith("-2000")
// OData: $filter=endswith(Phone, '-2000')
// Matches: "555-1234-2000", "555-2000"
Combined with other filters:
query.Where("Email").EndsWith("@partner.com").
Where("Status").Eq("Active")
// OData: $filter=endswith(Email, '@partner.com') and Status eq 'Active'
// Active partner email addresses
func (*FilterBuilder) Eq ¶
func (f *FilterBuilder) Eq(value interface{}) *QueryBuilder
Eq creates an equality filter: field eq value.
Eq filters for records where the field exactly equals the specified value. This is the most common filter operation.
Type Handling: The value parameter is automatically converted to OData literal format:
- Strings: Quoted and escaped ('value' format)
- Numbers: Decimal notation (42, -3.14, etc.)
- Booleans: "true" or "false"
- DateTime: OData timestamp format
- null/nil: Converts to "null" for null checks
Chaining: Eq returns *QueryBuilder to chain with other filters or query methods:
query.Where("Status").Eq("Active").Where("Age").Gt(30)
Returns:
- *QueryBuilder: For method chaining
Examples:
Simple equality:
query.Where("Status").Eq("Active")
// OData: $filter=Status eq 'Active'
Numeric equality:
query.Where("ID").Eq(42)
// OData: $filter=ID eq 42
Boolean:
query.Where("IsDeleted").Eq(false)
// OData: $filter=IsDeleted eq false
Null check:
query.Where("DeletedAt").Eq(nil)
// OData: $filter=DeletedAt eq null
Chained with other filters:
query.Where("Status").Eq("Active").
Where("Priority").Gt(1).
Where("DueDate").Gt(today)
func (*FilterBuilder) Ge ¶
func (f *FilterBuilder) Ge(value interface{}) *QueryBuilder
Ge creates a greater-than-or-equal filter: field ge value.
Ge filters for records where the field value is greater than or equal to the specified value. Includes the boundary value unlike Gt.
Typical Uses:
- Minimum thresholds: Where("Score").Ge(80) for passing grades
- Date ranges: Where("CreatedDate").Ge(startDate)
- Cutoff values: Where("Priority").Ge(3)
- Age verification: Where("Age").Ge(18)
Boundary Behavior: Unlike Gt, Ge INCLUDES records that exactly equal the specified value. Ge(100) returns 100, 101, 102, ... while Gt(100) returns 101, 102, ...
Chaining: Ge returns *QueryBuilder for combining with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Minimum score threshold:
query.Where("Score").Ge(80)
// OData: $filter=Score ge 80
// Includes score=80 and above
Date range (start inclusive):
query.Where("CreatedDate").Ge(startDate)
// Returns records from startDate onward (inclusive)
Combined with less-than for range:
query.Where("Price").Ge(50).Where("Price").Le(200)
// OData: $filter=Price ge 50 and Price le 200
// Returns $50-$200 range (both boundaries included)
Inventory threshold:
query.Where("StockLevel").Ge(10)
// OData: $filter=StockLevel ge 10
// Returns items with 10 or more in stock
func (*FilterBuilder) Gt ¶
func (f *FilterBuilder) Gt(value interface{}) *QueryBuilder
Gt creates a greater-than filter: field gt value.
Gt filters for records where the field value is strictly greater than the specified value. Useful for numeric ranges, dates, and ordered comparisons.
Typical Uses:
- Price ranges: Where("Price").Gt(100)
- Date filtering: Where("CreatedDate").Gt(startDate)
- Numeric thresholds: Where("Age").Gt(18)
- Ordered sequences: Where("Sequence").Gt(currentSeq)
Type Support: Works with any OData type supporting numeric comparison:
- Integers: 42
- Decimals: 99.99
- Dates: time.Time (converted to OData timestamp)
- Enums: Numeric enum values
- Durations: In milliseconds or appropriate units
Chaining: Gt returns *QueryBuilder to combine with other filters and query methods.
Returns:
- *QueryBuilder: For method chaining
Examples:
Simple numeric threshold:
query.Where("Age").Gt(30)
// OData: $filter=Age gt 30
// Returns people over 30 years old
Date filtering:
query.Where("OrderDate").Gt(startOfMonth)
// OData: $filter=OrderDate gt <timestamp>
// Returns orders from start of month onward
Price range (combined with Lt):
query.Where("Price").Gt(10).Where("Price").Lt(100)
// OData: $filter=Price gt 10 and Price lt 100
// Returns products between $10 and $100
Complex filtering:
query.Where("Salary").Gt(50000).
Where("Department").Eq("Sales").
Where("HireDate").Gt(minDate)
func (*FilterBuilder) In ¶
func (f *FilterBuilder) In(values ...interface{}) *QueryBuilder
In creates a membership test filter for checking if a field value is in a set.
In generates an OData v4 "in" expression: field in (val1, val2, val3, ...) This is efficient for testing whether a value belongs to a known set.
OData Version: In is only available in OData v4. OData v2 requires using multiple Eq conditions joined with "or": (field eq val1) or (field eq val2) or (field eq val3)
Behavior: Returns all records where the field equals ANY of the provided values. Equivalent to: OR-ing multiple Eq conditions.
Empty Values: If In is called with no values, it returns the query unchanged (no filter added). This allows optional filtering: if len(categories) > 0 { q = q.Where("Cat").In(categories...) }
Performance: Much more efficient than multiple Where().Eq() calls:
- Single clause: Where("Status").In("Active", "Pending", "Review")
- vs multiple: Where("Status").Eq("Active").Where("Status").Eq("Pending")...
- Network: 1 query parameter vs 3
- Processing: Server can use IN index optimization
Type Handling: All value types are automatically converted to OData literals:
- Strings: Single-quoted ('value')
- Numbers: Decimal notation
- Booleans: true/false
- Dates: OData timestamp format
Chaining: In returns *QueryBuilder to combine with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Status filter (common use):
query.Where("Status").In("Active", "Pending", "Review")
// OData: $filter=Status in ('Active', 'Pending', 'Review')
// Matches any of three statuses
Category selection:
query.Where("CategoryID").In(1, 2, 3, 5)
// OData: $filter=CategoryID in (1, 2, 3, 5)
// Products in categories 1, 2, 3, or 5
Priority levels:
priorities := []int{1, 2, 3} // High, Medium, Low
query.Where("Priority").In(append(priorities, 0)...)
// All high/medium/low plus unassigned
Optional filtering:
var allowedStatuses []string
q := client.From("Orders")
if len(allowedStatuses) > 0 {
q = q.Where("Status").In(allowedStatuses...)
}
results, _ := q.Collect(ctx)
Combined with other filters:
query.Where("Region").In("North", "South", "West").
Where("SalesAmount").Gt(10000).
OrderByDesc("SalesAmount").
Top(100)
// High-value sales in three regions, sorted
func (*FilterBuilder) Le ¶
func (f *FilterBuilder) Le(value interface{}) *QueryBuilder
Le creates a less-than-or-equal filter: field le value.
Le filters for records where the field value is less than or equal to the specified value. Includes the boundary value unlike Lt.
Typical Uses:
- Maximum thresholds: Where("Duration").Le(60) for minute-long clips
- On-or-before dates: Where("EndDate").Le(quarterEnd)
- Upper limits: Where("PageSize").Le(1000)
- Age/date cutoffs: Where("Age").Le(65)
Boundary Behavior: Unlike Lt, Le INCLUDES records that exactly equal the specified value. Le(100) returns 100, 99, 98, ... while Lt(100) returns 99, 98, ...
Common Pattern: Combine with Ge for inclusive ranges: Where("Age").Ge(18).Where("Age").Le(65)
Chaining: Le returns *QueryBuilder for combining with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Maximum duration (inclusive):
query.Where("DurationSeconds").Le(300)
// OData: $filter=DurationSeconds le 300
// Videos 300 seconds or shorter
On-or-before date:
query.Where("EndDate").Le(today)
// Includes today and all past dates
Inclusive range:
query.Where("Price").Ge(50).Where("Price").Le(200)
// OData: $filter=Price ge 50 and Price le 200
// Exactly $50-$200 including boundaries
Age demographic:
query.Where("Age").Ge(13).Where("Age").Le(19)
// OData: $filter=Age ge 13 and Age le 19
// Ages 13 through 19 inclusive (teenagers)
func (*FilterBuilder) Lt ¶
func (f *FilterBuilder) Lt(value interface{}) *QueryBuilder
Lt creates a less-than filter: field lt value.
Lt filters for records where the field value is strictly less than the specified value. Useful for upper bounds and before/prior comparisons.
Typical Uses:
- Upper price limits: Where("Price").Lt(100)
- Before dates: Where("DeadlineDate").Lt(today)
- Below thresholds: Where("DaysOverdue").Lt(7)
- Version checks: Where("BuildNumber").Lt(2000)
Boundary Behavior: Lt does NOT include the specified value. Lt(100) matches 99, 98, 97... Use Le() to include the boundary.
Chaining: Lt returns *QueryBuilder for combining with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Upper price limit (exclusive):
query.Where("Price").Lt(100)
// OData: $filter=Price lt 100
// Returns prices < $100 (99.99, 50, etc., but not 100)
Past events:
query.Where("EventDate").Lt(today)
// Returns events before today (yesterday and earlier)
Range query (both exclusive):
query.Where("Age").Gt(18).Where("Age").Lt(65)
// OData: $filter=Age gt 18 and Age lt 65
// Returns ages 19-64
Early warning system:
query.Where("DaysUntilExpiry").Lt(30)
// OData: $filter=DaysUntilExpiry lt 30
// Items expiring within 30 days
func (*FilterBuilder) Ne ¶
func (f *FilterBuilder) Ne(value interface{}) *QueryBuilder
Ne creates a not-equal filter: field ne value.
Ne filters for records where the field does NOT equal the specified value. Useful for excluding specific values or statuses.
Use Cases:
- Exclude deleted records: Where("Status").Ne("Deleted")
- Exclude null values: Where("Email").Ne(nil)
- Exclude specific user: Where("UserID").Ne(adminID)
Type Handling: Same automatic conversion as Eq() for all value types.
Chaining: Ne returns *QueryBuilder to chain with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Exclude status:
query.Where("Status").Ne("Deleted")
// OData: $filter=Status ne 'Deleted'
Exclude null:
query.Where("Email").Ne(nil)
// OData: $filter=Email ne null
Exclude numeric value:
query.Where("Quantity").Ne(0)
// OData: $filter=Quantity ne 0
Combined filtering (active non-deleted):
query.Where("Status").Eq("Active").
Where("Deleted").Ne(true)
// OData: $filter=Status eq 'Active' and Deleted ne true
func (*FilterBuilder) StartsWith ¶
func (f *FilterBuilder) StartsWith(prefix string) *QueryBuilder
StartsWith creates a prefix match filter using the OData startswith function.
StartsWith filters for records where the field value begins with the specified prefix string (case-sensitive per OData spec).
Behavior: Matches only at the beginning of the field value:
- "Product A" startswith "Product" ✓
- "Product A" startswith "product" ✗ (case-sensitive)
- "A Product" startswith "Product" ✗ (not at start)
Efficiency: StartsWith is more efficient than Contains for prefix matching and can use database indexes on string columns (B-tree prefix matching).
Use Cases:
- Category codes: StartsWith("A-") for category A codes
- Postal codes: StartsWith("90210") for specific zip code area
- Enum prefixes: StartsWith("STATUS_") for status fields
- Name filtering: StartsWith("John") for first names
- Auto-complete: StartsWith(userInput) for search suggestions
OData Implementation: StartsWith generates: startswith(FieldName, 'prefix')
Chaining: StartsWith returns *QueryBuilder to combine with other filters.
Returns:
- *QueryBuilder: For method chaining
Examples:
Product code filtering:
query.Where("ProductCode").StartsWith("ELEC-")
// OData: $filter=startswith(ProductCode, 'ELEC-')
// Matches: "ELEC-001", "ELEC-TV-50"
Name search (auto-complete):
query.Where("LastName").StartsWith("Smith")
// OData: $filter=startswith(LastName, 'Smith')
// Matches: "Smith", "Smithson", "Smithers" (but not "Smithy" capitalization)
Regional filtering:
query.Where("ZipCode").StartsWith("90210")
// OData: $filter=startswith(ZipCode, '90210')
// Matches: "90210", "90210-1234" (plus 4 format)
Combined with other filters:
query.Where("Email").StartsWith("admin").
Where("Department").Eq("IT")
// OData: $filter=startswith(Email, 'admin') and Department eq 'IT'
// IT admin email addresses
type FilterExpr ¶ added in v0.2.14
type FilterExpr struct {
// contains filtered or unexported fields
}
FilterExpr is a chainable OData filter expression builder.
FilterExpr provides a fluent API for constructing type-safe OData $filter expressions without string concatenation. Each method returns *FilterExpr to enable method chaining, and the final Build() or String() method generates the OData filter string.
Example:
expr := F("Name").Eq("Alice")
filter := expr.Build() // "Name eq 'Alice'"
func And ¶ added in v0.2.14
func And(exprs ...*FilterExpr) *FilterExpr
And combines multiple filter expressions with logical AND.
And takes multiple FilterExpr arguments and combines them with the 'and' operator, wrapping each in parentheses. If no expressions are provided, returns nil.
Example:
And(F("Age").Ge(18), F("Active").Eq(true)).Build()
// "(Age ge 18) and (Active eq true)"
func F ¶ added in v0.2.14
func F(field string) *FilterExpr
F starts a filter expression for a field.
F creates a new FilterExpr targeting the specified field. This is the entry point for building type-safe OData filter expressions.
Example:
F("Age").Gt(18).Build()
func Not ¶ added in v0.2.14
func Not(expr *FilterExpr) *FilterExpr
Not creates a logical NOT filter: not (expression).
Not negates the given filter expression.
Example:
Not(F("Deleted").Eq(true)).Build()
// "not (Deleted eq true)"
func Or ¶ added in v0.2.14
func Or(exprs ...*FilterExpr) *FilterExpr
Or combines multiple filter expressions with logical OR.
Or takes multiple FilterExpr arguments and combines them with the 'or' operator, wrapping each in parentheses. If no expressions are provided, returns nil.
Example:
Or(F("City").Eq("NY"), F("City").Eq("LA")).Build()
// "(City eq 'NY') or (City eq 'LA')"
func (*FilterExpr) Build ¶ added in v0.2.14
func (f *FilterExpr) Build() string
Build returns the OData filter string.
Build finalizes the filter expression and returns the OData $filter string. This method does not modify the FilterExpr and can be called multiple times.
func (*FilterExpr) Contains ¶ added in v0.2.14
func (f *FilterExpr) Contains(value string) *FilterExpr
Contains creates a string contains filter: contains(field, value).
Contains checks if the field (string) contains the specified substring.
func (*FilterExpr) EndsWith ¶ added in v0.2.14
func (f *FilterExpr) EndsWith(value string) *FilterExpr
EndsWith creates a string ends-with filter: endswith(field, value).
EndsWith checks if the field (string) ends with the specified suffix.
func (*FilterExpr) Eq ¶ added in v0.2.14
func (f *FilterExpr) Eq(value interface{}) *FilterExpr
Eq creates an equality filter: field eq value.
func (*FilterExpr) Ge ¶ added in v0.2.14
func (f *FilterExpr) Ge(value interface{}) *FilterExpr
Ge creates a greater-than-or-equal filter: field ge value.
func (*FilterExpr) Gt ¶ added in v0.2.14
func (f *FilterExpr) Gt(value interface{}) *FilterExpr
Gt creates a greater-than filter: field gt value.
func (*FilterExpr) Le ¶ added in v0.2.14
func (f *FilterExpr) Le(value interface{}) *FilterExpr
Le creates a less-than-or-equal filter: field le value.
func (*FilterExpr) Lt ¶ added in v0.2.14
func (f *FilterExpr) Lt(value interface{}) *FilterExpr
Lt creates a less-than filter: field lt value.
func (*FilterExpr) Ne ¶ added in v0.2.14
func (f *FilterExpr) Ne(value interface{}) *FilterExpr
Ne creates an inequality filter: field ne value.
func (*FilterExpr) Reset ¶ added in v0.22.0
func (f *FilterExpr) Reset() *FilterExpr
Reset clears the filter expression, allowing the FilterExpr to be reused for building a new expression without allocating a new object.
Example:
expr := F("Name").Eq("Alice")
filter1 := expr.Build() // "Name eq 'Alice'"
expr.Reset()
expr.Eq("Bob")
filter2 := expr.Build() // "Name eq 'Bob'"
func (*FilterExpr) StartsWith ¶ added in v0.2.14
func (f *FilterExpr) StartsWith(value string) *FilterExpr
StartsWith creates a string starts-with filter: startswith(field, value).
StartsWith checks if the field (string) starts with the specified prefix.
func (*FilterExpr) String ¶ added in v0.2.14
func (f *FilterExpr) String() string
String implements the Stringer interface, returning the OData filter string.
String is equivalent to Build() and allows FilterExpr to be used with fmt.Print and other functions expecting Stringer.
type FunctionBuilder ¶
type FunctionBuilder struct {
// contains filtered or unexported fields
}
FunctionBuilder provides a fluent API for calling OData Functions (v4).
FunctionBuilder enables execution of OData Function calls, which are read-only operations that may take parameters and return values. Functions use HTTP GET with parameters encoded in the URL path.
Typical usage:
result, err := client.Function("GetProducts").
Param("category", "Electronics").
Param("maxPrice", 1000).
Execute(ctx)
func (*FunctionBuilder) Execute ¶
func (f *FunctionBuilder) Execute(ctx context.Context) (map[string]interface{}, error)
Execute calls the function and returns the result as a map.
Execute constructs the OData function URL with parameters, sends an HTTP GET request, and parses the response into a map. The response format is normalized by [parseResponseValue].
Returns a map containing the function result, or an error if the call fails or the response status is not 2xx.
Example:
result, err := client.Function("GetTopProducts").
Param("count", 10).
Execute(ctx)
// result contains the function response data
func (*FunctionBuilder) Invoke ¶ added in v0.2.11
func (f *FunctionBuilder) Invoke(ctx context.Context, result any) error
Invoke calls the function and unmarshals the response into result.
Invoke is the result-receiver variant of FunctionBuilder.Execute. It sends an HTTP GET request to the OData function URL (with inline parameters) and unmarshals the response body into the value pointed to by result.
result must be a non-nil pointer. Invoke uses JSON round-trip via [mapToJsonStruct] for the unmarshaling, so standard json struct tags are respected.
Returns an error if the HTTP call fails, the response status is not 2xx, or unmarshaling fails.
Example:
type StatsResult struct {
Count int `json:"count"`
Label string `json:"label"`
}
var stats StatsResult
err := client.Function("GetStats").Param("top", 10).Invoke(ctx, &stats)
func (*FunctionBuilder) Param ¶
func (f *FunctionBuilder) Param(key string, value interface{}) *FunctionBuilder
Param adds a parameter to the function call.
Param appends a key-value parameter to the function. Parameters are encoded in the URL path (e.g., /GetProducts(category='Electronics',maxPrice=1000)).
Returns the receiver for method chaining.
Example:
f.Param("name", "John").Param("age", 30)
type FunctionImportBuilder ¶
type FunctionImportBuilder struct {
// contains filtered or unexported fields
}
FunctionImportBuilder provides a fluent API for calling OData Function Imports (v2).
FunctionImportBuilder enables execution of OData v2 Function Imports, which are similar to v4 Functions but use the v2 protocol. Function Imports use HTTP GET with parameters encoded in the URL path.
Typical usage:
result, err := client.FunctionImport("GetProductsByRating").
Param("minRating", 4).
Execute(ctx)
func (*FunctionImportBuilder) Execute ¶
func (f *FunctionImportBuilder) Execute(ctx context.Context) (map[string]interface{}, error)
Example:
result, err := client.FunctionImport("GetTop10Orders").Execute(ctx)
func (*FunctionImportBuilder) Invoke ¶ added in v0.2.19
func (f *FunctionImportBuilder) Invoke(ctx context.Context, result any) error
Invoke calls the function import and decodes the response into result.
For GET requests, parameters are encoded in the URL as FuncName(k=v,...). For POST requests, parameters are sent as a JSON body.
The response is unwrapped from the OData {"d":{...}} envelope when present before decoding into result. Pass nil to discard the response body.
Returns an error if the HTTP call fails, the status is not 2xx, or JSON decoding fails.
Example:
var stats Stats
err := client.FunctionImport("GetStats").Invoke(ctx, &stats)
func (*FunctionImportBuilder) InvokeCollection ¶ added in v0.2.19
func (f *FunctionImportBuilder) InvokeCollection(ctx context.Context, results any) error
InvokeCollection calls the function import and decodes a collection response.
It handles the following OData collection envelope formats:
- {"d":{"results":[...]}} - OData v2 collection
- {"results":[...]} - flat results array
- {"value":[...]} - OData v4 collection
- [...] - bare JSON array
results must be a pointer to a slice, or nil to discard.
Returns an error if the HTTP call fails, the status is not 2xx, or JSON decoding fails.
Example:
var orders []Order
err := client.FunctionImport("GetOrders").InvokeCollection(ctx, &orders)
func (*FunctionImportBuilder) Method ¶ added in v0.2.19
func (f *FunctionImportBuilder) Method(m string) *FunctionImportBuilder
Execute calls the function import and returns the result as a map.
Execute constructs the OData v2 function import URL with parameters, sends an HTTP GET request, and parses the response into a map. This is similar to FunctionBuilder.Execute but for OData v2 Function Imports.
Returns a map containing the function import result, or an error if the call fails. Method sets the HTTP method for the function import call (default is "GET").
Use Method("POST") for function imports that modify state or when parameters must be sent in the request body instead of the URL.
Returns the receiver for method chaining.
Example:
err := client.FunctionImport("ProcessQueue").Method("POST").Invoke(ctx, &result)
func (*FunctionImportBuilder) Param ¶
func (f *FunctionImportBuilder) Param(key string, value interface{}) *FunctionImportBuilder
Param adds a parameter to the function import.
Param appends a key-value parameter to the function import. Parameters are encoded in the URL path, similar to FunctionBuilder.Param.
Returns the receiver for method chaining.
type FunctionImportInfo ¶
type FunctionImportInfo struct {
// Name is the name of the function import.
Name string
// Parameters is the list of parameters for this function.
Parameters []FunctionParameter
// ReturnType is the return type of the function.
ReturnType string
// IsComposable indicates if the function can be used in URL composition.
IsComposable bool
}
FunctionImportInfo represents a function import definition (OData v2). Function imports are operations that can be called on the service.
type FunctionInfo ¶
type FunctionInfo struct {
Name string
Parameters []FunctionParameter
ReturnType string
IsComposable bool
}
FunctionInfo represents an OData function (v4).
type FunctionParameter ¶
FunctionParameter represents a function parameter.
type GeographyLineString ¶ added in v0.10.0
type GeographyLineString struct {
Points []GeographyPoint
}
GeographyLineString represents a geographic line string (Edm.GeographyLineString). A LineString requires at least 2 points.
func (GeographyLineString) MarshalJSON ¶ added in v0.10.0
func (ls GeographyLineString) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeographyLineString.
func (GeographyLineString) ODataLiteral ¶ added in v0.10.0
func (ls GeographyLineString) ODataLiteral() string
ODataLiteral returns the OData URL literal for a GeographyLineString. Format: geography'SRID=4326;LINESTRING(lon1 lat1,lon2 lat2,...)'
func (*GeographyLineString) UnmarshalJSON ¶ added in v0.10.0
func (ls *GeographyLineString) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeographyLineString.
type GeographyMultiPoint ¶ added in v0.10.0
type GeographyMultiPoint struct {
Points []GeographyPoint
}
GeographyMultiPoint represents a collection of geographic points (Edm.GeographyMultiPoint).
func (GeographyMultiPoint) MarshalJSON ¶ added in v0.10.0
func (mp GeographyMultiPoint) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeographyMultiPoint.
func (*GeographyMultiPoint) UnmarshalJSON ¶ added in v0.10.0
func (mp *GeographyMultiPoint) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeographyMultiPoint.
type GeographyPoint ¶ added in v0.10.0
type GeographyPoint struct {
// Longitude is the east-west coordinate (X axis), range -180 to 180.
Longitude float64
// Latitude is the north-south coordinate (Y axis), range -90 to 90.
Latitude float64
}
GeographyPoint represents a WGS84 geographic coordinate (Edm.GeographyPoint).
Coordinates follow the OData/WKT convention: Longitude first, then Latitude. This matches GeoJSON (RFC 7946) order.
Example OData literal: geography'SRID=4326;POINT(13.408 52.518)' Example GeoJSON: {"type":"Point","coordinates":[13.408,52.518]}
func AcquireGeographyPoint ¶ added in v0.10.0
func AcquireGeographyPoint(lon, lat float64) *GeographyPoint
AcquireGeographyPoint returns a *GeographyPoint from the pool. The caller must call ReleaseGeographyPoint when done.
func ParseGeographyPoint ¶ added in v0.10.0
func ParseGeographyPoint(s string) (GeographyPoint, error)
ParseGeographyPoint parses an OData geography point literal. Accepts: geography'SRID=4326;POINT(lon lat)' or POINT(lon lat)
func (GeographyPoint) MarshalJSON ¶ added in v0.10.0
func (p GeographyPoint) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeographyPoint. Produces GeoJSON Point: {"type":"Point","coordinates":[lon,lat]}
func (GeographyPoint) ODataLiteral ¶ added in v0.10.0
func (p GeographyPoint) ODataLiteral() string
ODataLiteral returns the OData URL literal for a GeographyPoint. Format: geography'SRID=4326;POINT(lon lat)'
func (*GeographyPoint) UnmarshalJSON ¶ added in v0.10.0
func (p *GeographyPoint) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeographyPoint.
type GeographyPolygon ¶ added in v0.10.0
type GeographyPolygon struct {
ExteriorRing []GeographyPoint
InteriorRings [][]GeographyPoint
}
GeographyPolygon represents a geographic polygon (Edm.GeographyPolygon). ExteriorRing is the outer boundary; InteriorRings are holes. All rings must be closed (first point == last point).
func (GeographyPolygon) MarshalJSON ¶ added in v0.10.0
func (poly GeographyPolygon) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeographyPolygon.
func (GeographyPolygon) ODataLiteral ¶ added in v0.10.0
func (poly GeographyPolygon) ODataLiteral() string
ODataLiteral returns the OData URL literal for a GeographyPolygon. Format: geography'SRID=4326;POLYGON((ring1),(ring2),...)'
func (*GeographyPolygon) UnmarshalJSON ¶ added in v0.10.0
func (poly *GeographyPolygon) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeographyPolygon.
type GeometryLineString ¶ added in v0.10.0
type GeometryLineString struct {
Points []GeometryPoint
}
GeometryLineString represents a planar line string (Edm.GeometryLineString).
func (GeometryLineString) MarshalJSON ¶ added in v0.10.0
func (ls GeometryLineString) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeometryLineString.
func (GeometryLineString) ODataLiteral ¶ added in v0.10.0
func (ls GeometryLineString) ODataLiteral() string
ODataLiteral returns the OData URL literal for a GeometryLineString.
func (*GeometryLineString) UnmarshalJSON ¶ added in v0.10.0
func (ls *GeometryLineString) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeometryLineString.
type GeometryMultiPoint ¶ added in v0.10.0
type GeometryMultiPoint struct {
Points []GeometryPoint
}
GeometryMultiPoint represents a collection of planar points (Edm.GeometryMultiPoint).
func (GeometryMultiPoint) MarshalJSON ¶ added in v0.10.0
func (mp GeometryMultiPoint) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeometryMultiPoint.
func (*GeometryMultiPoint) UnmarshalJSON ¶ added in v0.10.0
func (mp *GeometryMultiPoint) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeometryMultiPoint.
type GeometryPoint ¶ added in v0.10.0
GeometryPoint represents a point in a flat (Euclidean) coordinate system (Edm.GeometryPoint).
Example OData literal: geometry'SRID=0;POINT(1.5 2.5)' Example GeoJSON: {"type":"Point","coordinates":[1.5,2.5]}
func AcquireGeometryPoint ¶ added in v0.10.0
func AcquireGeometryPoint(x, y float64) *GeometryPoint
AcquireGeometryPoint returns a *GeometryPoint from the pool.
func ParseGeometryPoint ¶ added in v0.10.0
func ParseGeometryPoint(s string) (GeometryPoint, error)
ParseGeometryPoint parses an OData geometry point literal. Accepts: geometry'SRID=0;POINT(x y)' or POINT(x y)
func (GeometryPoint) MarshalJSON ¶ added in v0.10.0
func (p GeometryPoint) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeometryPoint. Produces GeoJSON Point: {"type":"Point","coordinates":[x,y]}
func (GeometryPoint) ODataLiteral ¶ added in v0.10.0
func (p GeometryPoint) ODataLiteral() string
ODataLiteral returns the OData URL literal for a GeometryPoint. Format: geometry'SRID=0;POINT(x y)'
func (*GeometryPoint) UnmarshalJSON ¶ added in v0.10.0
func (p *GeometryPoint) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeometryPoint.
type GeometryPolygon ¶ added in v0.10.0
type GeometryPolygon struct {
ExteriorRing []GeometryPoint
InteriorRings [][]GeometryPoint
}
GeometryPolygon represents a planar polygon (Edm.GeometryPolygon).
func (GeometryPolygon) MarshalJSON ¶ added in v0.10.0
func (poly GeometryPolygon) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for GeometryPolygon.
func (GeometryPolygon) ODataLiteral ¶ added in v0.10.0
func (poly GeometryPolygon) ODataLiteral() string
ODataLiteral returns the OData URL literal for a GeometryPolygon.
func (*GeometryPolygon) UnmarshalJSON ¶ added in v0.10.0
func (poly *GeometryPolygon) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler for GeometryPolygon.
type GraphConfig ¶ added in v0.2.15
type GraphError ¶ added in v0.2.15
func (*GraphError) Error ¶ added in v0.2.15
func (e *GraphError) Error() string
type Guid ¶
type Guid [16]byte
Guid represents an OData Edm.Guid value (UUID).
Guid wraps [16]byte to represent OData GUID values in the standard UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12 hex digits).
The Guid type provides custom JSON marshaling/unmarshaling to convert between UUID string format and the internal 16-byte representation.
Example formats in JSON responses:
"550e8400-e29b-41d4-a716-446655440000"
Use Guid.String to convert to standard UUID string format.
Example:
type Entity struct {
ID traverse.Guid `json:"id"`
}
json.Unmarshal([]byte(`{"id":"550e8400-e29b-41d4-a716-446655440000"}`), &entity)
uuidStr := entity.ID.String() // "550e8400-e29b-41d4-a716-446655440000"
func (Guid) String ¶
String returns the UUID string representation.
String converts the internal 16-byte representation to standard UUID format (8-4-4-4-12 hex digits).
func (*Guid) UnmarshalJSON ¶
UnmarshalJSON decodes UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
UnmarshalJSON parses a UUID string and converts it to the internal 16-byte representation. The UUID must be in standard format with 8-4-4-4-12 hex digit groups.
Returns an error if the format is invalid or contains non-hexadecimal characters.
type LambdaBuilder ¶ added in v0.2.8
type LambdaBuilder struct {
// contains filtered or unexported fields
}
LambdaBuilder constructs the expression inside an OData lambda operator (any/all).
LambdaBuilder is obtained from QueryBuilder.LambdaAny or QueryBuilder.LambdaAll via a callback function. Use Field() to start building conditions within the lambda.
The variable name used in the lambda expression is automatically derived from the first letter of the collection field name (e.g. "tags" -> "t", "items" -> "i").
func (*LambdaBuilder) Field ¶ added in v0.2.8
func (b *LambdaBuilder) Field(name string) *LambdaCondition
Field begins a condition expression for the given field within the lambda. The field is referenced as varName/fieldName in the generated OData expression.
b.Field("name").Eq("admin")
// produces: t/name eq 'admin' (when varName is "t")
type LambdaCondition ¶ added in v0.2.8
type LambdaCondition struct {
// contains filtered or unexported fields
}
LambdaCondition builds a comparison or function expression within a lambda.
LambdaCondition is obtained from LambdaBuilder.Field and provides comparison and string function operators. Each operator appends an expression to the parent LambdaBuilder and returns the LambdaBuilder for further chaining.
func (*LambdaCondition) Contains ¶ added in v0.2.8
func (c *LambdaCondition) Contains(s string) *LambdaBuilder
Contains adds a contains() function condition.
func (*LambdaCondition) EndsWith ¶ added in v0.2.8
func (c *LambdaCondition) EndsWith(s string) *LambdaBuilder
EndsWith adds an endswith() function condition.
func (*LambdaCondition) Eq ¶ added in v0.2.8
func (c *LambdaCondition) Eq(v any) *LambdaBuilder
Eq adds an equality condition: path eq value.
func (*LambdaCondition) Ge ¶ added in v0.2.8
func (c *LambdaCondition) Ge(v any) *LambdaBuilder
Ge adds a greater-than-or-equal condition: path ge value.
func (*LambdaCondition) Gt ¶ added in v0.2.8
func (c *LambdaCondition) Gt(v any) *LambdaBuilder
Gt adds a greater-than condition: path gt value.
func (*LambdaCondition) Le ¶ added in v0.2.8
func (c *LambdaCondition) Le(v any) *LambdaBuilder
Le adds a less-than-or-equal condition: path le value.
func (*LambdaCondition) Lt ¶ added in v0.2.8
func (c *LambdaCondition) Lt(v any) *LambdaBuilder
Lt adds a less-than condition: path lt value.
func (*LambdaCondition) Ne ¶ added in v0.2.8
func (c *LambdaCondition) Ne(v any) *LambdaBuilder
Ne adds a not-equal condition: path ne value.
func (*LambdaCondition) StartsWith ¶ added in v0.2.8
func (c *LambdaCondition) StartsWith(s string) *LambdaBuilder
StartsWith adds a startswith() function condition.
type MeasuresVocabulary ¶ added in v0.8.0
type MeasuresVocabulary struct {
// ISOCurrency is the ISO 4217 currency code for a monetary amount property.
// Corresponds to Org.OData.Measures.V1.ISOCurrency.
// The value may be a literal currency code (e.g. "USD") or a path to a property
// that holds the currency code.
ISOCurrency string
// Scale is the number of significant decimal places for a currency or measure value.
// Corresponds to Org.OData.Measures.V1.Scale.
Scale *int
// Unit is the unit of measure for the property value.
// Corresponds to Org.OData.Measures.V1.Unit.
// The value may be a literal unit symbol or a path to a property that holds it.
Unit string
// SIPrefix is the SI prefix that multiplies the unit (e.g. "Kilo", "Mega", "Milli").
// Corresponds to Org.OData.Measures.V1.SIPrefix.
SIPrefix string
// DurationGranularity is the minimum granularity of duration values (e.g. "days", "hours").
// Corresponds to Org.OData.Measures.V1.DurationGranularity.
DurationGranularity string
}
MeasuresVocabulary defines Org.OData.Measures.V1 annotation terms.
The Measures vocabulary annotates properties with unit-of-measure semantics. These annotations are defined in the OASIS OData Measures vocabulary (namespace: Org.OData.Measures.V1).
func ParseMeasuresVocabulary ¶ added in v0.8.0
func ParseMeasuresVocabulary(annotations map[string]string) MeasuresVocabulary
ParseMeasuresVocabulary extracts Org.OData.Measures.V1 annotation terms from a raw annotation map. The map keys are fully-qualified term names (e.g. "Org.OData.Measures.V1.ISOCurrency").
Example annotations map:
map[string]string{
"Org.OData.Measures.V1.ISOCurrency": "USD",
"Org.OData.Measures.V1.Scale": "2",
"Org.OData.Measures.V1.Unit": "kg",
}
type MemoryCache ¶
type MemoryCache struct {
// contains filtered or unexported fields
}
MemoryCache is a simple in-memory cache for OData metadata.
MemoryCache is safe for concurrent use using lock-free sync.Map. It stores Metadata objects keyed by service endpoint or custom identifier.
Metadata is cached for the lifetime of the MemoryCache instance; it is not automatically expired. For long-running processes, consider periodic cache invalidation or custom expiration logic in a wrapper.
Example:
cache := traverse.NewMemoryCache()
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.service/"),
traverse.WithCache(cache),
)
// First call fetches and caches metadata
metadata, _ := client.Metadata(ctx)
// Subsequent calls hit the cache
metadata, _ := client.Metadata(ctx)
func NewMemoryCache ¶
func NewMemoryCache() *MemoryCache
NewMemoryCache creates a new in-memory metadata cache.
NewMemoryCache returns a ready-to-use *MemoryCache that is safe for concurrent access from multiple goroutines.
func (*MemoryCache) Clear ¶
func (c *MemoryCache) Clear()
Clear removes all entries from the cache.
Clear iterates over all cached entries and deletes them, leaving the cache empty. Useful for cache invalidation or cleanup.
func (*MemoryCache) Get ¶
func (c *MemoryCache) Get(key string) (*Metadata, bool)
Get retrieves cached metadata by key.
Get returns the Metadata stored under the given key, or (nil, false) if not cached.
func (*MemoryCache) Set ¶
func (c *MemoryCache) Set(key string, metadata *Metadata)
Set stores metadata in the cache with the given key.
Set overwrites any existing entry for the key. The operation is atomic and safe for concurrent use.
type Metadata ¶
type Metadata struct {
// Version is the OData protocol version (e.g., "2.0" or "4.0").
Version string
// Namespace is the target namespace of the metadata document.
Namespace string
// EntityTypes is the list of all entity types defined in the service.
EntityTypes []EntityType
// EntitySets is the list of all entity set definitions.
EntitySets []EntitySetInfo
// Associations describes relationships between entity types.
Associations []Association
// FunctionImports contains function imports available in the service.
FunctionImports []FunctionImportInfo
// Actions contains action definitions (OData v4).
Actions []ActionInfo
// Functions contains function definitions (OData v4).
Functions []FunctionInfo
// ComplexTypes contains complex type definitions (OData v4).
ComplexTypes []ComplexType
// EnumTypes contains enum type definitions (OData v4).
EnumTypes []EnumType
}
Metadata represents the parsed OData $metadata document. It contains the Entity Data Model (EDM) describing all entity types, relationships, and operations available in the service.
func ParseCSDLJSON ¶ added in v0.2.28
ParseCSDLJSON parses an OData CSDL JSON v4.01 document and returns the service metadata. The input may be a full service document or just the $metadata endpoint response in JSON format.
CSDL JSON is the JSON alternative to the XML EDMX format used by services such as Microsoft Graph. The top-level object contains $Version and $EntityContainer annotations alongside schema namespace objects whose entries describe entity types, complex types, enum types, actions, functions, and entity containers.
Returns ErrMetadataInvalid wrapped in the error if the document cannot be decoded as valid JSON.
func ParseCSDLJSONReader ¶ added in v0.2.28
ParseCSDLJSONReader is like ParseCSDLJSON but reads from an io.Reader.
ParseCSDLJSONReader is convenient when the CSDL JSON document is available as an HTTP response body or other streaming source.
func ParseEDMX ¶
ParseEDMX parses an OData $metadata XML response in EDMX format.
ParseEDMX reads an OData service's metadata document (typically from $metadata endpoint) and extracts schema information including entity types, entity sets, properties, navigation properties, and associations.
The EDMX (Entity Data Model XML) format is the standard way OData services describe their data model. This parser supports both OData v2 and v4 metadata formats with special handling for SAP annotations (sap:label, sap:sortable, sap:filterable, sap:searchable, sap:required-in-filter, sap:text, sap:unit, sap:value-list, sap:display-format, sap:field-control, sap:semantics, sap:key, sap:updatable-path, and entity-set-level sap:creatable, sap:updatable, sap:deletable, sap:pageable, sap:addressable, sap:requires-filter, sap:change-tracking).
The parser extracts:
- Entity types with their properties (name, type, nullable, length constraints)
- Entity sets (the exposed data sources)
- Navigation properties (relationships between entities)
- Associations (cardinality and relationship definitions)
- SAP-specific annotations for UI rendering hints
Returns a Metadata struct containing the parsed schema information, or an error if XML parsing fails.
Example:
resp, _ := http.Get("https://odata.service/$metadata")
metadata, err := ParseEDMX(resp.Body)
for _, et := range metadata.EntityTypes {
fmt.Println("Entity:", et.Name, "Key:", et.Key)
}
type NavigationBinding ¶ added in v0.2.31
NavigationBinding maps a navigation property path to its target entity set.
type NavigationProperty ¶
type NavigationProperty struct {
Name string
FromEntityType string
ToEntityType string
FromMultiplicity string
ToMultiplicity string
Partner string
}
NavigationProperty represents a navigation property in an OData entity type. Navigation properties define relationships to other entity types.
type NoOpCache ¶
type NoOpCache struct{}
NoOpCache is a no-op cache implementation that doesn't cache anything.
NoOpCache is useful for development, testing, or when caching is not desired. It always returns cache misses and ignores Set/Clear operations.
type ODataError ¶
type ODataError struct {
Code string `json:"code"`
Message string `json:"message"`
Target string `json:"target,omitempty"`
Details []ODataErrorDetail `json:"details,omitempty"`
InnerError map[string]interface{} `json:"innererror,omitempty"` // SAP extensions
}
ODataError represents an error returned by an OData service.
ODataError encapsulates structured error information from OData responses, including error codes, messages, affected targets, and optional details. It also includes support for SAP-specific inner error extensions.
OData services return errors in a standardized JSON format, which traverse parses and wraps in this type for easier error handling and inspection.
Example:
err := client.From("InvalidSet").First(ctx)
if odErr, ok := IsODataError(err); ok {
fmt.Println("OData Error:", odErr.Code, odErr.Message)
for _, detail := range odErr.Details {
fmt.Println("Detail:", detail.Code, detail.Message)
}
}
func IsODataError ¶
func IsODataError(err error) (*ODataError, bool)
IsODataError extracts an *ODataError from an error value.
IsODataError uses errors.As to unwrap and retrieve the *ODataError from a potentially wrapped error. Returns the *ODataError and true if found, otherwise returns nil and false.
Example:
if odErr, ok := IsODataError(err); ok {
fmt.Println("Code:", odErr.Code)
}
func (*ODataError) Error ¶
func (e *ODataError) Error() string
Error implements the error interface.
Error formats the ODataError into a human-readable string for logging and display. It includes the code (if available), message, and traverse prefix.
type ODataErrorDetail ¶
type ODataErrorDetail struct {
Code string `json:"code"`
Message string `json:"message"`
Target string `json:"target,omitempty"`
}
ODataErrorDetail represents a detail in an OData error response.
ODataErrorDetail provides granular error information for specific fields or sub-operations that failed within a larger request.
type ODataVersion ¶
type ODataVersion int
ODataVersion represents the OData protocol version.
ODataVersion is used to distinguish between different OData protocol versions supported by different SAP systems. Version detection can happen automatically during query execution via HTTP headers.
const ( ODataV2 ODataVersion = 2 // SAP NetWeaver Gateway, OData v2 (legacy) ODataV4 ODataVersion = 4 // SAP S/4HANA, OData v4 (standard) )
func AutoDetectODataVersion ¶
func AutoDetectODataVersion(headers map[string][]string) ODataVersion
AutoDetectODataVersion attempts to detect the OData version from the response. Checks the "OData-Version" header first, then falls back to structural detection. AutoDetectODataVersion detects the OData version from HTTP response headers.
AutoDetectODataVersion examines the "Odata-Version" and "Dataserviceversion" headers to determine whether the response follows OData v2 or v4 conventions.
Returns ODataV4 as default if the version cannot be determined from headers.
Supported versions:
- OData v4: "Odata-Version: 4.0"
- OData v2: "Odata-Version: 2.0" or "Dataserviceversion: 2.0"
type Option ¶
type Option func(*clientConfig) error
Option is a functional option for configuring a Client.
Option implements the functional options pattern for flexible client construction, allowing callers to customize client behavior without modifying the New signature.
Example:
client, _ := traverse.New(
"https://odata.service/",
traverse.WithODataVersion(traverse.ODataV4),
traverse.WithPageSize(1000),
traverse.WithBasicAuth("user", "pass"),
)
func WithAPIKey ¶
WithAPIKey sets API key authentication using a custom header.
WithAPIKey adds a custom header with the provided key/value to all requests. This is useful for services that require API keys in custom headers (e.g., X-API-Key, X-Custom-Auth-Token).
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://api.service/odata/"),
traverse.WithAPIKey("X-API-Key", "sk-abcd1234..."),
)
func WithAfterExecute ¶
func WithAfterExecute(hook func(*QueryBuilder) error) Option
WithAfterExecute adds a hook function that is called after executing each query.
WithAfterExecute registers a callback that fires immediately after each OData query completes. Multiple hooks can be added and are called in registration order. The hook receives the QueryBuilder and can access query results, state, or perform actions after execution.
Useful for:
- Collecting metrics or statistics
- Logging or tracing
- Implementing response validation
- Cleanup operations
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithAfterExecute(func(qb *traverse.QueryBuilder) error {
metrics.RecordQuery(qb.EntitySet())
return nil
}),
)
func WithBaseURL ¶
WithBaseURL sets the base URL of the OData service.
WithBaseURL is required; New() will return an error if no base URL is provided. The URL should be the root endpoint of the OData service (e.g., "https://sap.example.com/sap/opu/odata/sap/MY_SRV").
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://sap.example.com/sap/opu/odata/sap/Products"),
)
func WithBasicAuth ¶
WithBasicAuth sets HTTP Basic Authentication credentials.
WithBasicAuth adds a Basic Authorization header to all requests using the provided username and password. Credentials are base64-encoded.
Note: Basic auth should only be used over HTTPS to avoid credential exposure.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://sap.example.com/..."),
traverse.WithBasicAuth("user@example.com", "password123"),
)
func WithBearerToken ¶
WithBearerToken sets a Bearer token for OAuth2/token-based authentication.
WithBearerToken adds a Bearer Authorization header to all requests using the provided token. Tokens are typically obtained from an OAuth2 token endpoint.
Example:
token := getOAuth2Token() // From OAuth2 provider
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.service/"),
traverse.WithBearerToken(token),
)
func WithBeforeQuery ¶
func WithBeforeQuery(hook func(*QueryBuilder) error) Option
WithBeforeQuery adds a hook function that is called before executing each query.
WithBeforeQuery registers a callback that fires immediately before each OData query is executed. Multiple hooks can be added and are called in registration order. The hook receives the QueryBuilder and can modify it, perform validation, or apply common filters/options.
Return an error from the hook to abort query execution.
Useful for:
- Adding common filters or parameters
- Logging or tracing
- Implementing access control
- Modifying query behavior dynamically
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithBeforeQuery(func(qb *traverse.QueryBuilder) error {
qb.Logger(log.Printf) // Log all queries
return nil
}),
)
func WithCapabilitiesValidation ¶ added in v0.2.21
func WithCapabilitiesValidation(registry *CapabilitiesRegistry) Option
WithCapabilitiesValidation enables capability checking on the client.
func WithCircuitBreaker ¶ added in v0.2.0
func WithCircuitBreaker(cfg *relay.CircuitBreakerConfig) Option
WithCircuitBreaker enables the circuit-breaker pattern for all requests.
The circuit breaker opens after relay.CircuitBreakerConfig.MaxFailures consecutive failures, preventing further requests until the reset timeout elapses. This protects downstream OData services from cascading failure.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithCircuitBreaker(&relay.CircuitBreakerConfig{
MaxFailures: 5,
ResetTimeout: 30 * time.Second,
HalfOpenRequests: 2,
OnStateChange: func(from, to relay.CircuitBreakerState) {
log.Printf("circuit breaker: %s → %s", from, to)
},
}),
)
func WithConnectTimeout ¶ added in v0.2.0
WithConnectTimeout sets the TCP/TLS connection timeout independently of the overall transfer timeout set by WithTimeout.
Use WithTimeout to limit the total time for a request (including reading the response body). Use WithConnectTimeout to limit only the time spent establishing the connection and reading response headers.
The default is 30 seconds. Setting 0 disables the connection timeout.
Example:
// 5-second connection deadline, no overall transfer limit traverse.WithConnectTimeout(5 * time.Second)
func WithCookieJar ¶ added in v0.2.0
WithCookieJar sets a custom net/http.CookieJar for session-based authentication.
Many OData services (particularly SAP) rely on session cookies after an initial CSRF token handshake. Providing a cookie jar allows traverse to maintain and reuse those session cookies across requests automatically.
Example - use the standard library cookie jar:
jar, _ := cookiejar.New(nil) traverse.WithCookieJar(jar)
func WithFormat ¶
func WithFormat(f ResponseFormat) Option
WithFormat sets the OData response format (FormatJSON or FormatAtom).
WithFormat controls the $format parameter sent to the OData service. Defaults to FormatJSON if not specified. Most modern OData services prefer JSON format.
- FormatJSON: Returns responses as JSON (recommended for performance) - FormatAtom: Returns responses as Atom+XML (legacy format)
func WithHTTPOption ¶ added in v0.2.26
WithHTTPOption passes a raw relay.Option through to the underlying relay client.
Use this to inject low-level relay transport options that do not have a dedicated traverse wrapper - for example, audit trail or custom transport middleware provided by a third-party extension.
Example:
import "github.com/jhonsferg/traverse/ext/audit"
logger := audit.AuditLoggerFunc(func(ctx context.Context, e audit.AuditEntry) {
log.Printf("[AUDIT] %s %s %d", e.Operation, e.EntitySet, e.StatusCode)
})
client, _ := traverse.New(url,
traverse.WithHTTPOption(audit.WithAuditTrail(logger)),
)
func WithHeader ¶
WithHeader adds a custom HTTP header to all requests made by this Client.
WithHeader can be called multiple times to add multiple custom headers. Useful for setting headers like User-Agent, Accept-Language, or custom tracking IDs.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithHeader("User-Agent", "MyApp/1.0"),
traverse.WithHeader("X-Tracking-ID", "12345"),
)
func WithLogger ¶
WithLogger sets a relay.Logger for diagnostic output.
WithLogger enables logging of HTTP requests/responses and other diagnostic information. If not provided, logging is disabled. Useful for debugging authentication issues or tracing OData query execution.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithLogger(customLogger),
)
func WithMaxPages ¶ added in v0.13.26
WithMaxPages sets the maximum number of pages to follow when paginating via nextLink. This guards against servers that always return a nextLink (whether by misconfiguration or malice), which would otherwise cause an infinite loop.
The default is [defaultMaxPages] (10,000). Omit or pass a positive value. A value of 1 fetches only the first page regardless of nextLink.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithMaxPages(100), // cap at 100 pages (e.g. 500k records at pageSize=5000)
)
func WithMaxRedirects ¶ added in v0.2.0
WithMaxRedirects sets the maximum number of HTTP redirects to follow.
The default is 10. Set to 0 to disable redirect following entirely.
Example:
traverse.WithMaxRedirects(0) // never follow redirects
func WithMetadataCache ¶
func WithMetadataCache(cache CacheStore) Option
WithMetadataCache sets a custom metadata cache implementation.
WithMetadataCache allows you to provide a CacheStore implementation for caching OData service metadata. By default, metadata is cached after the first fetch using NewMemoryCache. This option allows distributed caching or custom expiration policies.
Example implementations: Redis, memcached, file-based, or SQL database backends.
Example with memory cache:
cache := traverse.NewMemoryCache()
client, err := traverse.New(
traverse.WithBaseURL("https://odata.service/"),
traverse.WithMetadataCache(cache),
)
func WithODataErrors ¶ added in v0.2.0
func WithODataErrors() Option
WithODataErrors configures the client to decode OData error responses into structured ODataError values.
When enabled, any 4xx or 5xx response whose body follows the OData v2 or v4 error format is decoded into an ODataError instead of a generic HTTP error. Use IsODataError to inspect the result.
This option is recommended for all production clients as it surfaces the OData error code and message directly from the service.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithODataErrors(),
)
_, err := client.From("InvalidEntity").First(ctx)
if odErr, ok := traverse.IsODataError(err); ok {
fmt.Println("OData error code:", odErr.Code)
}
func WithODataVersion ¶
func WithODataVersion(v ODataVersion) Option
WithODataVersion sets the OData protocol version (ODataV2 or ODataV4).
WithODataVersion controls how requests are constructed and responses are parsed. Defaults to ODataV4 if not specified.
- ODataV2: Used by classic SAP ABAP Gateway; responses wrapped in "d" object - ODataV4: Modern protocol used by S/4HANA and most new OData services
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithODataVersion(traverse.ODataV2), // For legacy SAP systems
)
func WithPageSize ¶
WithPageSize sets the number of records fetched per HTTP request during pagination.
WithPageSize controls the $top parameter sent to the server. Defaults to 1000 if not specified. Larger values reduce network round-trips but consume more memory per request. Smaller values reduce per-request memory but increase latency due to more requests.
The server may reject the value or return fewer records; OData clients should handle the actual page size in responses.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithPageSize(5000), // Fetch 5000 records per request
)
func WithProxy ¶ added in v0.2.0
WithProxy sets an HTTP or HTTPS proxy for all outgoing requests.
proxyURL must be a fully qualified URL, e.g. "http://proxy.corp.com:3128" or "https://user:pass@proxy.corp.com:8080".
Example:
traverse.WithProxy("http://proxy.internal.corp:3128")
func WithRateLimit ¶ added in v0.2.0
WithRateLimit constrains the number of outgoing HTTP requests per second.
rps is the sustained request rate (e.g. 50.0 for 50 req/s). burst is the maximum number of requests allowed in a single instant above rps.
This is particularly useful when querying SAP OData services that enforce throttling via HTTP 429 responses.
Example:
// Allow 100 req/s with a burst of up to 110 traverse.WithRateLimit(100, 110)
func WithRelayClient ¶
WithRelayClient injects a pre-configured relay.Client for custom HTTP configuration.
WithRelayClient allows advanced users to configure HTTP behavior (connection pooling, custom transport, proxies, etc.) before passing the client to Traverse. If not provided, Traverse creates a default relay.Client.
When a relay client is provided, WithBaseURL on traverse is optional: the base URL is automatically inherited from the relay client's own relay.WithBaseURL option. This means you do NOT need to repeat the base URL in both clients.
Any traverse-level options that map to relay behaviour (e.g. WithHeader, WithTimeout, [WithBeforeRequest]) are applied on top of the provided relay client via relay.Client.With, so they are not silently dropped.
This is useful for:
- Configuring connection pooling for large-scale operations
- Setting up proxy or TLS configuration
- Using a relay client shared across multiple services
Example - base URL inherited automatically:
httpClient := relay.New(
relay.WithTimeout(60 * time.Second),
relay.WithBaseURL("https://sap.example.com/sap/opu/odata/sap"),
)
// No traverse.WithBaseURL needed - inherited from httpClient
client, _ := traverse.New(
traverse.WithRelayClient(httpClient),
traverse.WithODataVersion(traverse.ODataV2),
)
func WithRequestHook ¶ added in v0.2.0
WithRequestHook registers a low-level HTTP hook called before every request.
The hook receives the underlying relay.Request and may modify headers, inject tracing metadata, or cancel the context. It is called after OData-level hooks (WithBeforeQuery) but before the request is sent over the wire.
To register OData-level hooks (called before query building), use WithBeforeQuery instead.
Example - add a request-ID header to every request:
traverse.WithRequestHook(func(ctx context.Context, r *relay.Request) error {
r.WithHeader("X-Request-ID", uuid.New().String())
return nil
})
func WithResponseCache ¶ added in v0.2.25
func WithResponseCache(cache ResponseCache) Option
WithResponseCache sets a custom HTTP-level response cache for entity set queries.
WithResponseCache attaches a ResponseCache to the client. When a QueryBuilder uses QueryBuilder.WithCache, query responses are stored in this cache and served on subsequent identical requests until the TTL expires.
The default is no response cache. Provide NewInMemoryResponseCache for a simple in-process cache, or implement ResponseCache for a distributed backend.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithResponseCache(traverse.NewInMemoryResponseCache()),
)
// Cache Products queries for 5 minutes
products, _ := client.From("Products").
WithCache(5 * time.Minute).
Collect(ctx)
func WithResponseHook ¶ added in v0.2.0
WithResponseHook registers a low-level HTTP hook called after every response.
The hook receives the underlying relay.Response and may inspect headers, record metrics, or return an error to abort further processing. It is called before OData-level hooks (WithAfterExecute).
Example - record response latency to a metrics system:
traverse.WithResponseHook(func(ctx context.Context, r *relay.Response) error {
metrics.Record("odata.latency", r.Timing.Total)
return nil
})
func WithRetry ¶ added in v0.2.0
func WithRetry(rc *relay.RetryConfig) Option
WithRetry configures automatic retry behaviour for transient failures.
Relay will retry requests that fail with a network error or a 429/5xx status code. The retry strategy is exponential back-off with jitter.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithRetry(&relay.RetryConfig{
MaxAttempts: 4,
InitialInterval: 200 * time.Millisecond,
MaxInterval: 10 * time.Second,
Multiplier: 2.0,
RandomFactor: 0.3,
}),
)
func WithSchemaVersion ¶ added in v0.6.0
WithSchemaVersion sets the OData-SchemaVersion request header on all requests made by this Client.
OData 4.01 (spec section 8.2.10) introduced this header to allow clients to request a specific version of the service's metadata schema. Services that evolve their schema over time can use this to serve different schema versions to different clients simultaneously.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://api.example.com/odata/"),
traverse.WithSchemaVersion("2.0"),
)
func WithSigner ¶ added in v0.2.0
func WithSigner(s relay.RequestSigner) Option
WithSigner sets a relay.RequestSigner that signs every outgoing HTTP request.
This is useful for OData services that require request signing such as AWS API Gateway (SigV4) or custom HMAC schemes.
Example - sign requests with a custom HMAC scheme:
traverse.WithSigner(relay.RequestSignerFunc(func(r *http.Request) error {
mac := hmac.New(sha256.New, secretKey)
mac.Write([]byte(r.Method + r.URL.Path))
r.Header.Set("X-Signature", hex.EncodeToString(mac.Sum(nil)))
return nil
}))
func WithTLSConfig ¶ added in v0.2.0
WithTLSConfig sets a custom crypto/tls.Config for the HTTP transport.
Use this to configure mutual TLS, disable certificate verification for on-premise services with self-signed certificates, or pin specific CAs.
Example - disable TLS verification for a local development OData service:
traverse.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) // #nosec G402
func WithTimeout ¶
WithTimeout sets the request timeout for all HTTP requests.
WithTimeout controls the timeout for individual OData requests. Defaults to 30 seconds if not specified. Applies to query execution, metadata fetching, and all CRUD operations.
For operations that process millions of records with streaming, consider a longer timeout.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("..."),
traverse.WithTimeout(60 * time.Second),
)
type Page ¶
type Page struct {
// Value is the slice of entities in this page
Value []map[string]interface{}
// RawValue stores raw JSON for direct unmarshaling (optimization)
RawValue []json.RawMessage
// NextLink is the URL to fetch the next page (@odata.nextLink in v4, __next in v2)
NextLink string
// DeltaLink is the URL for delta queries for incremental sync (@odata.deltaLink)
DeltaLink string
// Count is the total count of matching records if $count=true was used
Count *int64
// Context is the @odata.context value (OData v4 only)
Context string
}
Page represents a single page of OData results with pagination metadata.
Page is returned by QueryBuilder.Page and contains the results for one page of a query, along with metadata for fetching subsequent pages or performing incremental updates.
Fields:
- Value: slice of entities for this page
- RawValue: slice of raw JSON for each entity (for optimization purposes)
- NextLink: URL to fetch the next page (if more records exist)
- DeltaLink: URL for delta queries to fetch only changed records
- Count: total count of all matching records (if WithCount was used)
- Context: OData context URI (v4 only)
func QueryParallel ¶
func QueryParallel(ctx context.Context, queries ...*QueryBuilder) ([]Page, error)
QueryParallel executes multiple queries concurrently and returns results in order.
QueryParallel takes multiple QueryBuilder queries and executes them in parallel using goroutines. Results are returned in the same order as the input queries, making it easy to correlate results.
Each query is executed independently. If any query fails, QueryParallel returns the first error encountered; partial results are discarded. For independent error handling per query, execute queries separately with goroutines and collect errors manually.
Example:
q1 := client.For("Customers").Filter("Country eq 'USA'")
q2 := client.For("Orders").Filter("Status eq 'Completed'")
results, err := QueryParallel(ctx, q1, q2)
// results[0] is from q1, results[1] is from q2
type Paginator ¶ added in v0.2.5
type Paginator[T any] struct { // contains filtered or unexported fields }
Paginator provides a typed, iterator-style interface for paging through an OData entity set one page at a time. It automatically follows @odata.nextLink (OData v4) or __next (OData v2) links between pages.
Obtain a Paginator via NewPaginator or [NewPaginatorJSON].
p := traverse.NewPaginator[Product](client.From("Products").Top(50))
for p.HasMorePages() {
items, err := p.NextPage(ctx)
if err != nil { break }
for _, item := range items {
process(item)
}
}
A Paginator is NOT safe for concurrent use; use separate Paginators in separate goroutines.
func NewPaginator ¶ added in v0.2.5
func NewPaginator[T any](query *QueryBuilder) *Paginator[T]
NewPaginator creates a typed Paginator that deserialises each entity via json.Unmarshal into T. The query is executed lazily - no network call is made until the first call to Paginator.NextPage.
type Product struct {
ID int `json:"ProductID"`
Name string `json:"Name"`
}
p := traverse.NewPaginator[Product](client.From("Products").Top(100))
func NewPaginatorWithDecoder ¶ added in v0.2.5
func NewPaginatorWithDecoder[T any](query *QueryBuilder, decode func(json.RawMessage) (T, error)) *Paginator[T]
NewPaginatorWithDecoder creates a Paginator that uses a custom decode function to convert each raw JSON entity into T. Use this when you need non-standard unmarshalling (e.g. case-insensitive keys, custom types).
p := traverse.NewPaginatorWithDecoder[Product](
client.From("Products"),
func(raw json.RawMessage) (Product, error) {
var p Product
return p, json.Unmarshal(raw, &p)
},
)
func (*Paginator[T]) HasMorePages ¶ added in v0.2.5
HasMorePages reports whether there are more pages to fetch. It returns true before the first call to Paginator.NextPage and after any page that included a nextLink. It returns false once the final page has been consumed.
func (*Paginator[T]) NextPage ¶ added in v0.2.5
NextPage fetches the next page of results and returns a typed slice. On the first call the original query is executed; subsequent calls follow the nextLink returned by the server until no more pages are available.
After the last page is returned, Paginator.HasMorePages returns false and subsequent calls to NextPage return an empty slice with no error.
func (*Paginator[T]) Reset ¶ added in v0.2.5
func (p *Paginator[T]) Reset()
Reset resets the Paginator to the beginning of the result set, discarding any accumulated nextLink. The next call to Paginator.NextPage will re-issue the original query from the start.
func (*Paginator[T]) TotalCount ¶ added in v0.2.5
TotalCount returns the total count of matching records as reported by the server (requires $count=true on the original query). Returns 0 if the server did not return a count. Note that this value is only available after the first page has been fetched.
type Property ¶
type Property struct {
// Name is the name of the property.
Name string
// Type is the OData type (e.g., "Edm.String", "Edm.Int32", "Edm.DateTime").
Type string
// Nullable indicates whether the property can have a null value.
Nullable bool
// MaxLength is the maximum string length (for string types).
MaxLength *int
// Precision is the number of significant digits (for numeric types).
Precision *int
// Scale is the number of decimal places (for decimal types).
Scale *int
// SAP contains SAP-specific annotations for this property.
SAP SAPAnnotations
// Core contains OData Core vocabulary annotations when present in metadata.
Core *CoreVocabulary
// Validation contains OData Validation vocabulary annotations when present in metadata.
Validation *ValidationVocabulary
// Measures contains OData Measures vocabulary annotations (unit-of-measure, currency, scale).
Measures *MeasuresVocabulary
// Analytics contains OData Aggregation/Analytics vocabulary annotations (dimension, measure, aggregation).
Analytics *AnalyticsVocabulary
}
Property represents an OData property definition. Properties describe the data that can be stored in entities.
type PropertyRef ¶
type PropertyRef struct {
// Name is the name of the property.
Name string
}
PropertyRef represents a reference to a property that is part of an entity's key.
type QueryBuilder ¶
type QueryBuilder struct {
// contains filtered or unexported fields
}
QueryBuilder builds and executes OData queries with a fluent, chainable API.
QueryBuilder provides an ergonomic interface for constructing complex OData queries with support for filtering, selection, ordering, pagination, expansion, and advanced features. All methods return the receiver (*QueryBuilder) to enable method chaining for readable, fluent query construction.
Core Query Methods (chainable):
- Select(): Choose specific properties to return ($select)
- Filter() / Where(): Add row filtering ($filter)
- OrderBy() / OrderByDesc(): Sort results ($orderby)
- Top(): Limit result count ($top for pagination)
- Skip(): Skip records before returning ($skip for pagination)
- Expand(): Include related entities ($expand)
- WithCount(): Include total record count
Execution Methods (terminal operations):
- Collect(ctx): Fetch all results at once into memory
- Stream(ctx): Stream results one at a time (O(1) memory)
- Page(ctx): Fetch one page of results
- Count(ctx): Get total matching record count
- Single(ctx): Get exactly one result (error if 0 or >1)
- First(ctx): Get first result or nil
QueryBuilder is NOT thread-safe. Create separate QueryBuilders for concurrent queries. Modifications to a QueryBuilder during execution produce undefined behavior.
Performance Notes:
- Lazy URL construction: URLs are rebuilt only when needed (marked by urlDirty flag)
- Object pooling: Uses buffer and FilterBuilder pools to reduce allocations
- Streaming support: Stream() provides O(1) memory for large datasets
- Query hooks: Client-level hooks can intercept before/after query execution
Fluent API Examples:
Simple query with filtering and sorting:
results, err := client.From("Products").
Filter("Price gt 100 and Category eq 'Electronics'").
OrderBy("Price desc").
Top(50).
Select("ProductID", "Name", "Price").
Collect(ctx)
Query with type-safe filtering:
results, err := client.From("Orders").
Where("Status").Eq("Completed").
Where("Amount").Gt(1000).
OrderByDesc("CreatedDate").
Expand("Customer").
Collect(ctx)
Streaming large dataset (constant memory):
stream := client.From("LargeDataSet").
Filter("Active eq true").
Stream(ctx)
for result := range stream {
if result.Error != nil {
log.Printf("Error: %v", result.Error)
break
}
processRecord(result.Value)
}
Pagination with streaming:
stream := client.From("Products").
Top(1000).
Skip(offset).
Stream(ctx)
func (*QueryBuilder) Apply ¶
func (q *QueryBuilder) Apply(expr string) *QueryBuilder
Apply applies an aggregation or data transformation using the $apply system query option.
Apply is only available in OData v4 and is used for complex data aggregation. It follows the OData Data Aggregation Extension specification. Apply is chainable and returns q for method chaining.
Example:
query.Apply("groupby((Region),aggregate(Sales with sum as Total))") // OData v4 only
func (*QueryBuilder) AsType ¶ added in v0.6.0
func (q *QueryBuilder) AsType(typeName string) *QueryBuilder
AsType appends a type cast segment to the entity set path, restricting results to entities of the specified derived type.
AsType implements OData v4.0 spec section 4.3 (type cast segments) and is used to query polymorphic entity sets returning only instances of a derived type.
The typeName should be the fully-qualified OData type name, e.g. "Model.Manager". The resulting URL path becomes /EntitySet/Namespace.DerivedType.
Example:
// GET /Employees/Model.Manager?$select=Name,Budget
managers, err := client.From("Employees").AsType("Model.Manager").
Select("Name", "Budget").Collect(ctx)
func (*QueryBuilder) BoundAction ¶ added in v0.2.11
func (q *QueryBuilder) BoundAction(name string) *ActionBuilder
BoundAction creates a builder for an OData action bound to this entity set.
The action URL is constructed as: /<entitySet>/<name> Parameters are sent as a JSON body. This is typically used for namespace-qualified actions bound to a collection or a specific entity, for example:
// Bound to collection: POST /Products/Namespace.BulkDiscount {"percent": 10}
var result ActionResult
err := client.From("Products").
BoundAction("Namespace.BulkDiscount").
Param("percent", 10).
Invoke(ctx, &result)
func (*QueryBuilder) BoundFunction ¶ added in v0.2.11
func (q *QueryBuilder) BoundFunction(name string) *FunctionBuilder
BoundFunction creates a builder for an OData function bound to this entity set.
The function URL is constructed as: /<entitySet>/<name>(params...) This is typically used for namespace-qualified functions bound to a collection or a specific entity, for example:
// Bound to collection: /Products/Namespace.GetDiscounted(threshold=10)
var result MyResult
err := client.From("Products").
BoundFunction("Namespace.GetDiscounted").
Param("threshold", 10).
Invoke(ctx, &result)
func (*QueryBuilder) BulkDelete ¶ added in v0.2.27
func (q *QueryBuilder) BulkDelete(ctx context.Context) error
BulkDelete deletes all entities in the entity set that match the current filter.
BulkDelete sends a DELETE request against the collection URL with any $filter already set on the QueryBuilder (OData v4.01 collection DELETE pattern).
This is useful for removing many records at once, such as purging old log entries, without fetching them first.
Example:
err := client.From("TempLogs").Filter("CreatedAt lt 2024-01-01").BulkDelete(ctx)
func (*QueryBuilder) BulkUpdate ¶ added in v0.6.0
func (q *QueryBuilder) BulkUpdate(ctx context.Context, data interface{}) error
BulkUpdate updates all entities in the entity set that match the current filter by applying the provided partial update data.
BulkUpdate sends a PATCH request against the collection URL with any $filter already set on the QueryBuilder (OData v4.01 section 11.4.13 - collection PATCH).
The data parameter can be any JSON-serializable value: a map, a typed struct, or a pointer to a struct. Only the fields present in data are updated on each matching entity; other fields are left unchanged.
BulkUpdate returns ErrEntityNotFound when no entities match the filter, and ErrConcurrencyConflict on a 409 or 412 response.
Example:
// Archive all discontinued products
err := client.From("Products").
Filter("Category eq 'Discontinued'").
BulkUpdate(ctx, map[string]any{"Status": "Archived"})
// Typed struct patch
err := client.From("Orders").
Filter("Status eq 'Draft'").
BulkUpdate(ctx, OrderPatch{Status: "Confirmed"})
func (*QueryBuilder) Collect ¶
func (q *QueryBuilder) Collect(ctx context.Context) ([]map[string]interface{}, error)
Collect materializes all matching records into a single slice.
Collect streams all pages and accumulates all matching records into a single in-memory slice. It uses the Stream method internally for multi-page fetching and pre-allocates memory based on query parameters to reduce allocations.
Memory Usage: Collect loads the ENTIRE result set into memory. For large result sets, memory usage can be problematic:
- 1M records × 1 KB each = ~1 GB
- 10M records × 1 KB each = ~10 GB
Use Stream() for large datasets to avoid memory exhaustion.
When to Use Collect:
- Dataset is known to be small (<10K records)
- Top() limits the result set
- Where() filters results to manageable size
- API endpoint or demo code
When NOT to Use Collect:
- Unbounded queries on large tables
- Production code handling user input
- Processing 100K+ records
Capacity Estimation: If Top() is set, Collect pre-allocates that capacity plus any Skip() offset. Otherwise defaults to 1000 record estimate. This reduces allocation overhead.
Error Handling: If any page fails to fetch, the partial results accumulated so far are lost and the error is returned.
Returns:
- []map[string]interface{}: All matching records
- error: If any page fetch fails
Examples:
OK: Limited result set:
// Collect with explicit limit
products, _ := client.From("Products").
Where("Category").Eq("Electronics").
Top(500).
Collect(ctx)
DANGER: Unbounded query:
// WARNING: Could exhaust memory on large table!
all, _ := client.From("Products").Collect(ctx)
Safe filtered query:
// Filter reduces result set to manageable size
orders, _ := client.From("Orders").
Where("OrderDate").Gt(startDate).
Where("OrderDate").Lt(endDate).
Collect(ctx)
Convert streaming to slice:
results := make([]map[string]interface{}, 0, 100)
for result := range client.From("Orders").Stream(ctx) {
if result.Err != nil {
return result.Err
}
results = append(results, result.Value)
}
func (*QueryBuilder) Compute ¶ added in v0.2.27
func (q *QueryBuilder) Compute(expressions ...string) *QueryBuilder
Compute adds a $compute expression to the OData query.
The $compute system query option (OData v4.01) allows defining computed properties that can be referenced in $select, $filter, and $orderby. Multiple calls append expressions separated by commas.
Compute is chainable and returns q for method chaining.
Example:
query.Compute("Price mul Quantity as Total")
query.Compute("Price mul Quantity as Total", "Tax div 100 as TaxRate")
func (*QueryBuilder) Count ¶
func (q *QueryBuilder) Count(ctx context.Context) (int64, error)
Count returns the total count of matching records without transferring entity data.
Count executes a /$count query which returns only the total number of matching entities without downloading the actual entity data. This is the most efficient way to determine result set size, as it minimizes network traffic and processing.
Filters Applied: Count applies any filters set via Filter() or Where() methods. Select, OrderBy, Top, and Skip are ignored (meaningless for counts).
Use Cases:
- Check if results exist: Count() > 0
- Determine pagination requirements
- Validate constraints before operations
- Performance optimization: 364x faster than Collect for large datasets
Returns:
- int64: Total number of matching records
- error: If the query fails or server returns error
Performance: Ultra-fast operation returning just an integer. Typical time: 1-2ms even for millions of records. Ideal for existence checks and result set sizing.
Examples:
Check if any records match:
count, _ := client.From("Orders").
Where("Status").Eq("Pending").
Count(ctx)
if count > 0 {
// Process pending orders
}
Pagination decision:
total, _ := client.From("Products").Count(ctx)
pageCount := (total + 99) / 100 // Ceiling division for 100-item pages
Filtered count:
activeCount, _ := client.From("Employees").
Where("Department").Eq("Sales").
Where("Active").Eq(true).
Count(ctx)
func (*QueryBuilder) CreateDeep ¶ added in v0.2.8
CreateDeep performs an OData 4.01 deep insert of an entity with related entities.
CreateDeep POSTs the entity to the entity set, setting the required headers for deep insert: Content-Type is set to "application/json;odata.metadata=minimal" and Prefer is set to "return=representation". The caller constructs nested structs or maps with related entities embedded.
Returns the raw relay.Response so the caller can decode nested result structures.
Example:
type OrderLine struct {
ProductID int `json:"ProductID"`
Quantity int `json:"Quantity"`
}
type Order struct {
CustomerID string `json:"CustomerID"`
Lines []OrderLine `json:"Lines"`
}
resp, err := client.From("Orders").CreateDeep(ctx, Order{
CustomerID: "CUST1",
Lines: []OrderLine{{ProductID: 1, Quantity: 5}},
})
func (*QueryBuilder) CreateDeepWithPrefer ¶ added in v0.2.8
func (q *QueryBuilder) CreateDeepWithPrefer(ctx context.Context, entity any, prefer string) (*relay.Response, error)
CreateDeepWithPrefer performs a deep insert with a custom Prefer header value.
Use this when you need full control over the Prefer header, for example to combine "return=representation" with "odata.continue-on-error", or to use a custom preference. Pass an empty string to omit the Prefer header.
opts := traverse.DeepInsertOptions{
ReturnRepresentation: true,
ContinueOnError: true,
}
resp, err := client.From("Orders").
CreateDeepWithPrefer(ctx, order, opts.PreferHeader())
func (*QueryBuilder) DeleteLink ¶ added in v0.5.0
DeleteLink removes a specific navigation link between two entities.
DeleteLink sends a DELETE request to:
/{EntitySet}({key})/{navProperty}/$ref?$id={relatedKey}
This removes the relationship between the source entity and the target entity without deleting either entity from the data store.
Parameters:
- ctx: Request context
- navProperty: Name of the navigation property on the source entity
- relatedKey: Primary key of the target entity to unlink
Example:
// Remove item 5 from order 1's Items collection
err := client.From("Orders").Key(1).DeleteLink(ctx, "Items", 5)
func (*QueryBuilder) DeleteLinks ¶ added in v0.5.0
func (q *QueryBuilder) DeleteLinks(ctx context.Context, navProperty string) error
DeleteLinks removes all navigation links for a given navigation property.
DeleteLinks sends a DELETE request to:
/{EntitySet}({key})/{navProperty}/$ref
This removes all relationships for the navigation property without deleting any of the related entities.
Parameters:
- ctx: Request context
- navProperty: Name of the navigation property whose links should be removed
Example:
// Remove all items from order 1's Items collection
err := client.From("Orders").Key(1).DeleteLinks(ctx, "Items")
func (*QueryBuilder) Expand ¶
func (q *QueryBuilder) Expand(navProp string, opts ...ExpandOption) *QueryBuilder
Expand adds a $expand clause for including related entities in the response.
Expand specifies navigation properties (relationships to other entities) to include in the OData response using the $expand system query option. Related entities are returned nested within the parent entity rather than requiring separate requests.
Navigation Properties: A navigation property represents a relationship to another entity type. For example, a Customer entity might have navigation properties to Orders, Addresses, Payments, etc.
Multiple Expand Calls: Calling Expand() multiple times accumulates properties. Expand("Orders") then Expand("Addresses") includes both Orders and Addresses relationships.
Nested Expansion Options: Expand supports optional ExpandOption parameters for filtering and selecting specific properties from related entities:
- WithExpandSelect(): Choose specific properties from related entities
- WithExpandFilter(): Filter related entities
- WithExpandOrderBy(): Sort related entities
- WithExpandTop(): Limit related entity count
Performance Considerations:
- Related entities increase response size; use Select() to limit properties
- Nested Expand can exponentially increase response size; be careful
- Use carefully with large entity collections (100k+ records)
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes
Examples:
Simple expand (include all related entities):
query.Expand("Orders")
// OData: $expand=Orders
Multiple navigation properties:
query.Expand("Orders").Expand("Addresses")
// OData: $expand=Orders,Addresses
Expand with nested options:
query.Expand("Orders",
WithExpandSelect("OrderID", "Amount"),
WithExpandFilter("Status eq 'Completed'"))
// OData: $expand=Orders($select=OrderID,Amount;$filter=Status eq 'Completed')
Complex query with expand and filtering:
results, _ := client.From("Customers").
Where("Country").Eq("USA").
Select("CustomerID", "Name").
Expand("Orders", WithExpandSelect("OrderID", "Amount")).
Collect(ctx)
func (*QueryBuilder) ExpandNested ¶ added in v0.2.6
func (q *QueryBuilder) ExpandNested(property string) *ExpandBuilder
ExpandNested begins a nested expand configuration for the given navigation property. Call the builder methods to configure sub-query options, then call Done() to return to the parent QueryBuilder.
query := client.From("Orders").
ExpandNested("Items").
Select("ID", "Qty").
Filter("Qty gt 0").
Top(10).
Done()
func (*QueryBuilder) Filter ¶
func (q *QueryBuilder) Filter(expr string) *QueryBuilder
Filter adds a raw OData filter expression using the $filter system query option.
Filter specifies a raw OData filter expression for row filtering. The filter expression should be a valid OData v2 or v4 filter expression following OData specification syntax.
Filter vs Where:
- Filter(): For raw OData expressions; caller responsible for syntax
- Where(): Type-safe builder interface; prevents syntax errors
Multiple Filter Calls: If Filter is called multiple times, the last expression replaces previous ones. Use the Where() method for type-safe, chainable filtering instead.
Valid Operators:
- Comparison: eq (equals), ne (not equals), gt, ge, lt, le
- Logical: and, or, not
- String: startswith, endswith, contains, substring, length, toupper, tolower
- Math: add, sub, mul, div, mod, round, floor, ceiling
- Date: year, month, day, hour, minute, second, now
- Membership: in, has (enum flags)
Escaping: String literals in expressions must be single-quoted with internal quotes doubled:
'John''s Company' for the value John's Company
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes (but replaces previous filter)
Examples:
Simple comparison:
query.Filter("Age gt 30")
// OData: $filter=Age gt 30
Complex expression with AND/OR:
query.Filter("Region eq 'North' and Status ne 'Inactive'")
// OData: $filter=Region eq 'North' and Status ne 'Inactive'
String functions:
query.Filter("startswith(CompanyName, 'Alpine')")
// OData: $filter=startswith(CompanyName, 'Alpine')
Date/time filtering:
query.Filter("year(OrderDate) eq 2024")
// OData: $filter=year(OrderDate) eq 2024
For safer filtering without syntax errors, use Where() instead:
query.Where("Age").Gt(30).Where("Status").Ne("Inactive")
func (*QueryBuilder) FilterBy ¶ added in v0.2.14
func (q *QueryBuilder) FilterBy(expr *FilterExpr) *QueryBuilder
FilterBy sets the $filter using a type-safe FilterExpr.
FilterBy accepts a *FilterExpr (built via F() and logical combinators And/Or/Not) and sets it as the query filter. This method provides a fluent alternative to Filter() with full type safety and composability.
Type Safety: FilterBy prevents string concatenation errors by using the FilterExpr builder API. Filters are built programmatically using F(), And(), Or(), and Not() functions.
Validation: If the QueryBuilder has a schema set, the filter expression will be validated.
Example:
expr := And(F("Age").Ge(18), F("Active").Eq(true))
query.FilterBy(expr)
// OData: $filter=(Age ge 18) and (Active eq true)
func (*QueryBuilder) FilterLambda ¶ added in v0.2.8
func (q *QueryBuilder) FilterLambda(expression string) *QueryBuilder
FilterLambda appends a raw lambda expression string to the query filter. Use this for complex lambda expressions that cannot be expressed through the typed LambdaAny/LambdaAll builders.
The expression is combined with any existing filter using "and".
query.FilterLambda("tags/any(t: t/name eq 'admin')")
func (*QueryBuilder) FindByCompositeKey ¶
func (q *QueryBuilder) FindByCompositeKey(ctx context.Context, keys map[string]interface{}) (map[string]interface{}, error)
FindByCompositeKey retrieves a single entity by a composite primary key.
FindByCompositeKey fetches the entity using a composite key where multiple properties form the primary key. The keys parameter should be a map of property names to their values.
For single-key entities, use QueryBuilder.FindByKey instead. FindByCompositeKey returns an error if keys is empty or if the server cannot find an entity matching the composite key.
Example:
entity, err := query.FindByCompositeKey(ctx, map[string]interface{}{
"Company": "ABC",
"Material": "XYZ",
})
func (*QueryBuilder) FindByKey ¶
func (q *QueryBuilder) FindByKey(ctx context.Context, key interface{}) (map[string]interface{}, error)
FindByKey retrieves a single entity by its primary key.
FindByKey fetches the entity directly using the OData GET endpoint with the key in the URL path: GET /<EntitySet>(<key>)
The key parameter should be the primary key value. For composite keys, use QueryBuilder.FindByCompositeKey instead. FindByKey returns ErrEntityNotFound if no entity matches the key.
Example:
entity, err := query.FindByKey(ctx, 42) // Single key
func (*QueryBuilder) First ¶
func (q *QueryBuilder) First(ctx context.Context) (map[string]interface{}, error)
First executes the query and returns only the first matching entity.
First automatically adds $top=1 to the query and returns the first matching entity without fetching additional results. It returns ErrEntityNotFound if no matching entities exist.
First is a convenience method for queries where only the first result matters. It's equivalent to calling Page().Value[0] but with built-in error handling.
Efficiency: First is more efficient than Collect() when only one record is needed, since it limits the OData query to return just one result.
Returns:
- map[string]interface{}: The first matching entity
- error: ErrEntityNotFound if no matches, or other error on query failure
Common Patterns:
- Get first item sorted by a key: OrderBy().First()
- Get first matching filtered item: Where().First()
- Check if any item exists: if _, err := query.First(ctx); err == nil { /* exists */ }
Examples:
Find first user by ID:
user, err := client.From("Users").
Where("ID").Eq(42).
First(ctx)
if errors.Is(err, traverse.ErrEntityNotFound) {
log.Println("User not found")
}
Get most recently created record:
latest, _ := client.From("Orders").
OrderByDesc("CreatedDate").
First(ctx)
log.Printf("Latest order: %v", latest)
Check if user exists (ignore result):
_, err := client.From("Users").
Where("Email").Eq("test@example.com").
First(ctx)
if err == nil {
log.Println("User already exists")
}
Safe pattern with filter verification:
result, err := client.From("Products").
Where("SKU").Eq("ABC123").
First(ctx)
if err != nil {
return fmt.Errorf("product lookup failed: %w", err)
}
productID := result["ID"]
func (*QueryBuilder) GeoDistance ¶ added in v0.10.0
func (q *QueryBuilder) GeoDistance(property string, point GeographyPoint, operator string, distance float64) *QueryBuilder
GeoDistance adds a geo.distance() filter to the query.
GeoDistance filters entities whose spatial property is within (or beyond) the specified distance from the given geographic point.
Example:
client.From("Restaurants").
GeoDistance("Location", traverse.GeographyPoint{Longitude: 13.408, Latitude: 52.518}, "le", 500)
// $filter=geo.distance(Location,geography'SRID=4326;POINT(13.408 52.518)') le 500
func (*QueryBuilder) GeoDistanceGeom ¶ added in v0.10.0
func (q *QueryBuilder) GeoDistanceGeom(property string, point GeometryPoint, operator string, distance float64) *QueryBuilder
GeoDistanceGeom adds a geo.distance() filter for geometry coordinates.
func (*QueryBuilder) GeoIntersects ¶ added in v0.10.0
func (q *QueryBuilder) GeoIntersects(property string, polygon GeographyPolygon) *QueryBuilder
GeoIntersects adds a geo.intersects() filter to the query.
GeoIntersects filters entities where the spatial point property lies within the given polygon.
Example:
poly := traverse.GeographyPolygon{ExteriorRing: []traverse.GeographyPoint{...}}
client.From("POI").GeoIntersects("Coordinates", poly)
// $filter=geo.intersects(Coordinates,geography'SRID=4326;POLYGON(...)')
func (*QueryBuilder) GeoIntersectsGeom ¶ added in v0.10.0
func (q *QueryBuilder) GeoIntersectsGeom(property string, polygon GeometryPolygon) *QueryBuilder
GeoIntersectsGeom adds a geo.intersects() filter for geometry coordinates.
func (*QueryBuilder) GeoLength ¶ added in v0.10.0
func (q *QueryBuilder) GeoLength(property string, operator string, threshold float64) *QueryBuilder
GeoLength adds a geo.length() filter to the query.
GeoLength filters entities where the length of the spatial LineString property satisfies the comparison.
Example:
client.From("Routes").GeoLength("Path", "le", 50000)
// $filter=geo.length(Path) le 50000
func (*QueryBuilder) IfMatch ¶ added in v0.2.8
func (q *QueryBuilder) IfMatch(etag string) *QueryBuilder
IfMatch sets the If-Match conditional request header on the query. The server will only process the request if the entity's current ETag matches. Returns HTTP 412 Precondition Failed if the ETags do not match.
Commonly used to guard updates and deletes against concurrent modifications:
page, err := client.From("Orders").
IfMatch(`W/"abc123"`).
Page(ctx)
func (*QueryBuilder) IfModifiedSince ¶ added in v0.2.8
func (q *QueryBuilder) IfModifiedSince(t time.Time) *QueryBuilder
IfModifiedSince sets the If-Modified-Since conditional request header. The server will only return data if the resource has been modified after t. Returns HTTP 304 Not Modified if unchanged.
since := time.Now().Add(-24 * time.Hour)
page, err := client.From("Orders").
IfModifiedSince(since).
Page(ctx)
func (*QueryBuilder) IfNoneMatch ¶ added in v0.2.8
func (q *QueryBuilder) IfNoneMatch(etag string) *QueryBuilder
IfNoneMatch sets the If-None-Match conditional request header on the query. The server will only return data if none of the given ETags match the current entity. Used for cache validation: the server returns 304 Not Modified if the ETag matches.
page, err := client.From("Products").
IfNoneMatch(`"xyz789"`).
Page(ctx)
func (*QueryBuilder) IfUnmodifiedSince ¶ added in v0.2.8
func (q *QueryBuilder) IfUnmodifiedSince(t time.Time) *QueryBuilder
IfUnmodifiedSince sets the If-Unmodified-Since conditional request header. The server will only process the request if the resource has not been modified since t. Returns HTTP 412 Precondition Failed if it was modified.
checkpoint := time.Now().Add(-1 * time.Hour)
page, err := client.From("Products").
IfUnmodifiedSince(checkpoint).
Page(ctx)
func (*QueryBuilder) Key ¶ added in v0.2.27
func (q *QueryBuilder) Key(key any) *QueryBuilder
Key sets the entity key for operations that target a single entity, such as DeleteLink and DeleteLinks.
Key is chainable and returns q for method chaining.
Example:
err := client.From("Orders").Key(1).DeleteLink(ctx, "Items", 5)
func (*QueryBuilder) LambdaAll ¶ added in v0.2.8
func (q *QueryBuilder) LambdaAll(collectionField string, fn func(*LambdaBuilder)) *QueryBuilder
LambdaAll appends an OData "all" lambda filter to the query. The fn callback receives a LambdaBuilder; use Field() to build conditions.
Generated OData: collectionField/all(v: <expression>)
The result is combined with any existing filter using "and".
query.LambdaAll("Items", func(b *LambdaBuilder) {
b.Field("Price").Gt(100)
})
// $filter=Items/all(i: i/Price gt 100)
func (*QueryBuilder) LambdaAny ¶ added in v0.2.8
func (q *QueryBuilder) LambdaAny(collectionField string, fn func(*LambdaBuilder)) *QueryBuilder
LambdaAny appends an OData "any" lambda filter to the query. The fn callback receives a LambdaBuilder; use Field() to build conditions.
Generated OData: collectionField/any(v: <expression>)
The result is combined with any existing filter using "and".
query.LambdaAny("Tags", func(b *LambdaBuilder) {
b.Field("Name").Eq("admin")
})
// $filter=Tags/any(t: t/Name eq 'admin')
func (*QueryBuilder) LinkTo ¶ added in v0.2.10
func (q *QueryBuilder) LinkTo(ctx context.Context, key any, navProperty string, targetEntitySet string, targetKey any) error
LinkTo creates a reference link from the current entity to a target entity.
LinkTo sends a PUT request to EntitySet(key)/NavProperty/$ref with a body containing the OData ID of the target entity. This establishes a navigation property relationship between two existing entities.
OData: PUT /EntitySet(key)/NavProperty/$ref Body: {"@odata.id": "serviceRoot/TargetEntitySet(targetKey)"}
Parameters:
- ctx: Request context
- key: Primary key of the source entity
- navProperty: Navigation property name on the source entity
- targetEntitySet: Entity set name of the target entity
- targetKey: Primary key of the target entity
Example:
err := client.From("Orders").LinkTo(ctx, 1, "Customer", "Customers", "ALFKI")
func (*QueryBuilder) NoCache ¶ added in v0.2.25
func (q *QueryBuilder) NoCache() *QueryBuilder
NoCache bypasses the cache for this request.
NoCache adds a Cache-Control: no-cache header to the outgoing request, forcing revalidation with the server even if a fresh cache entry exists in the relay-level HTTP cache. It does not affect the traverse-level ResponseCache; use QueryBuilder.WithCache with a zero TTL to skip that.
Use NoCache when you need guaranteed fresh data for a single query without disabling caching globally.
Example:
// Always fetch fresh data for this call
page, _ := client.From("Orders").
NoCache().
Page(ctx)
func (*QueryBuilder) OrderBy ¶
func (q *QueryBuilder) OrderBy(field string) *QueryBuilder
OrderBy adds a $orderby clause to sort results in ascending order.
OrderBy specifies one or more properties to sort the result set in ascending order using the OData $orderby system query option. Multiple calls to OrderBy accumulate sort expressions; results are sorted first by the first OrderBy, then by the second, etc.
Sort Order: Results are sorted by properties in the order OrderBy methods are called:
- First OrderBy: Primary sort key
- Second OrderBy: Secondary sort key (within primary groups)
- And so on...
For descending order, use OrderByDesc() instead.
Performance: OData services may create database indexes on frequently used sort keys. Sorting on non-indexed properties can be slow for large datasets.
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes (accumulates with subsequent OrderBy calls)
Examples:
Single sort:
query.OrderBy("LastName")
// OData: $orderby=LastName asc
Multiple sort keys (hierarchical):
query.OrderBy("LastName").OrderBy("FirstName")
// OData: $orderby=LastName asc,FirstName asc
// Sorts by last name first, then first name within each last name group
Descending sort:
query.OrderByDesc("CreatedDate")
// OData: $orderby=CreatedDate desc
Mixed ascending/descending:
query.OrderBy("Department").OrderByDesc("Salary")
// Sorts by department ascending, then salary descending within each department
Combined with filtering:
query.Where("Status").Eq("Active").
OrderBy("Priority").
OrderByDesc("CreatedDate").
Top(100)
func (*QueryBuilder) OrderByDesc ¶
func (q *QueryBuilder) OrderByDesc(field string) *QueryBuilder
OrderByDesc adds a $orderby clause to sort results in descending order.
OrderByDesc specifies one or more properties to sort the result set in descending order using the OData $orderby system query option. Multiple calls to OrderByDesc accumulate sort expressions; results are sorted first by the first OrderBy/OrderByDesc, then by the second, etc.
Sort Order: Results are sorted by properties in the order OrderBy/OrderByDesc methods are called:
- First call: Primary sort key (ascending or descending)
- Second call: Secondary sort key (ascending or descending)
- And so on...
Mixing Ascending and Descending: You can mix OrderBy() (ascending) and OrderByDesc() (descending) in the same query. Each call maintains its own sort direction.
Performance: OData services may create database indexes on frequently used sort keys. Sorting on non-indexed properties can be slow for large datasets.
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes (accumulates with subsequent OrderBy/OrderByDesc calls)
Examples:
Single descending sort:
query.OrderByDesc("Price")
// OData: $orderby=Price desc
Multiple descending sorts:
query.OrderByDesc("DateCreated").OrderByDesc("Priority")
// OData: $orderby=DateCreated desc,Priority desc
// Most recent first, then highest priority within each date
Mixed ascending and descending:
query.OrderBy("Category").OrderByDesc("Revenue")
// OData: $orderby=Category asc,Revenue desc
// Categories A-Z, highest revenue first within each category
Complex query with multiple sort criteria:
results, _ := client.From("Orders").
Where("Status").Eq("Shipped").
OrderByDesc("OrderDate").
OrderBy("CustomerName").
Top(50).
Collect(ctx)
func (*QueryBuilder) Page ¶
func (q *QueryBuilder) Page(ctx context.Context) (*Page, error)
Page returns a single page of results with pagination metadata.
Page executes the query and returns one page of results along with pagination metadata. The returned Page struct contains:
- Value: List of entities for this page (typically 1-1000 records)
- NextLink: URL to fetch the next page (empty if this is the last page)
- Count: Total count of matching records (only if WithCount() was called)
- DeltaLink: URL for delta queries (OData v4 with delta queries only)
Pagination Workflow: Page is useful when implementing paginated interfaces or processing results in controlled chunks. Manually fetch pages using NextLink or use Stream() for automatic multi-page streaming.
Typical Pagination Loop:
- query.Top(pageSize).WithCount().Page(ctx) - Get first page with total count
- Check page.NextLink to see if more results exist
- Call next query with Skip((page-1)*pageSize) for subsequent pages
- Repeat until page.NextLink is empty
Alternative to Manual Pagination: For simpler code, use Collect() to fetch all results at once, or Stream() for memory-efficient processing of large datasets.
Returns:
- *Page: Pagination metadata and results
- error: If query fails or server returns error
Thread Safety: The returned Page and its Value slice are not thread-safe if modified. Create copies before sharing across goroutines.
Examples:
Get first page with total count:
page, _ := client.From("Customers").
OrderBy("CustomerID").
Top(25).
WithCount().
Page(ctx)
log.Printf("Page 1 of %d (25 records per page)", page.Count/25)
Manual pagination loop:
pageSize := 50
for page := 1; ; page++ {
results, _ := client.From("Products").
OrderBy("ProductID").
Skip((page - 1) * pageSize).
Top(pageSize).
Page(ctx)
if len(results.Value) == 0 {
break
}
processPage(results.Value, page)
}
Using NextLink for pagination:
for url := initialURL; url != ""; {
page, _ := client.From("Orders").Page(ctx)
processBatch(page.Value)
url = page.NextLink // May require converting to query
}
func (*QueryBuilder) Param ¶
func (q *QueryBuilder) Param(key, value string) *QueryBuilder
Param adds a custom query parameter to the request.
Param allows adding arbitrary query parameters beyond the standard OData options. If called multiple times with the same key, the last value replaces the previous one. Param is chainable and returns q for method chaining.
Note: Param uses lazy initialization to avoid allocating a params map until needed.
Example:
query.Param("api-version", "1.0")
func (*QueryBuilder) Search ¶
func (q *QueryBuilder) Search(expr SearchExpression) *QueryBuilder
Search performs a full-text search using the $search system query option.
Search is only available in OData v4. Pass a SearchExpression built with SearchWord, SearchPhrase, SearchAnd, SearchOr, or SearchNot to construct complex search expressions. Use the package-level Search convenience function to create a simple word-or-phrase term from a plain string. Search is chainable and returns q for method chaining.
Examples:
// Simple word search
query.Search(traverse.SearchWord("mountain"))
// Combined expression
query.Search(traverse.SearchAnd(traverse.SearchWord("mountain"), traverse.SearchNot(traverse.SearchWord("bike"))))
func (*QueryBuilder) Select ¶
func (q *QueryBuilder) Select(fields ...string) *QueryBuilder
Select limits the returned properties to only the specified fields.
Select specifies which properties should be included in the query response using the OData $select system query option. When Select is used, the OData service returns only the specified properties, reducing bandwidth and improving query performance.
If Select is called multiple times, fields are accumulated (not replaced). Calling Select("A", "B") then Select("C") results in properties A, B, and C being selected.
Useful for:
- Reducing response size: Only fetch needed properties
- Improving performance: Fewer properties to transmit and parse
- Security: Exclude sensitive properties from result sets
- Bandwidth optimization: Critical for mobile or limited bandwidth scenarios
If Select is never called, all properties are returned (default behavior).
Parameters:
- fields: Property names to include (comma-separated is also valid: "ID,Name,Price")
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes
Examples:
Select specific fields:
query.Select("CustomerID", "Name", "Country")
// OData: $select=CustomerID,Name,Country
Multiple Select calls accumulate:
query.Select("ID", "Name").Select("Email")
// OData: $select=ID,Name,Email
Exclude internal/large properties:
query.Select("ProductID", "Name", "Price", "Category").
Filter("Price gt 100")
// Gets only essential info, improving performance
func (*QueryBuilder) Skip ¶
func (q *QueryBuilder) Skip(n int) *QueryBuilder
Skip skips a specified number of records using the $skip system query option.
Skip specifies the number of entities to skip before returning results. This is typically used with Top for cursor-based pagination through large datasets.
Behavior: If Skip is called multiple times, the last value replaces previous ones (not cumulative). Skip(10).Skip(20) results in $skip=20, not $skip=30.
Pagination Pattern: Combine Skip with Top to implement page-based pagination:
- Page 1: Top(10) [Skip defaults to 0]
- Page 2: Skip(10).Top(10) [Results 11-20]
- Page 3: Skip(20).Top(10) [Results 21-30]
- Page N: Skip((N-1)*PageSize).Top(PageSize)
Zero-Based Indexing: Skip uses zero-based indexing: Skip(0) means "don't skip any", Skip(10) means "skip the first 10 and start at the 11th record".
Performance: Skipping many records can be slower than using previous result bookmarks or date-based filters. For large datasets, consider filtering by date or using the Page() method which may be more efficient.
Relation to Page(): For simpler pagination with metadata, use Page(pageNumber, pageSize) instead of manually calculating Skip/Top offsets.
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes
Examples:
Skip first 10, get next 25:
query.Skip(10).Top(25) // OData: $skip=10&$top=25 // Returns records 11-35
Pagination loop (50 records per page):
pageSize := 50
for page := 1; ; page++ {
results, _ := client.From("Customers").
OrderBy("CustomerID").
Skip((page - 1) * pageSize).
Top(pageSize).
Collect(ctx)
if len(results) == 0 {
break
}
processPage(results)
}
Combined with filtering and sorting:
query.Where("Status").Eq("Active").
OrderByDesc("CreatedDate").
Skip(100).
Top(50)
// Skip first 100 active records, get next 50 (most recent first)
func (*QueryBuilder) Stream ¶
func (q *QueryBuilder) Stream(ctx context.Context, bufferSize ...int) <-chan Result[map[string]interface{}]
Stream streams all matching records from all pages using adaptive buffering. The returned channel will receive Result values containing either a data record (in Result.Value) or an error (in Result.Err). The channel is closed when streaming completes or an error occurs.
The bufferSize parameter controls the channel buffer capacity:
- If bufferSize is omitted or ≤0, adaptive buffering is used (recommended for large datasets)
- If bufferSize > 0, that exact buffer size is used
Adaptive buffering estimates the buffer size as: 10MB / avgRecordSize, clamped to [32, 1024]. This reduces memory usage and GC pressure for large datasets with small records.
Stream uses a worker pool for efficient goroutine management and implements backpressure through channel buffering to prevent unbounded memory growth.
Example:
for result := range query.Stream(ctx) {
if result.Err != nil {
return result.Err
}
process(result.Value) // map[string]interface{}
}
func (*QueryBuilder) StreamAs ¶
func (q *QueryBuilder) StreamAs(ctx context.Context, bufferSize ...int) <-chan Result[map[string]interface{}]
StreamAs is the generic typed version of Stream.
StreamAs is identical to Stream but allows specifying a result type through generic type parameters. Currently, this returns the same untyped map[string]interface{} as Stream, but the method signature allows for future type-safe streaming implementations.
See QueryBuilder.Stream for usage details and examples.
func (*QueryBuilder) StreamProperty ¶ added in v0.2.8
func (q *QueryBuilder) StreamProperty(ctx context.Context, propertyName string) (io.ReadCloser, error)
StreamProperty fetches a named binary stream property and returns its content as an io.ReadCloser. The caller is responsible for closing the returned reader.
StreamProperty issues a GET request to EntitySet/PropertyName with the Accept: application/octet-stream header. The response body is returned directly without buffering, making this suitable for large binary properties.
The entitySet on the QueryBuilder should include the entity key, e.g.:
reader, err := client.From("Products(42)").StreamProperty(ctx, "Photo")
if err != nil { ... }
defer reader.Close()
io.Copy(file, reader)
func (*QueryBuilder) StreamPropertySize ¶ added in v0.2.8
StreamPropertySize issues a HEAD request to determine the byte size of a named stream property without downloading its content.
Returns the value of the Content-Length response header. Returns -1 if the server does not include a Content-Length header.
size, err := client.From("Products(42)").StreamPropertySize(ctx, "Photo")
func (*QueryBuilder) Top ¶
func (q *QueryBuilder) Top(n int) *QueryBuilder
Top limits the number of records returned using the $top system query option.
Top specifies the maximum number of entities to return in a single response. Combined with Skip, Top enables cursor-based pagination through large datasets.
Behavior: If Top is called multiple times, the last value replaces previous ones (not cumulative). Top(10).Top(20) results in $top=20, not $top=30.
Pagination Pattern: Combine Top with Skip to implement page-based pagination:
- Page 1: Skip(0).Top(10) or just Top(10)
- Page 2: Skip(10).Top(10)
- Page 3: Skip(20).Top(10)
- And so on...
Server-Side Limits: Many OData services enforce maximum Top values. If you request $top=10000 but the service limit is 1000, the service may return only 1000 records. Check service documentation for limits.
Relation to Page(): For simpler pagination with metadata, use Page(pageNumber, pageSize) instead of manually calculating Skip/Top offsets.
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes
Examples:
Get first 10 records:
query.Top(10) // OData: $top=10
Pagination (page 3, 25 per page):
query.Skip(50).Top(25) // OData: $skip=50&$top=25 // Returns records 51-75
Combined with filtering and sorting:
query.Where("Status").Eq("Active").
OrderByDesc("CreatedDate").
Top(50)
// Get 50 most recent active records
Implementation of paging loop:
for page := 1; page <= maxPages; page++ {
results, _ := client.From("Products").
OrderBy("ProductID").
Skip((page - 1) * 100).
Top(100).
Collect(ctx)
}
func (*QueryBuilder) UnlinkFrom ¶ added in v0.2.10
func (q *QueryBuilder) UnlinkFrom(ctx context.Context, key any, navProperty string, targetKey ...any) error
UnlinkFrom removes a reference link from the current entity.
For a single-valued navigation property (no targetKey), it sends:
DELETE /EntitySet(key)/NavProperty/$ref
For a collection-valued navigation property (with targetKey), it sends:
DELETE /EntitySet(key)/NavProperty(targetKey)/$ref
Parameters:
- ctx: Request context
- key: Primary key of the source entity
- navProperty: Navigation property name on the source entity
- targetKey: (optional) Primary key of the target entity for collection nav properties
Example (single-valued):
err := client.From("Orders").UnlinkFrom(ctx, 1, "Customer")
Example (collection-valued):
err := client.From("Orders").UnlinkFrom(ctx, 1, "Items", 42)
func (*QueryBuilder) UpdateDeep ¶ added in v0.7.0
UpdateDeep performs an OData 4.01 deep update of an entity with related entities.
UpdateDeep PATCHes the entity at the keyed URL, embedding related entities in the request body for atomic nested updates. The request sets Content-Type to "application/json;odata.metadata=minimal" and Prefer to "return=representation".
Call QueryBuilder.Key before UpdateDeep to set the entity key:
resp, err := client.From("Orders").Key(1).UpdateDeep(ctx, OrderWithItems{
Status: "Confirmed",
Items: []Item{{ID: 10, Qty: 5}},
})
Spec ref: OData 4.01 Part 1 - Section 11.4.3 (Update Related Entities When Updating an Entity).
func (*QueryBuilder) UpdateDeepWithPrefer ¶ added in v0.7.0
func (q *QueryBuilder) UpdateDeepWithPrefer(ctx context.Context, entity any, prefer string) (*relay.Response, error)
UpdateDeepWithPrefer performs a deep update with a custom Prefer header value.
Use this when full control over the Prefer header is required, for example to combine "return=representation" with "odata.continue-on-error", or to use a custom preference. Pass an empty string to omit the Prefer header entirely.
opts := traverse.DeepUpdateOptions{
ReturnRepresentation: true,
ContinueOnError: true,
}
resp, err := client.From("Orders").Key(1).
UpdateDeepWithPrefer(ctx, patch, opts.PreferHeader())
func (*QueryBuilder) Where ¶
func (q *QueryBuilder) Where(field string) *FilterBuilder
Where starts building a type-safe filter expression for the given field.
Where returns a FilterBuilder that provides type-safe filter building with methods like Eq(), Gt(), StartsWith(), Contains(), etc. The FilterBuilder implements the builder pattern and returns *QueryBuilder from each method to enable chaining.
Type Safety: Unlike Filter() which uses raw strings, Where() prevents many syntax errors through a typed builder interface. Property names and operators are checked before sending to the service.
Object Pooling: FilterBuilder instances are obtained from a sync.Pool to reduce allocations. Returned FilterBuilder instances should NOT be retained after the QueryBuilder is returned; they are reused for subsequent calls.
Comparison Methods Available on FilterBuilder:
- Eq(): Equal (eq)
- Ne(): Not equal (ne)
- Gt(): Greater than (gt)
- Ge(): Greater or equal (ge)
- Lt(): Less than (lt)
- Le(): Less or equal (le)
- StartsWith(): String startswith
- EndsWith(): String endswith
- Contains(): String contains
Returns:
- *FilterBuilder: For chainable filter building
Chainable: Yes (chains to QueryBuilder through FilterBuilder methods)
Examples:
Single condition:
query.Where("Age").Gt(30)
// OData: $filter=Age gt 30
Multiple conditions (chained):
query.Where("Status").Eq("Active").Where("Priority").Gt(1)
// OData: $filter=Status eq 'Active' and Priority gt 1
String operations:
query.Where("Name").StartsWith("John")
// OData: $filter=startswith(Name, 'John')
Nested filtering with expand:
query.Where("Status").Eq("Active").
Expand("Orders").
Collect(ctx)
func (*QueryBuilder) WithCache ¶ added in v0.2.25
func (q *QueryBuilder) WithCache(ttl time.Duration) *QueryBuilder
WithCache marks the query as cacheable with the given TTL.
When WithCache is set and the client has a ResponseCache configured via WithResponseCache, the response body is stored in the cache under the full request URL key. Subsequent identical requests served within the TTL window are returned from cache without making an HTTP request.
If the cached response carries an ETag or Last-Modified header, and the entry has expired, traverse sends a conditional request (If-None-Match or If-Modified-Since). A 304 Not Modified response renews the entry without re-downloading the body.
Cache invalidation happens automatically after Client.Create, Client.Update, Client.Replace, and Client.Delete operations on the same entity set.
WithCache applies to QueryBuilder.Page, QueryBuilder.Collect, and QueryBuilder.First calls. It has no effect if no ResponseCache is configured.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithResponseCache(traverse.NewInMemoryResponseCache()),
)
products, _ := client.From("Products").
WithCache(5 * time.Minute).
Collect(ctx)
func (*QueryBuilder) WithCount ¶
func (q *QueryBuilder) WithCount() *QueryBuilder
WithCount includes the total count of matching records in results.
When called, the response will include a $count field with the total number of records matching the query filters, even if fewer records are returned due to Top/Skip settings. This is useful for pagination scenarios where the client needs to know the total result set size. WithCount is chainable and returns q for method chaining.
Example:
page, err := query.Top(10).WithCount().Page(ctx)
if err == nil {
log.Printf("Total records: %d", page.Count)
}
func (*QueryBuilder) WithDeltaToken ¶
func (q *QueryBuilder) WithDeltaToken(token string) *QueryBuilder
WithDeltaToken enables incremental updates using a delta token.
WithDeltaToken is only available in OData v4 and is used with delta queries to retrieve only entities that have changed since the previous query. The delta token is provided by a previous successful delta query in the Page.DeltaLink field. WithDeltaToken is chainable and returns q for method chaining.
Example:
page, err := query.WithDeltaToken(previousToken).Page(ctx) // OData v4 only
func (*QueryBuilder) WithNoPrefetch ¶ added in v0.2.27
func (q *QueryBuilder) WithNoPrefetch() *QueryBuilder
WithNoPrefetch disables background page prefetching.
WithNoPrefetch is useful when strict sequential page fetching is required, or when you want to undo an earlier WithPrefetch call on the same builder. WithNoPrefetch is chainable and returns q for method chaining.
func (*QueryBuilder) WithPrefer ¶ added in v0.6.0
func (q *QueryBuilder) WithPrefer(pref string) *QueryBuilder
WithPrefer adds an OData Prefer header value to this query.
WithPrefer can be called multiple times; values are comma-joined into a single Prefer header, as specified in RFC 7240. Use the Prefer* constants for the standard OData preference values.
This implements OData v4.0 spec section 8.2.8.
Examples:
// Strict query option handling
client.From("Products").WithPrefer(traverse.PreferHandlingStrict).Collect(ctx)
// Request delta tracking
client.From("Orders").WithPrefer(traverse.PreferTrackChanges).Page(ctx)
// Combine multiple preferences
client.From("Products").
WithPrefer(traverse.PreferHandlingLenient).
WithPrefer(traverse.PreferReturnRepresentation).
Collect(ctx)
func (*QueryBuilder) WithPrefetch ¶ added in v0.2.27
func (q *QueryBuilder) WithPrefetch(bufferPages int) *QueryBuilder
WithPrefetch enables background prefetching of the next page while the current page is being processed by the caller.
bufferPages controls how many pages to prefetch ahead. Values below 1 default to 1; values above 3 are clamped to 3. When Stream() is called on a query with prefetching enabled, an internal goroutine fetches page N+1 (and up to bufferPages-1 more) while the caller iterates over page N, reducing per-page latency for paginated reads.
WithPrefetch is chainable and returns q for method chaining.
Example:
for result := range client.From("Orders").WithPrefetch(1).Stream(ctx) {
// page 2 is already being fetched while you process page 1
}
func (*QueryBuilder) WithSchema ¶ added in v0.2.12
func (q *QueryBuilder) WithSchema(schema *EntitySchema) *QueryBuilder
WithSchema attaches an EntitySchema to the QueryBuilder for filter and orderby validation.
WithSchema enables schema-based validation of filter and orderby expressions before sending the request to the OData service. This catches typos in field names and type mismatches at build time rather than at runtime when the service rejects the request.
When a schema is set, subsequent calls to Filter() and OrderBy() validate field names against the schema properties. If a referenced field is not found, a SchemaValidationError is returned immediately.
Backward Compatibility: If schema is nil (not set), validation is skipped and the QueryBuilder behaves as before. This ensures existing code continues to work without modification.
Returns:
- *QueryBuilder: For method chaining
Chainable: Yes
Examples:
Define a schema and attach it:
schema := &EntitySchema{
Properties: map[string]string{
"ID": "int",
"Name": "string",
"Email": "string",
},
}
query := client.From("Users").WithSchema(schema)
Now Filter() validates field names:
query.Filter("UnknownField eq 'value'") // Returns error
Valid filters are accepted:
query.Filter("Name eq 'John'") // OK, Name exists in schema
func (*QueryBuilder) WithSchemaVersion ¶ added in v0.6.0
func (q *QueryBuilder) WithSchemaVersion(version string) *QueryBuilder
WithSchemaVersion sets the OData-SchemaVersion request header for this query only.
This overrides any schema version set at the client level for this specific query. See traverse.WithSchemaVersion for the client-level option.
Example:
client.From("Products").WithSchemaVersion("1.5").Collect(ctx)
type RawResult ¶
type RawResult struct {
// Raw is the raw JSON bytes for the record
Raw json.RawMessage
// Page is the 1-based page number this result came from
Page int
// Index is the 0-based index within the page
Index int
// Err is an error if one occurred, nil otherwise
Err error
}
RawResult represents a raw JSON record from an optimized streaming path.
RawResult is used internally for zero-allocation streaming scenarios where direct unmarshaling to custom types is needed. The Raw field contains the JSON bytes for the record, bypassing intermediate map allocations.
type ResponseCache ¶ added in v0.2.25
type ResponseCache interface {
// Get returns the cached entry for key.
// Expired entries are returned (callers must check isExpired) so that
// their ETag or Last-Modified can be used for conditional revalidation.
// Returns nil and false if the key is absent entirely.
Get(key string) (*ResponseCacheEntry, bool)
// Set stores entry for key. When ttl > 0, ExpiresAt is set to now+ttl.
// A zero or negative ttl stores the entry without automatic expiry.
Set(key string, entry *ResponseCacheEntry, ttl time.Duration)
// Delete removes the entry for key. No-op if key is absent.
Delete(key string)
// Invalidate removes all entries whose key begins with prefix.
// Used to purge all cached pages for an entity set after mutations.
Invalidate(prefix string)
// Clear removes all entries from the cache.
Clear()
}
ResponseCache is the storage backend for HTTP-level response caching in traverse.
Implement this interface to plug in a custom cache backend such as Redis, Memcached, or a specialised in-memory store. All methods must be safe for concurrent use.
The default implementation is returned by NewInMemoryResponseCache.
Example:
client, _ := traverse.New(
traverse.WithBaseURL("https://odata.example.com/v4"),
traverse.WithResponseCache(traverse.NewInMemoryResponseCache()),
)
func NewInMemoryResponseCache ¶ added in v0.2.25
func NewInMemoryResponseCache() ResponseCache
NewInMemoryResponseCache returns a new in-memory ResponseCache backed by sync.Map. It is safe for concurrent use and requires no external dependencies.
Entries are stored until they are explicitly removed via Delete, Invalidate, or Clear, or until they are lazily cleaned up on access after expiry.
type ResponseCacheEntry ¶ added in v0.2.25
type ResponseCacheEntry struct {
// Body is the raw JSON response body bytes.
Body []byte
// ETag is the value of the ETag response header, used for If-None-Match.
ETag string
// LastModified is the value of the Last-Modified response header,
// used for If-Modified-Since when ETag is absent.
LastModified string
// ExpiresAt is the absolute time after which this entry must be revalidated.
// A zero value means the entry never expires automatically.
ExpiresAt time.Time
}
ResponseCacheEntry holds a cached HTTP response body alongside metadata needed for conditional revalidation (ETag, Last-Modified) and TTL tracking.
type ResponseFormat ¶
type ResponseFormat int
Response format options
const ( FormatJSON ResponseFormat = iota // JSON (default) FormatAtom // XML/ATOM (legacy OData v2) )
type Result ¶
type Result[T any] struct { // Value is the actual data record Value T // Err is an error if one occurred, nil otherwise Err error // Page is the 1-based page number this result came from Page int // Index is the 0-based index within the page Index int }
Result represents a single record result from a stream, along with any error. Result represents a single streamed result from a query.
Result is a generic type that contains either a data value or an error. When streaming results via QueryBuilder.Stream, each item on the returned channel is a Result. Check Err before using Value.
Fields:
- Value: the actual data (map[string]interface{} for most queries)
- Err: error encountered during streaming (or nil if successful)
- Page: 1-based page number of this result
- Index: 0-based index within the page
type SAPAnnotations ¶
type SAPAnnotations struct {
// Label is a human-readable label for the property (sap:label).
Label string
// Filterable indicates if the property can be used in $filter clauses (sap:filterable).
Filterable bool
// Sortable indicates if the property can be used in $orderby clauses (sap:sortable).
Sortable bool
// Searchable indicates if the property can be used in search operations (sap:searchable).
Searchable bool
// Required indicates if the property is required in filter expressions (sap:required-in-filter).
Required bool
// Text refers to a property containing a descriptive text for this property (sap:text).
Text string
// Unit refers to a property holding the unit of measure for this property (sap:unit).
Unit string
// ValueList describes how value help is provided: "standard" or "fixed-values" (sap:value-list).
ValueList string
// DisplayFormat specifies the display format, e.g. "UpperCase", "NonNegative", "Date" (sap:display-format).
DisplayFormat string
// FieldControl specifies the field-control property name or mode: Mandatory, Optional, ReadOnly (sap:field-control).
FieldControl string
// Semantics is the semantic type of the value, e.g. "email", "phone", "url" (sap:semantics).
Semantics string
// IsKey is true when sap:key="true" is present on the property.
IsKey bool
// UpdatablePath refers to a boolean property that controls whether this property is updatable (sap:updatable-path).
UpdatablePath string
// Creatable indicates if the entity set supports create operations (sap:creatable). Used on EntitySet level.
Creatable bool
// Updatable indicates if the entity set supports update operations (sap:updatable). Used on EntitySet level.
Updatable bool
// Deletable indicates if the entity set supports delete operations (sap:deletable). Used on EntitySet level.
Deletable bool
// Pageable indicates if the entity set supports server-side paging (sap:pageable). Used on EntitySet level.
Pageable bool
// Addressable indicates if individual entities are directly addressable by key (sap:addressable). Used on EntitySet level.
Addressable bool
// RequiresFilter indicates if $filter must be specified in queries (sap:requires-filter). Used on EntitySet level.
RequiresFilter bool
// ChangeTracking indicates if the entity set supports delta/change-tracking (sap:change-tracking). Used on EntitySet level.
ChangeTracking bool
}
SAPAnnotations contains SAP-specific annotations for properties and entity types. These annotations provide hints about how SAP systems use the properties.
type SAPError ¶ added in v0.21.3
type SAPError struct {
Code string // SAP error code (e.g., "/IWFND/MED/170")
Message string // Human-readable error message
InnerError string // SAP inner error details if present
ErrorType SAPErrorType // Categorized error type
RawBody string // Original response body for debugging
IsXML bool // Whether original response was XML format
}
SAPError represents a structured error from a SAP OData service.
SAPError encapsulates both JSON and XML error formats that SAP systems return, parsing and normalizing them into a unified structure. This allows consumers to branch on error type (configuration, authorization, CSRF, transient) rather than inspecting raw response bodies.
SAP returns errors in multiple formats:
- JSON format (OData v4): {"error":{"code":"...","message":{"value":"..."},"innererror":{...}}}
- XML Atom format (OData v2): <error><code>...</code><message>...</message><innererror>...</innererror></error>
Example:
if sapErr := ParseSAPError(resp); sapErr != nil {
switch sapErr.ErrorType {
case SAPErrorTypeNotFound:
// Handle entity not found
case SAPErrorTypeCSRF:
// Handle CSRF token expiration
case SAPErrorTypeUnauthorized:
// Handle auth failure
}
}
func IsSAPError ¶ added in v0.21.3
IsSAPError extracts a *SAPError from an error value.
IsSAPError uses errors.As to unwrap and retrieve the *SAPError from a potentially wrapped error. Returns the *SAPError and true if found, otherwise returns nil and false.
func ParseSAPError ¶ added in v0.21.3
ParseSAPError extracts and categorizes a structured error from SAP response body.
ParseSAPError attempts to parse the response body as either JSON (OData v4 format) or XML (OData v2 Atom format), categorizing the error type for programmatic handling. Returns nil if the body is not a recognized SAP error format.
Supported categorizations:
- CSRF errors: Contains "csrf", "token invalid", "token expired", "x-csrf-token"
- Authorization errors: Contains "forbidden", "unauthorized", "permission"
- Configuration errors: Contains "/IWFND/MED", "not found", "unknown service"
- Transient errors: Contains "timeout", "gateway", "temporarily"
type SAPErrorType ¶ added in v0.21.3
type SAPErrorType int
SAPErrorType categorizes SAP errors for programmatic handling.
const ( SAPErrorTypeUnknown SAPErrorType = iota // Unknown or uncategorized error SAPErrorTypeNotFound // Service or entity not found SAPErrorTypeCSRF // CSRF token invalid or missing SAPErrorTypeServiceConfig // SAP configuration or setup issue SAPErrorTypeTransient // Transient error (timeout, gateway issue) )
type SchemaValidationError ¶ added in v0.2.12
SchemaValidationError is returned when a filter or orderby references an unknown field or has a type mismatch.
SchemaValidationError provides detailed information about validation failures so that developers can quickly identify and fix issues in their filter expressions.
Example:
if err != nil {
if schemaErr, ok := err.(*SchemaValidationError); ok {
fmt.Printf("Unknown field: %s\n", schemaErr.Field)
fmt.Printf("Details: %s\n", schemaErr.Message)
}
}
func (*SchemaValidationError) Error ¶ added in v0.2.12
func (e *SchemaValidationError) Error() string
Error implements the error interface.
type SearchExpression ¶ added in v0.2.23
type SearchExpression interface {
// contains filtered or unexported methods
}
SearchExpression represents an OData $search expression. OData $search is distinct from $filter - it performs full-text search across all searchable properties of an entity.
Example:
results, err := client.From("Products").
Search(SearchAnd(SearchWord("mountain"), SearchNot(SearchWord("bike")))).
Collect(ctx)
func Search ¶ added in v0.2.23
func Search(s string) SearchExpression
Search is a convenience constructor that auto-detects whether the input should be a word (no spaces) or a phrase (contains spaces).
Example:
Search("mountain") // same as SearchWord("mountain")
Search("mountain bike") // same as SearchPhrase("mountain bike")
func SearchAnd ¶ added in v0.2.23
func SearchAnd(exprs ...SearchExpression) SearchExpression
SearchAnd combines two or more search expressions with logical AND.
Example:
SearchAnd(SearchWord("mountain"), SearchWord("view"))
// "(mountain AND view)"
func SearchNot ¶ added in v0.2.23
func SearchNot(expr SearchExpression) SearchExpression
SearchNot negates a search expression.
Example:
SearchNot(SearchWord("bike")).searchString() // "NOT bike"
func SearchOr ¶ added in v0.2.23
func SearchOr(exprs ...SearchExpression) SearchExpression
SearchOr combines two or more search expressions with logical OR.
Example:
SearchOr(SearchWord("mountain"), SearchWord("hill"))
// "(mountain OR hill)"
func SearchPhrase ¶ added in v0.2.23
func SearchPhrase(phrase string) SearchExpression
SearchPhrase creates a quoted-phrase OData $search term.
The phrase is wrapped in double quotes so the OData service treats it as a literal phrase rather than individual words.
Example:
SearchPhrase("mountain bike").searchString() // `"mountain bike"`
func SearchWord ¶ added in v0.2.23
func SearchWord(word string) SearchExpression
SearchWord creates a single-word OData $search term.
The word is emitted unquoted. Use SearchPhrase for multi-word expressions.
Example:
SearchWord("mountain").searchString() // "mountain"
type ServiceDocument ¶
type ServiceDocument struct {
// EntitySets is the list of available entity sets.
EntitySets []EntitySetReference
}
ServiceDocument represents the root OData service document. It contains a list of all available entity sets in the OData service.
type StringInterning ¶
type StringInterning struct {
// contains filtered or unexported fields
}
StringInterning provides optimized string deduplication to reduce memory allocations when processing entity and property names that are repeated across many records.
When processing millions of records from an OData service, entity names and property names are repeated in every record's JSON response. StringInterning deduplicates these strings, ensuring that identical strings reference the same memory location.
For example, if every record has a "Name" property, StringInterning returns the same string reference for all 1 million records instead of allocating 1 million string copies.
Example:
si := traverse.NewStringInterning()
propName1 := si.Intern("Name") // allocates
propName2 := si.Intern("Name") // returns cached reference
propName1 == propName2 // true, same memory location
func NewStringInterning ¶
func NewStringInterning() *StringInterning
NewStringInterning creates a new string interning cache.
NewStringInterning returns a ready-to-use *StringInterning with an initial capacity of 256 entries. It is safe for concurrent use.
func (*StringInterning) CacheSize ¶
func (si *StringInterning) CacheSize() int
CacheSize returns the current number of strings in the cache.
CacheSize is useful for monitoring cache growth during bulk operations.
func (*StringInterning) Clear ¶
func (si *StringInterning) Clear()
Clear removes all strings from the cache.
Clear is useful for testing or resetting memory state. It reinitializes the cache with the default capacity of 256 entries.
func (*StringInterning) Intern ¶
func (si *StringInterning) Intern(s string) string
Intern returns a deduplicated reference to the string.
If the string already exists in the cache, Intern returns the cached reference. Otherwise, it adds the string to the cache and returns it.
Intern uses a fast-path read lock for cache hits and a slow-path write lock for misses with double-check pattern to avoid race conditions.
Empty strings are returned as-is without caching.
func (*StringInterning) InternBatch ¶
func (si *StringInterning) InternBatch(strings ...string) []string
InternBatch interns multiple strings efficiently with a single lock acquisition.
InternBatch is more efficient than calling [Intern] multiple times when processing many strings at once. It acquires one write lock to check/insert all provided strings, avoiding repeated lock acquisitions per string.
Returns a new slice with deduplicated string references.
type TrackedEntity ¶ added in v0.2.6
type TrackedEntity struct {
// contains filtered or unexported fields
}
TrackedEntity wraps an entity snapshot and tracks which fields have been modified. Use it to generate minimal PATCH payloads with only changed fields.
Usage:
// Fetch an entity
entity := map[string]interface{}{"ID": 42, "Name": "Widget", "Price": 9.99}
// Start tracking
t := traverse.TrackEntity(entity)
t.Set("Name", "Updated Widget")
t.Set("Price", 19.99)
// Save only changed fields
err = t.SaveChanges(ctx, client, "Products", 42)
func TrackEntity ¶ added in v0.2.6
func TrackEntity(entity map[string]interface{}) *TrackedEntity
TrackEntity creates a TrackedEntity from an entity snapshot. The snapshot is copied so the original is preserved for comparison.
func (*TrackedEntity) Changes ¶ added in v0.2.6
func (t *TrackedEntity) Changes() map[string]interface{}
Changes returns a map containing only the fields that have been modified. This is the payload to send in a PATCH request.
func (*TrackedEntity) DirtyFields ¶ added in v0.2.6
func (t *TrackedEntity) DirtyFields() []string
DirtyFields returns the names of all modified fields.
func (*TrackedEntity) Discard ¶ added in v0.2.6
func (t *TrackedEntity) Discard()
Discard reverts all changes back to the original snapshot.
func (*TrackedEntity) Get ¶ added in v0.2.6
func (t *TrackedEntity) Get(field string) (interface{}, bool)
Get returns the current value of a field.
func (*TrackedEntity) IsDirty ¶ added in v0.2.6
func (t *TrackedEntity) IsDirty() bool
IsDirty reports whether any field has been modified.
func (*TrackedEntity) MarshalJSON ¶ added in v0.2.6
func (t *TrackedEntity) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler and encodes only the changed fields.
func (*TrackedEntity) Original ¶ added in v0.2.6
func (t *TrackedEntity) Original() map[string]interface{}
Original returns the unmodified snapshot of the entity as it was when TrackEntity was called.
func (*TrackedEntity) Reset ¶ added in v0.2.6
func (t *TrackedEntity) Reset()
Reset clears all dirty flags, making the current state the new baseline. Use this after a successful save to reset the tracker.
func (*TrackedEntity) SaveChanges ¶ added in v0.2.6
func (t *TrackedEntity) SaveChanges(ctx context.Context, client *Client, entitySet string, key interface{}) error
SaveChanges saves the dirty fields to the OData service using a PATCH request. If no fields are dirty, it is a no-op and returns nil.
SaveChanges is safe to call concurrently: it atomically snapshots the dirty fields and clears only those fields on success. Any Set calls that race with the in-flight PATCH remain dirty and will be included in the next SaveChanges.
t := traverse.TrackEntity(entity)
t.Set("Name", "New Name")
err := t.SaveChanges(ctx, client, "Products", 42)
func (*TrackedEntity) Set ¶ added in v0.2.6
func (t *TrackedEntity) Set(field string, value interface{})
Set updates a field value and marks it dirty.
type ValidationVocabulary ¶ added in v0.2.29
type ValidationVocabulary struct {
// Minimum corresponds to Org.OData.Validation.V1.Minimum.
Minimum *float64
// Maximum corresponds to Org.OData.Validation.V1.Maximum.
Maximum *float64
// Pattern corresponds to Org.OData.Validation.V1.Pattern.
Pattern string
// AllowedValues corresponds to Org.OData.Validation.V1.AllowedValues.
AllowedValues []string
// Required corresponds to Org.OData.Validation.V1.Required.
Required bool
}
ValidationVocabulary defines Org.OData.Validation.V1 annotation terms.
func ParseValidationVocabulary ¶ added in v0.2.29
func ParseValidationVocabulary(annotations map[string]string) ValidationVocabulary
ParseValidationVocabulary extracts Org.OData.Validation.V1 annotation terms from a raw annotation map. The map keys are fully-qualified term names (e.g. "Org.OData.Validation.V1.Minimum").
Source Files
¶
- annotations.go
- async_op.go
- atom_parser.go
- batch.go
- cache.go
- capabilities.go
- change_tracking.go
- client.go
- compute.go
- conditional_headers.go
- crossjoin.go
- csdl_json.go
- deep_expand.go
- deep_insert.go
- deep_update.go
- delete.go
- delta.go
- doc.go
- edmx_parser.go
- entity.go
- errors.go
- etag_upsert.go
- filter_builder.go
- function.go
- geo_filter.go
- geo_types.go
- graph.go
- lambda_filter.go
- options_relay.go
- paginator.go
- prefetch.go
- query.go
- ref_links.go
- response_cache.go
- schema.go
- search.go
- service_parser.go
- singleton.go
- stream.go
- stream_property.go
- string_intern.go
- types.go
- vocabulary.go
Directories
¶
| Path | Synopsis |
|---|---|
|
benchmarks
|
|
|
cmd
|
|
|
demo
command
Package main implements a comprehensive OData v4 test server supporting both XML and JSON formats.
|
Package main implements a comprehensive OData v4 test server supporting both XML and JSON formats. |
|
traverse
command
|
|
|
traverse-gen
command
|
|
|
traverse-tui
command
Command traverse-tui provides an interactive terminal interface for building and executing OData queries.
|
Command traverse-tui provides an interactive terminal interface for building and executing OData queries. |
|
ext
|
|
|
audit
module
|
|
|
azure
module
|
|
|
cache/memory
module
|
|
|
cache/redis
module
|
|
|
cache/stale
module
|
|
|
dataverse
module
|
|
|
graphql
module
|
|
|
oauth2
module
|
|
|
offline
module
|
|
|
openapi
module
|
|
|
prometheus
module
|
|
|
sap
module
|
|
|
tracing
module
|
|
|
webhooks
module
|
|
|
internal
|
|
|
encoder
Package encoder provides OData URL construction and encoding utilities.
|
Package encoder provides OData URL construction and encoding utilities. |
|
parser
Package parser provides OData filter expression parsing and serialization.
|
Package parser provides OData filter expression parsing and serialization. |
|
tokenizer
Package tokenizer provides streaming JSON parsing utilities for OData responses.
|
Package tokenizer provides streaming JSON parsing utilities for OData responses. |
|
Package testutil provides testing utilities for traverse users.
|
Package testutil provides testing utilities for traverse users. |
|
tools
|
|
|
bench-compare
command
|