pkg,cmd: add document generator tool
This commit is contained in:
parent
b5c7f1978e
commit
c7ed4fdd60
8 changed files with 1265 additions and 0 deletions
96
cmd/gendoc/main.go
Normal file
96
cmd/gendoc/main.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/coreos/dex/pkg/gendoc"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "gendoc",
|
||||||
|
Short: "Generate documentation from REST specifications.",
|
||||||
|
Long: `A tool to generate documentation for dex's REST APIs.`,
|
||||||
|
RunE: gen,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
infile string
|
||||||
|
outfile string
|
||||||
|
readFlavor string
|
||||||
|
writeFlavor string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd.PersistentFlags().StringVar(&infile, "f", "", "File to read from. If ommitted read from stdin.")
|
||||||
|
cmd.PersistentFlags().StringVar(&outfile, "o", "", "File to write to. If ommitted write to stdout.")
|
||||||
|
cmd.PersistentFlags().StringVar(&readFlavor, "r", "googleapi", "Flavor of REST spec to read. Currently only supports 'googleapi'.")
|
||||||
|
cmd.PersistentFlags().StringVar(&writeFlavor, "w", "markdown", "Flavor of documentation. Currently only supports 'markdown'.")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gen(cmd *cobra.Command, args []string) error {
|
||||||
|
var (
|
||||||
|
in io.Reader
|
||||||
|
out io.Writer
|
||||||
|
decode func(io.Reader) (gendoc.Document, error)
|
||||||
|
encode func(gendoc.Document) ([]byte, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
switch readFlavor {
|
||||||
|
case "googleapi":
|
||||||
|
decode = gendoc.ParseGoogleAPI
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported read flavor %q", readFlavor)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch writeFlavor {
|
||||||
|
case "markdown":
|
||||||
|
encode = gendoc.Document.MarshalMarkdown
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported write flavor %q", writeFlavor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if infile == "" {
|
||||||
|
in = os.Stdin
|
||||||
|
} else {
|
||||||
|
f, err := os.OpenFile(infile, os.O_RDONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
in = f
|
||||||
|
}
|
||||||
|
|
||||||
|
if outfile == "" {
|
||||||
|
out = os.Stdout
|
||||||
|
} else {
|
||||||
|
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
out = f
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := decode(in)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode input: %v", err)
|
||||||
|
}
|
||||||
|
data, err := encode(doc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encode document: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = out.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
68
pkg/gendoc/gendoc.go
Normal file
68
pkg/gendoc/gendoc.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package gendoc
|
||||||
|
|
||||||
|
type Document struct {
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Version string
|
||||||
|
Paths []Path
|
||||||
|
Models []Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Summary string
|
||||||
|
Description string
|
||||||
|
Parameters []Parameter
|
||||||
|
Responses []Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type byPath []Path
|
||||||
|
|
||||||
|
func (p byPath) Len() int { return len(p) }
|
||||||
|
func (p byPath) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
func (p byPath) Less(i, j int) bool {
|
||||||
|
if p[i].Path == p[j].Path {
|
||||||
|
return p[i].Method < p[j].Method
|
||||||
|
}
|
||||||
|
return p[i].Path < p[j].Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parameter struct {
|
||||||
|
Name string
|
||||||
|
LocatedIn string
|
||||||
|
Description string
|
||||||
|
Required bool
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeArray = "array"
|
||||||
|
TypeBool = "boolean"
|
||||||
|
TypeFloat = "float"
|
||||||
|
TypeInt = "integer"
|
||||||
|
TypeObject = "object"
|
||||||
|
TypeString = "string"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Schema struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Description string
|
||||||
|
Children []Schema
|
||||||
|
Ref string
|
||||||
|
}
|
||||||
|
|
||||||
|
type byName []Schema
|
||||||
|
|
||||||
|
func (n byName) Len() int { return len(n) }
|
||||||
|
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
||||||
|
func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }
|
||||||
|
|
||||||
|
const CodeDefault = 0
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int // 0 means "Default"
|
||||||
|
Description string
|
||||||
|
Type string
|
||||||
|
}
|
143
pkg/gendoc/googleapi.go
Normal file
143
pkg/gendoc/googleapi.go
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
// Package gendoc generates documentation for REST APIs.
|
||||||
|
package gendoc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseGoogleAPI(r io.Reader) (Document, error) {
|
||||||
|
var d doc
|
||||||
|
if err := json.NewDecoder(r).Decode(&d); err != nil {
|
||||||
|
return Document{}, err
|
||||||
|
}
|
||||||
|
return d.toDocument(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doc represents a Google API specification document. It is NOT intended to encompass all
|
||||||
|
// options provided by the spec, only the minimal fields needed to convert dex's API
|
||||||
|
// definitions into documentation.
|
||||||
|
type doc struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
DocumentationLink string `json:"documentationLink"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
BasePath string `json:"basePath"`
|
||||||
|
Schemas map[string]schema `json:"schemas"`
|
||||||
|
Resources map[string]methods `json:"resources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type methods struct {
|
||||||
|
Methods map[string]resource `json:"methods"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type param struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Required bool `json:"required"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type schema struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Items *schema `json:"items"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
Properties map[string]schema
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type resource struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Method string `json:"httpMethod"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Parameters map[string]param `json:"parameters"`
|
||||||
|
Request *ref `json:"request"`
|
||||||
|
Response *ref `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ref struct {
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d doc) toDocument() Document {
|
||||||
|
gDoc := Document{
|
||||||
|
Title: d.Title,
|
||||||
|
Description: d.Description,
|
||||||
|
Version: d.Version,
|
||||||
|
}
|
||||||
|
for name, s := range d.Schemas {
|
||||||
|
s.ID = name
|
||||||
|
gDoc.Models = append(gDoc.Models, s.toSchema())
|
||||||
|
}
|
||||||
|
|
||||||
|
for object, methods := range d.Resources {
|
||||||
|
for action, r := range methods.Methods {
|
||||||
|
gDoc.Paths = append(gDoc.Paths, r.toPath(d, object, action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byPath(gDoc.Paths))
|
||||||
|
sort.Sort(byName(gDoc.Models))
|
||||||
|
return gDoc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schema) toSchema() Schema {
|
||||||
|
sch := Schema{
|
||||||
|
Name: s.ID,
|
||||||
|
Type: s.Type,
|
||||||
|
Description: s.Description,
|
||||||
|
Ref: s.Ref,
|
||||||
|
}
|
||||||
|
for name, prop := range s.Properties {
|
||||||
|
c := prop.toSchema()
|
||||||
|
c.Name = name
|
||||||
|
sch.Children = append(sch.Children, c)
|
||||||
|
}
|
||||||
|
if s.Items != nil {
|
||||||
|
sch.Children = []Schema{s.Items.toSchema()}
|
||||||
|
}
|
||||||
|
sort.Sort(byName(sch.Children))
|
||||||
|
return sch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r resource) toPath(d doc, object, action string) Path {
|
||||||
|
p := Path{
|
||||||
|
Method: r.Method,
|
||||||
|
Path: path.Join("/", r.Path),
|
||||||
|
Summary: strings.TrimSpace(action + " " + object),
|
||||||
|
Description: r.Description,
|
||||||
|
}
|
||||||
|
for name, param := range r.Parameters {
|
||||||
|
p.Parameters = append(p.Parameters, Parameter{
|
||||||
|
Name: name,
|
||||||
|
LocatedIn: param.Location,
|
||||||
|
Required: param.Required,
|
||||||
|
Type: param.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if r.Request != nil {
|
||||||
|
ref := r.Request.Ref
|
||||||
|
p.Parameters = append(p.Parameters, Parameter{
|
||||||
|
LocatedIn: "body",
|
||||||
|
Required: true,
|
||||||
|
Type: ref,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if r.Response != nil {
|
||||||
|
p.Responses = append(p.Responses, Response{
|
||||||
|
Code: 200,
|
||||||
|
Type: r.Response.Ref,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
p.Responses = append(p.Responses, Response{
|
||||||
|
Code: CodeDefault,
|
||||||
|
Description: "Unexpected error",
|
||||||
|
})
|
||||||
|
return p
|
||||||
|
}
|
333
pkg/gendoc/googleapi_test.go
Normal file
333
pkg/gendoc/googleapi_test.go
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
package gendoc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToSchema(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
s schema
|
||||||
|
want Schema
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
s: schema{
|
||||||
|
ID: "UsersResponse",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"users": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &schema{
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nextPageToken": {Type: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: Schema{
|
||||||
|
Name: "UsersResponse",
|
||||||
|
Type: "object",
|
||||||
|
Children: []Schema{
|
||||||
|
{
|
||||||
|
Name: "nextPageToken",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "users",
|
||||||
|
Type: "array",
|
||||||
|
Children: []Schema{
|
||||||
|
{
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
s: schema{
|
||||||
|
ID: "UserCreateResponse",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"user": {
|
||||||
|
Type: "object",
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
"resetPasswordLink": {Type: "string"},
|
||||||
|
"emailSent": {Type: "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: Schema{
|
||||||
|
Name: "UserCreateResponse",
|
||||||
|
Type: "object",
|
||||||
|
Children: []Schema{
|
||||||
|
{
|
||||||
|
Name: "emailSent",
|
||||||
|
Type: "boolean",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "resetPasswordLink",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "user",
|
||||||
|
Type: "object",
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := tt.s.toSchema()
|
||||||
|
if diff := pretty.Compare(got, tt.want); diff != "" {
|
||||||
|
t.Errorf("case %d: got != want: %s", i, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarsal(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
want doc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
file: "testdata/admin.json",
|
||||||
|
want: doc{
|
||||||
|
Name: "adminschema",
|
||||||
|
Version: "v1",
|
||||||
|
Title: "Dex Admin API",
|
||||||
|
Description: "The Dex Admin API.",
|
||||||
|
DocumentationLink: "http://github.com/coreos/dex",
|
||||||
|
Protocol: "rest",
|
||||||
|
BasePath: "/api/v1/",
|
||||||
|
Schemas: map[string]schema{
|
||||||
|
"Admin": schema{
|
||||||
|
ID: "Admin",
|
||||||
|
Type: "object",
|
||||||
|
Description: "Admin represents an admin user within the database",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"id": {Type: "string"},
|
||||||
|
"email": {Type: "string"},
|
||||||
|
"password": {Type: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"State": schema{
|
||||||
|
ID: "State",
|
||||||
|
Type: "object",
|
||||||
|
Description: "Admin represents dex data within.",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"AdminUserCreated": {Type: "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: map[string]methods{
|
||||||
|
"Admin": methods{
|
||||||
|
Methods: map[string]resource{
|
||||||
|
"Get": resource{
|
||||||
|
Description: "Retrieve information about an admin user.",
|
||||||
|
Method: "GET",
|
||||||
|
Path: "admin/{id}",
|
||||||
|
Parameters: map[string]param{
|
||||||
|
"id": param{
|
||||||
|
Type: "string",
|
||||||
|
Required: true,
|
||||||
|
Location: "path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Response: &ref{"Admin"},
|
||||||
|
},
|
||||||
|
"Create": resource{
|
||||||
|
Description: "Create a new admin user.",
|
||||||
|
Method: "POST",
|
||||||
|
Path: "admin",
|
||||||
|
Request: &ref{"Admin"},
|
||||||
|
Response: &ref{"Admin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"State": methods{
|
||||||
|
Methods: map[string]resource{
|
||||||
|
"Get": resource{
|
||||||
|
Description: "Get the state of the Dex DB",
|
||||||
|
Method: "GET",
|
||||||
|
Path: "state",
|
||||||
|
Response: &ref{"State"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
data, err := ioutil.ReadFile(tt.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %q: read file failed %v", tt.file, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var d doc
|
||||||
|
if err := json.Unmarshal(data, &d); err != nil {
|
||||||
|
t.Errorf("case %q: failed to unmarshal %v", tt.file, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if diff := pretty.Compare(d, tt.want); diff != "" {
|
||||||
|
t.Errorf("case %q: got did not match want: %s", tt.file, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalSchema(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
want map[string]schema
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
file: "testdata/worker.json",
|
||||||
|
want: map[string]schema{
|
||||||
|
"Error": schema{
|
||||||
|
ID: "Error",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"error": {Type: "string"},
|
||||||
|
"error_description": {Type: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Client": schema{
|
||||||
|
ID: "Client",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"id": {Type: "string"},
|
||||||
|
"redirectURIs": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &schema{Type: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ClientWithSecret": schema{
|
||||||
|
ID: "Client",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"id": {Type: "string"},
|
||||||
|
"secret": {Type: "string"},
|
||||||
|
"redirectURIs": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &schema{Type: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ClientPage": schema{
|
||||||
|
ID: "ClientPage",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"clients": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &schema{
|
||||||
|
Ref: "Client",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nextPageToken": {
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"User": schema{
|
||||||
|
ID: "User",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"id": {Type: "string"},
|
||||||
|
"email": {Type: "string"},
|
||||||
|
"displayName": {Type: "string"},
|
||||||
|
"emailVerified": {Type: "boolean"},
|
||||||
|
"admin": {Type: "boolean"},
|
||||||
|
"disabled": {Type: "boolean"},
|
||||||
|
"createdAt": {
|
||||||
|
Type: "string",
|
||||||
|
Format: "date-time",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"UserResponse": schema{
|
||||||
|
ID: "UserResponse",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"user": {Ref: "User"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"UsersResponse": schema{
|
||||||
|
ID: "UsersResponse",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"users": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &schema{
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nextPageToken": {Type: "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"UserCreateRequest": schema{
|
||||||
|
ID: "UserCreateRequest",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"user": {
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
"redirectURL": {Type: "string", Format: "url"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"UserCreateResponse": schema{
|
||||||
|
ID: "UserCreateResponse",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"user": {
|
||||||
|
Type: "object",
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
"resetPasswordLink": {Type: "string"},
|
||||||
|
"emailSent": {Type: "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"UserDisableRequest": schema{
|
||||||
|
ID: "UserDisableRequest",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"disable": {
|
||||||
|
Type: "boolean",
|
||||||
|
Description: "If true, disable this user, if false, enable them. No error is signaled if the user state doesn't change.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"UserDisableResponse": schema{
|
||||||
|
ID: "UserDisableResponse",
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]schema{
|
||||||
|
"ok": {Type: "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
data, err := ioutil.ReadFile(tt.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %q: read file failed %v", tt.file, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var d doc
|
||||||
|
if err := json.Unmarshal(data, &d); err != nil {
|
||||||
|
t.Errorf("case %q: failed to unmarshal %v", tt.file, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if diff := pretty.Compare(d.Schemas, tt.want); diff != "" {
|
||||||
|
t.Errorf("case %q: got did not match want: %s", tt.file, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
159
pkg/gendoc/markdown.go
Normal file
159
pkg/gendoc/markdown.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package gendoc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var funcs = template.FuncMap{
|
||||||
|
"renderJSON": func(i interface{}) string {
|
||||||
|
if s, ok := i.(Schema); ok {
|
||||||
|
return s.toJSON()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
},
|
||||||
|
"toLink": toLink,
|
||||||
|
"toCodeStr": func(code int) string {
|
||||||
|
if code == CodeDefault {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
return strconv.Itoa(code)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var markdownTmpl = template.Must(template.New("md").Funcs(funcs).Parse(`
|
||||||
|
# {{ .Title }}
|
||||||
|
|
||||||
|
{{ .Description }}
|
||||||
|
|
||||||
|
__Version:__ {{ .Version }}
|
||||||
|
|
||||||
|
## Models
|
||||||
|
|
||||||
|
{{ range $i, $model := .Models }}
|
||||||
|
### {{ $model.Name }}
|
||||||
|
|
||||||
|
{{ $model.Description }}
|
||||||
|
|
||||||
|
{{ $model | renderJSON }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
{{ range $i, $path := .Paths }}
|
||||||
|
### {{ $path.Method }} {{ $path.Path }}
|
||||||
|
|
||||||
|
> __Summary__
|
||||||
|
|
||||||
|
> {{ $path.Summary }}
|
||||||
|
|
||||||
|
> __Description__
|
||||||
|
|
||||||
|
> {{ $path.Description }}
|
||||||
|
|
||||||
|
{{ if $path.Parameters }}
|
||||||
|
> __Parameters__
|
||||||
|
|
||||||
|
> |Name|Located in|Description|Required|Type|
|
||||||
|
|:-----|:-----|:-----|:-----|:-----|
|
||||||
|
{{ range $i, $p := $path.Parameters }}| {{ $p.Name }} | {{ $p.LocatedIn }} | {{ $p.Description }} | {{ if $p.Required }}Yes{{ else }}No{{ end }} | {{ $p.Type | toLink }} |
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
> __Responses__
|
||||||
|
|
||||||
|
> |Code|Description|Type|
|
||||||
|
|:-----|:-----|:-----|
|
||||||
|
{{ range $i, $r := $path.Responses }}| {{ $r.Code | toCodeStr }} | {{ $r.Description }} | {{ $r.Type | toLink }} |
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
`))
|
||||||
|
|
||||||
|
func (doc Document) MarshalMarkdown() ([]byte, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := markdownTmpl.Execute(&b, doc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Schema) toJSON() string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteString("```\n")
|
||||||
|
m.writeJSON(&b, "", true)
|
||||||
|
b.WriteString("\n```")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var indentStr = " "
|
||||||
|
|
||||||
|
func (m Schema) writeJSON(b *bytes.Buffer, indent string, first bool) {
|
||||||
|
if m.Ref != "" {
|
||||||
|
b.WriteString(m.Ref)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
b.WriteString(indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m.Type {
|
||||||
|
case TypeArray:
|
||||||
|
b.WriteString("[")
|
||||||
|
for i, c := range m.Children {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteString(",")
|
||||||
|
}
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString(indent + indentStr)
|
||||||
|
c.writeJSON(b, indent+indentStr, false)
|
||||||
|
}
|
||||||
|
b.WriteString("\n" + indent + "]")
|
||||||
|
case TypeObject:
|
||||||
|
b.WriteString("{")
|
||||||
|
for i, c := range m.Children {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteString(",")
|
||||||
|
}
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString(indent + indentStr + c.Name + ": ")
|
||||||
|
c.writeJSON(b, indent+indentStr, false)
|
||||||
|
}
|
||||||
|
b.WriteString("\n" + indent + "}")
|
||||||
|
case TypeBool, TypeFloat, TypeInt, TypeString:
|
||||||
|
b.WriteString(m.Type)
|
||||||
|
if m.Description != "" {
|
||||||
|
b.WriteString(" // ")
|
||||||
|
b.WriteString(m.Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toAnchor(s string) string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
r := strings.NewReader(s)
|
||||||
|
for {
|
||||||
|
r, _, err := r.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case r == ' ':
|
||||||
|
b.WriteRune('-')
|
||||||
|
case 'a' <= r && r <= 'z':
|
||||||
|
b.WriteRune(r)
|
||||||
|
case 'A' <= r && r <= 'Z':
|
||||||
|
b.WriteRune(unicode.ToLower(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toLink(s string) string {
|
||||||
|
switch s {
|
||||||
|
case "string", "boolean", "integer", "":
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%s](#%s)", s, toAnchor(s))
|
||||||
|
}
|
68
pkg/gendoc/markdown_test.go
Normal file
68
pkg/gendoc/markdown_test.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package gendoc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kylelemons/godebug/diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToAnchor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
s string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"foo", "foo"},
|
||||||
|
{"foo bar", "foo-bar"},
|
||||||
|
{"POST /foo/{id}", "post-fooid"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := toAnchor(tt.s); got != tt.want {
|
||||||
|
t.Errorf("toAnchor(%q): want=%q, got=%q", tt.s, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToJSON(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
s Schema
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
s: Schema{
|
||||||
|
Name: "UsersResponse",
|
||||||
|
Type: "object",
|
||||||
|
Children: []Schema{
|
||||||
|
{
|
||||||
|
Name: "nextPageToken",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "users",
|
||||||
|
Type: "array",
|
||||||
|
Children: []Schema{
|
||||||
|
{
|
||||||
|
Ref: "User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "```" + `
|
||||||
|
{
|
||||||
|
nextPageToken: string,
|
||||||
|
users: [
|
||||||
|
User
|
||||||
|
]
|
||||||
|
}
|
||||||
|
` + "```",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := tt.s.toJSON()
|
||||||
|
if d := diff.Diff(got, tt.want); d != "" {
|
||||||
|
t.Errorf("case %d: want != got: %s", i, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
pkg/gendoc/testdata/admin.json
vendored
Normal file
102
pkg/gendoc/testdata/admin.json
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
{
|
||||||
|
"kind": "discovery#restDescription",
|
||||||
|
"discoveryVersion": "v1",
|
||||||
|
"id": "dex:v1",
|
||||||
|
"name": "adminschema",
|
||||||
|
"version": "v1",
|
||||||
|
"title": "Dex Admin API",
|
||||||
|
"description": "The Dex Admin API.",
|
||||||
|
"documentationLink": "http://github.com/coreos/dex",
|
||||||
|
"protocol": "rest",
|
||||||
|
"icons": {
|
||||||
|
"x16": "",
|
||||||
|
"x32": ""
|
||||||
|
},
|
||||||
|
"labels": [],
|
||||||
|
"baseUrl": "$ENDPOINT/api/v1/",
|
||||||
|
"basePath": "/api/v1/",
|
||||||
|
"rootUrl": "$ENDPOINT/",
|
||||||
|
"servicePath": "api/v1/",
|
||||||
|
"batchPath": "batch",
|
||||||
|
"parameters": {},
|
||||||
|
"auth": {},
|
||||||
|
"schemas": {
|
||||||
|
"Admin": {
|
||||||
|
"id": "Admin",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Admin represents an admin user within the database",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"State": {
|
||||||
|
"id": "State",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Admin represents dex data within.",
|
||||||
|
"properties": {
|
||||||
|
"AdminUserCreated": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"Admin": {
|
||||||
|
"methods": {
|
||||||
|
"Get": {
|
||||||
|
"id": "dex.admin.Admin.Get",
|
||||||
|
"description": "Retrieve information about an admin user.",
|
||||||
|
"httpMethod": "GET",
|
||||||
|
"path": "admin/{id}",
|
||||||
|
"parameters": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"location": "path"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameterOrder": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"response": {
|
||||||
|
"$ref": "Admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"Create": {
|
||||||
|
"id": "dex.admin.Admin.Create",
|
||||||
|
"description": "Create a new admin user.",
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "admin",
|
||||||
|
"request": {
|
||||||
|
"$ref": "Admin"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "Admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"State": {
|
||||||
|
"methods": {
|
||||||
|
"Get": {
|
||||||
|
"id": "dex.admin.State.Get",
|
||||||
|
"description": "Get the state of the Dex DB",
|
||||||
|
"httpMethod": "GET",
|
||||||
|
"path": "state",
|
||||||
|
"response": {
|
||||||
|
"$ref": "State"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
296
pkg/gendoc/testdata/worker.json
vendored
Normal file
296
pkg/gendoc/testdata/worker.json
vendored
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
{
|
||||||
|
"kind": "discovery#restDescription",
|
||||||
|
"discoveryVersion": "v1",
|
||||||
|
"id": "dex:v1",
|
||||||
|
"name": "workerschema",
|
||||||
|
"version": "v1",
|
||||||
|
"title": "Dex API",
|
||||||
|
"description": "The Dex REST API",
|
||||||
|
"documentationLink": "http://github.com/coreos/dex",
|
||||||
|
"protocol": "rest",
|
||||||
|
"icons": {
|
||||||
|
"x16": "",
|
||||||
|
"x32": ""
|
||||||
|
},
|
||||||
|
"labels": [],
|
||||||
|
"baseUrl": "$ENDPOINT/api/v1/",
|
||||||
|
"basePath": "/api/v1/",
|
||||||
|
"rootUrl": "$ENDPOINT/",
|
||||||
|
"servicePath": "api/v1/",
|
||||||
|
"batchPath": "batch",
|
||||||
|
"parameters": {},
|
||||||
|
"auth": {},
|
||||||
|
"schemas": {
|
||||||
|
"Error": {
|
||||||
|
"id": "Error",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"error_description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Client": {
|
||||||
|
"id": "Client",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"redirectURIs": {
|
||||||
|
"required": true,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ClientWithSecret": {
|
||||||
|
"id": "Client",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"secret": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"redirectURIs": {
|
||||||
|
"required": true,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ClientPage": {
|
||||||
|
"id": "ClientPage",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"clients": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "Client"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nextPageToken": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"User": {
|
||||||
|
"id": "User",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"displayName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserResponse": {
|
||||||
|
"id": "UserResponse",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"user": {
|
||||||
|
"$ref": "User"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UsersResponse": {
|
||||||
|
"id": "UsersResponse",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"users": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nextPageToken": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserCreateRequest": {
|
||||||
|
"id": "UserCreateRequest",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"user": {
|
||||||
|
"$ref": "User"
|
||||||
|
},
|
||||||
|
"redirectURL": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "url"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserCreateResponse": {
|
||||||
|
"id": "UserCreateResponse",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"user": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "User"
|
||||||
|
},
|
||||||
|
"resetPasswordLink": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"emailSent": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserDisableRequest": {
|
||||||
|
"id": "UserDisableRequest",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"disable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, disable this user, if false, enable them. No error is signaled if the user state doesn't change."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserDisableResponse": {
|
||||||
|
"id": "UserDisableResponse",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ok": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"Clients": {
|
||||||
|
"methods": {
|
||||||
|
"List": {
|
||||||
|
"id": "dex.Client.List",
|
||||||
|
"description": "Retrieve a page of Client objects.",
|
||||||
|
"httpMethod": "GET",
|
||||||
|
"path": "clients",
|
||||||
|
"parameters": {
|
||||||
|
"nextPageToken": {
|
||||||
|
"type": "string",
|
||||||
|
"location": "query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "ClientPage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Create": {
|
||||||
|
"id": "dex.Client.Create",
|
||||||
|
"description": "Register a new Client.",
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "clients",
|
||||||
|
"request": {
|
||||||
|
"$ref": "Client"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "ClientWithSecret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Users": {
|
||||||
|
"methods": {
|
||||||
|
"List": {
|
||||||
|
"id": "dex.User.List",
|
||||||
|
"description": "Retrieve a page of User objects.",
|
||||||
|
"httpMethod": "GET",
|
||||||
|
"path": "users",
|
||||||
|
"parameters": {
|
||||||
|
"nextPageToken": {
|
||||||
|
"type": "string",
|
||||||
|
"location": "query"
|
||||||
|
},
|
||||||
|
"maxResults": {
|
||||||
|
"type": "integer",
|
||||||
|
"location": "query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "UsersResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Get": {
|
||||||
|
"id": "dex.User.Get",
|
||||||
|
"description": "Get a single User object by id.",
|
||||||
|
"httpMethod": "GET",
|
||||||
|
"path": "users/{id}",
|
||||||
|
"parameters": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"location": "path"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameterOrder": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"response": {
|
||||||
|
"$ref": "UserResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Create": {
|
||||||
|
"id": "dex.User.Create",
|
||||||
|
"description": "Create a new User.",
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "users",
|
||||||
|
"request": {
|
||||||
|
"$ref": "UserCreateRequest"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "UserCreateResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Disable": {
|
||||||
|
"id": "dex.User.Disable",
|
||||||
|
"description": "Enable or disable a user.",
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "users/{id}/disable",
|
||||||
|
"parameters": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"location": "path"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameterOrder": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"$ref": "UserDisableRequest"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"$ref": "UserDisableResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue