Skip to content

Commit

Permalink
More openapi (#277)
Browse files Browse the repository at this point in the history
* More openapi

* Use forked openapi for extension support

* Fix example urls
  • Loading branch information
irees authored Oct 10, 2024
1 parent d9878a6 commit 7edc180
Show file tree
Hide file tree
Showing 20 changed files with 11,723 additions and 7,885 deletions.
62 changes: 60 additions & 2 deletions docs/openapi/gen.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package openapi
//go:generate go run . rest.json
package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"

oa "github.com/getkin/kin-openapi/openapi3"
"github.com/interline-io/transitland-server/server/rest"
)
Expand Down Expand Up @@ -41,11 +48,62 @@ func GenerateOpenAPI() (*oa.T, error) {
&rest.TripRequest{},
&rest.StopRequest{},
&rest.StopDepartureRequest{},
&rest.FeedVersionDownloadRequest{},
&rest.FeedDownloadLatestFeedVersionRequest{},
}
for _, handler := range handlers {
requestInfo := handler.RequestInfo()
pathOpts = append(pathOpts, oa.WithPath(requestInfo.Path, requestInfo.PathItem))
oaResponse, err := queryToOAResponses(requestInfo.Get.Query)
if err != nil {
return outdoc, err
}
getOp := requestInfo.Get.Operation
getOp.Responses = oaResponse
getOp.Description = requestInfo.Description
pathItem := &oa.PathItem{Get: getOp}
pathOpts = append(pathOpts, oa.WithPath(requestInfo.Path, pathItem))
}
outdoc.Paths = oa.NewPaths(pathOpts...)
return outdoc, nil
}

func main() {
args := os.Args
if len(args) != 2 {
exit(errors.New("output file required"))
}
outfile := args[1]

// Generate OpenAPI schema
outdoc, err := GenerateOpenAPI()
if err != nil {
exit(err)
}

// Validate output
jj, err := json.MarshalIndent(outdoc, "", " ")
if err != nil {
exit(err)
}

schema, err := oa.NewLoader().LoadFromData(jj)
if err != nil {
exit(err)
}
var validationOpts []oa.ValidationOption
if err := schema.Validate(context.Background(), validationOpts...); err != nil {
exit(err)
}

// After validation, write to file
outf, err := os.Create(outfile)
if err != nil {
exit(err)
}
outf.Write(jj)
}

func exit(err error) {
fmt.Println("Error: ", err.Error())
os.Exit(1)
}
34 changes: 0 additions & 34 deletions docs/openapi/gen_test.go

This file was deleted.

63 changes: 43 additions & 20 deletions server/rest/openapi_gql.go → docs/openapi/openapi.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package rest
package main

import (
"fmt"
Expand All @@ -12,14 +12,15 @@ import (
"github.com/vektah/gqlparser/v2/ast"
)

func queryToOAResponses(queryString string) *oa.Responses {
func queryToOAResponses(queryString string) (*oa.Responses, error) {
// Load schema
schema := gqlout.NewExecutableSchema(gqlout.Config{Resolvers: &gql.Resolver{}})
gs := schema.Schema()

// Prepare document
query, err := gqlparser.LoadQuery(schema.Schema(), queryString)
query, err := gqlparser.LoadQuery(gs, queryString)
if err != nil {
panic(err)
return nil, err
}

///////////
Expand All @@ -28,8 +29,8 @@ func queryToOAResponses(queryString string) *oa.Responses {
Properties: oa.Schemas{},
}}
for _, op := range query.Operations {
for _, sel := range op.SelectionSet {
queryRecurse(sel, responseObj.Value.Properties, 0)
for selOrder, sel := range op.SelectionSet {
queryRecurse(gs, sel, responseObj.Value.Properties, 0, selOrder)
}
}
desc := "ok"
Expand All @@ -38,7 +39,7 @@ func queryToOAResponses(queryString string) *oa.Responses {
Content: oa.NewContentWithSchemaRef(&responseObj, []string{"application/json"}),
}})
ret := oa.NewResponses(res)
return ret
return ret, nil
}

var gqlScalarToOASchema = map[string]oa.Schema{
Expand Down Expand Up @@ -107,6 +108,7 @@ type ParsedDocstring struct {
ExternalDocs []ParsedUrl
Examples []string
Enum []string
Hide bool
}

var reLinks = regexp.MustCompile(`(\[(?P<text>.+)\]\((?P<url>.+)\))`)
Expand All @@ -131,28 +133,30 @@ func ParseDocstring(v string) ParsedDocstring {
for _, e := range strings.Split(value, ",") {
ret.Enum = append(ret.Enum, strings.TrimSpace(e))
}
case "hide":
ret.Hide = true
}
}
ret.Text = strings.TrimSpace(reAnno.ReplaceAllString(v, ""))
return ret
}

func queryRecurse(recurseValue any, parentSchema oa.Schemas, level int) {
func queryRecurse(gs *ast.Schema, recurseValue any, parentSchema oa.Schemas, level int, order int) int {
schema := &oa.Schema{
Properties: oa.Schemas{},
Extensions: map[string]any{},
}
gqlType := ""
namedType := ""
isArray := false
if frag, ok := recurseValue.(*ast.FragmentSpread); ok {
for _, sel := range frag.Definition.SelectionSet {
queryRecurse(sel, schema.Properties, level)
}
schema.Title = frag.Name
schema.Description = frag.ObjectDefinition.Description
} else if field, ok := recurseValue.(*ast.Field); ok {
for _, sel := range field.SelectionSet {
queryRecurse(sel, schema.Properties, level+1)
if field, ok := recurseValue.(*ast.Field); ok {
if field.Comment != nil {
for _, c := range field.Comment.List {
pd := ParseDocstring(c.Value)
if pd.Hide {
return order
}
}
}
schema.Title = field.Name
schema.Description = field.Definition.Description
Expand All @@ -161,15 +165,32 @@ func queryRecurse(recurseValue any, parentSchema oa.Schemas, level int) {
gqlType = field.Definition.Type.NamedType
if field.Definition.Type.Elem != nil {
gqlType = field.Definition.Type.Elem.Name()

}
if gst, ok := gs.Types[field.Definition.Type.String()]; ok {
for _, ev := range gst.EnumValues {
schema.Enum = append(schema.Enum, ev.Name)
}
}
if strings.HasPrefix(field.Definition.Type.String(), "[") {
isArray = true
}
for _, sel := range field.SelectionSet {
order = queryRecurse(gs, sel, schema.Properties, level+1, order+1)
}
} else if frag, ok := recurseValue.(*ast.FragmentSpread); ok {
for _, sel := range frag.Definition.SelectionSet {
// Ugly hack to put fragments at the end of the selection set
order = queryRecurse(gs, sel, parentSchema, level, order+1)
}
return order
} else {
return
return order
}

fmt.Printf("%s %s (%s : %s)\n", strings.Repeat(" ", level*4), schema.Title, namedType, gqlType)
fmt.Printf("%s %s (%s : %s : order %d)\n", strings.Repeat(" ", level*4), schema.Title, namedType, gqlType, order)
order += 1
schema.Extensions["x-order"] = order

// Scalar types
if scalarType, ok := gqlScalarToOASchema[namedType]; ok {
Expand All @@ -179,7 +200,7 @@ func queryRecurse(recurseValue any, parentSchema oa.Schemas, level int) {
} else {
schema.Type = oa.NewObjectSchema().Type
if gqlType != "" {
schema.Extensions = map[string]any{"x-graphql-type": gqlType}
schema.Extensions["x-graphql-type"] = gqlType
}
}

Expand Down Expand Up @@ -212,12 +233,14 @@ func queryRecurse(recurseValue any, parentSchema oa.Schemas, level int) {
ExternalDocs: schema.ExternalDocs,
Enum: schema.Enum,
Items: oa.NewSchemaRef("", innerSchema),
Extensions: schema.Extensions,
}
schema = outerSchema
}

// Add to parent
parentSchema[schema.Title] = oa.NewSchemaRef("", schema)
return order
}

func parseGroups(re *regexp.Regexp, v string) []map[string]string {
Expand Down
Loading

0 comments on commit 7edc180

Please sign in to comment.