Working examples for presentation
For most of the files:
go run FILENAME
If something will require dependencies, check impoert section and do
go get PACKAGE_IN_IMPORT_SECTION
Aurelijus Banelis
| { | |
| "items": [ | |
| { | |
| "version": "v3", | |
| "title": "Google Analytics API", | |
| "description": "Views and manages your Google Analytics data.", | |
| "documentationLink": "https://developers.google.com/analytics/" | |
| }, | |
| { | |
| "title": "Google Analytics API", | |
| "description": "Views and manages your Google Analytics data.", | |
| "documentationLink": "https://developers.google.com/analytics/", | |
| "version": "v2.4" | |
| } | |
| ] | |
| } |
| package main | |
| import ( | |
| . "github.com/onsi/ginkgo" | |
| . "github.com/onsi/gomega" | |
| "testing" | |
| "net/http" | |
| "io/ioutil" | |
| "time" | |
| "encoding/json" | |
| "fmt" | |
| ) | |
| type ( | |
| FirstLastName string | |
| Date string | |
| Url string | |
| HTML string | |
| Address string | |
| Company struct { | |
| Name string `json:"name"` | |
| Url Url `json:"url"` | |
| } | |
| Topic struct { | |
| Title string `json:"title"` | |
| Description HTML `json:"description"` | |
| } | |
| Speaker struct { | |
| Name FirstLastName `json:"name"` | |
| Topic Topic `json:"topic"` | |
| Company *Company `json:"company,omitempty"` | |
| } | |
| Place struct { | |
| Name string `json:"name"` | |
| Address Address `json:"address"` | |
| } | |
| Meetup struct { | |
| Session *uint `json:"session"` | |
| Episode *uint `json:"episode"` | |
| Name string `json:"name"` | |
| Speakers []Speaker | |
| Date Date `json:"date"` | |
| Venue Place `json:"venue"` | |
| Sponsor Company `json:"sponsor"` | |
| Description HTML `json:"description"` | |
| } | |
| Meetups []Meetup | |
| ) | |
| var _ = Describe("Examples of usefulnes of running tests", func() { | |
| It("Should be easy to copy expected values from curl response", func() { | |
| response := readAll("https://www.googleapis.com/discovery/v1/apis?name=analytics&fields=items(description%2CdocumentationLink%2Ctitle%2Cversion)") | |
| // Note "version" and "title" are not ordered | |
| expected := `{ | |
| "items": [ | |
| { | |
| "title": "Google Analytics API", | |
| "description": "Views and manages your Google Analytics data.", | |
| "documentationLink": "https://developers.google.com/analytics/", | |
| "version": "v2.4" | |
| }, | |
| { | |
| "version": "v3", | |
| "title": "Google Analytics API", | |
| "description": "Views and manages your Google Analytics data.", | |
| "documentationLink": "https://developers.google.com/analytics/" | |
| } | |
| ] | |
| }` | |
| Expect(response).Should(MatchJSON(expected)) | |
| }) | |
| It("Is hard to write tests with deep unless you have good IDE and write by position", func() { | |
| meetups := Meetups{ | |
| Meetup{ | |
| Name: "Golang Meetup S02E01", | |
| Session: intPtr(2), | |
| Episode: intPtr(1), | |
| Speakers:[]Speaker{ | |
| { | |
| FirstLastName("Aurelijus Banelis"), | |
| Topic{ | |
| "JSON+Go in practice", | |
| HTML("Practical tips and tricks ..."), | |
| }, | |
| &Company{ | |
| "NFQ", | |
| Url("http://www.nfq.lt/"), | |
| }, | |
| }, | |
| { | |
| FirstLastName("Valdas Petrulis"), | |
| Topic{ | |
| "NATS pub-sub daemon packed inside your application ", | |
| HTML("Here at www.mysterium.network ..."), | |
| }, | |
| &Company{ | |
| "MysteriumNetwork", | |
| Url("http://www.mysterium.network/"), | |
| }, | |
| }, | |
| }, | |
| Date: Date("2017-09-27"), | |
| Description: HTML("Arriving: Call Povilas if you'll face any problems reaching the place."), | |
| Sponsor: Company{ | |
| "Uber", | |
| Url("https://www.uber.com/en-LT/"), | |
| }, | |
| Venue: Place{ | |
| Name: "Uber", | |
| Address: Address("Lvovo g. 25, Vilnius"), | |
| }, | |
| }, | |
| Meetup{ | |
| Name: "Golang Meetup #5", | |
| Speakers:[]Speaker{ | |
| { | |
| "Martynas Pumputis", | |
| Topic{ | |
| "Go and Linux Namespaces: Better Don't Mix", | |
| "This talk is about discovering Go runtime limitations which make us reconsider its adoption within container software", | |
| }, | |
| &Company{ | |
| "Weaveworks", | |
| "https://www.weave.works/", | |
| }, | |
| }, | |
| { | |
| "Mike Kabischev", | |
| Topic{ | |
| "Instrumenting Go application", | |
| "Instrumentation ...", | |
| }, | |
| nil, | |
| }, | |
| { | |
| "Max Chechel", | |
| Topic{ | |
| "Code generation in go", | |
| "Why do we need to generate Go ...", | |
| }, | |
| nil, | |
| }, | |
| }, | |
| Date: "2017-05-24", | |
| Description: "Arriving: Call Povilas if you'll face any problems reaching the place.", | |
| Sponsor: Company{ | |
| "Uber", | |
| "https://www.uber.com/en-LT/", | |
| }, | |
| Venue: Place{ | |
| Name: "Uber", | |
| Address: "Lvovo g. 25, Vilnius", | |
| }, | |
| }, | |
| } | |
| Expect(nextSpeaker(mockedClock, meetups)).Should(Equal("Aurelijus Banelis")) | |
| }) | |
| It("JSON can be used for deep structures and no fear of modification by pointers", func() { | |
| meetups := Meetups{} | |
| input := `[ | |
| { | |
| "name": "Golang Meetup S02E01", | |
| "session": 2, | |
| "episode": 1, | |
| "Speakers": [ | |
| { | |
| "name": "Aurelijus Banelis", | |
| "topic": { | |
| "title": "JSON+Go in practice", | |
| "description": "Practical tips and tricks ..." | |
| }, | |
| "company": { | |
| "name": "NFQ", | |
| "url": "http://www.nfq.lt/" | |
| } | |
| }, | |
| { | |
| "name": "Valdas Petrulis", | |
| "topic": { | |
| "title": "NATS pub-sub daemon packed inside your application ", | |
| "description": "Here at www.mysterium.network ..." | |
| }, | |
| "company": { | |
| "name": "MysteriumNetwork", | |
| "url": "http://www.mysterium.network/" | |
| } | |
| } | |
| ], | |
| "date": "2017-09-27", | |
| "venue": { | |
| "name": "Uber", | |
| "address": "Lvovo g. 25, Vilnius" | |
| }, | |
| "sponsor": { | |
| "name": "Uber", | |
| "url": "https://www.uber.com/en-LT/" | |
| }, | |
| "description": "Arriving: Call Povilas if you'll face any problems reaching the place." | |
| }, | |
| { | |
| "name": "Golang Meetup #5", | |
| "Speakers": [ | |
| { | |
| "name": "Martynas Pumputis", | |
| "topic": { | |
| "title": "Go and Linux Namespaces: Better Don't Mix", | |
| "description": "This talk is about discovering Go runtime limitations which make us reconsider its adoption within container software" | |
| }, | |
| "company": { | |
| "name": "Weaveworks", | |
| "url": "https://www.weave.works/" | |
| } | |
| }, | |
| { | |
| "name": "Mike Kabischev", | |
| "topic": { | |
| "title": "Instrumenting Go application", | |
| "description": "Instrumentation ..." | |
| } | |
| }, | |
| { | |
| "name": "Max Chechel", | |
| "topic": { | |
| "title": "Code generation in go", | |
| "description": "Why do we need to generate Go ..." | |
| } | |
| } | |
| ], | |
| "date": "2017-05-24", | |
| "venue": { | |
| "name": "Uber", | |
| "address": "Lvovo g. 25, Vilnius" | |
| }, | |
| "sponsor": { | |
| "name": "Uber", | |
| "url": "https://www.uber.com/en-LT/" | |
| }, | |
| "description": "Arriving: Call Povilas if you'll face any problems reaching the place." | |
| } | |
| ]` | |
| json.Unmarshal([]byte(input), &meetups) | |
| Expect(nextSpeaker(mockedClock, meetups)).Should(Equal("Aurelijus Banelis")) | |
| }) | |
| It("Fmt prints badly deep structures of pointers", func() { | |
| meetups := Meetups{ | |
| Meetup{ | |
| Name: "Golang Meetup S02E01", | |
| Session: intPtr(2), | |
| Episode: intPtr(1), | |
| Speakers: []Speaker{ | |
| { | |
| Company: &Company{ | |
| Name: "NFQ", | |
| }, | |
| }, | |
| }, | |
| }, | |
| } | |
| representation := fmt.Sprintf("%#v", meetups) | |
| // Representation: test.Meetups{test.Meetup{Session:(*uint)(0xc42028c5d0), Episode:(*uint)(0xc42028c5d8) ... | |
| Expect(representation).ShouldNot(BeEmpty()) | |
| }) | |
| }) | |
| func intPtr(value uint) *uint { | |
| return &value | |
| } | |
| var mockedClock, _ = time.Parse(time.RFC3339, "2017-09-27T19:10:00+03:00") | |
| func nextSpeaker(new time.Time, meetups Meetups) string { | |
| // Skipping real implementation just for the sake of example | |
| return string(meetups[0].Speakers[0].Name) | |
| } | |
| func readAll(url string) string { | |
| resp, err := http.Get(url) | |
| defer resp.Body.Close() | |
| if err != nil { | |
| panic(err) | |
| } | |
| body, err := ioutil.ReadAll(resp.Body) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return string(body) | |
| } | |
| func TestGoJson(t *testing.T) { | |
| RegisterFailHandler(Fail) | |
| RunSpecs(t, "Go+Json Example Suite") | |
| } | |
| default: | |
| golint *.go | |
| go vet *.go | |
| go fmt *.go | |
| run: | |
| go run naming.go | |
| go run naming2.go | |
| go run naming-unmarshal.go | |
| go run naming-polymorphism.go |
| package main | |
| // THIS IS AUTO GENERATED FILE - DO NOT EDIT! | |
| // SomeGeneratedDTO type | |
| type SomeGeneratedDTO struct { | |
| ID CommonGenerated.ID `json:"id"` | |
| ParentID CommonGenerated.ID `json:"parentId"` | |
| Title CommonGenerated.Translation `json:"title"` | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| ) | |
| // Main structure | |
| type polymorphic struct { | |
| Type string `json:"type"` | |
| Attributes iAttribute `json:"attributes"` | |
| } | |
| type iAttribute interface { | |
| Type() string | |
| } | |
| // Image type | |
| var _ iAttribute = (*image)(nil) | |
| func (i *image) Type() string { | |
| return "image" | |
| } | |
| type image struct { | |
| Path string `json:"path"` | |
| } | |
| // Meetup type | |
| var _ iAttribute = (*meetup)(nil) | |
| type meetup struct { | |
| Language string `json:"language"` | |
| Date string `json:"date"` | |
| } | |
| func (i *meetup) Type() string { | |
| return "meetup" | |
| } | |
| // Testing example | |
| func main() { | |
| data := []polymorphic{ | |
| { | |
| Type: "image", | |
| Attributes: &image{ | |
| Path: "http://tny.im/ajb", | |
| }, | |
| }, | |
| { | |
| Type: "meetup", | |
| Attributes: &meetup{ | |
| Language: "go", | |
| Date: "2017-09-27", | |
| }, | |
| }, | |
| } | |
| api, err := json.MarshalIndent(&data, "", " ") | |
| if err != nil { | |
| panic(err.Error()) | |
| } | |
| fmt.Printf("%s\n", api) | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "github.com/segmentio/go-camelcase" | |
| "reflect" | |
| ) | |
| type structureDynamic struct { | |
| NetworkID string | |
| EndpointID string | |
| Gateway string | |
| IPAddress string | |
| IPPrefixLen int | |
| } | |
| func (s *structureDynamic) MarshalJSON() ([]byte, error) { | |
| keyValues := map[string]interface{}{} | |
| value := reflect.ValueOf(s).Elem() | |
| for i := 0; i < value.NumField(); i++ { | |
| valueField := value.Field(i) | |
| typeField := value.Type().Field(i) | |
| name := camelcase.Camelcase(typeField.Name) | |
| keyValues[name] = valueField.Interface() | |
| } | |
| return json.Marshal(keyValues) | |
| } | |
| func main() { | |
| data := structureDynamic{ | |
| NetworkID: "b5a465c4ddf1", | |
| EndpointID: "5483c7bbf04", | |
| Gateway: "172.20.0.1", | |
| IPAddress: "172.20.0.4", | |
| IPPrefixLen: 16, | |
| } | |
| api, err := json.MarshalIndent(&data, "", " ") | |
| if err != nil { | |
| panic(err.Error()) | |
| } | |
| fmt.Printf("%s\n", api) | |
| } | |
| // Adapted from https://gist.github.com/drewolson/4771479 |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| ) | |
| type structureGo struct { | |
| NetworkID string | |
| EndpointID string | |
| Gateway string | |
| IPAddress string | |
| IPPrefixLen int | |
| } | |
| func main() { | |
| data := structureGo{ | |
| NetworkID: "b5a465c4ddf1", | |
| EndpointID: "5483c7bbf04", | |
| Gateway: "172.20.0.1", | |
| IPAddress: "172.20.0.4", | |
| IPPrefixLen: 16, | |
| } | |
| api, err := json.MarshalIndent(&data, "", " ") | |
| if err != nil { | |
| panic(err.Error()) | |
| } | |
| fmt.Printf("%s\n", api) | |
| } | |
| // Go lint: struct field NetworkID should be NetworkID |
| { | |
| "NetworkID": "b5a465c4ddf1", | |
| "EndpointID": "5483c7bbf04", | |
| "Gateway": "172.20.0.1", | |
| "IPAddress": "172.20.0.4", | |
| "IPPrefixLen": 16 | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| ) | |
| type structureJs struct { | |
| NetworkID string `json:"networkId"` | |
| EndpointID string `json:"endpointId"` | |
| Gateway string `json:"gateway"` | |
| IPAddress string `json:"ipAddress"` | |
| IPPrefixLen int `json:"ipPrefixLen"` | |
| } | |
| func main() { | |
| data := structureJs{ | |
| NetworkID: "b5a465c4ddf1", | |
| EndpointID: "5483c7bbf04", | |
| Gateway: "172.20.0.1", | |
| IPAddress: "172.20.0.4", | |
| IPPrefixLen: 16, | |
| } | |
| api, err := json.MarshalIndent(&data, "", " ") | |
| if err != nil { | |
| panic(err.Error()) | |
| } | |
| fmt.Printf("%s\n", api) | |
| } |
| { | |
| "networkId": "b5a465c4ddf1", | |
| "endpointId": "5483c7bbf04", | |
| "gateway": "172.20.0.1", | |
| "IpAddress": "172.20.0.4", | |
| "IpPrefixLen": 16 | |
| } |
| [ | |
| { | |
| "type": "image", | |
| "attributes": { | |
| "path": "http://tny.im/ajb" | |
| } | |
| }, | |
| { | |
| "type": "meetup", | |
| "attributes": { | |
| "language": "Go", | |
| "date": "2017-09-27" | |
| } | |
| } | |
| ] |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "github.com/juju/errors" | |
| ) | |
| type ( | |
| Image struct { | |
| Path string `json:"path"` | |
| } | |
| Meetup struct { | |
| Language string `json:"language"` | |
| Date string `json:"date"` | |
| } | |
| IAttribute interface { | |
| Type() string | |
| } | |
| Element struct { | |
| attribute IAttribute | |
| } | |
| ) | |
| func (i *Element) UnmarshalJSON(data []byte) error { | |
| intermediate := struct { | |
| Type string `json:"type"` | |
| Attributes json.RawMessage `json:"attributes"` | |
| }{} | |
| err := json.Unmarshal(data, &intermediate) | |
| if err != nil { | |
| return errors.Annotate(err, "Invalid Element") | |
| } | |
| switch intermediate.Type { | |
| case "image": | |
| img := Image{} | |
| err = json.Unmarshal(intermediate.Attributes, &img) | |
| if err == nil { | |
| i.attribute = &img | |
| } | |
| break | |
| case "meetup": | |
| meetup := Meetup{} | |
| err = json.Unmarshal(intermediate.Attributes, &meetup) | |
| if err == nil { | |
| i.attribute = &meetup | |
| } | |
| break | |
| default: | |
| err = fmt.Errorf("Unexpected element type %s", intermediate.Type) | |
| } | |
| return errors.Trace(err) | |
| } | |
| func (p *Image) Type() string { | |
| return "image" | |
| } | |
| func (p Meetup) Type() string { | |
| return "meetup" | |
| } | |
| type Elements []Element | |
| type GroupedElements map[string]Elements | |
| func main() { | |
| var elements []Element | |
| err := json.Unmarshal([]byte(` | |
| [ | |
| { | |
| "type": "image", | |
| "attributes": { | |
| "path": "http://tny.im/ajb" | |
| } | |
| }, | |
| { | |
| "type": "meetup", | |
| "attributes": { | |
| "language": "Go", | |
| "date": "2017-09-27" | |
| } | |
| } | |
| ] | |
| `), &elements) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| fmt.Printf("Data: %#v\n", elements) | |
| var grouped GroupedElements | |
| err = json.Unmarshal([]byte(` | |
| { | |
| "images": [ | |
| { | |
| "type": "image", | |
| "attributes": { | |
| "path": "http://tny.im/ajb" | |
| } | |
| } | |
| ], | |
| "meetups": [ | |
| { | |
| "type": "meetup", | |
| "attributes": { | |
| "language": "Go", | |
| "date": "2017-09-27" | |
| } | |
| } | |
| ] | |
| } | |
| `), &grouped) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| fmt.Printf("Data: %#v\n", grouped) | |
| var grouped2 GroupedElements | |
| err = json.Unmarshal([]byte(` | |
| { | |
| "images": [ | |
| { | |
| "type": "image", | |
| "attributes": { | |
| "path": "http://tny.im/ajb" | |
| } | |
| } | |
| ], | |
| "meetups": null | |
| } | |
| `), &grouped2) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| fmt.Printf("Data: %#v\n", grouped2) | |
| } |
| { | |
| "images": [ | |
| { | |
| "type": "image", | |
| "attributes": { | |
| "path": "http://tny.im/ajb" | |
| } | |
| } | |
| ], | |
| "meetups": null | |
| } |
| { | |
| "images": [ | |
| { | |
| "type": "image", | |
| "attributes": { | |
| "path": "http://tny.im/ajb" | |
| } | |
| } | |
| ], | |
| "meetups": [ | |
| { | |
| "type": "meetup", | |
| "attributes": { | |
| "language": "Go", | |
| "date": "2017-09-27" | |
| } | |
| } | |
| ] | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| ) | |
| type LocationNumber uint64 | |
| type LocationData struct { | |
| Number LocationNumber `json:"number"` | |
| } | |
| func (i *LocationNumber) UnmarshalJSON(data []byte) error { | |
| var longInteger uint64 | |
| var longFloat float64 | |
| errLong := json.Unmarshal(data, &longInteger) | |
| errFloat := json.Unmarshal(data, &longFloat) | |
| if errLong != nil && errFloat == nil { | |
| *i = LocationNumber(longFloat) | |
| return nil | |
| } | |
| if errLong != nil { | |
| return errLong | |
| } | |
| *i = LocationNumber(longInteger) | |
| return nil | |
| } | |
| func main() { | |
| // 1.0e+19 | |
| data1 := LocationData{} | |
| err := json.Unmarshal([]byte(`{"number": 100}`), &data1) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| fmt.Printf("Data: %#v\n", data1) | |
| data2 := LocationData{} | |
| err = json.Unmarshal([]byte(`{"number": 1.0e+8}`), &data2) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| fmt.Printf("Data: %#v %d\n", data2, data2.Number) | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "sort" | |
| ) | |
| func main() { | |
| set1 := []string{"a", "b", "c"} | |
| set2 := map[string]string{"a": "a", "b": "b", "c": "c"} | |
| sort.Strings(set1) | |
| setJson1, _ := json.MarshalIndent(set1, "", " ") | |
| setJson2, _ := json.MarshalIndent(set2, "", " ") | |
| fmt.Printf("Set as array:\n%s\n", setJson1) | |
| fmt.Printf("Set as objects:\n%s\n", setJson2) | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "gopkg.in/go-playground/validator.v9" | |
| "strings" | |
| ) | |
| type DataExample struct { | |
| Email string `json:"email" validate:"email,isExampleDomain"` | |
| Gln string `json:"gln" validate:"numeric,len=13"` | |
| } | |
| func main() { | |
| validate := validator.New() | |
| validate.RegisterValidation("isExampleDomain", func(fl validator.FieldLevel) bool { | |
| return strings.HasSuffix(fl.Field().String(), "@example.com") | |
| }) | |
| data1 := DataExample{} | |
| json.Unmarshal([]byte(`{"email":"[email protected]", "gln":"1234567890123"}`), &data1) | |
| err := validate.Struct(data1) | |
| if err == nil { | |
| fmt.Printf("Data: %#v\n", data1) | |
| } | |
| data2 := DataExample{} | |
| json.Unmarshal([]byte(`{"email":"[email protected]", "gln":"123"}`), &data2) | |
| err = validate.Struct(data2) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| data3 := DataExample{} | |
| json.Unmarshal([]byte(`{"email":"[email protected]", "gln":"1234567890123"}`), &data3) | |
| err = validate.Struct(data3) | |
| if err != nil { | |
| fmt.Printf("Validation: %s\n", err.Error()) | |
| } | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "github.com/juju/errors" | |
| "strings" | |
| "time" | |
| ) | |
| // Validation with builtin JSON types | |
| type Data struct { | |
| ID string `json:"id"` | |
| Location uint64 `json:"location"` | |
| } | |
| // Validation as custom type example | |
| type ID string | |
| type Data2 struct { | |
| ID ID `json:"id"` | |
| Location uint64 `json:"location"` | |
| } | |
| func (i *ID) UnmarshalJSON(data []byte) error { | |
| var parsed string = "" | |
| err := json.Unmarshal(data, &parsed) | |
| errorMessage := "Invalid input %s. Expected ID need to be 6 digits uppercase string" | |
| if err != nil { | |
| return errors.Annotatef(err, errorMessage) | |
| } | |
| if len(parsed) != 6 || strings.ToUpper(parsed) != parsed { | |
| return fmt.Errorf(errorMessage, data) | |
| } | |
| // Common mistake to forgot to update and not to confuse pointer and pointed value | |
| *i = ID(parsed) | |
| return nil | |
| } | |
| // Date type example | |
| type Date time.Time | |
| func (d *Date) UnmarshalJSON(data []byte) error { | |
| var parsed string = "" | |
| err := json.Unmarshal(data, &parsed) | |
| errorMessage := "Invalid input %s. Expected Date in format 2006-01-02" | |
| if err != nil { | |
| return errors.Annotatef(err, errorMessage, data) | |
| } | |
| var parsedTime time.Time = time.Now() | |
| err = json.Unmarshal([]byte(`"`+parsed+`T00:00:00Z"`), &parsedTime) | |
| if err != nil { | |
| return errors.Annotatef(err, errorMessage, data) | |
| } | |
| // Common mistake to forgot to update and not to confuse pointer and pointed value | |
| *d = Date(parsedTime) | |
| return nil | |
| } | |
| func (d Date) MarshalJSON() ([]byte, error) { | |
| date := time.Time(d).Format("2006-01-02") | |
| return json.Marshal(&date) | |
| } | |
| type DateExample struct { | |
| Date Date `json:"date"` | |
| } | |
| // Manual testing | |
| func main() { | |
| data := &Data{} | |
| err := json.Unmarshal([]byte(`{"id":"testas", "Location":"Vilnius"}`), &data) | |
| fmt.Printf("Validation error: %s\n", err.Error()) | |
| data2 := &Data2{} | |
| err = json.Unmarshal([]byte(`{"id":"testas", "Location":123456}`), &data2) | |
| if err != nil { | |
| fmt.Printf("Validation error: %s\n", err.Error()) | |
| } | |
| data2valid := &Data2{} | |
| err = json.Unmarshal([]byte(`{"id":"ABCDEF", "Location":123456}`), &data2valid) | |
| fmt.Printf("DateExample %#v\n", data2valid) | |
| data3 := &DateExample{} | |
| err = json.Unmarshal([]byte(`{"date":"2017 September 27th"}`), &data3) | |
| if err != nil { | |
| fmt.Printf("Validation error: %s\n", err.Error()) | |
| } | |
| data3valid := &DateExample{} | |
| err = json.Unmarshal([]byte(`{"date":"2017-09-27"}`), &data3valid) | |
| fmt.Printf("DateExample %#v\n", data3) | |
| jsonData3, _ := json.Marshal(data3valid) | |
| fmt.Printf("DateExample %s\n", jsonData3) | |
| } |
| {"number": 1.0e+8} |
| { | |
| "set": { | |
| "a": "a", | |
| "b": "b", | |
| "c": "c" | |
| } | |
| } |
| package main | |
| import ( | |
| "encoding/json" | |
| "github.com/ant0ine/go-json-rest/rest" | |
| "io/ioutil" | |
| "log" | |
| "net/http" | |
| "strings" | |
| ) | |
| type speakerLatest struct { | |
| Name string `json:"name"` | |
| Topic string `json:"topic"` | |
| LinkedIn string `json:"linkedIn"` | |
| } | |
| func writeValidationFailed(message string, w rest.ResponseWriter) { | |
| w.WriteHeader(http.StatusBadRequest) | |
| var errorResponse = struct { | |
| Error string `json:"error"` | |
| }{message} | |
| w.WriteJson(&errorResponse) | |
| } | |
| func writeSystemError(err error, w rest.ResponseWriter) { | |
| w.WriteHeader(http.StatusInternalServerError) | |
| var errorResponse = struct { | |
| Error string `json:"error"` | |
| }{err.Error()} | |
| w.WriteJson(&errorResponse) | |
| } | |
| func main() { | |
| api := rest.NewApi() | |
| api.Use(rest.DefaultDevStack...) | |
| router, err := rest.MakeRouter( | |
| rest.Post("/speaker", func(w rest.ResponseWriter, req *rest.Request) { | |
| // Request is read only once | |
| payload, err := ioutil.ReadAll(req.Body) | |
| if err != nil { | |
| writeSystemError(err, w) | |
| return | |
| } | |
| compatiblity := struct { | |
| HomePage string `json:"homePage"` | |
| }{} | |
| fields := map[string]json.RawMessage{} | |
| latest := speakerLatest{} | |
| err1 := json.Unmarshal(payload, &latest) | |
| err2 := json.Unmarshal(payload, &compatiblity) | |
| err3 := json.Unmarshal(payload, &fields) | |
| if err1 != nil { | |
| writeSystemError(err1, w) | |
| return | |
| } | |
| if err2 != nil { | |
| writeSystemError(err2, w) | |
| return | |
| } | |
| if err3 != nil { | |
| writeSystemError(err3, w) | |
| return | |
| } | |
| linkedInPrefix := "https://www.linkedin.com/in/" | |
| if _, exists := fields["linkedIn"]; !exists && strings.HasPrefix(compatiblity.HomePage, linkedInPrefix) { | |
| latest.LinkedIn = (compatiblity.HomePage)[len(linkedInPrefix):] | |
| } | |
| if _, exists := fields["topic"]; !exists { | |
| writeValidationFailed("topic property must be provided", w) | |
| return | |
| } | |
| if strings.TrimSpace(latest.Topic) == "" { | |
| writeValidationFailed("topic property cannot be empty", w) | |
| return | |
| } | |
| // Assuming everything was ok | |
| w.WriteHeader(201) | |
| w.WriteJson(&latest) | |
| }), | |
| ) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| api.SetApp(router) | |
| log.Fatal(http.ListenAndServe(":8082", api.MakeHandler())) | |
| } | |
| // Usage: | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis"}' http://127.0.0.1:8082/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice"}' http://127.0.0.1:8082/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://aurelijus.banelis.lt"}' http://127.0.0.1:8082/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://www.linkedin.com/in/aurelijusbanelis"}' http://127.0.0.1:8082/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8082/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8082/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8082/speaker |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| ) | |
| type optionalPointers struct { | |
| Attributes map[string]string | |
| } | |
| func main() { | |
| defaultValues, _ := json.Marshal(&optionalPointers{}) | |
| defaultInitialised, _ := json.Marshal(&optionalPointers{map[string]string{}}) | |
| normalValues, _ := json.Marshal(&optionalPointers{map[string]string{"field": "value"}}) | |
| fmt.Printf("%s\n", defaultValues) | |
| fmt.Printf("%s\n", defaultInitialised) | |
| fmt.Printf("%s\n", normalValues) | |
| payload := &struct{ Attributes map[string]string }{} | |
| json.Unmarshal([]byte("{}"), &payload) | |
| payload.Attributes["new"] = "value" | |
| } |
| package main | |
| import ( | |
| "github.com/ant0ine/go-json-rest/rest" | |
| "log" | |
| "net/http" | |
| "strings" | |
| ) | |
| type speakerData struct { | |
| Name string `json:"name"` | |
| Topic *string `json:"topic,omitempty"` | |
| HomePage *string `json:"homePage,omitempty"` | |
| LinkedIn string `json:"linkedIn,omitempty"` | |
| } | |
| func writeInvalid(message string, w rest.ResponseWriter) { | |
| w.WriteHeader(http.StatusBadRequest) | |
| var errorResponse = struct { | |
| Error string `json:"error"` | |
| }{message} | |
| w.WriteJson(&errorResponse) | |
| } | |
| func main() { | |
| api := rest.NewApi() | |
| api.Use(rest.DefaultDevStack...) | |
| router, err := rest.MakeRouter( | |
| rest.Post("/speaker", func(w rest.ResponseWriter, req *rest.Request) { | |
| markerForNotProvided := "\x00" | |
| data := speakerData{ | |
| Name: markerForNotProvided, | |
| } | |
| req.DecodeJsonPayload(&data) | |
| // Using pointer to distinguish provided and not provided field | |
| if data.Topic == nil || strings.TrimSpace(*data.Topic) == "" { | |
| writeInvalid("Old API is deprecated. Please provide topic", w) | |
| return | |
| } | |
| // Too many pointers are error prone | |
| linkedInPrefix := "https://www.linkedin.com/in/" | |
| if data.HomePage != nil && strings.HasPrefix(*data.HomePage, linkedInPrefix) { | |
| data.LinkedIn = (*data.HomePage)[len(linkedInPrefix):] | |
| data.HomePage = nil | |
| } | |
| if data.HomePage != nil && data.LinkedIn == "" { | |
| writeInvalid("Old API is deprecated. Please provide linkedIn instead of homePage", w) | |
| return | |
| } | |
| // Too many optional fields increase the number of checks | |
| if data.LinkedIn == "" { | |
| writeInvalid("Old API is deprecated. Please provide linkedIn field", w) | |
| return | |
| } | |
| // One of the hackish way to solve default values is to provide uncommon value before | |
| if data.Name == markerForNotProvided { | |
| writeInvalid("Validation failed. Please provide name field", w) | |
| return | |
| } | |
| if data.Name == "" { | |
| writeInvalid("Validation failed: name field cannot be empty", w) | |
| return | |
| } | |
| // Assuming everything was ok | |
| w.WriteHeader(201) | |
| w.WriteJson(&data) | |
| }), | |
| ) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| api.SetApp(router) | |
| log.Fatal(http.ListenAndServe(":8081", api.MakeHandler())) | |
| } | |
| // Usage: | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis"}' http://127.0.0.1:8081/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice"}' http://127.0.0.1:8081/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://aurelijus.banelis.lt"}' http://127.0.0.1:8081/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://www.linkedin.com/in/aurelijusbanelis"}' http://127.0.0.1:8081/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8081/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8081/speaker | |
| // curl -XPOST -H 'Content-type: application/json' -d '{"name":"", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8081/speaker | |
| type a struct { | |
| A *[]map[string]string | |
| } |
| package main | |
| import ( | |
| "errors" | |
| "github.com/ant0ine/go-json-rest/rest" | |
| "log" | |
| "net/http" | |
| "strings" | |
| ) | |
| // Input models | |
| type speaker0_0_1 struct { | |
| Name string `json:"name"` | |
| } | |
| type speaker0_1_0 struct { | |
| Name string `json:"name"` | |
| Topic string `json:"topic"` | |
| } | |
| type speaker0_2_0 struct { | |
| Name string `json:"name"` | |
| Topic string `json:"topic"` | |
| HomePage string `json:"homePage"` | |
| } | |
| type speaker0_3_0 struct { | |
| Name string `json:"name"` | |
| Topic string `json:"topic"` | |
| LinkedIn string `json:"linkedIn"` | |
| } | |
| // Controllers | |
| func sepakerController0_0_1(w rest.ResponseWriter, req *rest.Request) { | |
| writeBadRequest("API deprecated. Use /v0.3.0/speaker", w) | |
| } | |
| func speakerController0_1_0(w rest.ResponseWriter, req *rest.Request) { | |
| writeBadRequest("API deprecated. Use /v0.3.0/speaker", w) | |
| } | |
| func speakerController0_2_0(w rest.ResponseWriter, req *rest.Request) { | |
| decodeSpeaker(&speaker0_2_0{}, req, w, meetupController) | |
| } | |
| func speakerController0_3_0(w rest.ResponseWriter, req *rest.Request) { | |
| decodeSpeaker(&speaker0_3_0{}, req, w, meetupController) | |
| } | |
| // Converters to domain objects | |
| type speakerInputToDomain interface { | |
| ToDomainObject() (speaker, error) | |
| } | |
| func (s *speaker0_2_0) ToDomainObject() (speaker, error) { | |
| linkedinPrefix := "https://www.linkedin.com/in/" | |
| if strings.HasPrefix(s.HomePage, linkedinPrefix) { | |
| return speaker{ | |
| Name: s.Name, | |
| Topic: s.Topic, | |
| LinkedIn: s.HomePage[len(linkedinPrefix):], | |
| }, nil | |
| } | |
| return speaker{}, errors.New("Only Linkedin usrs are supported. Use /v0.3.0/speaker") | |
| } | |
| func (s *speaker0_3_0) ToDomainObject() (speaker, error) { | |
| return speaker{ | |
| Name: s.Name, | |
| Topic: s.Topic, | |
| LinkedIn: s.LinkedIn, | |
| }, nil | |
| } | |
| // Domain objects | |
| type speaker struct { | |
| Name string | |
| Topic string | |
| LinkedIn string | |
| } | |
| // Domain logic | |
| func meetupController(speaker speaker, w rest.ResponseWriter) { | |
| w.WriteHeader(201) | |
| w.WriteJson(&speaker) | |
| } | |
| // Representation helper functions | |
| func decodeSpeaker(speaker speakerInputToDomain, req *rest.Request, w rest.ResponseWriter, controller func(speaker, rest.ResponseWriter)) { | |
| if err := req.DecodeJsonPayload(&speaker); err != nil { | |
| w.WriteHeader(http.StatusInternalServerError) | |
| log.Fatal(err) | |
| return | |
| } | |
| latest, err := speaker.ToDomainObject() | |
| if err != nil { | |
| writeBadRequest(err.Error(), w) | |
| return | |
| } | |
| controller(latest, w) | |
| } | |
| type errorResponse struct { | |
| Error string `json:"error"` | |
| } | |
| func writeBadRequest(message string, w rest.ResponseWriter) { | |
| w.WriteHeader(http.StatusBadRequest) | |
| w.WriteJson(&errorResponse{message}) | |
| } | |
| // Initiating server | |
| func main() { | |
| api := rest.NewApi() | |
| api.Use(rest.DefaultDevStack...) | |
| router, err := rest.MakeRouter( | |
| rest.Post("/v0.0.1/speaker", sepakerController0_0_1), | |
| rest.Post("/v0.1.0/speaker", speakerController0_1_0), | |
| rest.Post("/v0.2.0/speaker", speakerController0_2_0), | |
| rest.Post("/v0.3.0/speaker", speakerController0_3_0), | |
| ) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| api.SetApp(router) | |
| log.Fatal(http.ListenAndServe(":8080", api.MakeHandler())) | |
| } | |
| // Usage: | |
| // curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis"}' http://127.0.0.1:8080/v0.0.1/speaker | |
| // curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice"}' http://127.0.0.1:8080/v0.1.0/speaker | |
| // curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://aurelijus.banelis.lt"}' http://127.0.0.1:8080/v0.2.0/speaker | |
| // curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "homePage": "https://www.linkedin.com/in/aurelijusbanelis"}' http://127.0.0.1:8080/v0.2.0/speaker | |
| // curl -v -XPOST -H 'Content-type: application/json' -d '{"name":"Aurelijus Banelis", "topic": "JSON+Go in practice", "linkedIn": "aurelijusbanelis"}' http://127.0.0.1:8080/v0.3.0/speaker |
| {"topic":""} | |
| {"Attributes":{}} | |
| {"Attributes":{"field":"value"}} | |
| {} |