emulator

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Host is the interface to bind. It defaults to loopback (127.0.0.1): the emulator
	// authenticates nothing, so it must not be reachable off the machine unless the
	// user deliberately widens it (a container that maps the port needs 0.0.0.0).
	Host string

	// Port is the TCP port to listen on.
	Port int

	// Seed is a directory of JSON fixtures to preload, or "" for an empty tenant.
	Seed string

	// Verbose logs one line per request to Log.
	Verbose bool

	// Log is where request logs and startup notices go — stderr, so stdout stays the
	// data contract even here.
	Log io.Writer

	// PubSubHost is the local Pub/Sub emulator to publish events to (the standard
	// PUBSUB_EMULATOR_HOST value). Empty disables eventing: subscriptions are still
	// stored and matched, but nothing is published. It is never a real Google Cloud
	// endpoint — the publisher pins every connection to this host with auth disabled.
	PubSubHost string
	// contains filtered or unexported fields
}

Config configures a Server.

type Publisher

type Publisher interface {
	Publish(ctx context.Context, projectID, topicID string, data []byte, attrs map[string]string) error
}

Publisher sends one event to one Pub/Sub topic. It is an interface so a test can record what would have been published without standing up a real emulator.

type Server

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

Server is a running emulator: a Fiber app answering the whole API surface from one in-memory store.

func New

func New(cfg Config) (*Server, error)

New builds a server: it registers every operation in the spec, wires the store, and preloads any seed data. It does not listen yet.

func (*Server) Listen

func (s *Server) Listen(ctx context.Context, ready func()) error

Listen binds the port and serves until ctx is cancelled, then shuts down gracefully and returns nil. It returns a non-nil error when the port could not be bound (e.g. already taken) or the server otherwise failed.

ready, if non-nil, is called the moment the port is bound — before Listen starts blocking to serve — so a caller can print a "point fft here" recipe only once it is actually true, instead of racing a bind failure with a recipe that looks like it worked.

ctx is the command's context, which the root cancels on SIGINT/SIGTERM — so Ctrl-C drains the server and exits 0.

type Store

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

Store is the emulator's in-memory tenant: a set of collections, each a map of entities by id with a stable insertion order for pagination. One RWMutex guards the whole thing — a dev-time emulator has no need for finer locking, and one lock is one fewer thing to reason about.

func NewStore

func NewStore(metas map[string]collectionMeta) *Store

NewStore returns an empty store that knows each collection's response metadata (its items-key), inferred from the spec.

func (*Store) Create

func (s *Store) Create(name string, doc entityDoc) map[string]any

Create stores a new entity, assigning it an id (unless the body carries one) and version 1 (unless the body carries a version), and returns the stored document. This honors an incoming id/version so seed() can replay a fixture's captured values; the live HTTP create handler strips both from the body before calling this, matching the real API's server-assigned id/version semantics. The exported methods speak the concrete map[string]any rather than the internal entityDoc alias, which is the same type.

func (*Store) Delete

func (s *Store) Delete(name, id string) bool

Delete removes an entity, reporting whether it was there.

func (*Store) FindBy

func (s *Store) FindBy(name, field, value string) (string, bool)

FindBy returns the id of the first entity in the collection whose string field equals value, in insertion order. It backs URN path resolution: the API addresses a facility as urn:fft:facility:tenantFacilityId:<value>, and this is how the emulator turns that selector back into the entity the store keeps.

func (*Store) Get

func (s *Store) Get(name, id string) (map[string]any, bool)

Get returns the entity, or false when the collection does not hold it.

func (*Store) List

func (s *Store) List(name string) []map[string]any

List returns every entity in the collection, in insertion order. It clones on every call — the paginators then slice the result — which is O(n) per page and so O(n²) across a full walk. That is deliberate simplicity: a dev emulator holds tens of entities, not the millions where this would matter.

func (*Store) Update

func (s *Store) Update(name, id string, doc entityDoc, patch bool) (map[string]any, error)

Update replaces (PUT) or merges (PATCH) an entity and bumps its version.

It enforces the body-carried optimistic lock: when the incoming document names a version and it does not match the stored one, the update is refused with a *conflictError so the client re-reads and retries. A missing version is taken as "whatever is current" rather than a conflict, so a PATCH need not echo it.

Jump to

Keyboard shortcuts

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