github.com/Jeffail/benthos/v3@v3.65.0/internal/docs/component.go (about)

     1  package docs
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"text/template"
     9  
    10  	"github.com/Jeffail/benthos/v3/lib/util/config"
    11  	"gopkg.in/yaml.v3"
    12  )
    13  
    14  // AnnotatedExample is an isolated example for a component.
    15  type AnnotatedExample struct {
    16  	// A title for the example.
    17  	Title string `json:"title"`
    18  
    19  	// Summary of the example.
    20  	Summary string `json:"summary"`
    21  
    22  	// A config snippet to show.
    23  	Config string `json:"config"`
    24  }
    25  
    26  // Status of a component.
    27  type Status string
    28  
    29  // Component statuses.
    30  var (
    31  	StatusStable       Status = "stable"
    32  	StatusBeta         Status = "beta"
    33  	StatusExperimental Status = "experimental"
    34  	StatusDeprecated   Status = "deprecated"
    35  )
    36  
    37  // Type of a component.
    38  type Type string
    39  
    40  // Component types.
    41  var (
    42  	TypeBuffer    Type = "buffer"
    43  	TypeCache     Type = "cache"
    44  	TypeInput     Type = "input"
    45  	TypeMetrics   Type = "metrics"
    46  	TypeOutput    Type = "output"
    47  	TypeProcessor Type = "processor"
    48  	TypeRateLimit Type = "rate_limit"
    49  	TypeTracer    Type = "tracer"
    50  )
    51  
    52  // Types returns a slice containing all component types.
    53  func Types() []Type {
    54  	return []Type{
    55  		TypeBuffer,
    56  		TypeCache,
    57  		TypeInput,
    58  		TypeMetrics,
    59  		TypeOutput,
    60  		TypeProcessor,
    61  		TypeRateLimit,
    62  		TypeTracer,
    63  	}
    64  }
    65  
    66  // ComponentSpec describes a Benthos component.
    67  type ComponentSpec struct {
    68  	// Name of the component
    69  	Name string `json:"name"`
    70  
    71  	// Type of the component (input, output, etc)
    72  	Type Type `json:"type"`
    73  
    74  	// The status of the component.
    75  	Status Status `json:"status"`
    76  
    77  	// Plugin is true for all plugin components.
    78  	Plugin bool `json:"plugin"`
    79  
    80  	// Summary of the component (in markdown, must be short).
    81  	Summary string `json:"summary,omitempty"`
    82  
    83  	// Description of the component (in markdown).
    84  	Description string `json:"description,omitempty"`
    85  
    86  	// Categories that describe the purpose of the component.
    87  	Categories []string `json:"categories"`
    88  
    89  	// Footnotes of the component (in markdown).
    90  	Footnotes string `json:"footnotes,omitempty"`
    91  
    92  	// Examples demonstrating use cases for the component.
    93  	Examples []AnnotatedExample `json:"examples,omitempty"`
    94  
    95  	// A summary of each field in the component configuration.
    96  	Config FieldSpec `json:"config"`
    97  
    98  	// Version is the Benthos version this component was introduced.
    99  	Version string `json:"version,omitempty"`
   100  }
   101  
   102  type componentContext struct {
   103  	Name               string
   104  	Type               string
   105  	FrontMatterSummary string
   106  	Summary            string
   107  	Description        string
   108  	Categories         string
   109  	Examples           []AnnotatedExample
   110  	Fields             []FieldSpecCtx
   111  	Footnotes          string
   112  	CommonConfig       string
   113  	AdvancedConfig     string
   114  	Status             string
   115  	Version            string
   116  }
   117  
   118  var componentTemplate = FieldsTemplate(true) + `---
   119  title: {{.Name}}
   120  type: {{.Type}}
   121  status: {{.Status}}
   122  {{if gt (len .FrontMatterSummary) 0 -}}
   123  description: "{{.FrontMatterSummary}}"
   124  {{end -}}
   125  {{if gt (len .Categories) 0 -}}
   126  categories: {{.Categories}}
   127  {{end -}}
   128  ---
   129  
   130  <!--
   131       THIS FILE IS AUTOGENERATED!
   132  
   133       To make changes please edit the contents of:
   134       lib/{{.Type}}/{{.Name}}.go
   135  -->
   136  
   137  import Tabs from '@theme/Tabs';
   138  import TabItem from '@theme/TabItem';
   139  
   140  {{if eq .Status "beta" -}}
   141  :::caution BETA
   142  This component is mostly stable but breaking changes could still be made outside of major version releases if a fundamental problem with the component is found.
   143  :::
   144  {{end -}}
   145  {{if eq .Status "experimental" -}}
   146  :::caution EXPERIMENTAL
   147  This component is experimental and therefore subject to change or removal outside of major version releases.
   148  :::
   149  {{end -}}
   150  {{if eq .Status "deprecated" -}}
   151  :::warning DEPRECATED
   152  This component is deprecated and will be removed in the next major version release. Please consider moving onto [alternative components](#alternatives).
   153  :::
   154  {{end -}}
   155  
   156  {{if gt (len .Summary) 0 -}}
   157  {{.Summary}}
   158  {{end -}}{{if gt (len .Version) 0}}
   159  Introduced in version {{.Version}}.
   160  {{end}}
   161  {{if eq .CommonConfig .AdvancedConfig -}}
   162  ` + "```yaml" + `
   163  # Config fields, showing default values
   164  {{.CommonConfig -}}
   165  ` + "```" + `
   166  {{else}}
   167  <Tabs defaultValue="common" values={{"{"}}[
   168    { label: 'Common', value: 'common', },
   169    { label: 'Advanced', value: 'advanced', },
   170  ]{{"}"}}>
   171  
   172  <TabItem value="common">
   173  
   174  ` + "```yaml" + `
   175  # Common config fields, showing default values
   176  {{.CommonConfig -}}
   177  ` + "```" + `
   178  
   179  </TabItem>
   180  <TabItem value="advanced">
   181  
   182  ` + "```yaml" + `
   183  # All config fields, showing default values
   184  {{.AdvancedConfig -}}
   185  ` + "```" + `
   186  
   187  </TabItem>
   188  </Tabs>
   189  {{end -}}
   190  {{if gt (len .Description) 0}}
   191  {{.Description}}
   192  {{end}}
   193  {{if and (le (len .Fields) 4) (gt (len .Fields) 0) -}}
   194  ## Fields
   195  
   196  {{template "field_docs" . -}}
   197  {{end -}}
   198  
   199  {{if gt (len .Examples) 0 -}}
   200  ## Examples
   201  
   202  <Tabs defaultValue="{{ (index .Examples 0).Title }}" values={{"{"}}[
   203  {{range $i, $example := .Examples -}}
   204    { label: '{{$example.Title}}', value: '{{$example.Title}}', },
   205  {{end -}}
   206  ]{{"}"}}>
   207  
   208  {{range $i, $example := .Examples -}}
   209  <TabItem value="{{$example.Title}}">
   210  
   211  {{if gt (len $example.Summary) 0 -}}
   212  {{$example.Summary}}
   213  {{end}}
   214  {{if gt (len $example.Config) 0 -}}
   215  ` + "```yaml" + `{{$example.Config}}` + "```" + `
   216  {{end}}
   217  </TabItem>
   218  {{end -}}
   219  </Tabs>
   220  
   221  {{end -}}
   222  
   223  {{if gt (len .Fields) 4 -}}
   224  ## Fields
   225  
   226  {{template "field_docs" . -}}
   227  {{end -}}
   228  
   229  {{if gt (len .Footnotes) 0 -}}
   230  {{.Footnotes}}
   231  {{end}}
   232  `
   233  
   234  func createOrderedConfig(t Type, rawExample interface{}, filter FieldFilter) (*yaml.Node, error) {
   235  	var newNode yaml.Node
   236  	if err := newNode.Encode(rawExample); err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	if err := SanitiseYAML(t, &newNode, SanitiseConfig{
   241  		RemoveTypeField: true,
   242  		Filter:          filter,
   243  		ForExample:      true,
   244  	}); err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	return &newNode, nil
   249  }
   250  
   251  func genExampleConfigs(t Type, nest bool, fullConfigExample interface{}) (commonConfigStr, advConfigStr string, err error) {
   252  	var advConfig, commonConfig interface{}
   253  	if advConfig, err = createOrderedConfig(t, fullConfigExample, func(f FieldSpec) bool {
   254  		return !f.IsDeprecated
   255  	}); err != nil {
   256  		panic(err)
   257  	}
   258  	if commonConfig, err = createOrderedConfig(t, fullConfigExample, func(f FieldSpec) bool {
   259  		return !f.IsAdvanced && !f.IsDeprecated
   260  	}); err != nil {
   261  		panic(err)
   262  	}
   263  
   264  	if nest {
   265  		advConfig = map[string]interface{}{string(t): advConfig}
   266  		commonConfig = map[string]interface{}{string(t): commonConfig}
   267  	}
   268  
   269  	advancedConfigBytes, err := config.MarshalYAML(advConfig)
   270  	if err != nil {
   271  		panic(err)
   272  	}
   273  	commonConfigBytes, err := config.MarshalYAML(commonConfig)
   274  	if err != nil {
   275  		panic(err)
   276  	}
   277  
   278  	return string(commonConfigBytes), string(advancedConfigBytes), nil
   279  }
   280  
   281  // AsMarkdown renders the spec of a component, along with a full configuration
   282  // example, into a markdown document.
   283  func (c *ComponentSpec) AsMarkdown(nest bool, fullConfigExample interface{}) ([]byte, error) {
   284  	if strings.Contains(c.Summary, "\n\n") {
   285  		return nil, fmt.Errorf("%v component '%v' has a summary containing empty lines", c.Type, c.Name)
   286  	}
   287  
   288  	ctx := componentContext{
   289  		Name:        c.Name,
   290  		Type:        string(c.Type),
   291  		Summary:     c.Summary,
   292  		Description: c.Description,
   293  		Examples:    c.Examples,
   294  		Footnotes:   c.Footnotes,
   295  		Status:      string(c.Status),
   296  		Version:     c.Version,
   297  	}
   298  	if ctx.Status == "" {
   299  		ctx.Status = string(StatusStable)
   300  	}
   301  
   302  	if len(c.Categories) > 0 {
   303  		cats, _ := json.Marshal(c.Categories)
   304  		ctx.Categories = string(cats)
   305  	}
   306  
   307  	var err error
   308  	if ctx.CommonConfig, ctx.AdvancedConfig, err = genExampleConfigs(c.Type, nest, fullConfigExample); err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	if len(c.Description) > 0 && c.Description[0] == '\n' {
   313  		ctx.Description = c.Description[1:]
   314  	}
   315  	if len(c.Footnotes) > 0 && c.Footnotes[0] == '\n' {
   316  		ctx.Footnotes = c.Footnotes[1:]
   317  	}
   318  
   319  	flattenedFields := c.Config.FlattenChildrenForDocs()
   320  	for _, v := range flattenedFields {
   321  		if v.Spec.Kind == KindMap {
   322  			v.Spec.Type = "object"
   323  		} else if v.Spec.Kind == KindArray {
   324  			v.Spec.Type = "array"
   325  		} else if v.Spec.Kind == Kind2DArray {
   326  			v.Spec.Type = "two-dimensional array"
   327  		}
   328  		v.Spec.Kind = KindScalar
   329  		ctx.Fields = append(ctx.Fields, v)
   330  	}
   331  
   332  	var buf bytes.Buffer
   333  	err = template.Must(template.New("component").Parse(componentTemplate)).Execute(&buf, ctx)
   334  
   335  	return buf.Bytes(), err
   336  }