attache

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Nov 30, 2020 License: GPL-2.0 Imports: 28 Imported by: 0

README

Attache

godoc Go Report Card CircleCI

Since this project is still a work in progress, please make sure you test it out before deciding to use it.

Preface

A couple of years ago, I got an idea. At the time, it was a fuzzy concept for "some kind of web framework" for Go. I ignored this idea for the longest time, because I felt Go was not conducive to that kind of project. That's not a slight at Go, I just felt that the language didn't really need it. As time went on I started building code that I re-used between several personal (read: half-finished, never-published) projects. I started to coalesce those items into a package. Over time, that package became unruly. I started refactoring. I started standardizing. I started toying with weird ideas and making bad decisions, removing them, and trying something new.

Eventually, I ended up with Attache, a really lame play on words:

  1. Attachè, noun: person on the staff of an ambassador, typically with a specialized area of responsibility
  2. Attache, mispronounced like "Apache" ('cuz it's a web server)).

I've been using this "framework" (which I think of as more of a collection of tools) in some freelance work recently and I really enjoy it. It makes some things much easier by allowing me to avoid some boilerplate each time I start a web app that uses Go, but without forcing me into a particular application structure.

This is my longest running personal project. For better or for worse, I think it could use some new ideas and new eyes.

Documentation is a little sparse (I'm working on it, but it takes time and I have a full time job outside of this :( ).

I'm a little nervous. All of this is my own code right now. It feels weird to put so much out there for the community to see and judge. I like this, though, and I hope maybe you all will as well.

Installation

$> go get -u github.com/attache/attache/...

Usage

Getting Started

The CLI is the canonical way to start a new Attache application. An example of a working application can be found in examples/todo

Create a new Attache application
$> attache new -n MyApp
$> cd ./my_app

Inside the my_app folder, you will find the basic structure of the application:

/my_app/            - root
|  /models/         - directory where Attache generates models
|  /views/          - directory where Attache generates views
|  |  index.tpl
|  |  layout.tpl
|  /web/            - directory where frontend files live
|  |  /dist/        - default public file server root 
|  |  |  /css/
|  |  |  /img/
|  |  |  /js/
|  |  /src/         - where TypeScript, Less, Sass, etc. should be written before compiling
|  |  |  /script/
|  |  |  /styles/
|  /secret/         - files that should be ignored by git
|  |  run.sh
|  |  schema.sql
|  main.go          - basic application structure & startup
|  .at-conf.json    - Attache CLI configuration - don't touch this
|  .gitignore

Run

# from within the application's root directory
$> ./secret/run.sh

Then, visit http://localhost:8080/, you should see a page with

Welcome to your Attache application!

Database Connection

To get Attache to manage a database connection for you, embed the attache.DefaultDB type into your content type. You can do so by uncommenting the auto-generated line. This will inspect 2 environment variables, DB_DRIVER and DB_DSN, and attempt to use these to establisha a database connection. This connection will then be available to your Context's methods via the .DB() method of the embedded attache.DefaultDB type when a context is initialized by Attache to handle a request

type MyApp struct {
        // required
        attache.BaseContext

        // capabilities
        attache.DefaultFileServer
        attache.DefaultViews
        attache.DefaultDB // <--- UNCOMMENT THIS LINE
        // attache.DefaultSession // enable session storage
}

Once you have enabled database connectivity, you can begin to generate (or manually build)models to represent your schema.

Let's say you have todos table in your database.

CREATE TABLE `todos` (
	`title` TEXT PRIMARY KEY NOT NULL,
	`desc`  TEXT NOT NULL DEFAULT ""
);

You can use the CLI to generate a model, views, and routes.

Create a model, view, and routes
# from within the application's root directory
$> attache gen -n Todo -t todos -f Title:string:key -f Desc:string
# creates   ./models/todos.go
#           ./views/todos/create.tpl
#           ./views/todos/list.tpl
#           ./views/todos/update.tpl
#           ./todos_routes.go
Create just models, just views, or just routes
# from within the application's root directory

# just generate the model
$> attache gen -model [...]

# just generate the views
$> attache gen -views [...]

# just generate the routes
$> attache gen -routes [...]

# just generate some combination
$> attache gen -routes -models [...]
Replace existing files
# from within the application's root directory
$> attache gen -replace [...]
Generate JSON routes (and no views)

If you want to generate the routes to serve JSON data rather than views, you can include the -json flag in your command. This will generate routes for a JSON-based API, and prevent generation of views.

# from within the application's root directory
$> attache gen -json [...]
Use CLI plugin

The CLI is extensible. It can take advantage of dynamically linked plugins, which can be used like so:

# attempts to load plugin from $HOME/.attache/plugins/PLUGNAME.so
$> attache PLUGNAME [...]

Contributing

If you'd like to help, here are the high-level TODOs:
  1. Documentation Wiki
    • document the method naming conventions
    • document some example applications
    • etc...
  2. Add CLI plugins for common use cases
    • Generate content for features such as Authorization, REST API for a database schema, etc
    • Embed other code generation tools
      • github.com/xo/xo
      • github.com/cheekybits/genny
      • etc...
  3. Add CLI plugin subcommand for managing installation of plugins
    • maybe have a central repo with checksum verification?
  4. Add context capabilities for
    • Config (maybe github.com/spf13/viper)
  5. Code cleanup and package re-org

Documentation

Overview

Package attache: an arsenal for the web.

Attachè (mispronounced for fun as "Attachey", said like "Apache") is a new kind of web framework for go. It allows you to define your applications in terms of methods on a context type that is instantiated for each request.

An Attachè Application is bootstrapped from a context type that implements the Context interface. In order to enforce that the context type is a struct type at compile time, embedding the BaseContext type is necessary to satisfy this interface. Functionality is provided by additional methods implemented on the context type.

Attachè provides several capabilities (optional interfaces) that a Context type can implement. These interfaces take the form of "Has...". They are documented individually.

Aside from these capabilites, a Context can provide several other types of methods:

1. Route methods Routing methods must follow the naming convention of <HTTP METHOD>_<[PathInCamelCase]> PathInCamelCase is optional, and defaults to the empty string. In this case, the method name would still need to include the underscore (i.e. GET_). The path which a routing method serves is determined by converting the PathInCamelCase to snake case, and then replacing underscores with the path separator. Examples:

(empty)				-> matches /
Index				-> matches /index
TwoWords			-> matches /two/words
Ignore_underscores	-> matches /ignoreunderscores (for making long names easier to read without affecting the path)
TESTInitialism		-> matches /test/initialism

Route methods can take any number/types of arguments. Values are provided by the Application's dependency injection context for this request. If a value isn't available for an argument's type, the zero-value is used. Return values are unchecked, and really shouldn't be provided. That limitation is not enforced in the code, to provide flexibility.

If a Context type provides a BEFORE_ method matching the name of the Route method, it will be run immediately before the Route method in the handler stack

If a Context type provides an AFTER_ method matching the name of the Route method, it will be run immediately after the Route method in the handler stack. Although there is no hard enforcement, an AFTER_ method should assume the ResponseWriter has been closed.

2. Mount methods Mount methods must follow the naming convention of MOUNT_<[PathInCamelCase]> and have the signature func() (http.Handler, error). They are only called once, on an uninitialized Context during bootstrapping. If an error is returned (or the method panics), bootstrapping will fail. If there is no error, the returned http.Handler is mounted at the path specified by PathInCamelCase, using the same method as Routing methods. The mounted handler is called with the mounted prefix stripped from the request.

3. Guard methods Mount methods must follow the naming convention of GUARD_<[PathInCamelCase]>. The path at which a Guard method is applied is determined from PathInCamelCase, in the same way as a Route method. A Guard method will run for the path at which it is registered, as well as any paths that contain the guarded path. To prevent the handler stack from continuing execution, the Guard method must either panic or call one of the Attachè helper methods (Error, ErrorFatal, ErrorMessage, etc...). If there are multiple Guard methods on a path, they are run in they order they are encountered (i.e. the guards for shorter paths run before the guards for longer paths).

4. Provider methods Provider methods must follow the naming convention of PROVIDE_<UniqueDescriptiveName> and have the signature func(*http.Request) interface{}. Provider methods are called when a route or a guarded mount match the request's path. The returned value is available for injection into any handlers in the handler stack for the current request. Because of the frequency with which these are called, it is best to define as few as possible.

Attachè also comes with a CLI. Currently, the supported commands are `attache new` and `attache gen`. Please see README.md for more info.

Index

Constants

This section is empty.

Variables

View Source
var ErrRecordNotFound = dbr.ErrNotFound

Functions

func Error

func Error(code int)

Error is the equivalent of calling ErrorMessage with an empty message

func ErrorFatal

func ErrorFatal(err error)

ErrorFatal logs err and then calls Error(500)

func ErrorMessage

func ErrorMessage(code int, msg string, args ...interface{})

ErrorMessage immediately terminates the executing handler chain with the given status code and status text

func ErrorMessageJSON

func ErrorMessageJSON(code int, msg string, args ...interface{})

ErrorMessageJSON immediately terminates the executing handler chain with the given status code and a json body containing the status text

func FormDecode

func FormDecode(dst interface{}, src map[string][]string) error

FormDecode decodes the form represented by src into dst. dst must be a pointer

func LoadEnvironment

func LoadEnvironment(conf EnvironmentConfig) error

LoadEnvironment will attempt to load environment variables based on the given EnvironmentConfig

func RedirectPage

func RedirectPage(path string)

RedirectPage immediately terminates the executing handler chain with a 303 (See Other)

func RedirectPermanent

func RedirectPermanent(path string)

RedirectPermanent immediately terminates the executing handler chain with a 308 (Permanent Redirect)

func RedirectTemporary

func RedirectTemporary(path string)

RedirectTemporary immediately terminates the executing handler chain with a 307 (Temporary Redirect)

func RegisterFormConverter

func RegisterFormConverter(val interface{}, converterFunc FormConverter)

RegisterFormConverter registers a FormConverter function that will return values of type reflect.TypeOf(val)

func RenderHTML

func RenderHTML(ctx interface {
	Context
	HasViews
}, name string)

RenderHTML renders the view with the given name from the ViewCache attached to ctx using data as the Execute's data argument. The rendered template is written to w with a Content-Type of "text/html". ErrorFatal is called for any error encountered

func RenderJSON

func RenderJSON(w http.ResponseWriter, data interface{})

RenderJSON marshals data to JSON, then writes the data to w with a Content-Type of "application/json". ErrorFatal is called for any error encountered

func Success

func Success()

Success immediately terminates the executing handler chain with a 200 OK

func ViewCacheRefresh

func ViewCacheRefresh(conf ViewConfig) error

ViewCacheRefresh will re-initialize the ViewCache for the given ViewConfig.

Types

type AfterDeleter

type AfterDeleter interface{ AfterDelete(sql.Result) }

AfterDeleter can be implemented by a Record type to provide a callback after a deletion. The callback has access to the returned sql.Result

type AfterInserter

type AfterInserter interface{ AfterInsert(sql.Result) }

AfterInserter can be implemented by a Record type to provide a callback after insertion. The callback has access to the returned sql.Result

type AfterUpdater

type AfterUpdater interface{ AfterUpdate(sql.Result) }

AfterUpdater can be implemented by a Record type to provide a callback after an update. The callback has access to the returned sql.Result

type Application

type Application struct {

	// NoLogging can be set to true to disable logging
	NoLogging bool
	// contains filtered or unexported fields
}

An Application routes HTTP traffic to an instance of its associated concrete Context type

func Bootstrap

func Bootstrap(ctxType Context) (*Application, error)

Bootstrap attempts to create an Application to serve requests for the provided concrete Context type. If an error is encountered during the bootstrapping process, it is returned. If a nil *Application is returned, the returned error will be non-nil.

func (*Application) Run

func (a *Application) Run(addr ...string) error

Run starts listening for HTTP requests to the application at the given bind address, or the default of :8080 if none is specified.

func (*Application) RunTLS

func (a *Application) RunTLS(certFile, keyFile string, addr ...string) error

RunTLS starts listening for HTTPS requests to the application at the given bind address, or the default of :8443 if none is specified.

func (*Application) RunWithServer

func (a *Application) RunWithServer(s *http.Server) error

RunWithServer binds the application to the server, and begins listening for HTTP requests.

func (*Application) RunWithServerTLS

func (a *Application) RunWithServerTLS(s *http.Server, certFile, keyFile string) error

RunWithServerTLS binds the application to the server, and begins listening for HTTPS requests.

func (*Application) ServeHTTP

func (a *Application) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler for *Application.

type BaseContext

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

BaseContext must be embedded into any Context implementation, thus enforcing that all Context implementations are struct types

func (*BaseContext) Request

func (b *BaseContext) Request() *http.Request

Request returns the *http.Request for the current request scope

func (*BaseContext) ResponseWriter

func (b *BaseContext) ResponseWriter() http.ResponseWriter

ResponseWriter returns the http.ResponseWriter for the current request scope

type BeforeDeleter

type BeforeDeleter interface{ BeforeDelete() (err error) }

BeforeDeleter can be implemented by a Record type to provide a callback before a deletion. If a non-nil error is returned, the deletion is aborted

type BeforeInserter

type BeforeInserter interface{ BeforeInsert() error }

BeforeInserter can be implemented by a Record type to provide a callback before insertion. If a non-nil error is returned, the insert operation is aborted

type BeforeUpdater

type BeforeUpdater interface{ BeforeUpdate() (err error) }

BeforeUpdater can be implemented by a Record type to provide a callback before an update. If a non-nil error is returned, the update operation is aborted

type BootstrapError

type BootstrapError struct {
	Cause error
	Phase string
}

A BootstrapError is returned by the Bootstrap function, incidating that the error was generated during the bootstrapping process

func (BootstrapError) Error

func (b BootstrapError) Error() string

type Context

type Context interface {
	Init(http.ResponseWriter, *http.Request)
	Request() *http.Request
	ResponseWriter() http.ResponseWriter
	// contains filtered or unexported methods
}

Context is the base set of methods that a concrete Context type must provide. It contains private members, thus requiring that the BaseContext type be embedded in all types implementing Context

type DB

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

func DBFor

func DBFor(conf DBConfig) (DB, error)

DBFor will returh the cached DB or a newly initialized DB for the given DBConfig. Any error encountered while initializing a new DB is returned.

func (DB) All

func (db DB) All(typ Record) ([]Record, error)

func (DB) Delete

func (db DB) Delete(r Record) error

func (DB) Get

func (db DB) Get(into Record, keyVals ...interface{}) error

func (DB) GetBy

func (db DB) GetBy(into Record, where string, args ...interface{}) error

func (DB) Insert

func (db DB) Insert(r Record) error

func (DB) Raw

func (db DB) Raw() *dbr.Session

func (DB) Tx

func (db DB) Tx(block func(tx TX) error) error

func (DB) Update

func (db DB) Update(r Record) error

func (DB) Where

func (db DB) Where(typ Record, where string, args ...interface{}) ([]Record, error)

func (DB) WhereFilter

func (db DB) WhereFilter(typ Record, where string) ([]Record, error)

type DBConfig

type DBConfig struct {
	Driver, DSN string
}

DBConfig provides configuration options for a database connection

type DBRunner

type DBRunner interface {
	// Insert inserts the Record into the database
	Insert(Record) error

	// Update updates the Record in the database
	Update(Record) error

	// Delete deletes the Record from the database
	Delete(Record) error

	// All reutrns all Records of the specified type
	All(typ Record) ([]Record, error)

	// Get fetches a single record whose key values match those provided into the target Record
	Get(into Record, keyVals ...interface{}) error

	// GetBy fetches a single record matching the where condition into the target Record
	GetBy(into Record, where string, args ...interface{}) error

	// Where finds all records of the specified type matching the where query
	Where(typ Record, where string, args ...interface{}) ([]Record, error)

	// WhereFilter finds all records of the specified type matching the filterString
	WhereFilter(typ Record, filterString string) ([]Record, error)
}

DBRunner is the set of functions provided by both DB and TX

type DefaultDB

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

DefaultDB is a type that can be embedded into a Context type to enable a database connection with default configuration options

func (*DefaultDB) CONFIG_DB

func (d *DefaultDB) CONFIG_DB() DBConfig

CONFIG_DB implements HasDB for DefaultDB

func (*DefaultDB) DB

func (d *DefaultDB) DB() DB

DB implements HasDB for DefaultDB

func (*DefaultDB) SetDB

func (d *DefaultDB) SetDB(db DB)

SetDB implements HasDB for DefaultDB

type DefaultEnvironment

type DefaultEnvironment struct{}

DefaultEnvironment is a type that can be embedded into a Context type to enable auto-load of environment variables with default configuration options

func (*DefaultEnvironment) CONFIG_Environment

func (*DefaultEnvironment) CONFIG_Environment() EnvironmentConfig

CONFIG_Environment implements HasEnvironment for DefaultEnvironment

type DefaultFileServer

type DefaultFileServer struct{}

DefaultFileServer is a type that can be embedded into a Context type to enable a static file server with default configuration options

func (DefaultFileServer) MOUNT_Web

func (DefaultFileServer) MOUNT_Web() (http.Handler, error)

MOUNT_Web provides a static file server under the path /web/*

type DefaultSession

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

DefaultSession is a type that can be embedded into a Context type to enable user sessions

func (*DefaultSession) CONFIG_Session

func (d *DefaultSession) CONFIG_Session() SessionConfig

CONFIG_Session implements HasSession for DefaultSession

func (*DefaultSession) Session

func (d *DefaultSession) Session() Session

Session implements HasSession for DefaultSession

func (*DefaultSession) SetSession

func (d *DefaultSession) SetSession(s Session)

SetSession implements HasSession for DefaultSession

type DefaultViews

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

DefaultViews is a type that can be embedded into a Context type to enable views with default configuration options

func (*DefaultViews) CONFIG_Views

func (d *DefaultViews) CONFIG_Views() ViewConfig

CONFIG_Views implements HasViews for DefaultViews

func (*DefaultViews) SetViewData

func (d *DefaultViews) SetViewData(data interface{})

SetViewData implements HasViews for DefaultViews

func (*DefaultViews) SetViews

func (d *DefaultViews) SetViews(v ViewCache)

SetViews implements HasViews for DefaultViews

func (*DefaultViews) ViewData

func (d *DefaultViews) ViewData() interface{}

ViewData implements HasViews for DefaultViews

func (*DefaultViews) Views

func (d *DefaultViews) Views() ViewCache

Views implements HasViews for DefaultViews

type EnvironmentConfig

type EnvironmentConfig struct {
	EnvPath string
}

EnvironmentConfig provides configuration options for auto-loading environment variables

type FileServerConfig

type FileServerConfig struct {
	Root, BasePath string
}

FileServerConfig provides configuration options for starting a file server

type FormConverter

type FormConverter func(string) reflect.Value

A FormConverter is a function that can convert a string value from an HTTP form to a concrete type

type HasDB

type HasDB interface {
	CONFIG_DB() DBConfig
	SetDB(DB)
	DB() DB
}

HasDB is the interaface implemented by Context types that use a database connection

type HasEnvironment

type HasEnvironment interface {
	CONFIG_Environment() EnvironmentConfig
}

HasEnvironment is the interface implemented by Context types that auto-load environment variables

type HasSession

type HasSession interface {
	CONFIG_Session() SessionConfig
	SetSession(Session)
	Session() Session
}

HasSession is the interface implemented by Context types that use user sessions

type HasViews

type HasViews interface {
	CONFIG_Views() ViewConfig
	SetViews(ViewCache)
	Views() ViewCache

	ViewData() interface{}
	SetViewData(data interface{})
}

HasViews is the interface implemented by Context types that use server-rendered views

type Record

type Record interface {
	// Table returns the name of the table that represents
	// the Record object in the database
	Table() string

	// Key returns the columns and values that compose the primary key
	// for the Record object
	Key() ([]string, []interface{})

	// InsertColumns returns the columns and values to be used for a default INSERT operation
	Insert() (columns []string, values []interface{})

	// UpdateColumns returns the columns and values to be used for a default UPDATE operation
	Update() (columns []string, values []interface{})
}

Record is the interface implemented by any type that can be stored/retrieved via database/sql

type Session

type Session struct {
	*sessions.Session
}

type SessionConfig

type SessionConfig struct {
	Name   string
	Secret []byte
}

SessionConfig provides configuration options for user sessions

type TX

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

func (TX) All

func (db TX) All(typ Record) ([]Record, error)

func (TX) Delete

func (db TX) Delete(r Record) error

func (TX) Get

func (db TX) Get(into Record, keyVals ...interface{}) error

func (TX) GetBy

func (db TX) GetBy(into Record, where string, args ...interface{}) error

func (TX) Insert

func (db TX) Insert(r Record) error

func (TX) Raw

func (db TX) Raw() *dbr.Tx

func (TX) Update

func (db TX) Update(r Record) error

func (TX) Where

func (db TX) Where(typ Record, where string, args ...interface{}) ([]Record, error)

func (TX) WhereFilter

func (db TX) WhereFilter(typ Record, where string) ([]Record, error)

type View

type View = viewDriver.View

A View is the interface implemented by a renderable type

type ViewCache

type ViewCache interface {
	// Get will return a valid view if one is stored under the given name,
	// otherwise it should return a valid no-op view.
	Get(name string) View
}

A ViewCache is a read-only view of a set of cached views. Implementations must be safe for concurrent use.

func ViewCacheFor

func ViewCacheFor(conf ViewConfig) (ViewCache, error)

ViewCacheFor will return the cached ViewCache or a newly initialized ViewCache for the given ViewConfig. Any error encountered while initializing the new ViewCahce is returned.

type ViewConfig

type ViewConfig struct {
	Driver, Root string
}

ViewConfig provides configuration options for initializing a ViewCache

Directories

Path Synopsis
cmd
drivers

Jump to

Keyboard shortcuts

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