[
  {
    "path": ".gitignore",
    "content": "bin/"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\ngo:\n    - 1.9\n    - \"1.10\"\ninstall:\n  - go get -t ./...\nscript:\n  - make test"
  },
  {
    "path": "LICENCE",
    "content": "Copyright (c) 2015 Geofrey Ernest <geofreyernest@live.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "test :\n\t@go test -cover\n\ndeps:\n\t@go get github.com/mitchellh/gox\n\ndist:\n\t@gox -output=\"bin/{{.Dir}}v$(VERSION)_{{.OS}}_{{.Arch}}/{{.Dir}}\" ./cmd/apidemic"
  },
  {
    "path": "README.md",
    "content": "# apidemic [![Build Status](https://travis-ci.org/gernest/apidemic.svg)](https://travis-ci.org/gernest/apidemic)\n\nApidemic 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.\n\nThis is experimental, so take it with a grain of salt.\n\n# Motivation\nI 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.\n\n# Installation\n\nYou can download the binaries for your respective operating system  [Download apidemic](https://github.com/gernest/apidemic/releases/latest)\n\nThen put the downloaded binary somewhere in your system path.\n\nAlternatively, if you have golang installed\n\n\tgo get github.com/gernest/apidemic/cmd/apidemic\n\t\n\nNow you can start the service like this \n\n\tapidemic start\n\t\nThis will run a service at localhost default port is 3000, you can change the port by adding a flag `--port=YOUR_PORT_NUMBER`\n\n\n# How to use\nLets say you expect a response like this\n\n```json\n{\n  \"name\": \"anton\",\n  \"age\": 29,\n  \"nothing\": null,\n  \"true\": true,\n  \"false\": false,\n  \"list\": [\n      \"first\",\n      \"second\"\n    ],\n  \"list2\": [\n    {\n      \"street\": \"Street 42\",\n      \"city\": \"Stockholm\"\n      },\n    {\n      \"street\": \"Street 42\",\n      \"city\": \"Stockholm\"\n      }\n    ],\n  \"address\": {\n    \"street\": \"Street 42\",\n    \"city\": \"Stockholm\"\n  },\n  \"country\": {\n    \"name\": \"Sweden\"\n  }\n}\n```\n\n\nIf 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.\n\n```json\n{\n  \"endpoint\": \"test\",\n  \"payload\": {\n    \"name: first_name\": \"anton\",\n    \"age: digits_n,max=2\": 29,\n    \"nothing:\": null,\n    \"true\": true,\n    \"false\": false,\n    \"list:word,max=3\": [\n      \"first\",\n      \"second\"\n    ],\n    \"list2\": [\n      {\n        \"street:street\": \"Street 42\",\n        \"city:city\": \"Stockholm\"\n      },\n      {\n        \"street\": \"Street 42\",\n        \"city\": \"Stockholm\"\n      }\n    ],\n    \"address\": {\n      \"street:street\": \"Street 42\",\n      \"city:city\": \"Stockholm\"\n    },\n    \"country\": {\n      \"name:country\": \"Sweden\"\n    }\n  }\n}\n```\n\nSee 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\"`.\n\nOnce 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.\n\nSo every GET call to `/api/test` will return the api response with fake data.\n\n# Routes \nApidemic server has only three http routes\n\n### /\nThis is the home path. It only renders information about the apidemic server.\n\n### /register\nThis is where you register endpoints. You POST the annotated sample JSON here. The request body should be a json object of signature.\n\n```json\n{\n\t\"endpoint\":\"my_endpoint\",\n\t\"payload\": { ANNOTATED__SAMPLE_JSON_GOES_HERE },\n}\n```\n\n#### /api/{REGISTERED_ENDPOINT_GOES_HERE}\nEvery GET request on this route will render a fake JSON object for the sample registered in this endpoint.\n\n#### Other HTTP Methods\nIn 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.\n\n```json\n{\n  \"endpoint\": \"test\",\n  \"http_method\": \"POST\",\n  \"payload\": {\n    \"name: first_name\": \"anton\"\n  }\n}\n```\n\nCurrently 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.\n\n#### Emulate unexpected responses\nSometimes 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.\n```json\n{\n  \"endpoint\": \"test\",\n  \"response_code_probabilities\": {\n    \"404\": 10,\n    \"503\": 5,\n    \"418\": 1\n  },\n  \"payload\": {\n    \"name: first_name\": \"anton\"\n  }\n}\n```\n\nWith the above configuration there's a 84% chance to get a `200 OK` response.\nThe server will respond with `404 Not Found` about 1 out of 10 times and with `503 Service Unavailable` 1 out of 20 times.\nThere's also a 1% chance for the server to claim to be a [Teapot](https://tools.ietf.org/html/rfc2324).\n\n**Note**: JSON keys must be strings, providing your response codes as integers will not work!\n\n# Tags\nApidemic uses tags to annotate what kind of fake data to generate and also control different requrements of fake data.\n\nYou 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\"}`.\n\nSo 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.\n\nApidemic comes shipped with a large number of tags, meaning it is capable to generate a wide range of fake information.\n\nThese are currently available tags to generate different fake data:\n\n Tag | Details( data generated)\n------|--------\nbrand | brand \n character | character \n characters | characters \n characters_n | characters of maximum length n\n city | city \n color | color \n company | company \n continent | continent \n country | country \n credit_card_num | credit card number \n currency | currency \n currency_code | currency code \n day | day \n digits | digits \n digits_n | digits of maximum number n\n domain_name | domain name \n domain_zone | domain zone \n email_address | email address \n email_body | email body \n female_first_name | female first name \n female_full_name | female full name \n female_full_name_with_prefix | female full name with prefix \n female_full_name_with_suffix | female full name with suffix \n female_last_name | female last name \n female_last_name_pratronymic | female last name pratronymic \n first_name | first name \n full_name | full name \n full_name_with_prefix | full name with prefix \n full_name_with_suffix | full name with suffix \n gender | gender \n gender_abrev | gender abrev \n hex_color | hex color \n hex_color_short | hex color short \n i_pv_4 | i pv 4 \n industry | industry \n job_title | job title \n language | language \n last_name | last name \n latitude_degrees | latitude degrees \n latitude_direction | latitude direction \n latitude_minutes | latitude minutes \n latitude_seconds | latitude seconds \n latitude | latitude \n longitude | longitude \n longitude_degrees | longitude degrees \n longitude_direction | longitude direction \n longitude_minutes | longitude minutes \n longitude_seconds | longitude seconds \n male_first_name | male first name \n male_full_name_with_prefix | male full name with prefix \n male_full_name_with_suffix | male full name with suffix \n male_last_name | male last name \n male_pratronymic | male pratronymic \n model | model \n month | month \n month_num | month num \n month_short | month short \n paragraph | paragraph \n patagraphs | patagraphs \n patagraphs_n | patagraphs of maximum n\n password | password \n patronymic | patronymic \n phone | phone \n product | product \n product_name | product name \n sentence | sentence \n sentences | sentences \n sentences_n | sentences of maximum n\n simple_pass_word | simple pass word \n state | state \n state_abbrev | state abbrev \n street | street \n street_address | street address \n title | title \n top_level_domain | top level domain \n user_name | user name \n week_day | week day \n week_day_short | week day short \n week_day_num | week day num \n word | word \n words | words \n words_n | words of maximum n\n year | year \n zip | zip \n\n# Benchmark\nThis 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)\n\n```bash\n boom -n 1000 -c 100 http://localhost:3000/api/test\n```\n\nThe result\n```bash\n\nSummary:\n  Total:\t0.6442 secs.\n  Slowest:\t0.1451 secs.\n  Fastest:\t0.0163 secs.\n  Average:\t0.0586 secs.\n  Requests/sec:\t1552.3336\n  Total Data Received:\t39000 bytes.\n  Response Size per Request:\t39 bytes.\n\nStatus code distribution:\n  [200]\t1000 responses\n\nResponse time histogram:\n  0.016 [1]\t|\n  0.029 [121]\t|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.042 [166]\t|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.055 [192]\t|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.068 [192]\t|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.081 [168]\t|∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.094 [69]\t|∎∎∎∎∎∎∎∎∎∎∎∎∎∎\n  0.106 [41]\t|∎∎∎∎∎∎∎∎\n  0.119 [22]\t|∎∎∎∎\n  0.132 [21]\t|∎∎∎∎\n  0.145 [7]\t|∎\n\nLatency distribution:\n  10% in 0.0280 secs.\n  25% in 0.0364 secs.\n  50% in 0.0560 secs.\n  75% in 0.0751 secs.\n  90% in 0.0922 secs.\n  95% in 0.1066 secs.\n  99% in 0.1287 secs.\n```\n\n# Contributing\n\nStart 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.\n\nIf you have any questions, just open an issue.\n\n# Author\nGeofrey Ernest\n\nTwitter  : [@gernesti](https://twitter.com/gernesti)\n\n\n# Licence\n\nThis project is released under the MIT licence. See [LICENCE](LICENCE) for more details.\n"
  },
  {
    "path": "api.go",
    "content": "package apidemic\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/pmylund/go-cache\"\n)\n\n// Version is the version of apidemic. Apidemic uses semver.\nconst Version = \"0.4\"\n\nvar maxItemTime = cache.DefaultExpiration\n\nvar store = func() *cache.Cache {\n\tc := cache.New(5*time.Minute, 30*time.Second)\n\treturn c\n}()\n\nvar allowedHttpMethods = []string{\"OPTIONS\", \"GET\", \"POST\", \"PUT\", \"DELETE\", \"HEAD\"}\n\n// API is the struct for the json object that is passed to apidemic for registration.\ntype API struct {\n\tEndpoint                  string                 `json:\"endpoint\"`\n\tHTTPMethod                string                 `json:\"http_method\"`\n\tResponseCodeProbabilities map[int]int            `json:\"response_code_probabilities\"`\n\tPayload                   map[string]interface{} `json:\"payload\"`\n}\n\n// Home renders hopme page. It renders a json response with information about the service.\nfunc Home(w http.ResponseWriter, r *http.Request) {\n\tdetails := make(map[string]interface{})\n\tdetails[\"app_name\"] = \"ApiDemic\"\n\tdetails[\"version\"] = Version\n\tdetails[\"details\"] = \"Fake JSON API response\"\n\tRenderJSON(w, http.StatusOK, details)\n\treturn\n}\n\n// FindResponseCode helps imitating the backend responding with an error message occasionally\n// Example:\n//   {\"404\": 8, \"403\": 12, \"500\": 20, \"503\": 3}\n//   8% chance of getting 404\n//   12% chance of getting a 500 error\n//   3% chance of getting a 503 error\n//   77% chance of getting 200 OK or 201 Created depending on the HTTP method\nfunc FindResponseCode(responseCodeProbabilities map[int]int, method string) int {\n\tsum := 0\n\tr := rand.Intn(100)\n\n\tfor code, probability := range responseCodeProbabilities {\n\t\tif probability+sum > r {\n\t\t\treturn code\n\t\t}\n\t\tsum = sum + probability\n\t}\n\n\tif method == \"POST\" {\n\t\treturn http.StatusCreated\n\t}\n\n\treturn http.StatusOK\n}\n\n// RenderJSON helper for rendering JSON response, it marshals value into json and writes\n// it into w.\nfunc RenderJSON(w http.ResponseWriter, code int, value interface{}) {\n\tif code >= 400 || code == http.StatusNoContent {\n\t\thttp.Error(w, \"\", code)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(code)\n\terr := json.NewEncoder(w).Encode(value)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\n// RegisterEndpoint receives API objects and registers them. The payload from the request is\n// transformed into a self aware Value that is capable of faking its own attribute.\nfunc RegisterEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvar httpMethod string\n\ta := API{}\n\terr := json.NewDecoder(r.Body).Decode(&a)\n\tif err != nil {\n\t\tRenderJSON(w, http.StatusBadRequest, NewResponse(err.Error()))\n\t\treturn\n\t}\n\n\tif httpMethod, err = getAllowedMethod(a.HTTPMethod); err != nil {\n\t\tRenderJSON(w, http.StatusBadRequest, NewResponse(err.Error()))\n\t\treturn\n\t}\n\n\teKey, rcpKey := getCacheKeys(a.Endpoint, httpMethod)\n\tif _, ok := store.Get(eKey); ok {\n\t\tRenderJSON(w, http.StatusOK, NewResponse(\"endpoint already taken\"))\n\t\treturn\n\t}\n\tobj := NewObject()\n\terr = obj.Load(a.Payload)\n\tif err != nil {\n\t\tRenderJSON(w, http.StatusInternalServerError, NewResponse(err.Error()))\n\t\treturn\n\t}\n\tstore.Set(eKey, obj, maxItemTime)\n\tstore.Set(rcpKey, a.ResponseCodeProbabilities, maxItemTime)\n\tRenderJSON(w, http.StatusOK, NewResponse(\"cool\"))\n}\n\nfunc getCacheKeys(endpoint, httpMethod string) (string, string) {\n\teKey := fmt.Sprintf(\"%s-%v-e\", endpoint, httpMethod)\n\trcpKey := fmt.Sprintf(\"%s-%v-rcp\", endpoint, httpMethod)\n\n\treturn eKey, rcpKey\n}\n\nfunc getAllowedMethod(method string) (string, error) {\n\tif method == \"\" {\n\t\treturn \"GET\", nil\n\t}\n\n\tfor _, m := range allowedHttpMethods {\n\t\tif method == m {\n\t\t\treturn m, nil\n\t\t}\n\t}\n\n\treturn \"\", errors.New(\"HTTP method is not allowed\")\n}\n\n// DynamicEndpoint renders registered endpoints.\nfunc DynamicEndpoint(w http.ResponseWriter, r *http.Request) {\n\tvars := mux.Vars(r)\n\n\teKey, rcpKey := getCacheKeys(vars[\"endpoint\"], r.Method)\n\tif eVal, ok := store.Get(eKey); ok {\n\t\tif rcpVal, ok := store.Get(rcpKey); ok {\n\t\t\tcode := FindResponseCode(rcpVal.(map[int]int), r.Method)\n\t\t\tRenderJSON(w, code, eVal)\n\t\t\treturn\n\t\t}\n\t}\n\tresponseText := fmt.Sprintf(\"apidemic: %s has no %s endpoint\", vars[\"endpoint\"], r.Method)\n\tRenderJSON(w, http.StatusNotFound, NewResponse(responseText))\n}\n\n// NewResponse helper for response JSON message\nfunc NewResponse(message string) interface{} {\n\treturn struct {\n\t\tText string `json:\"text\"`\n\t}{\n\t\tmessage,\n\t}\n}\n\n// NewServer returns a new apidemic server\nfunc NewServer() *mux.Router {\n\tm := mux.NewRouter()\n\tm.HandleFunc(\"/\", Home)\n\tm.HandleFunc(\"/register\", RegisterEndpoint).Methods(\"POST\")\n\tm.HandleFunc(\"/api/{endpoint}\", DynamicEndpoint).Methods(\"OPTIONS\", \"GET\", \"POST\", \"PUT\", \"DELETE\", \"HEAD\")\n\treturn m\n}\n"
  },
  {
    "path": "api_test.go",
    "content": "package apidemic\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/pmylund/go-cache\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDynamicEndpointFailsWithoutRegistration(t *testing.T) {\n\ts := setUp()\n\tpayload := registerPayload(t, \"fixtures/sample_request.json\")\n\n\tw := httptest.NewRecorder()\n\treq := jsonRequest(\"POST\", \"/api/test\", payload)\n\ts.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusNotFound, w.Code)\n}\n\nfunc TestDynamicEndpointWithGetRequest(t *testing.T) {\n\ts := setUp()\n\tpayload := registerPayload(t, \"fixtures/sample_request.json\")\n\n\tw := httptest.NewRecorder()\n\treq := jsonRequest(\"POST\", \"/register\", payload)\n\ts.ServeHTTP(w, req)\n\trequire.Equal(t, http.StatusOK, w.Code)\n\n\tw = httptest.NewRecorder()\n\treq = jsonRequest(\"GET\", \"/api/test\", nil)\n\ts.ServeHTTP(w, req)\n\n\tassert.Equal(t, http.StatusOK, w.Code)\n}\n\nfunc TestDynamicEndpointWithPostRequest(t *testing.T) {\n\ts := setUp()\n\tpayload := registerPayload(t, \"fixtures/sample_request.json\")\n\tpayload[\"http_method\"] = \"POST\"\n\n\tw := httptest.NewRecorder()\n\treq := jsonRequest(\"POST\", \"/register\", payload)\n\ts.ServeHTTP(w, req)\n\trequire.Equal(t, http.StatusOK, w.Code)\n\n\tw = httptest.NewRecorder()\n\treq = jsonRequest(\"POST\", \"/api/test\", nil)\n\n\ts.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusCreated, w.Code)\n}\n\nfunc TestDynamicEndpointWithForbiddenResponse(t *testing.T) {\n\ts := setUp()\n\tregisterPayload := registerPayload(t, \"fixtures/sample_request.json\")\n\tregisterPayload[\"response_code_probabilities\"] = map[string]int{\"403\": 100}\n\n\tw := httptest.NewRecorder()\n\treq := jsonRequest(\"POST\", \"/register\", registerPayload)\n\ts.ServeHTTP(w, req)\n\trequire.Equal(t, http.StatusOK, w.Code)\n\n\tw = httptest.NewRecorder()\n\treq = jsonRequest(\"GET\", \"/api/test\", nil)\n\n\ts.ServeHTTP(w, req)\n\tassert.Equal(t, http.StatusForbidden, w.Code)\n}\n\nfunc setUp() *mux.Router {\n\tstore = cache.New(5*time.Minute, 30*time.Second)\n\n\treturn NewServer()\n}\n\nfunc registerPayload(t *testing.T, fixtureFile string) map[string]interface{} {\n\tcontent, err := ioutil.ReadFile(fixtureFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar api map[string]interface{}\n\terr = json.NewDecoder(bytes.NewReader(content)).Decode(&api)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn api\n}\n\nfunc jsonRequest(method string, path string, body interface{}) *http.Request {\n\tvar bEnd io.Reader\n\tif body != nil {\n\t\tb, err := json.Marshal(body)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tbEnd = bytes.NewReader(b)\n\t}\n\treq, err := http.NewRequest(method, path, bEnd)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treturn req\n}\n"
  },
  {
    "path": "cmd/apidemic/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/codegangsta/cli\"\n\t\"github.com/gernest/apidemic\"\n)\n\nfunc server(ctx *cli.Context) {\n\tport := ctx.Int(\"port\")\n\ts := apidemic.NewServer()\n\n\tlog.Println(\"starting server on port :\", port)\n\tlog.Fatal(http.ListenAndServe(fmt.Sprintf(\":%d\", port), s))\n}\n\nfunc main() {\n\tapp := cli.NewApp()\n\tapp.Name = \"apidemic\"\n\tapp.Usage = \"Fake JSON API Responses\"\n\tapp.Authors = []cli.Author{\n\t\t{\"Geofrey Ernest\", \"geofreyernest@live.com\"},\n\t}\n\tapp.Version = apidemic.Version\n\tapp.Commands = []cli.Command{\n\t\tcli.Command{\n\t\t\tName:      \"start\",\n\t\t\tShortName: \"s\",\n\t\t\tUsage:     \"starts apidemic server\",\n\t\t\tAction:    server,\n\t\t\tFlags: []cli.Flag{\n\t\t\t\tcli.IntFlag{\n\t\t\t\t\tName:   \"port\",\n\t\t\t\t\tUsage:  \"HTTP port to run\",\n\t\t\t\t\tValue:  3000,\n\t\t\t\t\tEnvVar: \"PORT\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tapp.RunAndExitOnError()\n}\n"
  },
  {
    "path": "fixtures/response.json",
    "content": "{\n  \"name\": \"anton\",\n  \"age\": 29,\n  \"nothing\": null,\n  \"true\": true,\n  \"false\": false,\n  \"list\": [\n      \"first\",\n      \"second\"\n    ],\n  \"list2\": [\n    {\n      \"street\": \"Street 42\",\n      \"city\": \"Stockholm\"\n      },\n    {\n      \"street\": \"Street 42\",\n      \"city\": \"Stockholm\"\n      }\n    ],\n  \"address\": {\n    \"street\": \"Street 42\",\n    \"city\": \"Stockholm\"\n  },\n  \"country\": {\n    \"name\": \"Sweden\"\n  }\n}\n"
  },
  {
    "path": "fixtures/sample.json",
    "content": "{\n    \"name: first_name\": \"anton\",\n    \"age: digits_n,max=2\": 29,\n    \"nothing:\": null,\n    \"true\": true,\n    \"false\": false,\n    \"list:word,max=3\": [\n      \"first\",\n      \"second\"\n    ],\n    \"list2\": [\n      {\n        \"street:street\": \"Street 42\",\n        \"city:city\": \"Stockholm\"\n      },\n      {\n        \"street\": \"Street 42\",\n        \"city\": \"Stockholm\"\n      }\n    ],\n    \"address\": {\n      \"street:street\": \"Street 42\",\n      \"city:city\": \"Stockholm\"\n    },\n    \"country\": {\n      \"name:country\": \"Sweden\"\n    }\n  }"
  },
  {
    "path": "fixtures/sample_request.json",
    "content": "{\n  \"endpoint\": \"test\",\n  \"payload\": {\n    \"name: first_name\": \"anton\",\n    \"age: digits_n,max=2\": 29,\n    \"nothing:\": null,\n    \"true\": true,\n    \"false\": false,\n    \"list:word,max=3\": [\n      \"first\",\n      \"second\"\n    ],\n    \"list2\": [\n      {\n        \"street:street\": \"Street 42\",\n        \"city:city\": \"Stockholm\"\n      },\n      {\n        \"street\": \"Street 42\",\n        \"city\": \"Stockholm\"\n      }\n    ],\n    \"address\": {\n      \"street:street\": \"Street 42\",\n      \"city:city\": \"Stockholm\"\n    },\n    \"country\": {\n      \"name:country\": \"Sweden\"\n    }\n  }\n}\n"
  },
  {
    "path": "json.go",
    "content": "package apidemic\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/icrowley/fake\"\n)\n\ntype Value struct {\n\tTags Tags\n\tData interface{}\n}\n\nfunc (v Value) Update() Value {\n\tswitch v.Data.(type) {\n\tcase string:\n\t\treturn fakeString(&v)\n\tcase float64:\n\t\treturn fakeFloats(&v)\n\tcase []interface{}:\n\t\treturn fakeArray(&v)\n\tcase map[string]interface{}:\n\t\treturn fakeObject(&v)\n\t}\n\treturn v\n}\n\nfunc (v Value) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(v.Update().Data)\n}\n\nfunc NewValue(val interface{}) Value {\n\treturn Value{Tags: make(Tags), Data: val}\n}\n\ntype Object struct {\n\tData map[string]Value\n}\n\nfunc NewObject() *Object {\n\treturn &Object{Data: make(map[string]Value)}\n}\n\nfunc (o *Object) Load(src map[string]interface{}) error {\n\tfor key, val := range src {\n\t\tvalue := NewValue(val)\n\t\tsections := strings.Split(key, \":\")\n\t\tif len(sections) == 2 {\n\t\t\tkey = sections[0]\n\t\t\tvalue.Tags.Load(sections[1])\n\t\t}\n\t\to.Set(key, value)\n\t}\n\treturn nil\n}\n\nfunc (o *Object) Set(key string, val Value) {\n\to.Data[key] = val\n}\n\nfunc (v *Object) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(v.Data)\n}\n\nfunc parseJSONData(src io.Reader) (*Object, error) {\n\tvar in map[string]interface{}\n\terr := json.NewDecoder(src).Decode(&in)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\to := NewObject()\n\terr = o.Load(in)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn o, nil\n}\n\nfunc fakeString(v *Value) Value {\n\treturn Value{Data: genFakeData(v)}\n}\n\nfunc fakeArray(v *Value) Value {\n\tarrV, ok := v.Data.([]interface{})\n\tif !ok {\n\t\treturn *v\n\t}\n\tnv := *v\n\tvar rst []interface{}\n\tif len(arrV) > 0 {\n\t\torigin := arrV[0]\n\t\tn := len(arrV)\n\n\t\tif max, ok := v.Tags.Get(\"max\"); ok {\n\t\t\tmaxV, err := strconv.Atoi(max)\n\t\t\tif err != nil {\n\t\t\t\treturn *v\n\t\t\t}\n\t\t\tn = maxV\n\t\t}\n\t\tfor i := 0; i < n; i++ {\n\t\t\tnewVal := NewValue(origin)\n\t\t\trst = append(rst, newVal)\n\t\t}\n\t}\n\tnv.Data = rst\n\treturn nv\n}\n\nfunc fakeFloats(v *Value) Value {\n\treturn Value{Data: genFakeData(v)}\n}\n\nfunc fakeObject(v *Value) Value {\n\tobj := NewObject()\n\tobj.Load(v.Data.(map[string]interface{}))\n\treturn NewValue(obj.Data)\n}\n\nfunc genFakeData(v *Value) interface{} {\n\tif len(v.Tags) == 0 {\n\t\treturn v.Data\n\t}\n\n\ttyp, ok := v.Tags.Get(\"type\")\n\tif !ok {\n\t\treturn v.Data\n\t}\n\tswitch typ {\n\tcase fieldTags.Brand:\n\t\treturn fake.Brand()\n\tcase fieldTags.Character:\n\t\treturn fake.Character()\n\tcase fieldTags.Characters:\n\t\treturn fieldTags.Characters\n\tcase fieldTags.CharactersN:\n\t\tmax := 5\n\t\tif m, err := v.Tags.Int(\"max\"); err == nil {\n\t\t\tmax = m\n\t\t}\n\t\treturn fake.CharactersN(max)\n\tcase fieldTags.City:\n\t\treturn fake.City()\n\tcase fieldTags.Color:\n\t\treturn fake.Color()\n\tcase fieldTags.Company:\n\t\treturn fake.Company()\n\tcase fieldTags.Continent:\n\t\treturn fake.Continent()\n\tcase fieldTags.Country:\n\t\treturn fake.Country()\n\tcase fieldTags.CreditCardNum:\n\t\tvendor, _ := v.Tags.Get(\"vendor\")\n\t\tfake.CreditCardNum(vendor)\n\tcase fieldTags.Currency:\n\t\tfake.Currency()\n\tcase fieldTags.CurrencyCode:\n\t\tfake.CurrencyCode()\n\tcase fieldTags.Day:\n\t\treturn fake.Day()\n\tcase fieldTags.Digits:\n\t\treturn fake.Digits()\n\tcase fieldTags.DigitsN:\n\t\tmax := 5\n\t\tif m, err := v.Tags.Int(\"max\"); err == nil {\n\t\t\tmax = m\n\t\t}\n\t\treturn fake.DigitsN(max)\n\tcase fieldTags.DomainName:\n\t\treturn fake.DomainName()\n\tcase fieldTags.DomainZone:\n\t\treturn fake.DomainZone()\n\tcase fieldTags.EmailAddress:\n\t\treturn fake.EmailAddress()\n\tcase fieldTags.EmailBody:\n\t\treturn fake.EmailBody()\n\tcase fieldTags.FemaleFirstName:\n\t\treturn fake.FemaleFirstName()\n\tcase fieldTags.FemaleFullName:\n\t\treturn fake.FemaleFullName()\n\tcase fieldTags.FemaleFullNameWithPrefix:\n\t\treturn fake.FemaleFullNameWithPrefix()\n\tcase fieldTags.FemaleFullNameWithSuffix:\n\t\treturn fake.FemaleFullNameWithSuffix()\n\tcase fieldTags.FemaleLastName:\n\t\treturn fake.FemaleLastName()\n\tcase fieldTags.FemaleLastNamePratronymic:\n\t\treturn fake.FemalePatronymic()\n\tcase fieldTags.FirstName:\n\t\treturn fake.FirstName()\n\tcase fieldTags.FullName:\n\t\treturn fake.FullName()\n\tcase fieldTags.FullNameWithPrefix:\n\t\treturn fake.FullNameWithPrefix()\n\tcase fieldTags.FullNameWithSuffix:\n\t\treturn fake.FullNameWithSuffix()\n\tcase fieldTags.Gender:\n\t\treturn fake.Gender()\n\tcase fieldTags.GenderAbrev:\n\t\treturn fake.GenderAbbrev()\n\tcase fieldTags.HexColor:\n\t\treturn fake.HexColor()\n\tcase fieldTags.HexColorShort:\n\t\treturn fake.HexColorShort()\n\tcase fieldTags.IPv4:\n\t\treturn fake.IPv4()\n\tcase fieldTags.Industry:\n\t\treturn fake.Industry()\n\tcase fieldTags.JobTitle:\n\t\treturn fake.JobTitle()\n\tcase fieldTags.Language:\n\t\treturn fake.Language()\n\tcase fieldTags.LastName:\n\t\treturn fake.LastName()\n\tcase fieldTags.LatitudeDegrees:\n\t\treturn fake.LatitudeDegrees()\n\tcase fieldTags.LatitudeDirection:\n\t\treturn fake.LatitudeDirection()\n\tcase fieldTags.LatitudeMinutes:\n\t\treturn fake.LatitudeMinutes()\n\tcase fieldTags.LatitudeSeconds:\n\t\treturn fake.LatitudeSeconds()\n\tcase fieldTags.Latitude:\n\t\treturn fake.Latitude()\n\tcase fieldTags.LongitudeDegrees:\n\t\treturn fake.LongitudeDegrees()\n\tcase fieldTags.LongitudeDirection:\n\t\treturn fake.LongitudeDirection()\n\tcase fieldTags.LongitudeMinutes:\n\t\treturn fake.LongitudeMinutes()\n\tcase fieldTags.LongitudeSeconds:\n\t\treturn fake.LongitudeSeconds()\n\tcase fieldTags.MaleFirstName:\n\t\treturn fake.MaleFirstName()\n\tcase fieldTags.MaleFullNameWithPrefix:\n\t\treturn fake.MaleFullNameWithPrefix()\n\tcase fieldTags.MaleFullNameWithSuffix:\n\t\treturn fake.MaleFullNameWithSuffix()\n\tcase fieldTags.MaleLastName:\n\t\treturn fake.MaleLastName()\n\tcase fieldTags.MalePratronymic:\n\t\treturn fake.MalePatronymic()\n\tcase fieldTags.Model:\n\t\treturn fake.Model()\n\tcase fieldTags.Month:\n\t\treturn fake.Month()\n\tcase fieldTags.MonthNum:\n\t\treturn fake.MonthNum()\n\tcase fieldTags.MonthShort:\n\t\treturn fake.MonthShort()\n\tcase fieldTags.Paragraph:\n\t\treturn fake.Paragraph()\n\tcase fieldTags.Patagraphs:\n\t\treturn fake.Paragraphs()\n\tcase fieldTags.PatagraphsN:\n\t\tmax := 5\n\t\tif m, err := v.Tags.Int(\"max\"); err == nil {\n\t\t\tmax = m\n\t\t}\n\t\treturn fake.ParagraphsN(max)\n\tcase fieldTags.Password:\n\t\tvar (\n\t\t\tatLeast                                = 5\n\t\t\tatMost                                 = 8\n\t\t\tallowUpper, allowNumeric, allowSpecial = true, true, true\n\t\t)\n\t\tif least, err := v.Tags.Int(\"at_least\"); err == nil {\n\t\t\tatLeast = least\n\t\t}\n\t\tif most, err := v.Tags.Int(\"at_most\"); err == nil {\n\t\t\tatMost = most\n\t\t}\n\t\tif upper, err := v.Tags.Bool(\"upper\"); err == nil {\n\t\t\tallowUpper = upper\n\t\t}\n\t\tif numeric, err := v.Tags.Bool(\"numeric\"); err == nil {\n\t\t\tallowNumeric = numeric\n\t\t}\n\t\tif special, err := v.Tags.Bool(\"special\"); err == nil {\n\t\t\tallowSpecial = special\n\t\t}\n\t\treturn fake.Password(atLeast, atMost, allowUpper, allowNumeric, allowSpecial)\n\tcase fieldTags.Patronymic:\n\t\treturn fake.Patronymic()\n\tcase fieldTags.Phone:\n\t\treturn fake.Phone()\n\tcase fieldTags.Product:\n\t\treturn fake.Product()\n\tcase fieldTags.ProductName:\n\t\treturn fake.ProductName()\n\tcase fieldTags.Sentence:\n\t\treturn fake.Sentence()\n\tcase fieldTags.Sentences:\n\t\treturn fake.Sentence()\n\tcase fieldTags.SentencesN:\n\t\tmax := 5\n\t\tif m, err := v.Tags.Int(\"max\"); err == nil {\n\t\t\tmax = m\n\t\t}\n\t\treturn fake.SentencesN(max)\n\tcase fieldTags.SimplePassWord:\n\t\treturn fake.SimplePassword()\n\tcase fieldTags.State:\n\t\treturn fake.State()\n\tcase fieldTags.StateAbbrev:\n\t\treturn fake.StateAbbrev()\n\tcase fieldTags.Street:\n\t\treturn fake.Street()\n\tcase fieldTags.StreetAddress:\n\t\treturn fake.StreetAddress()\n\tcase fieldTags.Title:\n\t\treturn fake.Title()\n\tcase fieldTags.TopLevelDomain:\n\t\treturn fake.TopLevelDomain()\n\tcase fieldTags.UserName:\n\t\treturn fake.UserName()\n\tcase fieldTags.WeekDay:\n\t\treturn fake.WeekDay()\n\tcase fieldTags.WeekDayNum:\n\t\treturn fake.WeekdayNum()\n\tcase fieldTags.WeekDayShort:\n\t\treturn fake.WeekDayShort()\n\tcase fieldTags.Word:\n\t\treturn fake.Word()\n\tcase fieldTags.Words:\n\t\treturn fake.Words()\n\tcase fieldTags.WordsN:\n\t\tmax := 5\n\t\tif m, err := v.Tags.Int(\"max\"); err == nil {\n\t\t\tmax = m\n\t\t}\n\t\treturn fake.WordsN(max)\n\tcase fieldTags.Year:\n\t//return fake.Year()\n\tcase fieldTags.Zip:\n\t\treturn fake.Zip()\n\t}\n\n\treturn v.Data\n}\n"
  },
  {
    "path": "json_test.go",
    "content": "package apidemic\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"testing\"\n)\n\nfunc TestParseJSONData(t *testing.T) {\n\tdata, err := ioutil.ReadFile(\"fixtures/sample.json\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tob, err := parseJSONData(bytes.NewReader(data))\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t_, err = json.MarshalIndent(ob, \"\", \"\\t\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n"
  },
  {
    "path": "tags.go",
    "content": "package apidemic\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar ErrTagNotFound = errors.New(\"apidemic: Tag not found\")\n\nvar fieldTags = struct {\n\tBrand                     string\n\tCharacter                 string\n\tCharacters                string\n\tCharactersN               string\n\tCity                      string\n\tColor                     string\n\tCompany                   string\n\tContinent                 string\n\tCountry                   string\n\tCreditCardNum             string\n\tCurrency                  string\n\tCurrencyCode              string\n\tDay                       string\n\tDigits                    string\n\tDigitsN                   string\n\tDomainName                string\n\tDomainZone                string\n\tEmailAddress              string\n\tEmailBody                 string\n\tFemaleFirstName           string\n\tFemaleFullName            string\n\tFemaleFullNameWithPrefix  string\n\tFemaleFullNameWithSuffix  string\n\tFemaleLastName            string\n\tFemaleLastNamePratronymic string\n\tFirstName                 string\n\tFullName                  string\n\tFullNameWithPrefix        string\n\tFullNameWithSuffix        string\n\tGender                    string\n\tGenderAbrev               string\n\tHexColor                  string\n\tHexColorShort             string\n\tIPv4                      string\n\tIndustry                  string\n\tJobTitle                  string\n\tLanguage                  string\n\tLastName                  string\n\tLatitudeDegrees           string\n\tLatitudeDirection         string\n\tLatitudeMinutes           string\n\tLatitudeSeconds           string\n\tLatitude                  string\n\tLongitude                 string\n\tLongitudeDegrees          string\n\tLongitudeDirection        string\n\tLongitudeMinutes          string\n\tLongitudeSeconds          string\n\tMaleFirstName             string\n\tMaleFullNameWithPrefix    string\n\tMaleFullNameWithSuffix    string\n\tMaleLastName              string\n\tMalePratronymic           string\n\tModel                     string\n\tMonth                     string\n\tMonthNum                  string\n\tMonthShort                string\n\tParagraph                 string\n\tPatagraphs                string\n\tPatagraphsN               string\n\tPassword                  string\n\tPatronymic                string\n\tPhone                     string\n\tProduct                   string\n\tProductName               string\n\tSentence                  string\n\tSentences                 string\n\tSentencesN                string\n\tSimplePassWord            string\n\tState                     string\n\tStateAbbrev               string\n\tStreet                    string\n\tStreetAddress             string\n\tTitle                     string\n\tTopLevelDomain            string\n\tUserName                  string\n\tWeekDay                   string\n\tWeekDayShort              string\n\tWeekDayNum                string\n\tWord                      string\n\tWords                     string\n\tWordsN                    string\n\tYear                      string\n\tZip                       string\n}{\n\t\"brand\", \"character\", \"characters\", \"characters_n\",\n\t\"city\", \"color\", \"company\", \"continent\", \"country\",\n\t\"credit_card_num\", \"currency\", \"currency_code\", \"day\",\n\t\"digits\", \"digits_n\", \"domain_name\", \"domain_zone\",\n\t\"email_address\", \"email_body\", \"female_first_name\",\n\t\"female_full_name\", \"female_full_name_with_prefix\",\n\t\"female_full_name_with_suffix\", \"female_last_name\",\n\t\"female_last_name_pratronymic\", \"first_name\", \"full_name\",\n\t\"full_name_with_prefix\", \"full_name_with_suffix\", \"gender\",\n\t\"gender_abrev\", \"hex_color\", \"hex_color_short\", \"i_pv_4\",\n\t\"industry\", \"job_title\", \"language\", \"last_name\",\n\t\"latitude_degrees\", \"latitude_direction\", \"latitude_minutes\",\n\t\"latitude_seconds\", \"latitude\", \"longitude\", \"longitude_degrees\",\n\t\"longitude_direction\", \"longitude_minutes\", \"longitude_seconds\",\n\t\"male_first_name\", \"male_full_name_with_prefix\", \"male_full_name_with_suffix\",\n\t\"male_last_name\", \"male_pratronymic\", \"model\", \"month\",\n\t\"month_num\", \"month_short\", \"paragraph\", \"patagraphs\", \"patagraphs_n\",\n\t\"password\", \"patronymic\", \"phone\", \"product\", \"product_name\", \"sentence\",\n\t\"sentences\", \"sentences_n\", \"simple_pass_word\", \"state\", \"state_abbrev\",\n\t\"street\", \"street_address\", \"title\", \"top_level_domain\", \"user_name\", \"week_day\",\n\t\"week_day_short\", \"week_day_num\", \"word\", \"words\", \"words_n\", \"year\", \"zip\",\n}\n\n//Tags stores metadata about values\ntype Tags map[string]string\n\n// Load parses src and extacts tags from it. The src is a string with comma separated content.\n// \tExample \"character_n,max=30\"\n//\n// The first tag, is the value type information, the rest is extra information to fine tune\n// the generated fake value.\n//\n// For instance in the example above, the value is characters, where max=30 limits the number of characters\n// to the maximum size of 30.\nfunc (t Tags) Load(src string) {\n\tss := strings.Split(src, \",\")\n\tfirst := strings.TrimSpace(ss[0])\n\tif len(ss) > 0 {\n\t\tt[\"type\"] = first\n\t\trest := ss[1:]\n\t\tfor _, v := range rest {\n\t\t\tts := strings.Split(v, \"=\")\n\t\t\tif len(ts) < 2 {\n\t\t\t\tt[v] = \"\"\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt[strings.TrimSpace(ts[0])] = strings.TrimSpace(ts[1])\n\t\t}\n\t}\n\n}\n\n// Get returns the value for tag key.\nfunc (t Tags) Get(key string) (string, bool) {\n\tk, ok := t[key]\n\treturn k, ok\n}\n\n// Int returns an in value for tag key.\nfunc (t Tags) Int(key string) (int, error) {\n\ttag, ok := t.Get(key)\n\tif !ok {\n\t\treturn 0, ErrTagNotFound\n\t}\n\treturn strconv.Atoi(tag)\n}\n\n// Bool returns a boolean value for tag key\nfunc (t Tags) Bool(key string) (bool, error) {\n\ttag, ok := t.Get(key)\n\tif !ok {\n\t\treturn false, ErrTagNotFound\n\t}\n\treturn strconv.ParseBool(tag)\n}\n"
  },
  {
    "path": "tags_test.go",
    "content": "package apidemic\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTags(t *testing.T) {\n\tsrc := \"characters_n,max=30\"\n\tsample := []struct {\n\t\ttag, value string\n\t}{\n\t\t{\"type\", \"characters_n\"},\n\t\t{\"max\", \"30\"},\n\t}\n\n\ttags := make(Tags)\n\ttags.Load(src)\n\tfor _, v := range sample {\n\t\tk, ok := tags.Get(v.tag)\n\t\tif !ok {\n\t\t\tt.Errorf(\"expected %s to exist %#v\", v.tag, tags)\n\t\t}\n\t\tif k != v.value {\n\t\t\tt.Errorf(\"expected %s got %s\", v.value, k)\n\t\t}\n\t}\n\n\tmax, err := tags.Int(\"max\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif max != 30 {\n\t\tt.Errorf(\"expected %d got %d\", 30, max)\n\t}\n}\n"
  }
]