github.com/speakeasy-api/sdk-gen-config@v1.14.2/configuration.go (about)

     1  package config
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/AlekSi/pointer"
     7  	"github.com/mitchellh/mapstructure"
     8  )
     9  
    10  const (
    11  	v1      = "1.0.0"
    12  	v2      = "2.0.0"
    13  	Version = v2
    14  
    15  	GithubWritePermission = "write"
    16  
    17  	// Constants to be used as keys in the config files
    18  	Languages            = "languages"
    19  	Mode                 = "mode"
    20  	GithubAccessToken    = "github_access_token"
    21  	SpeakeasyApiKey      = "speakeasy_api_key"
    22  	SpeakeasyServerURL   = "speakeasy_server_url"
    23  	OpenAPIDocAuthHeader = "openapi_doc_auth_header"
    24  	OpenAPIDocAuthToken  = "openapi_doc_auth_token"
    25  	OpenAPIDocs          = "openapi_docs"
    26  )
    27  
    28  type OptionalPropertyRenderingOption string
    29  
    30  const (
    31  	OptionalPropertyRenderingOptionAlways      OptionalPropertyRenderingOption = "always"
    32  	OptionalPropertyRenderingOptionNever       OptionalPropertyRenderingOption = "never"
    33  	OptionalPropertyRenderingOptionWithExample OptionalPropertyRenderingOption = "withExample"
    34  )
    35  
    36  type UsageSnippets struct {
    37  	OptionalPropertyRendering OptionalPropertyRenderingOption `yaml:"optionalPropertyRendering"`
    38  	AdditionalProperties      map[string]any                  `yaml:",inline"` // Captures any additional properties that are not explicitly defined for backwards/forwards compatibility
    39  }
    40  
    41  type Fixes struct {
    42  	NameResolutionDec2023                bool           `yaml:"nameResolutionDec2023"`
    43  	ParameterOrderingFeb2024             bool           `yaml:"parameterOrderingFeb2024"`
    44  	RequestResponseComponentNamesFeb2024 bool           `yaml:"requestResponseComponentNamesFeb2024"`
    45  	AdditionalProperties                 map[string]any `yaml:",inline"` // Captures any additional properties that are not explicitly defined for backwards/forwards compatibility
    46  }
    47  
    48  type Auth struct {
    49  	OAuth2ClientCredentialsEnabled bool `yaml:"oAuth2ClientCredentialsEnabled"`
    50  }
    51  
    52  type Generation struct {
    53  	DevContainers               *DevContainers `yaml:"devContainers,omitempty"`
    54  	BaseServerURL               string         `yaml:"baseServerUrl,omitempty"`
    55  	SDKClassName                string         `yaml:"sdkClassName,omitempty"`
    56  	MaintainOpenAPIOrder        bool           `yaml:"maintainOpenAPIOrder,omitempty"`
    57  	UsageSnippets               *UsageSnippets `yaml:"usageSnippets,omitempty"`
    58  	UseClassNamesForArrayFields bool           `yaml:"useClassNamesForArrayFields,omitempty"`
    59  	Fixes                       *Fixes         `yaml:"fixes,omitempty"`
    60  	Auth                        *Auth          `yaml:"auth,omitempty"`
    61  
    62  	AdditionalProperties map[string]any `yaml:",inline"` // Captures any additional properties that are not explicitly defined for backwards/forwards compatibility
    63  }
    64  
    65  type DevContainers struct {
    66  	Enabled bool `yaml:"enabled"`
    67  	// This can be a local path or a remote URL
    68  	SchemaPath           string         `yaml:"schemaPath"`
    69  	AdditionalProperties map[string]any `yaml:",inline"` // Captures any additional properties that are not explicitly defined for backwards/forwards compatibility
    70  }
    71  
    72  type LanguageConfig struct {
    73  	Version string         `yaml:"version"`
    74  	Cfg     map[string]any `yaml:",inline"`
    75  }
    76  
    77  type SDKGenConfigField struct {
    78  	Name                  string  `yaml:"name" json:"name"`
    79  	Required              bool    `yaml:"required" json:"required"`
    80  	RequiredForPublishing *bool   `yaml:"requiredForPublishing,omitempty" json:"required_for_publishing,omitempty"`
    81  	DefaultValue          *any    `yaml:"defaultValue,omitempty" json:"default_value,omitempty"`
    82  	Description           *string `yaml:"description,omitempty" json:"description,omitempty"`
    83  	Language              *string `yaml:"language,omitempty" json:"language,omitempty"`
    84  	SecretName            *string `yaml:"secretName,omitempty" json:"secret_name,omitempty"`
    85  	ValidationRegex       *string `yaml:"validationRegex,omitempty" json:"validation_regex,omitempty"`
    86  	ValidationMessage     *string `yaml:"validationMessage,omitempty" json:"validation_message,omitempty"`
    87  	TestValue             *any    `yaml:"testValue,omitempty" json:"test_value,omitempty"`
    88  }
    89  
    90  type Configuration struct {
    91  	ConfigVersion string                    `yaml:"configVersion"`
    92  	Generation    Generation                `yaml:"generation"`
    93  	Languages     map[string]LanguageConfig `yaml:",inline"`
    94  	New           map[string]bool           `yaml:"-"`
    95  }
    96  
    97  type PublishWorkflow struct {
    98  	Name        string      `yaml:"name"`
    99  	Permissions Permissions `yaml:"permissions,omitempty"`
   100  	On          PublishOn   `yaml:"on"`
   101  	Jobs        Jobs        `yaml:"jobs"`
   102  }
   103  
   104  type PublishOn struct {
   105  	Push Push `yaml:"push"`
   106  }
   107  
   108  type Push struct {
   109  	Branches []string `yaml:"branches"`
   110  	Paths    []string `yaml:"paths"`
   111  }
   112  
   113  type GenerateWorkflow struct {
   114  	Name        string      `yaml:"name"`
   115  	Permissions Permissions `yaml:"permissions,omitempty"`
   116  	On          GenerateOn  `yaml:"on"`
   117  	Jobs        Jobs        `yaml:"jobs"`
   118  }
   119  
   120  type Permissions struct {
   121  	Checks       string `yaml:"checks,omitempty"`
   122  	Contents     string `yaml:"contents,omitempty"`
   123  	PullRequests string `yaml:"pull-requests,omitempty"`
   124  	Statuses     string `yaml:"statuses,omitempty"`
   125  }
   126  
   127  type GenerateOn struct {
   128  	WorkflowDispatch WorkflowDispatch `yaml:"workflow_dispatch"`
   129  	Schedule         []Schedule       `yaml:"schedule,omitempty"`
   130  }
   131  
   132  type Jobs struct {
   133  	Generate Job `yaml:"generate,omitempty"`
   134  	Publish  Job `yaml:"publish,omitempty"`
   135  }
   136  
   137  type Job struct {
   138  	Uses    string            `yaml:"uses"`
   139  	With    map[string]any    `yaml:"with,omitempty"`
   140  	Secrets map[string]string `yaml:"secrets,omitempty"`
   141  }
   142  
   143  type WorkflowDispatch struct {
   144  	Inputs Inputs `yaml:"inputs"`
   145  }
   146  
   147  type Schedule struct {
   148  	Cron string `yaml:"cron"`
   149  }
   150  
   151  type Inputs struct {
   152  	Force Force `yaml:"force"`
   153  }
   154  
   155  type Force struct {
   156  	Description string `yaml:"description"`
   157  	Type        string `yaml:"type"`
   158  	Default     bool   `yaml:"default"`
   159  }
   160  
   161  func GetDefaultConfig(newSDK bool, getLangDefaultFunc GetLanguageDefaultFunc, langs map[string]bool) (*Configuration, error) {
   162  	defaults := GetGenerationDefaults(newSDK)
   163  
   164  	fields := map[string]any{}
   165  	for _, field := range defaults {
   166  		if field.DefaultValue != nil {
   167  			if strings.Contains(field.Name, ".") {
   168  				parts := strings.Split(field.Name, ".")
   169  
   170  				currMap := fields
   171  
   172  				for i, part := range parts {
   173  					if i == len(parts)-1 {
   174  						currMap[part] = *field.DefaultValue
   175  					} else {
   176  						if _, ok := currMap[part]; !ok {
   177  							currMap[part] = map[string]any{}
   178  						}
   179  
   180  						currMap = currMap[part].(map[string]any)
   181  					}
   182  				}
   183  			} else {
   184  				fields[field.Name] = *field.DefaultValue
   185  			}
   186  		}
   187  	}
   188  
   189  	var genConfig Generation
   190  
   191  	d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   192  		Result:  &genConfig,
   193  		TagName: "yaml",
   194  	})
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	if err := d.Decode(fields); err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	cfg := &Configuration{
   204  		ConfigVersion: Version,
   205  		Generation:    genConfig,
   206  		Languages:     map[string]LanguageConfig{},
   207  		New:           map[string]bool{},
   208  	}
   209  
   210  	for lang, new := range langs {
   211  		langDefault := &LanguageConfig{
   212  			Version: "0.0.1",
   213  		}
   214  
   215  		if getLangDefaultFunc != nil {
   216  			var err error
   217  			langDefault, err = getLangDefaultFunc(lang, new)
   218  			if err != nil {
   219  				return nil, err
   220  			}
   221  		}
   222  
   223  		cfg.Languages[lang] = *langDefault
   224  	}
   225  
   226  	return cfg, nil
   227  }
   228  
   229  func GetGenerationDefaults(newSDK bool) []SDKGenConfigField {
   230  	return []SDKGenConfigField{
   231  		{
   232  			Name:              "baseServerURL",
   233  			Required:          false,
   234  			DefaultValue:      ptr(""),
   235  			Description:       pointer.To("The base URL of the server. This value will be used if global servers are not defined in the spec."),
   236  			ValidationRegex:   pointer.To(`^(https?):\/\/([\w\-]+\.)+\w+(\/.*)?$`),
   237  			ValidationMessage: pointer.To("Must be a valid server URL"),
   238  		},
   239  		{
   240  			Name:              "sdkClassName",
   241  			Required:          false,
   242  			DefaultValue:      ptr("SDK"),
   243  			Description:       pointer.To("Generated name of the root SDK class"),
   244  			ValidationRegex:   pointer.To(`^[\w.\-]+$`),
   245  			ValidationMessage: pointer.To("Letters, numbers, or .-_ only"),
   246  		},
   247  		{
   248  			Name:         "maintainOpenAPIOrder",
   249  			Required:     false,
   250  			DefaultValue: ptr(newSDK),
   251  			Description:  pointer.To("Maintains the order of things like parameters and fields when generating the SDK"),
   252  		},
   253  		{
   254  			Name:         "usageSnippets.optionalPropertyRendering",
   255  			Required:     false,
   256  			DefaultValue: ptr(OptionalPropertyRenderingOptionWithExample),
   257  			Description:  pointer.To("Controls how optional properties are rendered in usage snippets, by default they will be rendered when an example is present in the OpenAPI spec"),
   258  		},
   259  		{
   260  			Name:         "useClassNamesForArrayFields",
   261  			Required:     false,
   262  			DefaultValue: ptr(newSDK),
   263  			Description:  pointer.To("Use class names for array fields instead of the child's schema type"),
   264  		},
   265  		{
   266  			Name:         "fixes.nameResolutionDec2023",
   267  			Required:     false,
   268  			DefaultValue: ptr(newSDK),
   269  			Description:  pointer.To("Enables a number of breaking changes introduced in December 2023, that improve name resolution for inline schemas and reduce chances of name collisions"),
   270  		},
   271  		{
   272  			Name:         "fixes.parameterOrderingFeb2024",
   273  			Required:     false,
   274  			DefaultValue: ptr(newSDK),
   275  			Description:  pointer.To("Enables fixes to the ordering of parameters for an operation if they include multiple types of parameters (ie header, query, path) to match the order they are defined in the OpenAPI spec"),
   276  		},
   277  		{
   278  			Name:         "fixes.requestResponseComponentNamesFeb2024",
   279  			Required:     false,
   280  			DefaultValue: ptr(newSDK),
   281  			Description:  pointer.To("Enables fixes that will name inline schemas within request and response components with the component name of the parent if only one content type is defined"),
   282  		},
   283  		{
   284  			Name:         "fixes.methodSignaturesApr2024",
   285  			Required:     false,
   286  			DefaultValue: ptr(newSDK),
   287  			Description:  pointer.To("Enables fixes that will detect and mark optional request and security method arguments and order them according to optionality."),
   288  		},
   289  		{
   290  			Name:         "auth.oAuth2ClientCredentialsEnabled",
   291  			Required:     false,
   292  			DefaultValue: ptr(newSDK),
   293  			Description:  pointer.To("Enables support for OAuth2 client credentials grant type (Enterprise tier only)"),
   294  		},
   295  	}
   296  }
   297  
   298  func (c *Configuration) GetGenerationFieldsMap() (map[string]any, error) {
   299  	fields := map[string]any{}
   300  
   301  	// Yes the decoder can encode too :face_palm:
   302  	d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   303  		Result:  &fields,
   304  		TagName: "yaml",
   305  	})
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	if err := d.Decode(c.Generation); err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	return fields, nil
   315  }
   316  
   317  func ptr(a any) *any {
   318  	return &a
   319  }