Skip to content

Commit

Permalink
feat(#5): added option for external user defined plugins and able to …
Browse files Browse the repository at this point in the history
…pass options to plugins
  • Loading branch information
akhilmhdh committed Feb 2, 2023
1 parent d44c883 commit d3ea096
Show file tree
Hide file tree
Showing 7 changed files with 1,340 additions and 1,459 deletions.
77 changes: 59 additions & 18 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cli

import (
_ "embed"
"errors"
"fmt"
"log"
"os"
Expand All @@ -16,17 +15,21 @@ import (
"github.com/spf13/viper"
)

// Plugin API Design Spec Sheet
// Unique Name
// tags - string[]
// scores - Array(Object({tag:string, value: string}}))
// metadata - Array(Object({ key:string, value:string, type:string }))
type ApiCatalogRule struct {
Options map[string]any
Disable bool
}

type ApiCatalogConfig struct {
Title string
type ApiCatalogUserPlugin struct {
File string
Options map[string]any
}

var errNotAbsolute = errors.New("path is not absolute")
type ApiCatalogConfig struct {
Title string
Rules map[string]ApiCatalogRule
Plugins map[string]ApiCatalogUserPlugin
}

func Run() {
// cli flags
Expand Down Expand Up @@ -70,7 +73,7 @@ func Run() {
}

var apiSchemaFile map[string]interface{}
fd, err := fr.ReadFileAdvanced(apiURL, &apiSchemaFile)
raw, err := fr.ReadFileReturnRaw(apiURL, &apiSchemaFile)
if err != nil {
log.Fatal("Fail to read file\n", err)
}
Expand All @@ -81,7 +84,7 @@ func Run() {
switch apiType {
case "openapi":
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData(fd.Raw)
doc, err := loader.LoadFromData(raw)
if err != nil {
log.Fatal(err)
}
Expand All @@ -94,6 +97,11 @@ func Run() {
}

for rule, p := range pluginManager.Rules {
userRuleCfg, ok := config.Rules[rule]
if ok && userRuleCfg.Disable {
continue
}

// read original code
rawCode, err := pluginManager.ReadPluginCode(p.File)
if err != nil {
Expand All @@ -118,25 +126,58 @@ func Run() {
}

// execute the code
err = cmp.Run(code, runCfg)
err = cmp.Run(code, runCfg, userRuleCfg.Options)
if err != nil {
log.Fatal("Error in program: ", err)
}
}

scores := rm.GetTotalScore()
// run user defined plugins
for rule, p := range config.Plugins {
// read original code
rawCode, err := pluginManager.ReadPluginCode(p.File)
if err != nil {
log.Fatal("Failed to : ", err)
}
// babel transpile
code, err := cmp.Transform(rawCode)
if err != nil {
log.Fatal("Failed to : ", err)
}

fmt.Println("-----------------------------------------")
for _, score := range scores {
fmt.Printf("Category: %s, Score: %f \n", score.Category, score.Value)
// creating config for each rule because we also want rule name of each score and report setter
runCfg := &compiler.RunConfig{
Type: apiType,
ApiSchema: apiSchemaFile,
SetScore: func(category string, score float32) {
rm.SetScore(rule, reportmanager.Score{Category: category, Value: score})
},
Report: func(body *reportmanager.ReportDef) {
rm.PushReport(rule, *body)
},
}

// execute the code
err = cmp.Run(code, runCfg, p.Options)
if err != nil {
log.Fatal("Error in program: ", err)
}
}

for _, r := range rm {
for rule, r := range rm {
for _, report := range r.Reports {
fmt.Println("-----------------------------------------")
fmt.Printf("Method: %s\n Path: %s\n Message: %s\n", report.Method, report.Path, report.Message)
fmt.Printf("Rule: %s \nMethod: %s\nPath: %s\nMessage: %s\n", rule, report.Method, report.Path, report.Message)
}
}

scores := rm.GetTotalScore()

fmt.Println("-----------------------------------------")
for _, score := range scores {
fmt.Printf("Category: %s, Score: %f \n", score.Category, score.Value)
}

},
}

Expand Down
4 changes: 2 additions & 2 deletions internal/cli/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ type RunConfig struct {
Report func(body *reportmanager.ReportDef) `json:"report"`
}

func (c *Compiler) Run(pgm *goja.Program, cfg *RunConfig) error {
func (c *Compiler) Run(pgm *goja.Program, cfg *RunConfig, ruleOpt map[string]any) error {
v, err := c.babel.runtime.RunProgram(pgm)
if err != nil {
return err
Expand All @@ -143,7 +143,7 @@ func (c *Compiler) Run(pgm *goja.Program, cfg *RunConfig) error {
if !ok {
return fmt.Errorf("failed to get exports")
}
call(goja.Undefined(), c.babel.runtime.ToValue(cfg))
call(goja.Undefined(), c.babel.runtime.ToValue(cfg), c.babel.runtime.ToValue(ruleOpt))

return nil
}
96 changes: 58 additions & 38 deletions internal/cli/filereader/file_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,32 @@ func urlFromFilePath(path string) (*url.URL, error) {
}, nil
}

func (fr *FileReader) ReadFile(location string, data any) error {
_, err := fr.ReadFileAdvanced(location, data)
return err
}
func (fr *FileReader) ParseFile(raw []byte, data any, ext string) error {
switch ext {
case "json":
if err := json.Unmarshal(raw, data); err != nil {
return err
}
case "yaml":
if err := yaml.Unmarshal(raw, data); err != nil {
return err
}
case "yml":
if err := yaml.Unmarshal(raw, data); err != nil {
return err
}
case "toml":
if err := toml.Unmarshal(raw, data); err != nil {
return err
}
default:
return ErrExtNotSupported
}

type FileData struct {
Ext string
Raw []byte
return nil
}

// to read a file and parse it
// also return various other data like raw buffer, extension etc
func (fr *FileReader) ReadFileAdvanced(location string, data any) (*FileData, error) {
if reflect.ValueOf(data).Kind() != reflect.Ptr {
return nil, errors.New("data must be a pointer")
}

func (fr *FileReader) ReadIntoRawBytes(location string) ([]byte, error) {
// if location is not url convert to proper url with file:// format
// We use golang http client to get files both in system and from web
url, err := url.ParseRequestURI(location)
Expand All @@ -130,34 +139,45 @@ func (fr *FileReader) ReadFileAdvanced(location string, data any) (*FileData, er
return nil, fmt.Errorf("failed to get the file in %s - status code %d", location, resp.StatusCode)
}

fd := &FileData{}
fd.Raw, err = ioutil.ReadAll(resp.Body)
return ioutil.ReadAll(resp.Body)
}

// to read a file and parse it
func (fr *FileReader) ReadFile(location string, data any) error {
if reflect.ValueOf(data).Kind() != reflect.Ptr {
return errors.New("data must be a pointer")
}

raw, err := fr.ReadIntoRawBytes(location)
if err != nil {
return nil, err
return err
}

fd.Ext = filepath.Ext(location)[1:] // .json -> json
ext := filepath.Ext(location)[1:] // .json -> json

switch fd.Ext {
case "json":
if err = json.Unmarshal(fd.Raw, data); err != nil {
return nil, err
}
case "yaml":
if err = yaml.Unmarshal(fd.Raw, data); err != nil {
return nil, err
}
case "yml":
if err = yaml.Unmarshal(fd.Raw, data); err != nil {
return nil, err
}
case "toml":
if err = toml.Unmarshal(fd.Raw, data); err != nil {
return nil, err
}
default:
return nil, ErrExtNotSupported
if err := fr.ParseFile(raw, data, ext); err != nil {
return err
}
return nil
}

// this is just for a special case in whic swagger validation requires raw buffer
// for all other usecases use ReadFile to get parsed go structure
// or the GetRaw for getting raw bytes data
func (fr *FileReader) ReadFileReturnRaw(location string, data any) ([]byte, error) {
if reflect.ValueOf(data).Kind() != reflect.Ptr {
return nil, errors.New("data must be a pointer")
}

raw, err := fr.ReadIntoRawBytes(location)
if err != nil {
return nil, err
}

ext := filepath.Ext(location)[1:] // .json -> json
if err := fr.ParseFile(raw, data, ext); err != nil {
return nil, err
}

return fd, nil
return raw, nil
}
3 changes: 2 additions & 1 deletion internal/cli/pluginmanager/plugin_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type Reader interface {
// data contains the container for which data will be loaded
ReadFile(location string, data any) error
ReadIntoRawBytes(location string) ([]byte, error)
}

// Builtin will packaged as zip
Expand Down Expand Up @@ -88,7 +89,7 @@ func (p *PluginManager) LoadBuiltinPlugin() error {
}

func (p *PluginManager) ReadPluginCode(path string) (string, error) {
data, err := os.ReadFile(path)
data, err := p.Reader.ReadIntoRawBytes(path)
if err != nil {
return "", err
}
Expand Down
Loading

0 comments on commit d3ea096

Please sign in to comment.