leaflow

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package leaflow is the platform as a Go library: the contracts, the argument checking and the requests, with no opinion about who is calling.

The command line is one caller and holds no privileged position. It adds a shell's half — flags, positional arguments, a terminal to print to — on top of what is here, and so does the MCP server, and so can a service. None of them restates what an operation is or what it accepts; that is stated once, by the contract, and read once, here.

Loading it once

Reading two hundred operations out of seven contracts costs about forty milliseconds and thirty megabytes of garbage. A command line pays that once and exits. A service must not pay it per request — which is the whole reason this package exists separately from the binary — so Client holds the parsed contracts and is safe to share across goroutines for the life of a process.

Credentials belong to the call, not to the client

A command line serves one person and can keep their token in a keychain. A service serves many at once and is handed one per request, so a token is a field on Call rather than state on Client. A Client can carry a default for the single-tenant case, and a call that names its own always wins.

Operations returns the whole list. Deciding which of two hundred operations belongs in a prompt is retrieval, and a caller that feeds a model already has its own — a second one here would only be the one that disagrees with it. What this package owes such a caller is the complete list and an exact schema for each entry, both of which come from the contracts rather than from a guess about relevance.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoSuchService = errors.New("no such service")

	ErrNoSuchOperation = errors.New("no such operation")
)
View Source
var (
	// ErrUnauthenticated means the identity was never established.
	ErrUnauthenticated = transport.ErrUnauthenticated

	// ErrTokenExpired is the one worth retrying: the credential was good and is
	// not any more. It also satisfies ErrUnauthenticated.
	ErrTokenExpired = transport.ErrTokenExpired

	// ErrPermissionDenied means the identity is fine and not allowed.
	ErrPermissionDenied = transport.ErrPermissionDenied

	ErrNotFound = transport.ErrNotFound

	// ErrInvalidArgument covers both the contract refusing arguments here and
	// the service refusing them there.
	ErrInvalidArgument = transport.ErrInvalidArgument

	ErrConflict = transport.ErrConflict

	ErrRateLimited = transport.ErrRateLimited

	// ErrUnavailable is the service failing rather than refusing, including a
	// request that never got an answer at all.
	ErrUnavailable = transport.ErrUnavailable
)

What a failure can be, reached with errors.Is.

A caller has to be able to tell these apart without reading prose: the wording of a refusal changes between releases, and matching on it is how a program starts behaving differently after a deployment nobody told it about. Three of these call for opposite responses — get another token, give up, wait and try again — so collapsing them into "the call failed" throws away the only part that decides what happens next.

errors.Is(err, leaflow.ErrTokenExpired)      // mint a fresh one, call again
errors.Is(err, leaflow.ErrPermissionDenied)  // no retry will help
errors.Is(err, leaflow.ErrInvalidArgument)   // the arguments were wrong

They are aliases of the transport's, so a caller that reaches one through either package is asking the same question.

View Source
var (
	// ErrNoToken means the call carried no credential and the client had no
	// default. Distinct from a rejected token: nothing was sent.
	ErrNoToken = errors.New("no token for this call")
)
View Source
var ErrUnknownArgument = errors.New("unknown argument")

Functions

func CanRetry

func CanRetry(err error) bool

CanRetry reports whether the same call, made again, could succeed.

An expired token can be replaced and a service that is down can come back; a refusal about permissions or arguments will be the same refusal every time. Saying so here means each caller does not have to work it out from the kinds, and does not have to be updated when a new one is added.

func Code

func Code(err error) string

Code returns the service's own error code, which is the part of a refusal that is contract. Empty when the failure did not come from the service or carried no code.

func Contracts

func Contracts() (*spec.Set, error)

Contracts returns the parsed contracts. Most callers want New instead; this is for a caller that needs the raw OpenAPI documents.

func Problems

func Problems(err error) []string

Problems returns the individual complaints behind a failed call, when the failure was about the arguments.

The contract checks every argument before anything is sent and reports all of them at once, because a caller fixing three fields in three round trips is three round trips slower — and where the caller is a model, each of those is a turn. Nil for any other kind of failure.

func RenderError

func RenderError(err error, root string) string

RenderError renders a failure as the XML a model reads, matching what Result.XML does for a reply.

Here rather than in whichever surface happens to need it, so that a caller not going through MCP gets the same rendering — and so that the day APIError grows a field, every caller grows it too instead of one of them quietly falling behind.

Returns text in every case: a caller handling a failure should not have to handle a second one from the attempt to describe the first.

func Status

func Status(err error) int

Status returns the HTTP status a refusal came back with, or zero when the failure never reached the service.

Types

type Call

type Call struct {
	// Service and Operation name the operation, as Services and Operations
	// report them. Both the contract's operationId and its command-line
	// spelling are accepted, because those are the two names in circulation.
	Service string

	Operation string

	// Arguments are shaped the way Operation.Schema says: path and query
	// parameters at the top level under the contract's own names, a request body
	// whole under "body".
	//
	// They are checked against the contract before anything is sent, so a call
	// that satisfies the schema is a call that will be tried.
	Arguments map[string]any

	// Token is this caller's credentials, overriding the client's default. In a
	// service this is where the request's own user arrives.
	//
	// It is a token and nothing more, so a refused one cannot be replaced: see
	// Credentials for the case where it can.
	Token Token

	// Credentials replaces Token for a caller whose tokens expire sooner than
	// the work outlives them.
	//
	// An access token is good for a day. A piece of work that waits on a human
	// can be paused for longer than that, and when it resumes the first request
	// it makes is the one that gets a 401. The transport already retries that
	// exactly once, after asking the credentials to drop what was refused — but
	// a Token has nothing else to offer and says so, which turns the retry off.
	//
	// Supplying something that can mint a fresh token turns it back on. The
	// alternative is that the caller's own retry has to know which errors mean
	// "expired", which is the knowledge this package exists to hold.
	Credentials transport.Credentials
}

Call is one request to make.

type Client

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

Client is the platform, loaded once.

Safe for concurrent use: the contracts are read at construction and never written afterwards, and everything that varies per call travels in Call.

func New

func New(opts Options) (*Client, error)

func (*Client) AccessTokenOnly

func (c *Client) AccessTokenOnly() bool

AccessTokenOnly reports whether this client was built without the account face, leaving only what an access token can call.

func (*Client) Call

func (c *Client) Call(ctx context.Context, call Call) (*Result, error)

Call checks the arguments against the contract and makes the request.

func (*Client) Count

func (c *Client) Count() int

Count is how many operations this client exposes.

func (*Client) Operation

func (c *Client) Operation(service, id string) (*Operation, error)

Operation finds one by service and id.

A refusal names the near misses, so a caller that guessed can correct itself without listing everything again.

func (*Client) Operations

func (c *Client) Operations() []*Operation

Operations lists everything this client exposes, in the contract's order.

There is deliberately no search here. A caller that feeds these to a model has its own retrieval — that is a property of how it builds prompts, not of what the platform offers — and a second, worse search living in this library would only be the one that disagrees with it. What this package owes such a caller is the whole list and an exact schema for each entry.

Narrowing that is not retrieval belongs in Options: Services picks the contracts and ReadOnly drops the writes, both before anything is listed.

func (*Client) OperationsIn

func (c *Client) OperationsIn(service string) []*Operation

OperationsIn lists one service's, for a caller that walks them a contract at a time rather than holding all two hundred.

func (*Client) ReadOnly

func (c *Client) ReadOnly() bool

ReadOnly reports whether this client was built to refuse writes.

func (*Client) Services

func (c *Client) Services() []Service

Services lists what this client exposes.

type Group

type Group struct {
	Name string

	Description string

	Operations int
}

type Operation

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

Operation is one contract operation: what it accepts, and how a call against it becomes a request.

The parameter split is the contract's, taken from spec.Inputs — the same split the command tree works from. Which parameters live in the path, in what order, and which are query are facts about the contract, and deriving them twice is how two surfaces start disagreeing about one operation.

func (*Operation) AccountToken

func (o *Operation) AccountToken() bool

AccountToken reports that this operation takes an account token rather than an access token. It is the one thing about a call that is not in its arguments.

func (*Operation) Command

func (o *Operation) Command() string

Command is the equivalent command line, which is what lets an assistant tell someone how to do the same thing themselves.

func (*Operation) Deprecated

func (o *Operation) Deprecated() bool

func (*Operation) Details

func (o *Operation) Details() string

func (*Operation) Group

func (o *Operation) Group() string

Group is the resource group the contract files this under.

func (*Operation) Method

func (o *Operation) Method() string

func (*Operation) Name

func (o *Operation) Name() string

Name is the operationId, which is the operation's one identifier: the command line names a command after it, the SDKs generate a method name from it, and a tool takes its name from it.

func (*Operation) Path

func (o *Operation) Path() string

func (*Operation) ReadOnly

func (o *Operation) ReadOnly() bool

ReadOnly reports whether the operation changes anything, read off the method — which is where HTTP states it. Nothing is inferred from the name: `POST /instances/{id}/actions` is a write however it is spelled.

func (*Operation) Request

func (o *Operation) Request(arguments map[string]any) (*transport.Request, error)

request turns a call's arguments into the request to send.

Every problem is reported at once rather than one per attempt: a caller that has to fix three fields in three round trips is three round trips slower, and on this surface each of those is a model turn.

func (*Operation) Schema

func (o *Operation) Schema() map[string]any

inputSchema states everything the operation accepts, in one object.

Path and query parameters sit at the top level under the contract's own names; the request body arrives whole, with its nesting intact. The command line has to flatten a body into one flag per field because a shell has no way to type an object — here the argument is already JSON, so the contract's schema is handed over as it stands and nothing has to be reassembled.

func (*Operation) Service

func (o *Operation) Service() string

func (*Operation) Summary

func (o *Operation) Summary() string

type Options

type Options struct {
	// Services limits the catalogue to these contracts. Empty means all of them.
	//
	// An unknown name is an error rather than an empty selection: a service that
	// exposes nothing looks identical to a working one until the first call.
	Services []string

	// ReadOnly drops every operation that is not a GET before any caller can see
	// it. It is how an assistant is handed the platform without being handed the
	// ability to change it.
	ReadOnly bool

	// AccessTokenOnly drops every operation that takes an account token, leaving
	// the ones an access token can call.
	//
	// The account face — registering, listing projects, minting project tokens —
	// only accepts a token that comes from a person's sign-in session, which a
	// service acting on someone's behalf does not have. Left in, those
	// operations are ones a model can find, call, and be refused by, with
	// nothing in the refusal explaining that they were never available.
	//
	// Naming the services to exclude would do the same thing today and would be
	// wrong the day the platform adds another one — silently, because a list of
	// names cannot notice that it is short. The contract states which token each
	// operation takes, so that is what this reads.
	AccessTokenOnly bool

	// Endpoints points at another deployment. The zero value uses the addresses
	// the contracts declare.
	Endpoints transport.Endpoints

	// Credentials is where tokens come from when a Call does not carry its own.
	//
	// A service leaves this nil and puts a Token on every Call, because every
	// call is a different person. A command line supplies its token manager here
	// instead: it serves one person, and it can renew and exchange, which a bare
	// token cannot. A Token satisfies this interface, so a single-tenant caller
	// can simply pass one.
	Credentials transport.Credentials

	// HTTP is the client requests go out on. Supply one to control timeouts,
	// proxies or tracing; nil gets a sixty-second default.
	HTTP *http.Client
}

Options configures a Client. The zero value talks to the hosted platform with every contract exposed.

type Result

type Result struct {
	// Status is the HTTP status. Worth having even on success: a 204 carries no
	// body, and "deleted" is otherwise indistinguishable from "returned nothing".
	Status int

	// Value is the decoded reply — map[string]any, []any or a scalar — or nil
	// when the operation returned no body.
	Value any
}

Result is what came back.

func (*Result) JSON

func (r *Result) JSON() (string, error)

JSON renders the reply as JSON, with fields in the same order every other format uses.

func (*Result) XML

func (r *Result) XML(root string) (string, error)

XML renders the reply as tagged text, which is the shape to hand a model.

See pkg/output: a closing tag says which thing ended, where a closing brace says only that something did — and that redundancy is what survives being quoted out of the middle of a long reply.

type Service

type Service struct {
	Name string

	// Title is the contract's own, which is what to show a person or a model.
	Title string

	// Contract is the version of the document this was read from.
	Contract string

	// Operations is how many this client exposes, which under ReadOnly is fewer
	// than the contract declares.
	Operations int

	// Groups are the resource groups within the service — Disk, Instance,
	// SecurityGroup — and are what to narrow by before knowing any operation
	// names.
	Groups []Group
}

Service is one of the platform's faces.

type Token

type Token struct {
	// Access acts inside one project and is what almost every operation takes.
	// The contracts call it a project token: it names a project as well as a
	// person, which is why no request path carries a project id.
	Access string

	// Account is for the account face — registering, listing projects, minting
	// access tokens — and comes from a person's sign-in session. A service
	// acting on someone's behalf does not have one, which is what
	// AccessTokenOnly is for.
	Account string
}

Token is one caller's credentials.

Two fields because the platform has two user-facing tokens and they are not interchangeable. An operation says which it needs, so a caller supplying only one gets a clear refusal naming the other rather than a 401 it has to interpret.

func (Token) Invalidate

func (Token) Invalidate(spec.Credential) error

Invalidate always refuses. A token handed in from outside is the only one this library has; dropping it would leave nothing to retry with, so the refusal is reported to the caller who supplied it and can get another.

func (Token) Token

func (t Token) Token(_ context.Context, kind spec.Credential) (string, error)

Token satisfies the transport's Credentials interface.

Jump to

Keyboard shortcuts

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