blob: 9be3c8a9517020cc039029c2c352d8ef9435e65c [file] [log] [blame]
/*
Package endpoints will let you write Cloud Endpoints backend in Go.
Usage
Declare structs which describe your data. For instance:
// Greeting is a datastore entity that represents a single greeting.
// It also serves as (a part of) a response of GreetingService.
type Greeting struct {
Key *datastore.Key `json:"id" datastore:"-"`
Author string `json:"author"`
Content string `json:"content" datastore:",noindex" endpoints:"req"`
Date time.Time `json:"date"`
}
// GreetingsList is a response type of GreetingService.List method
type GreetingsList struct {
Items []*Greeting `json:"items"`
}
// Request type for GreetingService.List
type GreetingsListReq struct {
Limit int `json:"limit" endpoints:"d=10"`
}
Then, a service:
// GreetingService can sign the guesbook, list all greetings and delete
// a greeting from the guestbook.
type GreetingService struct {
}
// List responds with a list of all greetings ordered by Date field.
// Most recent greets come first.
func (gs *GreetingService) List(c context.Context, r *GreetingsListReq) (*GreetingsList, error) {
if r.Limit <= 0 {
r.Limit = 10
}
q := datastore.NewQuery("Greeting").Order("-Date").Limit(r.Limit)
greets := make([]*Greeting, 0, r.Limit)
keys, err := q.GetAll(c, &greets)
if err != nil {
return nil, err
}
for i, k := range keys {
greets[i].Key = k
}
return &GreetingsList{greets}, nil
}
Last step is to make the above available as a discoverable API
and leverage all the juicy stuff Cloud Endpoints are great at.
import "github.com/GoogleCloudPlatform/go-endpoints/endpoints"
func init() {
greetService := &GreetingService{}
api, err := endpoints.RegisterService(greetService,
"greeting", "v1", "Greetings API", true)
if err != nil {
panic(err.Error())
}
info := api.MethodByName("List").Info()
info.Name, info.HTTPMethod, info.Path, info.Desc =
"greets.list", "GET", "greetings", "List most recent greetings."
endpoints.HandleHTTP()
}
Don't forget to add URL matching in app.yaml:
application: my-app-id
version: v1
threadsafe: true
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
# Important! Even though there's a catch all routing above,
# without these two lines it's not going to work.
# Make sure you have this:
- url: /_ah/spi/.*
script: _go_app
That's it. It is time to start dev server and enjoy the discovery doc:
http://localhost:8080/_ah/api/explorer
Custom types
You can define your own types and use them directly as a field type in a
service method request/response as long as they implement json.Marshaler and
json.Unmarshaler interfaces.
Let's say we have this method:
func (s *MyService) ListItems(c context.Context, r *ListReq) (*ItemsList, error) {
// fetch a list of items
}
where ListReq and ItemsList are defined as follows:
type ListReq struct {
Limit int `json:"limit,string" endpoints:"d=10,max=100"`
Page *QueryMarker `json:"cursor"`
}
type ItemsList struct {
Items []*Item `json:"items"`
Next *QueryMarker `json:"next,omitempty"`
}
What's interesting here is ListReq.Page and ItemsList.Next fields which are
of type QueryMarker:
import "appengine/datastore"
type QueryMarker struct {
datastore.Cursor
}
func (qm *QueryMarker) MarshalJSON() ([]byte, error) {
return []byte(`"` + qm.String() + `"`), nil
}
func (qm *QueryMarker) UnmarshalJSON(buf []byte) error {
if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
return errors.New("QueryMarker: bad cursor value")
}
cursor, err := datastore.DecodeCursor(string(buf[1 : len(buf)-1]))
if err != nil {
return err
}
*qm = QueryMarker{cursor}
return nil
}
Now that our QueryMarker implements required interfaces we can use ListReq.Page
field as if it were a `datastore.Cursor` in our service method, for instance:
func (s *MyService) ListItems(c context.Context, r *ListReq) (*ItemsList, error) {
list := &ItemsList{Items: make([]*Item, 0, r.Limit)}
q := datastore.NewQuery("Item").Limit(r.Limit)
if r.Page != nil {
q = q.Start(r.Page.Cursor)
}
var iter *datastore.Iterator
for iter := q.Run(c); ; {
var item Item
key, err := iter.Next(&item)
if err == datastore.Done {
break
}
if err != nil {
return nil, err
}
item.Key = key
list.Items = append(list.Items, &item)
}
cur, err := iter.Cursor()
if err != nil {
return nil, err
}
list.Next = &QueryMarker{cur}
return list, nil
}
A serialized ItemsList would then look something like this:
{
"items": [
{
"id": "5629499534213120",
"name": "A TV set",
"price": 123.45
}
],
"next": "E-ABAIICImoNZGV2fmdvcGhtYXJrc3IRCxIEVXNlchiAgICAgICACgwU"
}
Another nice thing about this is, some types in appengine/datastore package
already implement json.Marshal and json.Unmarshal.
Take, for instance, datastore.Key. I could use it as an ID in my JSON response
out of the box, if I wanted to:
type User struct {
Key *datastore.Key `json:"id" datastore:"-"`
Name string `json:"name" datastore:"name"`
Role string `json:"role" datastore:"role"`
Email string `json:"email" datastore:"email"`
}
type GetUserReq struct {
Key *datastore.Key `json:"id"`
}
// defined with "users/{id}" path template
func (s *MyService) GetUser(c context.Context, r *GetUserReq) (*User, error) {
user := &User{}
if err := datastore.Get(c, r.Key, user); err != nil {
return nil, err
}
user.Key = r.Key
return user, nil
}
JSON would then look something like this:
GET /_ah/api/myapi/v1/users/ag1kZXZ-Z29waG1hcmtzchELEgRVc2VyGICAgICAgIAKDA
{
"id": "ag1kZXZ-Z29waG1hcmtzchELEgRVc2VyGICAgICAgIAKDA",
"name": "John Doe",
"role": "member",
"email": "user@example.org"
}
Field tags
Go Endpoints has its own field tag "endpoints" which you can use to let your
clients know what a service method data constraints are (on input):
- req, means "required".
- d, default value, cannot be used together with req.
- min and max constraints. Can be used only on int and uint (8/16/32/64 bits).
- desc, a field description. Cannot contain a "," (comma) for now.
Let's see an example:
type TaggedStruct struct {
A int `endpoints:"req,min=0,max=100,desc=An int field"`
B int `endpoints:"d=10,min=1,max=200"`
C string `endpoints:"req,d=Hello gopher,desc=A string field"`
}
- A field is required and has min & max constrains, is described as "An int field"
- B field is not required, defaults to 10 and has min & max constrains
- C field is required, defaults to "Hello gopher", is described as "A string field"
JSON tag and path templates
You can use JSON tags to shape your service method's response (the output).
Endpoints will honor Go's encoding/json marshaling rules
(http://golang.org/pkg/encoding/json/#Marshal), which means having this struct:
type TaggedStruct struct {
A int
B int `json:"myB"`
C string `json:"c"`
Skipped int `json:"-"`
}
a service method path template could then look like:
some/path/{A}/other/{c}/{myB}
Notice, the names are case-sensitive.
Naturally, you can combine json and endpoints tags to use a struct for both
input and output:
type TaggedStruct struct {
A int `endpoints:"req,min=0,max=100,desc=An int field"`
B int `json:"myB" endpoints:"d=10,min=1,max=200"`
C string `json:"c" endpoints:"req,d=Hello gopher,desc=A string field"`
Skipped int `json:"-"`
}
Long integers (int64, uint64)
As per Type and Format Summary (https://developers.google.com/discovery/v1/type-format):
a 64-bit integer cannot be represented in JSON (since JavaScript and JSON
support integers up to 2^53). Therefore, a 64-bit integer must be
represented as a string in JSON requests/responses
In this case, it is sufficient to append ",string" to the json tag:
type Int64Struct struct {
Id int64 `json:",string"`
}
Generate client libraries
Once an app is deployed on appspot.com, we can use the discovery doc to generate
libraries for different clients.
Android
$ URL='https://my-app-id.appspot.com/_ah/api/discovery/v1/apis/greeting/v1/rest'
$ curl -s $URL > greetings.rest.discovery
# Optionally check the discovery doc
$ less greetings.rest.discovery
$ GO_SDK/endpointscfg.py gen_client_lib java greetings.rest.discovery
You should be able to find ./greetings.rest.zip file with Java client source
code and its dependencies.
Once you have that, follow the official guide
https://developers.google.com/appengine/docs/python/endpoints/consume_android.
iOS
# Note the rpc suffix in the URL:
$ URL='https://my-app-id.appspot.com/_ah/api/discovery/v1/apis/greeting/v1/rpc'
$ curl -s $URL > greetings.rpc.discovery
# optionally check the discovery doc
$ less greetings.rpc.discovery
Then, feed greetings.rpc.discovery file to the library generator on OS X
as described in the official guide:
https://developers.google.com/appengine/docs/python/endpoints/consume_ios
JavaScript
There's really nothing to generate for JavaScript, you just use it!
Here's the official guide:
https://developers.google.com/appengine/docs/python/endpoints/consume_js
Other docs
Wiki pages on the github repo:
https://github.com/crhym3/go-endpoints/wiki
Samples
Check out TicTacToe sample:
https://github.com/crhym3/go-tictactoe
Or play it on the live demo app at https://go-endpoints.appspot.com/tictactoe
Running tests
We currently use aet tool (https://github.com/crhym3/aegot) to simplify running
tests on files that have "appengine" or "appengine_internal" imports.
Check out the readme of that tool but, assuming you cloned this repo
(so you can reach ./endpoints dir), the initial setup process is pretty simple:
- go get github.com/crhym3/aegot/aet
- aet init ./endpoints
That's it. You should be able to run tests with "aet test ./endpoints" now.
*/
package endpoints