podhmo / apikit

api toolkit (WIP)
MIT License
0 stars 0 forks source link

support openAPI doc #122

Open podhmo opened 2 years ago

podhmo commented 2 years ago

refs #19

⚠️ step2 is difficult

podhmo commented 2 years ago

prototype

func run() (err error) {
    emitter := emitgo.NewConfigFromRelativePath(design.ListArticle, "..").NewEmitter()
    defer emitter.EmitWith(&err)

    c := genchi.DefaultConfig()
    c.Override("db", design.NewDB)

    ////////////////////////////////////////
    rc := reflectopenapi.Config{
        SkipValidation: true,
        StrictSchema:   true,
        Extractor:      c.Resolver.UnsafeShapeExtractor(),
        IsRequiredCheckFunction: func(tag reflect.StructTag) bool {
            v, _ := strconv.ParseBool(tag.Get("required"))
            return v
        },
        Selector: &MergeParamsSelector{resolver: c.Resolver},
    }
    ////////////////////////////////////////

    var r *web.Router

    // TODO: use shape
    // TODO: share shape-extractor
    doc, err := rc.BuildDoc(context.Background(), func(m *reflectopenapi.Manager) {
        r = web.NewRouter()
        r.Group("/articles", func(r *web.Router) {
            // TODO: set tag
            r.Get("/", design.ListArticle, WithOpenAPIOperation(m))
            r.Get("/{articleId}", design.GetArticle, WithOpenAPIOperation(m))
            r.Post("/{articleId}/comments", design.PostArticleComment, WithOpenAPIOperation(m))
        })
    })
    if err != nil {
        return err
    }

    emitter.FileEmitter.Register("docs/openapi.json", emitfile.EmitFunc(func(w io.Writer) error {
        enc := json.NewEncoder(w)
        enc.SetIndent("", "  ")
        return enc.Encode(doc)
    }))

    g := c.New(emitter)
    if err := g.Generate(
        context.Background(),
        r,
        design.HTTPStatusOf,
    ); err != nil {
        return err
    }
    return nil
}

func WithOpenAPIOperation(m *reflectopenapi.Manager) web.RoutingOption {
    return func(node *web.Node, metadata *web.MetaData) {
        m.RegisterFunc(node.Value).After(func(op *openapi3.Operation) {
            m.Doc.AddOperation(metadata.Path, metadata.Method, op)
        })
    }
}

type MergeParamsSelector struct {
    resolver *resolve.Resolver
    reflectopenapi.FirstParamOutputSelector
}

func (s *MergeParamsSelector) useArglist() {
}
func (s *MergeParamsSelector) SelectInput(fn reflectshape.Function) reflectshape.Shape {
    if len(fn.Params.Values) == 0 {
        return nil
    }
    fields := reflectshape.ShapeMap{}
    tags := make([]reflect.StructTag, 0, fn.Params.Len())
    metadata := make([]reflectshape.FieldMetadata, 0, fn.Params.Len())
    for i, name := range fn.Params.Keys {
        p := fn.Params.Values[i]
        switch kind := s.resolver.DetectKind(p); kind {
        case resolve.KindIgnored, resolve.KindUnsupported, resolve.KindComponent:
            continue
        case resolve.KindData, resolve.KindPrimitive, resolve.KindPrimitivePointer:
            switch kind {
            case resolve.KindData:
                s := p.(reflectshape.Struct)
                fields.Keys = append(fields.Keys, s.Fields.Keys...)
                fields.Values = append(fields.Values, s.Fields.Values...)
                metadata = append(metadata, s.Metadata...)
                tags = append(tags, s.Tags...)
            case resolve.KindPrimitive:
                fields.Keys = append(fields.Keys, name)
                fields.Values = append(fields.Values, p)
                metadata = append(metadata, reflectshape.FieldMetadata{
                    FieldName: name,
                    Required:  true,
                })
                tags = append(tags, reflect.StructTag(`openapi:"path"`)) // todo: see path param (e.g. articleId)
            case resolve.KindPrimitivePointer:
                fields.Keys = append(fields.Keys, name)
                fields.Values = append(fields.Values, p)
                metadata = append(metadata, reflectshape.FieldMetadata{
                    FieldName: name,
                    Required:  false,
                })
                tags = append(tags, reflect.StructTag(`openapi:"query"`))
            }
        default:
            panic(fmt.Sprintf("unsupported kind %v", kind))
        }
    }

    retval := reflectshape.Struct{
        Info: &reflectshape.Info{
            Name:    "", // not ref
            Kind:    reflectshape.Kind(reflect.Struct),
            Package: fn.Info.Package,
        },
        Fields:   fields,
        Tags:     tags,
        Metadata: metadata,
    }
    retval.ResetReflectType(reflect.PtrTo(fn.GetReflectType()))
    return retval
}
podhmo commented 2 years ago

if #148 is implemented, will be able to reuse it?

podhmo commented 2 years ago

hmm