msgraph

package module
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 2026 License: MIT Imports: 14 Imported by: 1

README

msgraph-go

msgraph-go is a small, dependency-free Microsoft Graph REST client for Go. It owns transport concerns that every typed Microsoft SDK in this workspace needs: bearer-token injection, OData query construction, JSON request/response handling, paging, delta sync, throttling retries, and structured Graph errors.

It deliberately does not import msauth-go. Callers provide a TokenSource, so CLIs can wire Microsoft delegated auth while tests and services can provide their own token source.

Usage

client, err := msgraph.New(msgraph.TokenSourceFunc(func(ctx context.Context) (string, error) {
    return "access-token", nil
}))

With msauth-go, whose TokenSource satisfies the interface structurally:

client, err := msgraph.New(auth.TokenSource("Mail.Read"))
Typed requests

The generic helpers name the result type once and infer it everywhere after, instead of routing it through an any out-parameter:

message, resp, err := msgraph.Get[Message](ctx, client, "/me/messages/"+id, msgraph.Params{})

items, err := msgraph.Collect[Message](ctx, client, "/me/messages", msgraph.Params{Top: 50})

for message, err := range msgraph.Items[Message](ctx, client, "/me/messages", msgraph.Params{}) {
    // ...
}

Get, Post, Patch, Do, Collect, and First are typed; PostNoContent and Delete discard the body. The Client methods remain available for decoding into an existing value, streaming into an io.Writer, or ignoring the response body.

Typed filters

Params.Where builds $filter from a typed expression tree, which keeps literals escaped. A value containing an apostrophe — O'Brien — would otherwise close the OData string literal early and change what the query means:

params := msgraph.Params{
    Where: msgraph.And(
        msgraph.Eq("isRead", false),
        msgraph.Ge("receivedDateTime", cutoff),          // time.Time, normalized to UTC
        msgraph.Contains("subject", userSuppliedText),   // escaped
    ),
    Sort:   []msgraph.Order{msgraph.Desc("receivedDateTime")},
    Select: []string{"id", "subject", "from"},
    Top:    25,
}

Available: Eq, Ne, Gt, Ge, Lt, Le, In, IsNull, IsNotNull, Contains, StartsWith, EndsWith, And, Or, Not, Any, All, Field, and Raw as the escape hatch. The Literal constraint admits strings, bools, the integer and float types, and time.Time; anything else is a compile error. Params.Filter and Params.OrderBy still accept raw strings and are combined with the typed forms.

Transport behavior
  • Absolute request URLs must be same-origin with the base URL. Every request carries a bearer token, so an off-origin URL — a spoofed @odata.nextLink, a caller-supplied link — would hand that token to a third party. Those requests fail with ErrUntrustedHost; WithAllowedHosts opts specific hosts in. Scheme downgrades are rejected on the same grounds.
  • Retries are enabled for safe methods (GET, HEAD, OPTIONS) on throttling and transient server errors. Mutating methods are not retried unless WithRetryUnsafeMethods(true) is set explicitly.
  • Retry sleeps honor Retry-After and are capped by WithMaxRetryDelay (30s by default).
  • Passing an io.Writer as out streams successful response bodies instead of buffering them first.
  • Params.Validate rejects negative Top/Skip with ErrInvalidParams instead of silently dropping them; the client validates on every request.
  • Request.IfMatch and Request.IfNoneMatch send conditional headers. Response.ETag carries the tag back, a matched conditional GET returns ErrNotModified, and a stale If-Match surfaces via IsPreconditionFailed.
  • APIError carries the status, service Code, Message, Target, Details, RequestID, and the parsed Retry-After. Classify with IsNotFound, IsUnauthorized, IsForbidden, IsThrottled, IsConflict, IsPreconditionFailed, IsStatus, or HasCode.
  • BatchStrict returns all batch responses plus a BatchError when any subrequest fails.
  • Pages/Items detect repeated nextLink values, and WithMaxPages can cap traversal. Page[T] also exposes @odata.count and @odata.deltaLink.
  • Delta drains a delta query and returns the changed items plus the deltaLink to persist as the sync position.
  • SuggestDelegatedScopes provides conservative delegated-scope hints for common raw Graph routes.
  • TranslateExchangeIDs wraps /me/translateExchangeIds and /users/{id}/translateExchangeIds for Exchange/REST/immutable ID conversion.

Develop

just ci

Manual live smoke test:

cd cmd/msgraph-live-smoke
go run .
go run . -show-messages # opt in to printing sender/subject metadata
go run . # second run should reuse the msauth-go token cache silently

Documentation

Overview

Package msgraph provides a small Microsoft Graph REST client.

The package-level generics are the type-safe entry points: Get, Post, Patch, Do, Collect, and First name the result type at the call site instead of passing it through an any out-parameter, and Pages, Items, and Delta iterate collections. The Client methods remain available for decoding into an existing value, streaming into an io.Writer, or ignoring the response body.

Build $filter values with the Expr combinators rather than by formatting strings, so literals stay escaped. See Params for how the typed and raw query forms combine.

Requests to hosts other than the base URL's are refused with ErrUntrustedHost, because every request carries a bearer token.

Index

Constants

View Source
const (
	// GraphResourceAppID is the Microsoft Graph service principal app ID.
	GraphResourceAppID = "00000003-0000-0000-c000-000000000000"

	// GraphCLIClientID is Microsoft's first-party Microsoft Graph Command Line
	// Tools public client app ID.
	GraphCLIClientID = "14d82eec-204b-4c2f-b7e8-296a70dab67e"

	MailReadDelegatedID      = "570282fd-fa5c-430d-a7fd-fc8dc98a9dca"
	MailReadWriteDelegatedID = "024d486e-b451-40bb-833d-3e66d98c5c73"
	MailSendDelegatedID      = "e383f46e-2787-4529-855e-0e479a3ffac0"
	UserReadDelegatedID      = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
)
View Source
const (
	ScopeUserRead         = "User.Read"
	ScopeUserReadBasicAll = "User.ReadBasic.All"
	ScopeUserReadWriteAll = "User.ReadWrite.All"
	ScopeMailRead         = "Mail.Read"
	ScopeMailReadWrite    = "Mail.ReadWrite"
	ScopeMailSend         = "Mail.Send"
	// ScopeMailboxSettingsRead is required to read mailbox settings, including
	// automatic replies. Mail.ReadWrite does not cover it, and the service
	// answers a request without it with a bare ErrorAccessDenied that names no
	// permission.
	ScopeMailboxSettingsRead = "MailboxSettings.Read"
	// ScopeMailboxSettingsReadWrite is required to change them.
	ScopeMailboxSettingsReadWrite = "MailboxSettings.ReadWrite"
	ScopeCalendarsRead            = "Calendars.Read"
	ScopeCalendarsReadWrite       = "Calendars.ReadWrite"
	ScopeTasksRead                = "Tasks.Read"
	ScopeTasksReadWrite           = "Tasks.ReadWrite"
	ScopeContactsRead             = "Contacts.Read"
	ScopeContactsReadWrite        = "Contacts.ReadWrite"
	ScopeFilesRead                = "Files.Read"
	ScopeFilesReadWrite           = "Files.ReadWrite"
	ScopeGroupReadAll             = "Group.Read.All"
	ScopeGroupReadWriteAll        = "Group.ReadWrite.All"
	ScopeDirectoryReadAll         = "Directory.Read.All"
)
View Source
const (
	// ScopeChatReadBasic lists chats and their members without reading what
	// was said in them.
	ScopeChatReadBasic = "Chat.ReadBasic"
	// ScopeChatRead reads chat messages.
	ScopeChatRead = "Chat.Read"
	// ScopeChatReadWrite reads chat messages and changes them: reactions and
	// soft deletes need it, and ChatMessage.Send does not cover either.
	ScopeChatReadWrite = "Chat.ReadWrite"
	// ScopeChatMessageSend sends a chat message and nothing else. It does not
	// permit reading the chat it posts to.
	ScopeChatMessageSend = "ChatMessage.Send"
	// ScopeTeamReadBasicAll lists the teams the signed-in user belongs to.
	ScopeTeamReadBasicAll = "Team.ReadBasic.All"
	// ScopeChannelReadBasicAll lists a team's channels. Team.ReadBasic.All
	// does not cover it: listing teams and listing their channels are
	// separate permissions.
	ScopeChannelReadBasicAll = "Channel.ReadBasic.All"
	// ScopeChannelMessageReadAll reads channel messages and their replies.
	ScopeChannelMessageReadAll = "ChannelMessage.Read.All"
	// ScopeChannelMessageSend posts to a channel. Unlike its read
	// counterpart it is user-consentable, so an app can be allowed to post
	// where it is not allowed to read.
	ScopeChannelMessageSend = "ChannelMessage.Send"
	// ScopePresenceRead reads the signed-in user's own presence.
	ScopePresenceRead = "Presence.Read"
	// ScopePresenceReadAll reads anyone's presence, and is a different
	// permission rather than a wider setting of the same one.
	ScopePresenceReadAll = "Presence.Read.All"
)

Microsoft Teams delegated permissions.

The chat scopes and the channel scopes are not two halves of one thing. A signed-in user can consent to every chat scope themselves; not one of the channel scopes can be granted without a tenant administrator, because they are tenant-wide by construction -- there is no delegated permission that reads only the channels you are in. That split is why AdminConsentRequired is set the way it is below, and it is the difference between a remedy a caller can act on and one they cannot.

View Source
const DefaultMaxPages = 10_000

DefaultMaxPages bounds a traversal that does not set its own limit.

Detecting a repeated nextLink only catches a server that loops back on itself. One that returns a fresh link every time is followed forever, and the set of seen links grows without bound while it happens. At the largest page size Graph serves this still allows several million items, far past any real collection.

View Source
const MaxBatchRequests = 20

MaxBatchRequests is the most subrequests Graph accepts in one JSON batch. Callers chunking a large operation need it, and hard-coding twenty in every caller is how the number drifts.

Variables

View Source
var (
	ErrPageCycle        = errors.New("msgraph: repeated nextLink")
	ErrMaxPagesExceeded = errors.New("msgraph: max pages exceeded")
)

Pagination errors returned by Pages and Items.

View Source
var CommonPermissions = []Permission{
	{
		Name:                    ScopeMailRead,
		ID:                      MailReadDelegatedID,
		Kind:                    "delegated",
		PersonalAccountsAllowed: true,
	},
	{
		Name:                    ScopeMailReadWrite,
		ID:                      MailReadWriteDelegatedID,
		Kind:                    "delegated",
		PersonalAccountsAllowed: true,
	},
	{
		Name:                    ScopeMailSend,
		ID:                      MailSendDelegatedID,
		Kind:                    "delegated",
		PersonalAccountsAllowed: true,
	},
	{
		Name:                    ScopeUserRead,
		ID:                      UserReadDelegatedID,
		Kind:                    "delegated",
		PersonalAccountsAllowed: true,
	},
	{Name: ScopeUserReadBasicAll, Kind: "delegated"},
	{Name: ScopeUserReadWriteAll, Kind: "delegated", AdminConsentRequired: true},
	{Name: ScopeMailboxSettingsRead, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeMailboxSettingsReadWrite, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeCalendarsRead, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeCalendarsReadWrite, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeTasksRead, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeTasksReadWrite, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeContactsRead, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeContactsReadWrite, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeFilesRead, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeFilesReadWrite, Kind: "delegated", PersonalAccountsAllowed: true},
	{Name: ScopeGroupReadAll, Kind: "delegated", AdminConsentRequired: true},
	{Name: ScopeGroupReadWriteAll, Kind: "delegated", AdminConsentRequired: true},
	{Name: ScopeDirectoryReadAll, Kind: "delegated", AdminConsentRequired: true},

	{Name: ScopeChatReadBasic, Kind: "delegated"},
	{Name: ScopeChatRead, Kind: "delegated"},
	{Name: ScopeChatReadWrite, Kind: "delegated"},
	{Name: ScopeChatMessageSend, Kind: "delegated"},
	{Name: ScopeTeamReadBasicAll, Kind: "delegated", AdminConsentRequired: true},
	{Name: ScopeChannelReadBasicAll, Kind: "delegated", AdminConsentRequired: true},
	{Name: ScopeChannelMessageReadAll, Kind: "delegated", AdminConsentRequired: true},
	{Name: ScopeChannelMessageSend, Kind: "delegated"},
	{Name: ScopePresenceRead, Kind: "delegated"},
	{Name: ScopePresenceReadAll, Kind: "delegated", AdminConsentRequired: true},
}

CommonPermissions is a hand-curated seed set. The full catalog should be generated from the Graph service principal in a later package.

View Source
var ErrInvalidParams = errors.New("msgraph: invalid query parameters")

ErrInvalidParams is wrapped by Params.Validate failures.

View Source
var ErrNotModified = errors.New("msgraph: not modified")

ErrNotModified is returned when a conditional request matched and Graph answered 304. It is an outcome rather than a failure: the accompanying Response is still valid, and no body was sent.

View Source
var ErrRequestFailed = errors.New("msgraph: request failed")

ErrRequestFailed is wrapped by APIError for non-2xx Graph responses.

View Source
var ErrResponseTooLarge = errors.New("msgraph: response body too large")

ErrResponseTooLarge is returned when a response body exceeds the configured limit. Streaming into an io.Writer is not subject to it: a caller that asks to stream has taken responsibility for the size.

View Source
var ErrUntrustedHost = errors.New("msgraph: untrusted request host")

ErrUntrustedHost is returned when a request URL resolves to a host that is neither the Graph base host nor an explicitly allowed host. Every request carries a bearer token, so an off-origin URL — a spoofed @odata.nextLink, a caller-supplied link, a redirect target written into a response body — would hand that token to a third party. Refusing the request is the only safe behavior; use WithAllowedHosts to opt specific hosts in.

Functions

func Collect added in v0.3.0

func Collect[T any](ctx context.Context, client *Client, path string, params Params, opts ...PageOption) ([]T, error)

Collect drains a Graph collection into a slice. It is the eager counterpart to Items; prefer the iterator for large collections.

func ContainsRaw added in v0.4.0

func ContainsRaw(filter Expr) bool

ContainsRaw reports whether a filter includes a Raw fragment, whose referenced properties cannot be determined.

func Delta added in v0.3.0

func Delta[T any](
	ctx context.Context,
	client *Client,
	path string,
	params Params,
	opts ...PageOption,
) (items []T, deltaLink string, err error)

Delta drains a Microsoft Graph delta query and returns the changed items together with the deltaLink from the final page.

Pass a resource path such as "/me/mailFolders/inbox/messages/delta" for the initial full sync, then pass the returned deltaLink back on later calls to receive only what changed. Unlike Items, delta results have to be drained eagerly: the deltaLink only arrives on the last page, and skipping it would lose the sync position.

func Field added in v0.3.0

func Field(segments ...string) string

Field joins property segments into an OData path such as "from/emailAddress/address".

func FieldsOf added in v0.4.0

func FieldsOf(filter Expr) []string

FieldsOf reports the property paths a filter references, walking the expression tree rather than parsing the rendered string. Callers use it to decide whether a service will accept a filter — Microsoft Graph, for instance, restricts which properties may be filtered while sorting.

A Raw fragment is opaque and contributes no fields, so a filter containing one cannot be fully analyzed; ContainsRaw reports that case.

func First added in v0.3.0

func First[T any](ctx context.Context, client *Client, path string, params Params) (value T, found bool, err error)

First returns the first item of a collection. found reports whether the collection had any items, distinguishing "no matches" from a zero-valued first item.

func HasCode added in v0.3.0

func HasCode(err error, code string) bool

HasCode reports whether err is a Graph error carrying the given service error code, such as "ErrorItemNotFound". Comparison is case-insensitive because Graph is not consistent about casing across workloads.

func IsConflict added in v0.3.0

func IsConflict(err error) bool

IsConflict reports whether err is a Graph 409 response.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden reports whether err is a Graph 403 response.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a Graph 404 response.

func IsNotModified added in v0.3.0

func IsNotModified(err error) bool

IsNotModified reports whether err is ErrNotModified.

func IsPreconditionFailed added in v0.3.0

func IsPreconditionFailed(err error) bool

IsPreconditionFailed reports whether err is a Graph 412 response, which is how a stale Request.IfMatch ETag surfaces.

func IsStatus

func IsStatus(err error, statusCode int) bool

IsStatus reports whether err is a Graph response with statusCode.

func IsThrottled

func IsThrottled(err error) bool

IsThrottled reports whether err is a Graph throttling response.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err is a Graph 401 response.

func Items

func Items[T any](ctx context.Context, client *Client, path string, params Params, opts ...PageOption) iter.Seq2[T, error]

Items returns an iterator over every item in a Graph collection.

func LiteralOf added in v0.3.0

func LiteralOf[T Literal](value T) string

LiteralOf renders a Go value as an OData literal.

func MergeScopes added in v0.2.0

func MergeScopes(values ...[]string) []string

MergeScopes merges scope slices, preserving first-seen spelling and order.

func Pages

func Pages[T any](ctx context.Context, client *Client, path string, params Params, opts ...PageOption) iter.Seq2[Page[T], error]

Pages returns an iterator over Graph collection pages.

func ScopeSatisfies added in v0.8.0

func ScopeSatisfies(granted []string, required string) bool

ScopeSatisfies reports whether the granted permissions cover the required one, accounting for a broader permission standing in for a narrower one.

Comparison is case-insensitive: the service echoes granted scopes back with inconsistent casing, and treating a case difference as a missing permission is a confusing way to fail.

Types

type APIError

type APIError struct {
	StatusCode int
	Code       string
	Message    string
	// Target names the request property the error is about, when Graph says.
	Target string
	// Details holds per-property errors for multi-part validation failures.
	Details   []ErrorDetail
	RequestID string
	// RetryAfter is the parsed Retry-After header. It is zero when absent,
	// and is only meaningful for throttling and transient server errors.
	RetryAfter time.Duration
	Raw        []byte
}

APIError is a structured Microsoft Graph error response.

func ErrorFromBatch added in v0.6.2

func ErrorFromBatch(response BatchResponse) *APIError

ErrorFromBatch builds an APIError from a failed subresponse, so a caller splitting a bulk operation across batches can classify each failure with the same helpers it uses for an ordinary request.

It returns nil for a successful subresponse.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

type BatchError

type BatchError struct {
	Responses []BatchResponse
}

BatchError reports one or more failed subresponses from a strict batch call.

func (*BatchError) Error

func (e *BatchError) Error() string

type BatchRequest

type BatchRequest struct {
	ID      string            `json:"id"`
	Method  string            `json:"method"`
	URL     string            `json:"url"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    any               `json:"body,omitempty"`
}

BatchRequest is one request inside a Microsoft Graph JSON batch.

type BatchResponse

type BatchResponse struct {
	ID      string            `json:"id"`
	Status  int               `json:"status"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    json.RawMessage   `json:"body,omitempty"`
}

BatchResponse is one response inside a Microsoft Graph JSON batch.

func FailedBatchResponses

func FailedBatchResponses(responses []BatchResponse) []BatchResponse

FailedBatchResponses returns every subresponse with a non-2xx status.

type Client

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

Client is a small Microsoft Graph REST client.

func New

func New(token TokenSource, opts ...Option) (*Client, error)

New creates a Microsoft Graph client.

func (*Client) Batch

func (c *Client) Batch(ctx context.Context, requests []BatchRequest) ([]BatchResponse, error)

Batch sends a Microsoft Graph JSON batch request.

func (*Client) BatchStrict

func (c *Client) BatchStrict(ctx context.Context, requests []BatchRequest) ([]BatchResponse, error)

BatchStrict sends a JSON batch and returns a BatchError when any subrequest status is outside 2xx. The full response slice is still returned so callers can inspect successes and failures together.

func (*Client) Delete

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

Delete sends a Graph DELETE request.

func (*Client) Do

func (c *Client) Do(ctx context.Context, req Request, out any) (*Response, error)

Do sends req and decodes a successful JSON response into out. If out is an io.Writer, the response body is streamed into it instead.

func (*Client) Get

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

Get sends a Graph GET request.

func (*Client) Patch

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

Patch sends a Graph PATCH request.

func (*Client) Post

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

Post sends a Graph POST request.

func (*Client) TranslateExchangeIDs added in v0.2.0

func (c *Client) TranslateExchangeIDs(
	ctx context.Context,
	user string,
	inputIDs []string,
	sourceIDType ExchangeIDFormat,
	targetIDType ExchangeIDFormat,
) ([]TranslatedExchangeID, *Response, error)

TranslateExchangeIDs converts Exchange-family identifiers for the signed-in user ("", "me") or the supplied user ID / UPN.

type ConsistencyLevel added in v0.3.0

type ConsistencyLevel string

ConsistencyLevel is a value for the ConsistencyLevel request header.

const ConsistencyLevelEventual ConsistencyLevel = "eventual"

ConsistencyLevelEventual enables Graph advanced query capabilities where endpoints require the ConsistencyLevel header. It generally has to be paired with Params.Count.

type ErrorDetail added in v0.3.0

type ErrorDetail struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Target  string `json:"target"`
}

ErrorDetail is one entry from a Graph error's details array.

type ExchangeIDFormat added in v0.2.0

type ExchangeIDFormat string

ExchangeIDFormat is a Microsoft Graph exchangeIdFormat value.

const (
	ExchangeIDFormatEntryID              ExchangeIDFormat = "entryId"
	ExchangeIDFormatEWSID                ExchangeIDFormat = "ewsId"
	ExchangeIDFormatImmutableEntryID     ExchangeIDFormat = "immutableEntryId"
	ExchangeIDFormatRESTID               ExchangeIDFormat = "restId"
	ExchangeIDFormatRESTImmutableEntryID ExchangeIDFormat = "restImmutableEntryId"
)

type Expr added in v0.3.0

type Expr interface {
	// ODataFilter renders the expression as a $filter value. The result is not
	// URL-encoded; [Params] handles that.
	ODataFilter() string
	// contains filtered or unexported methods
}

Expr is a typed OData filter expression. Building filters through Expr instead of formatting strings keeps literals escaped: a value containing an apostrophe, such as the display name O'Brien, would otherwise terminate the OData string literal early and change the meaning of the query.

func All added in v0.3.0

func All(collection, variable string, predicate Expr) Expr

All builds a collection lambda using the "all" operator.

func And added in v0.3.0

func And(operands ...Expr) Expr

And joins operands with "and", skipping nil operands.

func Any added in v0.3.0

func Any(collection, variable string, predicate Expr) Expr

Any builds a collection lambda such as "toRecipients/any(r:r/emailAddress/address eq 'a@b.com')". A nil predicate yields nil.

func Contains added in v0.3.0

func Contains(field, value string) Expr

Contains builds "contains(field,'value')".

func EndsWith added in v0.3.0

func EndsWith(field, value string) Expr

EndsWith builds "endswith(field,'value')".

func Eq added in v0.3.0

func Eq[T Literal](field string, value T) Expr

Eq builds "field eq value".

func Ge added in v0.3.0

func Ge[T Literal](field string, value T) Expr

Ge builds "field ge value".

func Gt added in v0.3.0

func Gt[T Literal](field string, value T) Expr

Gt builds "field gt value".

func In added in v0.3.0

func In[T Literal](field string, values ...T) Expr

In builds "field in (a,b,c)". It returns nil when no values are supplied, because an empty OData "in" list is a syntax error rather than a filter that matches nothing.

func IsNotNull added in v0.3.0

func IsNotNull(field string) Expr

IsNotNull builds "field ne null".

func IsNull added in v0.3.0

func IsNull(field string) Expr

IsNull builds "field eq null".

func Le added in v0.3.0

func Le[T Literal](field string, value T) Expr

Le builds "field le value".

func Lt added in v0.3.0

func Lt[T Literal](field string, value T) Expr

Lt builds "field lt value".

func Ne added in v0.3.0

func Ne[T Literal](field string, value T) Expr

Ne builds "field ne value".

func Not added in v0.3.0

func Not(operand Expr) Expr

Not negates operand. A nil operand yields nil.

func Or added in v0.3.0

func Or(operands ...Expr) Expr

Or joins operands with "or", skipping nil operands.

func Raw added in v0.3.0

func Raw(filter string) Expr

Raw wraps a hand-written OData fragment. It is the escape hatch for expressions this package does not model; the caller owns escaping.

func StartsWith added in v0.3.0

func StartsWith(field, value string) Expr

StartsWith builds "startswith(field,'value')".

type Literal added in v0.3.0

type Literal interface {
	string | bool |
		int | int8 | int16 | int32 | int64 |
		uint | uint8 | uint16 | uint32 | uint64 |
		float32 | float64 |
		time.Time
}

Literal constrains the Go types that have an unambiguous OData literal encoding. Named types are excluded on purpose so the encoding stays a total function over the listed set.

type Option

type Option func(*Client) error

Option configures a Client.

func WithAllowedHosts added in v0.3.0

func WithAllowedHosts(hosts ...string) Option

WithAllowedHosts permits absolute request URLs on additional hosts. The WithBaseURL host is always allowed. Use this only for hosts that should legitimately receive the client's bearer token, such as a sovereign-cloud Graph endpoint the caller also talks to directly. Empty values are ignored.

func WithBaseURL

func WithBaseURL(value string) Option

WithBaseURL sets the Graph base URL. It is mostly useful for tests and sovereign clouds.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets the HTTP client. A nil client is ignored.

func WithMaxResponseBytes added in v0.6.0

func WithMaxResponseBytes(limit int64) Option

WithMaxResponseBytes bounds the size of a response the client will decode. A non-positive value removes the limit.

The limit does not apply when decoding into an io.Writer, because streaming is the supported way to receive something genuinely large.

func WithMaxRetries

func WithMaxRetries(value int) Option

WithMaxRetries sets the retry count for throttling and transient server errors. Negative values are clamped to zero.

func WithMaxRetryDelay

func WithMaxRetryDelay(value time.Duration) Option

WithMaxRetryDelay caps retry sleeps, including Retry-After values. A non-positive value disables the cap.

func WithRetryUnsafeMethods

func WithRetryUnsafeMethods(value bool) Option

WithRetryUnsafeMethods allows retries for non-idempotent HTTP methods. It is disabled by default because Graph mutations can have side effects before a transient response or connection failure is observed by the client.

func WithSleeper

func WithSleeper(fn sleepFunc) Option

WithSleeper overrides retry sleeping. It exists for tests.

func WithUserAgent

func WithUserAgent(value string) Option

WithUserAgent sets the User-Agent header.

type Order added in v0.3.0

type Order struct {
	Field      string
	Descending bool
}

Order is one $orderby term.

func Asc added in v0.3.0

func Asc(field string) Order

Asc sorts by field ascending.

func Desc added in v0.3.0

func Desc(field string) Order

Desc sorts by field descending.

func (Order) String added in v0.3.0

func (o Order) String() string

String renders the term as an OData $orderby clause.

type Page

type Page[T any] struct {
	Value    []T    `json:"value"`
	NextLink string `json:"@odata.nextLink,omitempty"`
	// DeltaLink is present on the final page of a delta query. Persist it and
	// pass it back to [Deltas] to fetch only what changed since.
	DeltaLink string `json:"@odata.deltaLink,omitempty"`
	// Count is the total matching item count, populated only when the request
	// set [Params.Count] and the endpoint supports it.
	Count *int64 `json:"@odata.count,omitempty"`
}

Page is the standard Microsoft Graph collection response shape.

type PageOption

type PageOption func(*pageConfig)

PageOption configures collection iteration.

func WithMaxPages

func WithMaxPages(maxPages int) PageOption

WithMaxPages stops iteration after max pages, replacing DefaultMaxPages. A non-positive value removes the bound entirely, which is only safe when the server is trusted to terminate.

type Params

type Params struct {
	Select []string
	Expand []string

	// Sort is the typed $orderby. Terms are emitted before OrderBy.
	Sort []Order
	// OrderBy is the raw $orderby, for expressions Sort cannot express.
	OrderBy []string

	// Where is the typed $filter. A nil Where emits no $filter.
	Where Expr
	// Filter is the raw $filter. When both are set they are combined with
	// "and", so a caller can narrow a typed filter with a raw fragment.
	Filter string

	Search string
	Top    int
	Skip   int
	// Count requests $count=true. Most collections also require
	// [ConsistencyLevelEventual] on the request for it to be accepted.
	Count bool
	// SkipToken sets $skiptoken directly. Prefer following @odata.nextLink.
	SkipToken string
	Custom    url.Values
}

Params holds common OData query parameters used by Microsoft Graph.

Each parameter has a typed form and a raw string form. Prefer the typed form: Params.Where escapes literals, while Params.Filter is passed through verbatim and makes the caller responsible for escaping.

func (Params) Validate added in v0.3.0

func (p Params) Validate() error

Validate reports whether p can be rendered as a Graph query string.

func (Params) Values

func (p Params) Values() url.Values

Values renders p as URL query values. Invalid values are dropped; call Params.Validate to detect them instead.

type Permission

type Permission struct {
	Name                    string
	ID                      string
	Kind                    string
	AdminConsentRequired    bool
	PersonalAccountsAllowed bool
}

Permission describes a known Microsoft Graph permission.

func FindPermission

func FindPermission(nameOrID string) (Permission, bool)

FindPermission returns a known permission by name or ID.

type PermissionSuggestion added in v0.2.0

type PermissionSuggestion struct {
	Method string
	Path   string
	Match  string
	Scopes []string
	Notes  []string
}

PermissionSuggestion describes delegated scopes likely needed for a raw Graph request. It is a conservative route-pattern hint, not a substitute for the endpoint-specific Microsoft documentation.

func SuggestDelegatedScopes added in v0.2.0

func SuggestDelegatedScopes(method, rawPath string) PermissionSuggestion

SuggestDelegatedScopes returns a least-privilege delegated-scope hint for common Microsoft Graph routes. Unknown routes return an empty scope list.

type PreferDirective added in v0.3.0

type PreferDirective string

PreferDirective is a single value for the Prefer request header. The named constants cover the directives this client is most often used with; any other directive can be spelled out with a conversion.

const (
	// PreferIDTypeImmutableID asks Graph to return IDs that survive moves.
	PreferIDTypeImmutableID PreferDirective = `IdType="ImmutableId"`

	// PreferBodyContentTypeText asks Outlook mail endpoints to return text bodies.
	PreferBodyContentTypeText PreferDirective = `outlook.body-content-type="text"`

	// PreferBodyContentTypeHTML asks Outlook mail endpoints to return HTML bodies.
	PreferBodyContentTypeHTML PreferDirective = `outlook.body-content-type="html"`

	// PreferReturnMinimal asks Graph to omit the resource from a write response.
	PreferReturnMinimal PreferDirective = "return=minimal"

	// PreferReturnRepresentation asks Graph to echo the written resource back.
	PreferReturnRepresentation PreferDirective = "return=representation"

	// PreferIncludeUnknownEnumMembers asks Graph to name evolvable enum
	// members added since the caller's version, instead of substituting
	// unknownFutureValue for them.
	//
	// Without it the substitution is silent and the response is otherwise
	// complete, so the cost is not an error but a value the caller cannot
	// switch on. Teams message types are the sharp case: every system event
	// message -- a member joining, a chat renamed, a call ending -- arrives
	// as unknownFutureValue, which in a real conversation is a large minority
	// of the history.
	PreferIncludeUnknownEnumMembers PreferDirective = "include-unknown-enum-members"
)

func BodyContentTypePreference

func BodyContentTypePreference(contentType string) PreferDirective

BodyContentTypePreference returns an Outlook body-content Prefer directive.

func MaxPageSizePreference added in v0.3.0

func MaxPageSizePreference(size int) PreferDirective

MaxPageSizePreference returns a Prefer directive capping server page size. Graph uses it on endpoints that ignore $top, such as mail delta queries.

func TimeZonePreference added in v0.3.0

func TimeZonePreference(timeZone string) PreferDirective

TimeZonePreference returns a Prefer directive asking Outlook to render date and time properties in the named Windows or IANA time zone.

type Request

type Request struct {
	Method           string
	URL              string
	Params           Params
	Query            url.Values
	Header           http.Header
	Prefer           []PreferDirective
	ConsistencyLevel ConsistencyLevel
	// IfMatch sends an If-Match header. Use it with an ETag from a prior
	// [Response] to make an update or delete conditional on the resource not
	// having changed; Graph answers a stale ETag with 412.
	IfMatch string
	// IfNoneMatch sends an If-None-Match header. Graph answers an unchanged
	// resource with 304, which surfaces as [ErrNotModified].
	IfNoneMatch string
	Body        any
	RawBody     []byte
	ContentType string
}

Request describes a Microsoft Graph request.

type Response

type Response struct {
	StatusCode int
	Header     http.Header
	RequestID  string
	// ETag is the response ETag with quoting preserved, ready to be handed
	// back as [Request.IfMatch].
	ETag string
}

Response contains metadata from a successful Graph response.

func Delete added in v0.3.0

func Delete(ctx context.Context, client *Client, path string, params Params) (*Response, error)

Delete sends a DELETE and discards any response body.

func Do added in v0.3.0

func Do[T any](ctx context.Context, client *Client, req Request) (T, *Response, error)

Do sends req and decodes the response into T. On error the returned T is the zero value.

func Get added in v0.3.0

func Get[T any](ctx context.Context, client *Client, path string, params Params) (T, *Response, error)

Get sends a GET and decodes the response into T.

func Patch added in v0.3.0

func Patch[T any](ctx context.Context, client *Client, path string, params Params, body any) (T, *Response, error)

Patch sends a PATCH with body and decodes the response into T.

func Post added in v0.3.0

func Post[T any](ctx context.Context, client *Client, path string, params Params, body any) (T, *Response, error)

Post sends a POST with body and decodes the response into T. Use PostNoContent for endpoints such as /sendMail that answer 202 with no body.

func PostNoContent added in v0.3.0

func PostNoContent(ctx context.Context, client *Client, path string, params Params, body any) (*Response, error)

PostNoContent sends a POST and discards any response body.

type TokenSource

type TokenSource interface {
	Token(ctx context.Context) (string, error)
}

TokenSource returns a Microsoft Graph access token. Implementations should refresh tokens before returning expired credentials.

type TokenSourceFunc

type TokenSourceFunc func(ctx context.Context) (string, error)

TokenSourceFunc adapts a function into a TokenSource.

func (TokenSourceFunc) Token

func (fn TokenSourceFunc) Token(ctx context.Context) (string, error)

Token returns fn(ctx).

type TranslateExchangeIDsRequest added in v0.2.0

type TranslateExchangeIDsRequest struct {
	InputIDs     []string         `json:"inputIds"`
	SourceIDType ExchangeIDFormat `json:"sourceIdType"`
	TargetIDType ExchangeIDFormat `json:"targetIdType"`
}

TranslateExchangeIDsRequest is the body accepted by /me/translateExchangeIds and /users/{id}/translateExchangeIds.

type TranslateExchangeIDsResponse added in v0.2.0

type TranslateExchangeIDsResponse struct {
	Value []TranslatedExchangeID `json:"value"`
}

TranslateExchangeIDsResponse is the response from translateExchangeIds.

type TranslatedExchangeID added in v0.2.0

type TranslatedExchangeID struct {
	SourceID string `json:"sourceId"`
	TargetID string `json:"targetId"`
}

TranslatedExchangeID is one translated Exchange identifier.

Directories

Path Synopsis
Package graphmock provides a typed fake Microsoft Graph endpoint for tests.
Package graphmock provides a typed fake Microsoft Graph endpoint for tests.

Jump to

Keyboard shortcuts

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