Go製WAFのuconを触ってみた その2

## Swagger Plugin

ここを見ながら頑張ります。
https://github.com/favclip/ucon/tree/master/sample/swagger

## まとめ

* 難しい！！
* [serverName]/api/swagger.jsonでJSONファイルを取得できる
* swagger.NewHandlerInfoでhandlerをwrapしよう
* swagger.NewPlugin(*Options)で作成
* ucon.DefaultMux.Prepare()でPlugin起動
* swagger.NewHandlerInfoでwrapしたものはucon.HandlerFuncではなくucon.Handleで登録する！
* swagger-uiで見るにはすべてのAPIでJSONを返す必要がある？（HTTP Statusのみでも）

## Swagger Pluginを作成する

```go:plugin.go
// NewPlugin returns new swagger plugin configured with the options.
func NewPlugin(opts *Options) *Plugin {
	if opts == nil {
		opts = &Options{}
	}
	plugin := &Plugin{
		options:          opts,
		swagger:          opts.Object,
		typeSchemaMapper: make(map[reflect.Type]*TypeSchema),
	}
	for k, v := range DefaultTypeSchemaMapper {
		plugin.typeSchemaMapper[k] = v
	}
	var _ ucon.HandlersScannerPlugin = plugin

	return plugin
}
```

`&swagger.Option`を引数にして新しいPluginを返しています。
`DefaultTypeSchemaMapper`から定義済のschemaを登録しています。
`var _ ucon.HandlersScannerPlugin = plugin`はGoでよく見る、右辺が左辺のInterfaceを実装しているかを確認する処理です。

## swagger.Option.Object

```go:swagger.Option.Object
type Options struct {
	Object                 *Object
	DefinitionNameModifier func(refT reflect.Type, defName string) string
}

// Object is https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
type Object struct {
	Swagger             string                 `json:"swagger" swagger:",req,d=2.0"`
	Info                *Info                  `json:"info" swagger:",req"`
	Host                string                 `json:"host,omitempty"`
	BasePath            string                 `json:"basePath,omitempty"`
	Schemes             []string               `json:"schemes,omitempty" swagger:",d=https"`
	Consumes            []string               `json:"consumes,omitempty" swagger:",d=application/json"`
	Produces            []string               `json:"produces,omitempty" swagger:",d=application/json"`
	Paths               Paths                  `json:"paths" swagger:",req"`
	Definitions         Definitions            `json:"definitions,omitempty"`
	Parameters          ParametersDefinitions  `json:"parameters,omitempty"`
	Responses           ResponsesDefinitions   `json:"responses,omitempty"`
	SecurityDefinitions SecurityDefinitions    `json:"securityDefinitions,omitempty"`
	Security            []SecurityRequirement  `json:"security,omitempty"`
	Tags                []*Tag                 `json:"tags,omitempty"`
	ExternalDocs        *ExternalDocumentation `json:"externalDocs,omitempty"`
}
```

swaggerで定義されているSchemaのObjectでしょうかね。。

## Pluginを登録

```go:register_plugin
///// core.go /////
// Plugin can append Plugin to ServeMux.
func Plugin(plugin interface{}) {
	DefaultMux.Plugin(plugin)
}

// Plugin can append Plugin to ServeMux.
func (m *ServeMux) Plugin(plugin interface{}) {
	p, ok := plugin.(*pluginContainer)
	if !ok {
		p = &pluginContainer{base: plugin}
	}
	p.check()
	m.plugins = append(m.plugins, p)
}

///// plugin.go /////
type pluginContainer struct {
	base interface{}
}

func (p *pluginContainer) check() {
	if p.HandlersScanner() != nil {
		return
	}

	panic(fmt.Sprintf("unused plugin: %#v", p.base))
}

// HandlersScanner returns itself if it implements HandlersScannerPlugin.
func (p *pluginContainer) HandlersScanner() HandlersScannerPlugin {
	if v, ok := p.base.(HandlersScannerPlugin); ok {
		return v
	}

	return nil
}

// HandlersScannerPlugin is an interface to make a plugin for scanning request handlers.
type HandlersScannerPlugin interface {
	HandlersScannerProcess(m *ServeMux, rds []*RouteDefinition) error
}
```

全体的に登録したいPluginが`HandlersScannerPlugin`を実装していればPluginに登録可能のようです。

## Pluginを使う

`ucon.DefautlMux.Prepare()`を実行します

```core.go

// Prepare the ServeMux.
// Plugin is not show affect to anything.
// This method is enabled plugins.
func (m *ServeMux) Prepare() {
	for _, plugin := range m.plugins {
		used := false
		if sc := plugin.HandlersScanner(); sc != nil {
			err := sc.HandlersScannerProcess(m, m.router.handlers)
			if err != nil {
				panic(err)
			}
			used = true
		}
		if !used {
			panic(fmt.Sprintf("unused plugin: %#v", plugin))
		}
	}
}
```

先ほど登録したpluginsを実行しています。
`plugin.HandlersScanner() == nil`の場合は`used == false`のままなのでpanicするようです。

## Swagger PluginのHandlersScannerProcess(m, m.router.handlers)を見る

Pluginが実行されるのは、`HandlersScannerProcess(m, m.router.handlers)`なので、
Swagger Pluginで実装されている箇所を見ていきます。

```go:plugin.go

// HandlersScannerProcess executes scanning all registered handlers to serve swagger.json.
func (p *Plugin) HandlersScannerProcess(m *ucon.ServeMux, rds []*ucon.RouteDefinition) error {
	// construct swagger.json
	for _, rd := range rds {
		err := p.processHandler(rd)
		if err != nil {
			return err
		}
	}

	err := p.swagger.finish()
	if err != nil {
		return err
	}

	// supply swagger.json endpoint
	m.HandleFunc("GET", "/api/swagger.json", func(w http.ResponseWriter, r *http.Request) *Object {
		return p.swagger
	})

	return nil
}
```

`p.processHandler(*ucon.RouteDefinition)`→`p.swagger.finish()`→`m.HandlerFunc`の順に実行されています。
順番に見ていきます。

```go:p.processHandler(*ucon.RouteDfinition)
func (p *Plugin) processHandler(rd *ucon.RouteDefinition) error {
	if p.swagger == nil {
		p.swagger = &Object{}
	}
	if p.swagger.Paths == nil {
		p.swagger.Paths = make(Paths, 0)
	}
	if p.swagger.Definitions == nil {
		p.swagger.Definitions = make(Definitions, 0)
	}

	item := p.swagger.Paths[rd.PathTemplate.PathTemplate]
	if item == nil {
		item = &PathItem{}
	}

	var putOperation func(op *Operation)
    // putOperationの設定は省略

	op, err := p.extractHandlerInfo(rd)
	if err != nil {
		return err
	}
	if op != nil {
		p.swagger.Paths[rd.PathTemplate.PathTemplate] = item

		putOperation(op)

		for _, tsc := range p.typeSchemaMapper {
			if !tsc.AllowRef {
				continue
			}
			if tsc.RefName == "" {
				return errors.New("Name is required")
			}

			_, ok := p.swagger.Definitions[tsc.RefName]
			if !ok {
				p.swagger.Definitions[tsc.RefName] = tsc.Schema
			}
		}
	}

	return nil
}
```

p.swaggerに各種設定を登録していることがわかります。
`p.extractHandlerInfo(*ucon.RouteDfinition)`を見てみます。

```go:
func (p *Plugin) extractHandlerInfo(rd *ucon.RouteDefinition) (*Operation, error) {
    // とても長いので省略・・・
}
```

まとめると、HandleFuncで登録したものがゴニョゴニョ処理されているようでした・・・（多分）

`p.swagger.finish()`関係は長いのでコード内にインラインで記述していきます。

```go:p.swagger.finish()
func (o *Object) finish() error {
	return checkObject(reflect.ValueOf(o))
}

func checkObject(refV reflect.Value) error {
	if refV.Kind() == reflect.Ptr {
		refV = refV.Elem()
	}

	var checkNext func(fV reflect.Value) error
    // checkNextの設定は省略

	for i, numField := 0, refV.NumField(); i < numField; i++ {
		sf := refV.Type().Field(i)
		fV := refV.Field(i)
		swaggerTag := NewTagSwagger(sf.Tag)

        // `swagger:",d=hogehoge`のhogehogeがObjectのFieldに設定される
		if d := swaggerTag.Default(); d != "" && ucon.IsEmpty(fV) {
			err := ucon.SetValueFromString(fV, d)
			if err != nil {
				return err
			}
		}
        // `swagger:"req"`のようにreqがあり、かつそのFieldが空の場合にエラー
        // reqがついたもののうち、Pathsはucon.HandleFuncしていれば自動で登録されている
		if swaggerTag.Required() && ucon.IsEmpty(fV) {
			return fmt.Errorf("%s is required", sf.Name)
		}
        // `swagger:",enum=a|b|c"のようにa,b,cがstringであれば処理が走る
		if enum := swaggerTag.Enum(); len(enum) != 0 && !ucon.IsEmpty(fV) {
			if sf.Type.Kind() != reflect.String {
				return fmt.Errorf("unsupported kind: %s", sf.Type.Kind().String())
			}

			str := fV.String()
			found := false
			for _, c := range enum {
				if c == str {
					found = true
					break
				}
			}

			if !found {
				return fmt.Errorf("invalid value: %s in %s", str, sf.Name)
			}
		}

        // checkNextはarray,slice,struct,mapなど入れ子の構造に使用される
        // 特にstructの場合はcheckObject自体が再帰的に実行される
		err := checkNext(fV)
		if err != nil {
			return err
		}
	}

	return nil
}
```

最終的にHandlerFuncで`p.swagger`を返しています。
なお、`ucon.Middleware(ResponseMapper())`か`ucon.Orthodox()`しないと多分エラーとなるので注意。

さて、ここまで読んで、`p.swagger.Swagger`に値が入っていないことに気がついた。
これはどこで定義されているかとsampleに戻ると、`swagger.NewHandlerInfo`が怪しそうだと気づく。
それでは、見てみましょう・・

```go:plugin.go
// NewHandlerInfo returns new HandlerInfo containing given handler function.
func NewHandlerInfo(handler interface{}) *HandlerInfo {
	ucon.CheckFunction(handler)
	return &HandlerInfo{
		HandlerFunc: handler,
	}
}
```

特に何もなかった。まあいいかｗ
`NewHandlerInfo`をしないと、おそらく`p.extractHandlerInfo(*ucon.RouteDfinition)`が機能しないと思われます。