Full Code of gernest/apidemic for AI

master ba1110a927d7 cached
15 files
34.3 KB
9.8k tokens
42 symbols
1 requests
Download .txt
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 <geofreyernest@live.com>

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)
	}
}
Download .txt
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
Download .txt
SYMBOL INDEX (42 symbols across 7 files)

FILE: api.go
  constant Version (line 16) | Version = "0.4"
  type API (line 28) | type API struct
  function Home (line 36) | func Home(w http.ResponseWriter, r *http.Request) {
  function FindResponseCode (line 52) | func FindResponseCode(responseCodeProbabilities map[int]int, method stri...
  function RenderJSON (line 72) | func RenderJSON(w http.ResponseWriter, code int, value interface{}) {
  function RegisterEndpoint (line 89) | func RegisterEndpoint(w http.ResponseWriter, r *http.Request) {
  function getCacheKeys (line 119) | func getCacheKeys(endpoint, httpMethod string) (string, string) {
  function getAllowedMethod (line 126) | func getAllowedMethod(method string) (string, error) {
  function DynamicEndpoint (line 141) | func DynamicEndpoint(w http.ResponseWriter, r *http.Request) {
  function NewResponse (line 157) | func NewResponse(message string) interface{} {
  function NewServer (line 166) | func NewServer() *mux.Router {

FILE: api_test.go
  function TestDynamicEndpointFailsWithoutRegistration (line 19) | func TestDynamicEndpointFailsWithoutRegistration(t *testing.T) {
  function TestDynamicEndpointWithGetRequest (line 30) | func TestDynamicEndpointWithGetRequest(t *testing.T) {
  function TestDynamicEndpointWithPostRequest (line 46) | func TestDynamicEndpointWithPostRequest(t *testing.T) {
  function TestDynamicEndpointWithForbiddenResponse (line 63) | func TestDynamicEndpointWithForbiddenResponse(t *testing.T) {
  function setUp (line 80) | func setUp() *mux.Router {
  function registerPayload (line 86) | func registerPayload(t *testing.T, fixtureFile string) map[string]interf...
  function jsonRequest (line 101) | func jsonRequest(method string, path string, body interface{}) *http.Req...

FILE: cmd/apidemic/main.go
  function server (line 12) | func server(ctx *cli.Context) {
  function main (line 20) | func main() {

FILE: json.go
  type Value (line 12) | type Value struct
    method Update (line 17) | func (v Value) Update() Value {
    method MarshalJSON (line 31) | func (v Value) MarshalJSON() ([]byte, error) {
  function NewValue (line 35) | func NewValue(val interface{}) Value {
  type Object (line 39) | type Object struct
    method Load (line 47) | func (o *Object) Load(src map[string]interface{}) error {
    method Set (line 60) | func (o *Object) Set(key string, val Value) {
    method MarshalJSON (line 64) | func (v *Object) MarshalJSON() ([]byte, error) {
  function NewObject (line 43) | func NewObject() *Object {
  function parseJSONData (line 68) | func parseJSONData(src io.Reader) (*Object, error) {
  function fakeString (line 82) | func fakeString(v *Value) Value {
  function fakeArray (line 86) | func fakeArray(v *Value) Value {
  function fakeFloats (line 113) | func fakeFloats(v *Value) Value {
  function fakeObject (line 117) | func fakeObject(v *Value) Value {
  function genFakeData (line 123) | func genFakeData(v *Value) interface{} {

FILE: json_test.go
  function TestParseJSONData (line 10) | func TestParseJSONData(t *testing.T) {

FILE: tags.go
  type Tags (line 121) | type Tags
    method Load (line 131) | func (t Tags) Load(src string) {
    method Get (line 150) | func (t Tags) Get(key string) (string, bool) {
    method Int (line 156) | func (t Tags) Int(key string) (int, error) {
    method Bool (line 165) | func (t Tags) Bool(key string) (bool, error) {

FILE: tags_test.go
  function TestTags (line 7) | func TestTags(t *testing.T) {
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (39K chars).
[
  {
    "path": ".gitignore",
    "chars": 4,
    "preview": "bin/"
  },
  {
    "path": ".travis.yml",
    "chars": 90,
    "preview": "language: go\ngo:\n    - 1.9\n    - \"1.10\"\ninstall:\n  - go get -t ./...\nscript:\n  - make test"
  },
  {
    "path": "LICENCE",
    "chars": 1083,
    "preview": "Copyright (c) 2015 Geofrey Ernest <geofreyernest@live.com>\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "Makefile",
    "chars": 153,
    "preview": "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}}_{"
  },
  {
    "path": "README.md",
    "chars": 9438,
    "preview": "# apidemic [![Build Status](https://travis-ci.org/gernest/apidemic.svg)](https://travis-ci.org/gernest/apidemic)\n\nApidem"
  },
  {
    "path": "api.go",
    "chars": 4819,
    "preview": "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\""
  },
  {
    "path": "api_test.go",
    "chars": 2709,
    "preview": "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\"ti"
  },
  {
    "path": "cmd/apidemic/main.go",
    "chars": 851,
    "preview": "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 s"
  },
  {
    "path": "fixtures/response.json",
    "chars": 408,
    "preview": "{\n  \"name\": \"anton\",\n  \"age\": 29,\n  \"nothing\": null,\n  \"true\": true,\n  \"false\": false,\n  \"list\": [\n      \"first\",\n      "
  },
  {
    "path": "fixtures/sample.json",
    "chars": 521,
    "preview": "{\n    \"name: first_name\": \"anton\",\n    \"age: digits_n,max=2\": 29,\n    \"nothing:\": null,\n    \"true\": true,\n    \"false\": f"
  },
  {
    "path": "fixtures/sample_request.json",
    "chars": 561,
    "preview": "{\n  \"endpoint\": \"test\",\n  \"payload\": {\n    \"name: first_name\": \"anton\",\n    \"age: digits_n,max=2\": 29,\n    \"nothing:\": n"
  },
  {
    "path": "json.go",
    "chars": 7937,
    "preview": "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 struc"
  },
  {
    "path": "json_test.go",
    "chars": 373,
    "preview": "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\t"
  },
  {
    "path": "tags.go",
    "chars": 5607,
    "preview": "package apidemic\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar ErrTagNotFound = errors.New(\"apidemic: Tag not found\")"
  },
  {
    "path": "tags_test.go",
    "chars": 556,
    "preview": "package apidemic\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTags(t *testing.T) {\n\tsrc := \"characters_n,max=30\"\n\tsample := []struct"
  }
]

About this extraction

This page contains the full source code of the gernest/apidemic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (34.3 KB), approximately 9.8k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!