github.com/seeker-insurance/kit@v0.0.13/README.md (about)

     1  # kit
     2  
     3  EyeCueLab's `kit` is a repository of open-source golang libraries developed by EyeCueLab for use in our various projects.
     4  
     5  ## philosophy / notes
     6  
     7  EyeCueLab tries to follow the philosphy of the [twelve-factor app](https://12factor.net).  All non-business logic should be open & accessible to the public, and configuration details should be in the environment.
     8  
     9  Accordingly, EyeCueLab uses the [cobra command-line interface](github.com/spf13/cobra) and the [viper configuration library](https://github.com/spf13/viper). Many parts of `kit` either rely on these libraries for configuration or hook into them during init() so that cobra/viper can access them.
    10  
    11  ## Testing
    12  
    13  Navigate to the root of the source directory for kit
    14  
    15  ```bash
    16  go test ./... -cover
    17  ```
    18  
    19  ## address
    20  
    21  `address` contains tools for storing and comparing street addreses.
    22  
    23  ## assets
    24  
    25  (TODO)
    26  
    27  ## brake
    28  
    29  `brake` contains tools for setting up and working with the [airbrake](https://airbrake.io/) error monitoring software.
    30  
    31  ## branch
    32  
    33  (TODO)
    34  
    35  ## cmd
    36  
    37  `cmd` contains helper fucntions for the [cobra command-line interface](https://github.com/spf13/cobra)
    38  
    39  ## coerce
    40  
    41  `coerce` contains functions which use reflection to coerce various numeric types using c-style numeric promotion. This is mostly for testing.
    42  
    43  ## config
    44  
    45  `config` contains helper functions to help set up [viper configuration library](https://github.com/spf13/viper)
    46  
    47  ## counter
    48  
    49  `counter` implements various counter types, similar to python's collections.counter
    50  
    51  ## db
    52  
    53  `db` contains tools for various databases. See `db/mongo` or `db/psql`.
    54  
    55  ## db/mongo
    56  
    57  `db/mongo` helps connect to our mongoDB instances and provides various helper functions 
    58  
    59  ## db/psql
    60  
    61  `db/psql` helps connect to psql instances and provides various helper functions
    62  
    63  ## dockerfiles
    64  
    65  (TODO)
    66  
    67  ## env
    68  
    69  `env` contains tools for accessing and manipulating environment variables
    70  
    71  ## errorlib
    72  
    73  `errorlib` contains tools to deal with errors, including the heavily-used `ErrorString` type (for errors that are compile-time constants) and `LoggedChannel` function (to report non-fatal errors during concurrent execution)
    74  
    75  ## flect
    76  
    77  `flect` is meant to work alongside go's `reflect` library, containing additional runtime reflection tools
    78  
    79  ## geojson
    80  
    81  `geojson` contains an interface and various structs for 2d and 3d geometry that correspond to the [geojson](http://geojson.org/) format.
    82  
    83  ## goenv
    84  
    85  (TODO)
    86  
    87  ## imath
    88  
    89  `imath` is contains tools for signed integer math. it largely corresponds with go's built in `math` library for float64s
    90  
    91  ### imath/operator
    92  
    93  `imath/operator` contains functions which represent all of golang's built in operators,as well as bitwise operators
    94  
    95  ## log
    96  
    97  `log` is an extension of the [logrus](https://github.com/sirupsen/logrus) package. It contains various tools for logging information and errors during runtime.
    98  
    99  ## mailman
   100  
   101  (TODO)
   102  
   103  ## maputil
   104  
   105  `maputil` contains various helper functions for maps with string keys.
   106  
   107  the maputil package itself covers `map[string]interface{}`
   108  
   109  ### maputil/string
   110  
   111  `maputil` contains helper functions for the type `map[string]string`
   112  
   113  ## oauth
   114  
   115  (TODO)
   116  
   117  ## pretty
   118  
   119  `pretty`  provides pretty-printing for go values. It is a fork of the [kr/pretty](https://github.com/kr/pretty) package, obtained under the MIT license.
   120  
   121  ## random
   122  
   123  `random` provides tools for generating cryptographically secure random elements. it uses golang's built in `crypto/rand` for it's RNG.
   124  
   125  ### random/shuffle
   126  
   127  `random/shuffle` provides tools for creating shuffled _copies_ of various golang slice types using the fisher-yates shuffle. It uses `crypto/rand` for it's RNG.
   128  
   129  ## retry
   130  
   131  (TODO)
   132  
   133  ## runeset
   134  
   135  `runeset` implements a set of runes and various methods for dealing with strings accordingly. it should probably be folded into the `set` package.
   136  
   137  ## s3
   138  
   139  `s3` contains helper functions for dealing with amazon's aws/s3 and integrating it with the cobra CLI.
   140  
   141  ## set
   142  
   143  `set` implements various set types and associated methods; union, intersection, containment, etc. see `runeset` for a set of runes (this will be folded into set.)
   144  
   145  ## str
   146  
   147  `str` contains various tools for manipulation of strings beyond those available in golang's `strings` library. it also contains a wide variety of string constants.
   148  
   149  ## stringslice
   150  
   151  `stringslice` contains various functions to work with slices of strings and the strings contained within.
   152  
   153  ### set/int
   154  
   155  `set/int` implements a set type for `int`
   156  
   157  ### set/string
   158  
   159  `set/string` implements a set type for `string`
   160  
   161  ## sortlib
   162  
   163  `sortlib` contains tools for producing sorted _copies_ of various golang types
   164  
   165  ## tickertape
   166  
   167  `tickertape` provides an implenetation of a concurrency-safe 'ticker tape' of current information during  a running program - that is, repeatedly updating the same line with new information.
   168  
   169  ## tsv
   170  
   171  `tsv` contains tools for dealing with tab-separated values.
   172  
   173  ## umath
   174  
   175  `umath` contains various math functions for unsigned integers, roughly corresponding with `imath` and golang's built in `math` package.
   176  
   177  ## web
   178  
   179  `web` is the bones of our web framework, built on top of google's [jsonapi framework](https://github.com/seeker-insurance/jsonapi) and labstack's [echo framework](https://github.com/labstack/echo).
   180  
   181  ### web/middleware
   182  
   183  (TODO)
   184  
   185  ### web/server
   186  
   187  (TODO)
   188  
   189  ### web/testing
   190  
   191  (TODO)
   192  
   193  ### web/webtest
   194  
   195  (TODO)
   196  
   197  
   198  # jsonapi
   199  
   200  [![Build Status](https://travis-ci.org/google/jsonapi.svg?branch=master)](https://travis-ci.org/google/jsonapi)
   201  [![Go Report Card](https://goreportcard.com/badge/github.com/google/jsonapi)](https://goreportcard.com/report/github.com/google/jsonapi)
   202  [![GoDoc](https://godoc.org/github.com/google/jsonapi?status.svg)](http://godoc.org/github.com/google/jsonapi)
   203  
   204  A serializer/deserializer for JSON payloads that comply to the
   205  [JSON API - jsonapi.org](http://jsonapi.org) spec in go.
   206  
   207  ## Installation
   208  
   209  ```
   210  go get -u github.com/google/jsonapi
   211  ```
   212  
   213  Or, see [Alternative Installation](#alternative-installation).
   214  
   215  ## Background
   216  
   217  You are working in your Go web application and you have a struct that is
   218  organized similarly to your database schema.  You need to send and
   219  receive json payloads that adhere to the JSON API spec.  Once you realize that
   220  your json needed to take on this special form, you go down the path of
   221  creating more structs to be able to serialize and deserialize JSON API
   222  payloads.  Then there are more models required with this additional
   223  structure.  Ugh! With JSON API, you can keep your model structs as is and
   224  use [StructTags](http://golang.org/pkg/reflect/#StructTag) to indicate
   225  to JSON API how you want your response built or your request
   226  deserialized.  What about your relationships?  JSON API supports
   227  relationships out of the box and will even put them in your response
   228  into an `included` side-loaded slice--that contains associated records.
   229  
   230  ## Introduction
   231  
   232  JSON API uses [StructField](http://golang.org/pkg/reflect/#StructField)
   233  tags to annotate the structs fields that you already have and use in
   234  your app and then reads and writes [JSON API](http://jsonapi.org)
   235  output based on the instructions you give the library in your JSON API
   236  tags.  Let's take an example.  In your app, you most likely have structs
   237  that look similar to these:
   238  
   239  
   240  ```go
   241  type Blog struct {
   242  	ID            int       `json:"id"`
   243  	Title         string    `json:"title"`
   244  	Posts         []*Post   `json:"posts"`
   245  	CurrentPost   *Post     `json:"current_post"`
   246  	CurrentPostId int       `json:"current_post_id"`
   247  	CreatedAt     time.Time `json:"created_at"`
   248  	ViewCount     int       `json:"view_count"`
   249  }
   250  
   251  type Post struct {
   252  	ID       int        `json:"id"`
   253  	BlogID   int        `json:"blog_id"`
   254  	Title    string     `json:"title"`
   255  	Body     string     `json:"body"`
   256  	Comments []*Comment `json:"comments"`
   257  }
   258  
   259  type Comment struct {
   260  	Id     int    `json:"id"`
   261  	PostID int    `json:"post_id"`
   262  	Body   string `json:"body"`
   263  	Likes  uint   `json:"likes_count,omitempty"`
   264  }
   265  ```
   266  
   267  These structs may or may not resemble the layout of your database.  But
   268  these are the ones that you want to use right?  You wouldn't want to use
   269  structs like those that JSON API sends because it is difficult to get at
   270  all of your data easily.
   271  
   272  ## Example App
   273  
   274  [examples/app.go](https://github.com/google/jsonapi/blob/master/examples/app.go)
   275  
   276  This program demonstrates the implementation of a create, a show,
   277  and a list [http.Handler](http://golang.org/pkg/net/http#Handler).  It
   278  outputs some example requests and responses as well as serialized
   279  examples of the source/target structs to json.  That is to say, I show
   280  you that the library has successfully taken your JSON API request and
   281  turned it into your struct types.
   282  
   283  To run,
   284  
   285  * Make sure you have [Go installed](https://golang.org/doc/install)
   286  * Create the following directories or similar: `~/go`
   287  * Set `GOPATH` to `PWD` in your shell session, `export GOPATH=$PWD`
   288  * `go get github.com/google/jsonapi`.  (Append `-u` after `get` if you
   289    are updating.)
   290  * `cd $GOPATH/src/github.com/google/jsonapi/examples`
   291  * `go build && ./examples`
   292  
   293  ## `jsonapi` Tag Reference
   294  
   295  ### Example
   296  
   297  The `jsonapi` [StructTags](http://golang.org/pkg/reflect/#StructTag)
   298  tells this library how to marshal and unmarshal your structs into
   299  JSON API payloads and your JSON API payloads to structs, respectively.
   300  Then Use JSON API's Marshal and Unmarshal methods to construct and read
   301  your responses and replies.  Here's an example of the structs above
   302  using JSON API tags:
   303  
   304  ```go
   305  type Blog struct {
   306  	ID            int       `jsonapi:"primary,blogs"`
   307  	Title         string    `jsonapi:"attr,title"`
   308  	Posts         []*Post   `jsonapi:"relation,posts"`
   309  	CurrentPost   *Post     `jsonapi:"relation,current_post"`
   310  	CurrentPostID int       `jsonapi:"attr,current_post_id"`
   311  	CreatedAt     time.Time `jsonapi:"attr,created_at"`
   312  	ViewCount     int       `jsonapi:"attr,view_count"`
   313  }
   314  
   315  type Post struct {
   316  	ID       int        `jsonapi:"primary,posts"`
   317  	BlogID   int        `jsonapi:"attr,blog_id"`
   318  	Title    string     `jsonapi:"attr,title"`
   319  	Body     string     `jsonapi:"attr,body"`
   320  	Comments []*Comment `jsonapi:"relation,comments"`
   321  }
   322  
   323  type Comment struct {
   324  	ID     int    `jsonapi:"primary,comments"`
   325  	PostID int    `jsonapi:"attr,post_id"`
   326  	Body   string `jsonapi:"attr,body"`
   327  	Likes  uint   `jsonapi:"attr,likes-count,omitempty"`
   328  }
   329  ```
   330  
   331  ### Permitted Tag Values
   332  
   333  #### `primary`
   334  
   335  ```
   336  `jsonapi:"primary,<type field output>"`
   337  ```
   338  
   339  This indicates this is the primary key field for this struct type.
   340  Tag value arguments are comma separated.  The first argument must be,
   341  `primary`, and the second must be the name that should appear in the
   342  `type`\* field for all data objects that represent this type of model.
   343  
   344  \* According the [JSON API](http://jsonapi.org) spec, the plural record
   345  types are shown in the examples, but not required.
   346  
   347  #### `attr`
   348  
   349  ```
   350  `jsonapi:"attr,<key name in attributes hash>,<optional: omitempty>"`
   351  ```
   352  
   353  These fields' values will end up in the `attributes`hash for a record.
   354  The first argument must be, `attr`, and the second should be the name
   355  for the key to display in the `attributes` hash for that record. The optional
   356  third argument is `omitempty` - if it is present the field will not be present
   357  in the `"attributes"` if the field's value is equivalent to the field types
   358  empty value (ie if the `count` field is of type `int`, `omitempty` will omit the
   359  field when `count` has a value of `0`). Lastly, the spec indicates that
   360  `attributes` key names should be dasherized for multiple word field names.
   361  
   362  #### `relation`
   363  
   364  ```
   365  `jsonapi:"relation,<key name in relationships hash>,<optional: omitempty>"`
   366  ```
   367  
   368  Relations are struct fields that represent a one-to-one or one-to-many
   369  relationship with other structs. JSON API will traverse the graph of
   370  relationships and marshal or unmarshal records.  The first argument must
   371  be, `relation`, and the second should be the name of the relationship,
   372  used as the key in the `relationships` hash for the record. The optional
   373  third argument is `omitempty` - if present will prevent non existent to-one and
   374  to-many from being serialized.
   375  
   376  ## Methods Reference
   377  
   378  **All `Marshal` and `Unmarshal` methods expect pointers to struct
   379  instance or slices of the same contained with the `interface{}`s**
   380  
   381  Now you have your structs prepared to be seralized or materialized, What
   382  about the rest?
   383  
   384  ### Create Record Example
   385  
   386  You can Unmarshal a JSON API payload using
   387  [jsonapi.UnmarshalPayload](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload).
   388  It reads from an [io.Reader](https://golang.org/pkg/io/#Reader)
   389  containing a JSON API payload for one record (but can have related
   390  records).  Then, it materializes a struct that you created and passed in
   391  (using new or &).  Again, the method supports single records only, at
   392  the top level, in request payloads at the moment. Bulk creates and
   393  updates are not supported yet.
   394  
   395  After saving your record, you can use,
   396  [MarshalOnePayload](http://godoc.org/github.com/google/jsonapi#MarshalOnePayload),
   397  to write the JSON API response to an
   398  [io.Writer](https://golang.org/pkg/io/#Writer).
   399  
   400  #### `UnmarshalPayload`
   401  
   402  ```go
   403  UnmarshalPayload(in io.Reader, model interface{})
   404  ```
   405  
   406  Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload)
   407  
   408  #### `MarshalPayload`
   409  
   410  ```go
   411  MarshalPayload(w io.Writer, models interface{}) error
   412  ```
   413  
   414  Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalPayload)
   415  
   416  Writes a JSON API response, with related records sideloaded, into an
   417  `included` array.  This method encodes a response for either a single record or
   418  many records.
   419  
   420  ##### Handler Example Code
   421  
   422  ```go
   423  func CreateBlog(w http.ResponseWriter, r *http.Request) {
   424  	blog := new(Blog)
   425  
   426  	if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil {
   427  		http.Error(w, err.Error(), http.StatusInternalServerError)
   428  		return
   429  	}
   430  
   431  	// ...save your blog...
   432  
   433  	w.Header().Set("Content-Type", jsonapi.MediaType)
   434  	w.WriteHeader(http.StatusCreated)
   435  
   436  	if err := jsonapi.MarshalPayload(w, blog); err != nil {
   437  		http.Error(w, err.Error(), http.StatusInternalServerError)
   438  	}
   439  }
   440  ```
   441  
   442  ### Create Records Example
   443  
   444  #### `UnmarshalManyPayload`
   445  
   446  ```go
   447  UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error)
   448  ```
   449  
   450  Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalManyPayload)
   451  
   452  Takes an `io.Reader` and a `reflect.Type` representing the uniform type
   453  contained within the `"data"` JSON API member.
   454  
   455  ##### Handler Example Code
   456  
   457  ```go
   458  func CreateBlogs(w http.ResponseWriter, r *http.Request) {
   459  	// ...create many blogs at once
   460  
   461  	blogs, err := UnmarshalManyPayload(r.Body, reflect.TypeOf(new(Blog)))
   462  	if err != nil {
   463  		t.Fatal(err)
   464  	}
   465  
   466  	for _, blog := range blogs {
   467  		b, ok := blog.(*Blog)
   468  		// ...save each of your blogs
   469  	}
   470  
   471  	w.Header().Set("Content-Type", jsonapi.MediaType)
   472  	w.WriteHeader(http.StatusCreated)
   473  
   474  	if err := jsonapi.MarshalPayload(w, blogs); err != nil {
   475  		http.Error(w, err.Error(), http.StatusInternalServerError)
   476  	}
   477  }
   478  ```
   479  
   480  
   481  ### Links
   482  
   483  If you need to include [link objects](http://jsonapi.org/format/#document-links) along with response data, implement the `Linkable` interface for document-links, and `RelationshipLinkable` for relationship links:
   484  
   485  ```go
   486  func (post Post) JSONAPILinks() *Links {
   487  	return &Links{
   488  		"self": "href": fmt.Sprintf("https://example.com/posts/%d", post.ID),
   489  		"comments": Link{
   490  			Href: fmt.Sprintf("https://example.com/api/blogs/%d/comments", post.ID),
   491  			Meta: map[string]interface{}{
   492  				"counts": map[string]uint{
   493  					"likes":    4,
   494  				},
   495  			},
   496  		},
   497  	}
   498  }
   499  
   500  // Invoked for each relationship defined on the Post struct when marshaled
   501  func (post Post) JSONAPIRelationshipLinks(relation string) *Links {
   502  	if relation == "comments" {
   503  		return &Links{
   504  			"related": fmt.Sprintf("https://example.com/posts/%d/comments", post.ID),
   505  		}
   506  	}
   507  	return nil
   508  }
   509  ```
   510  
   511  ### Meta
   512  
   513   If you need to include [meta objects](http://jsonapi.org/format/#document-meta) along with response data, implement the `Metable` interface for document-meta, and `RelationshipMetable` for relationship meta:
   514  
   515   ```go
   516  func (post Post) JSONAPIMeta() *Meta {
   517  	return &Meta{
   518  		"details": "sample details here",
   519  	}
   520  }
   521  
   522  // Invoked for each relationship defined on the Post struct when marshaled
   523  func (post Post) JSONAPIRelationshipMeta(relation string) *Meta {
   524  	if relation == "comments" {
   525  		return &Meta{
   526  			"this": map[string]interface{}{
   527  				"can": map[string]interface{}{
   528  					"go": []interface{}{
   529  						"as",
   530  						"deep",
   531  						map[string]interface{}{
   532  							"as": "required",
   533  						},
   534  					},
   535  				},
   536  			},
   537  		}
   538  	}
   539  	return nil
   540  }
   541  ```
   542  
   543  ### Errors
   544  This package also implements support for JSON API compatible `errors` payloads using the following types.
   545  
   546  #### `MarshalErrors`
   547  ```go
   548  MarshalErrors(w io.Writer, errs []*ErrorObject) error
   549  ```
   550  
   551  Writes a JSON API response using the given `[]error`.
   552  
   553  #### `ErrorsPayload`
   554  ```go
   555  type ErrorsPayload struct {
   556  	Errors []*ErrorObject `json:"errors"`
   557  }
   558  ```
   559  
   560  ErrorsPayload is a serializer struct for representing a valid JSON API errors payload.
   561  
   562  #### `ErrorObject`
   563  ```go
   564  type ErrorObject struct { ... }
   565  
   566  // Error implements the `Error` interface.
   567  func (e *ErrorObject) Error() string {
   568  	return fmt.Sprintf("Error: %s %s\n", e.Title, e.Detail)
   569  }
   570  ```
   571  
   572  ErrorObject is an `Error` implementation as well as an implementation of the JSON API error object.
   573  
   574  The main idea behind this struct is that you can use it directly in your code as an error type and pass it directly to `MarshalErrors` to get a valid JSON API errors payload.
   575  
   576  ##### Errors Example Code
   577  ```go
   578  // An error has come up in your code, so set an appropriate status, and serialize the error.
   579  if err := validate(&myStructToValidate); err != nil {
   580  	context.SetStatusCode(http.StatusBadRequest) // Or however you need to set a status.
   581  	jsonapi.MarshalErrors(w, []*ErrorObject{{
   582  		Title: "Validation Error",
   583  		Detail: "Given request body was invalid.",
   584  		Status: "400",
   585  		Meta: map[string]interface{}{"field": "some_field", "error": "bad type", "expected": "string", "received": "float64"},
   586  	}})
   587  	return
   588  }
   589  ```
   590  
   591  ## Testing
   592  
   593  ### `MarshalOnePayloadEmbedded`
   594  
   595  ```go
   596  MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error
   597  ```
   598  
   599  Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalOnePayloadEmbedded)
   600  
   601  This method is not strictly meant to for use in implementation code,
   602  although feel free.  It was mainly created for use in tests; in most cases,
   603  your request payloads for create will be embedded rather than sideloaded
   604  for related records.  This method will serialize a single struct pointer
   605  into an embedded json response.  In other words, there will be no,
   606  `included`, array in the json; all relationships will be serialized
   607  inline with the data.
   608  
   609  However, in tests, you may want to construct payloads to post to create
   610  methods that are embedded to most closely model the payloads that will
   611  be produced by the client.  This method aims to enable that.
   612  
   613  ### Example
   614  
   615  ```go
   616  out := bytes.NewBuffer(nil)
   617  
   618  // testModel returns a pointer to a Blog
   619  jsonapi.MarshalOnePayloadEmbedded(out, testModel())
   620  
   621  h := new(BlogsHandler)
   622  
   623  w := httptest.NewRecorder()
   624  r, _ := http.NewRequest(http.MethodPost, "/blogs", out)
   625  
   626  h.CreateBlog(w, r)
   627  
   628  blog := new(Blog)
   629  jsonapi.UnmarshalPayload(w.Body, blog)
   630  
   631  // ... assert stuff about blog here ...
   632  ```
   633  
   634  ## Alternative Installation
   635  I use git subtrees to manage dependencies rather than `go get` so that
   636  the src is committed to my repo.
   637  
   638  ```
   639  git subtree add --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master
   640  ```
   641  
   642  To update,
   643  
   644  ```
   645  git subtree pull --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master
   646  ```
   647  
   648  This assumes that I have my repo structured with a `src` dir containing
   649  a collection of packages and `GOPATH` is set to the root
   650  folder--containing `src`.
   651  
   652  ## Contributing
   653  
   654  Fork, Change, Pull Request *with tests*.