apigen

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Dec 10, 2020 License: MIT Imports: 18 Imported by: 0

README

apigen

PkgGoDev GitHub Actions

apigen generates API client via execution environment such as curl.

Installation

$ go get github.com/ktr0731/apigen

Usage

This example is located under here

Generator

apigen requires *Definition which describes methods the service has.
Following definition defines CreatePost, ListPosts, GetPost, UpdatePost and DeletePost which belong to Dummy service. Request specify execution environment, apigen generates the API client and request/response types based on the execution result. ParamHint is only required when its method uses path parameters such as "/post/{postID}". apigen generates the request type by using it.

The artifact will be written to client_gen.go which is specified by apigen.WithWriter. Default output is stdout.

package main

import (
	"context"
	"log"
	"os"

	"github.com/ktr0731/apigen"
	"github.com/ktr0731/apigen/curl"
)

func main() {
	def := &apigen.Definition{
		Services: map[string][]*apigen.Method{
			"Dummy": {
				{
					Name:    "CreatePost",
					Request: curl.ParseCommand(`curl 'https://jsonplaceholder.typicode.com/posts' --data-binary '{"title":"foo","body":"bar","userId":1}'`),
				},
				{
					Name:    "ListPosts",
					Request: curl.ParseCommand(`curl https://jsonplaceholder.typicode.com/posts`),
				},
				{
					Name:    "GetPost",
					Request: curl.ParseCommand(`curl https://jsonplaceholder.typicode.com/posts?id=1`),
				},
				{
					Name:      "UpdatePost",
					Request:   curl.ParseCommand(`curl 'https://jsonplaceholder.typicode.com/posts/1' -X 'PUT' --data-binary '{"title":"foo","body":"bar","userId":1}'`),
					ParamHint: "/posts/{postID}",
				},
				{
					Name:      "DeletePost",
					Request:   curl.ParseCommand(`curl 'https://jsonplaceholder.typicode.com/posts/1' -X 'DELETE'`),
					ParamHint: "/posts/{postID}",
				},
			},
		},
	}

	f, err := os.Create("client_gen.go")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	if err := apigen.Generate(context.Background(), def, apigen.WithWriter(f)); err != nil {
		log.Fatal(err)
	}
}

The artifact is here.

Client

We can invoke the API server using the generated API client.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/ktr0731/apigen/client"
)

func main() {
	client := NewDummyClient(client.WithInterceptors(client.ConvertStatusCodeToErrorInterceptor()))

	res, err := client.GetPost(context.Background(), &GetPostRequest{ID: "10"})
	if err != nil {
		log.Fatal(err)
	}

	b, err := json.MarshalIndent(&res, "", "  ")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(b))
}

The output is:

[
  {
    "body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error",
    "id": 10,
    "title": "optio molestias id quia eum",
    "userId": 1
  }
]

Documentation

Overview

Package apigen provides a generator which generates API clients and request/response types via specific execution environment such as curl.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidDefinition represents the error is caused by definition misconfiguration.
	ErrInvalidDefinition = errors.New("invalid definition")
	// ErrUnimplemented represents the provided definition contains unsupported things (such as HTTP method).
	ErrUnimplemented = errors.New("unimplemented")
	// ErrInternal represents an unexpected error has occurred internally.
	ErrInternal = errors.New("internal error")
)

Functions

func Generate

func Generate(ctx context.Context, def *Definition, opts ...Option) error

Generate generates client interfaces, types for requests and responses based on the passed API definitions. If the API definition is invalid, Generate returns an error wrapping ErrInvalidDefinition.

Example
package main

import (
	"context"
	"log"

	"github.com/ktr0731/apigen"
	"github.com/ktr0731/apigen/curl"
)

func main() {
	def := &apigen.Definition{
		Services: map[string][]*apigen.Method{
			"Dummy": {
				{
					Name:    "CreatePost",
					Request: curl.ParseCommand(`curl 'https://jsonplaceholder.typicode.com/posts' --data-binary '{"title":"foo","body":"bar","userId":1}'`),
				},
				{
					Name:    "GetPost",
					Request: curl.ParseCommand(`curl https://jsonplaceholder.typicode.com/posts?id=1`),
				},
				{
					Name:      "ListComments",
					Request:   curl.ParseCommand(`curl https://jsonplaceholder.typicode.com/posts/1/comments`),
					ParamHint: "/posts/{postID}/comments",
				},
				{
					Name:      "UpdatePost",
					Request:   curl.ParseCommand(`curl 'https://jsonplaceholder.typicode.com/posts/1' -X 'PUT' --data-binary '{"title":"foo","body":"bar","userId":1}'`),
					ParamHint: "/posts/{postID}",
				},
			},
		},
	}
	if err := apigen.Generate(context.Background(), def); err != nil {
		log.Fatal(err)
	}
}
Output:

// Code generated by apigen; DO NOT EDIT.
// github.com/ktr0731/apigen

package main

import (
	"context"
	"fmt"
	"net/url"

	"github.com/ktr0731/apigen/client"
)

type DummyClient interface {
	CreatePost(ctx context.Context, req *CreatePostRequest) (*CreatePostResponse, error)
	GetPost(ctx context.Context, req *GetPostRequest) (*GetPostResponse, error)
	ListComments(ctx context.Context, req *ListCommentsRequest) (*ListCommentsResponse, error)
	UpdatePost(ctx context.Context, req *UpdatePostRequest) (*UpdatePostResponse, error)
}

type dummyClient struct {
	*client.Client
}

func NewDummyClient(opts ...client.Option) DummyClient {
	return &dummyClient{Client: client.New(opts...)}
}

func (c *dummyClient) CreatePost(ctx context.Context, req *CreatePostRequest) (*CreatePostResponse, error) {
	u, err := url.Parse("https://jsonplaceholder.typicode.com/posts")
	if err != nil {
		return nil, err
	}

	var res CreatePostResponse
	err = c.Do(ctx, "POST", u, req.Body, &res)
	return &res, err
}

func (c *dummyClient) GetPost(ctx context.Context, req *GetPostRequest) (*GetPostResponse, error) {
	query := url.Values{
		"id": []string{req.ID},
	}.Encode()

	u, err := url.Parse("https://jsonplaceholder.typicode.com/posts")
	if err != nil {
		return nil, err
	}

	u.RawQuery = query

	var res GetPostResponse
	err = c.Do(ctx, "GET", u, nil, &res)
	return &res, err
}

func (c *dummyClient) ListComments(ctx context.Context, req *ListCommentsRequest) (*ListCommentsResponse, error) {
	u, err := url.Parse(fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%s/comments", req.PostID))
	if err != nil {
		return nil, err
	}

	var res ListCommentsResponse
	err = c.Do(ctx, "GET", u, nil, &res)
	return &res, err
}

func (c *dummyClient) UpdatePost(ctx context.Context, req *UpdatePostRequest) (*UpdatePostResponse, error) {
	u, err := url.Parse(fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%s", req.PostID))
	if err != nil {
		return nil, err
	}

	var res UpdatePostResponse
	err = c.Do(ctx, "PUT", u, req.Body, &res)
	return &res, err
}

type CreatePostRequest struct {
	Body *CreatePostRequestBody
}

type CreatePostRequestBody struct {
	Body   string  `json:"body,omitempty"`
	Title  string  `json:"title,omitempty"`
	UserID float64 `json:"userId,omitempty"`
}

type CreatePostResponse struct {
	ID float64 `json:"id,omitempty"`
}

type GetPostRequest struct {
	ID string
}

type GetPostResponse []struct {
	Body   string  `json:"body,omitempty"`
	ID     float64 `json:"id,omitempty"`
	Title  string  `json:"title,omitempty"`
	UserID float64 `json:"userId,omitempty"`
}

type ListCommentsRequest struct {
	PostID string
}

type ListCommentsResponse []struct {
	Body   string  `json:"body,omitempty"`
	Email  string  `json:"email,omitempty"`
	ID     float64 `json:"id,omitempty"`
	Name   string  `json:"name,omitempty"`
	PostID float64 `json:"postId,omitempty"`
}

type UpdatePostRequest struct {
	PostID string
	Body   *UpdatePostRequestBody
}

type UpdatePostRequestBody struct {
	Body   string  `json:"body,omitempty"`
	Title  string  `json:"title,omitempty"`
	UserID float64 `json:"userId,omitempty"`
}

type UpdatePostResponse struct {
	ID float64 `json:"id,omitempty"`
}

Types

type Definition

type Definition struct {
	// Services defines API services and its methods.
	// Each method name must be unique.
	Services map[string][]*Method
}

Definition defines API metadata for code generation.

type Method

type Method struct {
	// Name defines the name of method which represents an API.
	Name string
	// Request instantiates a new *http.Request. See examples for details.
	Request RequestFunc
	// ParamHint specifies path parameters.
	// These will be organized as the request fields. Each parameter must be start with ":".
	// For example, "/posts/:postID" is given as a ParamHint, apigen generates the following request type:
	//
	//  type Request struct {
	//    PostID string
	//  }
	//
	// If ParamHint differs the actual path, it will be ignored and never generate any request fields.
	ParamHint string
}

Method defines an API method.

type Option

type Option func(*runner)

Option represents an option for Generate.

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient specifies the HTTP client for invoking HTTP requests to know API structure. Default is *http.DefaultClient.

func WithPackage

func WithPackage(name string) Option

WithPackage specifies the generated file's package name. Default is main.

func WithWriter

func WithWriter(w io.Writer) Option

WithWriter specifies the destination writer for generated files. Default is stdout.

type RequestFunc

type RequestFunc func(context.Context) (*http.Request, error)

RequestFunc defines a function which instantiates a new *http.Request. RequestFunc may return errors wrapping a pre-defined error in apigen.

Directories

Path Synopsis
Package curl provides apigen.RequestFunc for curl command.
Package curl provides apigen.RequestFunc for curl command.

Jump to

Keyboard shortcuts

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