From 7f880c1c3826096f52aabc34c633c0de43fc662c Mon Sep 17 00:00:00 2001 From: amyxia Date: Fri, 15 Sep 2023 10:22:09 +0800 Subject: [PATCH] feat: add export openapi API (#160) add API: export openapi from kcl package --- pkg/tools/gen/gendoc.go | 36 +-------- pkg/tools/gen/genopenapi.go | 73 +++++++++++++++---- pkg/tools/gen/genopenapi_test.go | 73 +++++++++++++++++++ .../gen/testdata/oai/k8s/apps/deployment.k | 5 ++ pkg/tools/gen/testdata/oai/k8s/core/podSpec.k | 2 + pkg/tools/gen/testdata/oai/k8s/kcl.mod | 0 6 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 pkg/tools/gen/genopenapi_test.go create mode 100644 pkg/tools/gen/testdata/oai/k8s/apps/deployment.k create mode 100644 pkg/tools/gen/testdata/oai/k8s/core/podSpec.k create mode 100644 pkg/tools/gen/testdata/oai/k8s/kcl.mod diff --git a/pkg/tools/gen/gendoc.go b/pkg/tools/gen/gendoc.go index 9010b23a..aba7d555 100644 --- a/pkg/tools/gen/gendoc.go +++ b/pkg/tools/gen/gendoc.go @@ -11,8 +11,6 @@ import ( "sort" "strings" "text/template" - - kpm "kcl-lang.io/kpm/pkg/api" ) //go:embed templates/doc/schemaDoc.gotmpl @@ -244,12 +242,11 @@ func (pkg *KclPackage) getIndexContent(level int, indentation string, pkgPath st } func (g *GenContext) renderPackage(pkg *KclPackage, parentDir string) error { - // render the package's index.md page - //fmt.Println(fmt.Sprintf("creating %s/index.md", parentDir)) pkgName := pkg.Name if pkg.Name == "" { pkgName = "main" } + fmt.Println(fmt.Sprintf("generating doc for package %s", pkgName)) indexFileName := fmt.Sprintf("%s.%s", pkgName, g.Format) var contentBuf bytes.Buffer err := g.Template.ExecuteTemplate(&contentBuf, "packageDoc", struct { @@ -398,11 +395,7 @@ func (opts *GenOpts) ValidateComplete() (*GenContext, error) { // GenDoc generate document files from KCL source files func (g *GenContext) GenDoc() error { - pkg, err := kpm.GetKclPackage(g.PackagePath) - if err != nil { - return fmt.Errorf("filePath is not a KCL package: %s", err) - } - spec, err := g.getSwagger2Spec(pkg) + spec, err := KclPackageToSwaggerV2Spec(g.PackagePath) if err != nil { return err } @@ -412,28 +405,3 @@ func (g *GenContext) GenDoc() error { } return nil } - -func (g *GenContext) getSwagger2Spec(pkg *kpm.KclPackage) (*SwaggerV2Spec, error) { - spec := &SwaggerV2Spec{ - Swagger: "2.0", - Definitions: make(map[string]*KclOpenAPIType), - Info: SpecInfo{ - Title: pkg.GetPkgName(), - Version: pkg.GetVersion(), - }, - } - pkgMapping, err := pkg.GetAllSchemaTypeMapping() - if err != nil { - return spec, err - } - // package path -> package - for packagePath, p := range pkgMapping { - // schema name -> schema type - for _, t := range p { - id := SchemaId(packagePath, t.KclType) - spec.Definitions[id] = GetKclOpenAPIType(packagePath, t.KclType, false) - fmt.Println(fmt.Sprintf("generate docs for schema %s", id)) - } - } - return spec, nil -} diff --git a/pkg/tools/gen/genopenapi.go b/pkg/tools/gen/genopenapi.go index 3a4ee0c8..b4ed95e9 100644 --- a/pkg/tools/gen/genopenapi.go +++ b/pkg/tools/gen/genopenapi.go @@ -2,6 +2,8 @@ package gen import ( "fmt" + kpm "kcl-lang.io/kpm/pkg/api" + "os" "path/filepath" "strings" @@ -9,6 +11,47 @@ import ( kcl "kcl-lang.io/kcl-go" ) +// ExportSwaggerV2Spec export swagger v2 spec of a kcl package +func ExportSwaggerV2Spec(pkgPath string) (string, error) { + spec, err := KclPackageToSwaggerV2Spec(pkgPath) + if err != nil { + return "", err + } + return jsonString(spec), nil +} + +// KclPackageToSwaggerV2Spec extracts the swagger v2 representation of a kcl package +func KclPackageToSwaggerV2Spec(pkgPath string) (*SwaggerV2Spec, error) { + pkg, err := kpm.GetKclPackage(pkgPath) + if err != nil { + return nil, fmt.Errorf("filePath is not a KCL package: %s", err) + } + + spec := &SwaggerV2Spec{ + Swagger: "2.0", + Definitions: make(map[string]*KclOpenAPIType), + Paths: map[string]interface{}{}, + Info: SpecInfo{ + Title: pkg.GetPkgName(), + Version: pkg.GetVersion(), + }, + } + pkgMapping, err := pkg.GetAllSchemaTypeMapping() + if err != nil { + return spec, err + } + // package path -> package + for packagePath, p := range pkgMapping { + // schema name -> schema type + for _, t := range p { + id := SchemaId(packagePath, t.KclType) + spec.Definitions[id] = GetKclOpenAPIType(packagePath, t.KclType, false) + fmt.Println(fmt.Sprintf("exporting openAPI spec from schema %s", id)) + } + } + return spec, nil +} + // SwaggerV2Spec defines KCL OpenAPI Spec based on Swagger v2.0 type SwaggerV2Spec struct { Definitions map[string]*KclOpenAPIType `json:"definitions"` @@ -21,7 +64,7 @@ type SwaggerV2Spec struct { type SpecInfo struct { Title string `json:"title"` Version string `json:"version"` - Description string `json:"description"` + Description string `json:"description,omitempty"` } // KclOpenAPIType defines the KCL representation of SchemaObject field in Swagger v2.0. @@ -58,20 +101,20 @@ type SpecInfo struct { └───────────────────────┴───────────────────────────────────────────────────────────────────────────────┘ */ type KclOpenAPIType struct { - Type SwaggerTypeName // object, string, array, integer, number, bool - Format TypeFormat // type format - Default string // default value - Enum []string // enum values - ReadOnly bool // readonly - Description string // description - Properties map[string]*KclOpenAPIType // schema properties - Required []string // list of required schema property names - Items *KclOpenAPIType // list item type - AdditionalProperties *KclOpenAPIType // dict value type - Examples map[string]KclExample // examples - ExternalDocs string // externalDocs - KclExtensions *KclExtensions // x-kcl- extensions - Ref string // reference to schema path + Type SwaggerTypeName `json:"type,omitempty"` // object, string, array, integer, number, bool + Format TypeFormat `json:"format,omitempty"` // type format + Default string `json:"default,omitempty"` // default value + Enum []string `json:"enum,omitempty"` // enum values + ReadOnly bool `json:"readOnly,omitempty"` // readonly + Description string `json:"description,omitempty"` // description + Properties map[string]*KclOpenAPIType `json:"properties,omitempty"` // schema properties + Required []string `json:"required,omitempty"` // list of required schema property names + Items *KclOpenAPIType `json:"items,omitempty"` // list item type + AdditionalProperties *KclOpenAPIType `json:"additionalProperties,omitempty"` // dict value type + Examples map[string]KclExample `json:"examples,omitempty"` // examples + ExternalDocs string `json:"externalDocs,omitempty"` // externalDocs + Ref string `json:"ref,omitempty"` // reference to schema path + *KclExtensions // x-kcl- extensions } // SwaggerTypeName defines possible values of "type" field in Swagger v2.0 spec diff --git a/pkg/tools/gen/genopenapi_test.go b/pkg/tools/gen/genopenapi_test.go new file mode 100644 index 00000000..5396e568 --- /dev/null +++ b/pkg/tools/gen/genopenapi_test.go @@ -0,0 +1,73 @@ +package gen + +import ( + assert2 "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestExportSwaggerV2Spec(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatal("get work directory failed") + } + pkgPath := filepath.Join(cwd, "testdata", "doc", "k8s") + got, err := ExportSwaggerV2Spec(pkgPath) + if err != nil { + t.Fatal(err) + } + + expect := `{ + "definitions": { + "apps.Deployment": { + "type": "object", + "properties": { + "metadata": { + "type": "string" + }, + "podSpec": { + "type": "object" + } + }, + "required": [ + "metadata", + "podSpec" + ], + "x-kcl-type": { + "type": "Deployment", + "import": { + "package": "apps", + "alias": "deployment.k" + } + } + }, + "core.PodSpec": { + "type": "object", + "properties": { + "image": { + "type": "string" + } + }, + "required": [ + "image" + ], + "x-kcl-type": { + "type": "PodSpec", + "import": { + "package": "core", + "alias": "podSpec.k" + } + } + } + }, + "paths": {}, + "swagger": "2.0", + "info": { + "title": "", + "version": "" + } +}` + + assert2.Equal(t, expect, got) +} diff --git a/pkg/tools/gen/testdata/oai/k8s/apps/deployment.k b/pkg/tools/gen/testdata/oai/k8s/apps/deployment.k new file mode 100644 index 00000000..6afa7618 --- /dev/null +++ b/pkg/tools/gen/testdata/oai/k8s/apps/deployment.k @@ -0,0 +1,5 @@ +import k8s.core + +schema Deployment: + metadata: str + podSpec: core.PodSpec diff --git a/pkg/tools/gen/testdata/oai/k8s/core/podSpec.k b/pkg/tools/gen/testdata/oai/k8s/core/podSpec.k new file mode 100644 index 00000000..00e00e23 --- /dev/null +++ b/pkg/tools/gen/testdata/oai/k8s/core/podSpec.k @@ -0,0 +1,2 @@ +schema PodSpec: + image: str diff --git a/pkg/tools/gen/testdata/oai/k8s/kcl.mod b/pkg/tools/gen/testdata/oai/k8s/kcl.mod new file mode 100644 index 00000000..e69de29b