jelly

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Feb 25, 2024 License: MIT Imports: 16 Imported by: 0

README

jelly

Tests Status Badge Go Reference

Go server framework to run projects that need websites and to learn building servers with Go.

It grew out of the TunaQuest server.

This readme will be updated with future releases.

Dev Dependencies

In general, Jelly can be developed on with no tools beyond whatever environment you use for Go code.

The exception is changing the mocks. Some places in Jelly use interfaces to decouple certain packages from each other, and their code is mocked by using the gomock package. The mocks are pre-generated, but will need to be re-generated if the interface they mock is modified.

To get the gomock package, simply run the following:

go install go.uber.org/mock/mockgen@latest

Then execute tools/scripts/mocks to create the mocks.

Documentation

Overview

Package jelly is a simple and quick framework dekarrin 'jello' uses for learning Go servers.

"Gelatin-based web servers".

Index

Constants

View Source
const (
	ConfigKeyAPIName    = "name"
	ConfigKeyAPIBase    = "base"
	ConfigKeyAPIEnabled = "enabled"
	ConfigKeyAPIUsesDBs = "uses"
)

Variables

View Source
var (
	ErrBadCredentials = errors.New("the supplied username/password combination is incorrect")
	ErrPermissions    = errors.New("you don't have permission to do that")
	ErrNotFound       = errors.New("the requested entity could not be found")
	ErrAlreadyExists  = errors.New("resource with same identifying information already exists")
	ErrDB             = errors.New("an error occured with the DB")
	ErrBadArgument    = errors.New("one or more of the arguments is invalid")
	ErrBodyUnmarshal  = errors.New("malformed data in request")

	// TODO: merge the two types of errors.
	ErrDBConstraintViolation = errors.New("a uniqueness constraint was violated")
	ErrDBNotFound            = errors.New("the requested resource was not found")
	ErrDBDecodingFailure     = errors.New("field could not be decoded from DB storage format to model format")
)

Functions

func GetURLParam

func GetURLParam[E any](r *http.Request, key string, parse func(string) (E, error)) (val E, err error)

func ParseJSONRequest

func ParseJSONRequest(req *http.Request, v interface{}) error

v must be a pointer to a type. Will return error such that errors.Is(err, ErrMalformedBody) returns true if it is problem decoding the JSON itself.

func PathParam

func PathParam(nameType string) string

PathParam translates strings of the form "name:type" to a URI path parameter string of the form "{name:regex}" compatible with the routers used in the jelly framework. Only request URIs whose path parameters match their respective regexes (if any) will match that route.

Note that this only does basic matching for path routing. API endpoint logic will still need to decode the received string. Do not rely on, for example, the "email" type preventing malicious or invalid email; it only checks the string.

Currently, PathParam supports the following parameter type names:

  • "uuid" - UUID strings.
  • "email" - Two strings separated by an @ sign.
  • "num" - One or more digits 0-9.
  • "alpha" - One or more Latin letters A-Z or a-z.
  • "alphanum" - One or more Latin letters A-Z, a-z, or digits 0-9.

If a different regex is needed for a path parameter, give it manually in the path using "{name:regex}" syntax instead of using PathParam; this is simply to use the above listed shortcuts.

If only name is given in the string (with no colon), then the string "{" + name + "}" is returned.

func RedirectNoTrailingSlash

func RedirectNoTrailingSlash(sp ServiceProvider) http.HandlerFunc

RedirectNoTrailingSlash is an http.HandlerFunc that redirects to the same URL as the request but with no trailing slash.

func RequireIDParam

func RequireIDParam(r *http.Request) uuid.UUID

RequireIDParam gets the ID of the main entity being referenced in the URI and returns it. It panics if the key is not there or is not parsable.

func TypedSlice

func TypedSlice[E any](key string, value interface{}) ([]E, error)

TypedSlice takes a value that is passed to Set that is expected to be a slice of the given type and performs the required conversions. If a non-nil error is returned it will contain the key name automatically in its error string.

func UnPathParam

func UnPathParam(s string) string

Types

type API

type API interface {

	// Init creates the API initially and does any setup other than routing its
	// endpoints. It takes in a bundle that allows access to API config object,
	// connected DBs that the API is configured to use, a logger, and any other
	// resources available to the initializing API. Only those stores requested
	// in the API's config in the 'uses' key will be included in the bundle.
	//
	// The API should not expect that any other API has yet been initialized,
	// during a call to Init, and should not attempt to use auth middleware that
	// relies on other APIs (such as jellyauth's jwt provider). Defer actual
	// usage to another function, such as Routes.
	Init(bndl Bundle) error

	// Authenticators returns any configured authenticators that this API
	// provides. Other APIs will be able to refer to these authenticators by
	// name.
	//
	// Init must be called before Authenticators is called. It is not gauranteed
	// that all APIs in the server will have had Init called by the time a given
	// API has Authenticators called on it.
	//
	// Any Authenticator returned from this is automatically registered as an
	// Authenticator with the Auth middleware engine. Do not do so manually or
	// there may be conflicts.
	Authenticators() map[string]Authenticator

	// Routes returns a router that leads to all accessible routes in the API.
	// Additionally, returns whether the API's router contains subpaths beyond
	// just setting methods on its relative root; this affects whether
	// path-terminal slashes are redirected in the base router the API
	// router is mounted in.
	//
	// An endpoint creator passed in provides access to creation of middleware
	// configured by the server's main config file for the server to use.
	// Additionally, it also provides an Endpoint method which will wrap a
	// jelly-framework style endpoint in an http.HandlerFunc that will apply
	// standard actions such as logging, error, and panic catching.
	//
	// Init is guaranteed to have been called for all APIs in the server before
	// Routes is called, and it is safe to refer to middleware services that
	// rely on other APIs within.
	Routes(ServiceProvider) (router chi.Router, subpaths bool) // TODO: remove subpaths!!

	// Shutdown terminates any pending operations cleanly and releases any held
	// resources. It will be called after the server listener socket is shut
	// down. Implementors should examine the context's Done() channel to see if
	// they should halt during long-running operations, and do so if requested.
	Shutdown(ctx context.Context) error
}

API holds parameters for endpoints needed to run and a service layer that will perform most of the actual logic. To use API, create one and then assign the result of its HTTP* methods as handlers to a router or some other kind of server mux.

type APIConfig

type APIConfig interface {
	// Common returns the parts of the API configuration that all APIs are
	// required to have. Its keys should be considered part of the configuration
	// held within the APIConfig and any function that accepts keys will accept
	// the Common keys; additionally, FillDefaults and Validate will both
	// perform their operations on the Common's keys.
	//
	// Performing mutation operations on the Common() returned will not
	// necessarily affect the APIConfig it came from. Affecting one of its key's
	// values should be done by calling the appropriate method on the APIConfig
	// with the key name.
	Common() CommonConfig

	// Keys returns a list of strings, each of which is a valid key that this
	// configuration contains. These keys may be passed to other methods to
	// access values in this config.
	//
	// Each key returned should be alpha-numeric, and snake-case is preferred
	// (though not required). If a key contains an illegal character for a
	// particular format of a config source, it will be replaced with an
	// underscore in that format; e.g. a key called "test!" would be retrieved
	// from an envvar called "APPNAME_TEST_" as opposed to "APPNAME_TEST!", as
	// the exclamation mark is not allowed in most environment variable names.
	//
	// The returned slice will contain the values returned by Common()'s Keys()
	// function as well as any other keys provided by the APIConfig. Each item
	// in the returned slice must be non-empty and unique when all keys are
	// converted to lowercase.
	Keys() []string

	// Get gets the current value of a config key. The parameter key should be a
	// string that is returned from Keys(). If key is not a string that was
	// returned from Keys, this function must return nil.
	//
	// The key is not case-sensitive.
	Get(key string) interface{}

	// Set sets the current value of a config key directly. The value must be of
	// the correct type; no parsing is done in Set.
	//
	// The key is not case-sensitive.
	Set(key string, value interface{}) error

	// SetFromString sets the current value of a config key by parsing the given
	// string for its value.
	//
	// The key is not case-sensitive.
	SetFromString(key string, value string) error

	// FillDefaults returns a copy of the APIConfig with any unset values set to
	// default values, if possible. It need not be a brand new copy; it is legal
	// for implementers to returns the same APIConfig that FillDefaults was
	// called on.
	//
	// Implementors must ensure that the returned APIConfig's Common() returns a
	// common config that has had its keys set to their defaults as well.
	FillDefaults() APIConfig

	// Validate checks all current values of the APIConfig and returns whether
	// there is any issues with them.
	//
	// Implementors must ensure that calling Validate() also calls validation on
	// the common keys as well as those that they provide.
	Validate() error
}

type AuthUser

type AuthUser struct {
	ID         uuid.UUID // PK, NOT NULL
	Username   string    // UNIQUE, NOT NULL
	Password   string    // NOT NULL
	Email      string    // NOT NULL
	Role       Role      // NOT NULL
	Created    time.Time // NOT NULL
	Modified   time.Time // NOT NULL
	LastLogout time.Time // NOT NULL DEFAULT NOW()
	LastLogin  time.Time // NOT NULL
}

AuthUser is an auth model for use in the pre-rolled auth mechanism of user-in-db and login identified via JWT.

type AuthUserRepo

type AuthUserRepo interface {
	// Create creates a new model in the DB based on the provided one. Some
	// attributes in the provided one might not be used; for instance, many
	// Repos will automatically set the ID of new entities on creation, ignoring
	// any initially set ID. It is up to implementors to decide which attributes
	// are used.
	//
	// This returns the object as it appears in the DB after creation.
	//
	// An implementor may provide an empty implementation with a function that
	// always returns an error regardless of state and input. Consult the
	// documentation of the implementor for info.
	Create(context.Context, AuthUser) (AuthUser, error)

	// Get retrieves the model with the given ID. If no entity with that ID
	// exists, an error is returned.
	//
	// An implementor may provide an empty implementation with a function that
	// always returns an error regardless of state and input. Consult the
	// documentation of the implementor for info.
	Get(context.Context, uuid.UUID) (AuthUser, error)

	// GetAll retrieves all entities in the associated store. If no entities
	// exist but no error otherwise occurred, the returned list of entities will
	// have a length of zero and the returned error will be nil.
	//
	// An implementor may provide an empty implementation with a function that
	// always returns an error regardless of state and input. Consult the
	// documentation of the implementor for info.
	GetAll(context.Context) ([]AuthUser, error)

	// Update updates a particular entity in the store to match the provided
	// model. Implementors may choose which properties of the provided value are
	// actually used.
	//
	// This returns the object as it appears in the DB after updating.
	//
	// An implementor may provide an empty implementation with a function that
	// always returns an error regardless of state and input. Consult the
	// documentation of the implementor for info.
	Update(context.Context, uuid.UUID, AuthUser) (AuthUser, error)

	// Delete removes the given entity from the store.
	//
	// This returns the object as it appeared in the DB immediately before
	// deletion.
	//
	// An implementor may provide an empty implementation with a function that
	// always returns an error regardless of state and input. Consult the
	// documentation of the implementor for info.
	Delete(context.Context, uuid.UUID) (AuthUser, error)

	// Close performs any clean-up operations required and flushes pending
	// operations. Not all Repos will actually perform operations, but it should
	// always be called as part of tear-down operations.
	Close() error

	// GetByUsername retrieves the User with the given username. If no entity
	// with that username exists, an error is returned.
	GetByUsername(ctx context.Context, username string) (AuthUser, error)
}

type AuthUserStore

type AuthUserStore interface {
	Store

	// AuthUsers returns a repository that holds users used as part of
	// authentication and login.
	AuthUsers() AuthUserRepo
}

AuthUserStore is an interface that defines methods for building a DAO store to be used as part of user auth via the jelly framework packages.

TODO: should this be its own "sub-package"? example implementations. Or something. feels like it should live closer to auth-y type things.

type Authenticator

type Authenticator interface {

	// Authenticate retrieves the user details from the request using whatever
	// method is correct for the auth handler. Returns the user, whether the
	// user is currently logged in, and any error that occured. If the user is
	// not logged in but no error actually occured, a default user and logged-in
	// = false are returned with a nil error. An error should only be returned
	// if there is an issue authenticating the user, and a user not being logged
	// in does not count as an issue. If the user fails to validate due to bad
	// credentials, that does count and should be returned as an error.
	//
	// If the user is logged-in, returns the logged-in user, true, and a nil
	// error.
	Authenticate(req *http.Request) (AuthUser, bool, error)

	// Service returns the UserLoginService that can be used to control active
	// logins and the list of users.
	Service() UserLoginService

	// UnauthDelay is the amount of time that the system should delay responding
	// to unauthenticated requests to endpoints that require auth.
	UnauthDelay() time.Duration
}

Authenticator is middleware for an endpoint that will accept a request, extract the token used for authentication, and make calls to get a User entity that represents the logged in user from the token.

Keys are added to the request context before the request is passed to the next step in the chain. AuthUser will contain the logged-in user, and AuthLoggedIn will return whether the user is logged in (only applies for optional logins; for non-optional, not being logged in will result in an HTTP error being returned before the request is passed to the next handler).

type Bundle

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

TODO: combine this bundle with the primary one

func NewBundle

func NewBundle(api APIConfig, g Globals, log Logger, dbs map[string]Store) Bundle

func (Bundle) APIBase

func (bndl Bundle) APIBase() string

APIBase returns the base path of the API that its routes are all mounted at. It will perform any needed normalization of the base string to ensure that it is non-empty, starts with a slash, and does not end with a slash except if it is "/". The returned base path is relative to the ServerBase; combine both ServerBase and APIBase to get the complete URI base path, or call Base() to do it for you.

This is a convenience function equivalent to calling bnd.Get(KeyAPIBase).

func (Bundle) Base

func (bndl Bundle) Base() string

Base returns the complete URIBase path configured for any methods. This takes ServerBase() and APIBase() and appends them together, handling doubled-slashes.

func (Bundle) DB

func (bndl Bundle) DB(n int) Store

DB gets the connection to the Nth DB listed in the API's uses. Panics if the API config does not have at least n+1 entries.

func (Bundle) DBNamed

func (bndl Bundle) DBNamed(name string) Store

NamedDB gets the exact DB with the given name. This will only return the DB if it was configured as one of the used DBs for the API.

func (Bundle) Enabled

func (bndl Bundle) Enabled() bool

Enabled returns whether the API was set to be enabled. Since this is required for an API to be initialized, this will always be true for an API receiving a Bundle in its Init method.

This is a convenience function equivalent to calling bnd.GetBool(KeyAPIEnabled).

func (Bundle) Get

func (bndl Bundle) Get(key string) string

Get retrieves the value of a string-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetBool

func (bndl Bundle) GetBool(key string) bool

GetBool retrieves the value of a bool-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetBoolSlice

func (bndl Bundle) GetBoolSlice(key string) []bool

GetBoolSlice retrieves the value of a []bool-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetByteSlice

func (bndl Bundle) GetByteSlice(key string) []byte

GetByteSlice retrieves the value of a []byte-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetFloat

func (bndl Bundle) GetFloat(key string) float64

GetFloat retrieves the value of a float64-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetFloatSlice

func (bndl Bundle) GetFloatSlice(key string) []float64

GetFloatSlice retrieves the value of a []float64-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetInt

func (bndl Bundle) GetInt(key string) int

GetInt retrieves the value of an int-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetInt16

func (bndl Bundle) GetInt16(key string) int16

GetInt16 retrieves the value of an int16-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetInt32

func (bndl Bundle) GetInt32(key string) int32

GetInt32 retrieves the value of an int32-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetInt64

func (bndl Bundle) GetInt64(key string) int64

GetInt64 retrieves the value of an int64-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetInt8

func (bndl Bundle) GetInt8(key string) int8

GetInt8 retrieves the value of an int8-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetIntSlice

func (bndl Bundle) GetIntSlice(key string) []int

GetIntSlice retrieves the value of a []int-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetSlice

func (bndl Bundle) GetSlice(key string) []string

GetSlice retrieves the value of a []string-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetTime

func (bndl Bundle) GetTime(key string) time.Time

GetTime retrieves the value of a time.Time-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetUint

func (bndl Bundle) GetUint(key string) uint

GetUint retrieves the value of a uint-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetUint16

func (bndl Bundle) GetUint16(key string) uint16

GetUint16 retrieves the value of a uint16-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetUint32

func (bndl Bundle) GetUint32(key string) uint32

GetUint32 retrieves the value of a uint32-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetUint64

func (bndl Bundle) GetUint64(key string) uint64

GetUint64 retrieves the value of a uint64-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) GetUint8

func (bndl Bundle) GetUint8(key string) uint8

GetUint8 retrieves the value of a uint8-typed API configuration key. If it doesn't exist in the config, the zero-value is returned.

func (Bundle) Has

func (bndl Bundle) Has(key string) bool

Has returns whether the given key exists in the API config.

func (Bundle) Logger

func (bndl Bundle) Logger() Logger

func (Bundle) Name

func (bndl Bundle) Name() string

Name returns the name of the API as read from the API config.

This is a convenience function equivalent to calling bnd.Get(KeyAPIName).

func (Bundle) ServerAddress

func (bndl Bundle) ServerAddress() string

ServerAddress returns the address that the server the API is being initialized for will listen on.

func (Bundle) ServerBase

func (bndl Bundle) ServerBase() string

ServerBase returns the base path that all APIs in the server are mounted at. It will perform any needed normalization of the base string to ensure that it is non-empty, starts with a slash, and does not end with a slash except if it is "/". Can be useful for establishing "complete" paths to entities, although if a complete base path to the API is needed, Bundle.Base can be called.

func (Bundle) ServerPort

func (bndl Bundle) ServerPort() int

ServerPort returns the port that the server the API is being initialized for will listen on.

func (Bundle) UsesDBs

func (bndl Bundle) UsesDBs() []string

UsesDBs returns the list of database names that the API is configured to connect to, in the order they were listed in config.

This is a convenience function equivalent to calling bnd.GetSlice(KeyAPIUsesDBs).

func (Bundle) WithDBs

func (bndl Bundle) WithDBs(dbs map[string]Store) Bundle

type CommonConfig

type CommonConfig struct {
	// Name is the name of the API. Must be unique.
	Name string

	// Enabled is whether the API is to be enabled. By default, this is false in
	// all cases.
	Enabled bool

	// Base is the base URI that all paths will be rooted at, relative to the
	// server base path. This can be "/" (or "", which is equivalent) to
	// indicate that the API is to be based directly at the URIBase of the
	// server config that this API is a part of.
	Base string

	// UsesDBs is a list of names of data stores and authenticators that the API
	// uses directly. When Init is called, it is passed active connections to
	// each of the DBs. There must be a corresponding entry for each DB name in
	// the root DBs listing in the Config this API is a part of. The
	// Authenticators slice should contain only authenticators that are provided
	// by other APIs; see their documentation for which they provide.
	UsesDBs []string
}

CommonConfig holds configuration options common to all APIs.

func (*CommonConfig) Common

func (cc *CommonConfig) Common() CommonConfig

func (*CommonConfig) FillDefaults

func (cc *CommonConfig) FillDefaults() APIConfig

FillDefaults returns a new *Common identical to cc but with unset values set to their defaults and values normalized.

func (*CommonConfig) Get

func (cc *CommonConfig) Get(key string) interface{}

func (*CommonConfig) Keys

func (cc *CommonConfig) Keys() []string

func (*CommonConfig) Set

func (cc *CommonConfig) Set(key string, value interface{}) error

func (*CommonConfig) SetFromString

func (cc *CommonConfig) SetFromString(key string, value string) error

func (*CommonConfig) Validate

func (cc *CommonConfig) Validate() error

Validate returns an error if the Config has invalid field values set. Empty and unset values are considered invalid; if defaults are intended to be used, call Validate on the return value of FillDefaults.

type Component

type Component interface {
	// Name returns the name of the component, which must be unique across all
	// components that jelly is set up to use.
	Name() string

	// API returns a new, uninitialized API that the Component uses as its
	// server frontend. This instance will be initialized and passed its config
	// object at config loading time.
	API() API

	// Config returns a new APIConfig instance that the Component's config
	// section is loaded into.
	Config() APIConfig
}

type Config

type Config struct {

	// Globals is all variables shared with initialization of all APIs.
	Globals Globals

	// DBs is the configurations to use for connecting to databases and other
	// persistence layers. If not provided, it will be set to a configuration
	// for using an in-memory persistence layer.
	DBs map[string]DatabaseConfig

	// APIs is the configuration for each API that will be included in a
	// configured jelly framework server. Each APIConfig must return a
	// CommonConfig whose Name is either set to blank or to the key that maps to
	// it.
	APIs map[string]APIConfig

	// Log is used to configure the built-in logging system. It can be left
	// blank to disable logging entirely.
	Log LogConfig

	// Format is the format of config, used in Dump. It will only be
	// automatically set if the Config was created via a call to Load.
	Format Format
}

Config is a complete configuration for a server. It contains all parameters that can be used to configure its operation.

func (Config) FillDefaults

func (cfg Config) FillDefaults() Config

FillDefaults returns a new Config identical to cfg but with unset values set to their defaults.

func (Config) Validate

func (cfg Config) Validate() error

Validate returns an error if the Config has invalid field values set. Empty and unset values are considered invalid; if defaults are intended to be used, call Validate on the return value of FillDefaults.

type DBType

type DBType string

DBType is the type of a Database connection.

const (
	DatabaseNone     DBType = "none"
	DatabaseSQLite   DBType = "sqlite"
	DatabaseOWDB     DBType = "owdb"
	DatabaseInMemory DBType = "inmem"
)

func ParseDBType

func ParseDBType(s string) (DBType, error)

ParseDBType parses a string found in a connection string into a DBType.

func (DBType) String

func (dbt DBType) String() string

type DatabaseConfig

type DatabaseConfig struct {
	// Type is the type of database the config refers to, primarily for data
	// validation purposes. It also determines which of its other fields are
	// valid.
	Type DBType

	// Connector is the name of the registered connector function that should be
	// used. The function name must be registered for DBs of the given type.
	Connector string

	// DataDir is the path on disk to a directory to use to store data in. This
	// is only applicable for certain DB types: SQLite, OWDB.
	DataDir string

	// DataFile is the name of the DB file to use for an OrbweaverDB (OWDB)
	// persistence store. By default, it is "db.owv". This is only applicable
	// for certain DB types: OWDB.
	DataFile string
}

Database contains configuration settings for connecting to a persistence layer.

func ParseDBConnString

func ParseDBConnString(s string) (DatabaseConfig, error)

ParseDBConnString parses a database connection string of the form "engine:params" (or just "engine" if no other params are required) into a valid Database config object.

Supported database types and a sample string containing valid configurations for each are shown below. Placeholder values are between angle brackets, optional parts are between square brackets. Ordering of parameters does not matter.

* In-memory database: "inmem" * SQLite3 DB file: "sqlite:</path/to/db/dir>"" * OrbweaverDB: "owdb:dir=<path/to/db/dir>[,file=<new-db-file-name.owv>]"

func (DatabaseConfig) FillDefaults

func (db DatabaseConfig) FillDefaults() DatabaseConfig

FillDefaults returns a new Database identical to db but with unset values set to their defaults. In this case, if the type is not set, it is changed to types.DatabaseInMemory. If OWDB File is not set, it is changed to "db.owv".

func (DatabaseConfig) Validate

func (db DatabaseConfig) Validate() error

Validate returns an error if the Database does not have the correct fields set. Its type will be checked to ensure that it is a valid type to use and any fields necessary for connecting to that type of DB are also checked.

type EndpointFunc

type EndpointFunc func(req *http.Request) Result

type Error

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

Error is a typed error returned by certain functions in the TunaScript server as their error value. It contains both a message explaining what happened as well as one or more error values it considers to be its causes. Error is compatible with the use of errors.Is() - calling errors.Is on some Error value err along with any value of error it holds as one of its causes will return true. This allows for easy examination and failure condition checking without needing to resort to manual typecasting.

If Error has at least one cause defined, the result of calling Error.Error() will be its primary message with the result of calling Error() on its first cause appended to it.

Error should not be used directly; call New to create one.

func NewError

func NewError(msg string, causes ...error) Error

NewError creates a new Error with the given message, along with any errors it should wrap as its causes. Providing cause errors is not required, but will cause it to return true when it is checked against that error via a call to errors.Is.

func WrapDBError

func WrapDBError(err error, msg ...any) Error

WrapDBError creates a new Error that wraps the given error as a cause and automatically adds ErrDB as another cause. A user-set message may be provided if desired with msg, but it may be left as "".

The provided error being wrapped will itself be converted to an Error of the approriate jelly type if possible; e.g. SQLite-specific errors indicating that a record could not be found would be converted to an Error that returns true for errors.Is(err, jelly.ErrNotFound).

msg, if provided, is used to create the msg of the error by calling fmt.Sprint. For format capability, use WrapDBErrorf.

func WrapDBErrorf

func WrapDBErrorf(err error, format string, a ...any) Error

WrapDBError creates a new Error that wraps the given error as a cause and automatically adds ErrDB as another cause. A user-set message may be provided if desired with format and arguments a.

The provided error being wrapped will itself be converted to an Error of the approriate jelly type if possible; e.g. SQLite-specific errors indicating that a record could not be found would be converted to an Error that returns true for errors.Is(err, jelly.ErrNotFound).

msg, if provided, is used to create the msg of the error by calling fmt.Sprintf.

func (Error) Error

func (e Error) Error() string

Error returns the message defined for the Error. If a message was defined for it when created, that message is returned, concatenated with the result of calling Error() on the its first cause if one is defined. If no message or an empty message was defined for it when created, but there is at least one cause defined for it, the result of calling Error() on the first cause is returned. If no message is defined and no causes are defined, returns the empty string.

func (Error) Is

func (e Error) Is(target error) bool

Is returns whether Error either Is itself the given target error, or one of its causes is.

This function is for interaction with the errors API.

func (Error) Unwrap

func (e Error) Unwrap() []error

Unwrap returns the causes of Error. The return value will be nil if no causes were defined for it.

This function is for interaction with the errors API. It will only be used in Go version 1.20 and later; 1.19 will default to use of Error.Is when calling errors.Is on the Error.

type ErrorResponse

type ErrorResponse struct {
	Error  string `json:"error"`
	Status int    `json:"status"`
}

type Format

type Format int
const (
	NoFormat Format = iota
	JSON
	YAML
)

func (Format) Extensions

func (f Format) Extensions() []string

func (Format) String

func (f Format) String() string

type Globals

type Globals struct {

	// Port is the port that the server will listen on. It will default to 8080
	// if none is given.
	Port int

	// Address is the internet address that the server will listen on. It will
	// default to "localhost" if none is given.
	Address string

	// URIBase is the base path that all APIs are rooted on. It will default to
	// "/", which is equivalent to being directly on root.
	URIBase string

	// The main auth provider to use for the project. Must be the
	// fully-qualified name of it, e.g. COMPONENT.PROVIDER format.
	MainAuthProvider string
}

Globals are the values of global configuration values from the top level config. These values are shared with every API.

func (Globals) FillDefaults

func (g Globals) FillDefaults() Globals

func (Globals) Validate

func (g Globals) Validate() error

type LogConfig

type LogConfig struct {
	// Enabled is whether to enable built-in logging statements.
	Enabled bool

	// Provider must be the name of one of the logging providers. If set to
	// None or unset, it will default to logging.Jellog.
	Provider LogProvider

	// File to log to. If not set, all logging will be done to stderr and it
	// will display all logging statements. If set, the file will receive all
	// levels of log messages and stderr will show only those of Info level or
	// higher.
	File string
}

LogConfig contains logging options. Loggers are provided to APIs in the form of sub-components of the primary logger. If logging is enabled, the Jelly server will configure the logger of the chosen provider and use it for messages about the server itself, and will pass a sub-component logger to each API to use for its own logging.

func (LogConfig) FillDefaults

func (log LogConfig) FillDefaults() LogConfig

func (LogConfig) Validate

func (g LogConfig) Validate() error

type LogProvider

type LogProvider int
const (
	NoLog LogProvider = iota
	Jellog
	StdLog
)

func ParseLogProvider

func ParseLogProvider(s string) (LogProvider, error)

func (LogProvider) String

func (p LogProvider) String() string

type Logger

type Logger interface {
	// Debug writes a message to the log at Debug level.
	Debug(string)

	// Debugf writes a formatted message to the log at Debug level.
	Debugf(string, ...interface{})

	// Error writes a message to the log at Error level.
	Error(string)

	// Errorf writes a formatted message to the log at Error level.
	Errorf(string, ...interface{})

	// Info writes a message to the log at Info level.
	Info(string)

	// Infof writes a formatted message to the log at Info level.
	Infof(string, ...interface{})

	// Trace writes a message to the log at Trace level.
	Trace(string)

	// Tracef writes a formatted message to the log at Trace level.
	Tracef(string, ...interface{})

	// Warn writes a message to the log at Warn level.
	Warn(string)

	// Warnf writes a formatted message to the log at Warn level.
	Warnf(string, ...interface{})

	// DebugBreak adds a 'break' between events in the log at Debug level. The
	// meaning of a break varies based on the underlying log; for text-based
	// logs, it is generally a newline character.
	DebugBreak()

	// ErrorBreak adds a 'break' between events in the log at Error level. The
	// meaning of a break varies based on the underlying log; for text-based
	// logs, it is generally a newline character.
	ErrorBreak()

	// InfoBreak adds a 'break' between events in the log at Info level. The
	// meaning of a break varies based on the underlying log; for text-based
	// logs, it is generally a newline character.
	InfoBreak()

	// TraceBreak adds a 'break' between events in the log at Trace level. The
	// meaning of a break varies based on the underlying log; for text-based
	// logs, it is generally a newline character.
	TraceBreak()

	// WarnBreak adds a 'break' between events in the log at Warn level. The
	// meaning of a break varies based on the underlying log; for text-based
	// logs, it is generally a newline character.
	WarnBreak()

	// LogResult logs a request and the response to that request.
	LogResult(req *http.Request, r Result)
}

Logger is an object that is used to log messages. Use the New functions in the logging sub-package to create one.

type Middleware

type Middleware func(next http.Handler) http.Handler

Middleware is a function that takes a handler and returns a new handler which wraps the given one and provides some additional functionality.

type Override

type Override struct {
	Authenticators []string
}

Override is a per-endpoint optional overriding of a global configuration in order to, for instance, use a specific Authenticator. Multiple Overrides can be given in a single Endpoint; if given, they will be evaluated in order with later ones taking precedence over others in cases of conflict and later ones being added to lists at lower prority in cases of lists.

func CombineOverrides

func CombineOverrides(overs []Override) Override

type RESTServer

type RESTServer interface {
	Config() Config
	RoutesIndex() string
	Add(name string, api API) error
	ServeForever() error
	Shutdown(ctx context.Context) error
}

type ResponseGenerator

type ResponseGenerator interface {
	OK(respObj interface{}, internalMsg ...interface{}) Result
	NoContent(internalMsg ...interface{}) Result
	Created(respObj interface{}, internalMsg ...interface{}) Result
	Conflict(userMsg string, internalMsg ...interface{}) Result
	BadRequest(userMsg string, internalMsg ...interface{}) Result
	MethodNotAllowed(req *http.Request, internalMsg ...interface{}) Result
	NotFound(internalMsg ...interface{}) Result
	Forbidden(internalMsg ...interface{}) Result
	Unauthorized(userMsg string, internalMsg ...interface{}) Result
	InternalServerError(internalMsg ...interface{}) Result
	Redirection(uri string) Result
	Response(status int, respObj interface{}, internalMsg string, v ...interface{}) Result
	Err(status int, userMsg, internalMsg string, v ...interface{}) Result
	TextErr(status int, userMsg, internalMsg string, v ...interface{}) Result
	LogResponse(req *http.Request, r Result)

	// Logger should not be called by external users of jelly; it is in a
	// transitory state and is slated for removal in a future release.
	Logger() Logger
}

type Result

type Result struct {
	Status      int
	IsErr       bool
	IsJSON      bool
	InternalMsg string

	Resp  interface{}
	Redir string // only used for redirects
	// contains filtered or unexported fields
}

should not be directly init'd probs because log will not be set

func (*Result) PrepareMarshaledResponse

func (r *Result) PrepareMarshaledResponse() error

PrepareMarshaledResponse sets the respJSONBytes to the marshaled version of the response if required. If required, and there is a problem marshaling, an error is returned. If not required, nil error is always returned.

If PrepareMarshaledResponse has been successfully called with a non-nil returned error at least once for r, calling this method again has no effect and will return a non-nil error.

func (Result) WithHeader

func (r Result) WithHeader(name, val string) Result

func (Result) WriteResponse

func (r Result) WriteResponse(w http.ResponseWriter)

type Role

type Role int64
const (
	Guest Role = iota
	Unverified
	Normal

	Admin Role = 100
)

func ParseRole

func ParseRole(s string) (Role, error)

func (*Role) Scan

func (r *Role) Scan(value interface{}) error

func (Role) String

func (r Role) String() string

func (Role) Value

func (r Role) Value() (driver.Value, error)

type ServiceProvider

type ServiceProvider interface {
	ResponseGenerator
	DontPanic() Middleware
	OptionalAuth(authenticators ...string) Middleware
	RequiredAuth(authenticators ...string) Middleware
	SelectAuthenticator(authenticators ...string) Authenticator
	Endpoint(ep EndpointFunc, overrides ...Override) http.HandlerFunc
	GetLoggedInUser(req *http.Request) (user AuthUser, loggedIn bool)
}

ServiceProvider is passed to an API's Routes method and is used to access jelly middleware and standardized endpoint function wrapping to produce an http.HandlerFunc from an EndpointFunc.

type Store

type Store interface {

	// Close closes any pending operations on the DAO store and on all of its
	// Repos. It performs any clean-up operations necessary and should always be
	// called once the Store is no longer in use.
	Close() error
}

type UserLoginService

type UserLoginService interface {
	// Login verifies the provided username and password against the existing user
	// in persistence and returns that user if they match. Returns the user entity
	// from the persistence layer that the username and password are valid for.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If the credentials do not match
	// a user or if the password is incorrect, it will match ErrBadCredentials. If
	// the error occured due to an unexpected problem with the DB, it will match
	// serr.ErrDB.
	Login(ctx context.Context, username string, password string) (AuthUser, error)

	// Logout marks the user with the given ID as having logged out, invalidating
	// any login that may be active. Returns the user entity that was logged out.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If the user doesn't exist, it
	// will match serr.ErrNotFound. If the error occured due to an unexpected
	// problem with the DB, it will match serr.ErrDB.
	Logout(ctx context.Context, who uuid.UUID) (AuthUser, error)

	// GetAllUsers returns all auth users currently in persistence.
	GetAllUsers(ctx context.Context) ([]AuthUser, error)

	// GetUser returns the user with the given ID.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If no user with that ID exists,
	// it will match serr.ErrNotFound. If the error occured due to an unexpected
	// problem with the DB, it will match serr.ErrDB. Finally, if there is an issue
	// with one of the arguments, it will match serr.ErrBadArgument.
	GetUser(ctx context.Context, id string) (AuthUser, error)

	// GetUserByUsername returns the user with the given username.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If no user with that ID exists,
	// it will match serr.ErrNotFound. If the error occured due to an unexpected
	// problem with the DB, it will match serr.ErrDB. Finally, if there is an issue
	// with one of the arguments, it will match serr.ErrBadArgument.
	GetUserByUsername(ctx context.Context, username string) (AuthUser, error)

	// CreateUser creates a new user with the given username, password, and email
	// combo. Returns the newly-created user as it exists after creation.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If a user with that username is
	// already present, it will match serr.ErrAlreadyExists. If the error occured
	// due to an unexpected problem with the DB, it will match serr.ErrDB. Finally,
	// if one of the arguments is invalid, it will match serr.ErrBadArgument.
	CreateUser(ctx context.Context, username, password, email string, role Role) (AuthUser, error)

	// UpdateUser sets all properties except the password of the user with the
	// given ID to the properties in the provider user. All the given properties
	// of the user (except password) will overwrite the existing ones. Returns
	// the updated user.
	//
	// This function cannot be used to update the password. Use UpdatePassword for
	// that.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If a user with that username or
	// ID (if they are changing) is already present, it will match
	// serr.ErrAlreadyExists. If no user with the given ID exists, it will match
	// serr.ErrNotFound. If the error occured due to an unexpected problem with the
	// DB, it will match serr.ErrDB. Finally, if one of the arguments is invalid, it
	// will match serr.ErrBadArgument.
	UpdateUser(ctx context.Context, curID, newID, username, email string, role Role) (AuthUser, error)

	// UpdatePassword sets the password of the user with the given ID to the new
	// password. The new password cannot be empty. Returns the updated user.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If no user with the given ID
	// exists, it will match serr.ErrNotFound. If the error occured due to an
	// unexpected problem with the DB, it will match serr.ErrDB. Finally, if one of
	// the arguments is invalid, it will match serr.ErrBadArgument.
	UpdatePassword(ctx context.Context, id, password string) (AuthUser, error)

	// DeleteUser deletes the user with the given ID. It returns the deleted user
	// just after they were deleted.
	//
	// The returned error, if non-nil, will return true for various calls to
	// errors.Is depending on what caused the error. If no user with that username
	// exists, it will match serr.ErrNotFound. If the error occured due to an
	// unexpected problem with the DB, it will match serr.ErrDB. Finally, if there
	// is an issue with one of the arguments, it will match serr.ErrBadArgument.
	DeleteUser(ctx context.Context, id string) (AuthUser, error)
}

UserLoginService provides a way to control the state of login of users and retrieve users from the backend store.

Directories

Path Synopsis
Package auth provides user authentication and login services and APIs.
Package auth provides user authentication and login services and APIs.
cmd
jellytest
Jellytest starts a jelly-based RESTServer that uses the pre-rolled jelly auth API as well as its own separate echo API.
Jellytest starts a jelly-based RESTServer that uses the pre-rolled jelly auth API as well as its own separate echo API.
jellytest/dao
Package dao provides data abstraction objects and database connection functions for the jellytest test server.
Package dao provides data abstraction objects and database connection functions for the jellytest test server.
db
Package db provides data access objects compatible with the rest of the jelly framework packages.
Package db provides data access objects compatible with the rest of the jelly framework packages.
owdb
Package owdb provides OrbweaverDB stores.
Package owdb provides OrbweaverDB stores.
internal
config
Package config contains configuration options for the server as well as various config contstants.
Package config contains configuration options for the server as well as various config contstants.
jelsort
Package jelsort provides custom sorting.
Package jelsort provides custom sorting.
middle
Package middle contains middleware for use with the jelly server framework.
Package middle contains middleware for use with the jelly server framework.
tools
mocks/jelly
Package mock_jelly is a generated GoMock package.
Package mock_jelly is a generated GoMock package.

Jump to

Keyboard shortcuts

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