From 4c3947ac655319831d475eebaa25987752ad7e53 Mon Sep 17 00:00:00 2001 From: Josh Carp Date: Wed, 14 Jan 2026 13:09:36 -0500 Subject: [PATCH] Bump golangci. We're using an older version of golang-ci. This patch updates to latest, adds linting and formatting for long lines, and disables the errcheck linter due to noise. We also replace the previous `make fmt` target, which ran a linter rather than a formatter. --- .golangci.yml | 28 +++ Makefile | 12 +- internal/generate/main.go | 5 +- internal/generate/paths.go | 80 +++++++-- internal/generate/responses_test.go | 5 +- internal/generate/types.go | 107 +++++++++--- internal/generate/types_test.go | 258 ++++++++++++++++++++++------ internal/generate/utils.go | 12 +- oxide/errors.go | 14 +- oxide/lib.go | 21 ++- oxide/lib_test.go | 43 +++-- oxide/utils.go | 4 +- 12 files changed, 466 insertions(+), 123 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..70fa234 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,28 @@ +version: "2" + +linters: + default: standard + enable: + - godoclint + disable: + - errcheck + settings: + godoclint: + default-rule-set: basic + rules: + - max-len + options: + max-len: + length: 100 + +formatters: + enable: + - gofmt + - golines + settings: + golines: + max-len: 100 + shorten-comments: true + +run: + timeout: 5m diff --git a/Makefile b/Makefile index 627d548..5a01cf4 100644 --- a/Makefile +++ b/Makefile @@ -35,11 +35,9 @@ $(NAME): $(wildcard *.go) $(wildcard */*.go) all: generate test fmt lint staticcheck vet ## Runs a fmt, lint, test, staticcheck, and vet. .PHONY: fmt -fmt: ## Verifies all files have been `gofmt`ed. - @ echo "+ Verifying all files have been gofmt-ed..." - @if [[ ! -z "$(shell gofmt -s -d . | grep -v -e internal/generate/test_generated -e internal/generate/test_utils | tee /dev/stderr)" ]]; then \ - exit 1; \ - fi +fmt: tools ## Formats Go code including long line wrapping. + @ echo "+ Formatting Go code..." + @ $(GOBIN)/golangci-lint fmt .PHONY: fmt-md fmt-md: ## Formats markdown files with prettier. @@ -49,7 +47,7 @@ fmt-md: ## Formats markdown files with prettier. .PHONY: lint lint: tools ## Verifies `golangci-lint` passes. @ echo "+ Running Go linters..." - @ $(GOBIN)/golangci-lint run -E gofmt + @ $(GOBIN)/golangci-lint run .PHONY: test test: ## Runs the go tests. @@ -90,7 +88,7 @@ help: # want to run the linters or generate the SDK. VERSION_DIR:=$(GOBIN)/versions VERSION_GOIMPORTS:=v0.33.0 -VERSION_GOLANGCILINT:=v1.64.8 +VERSION_GOLANGCILINT:=v2.8.0 VERSION_STATICCHECK:=2025.1.1 VERSION_WHATSIT:=053446d diff --git a/internal/generate/main.go b/internal/generate/main.go index 5b9604a..ccbf08f 100644 --- a/internal/generate/main.go +++ b/internal/generate/main.go @@ -135,7 +135,10 @@ func loadAPIFromFile(file string) (*openapi3.T, error) { // symbolic link target to get the versioned filename, then construct the URL // to the actual versioned specification. func getOpenAPISpecURL(omicronVersion string) (*url.URL, error) { - rawURL := fmt.Sprintf("https://raw.githubusercontent.com/oxidecomputer/omicron/%s", omicronVersion) + rawURL := fmt.Sprintf( + "https://raw.githubusercontent.com/oxidecomputer/omicron/%s", + omicronVersion, + ) baseURL, err := url.Parse(rawURL) if err != nil { return nil, fmt.Errorf("error parsing base url %q: %w", rawURL, err) diff --git a/internal/generate/paths.go b/internal/generate/paths.go index 58562ee..cf0c9b0 100644 --- a/internal/generate/paths.go +++ b/internal/generate/paths.go @@ -124,7 +124,14 @@ func buildPath(f *os.File, spec *openapi3.T, path string, p *openapi3.PathItem) return nil } -func buildMethod(f *os.File, spec *openapi3.T, method string, path string, o *openapi3.Operation, isGetAllPages bool) error { +func buildMethod( + f *os.File, + spec *openapi3.T, + method string, + path string, + o *openapi3.Operation, + isGetAllPages bool, +) error { respType, pagedRespType, err := getSuccessResponseType(o, isGetAllPages) if err != nil { return err @@ -141,7 +148,10 @@ func buildMethod(f *os.File, spec *openapi3.T, method string, path string, o *op } if o.Tags[0] == "console-auth" { - fmt.Printf("[WARN] TODO: skipping operation %q, since it is for console authentication\n", o.OperationID) + fmt.Printf( + "[WARN] TODO: skipping operation %q, since it is for console authentication\n", + o.OperationID, + ) return nil } @@ -247,7 +257,11 @@ func getSuccessResponseType(o *openapi3.Operation, isGetAllPages bool) (string, } if response.Ref != "" { - fmt.Printf("[WARN] TODO: skipping response for %q, since it is a reference: %q\n", name, response.Ref) + fmt.Printf( + "[WARN] TODO: skipping response for %q, since it is a reference: %q\n", + name, + response.Ref, + ) continue } @@ -258,7 +272,11 @@ func getSuccessResponseType(o *openapi3.Operation, isGetAllPages bool) (string, if items, ok := content.Schema.Value.Properties["items"]; ok { getAllPagesType = convertToValidGoType("", "", items) } else { - fmt.Printf("[WARN] TODO: skipping response for %q, since it is a get all pages response and has no `items` property:\n%#v\n", o.OperationID, content.Schema.Value.Properties) + fmt.Printf( + "[WARN] TODO: skipping response for %q, since it is a get all pages response and has no `items` property:\n%#v\n", + o.OperationID, + content.Schema.Value.Properties, + ) return "", "", nil } } @@ -267,7 +285,10 @@ func getSuccessResponseType(o *openapi3.Operation, isGetAllPages bool) (string, } if content.Schema.Value.Type.Is("array") { - return fmt.Sprintf("[]%s", getReferenceSchema(content.Schema.Value.Items)), getAllPagesType, nil + return fmt.Sprintf( + "[]%s", + getReferenceSchema(content.Schema.Value.Items), + ), getAllPagesType, nil } return fmt.Sprintf("%sResponse", strcase.ToCamel(o.OperationID)), getAllPagesType, nil @@ -279,8 +300,8 @@ func getSuccessResponseType(o *openapi3.Operation, isGetAllPages bool) (string, // cleanPath returns the path as a function we can use for a go template. func cleanPath(path string) string { - path = strings.Replace(path, "{", "{{.", -1) - return strings.Replace(path, "}", "}}", -1) + path = strings.ReplaceAll(path, "{", "{{.") + return strings.ReplaceAll(path, "}", "}}") } // splitDocString inserts newlines into doc comments at approximately 100 character intervals. @@ -315,27 +336,42 @@ func writeTpl(f *os.File, config methodTemplate) error { var err error if config.IsListAll { - t, err = template.ParseFiles("./templates/listall_method.go.tpl", "./templates/description.go.tpl") + t, err = template.ParseFiles( + "./templates/listall_method.go.tpl", + "./templates/description.go.tpl", + ) if err != nil { return err } } else if config.ResponseType == "" && config.HasBody { - t, err = template.ParseFiles("./templates/no_resptype_body_method.go.tpl", "./templates/description.go.tpl") + t, err = template.ParseFiles( + "./templates/no_resptype_body_method.go.tpl", + "./templates/description.go.tpl", + ) if err != nil { return err } } else if config.ResponseType == "" { - t, err = template.ParseFiles("./templates/no_resptype_method.go.tpl", "./templates/description.go.tpl") + t, err = template.ParseFiles( + "./templates/no_resptype_method.go.tpl", + "./templates/description.go.tpl", + ) if err != nil { return err } } else if config.HasBody { - t, err = template.ParseFiles("./templates/resptype_body_method.go.tpl", "./templates/description.go.tpl") + t, err = template.ParseFiles( + "./templates/resptype_body_method.go.tpl", + "./templates/description.go.tpl", + ) if err != nil { return err } } else { - t, err = template.ParseFiles("./templates/resptype_method.go.tpl", "./templates/description.go.tpl") + t, err = template.ParseFiles( + "./templates/resptype_method.go.tpl", + "./templates/description.go.tpl", + ) if err != nil { return err } @@ -349,7 +385,10 @@ func writeTpl(f *os.File, config methodTemplate) error { return nil } -func buildPathOrQueryParams(paramType string, params map[string]*openapi3.Parameter) ([]string, error) { +func buildPathOrQueryParams( + paramType string, + params map[string]*openapi3.Parameter, +) ([]string, error) { pathParams := make([]string, 0) if paramType != "query" && paramType != "path" { return nil, errors.New("paramType must be one of 'query' or 'path'") @@ -376,11 +415,17 @@ func buildPathOrQueryParams(paramType string, params map[string]*openapi3.Parame case "bool": pathParams = append(pathParams, fmt.Sprintf("%q: strconv.FormatBool(%s),", name, n)) case "*bool": - pathParams = append(pathParams, fmt.Sprintf("%q: strconv.FormatBool(*%s),", name, n)) + pathParams = append( + pathParams, + fmt.Sprintf("%q: strconv.FormatBool(*%s),", name, n), + ) case "*int": pathParams = append(pathParams, fmt.Sprintf("%q: PointerIntToStr(%s),", name, n)) case "*time.Time": - pathParams = append(pathParams, fmt.Sprintf("%q: %s.Format(time.RFC3339),", name, n)) + pathParams = append( + pathParams, + fmt.Sprintf("%q: %s.Format(time.RFC3339),", name, n), + ) default: pathParams = append(pathParams, fmt.Sprintf("%q: string(%s),", name, n)) } @@ -399,7 +444,10 @@ func buildParams(operation *openapi3.Operation, opID string) paramsInfo { for _, p := range operation.Parameters { if p.Ref != "" { - fmt.Printf("[WARN] TODO: skipping parameter for %q, since it is a reference\n", p.Value.Name) + fmt.Printf( + "[WARN] TODO: skipping parameter for %q, since it is a reference\n", + p.Value.Name, + ) continue } diff --git a/internal/generate/responses_test.go b/internal/generate/responses_test.go index c259c3e..143f19e 100644 --- a/internal/generate/responses_test.go +++ b/internal/generate/responses_test.go @@ -57,7 +57,10 @@ func Test_generateResponses(t *testing.T) { return } - if err := compareFiles("test_utils/responses_output_expected", tt.args.file); err != nil { + if err := compareFiles( + "test_utils/responses_output_expected", + tt.args.file, + ); err != nil { t.Error(err) } }) diff --git a/internal/generate/types.go b/internal/generate/types.go index faf05c0..0a11c49 100644 --- a/internal/generate/types.go +++ b/internal/generate/types.go @@ -17,9 +17,8 @@ import ( "github.com/iancoleman/strcase" ) -// TODO: Find a better way to deal with enum types -// For now they are being collected to make sure they -// are not duplicated in createStringEnum() +// TODO: Find a better way to deal with enum types. For now they are being collected to make sure +// they are not duplicated in createStringEnum() var collectEnumStringTypes = enumStringTypes() func enumStringTypes() map[string][]string { @@ -76,7 +75,8 @@ type TypeField struct { MarshalKey string Required bool - // FallbackDescription generates a generic description for the field when the Schema doesn't have one. + // FallbackDescription generates a generic description for the field when the Schema doesn't + // have one. // TODO: Drop this, since generated descriptions don't contain useful information. FallbackDescription bool @@ -237,7 +237,10 @@ func constructParamTypes(paths map[string]*openapi3.PathItem) []TypeTemplate { for _, field := range values { str, ok := field.(string) if ok { - requiredFields = requiredFields + fmt.Sprintf("\n// - %v", strcase.ToCamel(str)) + requiredFields = requiredFields + fmt.Sprintf( + "\n// - %v", + strcase.ToCamel(str), + ) } } } @@ -256,7 +259,10 @@ func constructParamTypes(paths map[string]*openapi3.PathItem) []TypeTemplate { fields := make([]TypeField, 0) for _, p := range o.Parameters { if p.Ref != "" { - fmt.Printf("[WARN] TODO: skipping parameter for %q, since it is a reference\n", p.Value.Name) + fmt.Printf( + "[WARN] TODO: skipping parameter for %q, since it is a reference\n", + p.Value.Name, + ) continue } @@ -424,7 +430,10 @@ func constructEnums(enumStrCollection map[string][]string) []EnumTemplate { sort.Strings(enums) for _, enum := range enums { // Most likely, the enum values are strings. - enumItems = enumItems + fmt.Sprintf("\t%s,\n", strcase.ToCamel(fmt.Sprintf("%s_%s", name, enum))) + enumItems = enumItems + fmt.Sprintf( + "\t%s,\n", + strcase.ToCamel(fmt.Sprintf("%s_%s", name, enum)), + ) } if enumItems == "" { @@ -448,7 +457,12 @@ func constructEnums(enumStrCollection map[string][]string) []EnumTemplate { } // writeTypes iterates over the templates, constructs the different types and writes to file. -func writeTypes(f *os.File, typeCollection []TypeTemplate, typeValidationCollection []ValidationTemplate, enumCollection []EnumTemplate) { +func writeTypes( + f *os.File, + typeCollection []TypeTemplate, + typeValidationCollection []ValidationTemplate, + enumCollection []EnumTemplate, +) { for _, tt := range typeCollection { fmt.Fprint(f, tt.Render()) } @@ -465,7 +479,11 @@ func writeTypes(f *os.File, typeCollection []TypeTemplate, typeValidationCollect // populateTypeTemplates populates the template of a type definition for the given schema. // The additional parameter is only used as a suffix for the type name. // This is mostly for oneOf types. -func populateTypeTemplates(name string, s *openapi3.Schema, enumFieldName string) ([]TypeTemplate, []EnumTemplate) { +func populateTypeTemplates( + name string, + s *openapi3.Schema, + enumFieldName string, +) ([]TypeTemplate, []EnumTemplate) { typeName := name // Type name will change for each enum type @@ -481,7 +499,9 @@ func populateTypeTemplates(name string, s *openapi3.Schema, enumFieldName string if slices.Contains(emptyTypes(), name) { bgpOT := getObjectType(s) if bgpOT != "" { - panic("[ERROR] " + name + " is no longer an empty type. Remove workaround in exceptions.go") + panic( + "[ERROR] " + name + " is no longer an empty type. Remove workaround in exceptions.go", + ) } s.Type = &openapi3.Types{"string"} } @@ -509,14 +529,22 @@ func populateTypeTemplates(name string, s *openapi3.Schema, enumFieldName string for _, k := range properties { v := s.Properties[k] if isLocalEnum(v) { - tt, et := populateTypeTemplates(fmt.Sprintf("%s%s", name, strcase.ToCamel(k)), v.Value, "") + tt, et := populateTypeTemplates( + fmt.Sprintf("%s%s", name, strcase.ToCamel(k)), + v.Value, + "", + ) types = append(types, tt...) enumTypes = append(enumTypes, et...) } // TODO: So far this code is never hit with the current openapi spec if isLocalObject(v) { - tt, et := populateTypeTemplates(fmt.Sprintf("%s%s", name, strcase.ToCamel(k)), v.Value, "") + tt, et := populateTypeTemplates( + fmt.Sprintf("%s%s", name, strcase.ToCamel(k)), + v.Value, + "", + ) types = append(types, tt...) enumTypes = append(enumTypes, et...) } @@ -577,7 +605,8 @@ func createTypeObject(schema *openapi3.Schema, name, typeName, description strin } // When `additionalProperties` is set, the type will be a map. - // See the spec for details: https://spec.openapis.org/oas/v3.0.3.html#x4-7-24-3-3-model-with-map-dictionary-properties. + // See the spec for details: + // https://spec.openapis.org/oas/v3.0.3.html#x4-7-24-3-3-model-with-map-dictionary-properties. // // TODO correctness: Currently our API spec does not specify // what type the key will be, so we set it to string to avoid @@ -623,7 +652,11 @@ func createTypeObject(schema *openapi3.Schema, name, typeName, description strin return typeTpl } -func createStringEnum(s *openapi3.Schema, stringEnums map[string][]string, name, typeName string) (map[string][]string, []TypeTemplate, []EnumTemplate) { +func createStringEnum( + s *openapi3.Schema, + stringEnums map[string][]string, + name, typeName string, +) (map[string][]string, []TypeTemplate, []EnumTemplate) { typeTpls := make([]TypeTemplate, 0) // Make sure we don't redeclare the enum type. @@ -650,10 +683,15 @@ func createStringEnum(s *openapi3.Schema, stringEnums map[string][]string, name, snakeCaseTypeName := fmt.Sprintf("%s_%s", name, enum) enumTpl := EnumTemplate{ - Description: fmt.Sprintf("// %s represents the %s `%q`.", strcase.ToCamel(snakeCaseTypeName), name, enum), - Name: strcase.ToCamel(snakeCaseTypeName), - ValueType: "const", - Value: fmt.Sprintf("%s = %q", name, enum), + Description: fmt.Sprintf( + "// %s represents the %s `%q`.", + strcase.ToCamel(snakeCaseTypeName), + name, + enum, + ), + Name: strcase.ToCamel(snakeCaseTypeName), + ValueType: "const", + Value: fmt.Sprintf("%s = %q", name, enum), } enumTpls = append(enumTpls, enumTpl) @@ -678,7 +716,11 @@ func createStringEnum(s *openapi3.Schema, stringEnums map[string][]string, name, // // Probably not the best approach, but will leave them this way until I come up with // a more idiomatic solution. Keep an eye out on this one to refine. -func createAllOf(s *openapi3.Schema, stringEnums map[string][]string, name, typeName string) []TypeTemplate { +func createAllOf( + s *openapi3.Schema, + stringEnums map[string][]string, + name, typeName string, +) []TypeTemplate { typeTpls := make([]TypeTemplate, 0) // Make sure we don't redeclare the enum type. @@ -707,11 +749,13 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E enumTpls := make([]EnumTemplate, 0) typeTpls := make([]TypeTemplate, 0) - // Loop over variants, creating types and enums for nested types, and gathering metadata about the oneOf overall. + // Loop over variants, creating types and enums for nested types, and gathering metadata about + // the oneOf overall. // Set of candidate discriminator keys. There must be exactly zero or one discriminator key. discriminatorKeys := map[string]struct{}{} - // Map of properties to sets of variant types. We use this to identify fields with multiple types across variants. + // Map of properties to sets of variant types. We use this to identify fields with multiple + // types across variants. propToVariantTypes := map[string]map[string]struct{}{} for _, variantRef := range s.OneOf { @@ -724,7 +768,12 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E discriminatorKeys[propName] = struct{}{} enumField = strcase.ToCamel(propRef.Value.Enum[0].(string)) } else if len(propRef.Value.Enum) > 1 { - fmt.Printf("[WARN] TODO: oneOf for %q -> %q enum %#v\n", name, propName, propRef.Value.Enum) + fmt.Printf( + "[WARN] TODO: oneOf for %q -> %q enum %#v\n", + name, + propName, + propRef.Value.Enum, + ) } else if propRef.Value.Enum == nil && len(variantRef.Value.Properties) == 1 { enumField = propField } @@ -741,7 +790,13 @@ func createOneOf(s *openapi3.Schema, name, typeName string) ([]TypeTemplate, []E // Check invariant: there must be exactly zero or one discriminator field. if len(discriminatorKeys) > 1 { - panic(fmt.Sprintf("[ERROR] Found multiple discriminator properties for type %s: %+v", name, discriminatorKeys)) + panic( + fmt.Sprintf( + "[ERROR] Found multiple discriminator properties for type %s: %+v", + name, + discriminatorKeys, + ), + ) } // Find properties that have different types across variants. @@ -856,7 +911,11 @@ func getObjectType(s *openapi3.Schema) string { // formatTypeDescription returns the description of the given type. func formatTypeDescription(name string, s *openapi3.Schema) string { if s.Description != "" { - return fmt.Sprintf("// %s is %s", name, toLowerFirstLetter(strings.ReplaceAll(s.Description, "\n", "\n// "))) + return fmt.Sprintf( + "// %s is %s", + name, + toLowerFirstLetter(strings.ReplaceAll(s.Description, "\n", "\n// ")), + ) } return fmt.Sprintf("// %s is the type definition for a %s.", name, name) } diff --git a/internal/generate/types_test.go b/internal/generate/types_test.go index 028a292..e79422a 100644 --- a/internal/generate/types_test.go +++ b/internal/generate/types_test.go @@ -46,10 +46,16 @@ func Test_generateTypes(t *testing.T) { Type: &openapi3.Types{"object"}, Properties: openapi3.Schemas{ "snapshot_id": &openapi3.SchemaRef{ - Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Format: "uuid"}, + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Format: "uuid", + }, }, "type": &openapi3.SchemaRef{ - Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []interface{}{"snapshot"}}, + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []interface{}{"snapshot"}, + }, }, }, }, @@ -60,10 +66,16 @@ func Test_generateTypes(t *testing.T) { Type: &openapi3.Types{"object"}, Properties: openapi3.Schemas{ "image_id": &openapi3.SchemaRef{ - Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Format: "uuid"}, + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Format: "uuid", + }, }, "type": &openapi3.SchemaRef{ - Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []interface{}{"image"}}, + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []interface{}{"image"}, + }, }, }, }, @@ -149,8 +161,10 @@ func TestTypeField_StructTag(t *testing.T) { f := TypeField{ Name: "Id", MarshalKey: "id", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, - Required: true, + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, + Required: true, } assert.Equal(t, "`json:\"id\" yaml:\"id\"`", f.StructTag()) }) @@ -159,8 +173,10 @@ func TestTypeField_StructTag(t *testing.T) { f := TypeField{ Name: "Items", MarshalKey: "items", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"array"}, Nullable: true}}, - Required: false, + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"array"}, Nullable: true}, + }, + Required: false, } assert.Equal(t, "`json:\"items\" yaml:\"items\"`", f.StructTag()) }) @@ -169,17 +185,21 @@ func TestTypeField_StructTag(t *testing.T) { f := TypeField{ Name: "Value", MarshalKey: "value", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Nullable: true}}, - Required: false, + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Nullable: true}, + }, + Required: false, } assert.Equal(t, "`json:\"value,omitempty\" yaml:\"value,omitempty\"`", f.StructTag()) }) t.Run("omitdirective", func(t *testing.T) { f := TypeField{ - Name: "Value", - MarshalKey: "value", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + Name: "Value", + MarshalKey: "value", + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}, + }, OmitDirective: "omitzero", } assert.Equal(t, "`json:\"value,omitzero\" yaml:\"value,omitzero\"`", f.StructTag()) @@ -190,8 +210,10 @@ func TestTypeField_StructTag(t *testing.T) { Name: "Count", Type: "int", MarshalKey: "count", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}}, - Required: false, + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}, + }, + Required: false, } assert.Equal(t, "`json:\"count,omitempty\" yaml:\"count,omitempty\"`", f.StructTag()) }) @@ -211,9 +233,11 @@ func TestTypeField_IsPointer(t *testing.T) { { name: "nullable required", field: TypeField{ - Name: "Config", - Type: "SomeConfig", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Nullable: true}}, + Name: "Config", + Type: "SomeConfig", + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Nullable: true}, + }, Required: true, }, expected: true, @@ -221,9 +245,11 @@ func TestTypeField_IsPointer(t *testing.T) { { name: "nullable not required", field: TypeField{ - Name: "Config", - Type: "SomeConfig", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Nullable: true}}, + Name: "Config", + Type: "SomeConfig", + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Nullable: true}, + }, Required: false, }, expected: false, @@ -231,9 +257,11 @@ func TestTypeField_IsPointer(t *testing.T) { { name: "not nullable required", field: TypeField{ - Name: "Config", - Type: "SomeConfig", - Schema: &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Nullable: false}}, + Name: "Config", + Type: "SomeConfig", + Schema: &openapi3.SchemaRef{ + Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Nullable: false}, + }, Required: true, }, expected: false, @@ -259,7 +287,12 @@ func Test_createTypeObject(t *testing.T) { }, }} - got := createTypeObject(&typesSpec, "DiskSource", "DiskSourceSnapshot", "Create a disk from a disk snapshot") + got := createTypeObject( + &typesSpec, + "DiskSource", + "DiskSourceSnapshot", + "Create a disk from a disk snapshot", + ) want := TypeTemplate{ Name: "DiskSourceSnapshot", @@ -293,20 +326,46 @@ func Test_createStringEnum(t *testing.T) { want2 []EnumTemplate }{ { - name: "success", - args: args{typesSpec, enums, "FleetRole", "FleetRole"}, - want: map[string][]string{"FleetRole": {"admin", "collaborator", "viewer"}}, - want1: []TypeTemplate{{Description: "// FleetRole is the type definition for a FleetRole.", Name: "FleetRole", Type: "string"}}, + name: "success", + args: args{typesSpec, enums, "FleetRole", "FleetRole"}, + want: map[string][]string{"FleetRole": {"admin", "collaborator", "viewer"}}, + want1: []TypeTemplate{ + { + Description: "// FleetRole is the type definition for a FleetRole.", + Name: "FleetRole", + Type: "string", + }, + }, want2: []EnumTemplate{ - {Description: "// FleetRoleAdmin represents the FleetRole `\"admin\"`.", Name: "FleetRoleAdmin", ValueType: "const", Value: "FleetRole = \"admin\""}, - {Description: "// FleetRoleCollaborator represents the FleetRole `\"collaborator\"`.", Name: "FleetRoleCollaborator", ValueType: "const", Value: "FleetRole = \"collaborator\""}, - {Description: "// FleetRoleViewer represents the FleetRole `\"viewer\"`.", Name: "FleetRoleViewer", ValueType: "const", Value: "FleetRole = \"viewer\""}, + { + Description: "// FleetRoleAdmin represents the FleetRole `\"admin\"`.", + Name: "FleetRoleAdmin", + ValueType: "const", + Value: "FleetRole = \"admin\"", + }, + { + Description: "// FleetRoleCollaborator represents the FleetRole `\"collaborator\"`.", + Name: "FleetRoleCollaborator", + ValueType: "const", + Value: "FleetRole = \"collaborator\"", + }, + { + Description: "// FleetRoleViewer represents the FleetRole `\"viewer\"`.", + Name: "FleetRoleViewer", + ValueType: "const", + Value: "FleetRole = \"viewer\"", + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1, got2 := createStringEnum(tt.args.s, tt.args.stringEnums, tt.args.name, tt.args.typeName) + got, got1, got2 := createStringEnum( + tt.args.s, + tt.args.stringEnums, + tt.args.name, + tt.args.typeName, + ) assert.Equal(t, tt.want, got) assert.Equal(t, tt.want1, got1) assert.Equal(t, tt.want2, got2) @@ -331,8 +390,13 @@ func Test_createOneOf(t *testing.T) { Value: &openapi3.Schema{ Type: &openapi3.Types{"object"}, Properties: map[string]*openapi3.SchemaRef{ - "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"url"}}}, - "url": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, + "type": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"url"}, + }, + }, + "url": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, }, Required: []string{"type", "url"}, }, @@ -341,8 +405,18 @@ func Test_createOneOf(t *testing.T) { Value: &openapi3.Schema{ Type: &openapi3.Types{"object"}, Properties: map[string]*openapi3.SchemaRef{ - "id": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Format: "uuid"}}, - "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"snapshot"}}}, + "id": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Format: "uuid", + }, + }, + "type": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"snapshot"}, + }, + }, }, Required: []string{"id", "type"}, }, @@ -351,7 +425,11 @@ func Test_createOneOf(t *testing.T) { }, typeName: "ImageSource", wantTypes: []TypeTemplate{ - {Description: "// ImageSourceType is the type definition for a ImageSourceType.", Name: "ImageSourceType", Type: "string"}, + { + Description: "// ImageSourceType is the type definition for a ImageSourceType.", + Name: "ImageSourceType", + Type: "string", + }, { Description: "// ImageSourceUrl is the type definition for a ImageSourceUrl.\n//\n// Required fields:\n// - Type\n// - Url", Name: "ImageSourceUrl", @@ -375,15 +453,30 @@ func Test_createOneOf(t *testing.T) { Name: "ImageSource", Type: "struct", Fields: []TypeField{ - {Name: "Type", Type: "ImageSourceType", MarshalKey: "type", FallbackDescription: true}, + { + Name: "Type", + Type: "ImageSourceType", + MarshalKey: "type", + FallbackDescription: true, + }, {Name: "Url", Type: "string", MarshalKey: "url", FallbackDescription: true}, {Name: "Id", Type: "string", MarshalKey: "id", FallbackDescription: true}, }, }, }, wantEnums: []EnumTemplate{ - {Description: "// ImageSourceTypeUrl represents the ImageSourceType `\"url\"`.", Name: "ImageSourceTypeUrl", ValueType: "const", Value: "ImageSourceType = \"url\""}, - {Description: "// ImageSourceTypeSnapshot represents the ImageSourceType `\"snapshot\"`.", Name: "ImageSourceTypeSnapshot", ValueType: "const", Value: "ImageSourceType = \"snapshot\""}, + { + Description: "// ImageSourceTypeUrl represents the ImageSourceType `\"url\"`.", + Name: "ImageSourceTypeUrl", + ValueType: "const", + Value: "ImageSourceType = \"url\"", + }, + { + Description: "// ImageSourceTypeSnapshot represents the ImageSourceType `\"snapshot\"`.", + Name: "ImageSourceTypeSnapshot", + ValueType: "const", + Value: "ImageSourceType = \"snapshot\"", + }, }, }, { @@ -395,8 +488,15 @@ func Test_createOneOf(t *testing.T) { Value: &openapi3.Schema{ Type: &openapi3.Types{"object"}, Properties: map[string]*openapi3.SchemaRef{ - "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"int"}}}, - "value": {Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}}, + "type": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"int"}, + }, + }, + "value": { + Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}, + }, }, Required: []string{"type", "value"}, }, @@ -405,7 +505,12 @@ func Test_createOneOf(t *testing.T) { Value: &openapi3.Schema{ Type: &openapi3.Types{"object"}, Properties: map[string]*openapi3.SchemaRef{ - "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"string"}}}, + "type": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"string"}, + }, + }, "value": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, }, Required: []string{"type", "value"}, @@ -415,7 +520,11 @@ func Test_createOneOf(t *testing.T) { }, typeName: "IntOrString", wantTypes: []TypeTemplate{ - {Description: "// IntOrStringType is the type definition for a IntOrStringType.", Name: "IntOrStringType", Type: "string"}, + { + Description: "// IntOrStringType is the type definition for a IntOrStringType.", + Name: "IntOrStringType", + Type: "string", + }, { Description: "// IntOrStringInt is the type definition for a IntOrStringInt.\n//\n// Required fields:\n// - Type\n// - Value", Name: "IntOrStringInt", @@ -439,14 +548,34 @@ func Test_createOneOf(t *testing.T) { Name: "IntOrString", Type: "struct", Fields: []TypeField{ - {Name: "Type", Type: "IntOrStringType", MarshalKey: "type", FallbackDescription: true}, - {Name: "Value", Type: "any", MarshalKey: "value", FallbackDescription: true}, + { + Name: "Type", + Type: "IntOrStringType", + MarshalKey: "type", + FallbackDescription: true, + }, + { + Name: "Value", + Type: "any", + MarshalKey: "value", + FallbackDescription: true, + }, }, }, }, wantEnums: []EnumTemplate{ - {Description: "// IntOrStringTypeInt represents the IntOrStringType `\"int\"`.", Name: "IntOrStringTypeInt", ValueType: "const", Value: "IntOrStringType = \"int\""}, - {Description: "// IntOrStringTypeString represents the IntOrStringType `\"string\"`.", Name: "IntOrStringTypeString", ValueType: "const", Value: "IntOrStringType = \"string\""}, + { + Description: "// IntOrStringTypeInt represents the IntOrStringType `\"int\"`.", + Name: "IntOrStringTypeInt", + ValueType: "const", + Value: "IntOrStringType = \"int\"", + }, + { + Description: "// IntOrStringTypeString represents the IntOrStringType `\"string\"`.", + Name: "IntOrStringTypeString", + ValueType: "const", + Value: "IntOrStringType = \"string\"", + }, }, }, } @@ -470,8 +599,18 @@ func Test_createOneOf(t *testing.T) { Value: &openapi3.Schema{ Type: &openapi3.Types{"object"}, Properties: map[string]*openapi3.SchemaRef{ - "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"a"}}}, - "kind": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"x"}}}, + "type": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"a"}, + }, + }, + "kind": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"x"}, + }, + }, "value": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, }, }, @@ -480,8 +619,18 @@ func Test_createOneOf(t *testing.T) { Value: &openapi3.Schema{ Type: &openapi3.Types{"object"}, Properties: map[string]*openapi3.SchemaRef{ - "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"b"}}}, - "kind": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"y"}}}, + "type": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"b"}, + }, + }, + "kind": { + Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"y"}, + }, + }, "value": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}, }, }, @@ -489,7 +638,8 @@ func Test_createOneOf(t *testing.T) { }, } - assert.PanicsWithValue(t, + assert.PanicsWithValue( + t, "[ERROR] Found multiple discriminator properties for type MultiDiscriminator: map[kind:{} type:{}]", func() { createOneOf(schema, "MultiDiscriminator", "MultiDiscriminator") }, ) diff --git a/internal/generate/utils.go b/internal/generate/utils.go index ce5a368..d18e8cc 100644 --- a/internal/generate/utils.go +++ b/internal/generate/utils.go @@ -128,7 +128,11 @@ func schemaValueToGoType(schemaValue *openapi3.Schema, property string) string { value := schemaValue.AllOf[0] reference := getReferenceSchema(value) if reference == "" { - fmt.Printf("[WARN] TODO: handle allOf %+v for %q, marking as any for now\n", value, property) + fmt.Printf( + "[WARN] TODO: handle allOf %+v for %q, marking as any for now\n", + value, + property, + ) return "any" } if schemaValue.Nullable { @@ -178,7 +182,11 @@ func schemaValueToGoType(schemaValue *openapi3.Schema, property string) string { return fmt.Sprintf("[]%v", schemaType) } - fmt.Printf("[WARN] TODO: handle type %q for %q, marking as any for now\n", schemaValue.Type, property) + fmt.Printf( + "[WARN] TODO: handle type %q for %q, marking as any for now\n", + schemaValue.Type, + property, + ) return "any" } diff --git a/oxide/errors.go b/oxide/errors.go index 71c149e..c69cbda 100644 --- a/oxide/errors.go +++ b/oxide/errors.go @@ -24,14 +24,24 @@ type HTTPError struct { func (err HTTPError) Error() string { output := new(bytes.Buffer) if err.HTTPResponse.Request.URL != nil { - fmt.Fprintf(output, "%s %s\n", err.HTTPResponse.Request.Method, err.HTTPResponse.Request.URL) + fmt.Fprintf( + output, + "%s %s\n", + err.HTTPResponse.Request.Method, + err.HTTPResponse.Request.URL, + ) } else { // This case is extremely unlikely, just adding to avoid a panic due to a nil pointer fmt.Fprintf(output, "%s \n", err.HTTPResponse.Request.Method) } fmt.Fprintln(output, "----------- RESPONSE -----------") if err.ErrorResponse != nil { - fmt.Fprintf(output, "Status: %d %s\n", err.HTTPResponse.StatusCode, err.ErrorResponse.ErrorCode) + fmt.Fprintf( + output, + "Status: %d %s\n", + err.HTTPResponse.StatusCode, + err.ErrorResponse.ErrorCode, + ) fmt.Fprintf(output, "Message: %s\n", err.ErrorResponse.Message) fmt.Fprintf(output, "RequestID: %s\n", err.ErrorResponse.RequestId) } else { diff --git a/oxide/lib.go b/oxide/lib.go index 979fcbb..ebf71ca 100644 --- a/oxide/lib.go +++ b/oxide/lib.go @@ -228,11 +228,14 @@ func NewClient(opts ...ClientOption) (*Client, error) { } // Validate conflicting options. - if (cfg.profileSetFromOption || cfg.defaultProfileSetFromOption) && (cfg.hostSetFromOption || cfg.tokenSetFromOption) { + if (cfg.profileSetFromOption || cfg.defaultProfileSetFromOption) && + (cfg.hostSetFromOption || cfg.tokenSetFromOption) { return nil, errors.New("cannot authenticate with both a profile and host/token") } if cfg.profileSetFromOption && cfg.defaultProfileSetFromOption { - return nil, errors.New("cannot authenticate with both default profile and a defined profile") + return nil, errors.New( + "cannot authenticate with both default profile and a defined profile", + ) } // Options override environment variables. @@ -326,7 +329,12 @@ func getProfile(configDir string, profile string, useDefault bool) (*authCredent credentialsPath := filepath.Join(configDir, credentialsFile) fileCreds, err := parseCredentialsFile(credentialsPath, profile) if err != nil { - return nil, fmt.Errorf("failed to get credentials for profile %q from %q: %w", profile, credentialsPath, err) + return nil, fmt.Errorf( + "failed to get credentials for profile %q from %q: %w", + profile, + credentialsPath, + err, + ) } return fileCreds, nil @@ -404,7 +412,12 @@ func parseBaseURL(baseURL string) (string, error) { } // buildRequest creates an HTTP request to interact with the Oxide API. -func (c *Client) buildRequest(ctx context.Context, body io.Reader, method, uri string, params, queries map[string]string) (*http.Request, error) { +func (c *Client) buildRequest( + ctx context.Context, + body io.Reader, + method, uri string, + params, queries map[string]string, +) (*http.Request, error) { // Create the request. req, err := http.NewRequestWithContext(ctx, method, uri, body) if err != nil { diff --git a/oxide/lib_test.go b/oxide/lib_test.go index b829f3f..01c1105 100644 --- a/oxide/lib_test.go +++ b/oxide/lib_test.go @@ -152,7 +152,14 @@ func Test_buildRequest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := c.buildRequest(context.TODO(), tt.args.body, tt.args.method, tt.args.uri, tt.args.params, tt.args.queries) + got, err := c.buildRequest( + context.TODO(), + tt.args.body, + tt.args.method, + tt.args.uri, + tt.args.params, + tt.args.queries, + ) if err != nil { assert.ErrorContains(t, err, tt.wantErr) return @@ -492,7 +499,11 @@ func Test_NewClient(t *testing.T) { c, err := NewClient(opts...) if testCase.expectedError != "" { - assert.EqualError(t, err, strings.ReplaceAll(testCase.expectedError, "", oxideDir)) + assert.EqualError( + t, + err, + strings.ReplaceAll(testCase.expectedError, "", oxideDir), + ) } else { assert.NoError(t, err) } @@ -520,8 +531,18 @@ host = "http://other-host" token = "other-token" user = "other-user" `) - require.NoError(t, os.WriteFile(filepath.Join(oxideDir, "credentials.toml"), credentials, 0o600)) - require.NoError(t, os.WriteFile(filepath.Join(oxideDir, "config.toml"), []byte(`default-profile = "file"`), 0o644)) + require.NoError( + t, + os.WriteFile(filepath.Join(oxideDir, "credentials.toml"), credentials, 0o600), + ) + require.NoError( + t, + os.WriteFile( + filepath.Join(oxideDir, "config.toml"), + []byte(`default-profile = "file"`), + 0o644, + ), + ) return tmpDir } @@ -560,12 +581,14 @@ func Test_MakeRequest(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var capturedRequest *http.Request - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - capturedRequest = r - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(`{"status":"ok"}`)) - require.NoError(t, err) - })) + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + capturedRequest = r + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"status":"ok"}`)) + require.NoError(t, err) + }), + ) defer server.Close() client, err := NewClient( diff --git a/oxide/utils.go b/oxide/utils.go index a82a849..70c06e7 100644 --- a/oxide/utils.go +++ b/oxide/utils.go @@ -30,8 +30,8 @@ func resolveRelative(basestr, relstr string) string { rel, _ := url.Parse(relstr) u = u.ResolveReference(rel) us := u.String() - us = strings.Replace(us, "%7B", "{", -1) - us = strings.Replace(us, "%7D", "}", -1) + us = strings.ReplaceAll(us, "%7B", "{") + us = strings.ReplaceAll(us, "%7D", "}") return us }