Repository: gernest/apidemic Branch: master Commit: ba1110a927d7 Files: 15 Total size: 34.3 KB Directory structure: gitextract_lzm2lnmp/ ├── .gitignore ├── .travis.yml ├── LICENCE ├── Makefile ├── README.md ├── api.go ├── api_test.go ├── cmd/ │ └── apidemic/ │ └── main.go ├── fixtures/ │ ├── response.json │ ├── sample.json │ └── sample_request.json ├── json.go ├── json_test.go ├── tags.go └── tags_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ bin/ ================================================ FILE: .travis.yml ================================================ language: go go: - 1.9 - "1.10" install: - go get -t ./... script: - make test ================================================ FILE: LICENCE ================================================ Copyright (c) 2015 Geofrey Ernest Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ test : @go test -cover deps: @go get github.com/mitchellh/gox dist: @gox -output="bin/{{.Dir}}v$(VERSION)_{{.OS}}_{{.Arch}}/{{.Dir}}" ./cmd/apidemic ================================================ FILE: README.md ================================================ # apidemic [![Build Status](https://travis-ci.org/gernest/apidemic.svg)](https://travis-ci.org/gernest/apidemic) Apidemic is a service for generating fake JSON response. You first register the sample JSON response, and apidemic will serve that response with random fake data. This is experimental, so take it with a grain of salt. # Motivation I got bored with hardcoding the sample json api response in tests. If you know golang, you can benefit by using the library, I have included a router that you can use to run disposable servers in your tests. # Installation You can download the binaries for your respective operating system [Download apidemic](https://github.com/gernest/apidemic/releases/latest) Then put the downloaded binary somewhere in your system path. Alternatively, if you have golang installed go get github.com/gernest/apidemic/cmd/apidemic Now you can start the service like this apidemic start This will run a service at localhost default port is 3000, you can change the port by adding a flag `--port=YOUR_PORT_NUMBER` # How to use Lets say you expect a response like this ```json { "name": "anton", "age": 29, "nothing": null, "true": true, "false": false, "list": [ "first", "second" ], "list2": [ { "street": "Street 42", "city": "Stockholm" }, { "street": "Street 42", "city": "Stockholm" } ], "address": { "street": "Street 42", "city": "Stockholm" }, "country": { "name": "Sweden" } } ``` If you have already started apidemic server you can register that response by making a POST request to the `/register` path. Passing the json body of the form. ```json { "endpoint": "test", "payload": { "name: first_name": "anton", "age: digits_n,max=2": 29, "nothing:": null, "true": true, "false": false, "list:word,max=3": [ "first", "second" ], "list2": [ { "street:street": "Street 42", "city:city": "Stockholm" }, { "street": "Street 42", "city": "Stockholm" } ], "address": { "street:street": "Street 42", "city:city": "Stockholm" }, "country": { "name:country": "Sweden" } } } ``` See the annotation tags on the payload. Example if I want to generate full name for a field name I will just add `"name:full_name"`. Once your POST request is submitted you are good to ask for the response with fake values. Just make a GET request to the endpoint you registered. So every GET call to `/api/test` will return the api response with fake data. # Routes Apidemic server has only three http routes ### / This is the home path. It only renders information about the apidemic server. ### /register This is where you register endpoints. You POST the annotated sample JSON here. The request body should be a json object of signature. ```json { "endpoint":"my_endpoint", "payload": { ANNOTATED__SAMPLE_JSON_GOES_HERE }, } ``` #### /api/{REGISTERED_ENDPOINT_GOES_HERE} Every GET request on this route will render a fake JSON object for the sample registered in this endpoint. #### Other HTTP Methods In case you need to mimic endpoints which respond to requests other than GET then make sure to add an `http_method` key with the required method name into your API description. ```json { "endpoint": "test", "http_method": "POST", "payload": { "name: first_name": "anton" } } ``` Currently supported HTTP methods are: `OPTIONS`, `GET`, `POST`, `PUT`, `DELETE`, `HEAD`, default is `GET`. Please open an issue if you think there should be others added. #### Emulate unexpected responses Sometimes you need to ensure that your application handles API errors correctly in which case you can add a `response_code_probabilities` field with a map of response codes to probabilities. ```json { "endpoint": "test", "response_code_probabilities": { "404": 10, "503": 5, "418": 1 }, "payload": { "name: first_name": "anton" } } ``` With the above configuration there's a 84% chance to get a `200 OK` response. The server will respond with `404 Not Found` about 1 out of 10 times and with `503 Service Unavailable` 1 out of 20 times. There's also a 1% chance for the server to claim to be a [Teapot](https://tools.ietf.org/html/rfc2324). **Note**: JSON keys must be strings, providing your response codes as integers will not work! # Tags Apidemic uses tags to annotate what kind of fake data to generate and also control different requrements of fake data. You add tags to object keys. For instance let's say you have a JSON object `{ "user_name": "gernest"}`. If you want to have a fake username then you can annotate the key by adding user_name tag like this `{ "user_name:user_name": "gernest"}`. So JSON keys can be annotated by adding the `:` symbol then followed by comma separated list of tags. The first entry after `:` is for the tag type, the following entries are in the form `key=value` which will be the extra information to fine-tune your fake data. Please see the example above to see how tags are used. Apidemic comes shipped with a large number of tags, meaning it is capable to generate a wide range of fake information. These are currently available tags to generate different fake data: Tag | Details( data generated) ------|-------- brand | brand character | character characters | characters characters_n | characters of maximum length n city | city color | color company | company continent | continent country | country credit_card_num | credit card number currency | currency currency_code | currency code day | day digits | digits digits_n | digits of maximum number n domain_name | domain name domain_zone | domain zone email_address | email address email_body | email body female_first_name | female first name female_full_name | female full name female_full_name_with_prefix | female full name with prefix female_full_name_with_suffix | female full name with suffix female_last_name | female last name female_last_name_pratronymic | female last name pratronymic first_name | first name full_name | full name full_name_with_prefix | full name with prefix full_name_with_suffix | full name with suffix gender | gender gender_abrev | gender abrev hex_color | hex color hex_color_short | hex color short i_pv_4 | i pv 4 industry | industry job_title | job title language | language last_name | last name latitude_degrees | latitude degrees latitude_direction | latitude direction latitude_minutes | latitude minutes latitude_seconds | latitude seconds latitude | latitude longitude | longitude longitude_degrees | longitude degrees longitude_direction | longitude direction longitude_minutes | longitude minutes longitude_seconds | longitude seconds male_first_name | male first name male_full_name_with_prefix | male full name with prefix male_full_name_with_suffix | male full name with suffix male_last_name | male last name male_pratronymic | male pratronymic model | model month | month month_num | month num month_short | month short paragraph | paragraph patagraphs | patagraphs patagraphs_n | patagraphs of maximum n password | password patronymic | patronymic phone | phone product | product product_name | product name sentence | sentence sentences | sentences sentences_n | sentences of maximum n simple_pass_word | simple pass word state | state state_abbrev | state abbrev street | street street_address | street address title | title top_level_domain | top level domain user_name | user name week_day | week day week_day_short | week day short week_day_num | week day num word | word words | words words_n | words of maximum n year | year zip | zip # Benchmark This Benchmark uses [boom](https://github.com/rakyll/boom). After registering the sample json above run the following command (Note this is just to check things out, my machine is very slow) ```bash boom -n 1000 -c 100 http://localhost:3000/api/test ``` The result ```bash Summary: Total: 0.6442 secs. Slowest: 0.1451 secs. Fastest: 0.0163 secs. Average: 0.0586 secs. Requests/sec: 1552.3336 Total Data Received: 39000 bytes. Response Size per Request: 39 bytes. Status code distribution: [200] 1000 responses Response time histogram: 0.016 [1] | 0.029 [121] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 0.042 [166] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 0.055 [192] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 0.068 [192] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 0.081 [168] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 0.094 [69] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎ 0.106 [41] |∎∎∎∎∎∎∎∎ 0.119 [22] |∎∎∎∎ 0.132 [21] |∎∎∎∎ 0.145 [7] |∎ Latency distribution: 10% in 0.0280 secs. 25% in 0.0364 secs. 50% in 0.0560 secs. 75% in 0.0751 secs. 90% in 0.0922 secs. 95% in 0.1066 secs. 99% in 0.1287 secs. ``` # Contributing Start with clicking the star button to make the author and his neighbors happy. Then fork the repository and submit a pull request for whatever change you want to be added to this project. If you have any questions, just open an issue. # Author Geofrey Ernest Twitter : [@gernesti](https://twitter.com/gernesti) # Licence This project is released under the MIT licence. See [LICENCE](LICENCE) for more details. ================================================ FILE: api.go ================================================ package apidemic import ( "encoding/json" "errors" "fmt" "math/rand" "net/http" "time" "github.com/gorilla/mux" "github.com/pmylund/go-cache" ) // Version is the version of apidemic. Apidemic uses semver. const Version = "0.4" var maxItemTime = cache.DefaultExpiration var store = func() *cache.Cache { c := cache.New(5*time.Minute, 30*time.Second) return c }() var allowedHttpMethods = []string{"OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD"} // API is the struct for the json object that is passed to apidemic for registration. type API struct { Endpoint string `json:"endpoint"` HTTPMethod string `json:"http_method"` ResponseCodeProbabilities map[int]int `json:"response_code_probabilities"` Payload map[string]interface{} `json:"payload"` } // Home renders hopme page. It renders a json response with information about the service. func Home(w http.ResponseWriter, r *http.Request) { details := make(map[string]interface{}) details["app_name"] = "ApiDemic" details["version"] = Version details["details"] = "Fake JSON API response" RenderJSON(w, http.StatusOK, details) return } // FindResponseCode helps imitating the backend responding with an error message occasionally // Example: // {"404": 8, "403": 12, "500": 20, "503": 3} // 8% chance of getting 404 // 12% chance of getting a 500 error // 3% chance of getting a 503 error // 77% chance of getting 200 OK or 201 Created depending on the HTTP method func FindResponseCode(responseCodeProbabilities map[int]int, method string) int { sum := 0 r := rand.Intn(100) for code, probability := range responseCodeProbabilities { if probability+sum > r { return code } sum = sum + probability } if method == "POST" { return http.StatusCreated } return http.StatusOK } // RenderJSON helper for rendering JSON response, it marshals value into json and writes // it into w. func RenderJSON(w http.ResponseWriter, code int, value interface{}) { if code >= 400 || code == http.StatusNoContent { http.Error(w, "", code) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) err := json.NewEncoder(w).Encode(value) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // RegisterEndpoint receives API objects and registers them. The payload from the request is // transformed into a self aware Value that is capable of faking its own attribute. func RegisterEndpoint(w http.ResponseWriter, r *http.Request) { var httpMethod string a := API{} err := json.NewDecoder(r.Body).Decode(&a) if err != nil { RenderJSON(w, http.StatusBadRequest, NewResponse(err.Error())) return } if httpMethod, err = getAllowedMethod(a.HTTPMethod); err != nil { RenderJSON(w, http.StatusBadRequest, NewResponse(err.Error())) return } eKey, rcpKey := getCacheKeys(a.Endpoint, httpMethod) if _, ok := store.Get(eKey); ok { RenderJSON(w, http.StatusOK, NewResponse("endpoint already taken")) return } obj := NewObject() err = obj.Load(a.Payload) if err != nil { RenderJSON(w, http.StatusInternalServerError, NewResponse(err.Error())) return } store.Set(eKey, obj, maxItemTime) store.Set(rcpKey, a.ResponseCodeProbabilities, maxItemTime) RenderJSON(w, http.StatusOK, NewResponse("cool")) } func getCacheKeys(endpoint, httpMethod string) (string, string) { eKey := fmt.Sprintf("%s-%v-e", endpoint, httpMethod) rcpKey := fmt.Sprintf("%s-%v-rcp", endpoint, httpMethod) return eKey, rcpKey } func getAllowedMethod(method string) (string, error) { if method == "" { return "GET", nil } for _, m := range allowedHttpMethods { if method == m { return m, nil } } return "", errors.New("HTTP method is not allowed") } // DynamicEndpoint renders registered endpoints. func DynamicEndpoint(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) eKey, rcpKey := getCacheKeys(vars["endpoint"], r.Method) if eVal, ok := store.Get(eKey); ok { if rcpVal, ok := store.Get(rcpKey); ok { code := FindResponseCode(rcpVal.(map[int]int), r.Method) RenderJSON(w, code, eVal) return } } responseText := fmt.Sprintf("apidemic: %s has no %s endpoint", vars["endpoint"], r.Method) RenderJSON(w, http.StatusNotFound, NewResponse(responseText)) } // NewResponse helper for response JSON message func NewResponse(message string) interface{} { return struct { Text string `json:"text"` }{ message, } } // NewServer returns a new apidemic server func NewServer() *mux.Router { m := mux.NewRouter() m.HandleFunc("/", Home) m.HandleFunc("/register", RegisterEndpoint).Methods("POST") m.HandleFunc("/api/{endpoint}", DynamicEndpoint).Methods("OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD") return m } ================================================ FILE: api_test.go ================================================ package apidemic import ( "bytes" "encoding/json" "io" "io/ioutil" "net/http" "net/http/httptest" "testing" "time" "github.com/gorilla/mux" "github.com/pmylund/go-cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDynamicEndpointFailsWithoutRegistration(t *testing.T) { s := setUp() payload := registerPayload(t, "fixtures/sample_request.json") w := httptest.NewRecorder() req := jsonRequest("POST", "/api/test", payload) s.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestDynamicEndpointWithGetRequest(t *testing.T) { s := setUp() payload := registerPayload(t, "fixtures/sample_request.json") w := httptest.NewRecorder() req := jsonRequest("POST", "/register", payload) s.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) w = httptest.NewRecorder() req = jsonRequest("GET", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestDynamicEndpointWithPostRequest(t *testing.T) { s := setUp() payload := registerPayload(t, "fixtures/sample_request.json") payload["http_method"] = "POST" w := httptest.NewRecorder() req := jsonRequest("POST", "/register", payload) s.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) w = httptest.NewRecorder() req = jsonRequest("POST", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) } func TestDynamicEndpointWithForbiddenResponse(t *testing.T) { s := setUp() registerPayload := registerPayload(t, "fixtures/sample_request.json") registerPayload["response_code_probabilities"] = map[string]int{"403": 100} w := httptest.NewRecorder() req := jsonRequest("POST", "/register", registerPayload) s.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) w = httptest.NewRecorder() req = jsonRequest("GET", "/api/test", nil) s.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) } func setUp() *mux.Router { store = cache.New(5*time.Minute, 30*time.Second) return NewServer() } func registerPayload(t *testing.T, fixtureFile string) map[string]interface{} { content, err := ioutil.ReadFile(fixtureFile) if err != nil { t.Fatal(err) } var api map[string]interface{} err = json.NewDecoder(bytes.NewReader(content)).Decode(&api) if err != nil { t.Fatal(err) } return api } func jsonRequest(method string, path string, body interface{}) *http.Request { var bEnd io.Reader if body != nil { b, err := json.Marshal(body) if err != nil { return nil } bEnd = bytes.NewReader(b) } req, err := http.NewRequest(method, path, bEnd) if err != nil { panic(err) } req.Header.Set("Content-Type", "application/json") return req } ================================================ FILE: cmd/apidemic/main.go ================================================ package main import ( "fmt" "log" "net/http" "github.com/codegangsta/cli" "github.com/gernest/apidemic" ) func server(ctx *cli.Context) { port := ctx.Int("port") s := apidemic.NewServer() log.Println("starting server on port :", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), s)) } func main() { app := cli.NewApp() app.Name = "apidemic" app.Usage = "Fake JSON API Responses" app.Authors = []cli.Author{ {"Geofrey Ernest", "geofreyernest@live.com"}, } app.Version = apidemic.Version app.Commands = []cli.Command{ cli.Command{ Name: "start", ShortName: "s", Usage: "starts apidemic server", Action: server, Flags: []cli.Flag{ cli.IntFlag{ Name: "port", Usage: "HTTP port to run", Value: 3000, EnvVar: "PORT", }, }, }, } app.RunAndExitOnError() } ================================================ FILE: fixtures/response.json ================================================ { "name": "anton", "age": 29, "nothing": null, "true": true, "false": false, "list": [ "first", "second" ], "list2": [ { "street": "Street 42", "city": "Stockholm" }, { "street": "Street 42", "city": "Stockholm" } ], "address": { "street": "Street 42", "city": "Stockholm" }, "country": { "name": "Sweden" } } ================================================ FILE: fixtures/sample.json ================================================ { "name: first_name": "anton", "age: digits_n,max=2": 29, "nothing:": null, "true": true, "false": false, "list:word,max=3": [ "first", "second" ], "list2": [ { "street:street": "Street 42", "city:city": "Stockholm" }, { "street": "Street 42", "city": "Stockholm" } ], "address": { "street:street": "Street 42", "city:city": "Stockholm" }, "country": { "name:country": "Sweden" } } ================================================ FILE: fixtures/sample_request.json ================================================ { "endpoint": "test", "payload": { "name: first_name": "anton", "age: digits_n,max=2": 29, "nothing:": null, "true": true, "false": false, "list:word,max=3": [ "first", "second" ], "list2": [ { "street:street": "Street 42", "city:city": "Stockholm" }, { "street": "Street 42", "city": "Stockholm" } ], "address": { "street:street": "Street 42", "city:city": "Stockholm" }, "country": { "name:country": "Sweden" } } } ================================================ FILE: json.go ================================================ package apidemic import ( "encoding/json" "io" "strconv" "strings" "github.com/icrowley/fake" ) type Value struct { Tags Tags Data interface{} } func (v Value) Update() Value { switch v.Data.(type) { case string: return fakeString(&v) case float64: return fakeFloats(&v) case []interface{}: return fakeArray(&v) case map[string]interface{}: return fakeObject(&v) } return v } func (v Value) MarshalJSON() ([]byte, error) { return json.Marshal(v.Update().Data) } func NewValue(val interface{}) Value { return Value{Tags: make(Tags), Data: val} } type Object struct { Data map[string]Value } func NewObject() *Object { return &Object{Data: make(map[string]Value)} } func (o *Object) Load(src map[string]interface{}) error { for key, val := range src { value := NewValue(val) sections := strings.Split(key, ":") if len(sections) == 2 { key = sections[0] value.Tags.Load(sections[1]) } o.Set(key, value) } return nil } func (o *Object) Set(key string, val Value) { o.Data[key] = val } func (v *Object) MarshalJSON() ([]byte, error) { return json.Marshal(v.Data) } func parseJSONData(src io.Reader) (*Object, error) { var in map[string]interface{} err := json.NewDecoder(src).Decode(&in) if err != nil { return nil, err } o := NewObject() err = o.Load(in) if err != nil { return nil, err } return o, nil } func fakeString(v *Value) Value { return Value{Data: genFakeData(v)} } func fakeArray(v *Value) Value { arrV, ok := v.Data.([]interface{}) if !ok { return *v } nv := *v var rst []interface{} if len(arrV) > 0 { origin := arrV[0] n := len(arrV) if max, ok := v.Tags.Get("max"); ok { maxV, err := strconv.Atoi(max) if err != nil { return *v } n = maxV } for i := 0; i < n; i++ { newVal := NewValue(origin) rst = append(rst, newVal) } } nv.Data = rst return nv } func fakeFloats(v *Value) Value { return Value{Data: genFakeData(v)} } func fakeObject(v *Value) Value { obj := NewObject() obj.Load(v.Data.(map[string]interface{})) return NewValue(obj.Data) } func genFakeData(v *Value) interface{} { if len(v.Tags) == 0 { return v.Data } typ, ok := v.Tags.Get("type") if !ok { return v.Data } switch typ { case fieldTags.Brand: return fake.Brand() case fieldTags.Character: return fake.Character() case fieldTags.Characters: return fieldTags.Characters case fieldTags.CharactersN: max := 5 if m, err := v.Tags.Int("max"); err == nil { max = m } return fake.CharactersN(max) case fieldTags.City: return fake.City() case fieldTags.Color: return fake.Color() case fieldTags.Company: return fake.Company() case fieldTags.Continent: return fake.Continent() case fieldTags.Country: return fake.Country() case fieldTags.CreditCardNum: vendor, _ := v.Tags.Get("vendor") fake.CreditCardNum(vendor) case fieldTags.Currency: fake.Currency() case fieldTags.CurrencyCode: fake.CurrencyCode() case fieldTags.Day: return fake.Day() case fieldTags.Digits: return fake.Digits() case fieldTags.DigitsN: max := 5 if m, err := v.Tags.Int("max"); err == nil { max = m } return fake.DigitsN(max) case fieldTags.DomainName: return fake.DomainName() case fieldTags.DomainZone: return fake.DomainZone() case fieldTags.EmailAddress: return fake.EmailAddress() case fieldTags.EmailBody: return fake.EmailBody() case fieldTags.FemaleFirstName: return fake.FemaleFirstName() case fieldTags.FemaleFullName: return fake.FemaleFullName() case fieldTags.FemaleFullNameWithPrefix: return fake.FemaleFullNameWithPrefix() case fieldTags.FemaleFullNameWithSuffix: return fake.FemaleFullNameWithSuffix() case fieldTags.FemaleLastName: return fake.FemaleLastName() case fieldTags.FemaleLastNamePratronymic: return fake.FemalePatronymic() case fieldTags.FirstName: return fake.FirstName() case fieldTags.FullName: return fake.FullName() case fieldTags.FullNameWithPrefix: return fake.FullNameWithPrefix() case fieldTags.FullNameWithSuffix: return fake.FullNameWithSuffix() case fieldTags.Gender: return fake.Gender() case fieldTags.GenderAbrev: return fake.GenderAbbrev() case fieldTags.HexColor: return fake.HexColor() case fieldTags.HexColorShort: return fake.HexColorShort() case fieldTags.IPv4: return fake.IPv4() case fieldTags.Industry: return fake.Industry() case fieldTags.JobTitle: return fake.JobTitle() case fieldTags.Language: return fake.Language() case fieldTags.LastName: return fake.LastName() case fieldTags.LatitudeDegrees: return fake.LatitudeDegrees() case fieldTags.LatitudeDirection: return fake.LatitudeDirection() case fieldTags.LatitudeMinutes: return fake.LatitudeMinutes() case fieldTags.LatitudeSeconds: return fake.LatitudeSeconds() case fieldTags.Latitude: return fake.Latitude() case fieldTags.LongitudeDegrees: return fake.LongitudeDegrees() case fieldTags.LongitudeDirection: return fake.LongitudeDirection() case fieldTags.LongitudeMinutes: return fake.LongitudeMinutes() case fieldTags.LongitudeSeconds: return fake.LongitudeSeconds() case fieldTags.MaleFirstName: return fake.MaleFirstName() case fieldTags.MaleFullNameWithPrefix: return fake.MaleFullNameWithPrefix() case fieldTags.MaleFullNameWithSuffix: return fake.MaleFullNameWithSuffix() case fieldTags.MaleLastName: return fake.MaleLastName() case fieldTags.MalePratronymic: return fake.MalePatronymic() case fieldTags.Model: return fake.Model() case fieldTags.Month: return fake.Month() case fieldTags.MonthNum: return fake.MonthNum() case fieldTags.MonthShort: return fake.MonthShort() case fieldTags.Paragraph: return fake.Paragraph() case fieldTags.Patagraphs: return fake.Paragraphs() case fieldTags.PatagraphsN: max := 5 if m, err := v.Tags.Int("max"); err == nil { max = m } return fake.ParagraphsN(max) case fieldTags.Password: var ( atLeast = 5 atMost = 8 allowUpper, allowNumeric, allowSpecial = true, true, true ) if least, err := v.Tags.Int("at_least"); err == nil { atLeast = least } if most, err := v.Tags.Int("at_most"); err == nil { atMost = most } if upper, err := v.Tags.Bool("upper"); err == nil { allowUpper = upper } if numeric, err := v.Tags.Bool("numeric"); err == nil { allowNumeric = numeric } if special, err := v.Tags.Bool("special"); err == nil { allowSpecial = special } return fake.Password(atLeast, atMost, allowUpper, allowNumeric, allowSpecial) case fieldTags.Patronymic: return fake.Patronymic() case fieldTags.Phone: return fake.Phone() case fieldTags.Product: return fake.Product() case fieldTags.ProductName: return fake.ProductName() case fieldTags.Sentence: return fake.Sentence() case fieldTags.Sentences: return fake.Sentence() case fieldTags.SentencesN: max := 5 if m, err := v.Tags.Int("max"); err == nil { max = m } return fake.SentencesN(max) case fieldTags.SimplePassWord: return fake.SimplePassword() case fieldTags.State: return fake.State() case fieldTags.StateAbbrev: return fake.StateAbbrev() case fieldTags.Street: return fake.Street() case fieldTags.StreetAddress: return fake.StreetAddress() case fieldTags.Title: return fake.Title() case fieldTags.TopLevelDomain: return fake.TopLevelDomain() case fieldTags.UserName: return fake.UserName() case fieldTags.WeekDay: return fake.WeekDay() case fieldTags.WeekDayNum: return fake.WeekdayNum() case fieldTags.WeekDayShort: return fake.WeekDayShort() case fieldTags.Word: return fake.Word() case fieldTags.Words: return fake.Words() case fieldTags.WordsN: max := 5 if m, err := v.Tags.Int("max"); err == nil { max = m } return fake.WordsN(max) case fieldTags.Year: //return fake.Year() case fieldTags.Zip: return fake.Zip() } return v.Data } ================================================ FILE: json_test.go ================================================ package apidemic import ( "bytes" "encoding/json" "io/ioutil" "testing" ) func TestParseJSONData(t *testing.T) { data, err := ioutil.ReadFile("fixtures/sample.json") if err != nil { t.Fatal(err) } ob, err := parseJSONData(bytes.NewReader(data)) if err != nil { t.Error(err) } _, err = json.MarshalIndent(ob, "", "\t") if err != nil { t.Error(err) } } ================================================ FILE: tags.go ================================================ package apidemic import ( "errors" "strconv" "strings" ) var ErrTagNotFound = errors.New("apidemic: Tag not found") var fieldTags = struct { Brand string Character string Characters string CharactersN string City string Color string Company string Continent string Country string CreditCardNum string Currency string CurrencyCode string Day string Digits string DigitsN string DomainName string DomainZone string EmailAddress string EmailBody string FemaleFirstName string FemaleFullName string FemaleFullNameWithPrefix string FemaleFullNameWithSuffix string FemaleLastName string FemaleLastNamePratronymic string FirstName string FullName string FullNameWithPrefix string FullNameWithSuffix string Gender string GenderAbrev string HexColor string HexColorShort string IPv4 string Industry string JobTitle string Language string LastName string LatitudeDegrees string LatitudeDirection string LatitudeMinutes string LatitudeSeconds string Latitude string Longitude string LongitudeDegrees string LongitudeDirection string LongitudeMinutes string LongitudeSeconds string MaleFirstName string MaleFullNameWithPrefix string MaleFullNameWithSuffix string MaleLastName string MalePratronymic string Model string Month string MonthNum string MonthShort string Paragraph string Patagraphs string PatagraphsN string Password string Patronymic string Phone string Product string ProductName string Sentence string Sentences string SentencesN string SimplePassWord string State string StateAbbrev string Street string StreetAddress string Title string TopLevelDomain string UserName string WeekDay string WeekDayShort string WeekDayNum string Word string Words string WordsN string Year string Zip string }{ "brand", "character", "characters", "characters_n", "city", "color", "company", "continent", "country", "credit_card_num", "currency", "currency_code", "day", "digits", "digits_n", "domain_name", "domain_zone", "email_address", "email_body", "female_first_name", "female_full_name", "female_full_name_with_prefix", "female_full_name_with_suffix", "female_last_name", "female_last_name_pratronymic", "first_name", "full_name", "full_name_with_prefix", "full_name_with_suffix", "gender", "gender_abrev", "hex_color", "hex_color_short", "i_pv_4", "industry", "job_title", "language", "last_name", "latitude_degrees", "latitude_direction", "latitude_minutes", "latitude_seconds", "latitude", "longitude", "longitude_degrees", "longitude_direction", "longitude_minutes", "longitude_seconds", "male_first_name", "male_full_name_with_prefix", "male_full_name_with_suffix", "male_last_name", "male_pratronymic", "model", "month", "month_num", "month_short", "paragraph", "patagraphs", "patagraphs_n", "password", "patronymic", "phone", "product", "product_name", "sentence", "sentences", "sentences_n", "simple_pass_word", "state", "state_abbrev", "street", "street_address", "title", "top_level_domain", "user_name", "week_day", "week_day_short", "week_day_num", "word", "words", "words_n", "year", "zip", } //Tags stores metadata about values type Tags map[string]string // Load parses src and extacts tags from it. The src is a string with comma separated content. // Example "character_n,max=30" // // The first tag, is the value type information, the rest is extra information to fine tune // the generated fake value. // // For instance in the example above, the value is characters, where max=30 limits the number of characters // to the maximum size of 30. func (t Tags) Load(src string) { ss := strings.Split(src, ",") first := strings.TrimSpace(ss[0]) if len(ss) > 0 { t["type"] = first rest := ss[1:] for _, v := range rest { ts := strings.Split(v, "=") if len(ts) < 2 { t[v] = "" continue } t[strings.TrimSpace(ts[0])] = strings.TrimSpace(ts[1]) } } } // Get returns the value for tag key. func (t Tags) Get(key string) (string, bool) { k, ok := t[key] return k, ok } // Int returns an in value for tag key. func (t Tags) Int(key string) (int, error) { tag, ok := t.Get(key) if !ok { return 0, ErrTagNotFound } return strconv.Atoi(tag) } // Bool returns a boolean value for tag key func (t Tags) Bool(key string) (bool, error) { tag, ok := t.Get(key) if !ok { return false, ErrTagNotFound } return strconv.ParseBool(tag) } ================================================ FILE: tags_test.go ================================================ package apidemic import ( "testing" ) func TestTags(t *testing.T) { src := "characters_n,max=30" sample := []struct { tag, value string }{ {"type", "characters_n"}, {"max", "30"}, } tags := make(Tags) tags.Load(src) for _, v := range sample { k, ok := tags.Get(v.tag) if !ok { t.Errorf("expected %s to exist %#v", v.tag, tags) } if k != v.value { t.Errorf("expected %s got %s", v.value, k) } } max, err := tags.Int("max") if err != nil { t.Fatal(err) } if max != 30 { t.Errorf("expected %d got %d", 30, max) } }