Dynamic group names with viper - go

I'm trying to get some groups from the config, which names are not known (will get more later on).
I'm new to golang and struggling a bit. I'm using Viper, because of its support for yaml, json and toml.
json config:
{
"database": {
"db": "mydb",
"host": "localhost",
"pass": "mypassword",
"port": 3306,
"user": "username"
},
"customers": {
"company_one": {
"address": "66 Great Queen St, London WC2B 5BX, UK",
"contacts": [
{
"email": "joe.doe#company-one.local",
"name": "Joe Doe"
},
{
"email": "jane.doe#company-one.local",
"name": "Jane Doe"
}
]
},
"company_two": {
"address": "Irish Town Pl, Gibraltar GX11 1AA, Gibraltar",
"contacts": [
{
"email": "lucky.luke#second-company.local",
"name": "Lucky Luke"
}
]
}
}
}
and the source:
package main
import (
"fmt"
"log"
"github.com/spf13/viper"
)
type Config struct {
database Database
customers Customer
}
type Database struct {
host string
port uint16
user string
pass string
db string
}
type Customer struct {
company Company
}
type Company struct {
contacts Contact
address string
}
type Contact struct {
name string
email string
}
func main() {
viper.SetConfigName("a")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
log.Fatal("Unable to read config file", err)
}
var conf Config
database := viper.GetStringMap("database")
for key, i := range database {
switch key {
case "host":
conf.database.host = i.(string)
case "port":
conf.database.port = i.(uint16)
case "user":
conf.database.user = i.(string)
case "pass":
conf.database.pass = i.(string)
case "db":
conf.database.db = i.(string)
}
}
fmt.Printf("%v\n", conf)
}
I have no idea how to cycle through the customers.
I have tried this one already (converted from go-toml to viper), but it didn't work as expected:
var contMap = map[string]Contacts
cust := config.Get("customers")
for _, c := range cust.Keys() {
sub := cust.Get(c).([]interface{})
for _,d := range sub{
address := d.([]interface{})[0].(string)
hostMap[host] = Contacts{
email: email,
name: name,
}
}
log.Printf("Customers: %s contact: %q", c, sub)
}

I started down the road of answering your initial question but it is so cumbersome and probably not what you actually want.
Your code as you've pasted it has a bunch of errors in it so instead let me offer a simpler solution like I was talking about in my comment.
package main
import (
"log"
"github.com/spf13/viper"
)
type Config struct {
Database Database `mapstructure:"database"`
Customers map[string]Company `mapstructure:"customers"`
}
type Database struct {
Host string `mapstructure:"host"`
Port uint16 `mapstructure:"port"`
User string `mapstructure:"user"`
Pass string `mapstructure:"pass"`
Db string `mapstructure:"db"`
}
type Company struct {
Address string `mapstructure:"address"`
Contacts []Contact `mapstructure:"contacts"`
}
type Contact struct {
Name string `mapstructure:"name"`
Email string `mapstructure:"email"`
}
func main() {
viper.SetConfigName("config")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
log.Fatal("Unable to read config file", err)
}
var conf Config
err := viper.Unmarshal(&conf)
if err != nil {
panic(err)
}
log.Printf("%#v", conf)
}
If you run this code with your JSON config file (named config) it returns the following output:
2018/04/28 14:47:54
main.Config{Database:main.Database{Host:"localhost", Port:0xcea,
User:"username", Pass:"mypassword", Db:"mydb"},
Customers:map[string]main.Company{"company_two":main.Company{Address:"Irish
Town Pl, Gibraltar GX11 1AA, Gibraltar",
Contacts:[]main.Contact{main.Contact{Name:"Lucky Luke",
Email:"lucky.luke#second-company.local"}}},
"company_one":main.Company{Address:"66 Great Queen St, London WC2B
5BX, UK", Contacts:[]main.Contact{main.Contact{Name:"Joe Doe",
Email:"joe.doe#company-one.local"}, main.Contact{Name:"Jane Doe",
Email:"jane.doe#company-one.local"}}}}}
And here it is reformatted as it would be written if you were creating this whole structure in code:
Config{
Database{
Host: "localhost",
Port: 0xcea,
User: "username",
Pass: "mypassword",
Db: "mydb"},
Customers: map[string]Company{
"company_two": Company{Address: "Irish Town Pl, Gibraltar GX11 1AA, Gibraltar",
Contacts: []Contact{
Contact{Name: "Lucky Luke", Email: "lucky.luke#second-company.local"}}},
"company_one": Company{Address: "66 Great Queen St, London WC2B 5BX, UK",
Contacts: []Contact{
Contact{Name: "Joe Doe", Email: "joe.doe#company-one.local"},
Contact{Name: "Jane Doe", Email: "jane.doe#company-one.local"}}},
},
}

Related

Read json array with json.NewDecoder

How do I get a json array (named list in the json file) to a list using json.NewDecoder? My struct looks like this:
type Config struct {
Data1 struct {
Host string `json:"host"`
Port string `json:"port"`
} `json:"data1"`
Data2 struct {
Host string `json:"host"`
Port string `json:"port"`
} `json:"data2"`
List struct {
Items []string
} `json:"list"`
}
and I'm parsing like this:
jsonParser := json.NewDecoder(configFile)
jsonParser.Decode(&config)
my config.json looks like this
{
"data1": {
"host": "10.10.20.20",
"port": "1234"
},
"data2": {
"host": "10.10.30.30",
"port": "5678"
},
"list": [
"item 1",
"item 2",
"item 3",
"item 4"
]
}
It's easy when the fields have names but I havn't figured out on how to get the information from the list...
I found a way to resolve your problom. here is the code:
package main
import (
"encoding/json"
"fmt"
"strings"
)
type ConfigWithoutList struct {
Data1 struct {
Host string `json:"host"`
Port string `json:"port"`
} `json:"data1"`
Data2 struct {
Host string `json:"host"`
Port string `json:"port"`
} `json:"data2"`
}
type Config struct {
ConfigWithoutList
List struct {
Items []string
} `json:"list"`
}
func (u *Config) UnmarshalJSON(data []byte) error {
aux := struct {
List []string `json:"list"`
ConfigWithoutList
}{}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
u.List = struct {
Items []string
}{
Items: aux.List,
}
return nil
}
func main() {
const jsonStream = `{
"data1": {
"host": "10.10.20.20",
"port": "1234"
},
"data2": {
"host": "10.10.30.30",
"port": "5678"
},
"list": [
"item 1",
"item 2",
"item 3",
"item 4"
]
}
`
config := Config{}
jsonParser := json.NewDecoder(strings.NewReader(jsonStream))
jsonParser.Decode(&config)
fmt.Println(config.List) // output => {[item 1 item 2 item 3 item 4]}
}

Golang REST api request with token auth to json array reponse

EDIT
This is the working code incase someone finds it useful. The title to this question was originally
"How to parse a list fo dicts in golang".
This is title is incorrect because I was referencing terms I'm familiar with in python.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
//Regional Strut
type Region []struct {
Region string `json:"region"`
Description string `json:"Description"`
ID int `json:"Id"`
Name string `json:"Name"`
Status int `json:"Status"`
Nodes []struct {
NodeID int `json:"NodeId"`
Code string `json:"Code"`
Continent string `json:"Continent"`
City string `json:"City"`
} `json:"Nodes"`
}
//working request and response
func main() {
url := "https://api.geo.com"
// Create a Bearer string by appending string access token
var bearer = "TOK:" + "TOKEN"
// Create a new request using http
req, err := http.NewRequest("GET", url, nil)
// add authorization header to the req
req.Header.Add("Authorization", bearer)
//This is what the response from the API looks like
//regionJson := `[{"region":"GEO:ABC","Description":"ABCLand","Id":1,"Name":"ABCLand [GEO-ABC]","Status":1,"Nodes":[{"NodeId":17,"Code":"LAX","Continent":"North America","City":"Los Angeles"},{"NodeId":18,"Code":"LBC","Continent":"North America","City":"Long Beach"}]},{"region":"GEO:DEF","Description":"DEFLand","Id":2,"Name":"DEFLand","Status":1,"Nodes":[{"NodeId":15,"Code":"NRT","Continent":"Asia","City":"Narita"},{"NodeId":31,"Code":"TYO","Continent":"Asia","City":"Tokyo"}]}]`
//Send req using http Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("Error on response.\n[ERROR] -", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("Error while reading the response bytes:", err)
}
var regions []Region
json.Unmarshal([]byte(body), &regions)
fmt.Printf("Regions: %+v", regions)
}
Have a look at this playground example for some pointers.
Here's the code:
package main
import (
"encoding/json"
"log"
)
func main() {
b := []byte(`
[
{"key": "value", "key2": "value2"},
{"key": "value", "key2": "value2"}
]`)
var mm []map[string]string
if err := json.Unmarshal(b, &mm); err != nil {
log.Fatal(err)
}
for _, m := range mm {
for k, v := range m {
log.Printf("%s [%s]", k, v)
}
}
}
I reformatted the API response you included because it is not valid JSON.
In Go it's necessary to define types to match the JSON schema.
I don't know why the API appends % to the end of the result so I've ignored that. If it is included, you will need to trim the results from the file before unmarshaling.
What you get from the unmarshaling is a slice of maps. Then, you can iterate over the slice to get each map and then iterate over each map to extract the keys and values.
Update
In your updated question, you include a different JSON schema and this change must be reflect in the Go code by update the types. There are some other errors in your code. Per my comment, I encourage you to spend some time learning the language.
package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
)
// Response is a type that represents the API response
type Response []Record
// Record is a type that represents the individual records
// The name Record is arbitrary as it is unnamed in the response
// Golang supports struct tags to map the JSON properties
// e.g. JSON "region" maps to a Golang field "Region"
type Record struct {
Region string `json:"region"`
Description string `json:"description"`
ID int `json:"id"`
Nodes []Node
}
type Node struct {
NodeID int `json:"NodeId`
Code string `json:"Code"`
}
func main() {
// A slice of byte representing your example response
b := []byte(`[{
"region": "GEO:ABC",
"Description": "ABCLand",
"Id": 1,
"Name": "ABCLand [GEO-ABC]",
"Status": 1,
"Nodes": [{
"NodeId": 17,
"Code": "LAX",
"Continent": "North America",
"City": "Los Angeles"
}, {
"NodeId": 18,
"Code": "LBC",
"Continent": "North America",
"City": "Long Beach"
}]
}, {
"region": "GEO:DEF",
"Description": "DEFLand",
"Id": 2,
"Name": "DEFLand",
"Status": 1,
"Nodes": [{
"NodeId": 15,
"Code": "NRT",
"Continent": "Asia",
"City": "Narita"
}, {
"NodeId": 31,
"Code": "TYO",
"Continent": "Asia",
"City": "Tokyo"
}]
}]`)
// To more closely match your code, create a Reader
rdr := bytes.NewReader(b)
// This matches your code, read from the Reader
body, err := ioutil.ReadAll(rdr)
if err != nil {
// Use Printf to format strings
log.Printf("Error while reading the response bytes\n%s", err)
}
// Initialize a variable of type Response
resp := &Response{}
// Try unmarshaling the body into it
if err := json.Unmarshal(body, resp); err != nil {
log.Fatal(err)
}
// Print the result
log.Printf("%+v", resp)
}

Parsing JSONSchema to struct type in golang

So, my use case consists of parsing varying JSON schemas into new struct types, which will be further used with an ORM to fetch data from a SQL database. Being compiled in nature, I believe there will not be an out-of-the-box solution in go, but is there any hack available to do this, without creating a separate go process. I tried with reflection, but could not find a satisfactory approach.
Currently, I am using a-h generate library which does generate the structs, but I am stuck at how to load these new struct types in go runtime.
EDIT
Example JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Address",
"id": "Address",
"type": "object",
"description": "address",
"properties": {
"houseName": {
"type": "string",
"description": "House Name",
"maxLength": 30
},
"houseNumber": {
"type": "string",
"description": "House Number",
"maxLength": 4
},
"flatNumber": {
"type": "string",
"description": "Flat",
"maxLength": 15
},
"street": {
"type": "string",
"description": "Address 1",
"maxLength": 40
},
"district": {
"type": "string",
"description": "Address 2",
"maxLength": 30
},
"town": {
"type": "string",
"description": "City",
"maxLength": 20
},
"county": {
"type": "string",
"description": "County",
"maxLength": 20
},
"postcode": {
"type": "string",
"description": "Postcode",
"maxLength": 8
}
}
}
Now, in the above-mentioned library, there is a command line tool, which generates the text for struct type for above json as below:
// Code generated by schema-generate. DO NOT EDIT.
package main
// Address address
type Address struct {
County string `json:"county,omitempty"`
District string `json:"district,omitempty"`
FlatNumber string `json:"flatNumber,omitempty"`
HouseName string `json:"houseName,omitempty"`
HouseNumber string `json:"houseNumber,omitempty"`
Postcode string `json:"postcode,omitempty"`
Street string `json:"street,omitempty"`
Town string `json:"town,omitempty"`
}
Now, the issue is that how to use this struct type without re-compilation in the program. There is a hack, where I can start a new go process, but that doesn't seem a good way to do it. One other way is to write my own parser for unmarshalling JSON schema, something like:
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
var f interface{}
json.Unmarshal(b, &f)
m := f.(map[string]interface{})
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case float64:
fmt.Println(k, "is float64", vv)
case int:
fmt.Println(k, "is int", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
Can someone please suggest some pointers to look for. Thanks.
So it looks like you're trying to implement your own json marshalling. That's no biggie: the standard json package already supports that. Just have your type implement the MarshalJSON and UnmarshalJSON functions (cf first example on the docs). Assuming some fields will be shared (eg schema, id, type), you can create a unified type like this:
// poor naming, but we need this level of wrapping here
type Data struct {
Metadata
}
type Metadata struct {
Schema string `json:"$schema"`
Type string `json:"type"`
Description string `json:"description"`
Id string `json:"id"`
Properties json.RawMessage `json:"properties"`
Address *Address `json:"-"`
// other types go here, too
}
Now all properties will be unmarshalled into a json.RawMessage field (essentially this is a []byte field). What you can do in your custom unmarshall function now is something like this:
func (d *Data) UnmarshalJSON(b []byte) error {
meta := Metadata{}
// unmarshall common fields
if err := json.Unmarshal(b, &meta); err != nil {
return err
}
// Assuming the Type field contains the value that allows you to determine what data you're actually unmarshalling
switch meta.Type {
case "address":
meta.Address = &Address{} // initialise field
if err := json.Unmarshal([]byte(meta.Properties), meta.Address); err != nil {
return err
}
case "name":
meta.Name = &Name{}
if err := json.Unmarshal([]byte(meta.Properties), meta.Name); err != nil {
return err
}
default:
return errors.New("unknown message type")
}
// all done
d.Metadata = meta // assign to embedded
// optionally: clean up the Properties field, as it contains raw JSON, and is exported
d.Metadata.Properties = json.RawMessage{}
return nil
}
You can do pretty much the same thing for marshalling. First work out what type you're actually working with, then marshal that object into the properties field, and then marhsal the entire structure
func (d Data) MarshalJSON() ([]byte, error) {
var (
prop []byte
err error
)
switch {
case d.Metadata.Address != nil:
prop, err = json.Marshal(d.Address)
case d.Metadata.Name != nil:
prop, err = json.Marshal(d.Name) // will only work if field isn't masked, better to be explicit
default:
err = errors.New("No properties to marshal") // handle in whatever way is best
}
if err != nil {
return nil, err
}
d.Metadata.Properties = json.RawMessage(prop)
return json.Marshal(d.Metadata) // marshal the unified type here
}

How to write insert golang code?

Here Is my json file and i want to insert the data using golang and
mgo in this json format
[{
"_id" : ObjectId("57307906f051147d5317984e"),
"dateAdded" : " 20015-11-10 23:00:00 +0000 UTC"
"firstName" : "chetan",
"lastName" : "kumar",
"age" : 23,
"user" : [
{
"userid" : ObjectId("57307906f051147d5317984a"),
"firstName" : "chetan",
"lastName" : "kumar",
"age" : 23
},
{
"userid" : ObjectId("57307906f051147d5317984b"),
"firstName" : "nepolean",
"lastName" : "dang",
"age" : 26
},
{
"userid" : ObjectId("57307906f051147d5317984c"),
"firstName" : "Raj",
"lastname" : "kumar",
"age" : 26
}
],
"sales" : [
{
"salesid" : ObjectId("57307906f051147d5317984d"),
"firstName" : "ashu",
"lastName" : "jha",
"age" : 27
}
]
}]
Now ,here is my go file which i was trying to insert data through
golang and mgo
package main
import(
"fmt"
"time"
"net/http"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type userinfo struct{
ID bson.ObjectId `json:"_id" bson:"_id"`
USER []User `json:"user" bson:"user"`
SALES []Sales `json:"sales" bson:"sales"`
DATEADDED time.Time `json:"dateAdded" bson:"dateAdded"`
NAME string `json:"name" bson:"name"`
}
type User struct{
USERID bson.ObjectId `json:"userid" bson:"userid"`
FIRSTNAME string `json:"firstName" bson:"firstName"`
LASTNAME string `json:"lastName" bson:"lastName"`
AGE int `json:"age" bson:"age"`
}
type Sales struct{
SALESID bson.ObjectId `json:"salesid" bson:"salesid"`
FIRSTNAME string `json:"firstName" bson:"firstName"`
LASTNAME string `json:"lastName" bson:"lastName"`
AGE int `json:"age" bson:"age"`
}
func post(w http.ResponseWriter,r *http.Request){
session,err := mgo.Dial("127.0.0.1")
if err != nil{
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic,true)
c:= session.DB("userdb").C("user")
fmt.Fprintln(w,"conn")
err = c.Insert(&userinfo{ID:new ObjectId(),NAME:"Admin",USER:{USERID:new ObjectId(), FIRSTNAME: "sam",LASTNAME : "billing",AGE : 25},SALES:{SALESID:new ObjectId(),FIRSTNAME : "joe",LASTNAME : "root",AGE : 23},DATEADDED:time.Now()})
if err != nil {
panic(err)
}
}
func main(){
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/post/",post)
http.ListenAndServe(":8080",router)
}
but it's not work at all please help me out
There is so much wrong with this.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Userinfo struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
USER []string `json:"user" bson:"user"`
SALES []string `json:"sales" bson:"sales"`
DATEADDED time.Time `json:"dateAdded" bson:"dateAdded"`
NAME string `json:"name" bson:"name"`
}
type User struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
FIRSTNAME string `json:"firstName" bson:"firstName"`
LASTNAME string `json:"lastName" bson:"lastName"`
AGE int `json:"age" bson:"age"`
}
type Sales struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
FIRSTNAME string `json:"firstName" bson:"firstName"`
LASTNAME string `json:"lastName" bson:"lastName"`
AGE int `json:"age" bson:"age"`
}
var session *mgo.Session
func main() {
var err error
session, err = mgo.Dial("127.0.0.1")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
fmt.Fprintln(w, "conn")
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/post/", post)
router.HandleFunc("/getusers/", getusers)
http.ListenAndServe(":8080", router)
}
func post(w http.ResponseWriter, r *http.Request) {
ms := session.Copy()
defer ms.Close()
cui := session.DB("userdb").C("userinfo")
cu := session.DB("userdb").C("user")
cs := session.DB("userdb").C("sales")
u := User{FIRSTNAME: "sam", LASTNAME: "billing", AGE: 25}
s := Sales{FIRSTNAME: "joe", LASTNAME: "root", AGE: 23}
if e := cu.Insert(u); e != nil {
log.Println(e.Error)
w.WriteHeader(500)
return
}
if e := cs.Insert(s); e != nil {
log.Println(e.Error)
w.WriteHeader(500)
return
}
ui := new(Userinfo)
ui.ID = bson.NewObjectId()
ui.NAME = "admin"
ui.USER = []string{u.Id.Hex()}
ui.SALES = []string{s.Id.Hex()}
ui.DATEADDED = time.Now()
if e := cui.Insert(ui); e != nil {
log.Println(e.Error)
w.WriteHeader(500)
return
}
w.WriteHeader(201)
}
func getusers(w http.ResponseWriter, r *http.Request) {
ms := session.Copy()
defer ms.Close()
cui := session.DB("userdb").C("userinfo")
cu := session.DB("userdb").C("user")
cs := session.DB("userdb").C("sales")
// Query for users of userinfo
uadm := new(Userinfo)
if e := cui.Find(bson.M{"name": "admin"}).One(uadm); e != nil {
log.Println(e.Error)
}
for _, userid := range uadm.USER {
tempu := new(User)
if e := cu.Find(bson.M{"_id": bson.ObjectIdHex(userid)}).One(tempu); e != nil {
log.Println(e.Error)
w.WriteHeader(500)
return
}
enc := json.NewEncoder(w)
if e := enc.Encode(tempu); e != nil {
log.Println(e.Error)
w.WriteHeader(500)
return
}
}
}
The session
Separate collections for all models
I've changed the user and sales fields of Userinfo into []string because every independantly editable and queryable model should have its own collection. Instead of the whole object the id reference is saved so you can query for a user by ObjectId.
Userinfo should be uppercase
Your syntax is off, in several places. However the code is messy so I'll split it like that:
users := {USERID: new ObjectId(), FIRSTNAME: "sam",LASTNAME: "billing", AGE: 25}
sales := {SALESID: new ObjectId(),FIRSTNAME: "joe", LASTNAME: "root", AGE: 23}
info := &userinfo{ID:new ObjectId(), NAME:"Admin", USER: users, SALES: sales, DATEADDED: time.Now()}
About the creation of sales and user, this is not the right way to create a slice and a struct. Instantiation of a struct of type T in Golang is T{}. You can create slices the same way. Therefore the sales and users become
users := []User{User{USERID: new ObjectId(), FIRSTNAME: "sam",LASTNAME: "billing", AGE: 25}}
sales := []Sales{Sales{SALESID: new ObjectId(),FIRSTNAME: "joe", LASTNAME: "root", AGE: 23}}
Then, you should take a look at mgo's documentation. The way of creating an ObjectId is bson.NewObjectId(). Now, with all the modifications:
func main() {
users := []User{User{USERID: bson.NewObjectId(), FIRSTNAME: "sam", LASTNAME: "billing", AGE: 25}}
sales := []Sales{Sales{SALESID: bson.NewObjectId(), FIRSTNAME: "joe", LASTNAME: "root", AGE: 23}}
info := &userinfo{ID: bson.NewObjectId(), NAME: "Admin", USER: users, SALES: sales, DATEADDED: time.Now()}
data, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(data))
}
/* Prints
{
"_id": "57402f27e13823740d742417",
"user": [
{
"userid": "57402f27e13823740d742415",
"firstName": "sam",
"lastName": "billing",
"age": 25
}
],
"sales": [
{
"salesid": "57402f27e13823740d742416",
"firstName": "joe",
"lastName": "root",
"age": 23
}
],
"dateAdded": "2016-05-21T11:49:27.507636096+02:00",
"name": "Admin"
}
*/

go-swagger not generating model info

Here is my simple rest service:
// Package classification User API.
//
// the purpose of this application is to provide an application
// that is using plain go code to define an API
//
// This should demonstrate all the possible comment annotations
// that are available to turn go code into a fully compliant swagger 2.0 spec
//
// Terms Of Service:
//
// there are no TOS at this moment, use at your own risk we take no responsibility
//
// Schemes: http, https
// Host: localhost
// BasePath: /v2
// Version: 0.0.1
// License: MIT http://opensource.org/licenses/MIT
// Contact: John Doe<john.doe#example.com> http://john.doe.com
//
// Consumes:
// - application/json
// - application/xml
//
// Produces:
// - application/json
// - application/xml
//
//
// swagger:meta
package main
import (
"github.com/gin-gonic/gin"
"strconv"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/gorp.v1"
"log"
)
// swagger:model
// User represents the user for this application
//
// A user is the security principal for this application.
// It's also used as one of main axis for reporting.
//
// A user can have friends with whom they can share what they like.
//
type User struct {
// the id for this user
//
// required: true
// min: 1
Id int64 `db:"id" json:"id"`
// the first name for this user
// required: true
// min length: 3
Firstname string `db:"firstname" json:"firstname"`
// the last name for this user
// required: true
// min length: 3
Lastname string `db:"lastname" json:"lastname"`
}
func main() {
r := gin.Default()
r.Use(Cors())
v1 := r.Group("api/v1")
{
v1.GET("/users", GetUsers)
v1.GET("/users/:id", GetUser)
v1.POST("/users", PostUser)
v1.PUT("/users/:id", UpdateUser)
v1.DELETE("/users/:id", DeleteUser)
v1.OPTIONS("/users", OptionsUser) // POST
v1.OPTIONS("/users/:id", OptionsUser) // PUT, DELETE
}
r.Run(":8696")
}
func GetUsers(c *gin.Context) {
// swagger:route GET /user listPets pets users
//
// Lists pets filtered by some parameters.
//
// This will show all available pets by default.
// You can get the pets that are out of stock
//
// Consumes:
// - application/json
// - application/x-protobuf
//
// Produces:
// - application/json
// - application/x-protobuf
//
// Schemes: http, https, ws, wss
//
// Security:
// api_key:
// oauth: read, write
//
// Responses:
// default: genericError
// 200: someResponse
// 422: validationError
var users []User
_, err := dbmap.Select(&users, "SELECT * FROM user")
if err == nil {
c.JSON(200, users)
} else {
c.JSON(404, gin.H{"error": "no user(s) into the table"})
}
// curl -i http://localhost:8080/api/v1/users
}
func GetUser(c *gin.Context) {
id := c.Params.ByName("id")
var user User
err := dbmap.SelectOne(&user, "SELECT * FROM user WHERE id=?", id)
if err == nil {
user_id, _ := strconv.ParseInt(id, 0, 64)
content := &User{
Id: user_id,
Firstname: user.Firstname,
Lastname: user.Lastname,
}
c.JSON(200, content)
} else {
c.JSON(404, gin.H{"error": "user not found"})
}
// curl -i http://localhost:8080/api/v1/users/1
}
func PostUser(c *gin.Context) {
var user User
c.Bind(&user)
if user.Firstname != "" && user.Lastname != "" {
if insert, _ := dbmap.Exec(`INSERT INTO user (firstname, lastname) VALUES (?, ?)`, user.Firstname, user.Lastname); insert != nil {
user_id, err := insert.LastInsertId()
if err == nil {
content := &User{
Id: user_id,
Firstname: user.Firstname,
Lastname: user.Lastname,
}
c.JSON(201, content)
} else {
checkErr(err, "Insert failed")
}
}
} else {
c.JSON(422, gin.H{"error": "fields are empty"})
}
// curl -i -X POST -H "Content-Type: application/json" -d "{ \"firstname\": \"Thea\", \"lastname\": \"Queen\" }" http://localhost:8080/api/v1/users
}
func UpdateUser(c *gin.Context) {
id := c.Params.ByName("id")
var user User
err := dbmap.SelectOne(&user, "SELECT * FROM user WHERE id=?", id)
if err == nil {
var json User
c.Bind(&json)
user_id, _ := strconv.ParseInt(id, 0, 64)
user := User{
Id: user_id,
Firstname: json.Firstname,
Lastname: json.Lastname,
}
if user.Firstname != "" && user.Lastname != ""{
_, err = dbmap.Update(&user)
if err == nil {
c.JSON(200, user)
} else {
checkErr(err, "Updated failed")
}
} else {
c.JSON(422, gin.H{"error": "fields are empty"})
}
} else {
c.JSON(404, gin.H{"error": "user not found"})
}
// curl -i -X PUT -H "Content-Type: application/json" -d "{ \"firstname\": \"Thea\", \"lastname\": \"Merlyn\" }" http://localhost:8080/api/v1/users/1
}
func DeleteUser(c *gin.Context) {
id := c.Params.ByName("id")
var user User
err := dbmap.SelectOne(&user, "SELECT id FROM user WHERE id=?", id)
if err == nil {
_, err = dbmap.Delete(&user)
if err == nil {
c.JSON(200, gin.H{"id #" + id: " deleted"})
} else {
checkErr(err, "Delete failed")
}
} else {
c.JSON(404, gin.H{"error": "user not found"})
}
// curl -i -X DELETE http://localhost:8080/api/v1/users/1
}
var dbmap = initDb()
func initDb() *gorp.DbMap {
db, err := sql.Open("mysql",
"root:max_123#tcp(127.0.0.1:3306)/gotest")
checkErr(err, "sql.Open failed")
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
dbmap.AddTableWithName(User{}, "User").SetKeys(true, "Id")
err = dbmap.CreateTablesIfNotExists()
checkErr(err, "Create table failed")
return dbmap
}
func checkErr(err error, msg string) {
if err != nil {
log.Fatalln(msg, err)
}
}
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
c.Next()
}
}
func OptionsUser(c *gin.Context) {
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "DELETE,POST, PUT")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
c.Next()
}
Now when I'm executing :
swagger generate spec -o ./swagger.json
to generate the json spec I'm getting:
{
"consumes": ["application/json", "application/xml"],
"produces": ["application/json", "application/xml"],
"schemes": ["http", "https"],
"swagger": "2.0",
"info": {
"description": "the purpose of this application is to provide an application\nthat is using plain go code to define an API\n\nThis should demonstrate all the possible comment annotations\nthat are available to turn go code into a fully compliant swagger 2.0 spec",
"title": "User API.",
"termsOfService": "there are no TOS at this moment, use at your own risk we take no responsibility",
"contact": {
"name": "John Doe",
"url": "http://john.doe.com",
"email": "john.doe#example.com"
},
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/MIT"
},
"version": "0.0.1"
},
"host": "localhost",
"basePath": "/v2",
"paths": {
"/user": {
"get": {
"description": "This will show all available pets by default.\nYou can get the pets that are out of stock",
"consumes": ["application/json", "application/x-protobuf"],
"produces": ["application/json", "application/x-protobuf"],
"schemes": ["http", "https", "ws", "wss"],
"tags": ["listPets", "pets"],
"summary": "Lists pets filtered by some parameters.",
"operationId": "users",
"security": [{
"api_key": null
}, {
"oauth": ["read", "write"]
}],
"responses": {
"200": {
"$ref": "#/responses/someResponse"
},
"422": {
"$ref": "#/responses/validationError"
},
"default": {
"$ref": "#/responses/genericError"
}
}
}
}
},
"definitions": {}
}
Note that my definitions are empty, not sure why.
If I paste the same json spec in http://editor.swagger.io/#/
It says
Error
Object
message: "options.definition is required"
code: "UNCAUGHT_SWAY_WORKER_ERROR"
Any directions on what is the right way to generate swagger documentation would help
It's because go-swagger can't detect the usage of your definitions. I made the assumption that you'd always have a struct describing the parameters and that those would always use the definitions that are in use.
It would be great if you could submit this question as an issue on the repo with your sample program. I'll add it to my tests.

Resources