nibbler

package module
v0.23.2 Latest Latest
Warning

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

Go to latest
Published: Dec 23, 2023 License: MIT Imports: 19 Imported by: 24

README

Nibbler

CircleCI

An extension-oriented framework designed to take a lot of the boilerplate out of making a top-notch Go web server or service worker. Requires Go v1.12+. Go v1.13+ recommended.

A sample app is available that uses Nibbler. Hopefully it highlights the ease-of-use of the framework.

Running the included sample apps

Sample apps have been provided to show how Nibbler and some extensions are used. They'll be helpful as I fill in many documentation gaps. You can find them in their respective extension repositories.

Build/run the sample app you're interested in using (from the current directory, the one in this repository is in ./sample):

go run main.go

Extensions

Extensions are the backbone of Nibbler. Extensions must implement a very simple interface (nibbler.Extension). A base class extension (NoOpExtension) is available for cases where only very few Extension methods (or none?) are required for the extension you're building.

Included Extension Categories

Nibbler also provides some extension implementations that perform common tasks for web services.

Extensions provided with Nibbler are organized by functional category, with the main Nibbler structures at the root level. These are the module categories below the root level:

  • Session - session storage and retrieval. See ./session/README.md
  • User - the Nibbler user model, and various integrations that can operate with it. These will tend to be auth integrations. See ./user/README.md
  • Local auth - local auth implementation, expects a user extension and session extension implementation to be provided to it. See ./user/auth/local/README.md

External Extensions

There are various features provided by external modules:

  • Auth - authentication/authorization modules that do not integrate with Nibbler's user model (source of truth is not Nibbler).
  • Database storage - connect to databases and expose mechanisms to create, query, etc.
  • Mail - outbound email/sms/etc
  • Storage - block/blob storage

Here are some repositories containing such extensions (more to come):

https://github.com/markdicksonjr/nibbler-auth0

https://github.com/markdicksonjr/nibbler-elasticsearch

https://github.com/markdicksonjr/nibbler-mail-outbound

https://github.com/markdicksonjr/nibbler-oauth2

https://github.com/markdicksonjr/nibbler-socket

https://github.com/markdicksonjr/nibbler-sql

https://github.com/markdicksonjr/nibbler-storage

Configuration

The app configuration can be created with this method:

config, err := nibbler.LoadConfiguration()

If nil is provided to the core.LoadConfiguration method, it will use environment variables for configuration of your app, with anything in ./config.json overriding it. This feature can be overridden by doing something like this in your app:

envSources := []source.Source{
    file.NewSource(file.WithPath("./config.json")),
    env.NewSource(),
}

config, err := nibbler.LoadConfiguration(&envSources)

In this case (providing nil results in this behavior, but it's shown here explicitly to show how the mechanism works), it will first apply environment variables, then apply properties from the json file (reverse order of definition). The environment variables used are upper-case but with dot-notation where the dots are replaced by underscores (e.g. nibbler.port is NIBBLER_PORT).

The following properties are available by default, but custom properties can be obtained from your extension from the Application passed to it in the Init() method. For example:

app.Configuration.Raw.Get("some", "property")

The core properties (all optional) are:

  • NIBBLER_PORT (or just PORT) = nibbler.port in JSON, etc
  • NIBBLER_DIRECTORY_STATIC = nibbler.directory.static in JSON, etc, defaults to "/public"
  • NIBBLER_AC_ALLOW_ORIGIN = nibbler.ac.allow.origin in JSON, etc, defaults to "*"
  • NIBBLER_AC_ALLOW_HEADERS = nibbler.ac.allow.headers in JSON, etc, defaults to "Origin, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version, X-Response-Time, X-PINGOTHER, X-CSRF-Token, Authorization"
  • NIBBLER_AC_ALLOW_METHODS = nibbler.ac.allow.methods in JSON, etc, defaults to "GET, POST, OPTIONS, PUT, PATCH, DELETE"

For specific configuration values for a given extension, look at the relevant module README.md.

A sample config example is provided "./sample/config.json":

{
  "port": 5001,
  "nibbler": {
    "port": 5000
  }
}

There are a few things to notice:

  • both port and nibbler.port are defined. The nibbler.port value takes priority over the port value (it's considered more specific). So you will notice when the app starts that it uses nibbler.port's value when started. The "port" value (really, the PORT env var) is frequently used by PaaS providers and is often a requirement for apps and containers to register as "healthy" or "started". In any case, nibbler.port is also more explicit, so it takes priority. To experiment, set the environment variable NIBBLER_PORT to something like 8001 and start the app. You'll see that environment variables in the sample app take priorityover file-defined values. If you remove all environment variables and values in the JSON file, you'll see the server start without listening on a port. Your app can define sources its own way, so keep in mind the sample is just one demonstration of how this can be done.

  • the environment variables can be directly mapped to JSON values (when provided as a source to LoadConfiguration). Environment variables are all caps, and underscores are used where dots were used in the JSON format.

Logging

A simple logger must be passed to most Nibbler methods. Some simple logger implementations have been provided:

  • DefaultLogger - logs to the console
  • SilentLogger - logs nothing

Build utilities

A few optional build utilities are included. Specifically, ./build contains a simplistic way to cross-compile as well as embed the git tag into your app.

To use, make something like build/main.go in your app, which contains:

import "github.com/markdicksonjr/nibbler/build"
...
build.ProcessDefaultTargets("BinaryName", "main/main.go")

This will build your app for a few platforms (currently, Windows, darwin, linux).

To access the git tag in your app, use:

import "github.com/markdicksonjr/nibbler/build"
...
build.GitTag

Auto-wiring

To prepare for very complex apps, an auto-wiring mechanism has been added. This mechanism will take a given slice of allocated extensions and assign Extension pointer field values for each of them that is undefined, as well as order the extensions for initialization.

There are currently a few restrictions, however. The current auto-wiring implementation still has some trouble where fields are interfaces. If the field is a pointer to a struct type (e.g. *sendgrid.Extension), the auto-wiring will work fine.

Extensions like the user extension are currently trouble for auto-wiring, as the extension has a field that is an interface (which is also an extension). For now, manually wire something like this, and automatically wire everything else.

Example:

extensions := []nibbler.Extension{
    &A{},
    &A1{},
    &B1{},
    &AB{},
    &B{},
    &C{},
    &BC{},
}
exts, err := nibbler.AutoWireExtensions(extensions, logger)

// check error

err = app.Init(config, logger, extensions)

// check error

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetConfigurationFromSources

func GetConfigurationFromSources(sources []source.Source) (config.Config, error)

GetConfigurationFromSources will handle loading a configuration from a provided list of sources

func LogErrorNonNil added in v0.14.0

func LogErrorNonNil(logger Logger, err error, wrap ...string) error

LogErrorNonNil is a utility function that allows for a conditional check against an error in such a way that it logs the error if it is non-nil. It also allows for an optional "wrap" string to provide extra context to the message

func LogFatalNonNil added in v0.14.0

func LogFatalNonNil(logger Logger, err error, wrap ...string)

LogFatalNonNil is a utility function that handles a conditional check against an error in such a way that it treats non-nil errors as fatal. Specifically, it exits with a non-zero code

func Write200Json added in v0.3.4

func Write200Json(w http.ResponseWriter, content string)

Write200Json is some syntactic sugar to allow for a quick way to write JSON responses with an OK code

func Write201Json added in v0.21.0

func Write201Json(w http.ResponseWriter, content string)

Write201Json is some syntactic sugar to allow for a quick way to write JSON responses with an OK code

func Write204 added in v0.21.0

func Write204(w http.ResponseWriter, content string)

Write204 is some syntactic sugar to allow for a quick way to write JSON responses with a no-content code

func Write401Json added in v0.18.0

func Write401Json(w http.ResponseWriter)

Write401Json

func Write404Json

func Write404Json(w http.ResponseWriter)

Write404Json is some syntactic sugar to allow for a quick way to write JSON responses with a StatusNotFound code

func Write500Json

func Write500Json(w http.ResponseWriter, message string)

Write500Json is some syntactic sugar to allow for a quick way to write JSON responses with an InternalServererror code

func WriteJson added in v0.14.0

func WriteJson(w http.ResponseWriter, content string, code int)

WriteJson is some syntactic sugar to allow for a quick way to write JSON responses with a status code

func WriteStructToJson added in v0.23.1

func WriteStructToJson(w http.ResponseWriter, content interface{}, code int)

WriteJson is some syntactic sugar to allow for a quick way to write JSON responses from structs with a status code

Types

type Application

type Application struct {
	Config *Configuration
	Logger Logger
	Router *mux.Router
	// contains filtered or unexported fields
}

Application stores the state of the running application

func (*Application) Init

func (ac *Application) Init(config *Configuration, logger Logger, extensions []Extension) error

func (*Application) Run

func (ac *Application) Run() error

Run will put the app into its running state

type Configuration

type Configuration struct {
	Headers         HeaderConfiguration
	Port            int
	Raw             config.Config
	ApiPrefix       string
	StaticDirectory string
}

Configuration is the composite of other configs, including values directly from external sources (Raw)

func LoadConfiguration added in v0.2.0

func LoadConfiguration(sources ...source.Source) (*Configuration, error)

LoadConfiguration will handle loading the configuration from multiple sources, allowing for this module's default source set to be used if none are provided to this function

"merging priority is in reverse order" if nil or empty, file and environment sources used (file takes precedence)

type DefaultLogger

type DefaultLogger struct{}

DefaultLogger logs using Go's standard "log" package, always via Println

func (DefaultLogger) Debug

func (logger DefaultLogger) Debug(message ...string)

func (DefaultLogger) Error

func (logger DefaultLogger) Error(message ...string)

func (DefaultLogger) Info

func (logger DefaultLogger) Info(message ...string)

func (DefaultLogger) Trace added in v0.14.0

func (logger DefaultLogger) Trace(message ...string)

func (DefaultLogger) Warn

func (logger DefaultLogger) Warn(message ...string)

type EmailAddress added in v0.8.0

type EmailAddress struct {
	Name    string
	Address string
}

type Extension

type Extension interface {

	// GetName returns a static name for the extension, primarily for logging purposes
	GetName() string

	// Init should handle the initialization of the extension with the knowledge that the Router and other Extensions
	// are not likely to be initialized yet
	Init(app *Application) error

	// PostInit should handle all initialization that could not be handled in Init.  Specifically, anything requiring
	// the Router and other Extensions to be initialized
	PostInit(app *Application) error

	// Destroy handles application shutdown, with the most recently-initialized extensions destroyed first
	Destroy(app *Application) error
}

func AutoWireExtensions

func AutoWireExtensions(extensions *[]Extension, logger *Logger) ([]Extension, error)

TODO: this will blow up if there's a cycle

type Group added in v0.10.0

type Group struct {
	ID         string           `json:"id" bson:"_id" gorm:"primary_key"`
	CreatedAt  time.Time        `json:"createdAt"`
	UpdatedAt  time.Time        `json:"updatedAt"`
	DeletedAt  *time.Time       `json:"deletedAt,omitempty" sql:"index"`
	Name       string           `json:"name"`
	Type       string           `json:"type"`
	Context    *string          `json:"data,omitempty"`
	Privileges []GroupPrivilege `json:"privileges"`
}

type GroupMembership added in v0.10.0

type GroupMembership struct {
	ID        string     `json:"id" bson:"_id" gorm:"primary_key"`
	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt,omitempty" sql:"index"`
	GroupID   string     `json:"groupId"`
	MemberID  string     `json:"memberId"`
	Role      string     `json:"role"`
}

type GroupPrivilege added in v0.10.0

type GroupPrivilege struct {
	ID         string     `json:"id" bson:"_id" gorm:"primary_key"`
	CreatedAt  time.Time  `json:"createdAt"`
	UpdatedAt  time.Time  `json:"updatedAt"`
	DeletedAt  *time.Time `json:"deletedAt,omitempty" sql:"index"`
	GroupID    string     `json:"groupID" gorm:"foreignkey:GroupID"` // "performing group id" e.g. "administrators" ID
	ResourceID string     `json:"resourceID"`                        // e.g. "customers" ID
	Action     string     `json:"action"`                            // e.g. read/write/admin/etc
}

type HeaderConfiguration

type HeaderConfiguration struct {
	AccessControlAllowHeaders string
	AccessControlAllowMethods string
	AccessControlAllowOrigin  string
}

HeaderConfiguration controls settings for request/response headers

type Logger

type Logger interface {
	Trace(message ...string)
	Debug(message ...string)
	Error(message ...string)
	Info(message ...string)
	Warn(message ...string)
}

Logger is a generic interface to reflect logging output at various levels

type MailSendResponse added in v0.8.0

type MailSendResponse struct {
	StatusCode int
	Body       string
	Headers    map[string][]string
}

type MailSender added in v0.8.0

type MailSender interface {
	SendMail(from *EmailAddress, subject string, to []*EmailAddress, plainTextContent string, htmlContent string) (*MailSendResponse, error)
}

type NoOpExtension

type NoOpExtension struct {
	Logger Logger
}

NoOpExtension implements nibbler.Extension, but all methods do nothing This is provided as a base class that can be used to more tersely define extensions

func (*NoOpExtension) Destroy

func (s *NoOpExtension) Destroy(app *Application) error

func (*NoOpExtension) GetName added in v0.14.0

func (s *NoOpExtension) GetName() string

func (*NoOpExtension) Init

func (s *NoOpExtension) Init(app *Application) error

func (*NoOpExtension) PostInit added in v0.14.0

func (s *NoOpExtension) PostInit(app *Application) error

type SearchParameters added in v0.21.0

type SearchParameters struct {
	Query        interface{}             `json:"query,omitempty"`
	Offset       *int                    `json:"offset,omitempty"`
	Size         *int                    `json:"size,omitempty"`
	IncludeTotal bool                    `json:"includeTotal"`
	SortBy       []SortByFieldParameters `json:"sortBy"`
}

type SearchResults added in v0.21.0

type SearchResults struct {
	Hits   interface{} `json:"hits"`
	Offset *int        `json:"offset,omitempty"`
	Total  *int        `json:"total,omitempty"`
}

type SilentLogger

type SilentLogger struct{}

SilentLogger simply doesn't do anything when logs are to be written

func (SilentLogger) Debug

func (logger SilentLogger) Debug(message ...string)

func (SilentLogger) Error

func (logger SilentLogger) Error(message ...string)

func (SilentLogger) Info

func (logger SilentLogger) Info(message ...string)

func (SilentLogger) Trace added in v0.14.0

func (logger SilentLogger) Trace(message ...string)

func (SilentLogger) Warn

func (logger SilentLogger) Warn(message ...string)

type SortByFieldParameters added in v0.23.1

type SortByFieldParameters struct {
	Field       string
	IsAscending bool
}

type User added in v0.10.0

type User struct {
	ID                        string     `json:"id" bson:"_id" gorm:"primary_key"`
	CreatedAt                 time.Time  `json:"createdAt"`
	UpdatedAt                 time.Time  `json:"updatedAt"`
	DeletedAt                 *time.Time `json:"deletedAt,omitempty" sql:"index"`
	Email                     *string    `json:"email,omitempty" gorm:"unique"`
	Username                  *string    `json:"username,omitempty" gorm:"unique"`
	FirstName                 *string    `json:"firstName,omitempty"`
	MiddleName                *string    `json:"middleName,omitempty"`
	LastName                  *string    `json:"lastName,omitempty"`
	MaidenName                *string    `json:"maidenName,omitempty"`
	Title                     *string    `json:"title,omitempty"`
	Password                  *string    `json:"password,omitempty"`
	PortraitUri               *string    `json:"portraitUri,omitempty"`
	AvatarUri                 *string    `json:"avatarUri,omitempty"`
	StatusText                *string    `json:"statusText,omitempty"`
	IsActive                  *bool      `json:"isActive,omitempty"`
	IsEmailValidated          *bool      `json:"isEmailValidated,omitempty"`
	DeactivatedAt             *time.Time `json:"deactivatedAt,omitempty"`
	LastLogin                 *time.Time `json:"lastLogin,omitempty"`
	FailedLoginCount          *int8      `json:"failedLoginCount,omitempty"`
	Gender                    *string    `json:"gender,omitempty" gorm:"size:1"`
	PhoneHome                 *string    `json:"phoneHome,omitempty" gorm:"size:24"`
	PhoneWork                 *string    `json:"phoneWork,omitempty" gorm:"size:24"`
	PhoneMobile               *string    `json:"phoneMobile,omitempty" gorm:"size:24"`
	PhoneOther                *string    `json:"phoneOther,omitempty" gorm:"size:24"`
	Fax                       *string    `json:"fax,omitempty" gorm:"size:24"`
	Uri                       *string    `json:"uri,omitempty"`
	Birthday                  *string    `json:"birthday,omitempty"`
	Bio                       *string    `json:"bio,omitempty"`
	AddressLine1              *string    `json:"addressLine1,omitempty"`
	AddressLine2              *string    `json:"addressLine2,omitempty"`
	AddressLine3              *string    `json:"addressLine3,omitempty"`
	AddressCity               *string    `json:"addressCity,omitempty"`
	AddressStateOrProvince    *string    `json:"addressStateOrProvince,omitempty"`
	AddressPostalCode         *string    `gorm:"size:16" json:"postalCode,omitempty"`
	CountryCode               *string    `gorm:"size:3" json:"countryCode,omitempty"`
	CompanyID                 *string    `json:"companyId,omitempty"`
	EmployeeID                *string    `json:"employeeId,omitempty"`
	SupervisorID              *string    `json:"supervisorId,omitempty"`
	CreatedByUserID           *string    `json:"createdByUserId,omitempty"`
	ReferenceID               *string    `json:"referenceId,omitempty"`
	PasswordResetToken        *string    `json:"passwordResetToken,omitempty"`
	PasswordResetExpiration   *time.Time `json:"passwordResetExpiration,omitempty"`
	EmailValidationToken      *string    `json:"emailValidationToken,omitempty"`
	EmailValidationExpiration *time.Time `json:"emailValidationExpiration,omitempty"`
	EmploymentStartDate       *time.Time `json:"employmentStartDate,omitempty"`
	EmploymentEndDate         *time.Time `json:"employmentEndDate,omitempty"`
	ContractStartDate         *time.Time `json:"contractStartDate,omitempty"`
	ContractEndDate           *time.Time `json:"contractEndDate,omitempty"`
	PrimaryLocation           *string    `json:"primaryLocation,omitempty"` // e.g. lat/long, grid codes, etc
	CurrentGroupID            *string    `json:"currentGroupId,omitempty"`
	Context                   *string    `json:"context,omitempty"`          // to store extra data we don't have modeled
	ProtectedContext          *string    `json:"protectedContext,omitempty"` // to store extra data not modeled that users shouldn't see
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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