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 [](https://travis-ci.org/google/jsonapi) 201 [](https://goreportcard.com/report/github.com/google/jsonapi) 202 [](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*.