Documentation
¶
Index ¶
- type Config
- type Publisher
- type Server
- type Store
- func (s *Store) Create(name string, doc entityDoc) map[string]any
- func (s *Store) Delete(name, id string) bool
- func (s *Store) FindBy(name, field, value string) (string, bool)
- func (s *Store) Get(name, id string) (map[string]any, bool)
- func (s *Store) List(name string) []map[string]any
- func (s *Store) Update(name, id string, doc entityDoc, patch bool) (map[string]any, error)
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 ¶
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 ¶
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 ¶
NewStore returns an empty store that knows each collection's response metadata (its items-key), inferred from the spec.
func (*Store) Create ¶
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) FindBy ¶
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) List ¶
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 ¶
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.