apifaker

package module
v0.0.0-...-2777a15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2016 License: MIT Imports: 18 Imported by: 0

README

apifaker

apifaker can help you start a json api server in a fast way.

If you would like to start a simple json api server for testing front-end, apifaker could be a great fit.

No need of database, just create some json files, and write two line codes, then, everything is done, you can start implementing the happy(hope so) front-end features.

Example project

Install

go get github.com/Focinfi/apifaker

Usage


Add a directory

apifaker need a directory for containing the api json files.

Add api files

Rules:

  1. "resource_name" string(required), resource name for this api routes, you can treat it as table name in a database. apifaker assumes that resource name is plural.

  2. "has_many" array(optional), every element must be a string of one of the other "resource_name", if a resource's "has_many" is not empty:

    1. The response of GET /collention/:id and GET /collention will be insert the related resources.
    2. The DELETE /collention/:id will also delete the related resources.
  3. "has_one" array(optional), its rules are same as of the "has_many" except every element must be singular and the response of GET /collention/:id and GET /collention will be only insert the a first-found item.

  4. "columns" array(required), columuns for resource, only support "id" "name", "type", "regexp_pattern", "unique"

    1. "id" must be a "number" as the first cloumn.
    2. Every colmun must have at lest a "name" and a "type".
    3. "type" supports: "boolean" "number" "string" "array" "object", these types will be used to check every item data.
    4. "regexp_pattern" add regular expression for validating your string-type column, using internal regexp package, you could run go doc regexp/syntax to learn all syntax.
    5. "unique": set true(default false) to specify this column should be unique.
  5. "seed" array(optional), initial data for this resource, note that every lineitem of seeds should have columns descriped in "columns" array, otherwise, it will throw an non-nil error.

Here is an example for users.json

{
    "resource_name": "users",
    "has_many": [
        "books"
    ],
    "columns": [
        {
            "name": "id",
            "type": "number"
        },
        {
            "name": "name",
            "type": "string",
            "regexp_pattern": "[A-z]|[0-9]",
            "unique": true
        },
        {
            "name": "phone",
            "type": "string",
            "regexp_pattern": "^132",
            "unique": true
        },
        {
            "name": "age",
            "type": "number"
        }
    ],
    "seeds": [
        {
            "id": 1,
            "name": "Frank",
            "phone": "13213213213",
            "age": 22
        },
        {
            "id": 2,
            "name": "Antony",
            "phone": "13213213211",
            "age": 22
        },
        {
            "id": 3,
            "name": "Foci",
            "phone": "13213213212",
            "age": 22
        }
    ]
}

And books.json

{
    "resource_name": "books",
    "columns": [
        {
            "name": "id",
            "type": "number"
        },
        {
            "name": "title",
            "type": "string",
            "regexp_pattern": "[A-z]|[0-9]",
            "unique": true
        },
        {
            "name": "user_id",
            "type": "number"
        }
        
    ],
    "seeds": [
        {
            "id": 1,
            "title": "The Little Prince",
            "user_id": 1
        },
        {
            "id": 2,
            "title": "Life of Pi",
            "user_id": 2
        },
        {
            "id": 3,
            "title": "The Alchemist",
            "user_id": 1
        }
    ]
}
Creat a apifaker
// if there are any errors of the directory or json file unmarshaling, err will not be nil
fakeApi, err := apifaker.NewWithApiDir("/path/to/your/fake_apis")

And you can use it as a http.Handler to listen and serve on a port:

http.ListenAndServe(":3000", fakeApi)

Now almost everything is done, if we use the above users.json and books.json for the fakerApi, then you have a list of restful apis for users and books:

GET    /users                   
GET    /users/:id               
POST   /users                   
PUT    /users/:id               
PATCH  /users/:id               
DELETE /users/:id

GET    /books                   
GET    /books/:id               
POST   /books                   
PUT    /books/:id               
PATCH  /books/:id               
DELETE /books/:id

And this apis are really be able to manage the users and books resource, just like using database, what's more, it will validate every request using the rules defined in "columns", in this example, rules are:

  1. every request: resource with the given id must exist.
  2. name of users and books must be unique and users'name must contain A-Z or 0-9.
  3. phone of users must has prefix "132".
  4. every POST/PATH/PUT request of books: the user with given user_id must exist.

In a word, it acts like a standard restful api server.

Data persistence

apifaker will save automatically the changes back to the json file once 24 hours and when you handlers panic something. On the other hand, you can save data manually by calling a method directly:

fakeApi.SaveTofile()
Integrate other mutex

Also, you can integrate other mutex which implemnets http.Handler into the fakeApi, to differetiate faker api from extenal mutex, you can give fakeApi a prefix:

mux := http.NewServeMux()
mux.HandleFunc("/greet", func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
rw.Write([]byte("hello world"))
})

fakeApi.MountTo("/fake_api")
faker.IntegrateHandler(mux)
http.ListenAndServe("localhost:3000", fakeApi)

Then, /greet will be available, at the same time, users and books apis will change to be:

GET     /fake_api/users                   
GET     /fake_api/users/:id               
POST    /fake_api/users                   
PUT     /fake_api/users/:id               
PATCH   /fake_api/users/:id               
DELETE  /fake_api/users/:id

GET     /fake_api/books                   
GET     /fake_api/books/:id               
POST    /fake_api/books                   
PUT     /fake_api/books/:id               
PATCH   /fake_api/books/:id               
DELETE  /fake_api/books/:id

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ColumnsErrorf

func ColumnsErrorf(format string, a ...interface{}) error

func FormatValue

func FormatValue(valueType, value string) (interface{}, error)

FormatValue format the given string value described by the given valueType

func HasManyErrorf

func HasManyErrorf(format string, a ...interface{}) error

func HasOneErrorf

func HasOneErrorf(format string, a ...interface{}) error

func JsonFileErrorf

func JsonFileErrorf(format string, a ...interface{}) error

func NewGinEngineWithFaker

func NewGinEngineWithFaker(faker *ApiFaker) *gin.Engine

NewGinEngineWithFaker allocate and returns a new gin.Engine pointer, added a new middleware which will check the type id param and the resource existence, if ok, set the float64 value of id named idFloat64, otherwise response 404 or 400.

func ResponseErrorMsg

func ResponseErrorMsg(err error) map[string]string

func SeedsErrorf

func SeedsErrorf(format string, a ...interface{}) error

Types

type ApiFaker

type ApiFaker struct {
	// Engine in charge of serving http requests
	*gin.Engine

	// ApiDir the directory contains api json files
	ApiDir string

	// Routers contains all routes use their name as the key
	Routers map[string]*Router

	// ExtMux the external mux for the real api
	ExtMux http.Handler

	// Prefix the prefix of fake apis
	Prefix string
}

func NewWithApiDir

func NewWithApiDir(dir string) (*ApiFaker, error)

NewWithApiDir alloactes and returns a new ApiFaker with the given dir as its ApiDir, the error will not be nil if

  1. dir is wrong
  2. json file format is wrong
  3. break rules described in README.md

func (*ApiFaker) CheckRelationships

func (af *ApiFaker) CheckRelationships() error

CheckRelationships

func (*ApiFaker) CheckUniqueness

func (af *ApiFaker) CheckUniqueness() error

CheckUniqueness

func (*ApiFaker) IntegrateHandler

func (af *ApiFaker) IntegrateHandler(handler http.Handler)

IntegrateHandler set ApiFaker's ExtMux

func (*ApiFaker) MountTo

func (af *ApiFaker) MountTo(path string)

MountTo assign path as ApiFaker's Prefix and reset the handlers

func (*ApiFaker) SaveToFile

func (af *ApiFaker) SaveToFile()

SaveToFile

func (*ApiFaker) ServeHTTP

func (af *ApiFaker) ServeHTTP(rw http.ResponseWriter, req *http.Request)

ServeHTTP implements the http.Handler. It will use Engine when req.URL.Path hasing prefix of Prefix or ExtMux is nil otherwise it will call ApiFaker.ExtMux.ServeHTTP()

type Column

type Column struct {
	Name          string `json:"name"`
	Type          string `json:"type"`
	Unique        bool   `json:"unique"`
	RegexpPattern string `json:"regexp_pattern"`
	// contains filtered or unexported fields
}

func (*Column) AddUniquenessOf

func (column *Column) AddUniquenessOf(value interface{})

AddValue add the give value into the Column's uniqueValues

func (Column) CheckMeta

func (column Column) CheckMeta() error

CheckType checks

  1. Name and Type must be present
  2. Type must in jsonTypes
  3. RegexpPattern must valid

func (*Column) CheckRelationships

func (column *Column) CheckRelationships(seedVal interface{}, model *Model) error

CheckRelationships checks the if resource exists with the xxx_id

func (*Column) CheckUniquenessOf

func (column *Column) CheckUniquenessOf(value interface{}) bool

CheckUniquenessOf checks if the given value exists

func (*Column) CheckValue

func (column *Column) CheckValue(seedVal interface{}, model *Model) error

CheckValue checks the value to insert database

  1. type
  2. regexp pattern matching
  3. uniqueness if unique is true

func (*Column) RemoveUniquenessOf

func (column *Column) RemoveUniquenessOf(value interface{})

RemoveValue remove the given value from Column's uniqueValues

type JsonType

type JsonType string

func (JsonType) GoType

func (j JsonType) GoType() string

GoType returns the corresponding type in golang

func (JsonType) Name

func (j JsonType) Name() string

Name returns JsonType string itself

type LineItem

type LineItem struct {
	// contains filtered or unexported fields
}

func NewLineItemWithGinContext

func NewLineItemWithGinContext(ctx *gin.Context, model *Model) (LineItem, error)

NewLineItemWithGinContext allocates and returns a new LineItem, its keys are from Model.Cloumns, values are from gin.Contex.PostForm(), error will be not nil if gin.Contex.PostForm() has no value for any key

func NewLineItemWithMap

func NewLineItemWithMap(dataMap map[string]interface{}) LineItem

NewLineItemWithMap allocates and returns a new LineItem, using the dataMap param as the new LineItem's dataMap

func (LineItem) DeleteRelatedLis

func (li LineItem) DeleteRelatedLis(id float64, model *Model)

DeleteRelatedLis deletes all related data

func (LineItem) Get

func (li LineItem) Get(key string) (interface{}, bool)

Get gets dataMap[key] and returns its value and exsiting in LinItem

func (*LineItem) ID

func (li *LineItem) ID() float64

ID returns the float64 of id

func (LineItem) Id

func (li LineItem) Id() interface{}

Id returns the value of LineItem's dataMap["id"] it will panic if got a nil or a not float64 "id"

func (LineItem) InsertRelatedData

func (li LineItem) InsertRelatedData(model *Model) LineItem

InsertRelatedData allocates and returns a new LineItem, it will has all data of the caller LineItem, it will insert all related data if the given Model's has any Column named xxx_id

func (LineItem) Len

func (li LineItem) Len() int

Len returns LineItem's dataMap's length

func (*LineItem) Set

func (li *LineItem) Set(key string, value interface{})

Set set key-value of dataMap in LineItem

func (*LineItem) SetStringValue

func (li *LineItem) SetStringValue(key, value, valueType string) error

SetStringValue formats the given string value with given valueType, sets key into LineItem formated value

func (LineItem) ToMap

func (li LineItem) ToMap() map[string]interface{}

ToMap allocates and returns a new map[string]interface{} filled with LineItem's dataMap

type LineItems

type LineItems []LineItem

func (LineItems) Len

func (lis LineItems) Len() int

Len returns LineItems's length

func (LineItems) Less

func (lis LineItems) Less(i, j int) bool

Len returns comparation of value's of two LineItem's "id"

func (LineItems) Swap

func (lis LineItems) Swap(i, j int)

Swap swaps two LineItem

func (LineItems) ToSlice

func (lis LineItems) ToSlice() []map[string]interface{}

ToSlice return a []map[string]interface{} filled with LineItems' elements

type Model

type Model struct {
	Name string `json:"resource_name"`

	// Seeds acts as a snapshot of the whole database
	Seeds   []map[string]interface{} `json:"seeds"`
	Columns []*Column                `json:"columns"`

	// relationships
	HasMany []string `json:"has_many"`
	HasOne  []string `json:"has_one"`

	// Set contains runtime data
	Set *gset.SetThreadSafe `json:"-"`

	sync.RWMutex
	// contains filtered or unexported fields
}

func NewModel

func NewModel(router *Router) *Model

------Model CURD------// NewModel allocates and returns a new Model

func NewModelWithPath

func NewModelWithPath(path string, router *Router) (*Model, error)

NewModelWithPath allocates and returns a new Model, using the given path as it's json file path

func (*Model) Add

func (model *Model) Add(li LineItem) error

Add add a LineItem to Model.Set

func (*Model) CheckColumnsMeta

func (model *Model) CheckColumnsMeta() error

checkColumnsMeta checks columns:

  1. id must be the first column, its type must be number
  2. CheckMeta

func (*Model) CheckRelationship

func (model *Model) CheckRelationship(seed map[string]interface{}) error

CheckRelationship

  1. checks if every resource in HasOne and HasMany exists
  2. CheckRelationships

func (*Model) CheckRelationships

func (model *Model) CheckRelationships() error

CheckRelationships

func (*Model) CheckRelationshipsMeta

func (model *Model) CheckRelationshipsMeta() error

------Check------// CheckRelationshipsMeta check the uniqueness of every element in HasOne and HasMany

func (*Model) CheckUniqueness

func (model *Model) CheckUniqueness() error

CheckUniqueness check uniqueness for initialization for ApiFaker

func (*Model) Delete

func (model *Model) Delete(id float64)

Delete deletes the LineItem and its related data with the given id

func (*Model) Get

func (model *Model) Get(id float64) (li LineItem, ok bool)

Get gets and returns element with id param and the existence of it

func (*Model) Has

func (model *Model) Has(id float64) bool

Has returns if Model has LineItem with the given id

func (*Model) Len

func (model *Model) Len() int

Len returns the length of Model's Set

func (*Model) SaveToFile

func (model *Model) SaveToFile(path string) error

SaveToFile save model to file with the given path

func (*Model) ToLineItems

func (model *Model) ToLineItems() LineItems

ToLineItems allocate a new LineItems filled with Model elements slice

func (*Model) Update

func (model *Model) Update(id float64, li *LineItem) error

Update updates the LineItem with the given id by the given LineItem

func (*Model) UpdateWithAttrs

func (model *Model) UpdateWithAttrs(id float64, ctx *gin.Context) (LineItem, error)

UpdateWithAttrsInGinContext finds a LineItem with id param, updates it with attrs from gin.Contex.PostForm(), returns the edited LineItem

func (*Model) Validate

func (model *Model) Validate(seed map[string]interface{}) error

Validate ValidateValue and CheckRelationships

func (*Model) ValidateSeedsValue

func (model *Model) ValidateSeedsValue() error

ValidateSeedsValue

func (*Model) ValidateValue

func (model *Model) ValidateValue(seed map[string]interface{}) error

ValidateValue checks specific seed

type RestMethod

type RestMethod int
const (
	GET RestMethod = 1 << (10 * iota)
	POST
	PUT
	PATCH
	DELETE
)

type Route

type Route struct {
	// Method request method only supports GET, POST, PUT, PATCH, DELETE
	Method RestMethod

	// Path
	Path string
}

type Router

type Router struct {
	Model  *Model
	Routes []Route
	// contains filtered or unexported fields
}

func NewRouterWithPath

func NewRouterWithPath(path string, apiFaker *ApiFaker) (*Router, error)

NewRouterWithPath allocates and returns a new Router with the givn file path

func (*Router) SaveToFile

func (r *Router) SaveToFile() error

SaveToFile

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL