diff --git a/.changesets/1792662097-11a83540.yaml b/.changesets/1792662097-11a83540.yaml new file mode 100644 index 000000000..71464cfb3 --- /dev/null +++ b/.changesets/1792662097-11a83540.yaml @@ -0,0 +1,10 @@ +id: 1792662097-11a83540 +features: + - constArrays +targets: + - terraform +type: feat +bump: minor +description: array properties with a const value are hidden from the resource schema and the const value is sent in request bodies +author: AshGodfrey +date: "2026-10-22" diff --git a/.changesets/1792662097-2435dcc7.yaml b/.changesets/1792662097-2435dcc7.yaml new file mode 100644 index 000000000..daeefe689 --- /dev/null +++ b/.changesets/1792662097-2435dcc7.yaml @@ -0,0 +1,11 @@ +id: 1792662097-2435dcc7 +features: + - constsAndDefaults +targets: + - go + - cli +type: fix +bump: patch +description: const values on array and map fields are emitted when marshaling and accepted when unmarshaling +author: AshGodfrey +date: "2026-10-22" diff --git a/.changesets/1792662097-4b2c17a5.yaml b/.changesets/1792662097-4b2c17a5.yaml new file mode 100644 index 000000000..af2bcc2df --- /dev/null +++ b/.changesets/1792662097-4b2c17a5.yaml @@ -0,0 +1,10 @@ +id: 1792662097-4b2c17a5 +features: + - constObjects +targets: + - terraform +type: feat +bump: minor +description: map properties with a const value are hidden from the resource schema and the const value is sent in request bodies +author: AshGodfrey +date: "2026-10-22" diff --git a/internal/features/features.go b/internal/features/features.go index ff07148e8..1bc3574f0 100644 --- a/internal/features/features.go +++ b/internal/features/features.go @@ -149,6 +149,12 @@ const ( // FeatureDefaultObjects relates to the ability to handle default values for objects // Tier: Experimental FeatureDefaultObjects + // FeatureConstArrays relates to the ability to handle const values for arrays + // Tier: Experimental + FeatureConstArrays + // FeatureConstObjects relates to the ability to handle const values for objects + // Tier: Experimental + FeatureConstObjects // FeatureAdditionalProperties relates to the ability to deserialize any unknown properties into a map available as an extra property on the generated models for both request bodies and parameters // Tier: Gold FeatureAdditionalProperties diff --git a/internal/features/features_generated.go b/internal/features/features_generated.go index a3bb2c2ec..1c7661d2e 100644 --- a/internal/features/features_generated.go +++ b/internal/features/features_generated.go @@ -20,6 +20,10 @@ func (f Feature) String() string { return "callbacks" case FeatureConfigurableModuleName: return "configurableModuleName" + case FeatureConstArrays: + return "constArrays" + case FeatureConstObjects: + return "constObjects" case FeatureConstsAndDefaults: return "constsAndDefaults" case FeatureContentMediaTypeApplicationJSON: @@ -198,6 +202,10 @@ func FeatureFromString(s string) Feature { return FeatureCallbacks case "configurableModuleName": return FeatureConfigurableModuleName + case "constArrays": + return FeatureConstArrays + case "constObjects": + return FeatureConstObjects case "constsAndDefaults": return FeatureConstsAndDefaults case "contentMediaTypeApplicationJSON": @@ -367,6 +375,8 @@ var featureList = []Feature{ FeatureCLICommands, FeatureCallbacks, FeatureConfigurableModuleName, + FeatureConstArrays, + FeatureConstObjects, FeatureConstsAndDefaults, FeatureContentMediaTypeApplicationJSON, FeatureCore, @@ -456,6 +466,8 @@ var featureTiers = map[Feature]string{ FeatureCLICommands: TierExperimental, FeatureCallbacks: TierCore, FeatureConfigurableModuleName: TierExperimental, + FeatureConstArrays: TierExperimental, + FeatureConstObjects: TierExperimental, FeatureConstsAndDefaults: TierBeta, FeatureContentMediaTypeApplicationJSON: TierExperimental, FeatureCore: TierCore, @@ -571,6 +583,8 @@ var tierCoreFeatures = []Feature{ var tierExperimentalFeatures = []Feature{ FeatureCLICommands, FeatureConfigurableModuleName, + FeatureConstArrays, + FeatureConstObjects, FeatureContentMediaTypeApplicationJSON, FeatureCustomCodeRegions, FeatureCustomSecuritySchemes, diff --git a/internal/schemas/handle_array_test.go b/internal/schemas/handle_array_test.go index 5f8943a9a..5d4e71f1e 100644 --- a/internal/schemas/handle_array_test.go +++ b/internal/schemas/handle_array_test.go @@ -69,6 +69,106 @@ func TestHandleArray(t *testing.T) { Build()). Build(), }, + { + name: "ArrayWithConst", + schemasYAML: `TestSchema: + type: array + items: + type: string + const: + - read + - write`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstArrays: true, + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("strings", + testutils.NewTypeDef(ast.DataTypeArray). + WithItemType(testutils.NewTypeDef(ast.DataTypeString).WithLocation(&yaml.Node{Line: 11, Column: 9}).WithValidations(&ast.Validations{}).Build()). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithValidations(&ast.Validations{}). + Build()). + WithConst(&ast.AnyValue{Value: []interface{}{"read", "write"}}). + Build(), + }, + { + name: "ArrayWithEmptyConst", + schemasYAML: `TestSchema: + type: array + items: + type: string + const: []`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstArrays: true, + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("strings", + testutils.NewTypeDef(ast.DataTypeArray). + WithItemType(testutils.NewTypeDef(ast.DataTypeString).WithLocation(&yaml.Node{Line: 11, Column: 9}).WithValidations(&ast.Validations{}).Build()). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithValidations(&ast.Validations{}). + Build()). + WithConst(&ast.AnyValue{Value: []interface{}{}}). + Build(), + }, + { + name: "ArrayWithConstFeatureUnsupported", + schemasYAML: `TestSchema: + type: array + items: + type: string + const: []`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("strings", + testutils.NewTypeDef(ast.DataTypeArray). + WithItemType(testutils.NewTypeDef(ast.DataTypeString).WithLocation(&yaml.Node{Line: 11, Column: 9}).WithValidations(&ast.Validations{}).Build()). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithValidations(&ast.Validations{}). + Build()). + Build(), + }, + { + name: "ArrayWithConstItemTypeMismatch", + schemasYAML: `TestSchema: + type: array + items: + type: string + const: + - 1`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstArrays: true, + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("strings", + testutils.NewTypeDef(ast.DataTypeArray). + WithItemType(testutils.NewTypeDef(ast.DataTypeString).WithLocation(&yaml.Node{Line: 11, Column: 9}).WithValidations(&ast.Validations{}).Build()). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithValidations(&ast.Validations{}). + Build()). + Build(), + }, { name: "ArrayWithMinMaxItems", schemasYAML: `TestSchema: @@ -165,6 +265,33 @@ func TestHandleArray(t *testing.T) { Build()). Build(), }, + { + name: "ArrayWithConstNullableItems", + schemasYAML: `TestSchema: + type: array + items: + type: string + nullable: true + const: + - read`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstArrays: true, + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("strings", + testutils.NewTypeDef(ast.DataTypeArray). + WithItemType(testutils.NewTypeDef(ast.DataTypeString).WithLocation(&yaml.Node{Line: 11, Column: 9}).WithValidations(&ast.Validations{}).Build()). + WithContainsNull(true). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithValidations(&ast.Validations{}). + Build()). + Build(), + }, { name: "ArrayWithExample", schemasYAML: `TestSchema: diff --git a/internal/schemas/handle_object_test.go b/internal/schemas/handle_object_test.go index bce54b65d..7e5906855 100644 --- a/internal/schemas/handle_object_test.go +++ b/internal/schemas/handle_object_test.go @@ -245,6 +245,102 @@ func TestHandleObject(t *testing.T) { Build()). Build(), }, + { + name: "MapWithConst", + schemasYAML: `TestSchema: + type: object + additionalProperties: + type: string + const: + key: value`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstObjects: true, + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("object", + testutils.NewTypeDef(ast.DataTypeMap). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithItemType( + testutils.NewTypeDef(ast.DataTypeString). + WithLocation(&yaml.Node{Line: 11, Column: 9}). + WithValidations(&ast.Validations{}). + Build(), + ). + Build()). + WithConst(&ast.AnyValue{Value: map[string]interface{}{"key": "value"}}). + Build(), + }, + { + name: "MapWithConstFeatureUnsupported", + schemasYAML: `TestSchema: + type: object + additionalProperties: + type: string + const: {}`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("object", + testutils.NewTypeDef(ast.DataTypeMap). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithItemType( + testutils.NewTypeDef(ast.DataTypeString). + WithLocation(&yaml.Node{Line: 11, Column: 9}). + WithValidations(&ast.Validations{}). + Build(), + ). + Build()). + Build(), + }, + { + name: "ObjectWithUnsupportedConst", + schemasYAML: `TestSchema: + type: object + properties: + name: + type: string + const: + name: "fixed"`, + schemaName: "TestSchema", + mockFeatures: func() *testutils.MockFeaturesConfig { + config := testutils.NewMockFeaturesConfig() + config.SupportedFeatures = map[features.Feature]bool{ + features.FeatureConstObjects: true, + features.FeatureConstsAndDefaults: true, + } + return config + }, + expected: testutils.NewFieldDef("object", + testutils.NewTypeDef(ast.DataTypeClass). + WithLocation(&yaml.Node{Line: 9, Column: 7}). + WithScope(ast.ScopeShared). + WithRegistered(). + WithOriginalNameFrozen(). + WithContextStack(ast.ContextStack{}). + WithFields( + testutils.NewFieldDef("name", + testutils.NewTypeDef(ast.DataTypeString). + WithLocation(&yaml.Node{Line: 12, Column: 11}). + WithValidations(&ast.Validations{}). + Build()). + WithOriginalName("name"). + WithJSONAnnotation("name"). + WithOptional(true). + Build(), + ). + Build()). + Build(), + }, { name: "ObjectWithExample", schemasYAML: `TestSchema: diff --git a/internal/schemas/schemas.go b/internal/schemas/schemas.go index 26cec94b9..a63fc9414 100644 --- a/internal/schemas/schemas.go +++ b/internal/schemas/schemas.go @@ -562,9 +562,7 @@ func (s *Schemas) handleArray(ctx context.Context, params Params, nullable bool) Optional: nullable, Nullable: nullable, } - if s.Subsystem.Features.IsFeatureSupported(ctx, features.FeatureDefaultArrays) && schema.Default != nil { - fieldDef = s.handleFieldDefMetaData(ctx, fieldDef, nullable, schema) - } + fieldDef = s.handleContainerMetaData(ctx, fieldDef, nullable, schema, s.Subsystem.Features.IsFeatureSupported(ctx, features.FeatureDefaultArrays), features.FeatureConstArrays) // We need to inherit the multipart annotations if itemField != nil { @@ -818,12 +816,12 @@ func (s *Schemas) handleObject(ctx context.Context, params Params, nullable bool typeDef.Extensions = extensions - return &ast.FieldDef{ + return s.handleContainerMetaData(ctx, &ast.FieldDef{ Name: fieldName, Type: ast.NewType(typeDef, nil), Optional: nullable, Nullable: nullable, - }, nil + }, nullable, schema, false, features.FeatureConstObjects), nil } case recurseAdditionalProperties: additionalPropertiesParams := params @@ -891,12 +889,12 @@ func (s *Schemas) handleObject(ctx context.Context, params Params, nullable bool typeDef.Extensions = extensions - return &ast.FieldDef{ + return s.handleContainerMetaData(ctx, &ast.FieldDef{ Name: fieldName, Type: ast.NewType(typeDef, nil), Optional: nullable, Nullable: nullable, - }, nil + }, nullable, schema, false, features.FeatureConstObjects), nil } } } @@ -1298,9 +1296,7 @@ func (s *Schemas) handleObject(ctx context.Context, params Params, nullable bool Nullable: nullable, } - if s.Subsystem.Features.IsFeatureSupported(ctx, features.FeatureDefaultObjects) && schema.Default != nil { - fieldDef = s.handleFieldDefMetaData(ctx, fieldDef, nullable, schema) - } + fieldDef = s.handleContainerMetaData(ctx, fieldDef, nullable, schema, s.Subsystem.Features.IsFeatureSupported(ctx, features.FeatureDefaultObjects), features.FeatureConstObjects) s.setCachedType(fieldType, ¶ms) @@ -1660,6 +1656,17 @@ func (s *Schemas) checkDefaultConformsToType(ctx context.Context, field *ast.Fie continue } + if pair.label == "const" && (field.Type.IsContainer() || field.Type.Type == ast.DataTypeClass) { + if !constContainerValueSupported(v.Value, field.Type) { + logging.LogWarning(ctx, "validation warning", errors.NewValidationWarning( + fmt.Sprintf("const value %v is not supported for type %q, only arrays and maps of scalar values are supported, omitting const", v.Value, dataType), + schema.GetPropertyNode("Const"), nil, + )) + *pair.val = nil + } + continue + } + if !defaultValueMatchesType(v.Value, dataType) { // Before clearing, try to coerce string values to the target type. // Many real-world specs have string defaults on numeric fields (e.g. default: "0.00"). @@ -1681,6 +1688,60 @@ func (s *Schemas) checkDefaultConformsToType(ctx context.Context, field *ast.Fie } } +func constContainerValueSupported(val any, typ *ast.TypeDef) bool { + if typ.ContainsNull { + return false + } + + switch typ.Type { + case ast.DataTypeArray, ast.DataTypeSet: + items, ok := val.([]any) + if !ok { + return false + } + for _, item := range items { + if !constScalarMatchesType(item, typ.ItemType) { + return false + } + } + return true + case ast.DataTypeMap: + entries, ok := val.(map[string]any) + if !ok { + return false + } + for _, item := range entries { + if !constScalarMatchesType(item, typ.ItemType) { + return false + } + } + return true + default: + return false + } +} + +func constScalarMatchesType(val any, itemType *ast.TypeDef) bool { + if val == nil || itemType == nil { + return false + } + + switch itemType.Type { + case ast.DataTypeEnum: + return itemType.Enum != nil && slices.Contains(itemType.Enum.Values, fmt.Sprint(val)) + case ast.DataTypeBoolean, ast.DataTypeInteger, ast.DataTypeInt32, ast.DataTypeBigInt, + ast.DataTypeNumber, ast.DataTypeFloat32, ast.DataTypeDecimal, + ast.DataTypeString, ast.DataTypeDate, ast.DataTypeDateTime, ast.DataTypeUUID, ast.DataTypeDuration: + dataType := itemType.Type + if itemType.Format == "string" { + dataType = ast.DataTypeString + } + return defaultValueMatchesType(val, dataType) + default: + return false + } +} + // defaultValueMatchesType checks whether a decoded default/const value is compatible // with the given AST data type. func defaultValueMatchesType(val any, dataType ast.DataType) bool { @@ -2752,6 +2813,28 @@ func (s *Schemas) handleFieldDefMetaData(ctx context.Context, fieldDef *ast.Fiel return fieldDef } +func (s *Schemas) handleContainerMetaData(ctx context.Context, fieldDef *ast.FieldDef, nullable bool, schema *oas3.Schema, defaultSupported bool, constFeature features.Feature) *ast.FieldDef { + applyDefault := schema.Default != nil && defaultSupported + applyConst := schema.Const != nil && s.Subsystem.Features.IsFeatureSupported(ctx, constFeature) + if !applyDefault && !applyConst { + return fieldDef + } + + fieldDef = s.handleFieldDefMetaData(ctx, fieldDef, nullable, schema) + if !applyDefault { + fieldDef.Default = nil + fieldDef.Optional = nullable + } + if !applyConst { + fieldDef.Const = nil + } + if fieldDef.Const != nil { + s.Subsystem.Features.RecordFeatureUsage(ctx, constFeature) + } + + return fieldDef +} + func (s *Schemas) sanitizeDefaultConstValue(ctx context.Context, value values.Value) *ast.AnyValue { if value == nil { return nil diff --git a/templates/templates/common/common/features.d.ts b/templates/templates/common/common/features.d.ts index faa544746..773e3374e 100644 --- a/templates/templates/common/common/features.d.ts +++ b/templates/templates/common/common/features.d.ts @@ -15,6 +15,8 @@ declare global { | "cliCommands" | "callbacks" | "configurableModuleName" + | "constArrays" + | "constObjects" | "constsAndDefaults" | "contentMediaTypeApplicationJSON" | "core" diff --git a/templates/templates/common/go/annotations.ts b/templates/templates/common/go/annotations.ts index 91d4b6efb..77292fc16 100644 --- a/templates/templates/common/go/annotations.ts +++ b/templates/templates/common/go/annotations.ts @@ -295,10 +295,6 @@ function templateFormAnnotation( function templateConstsAndDefaultsAnnotation( fieldDef: FieldDef, ): TemplatedAnnotation | null { - if (fieldDef.Type.IsContainer()) { - return null; - } - function sanitizeValue(value: any): string { if (value === null) { return "null"; @@ -311,6 +307,17 @@ function templateConstsAndDefaultsAnnotation( return value; } + if (fieldDef.Type.IsContainer()) { + if (fieldDef.Const) { + return { + Tag: "const", + Value: sanitizeValue(JSON.stringify(fieldDef.Const.Value)), + }; + } + + return null; + } + if (fieldDef.Const) { return { Tag: "const", diff --git a/templates/templates/common/go/sanitization.ts b/templates/templates/common/go/sanitization.ts index 0fa062913..9f4d2e0c0 100644 --- a/templates/templates/common/go/sanitization.ts +++ b/templates/templates/common/go/sanitization.ts @@ -736,6 +736,10 @@ function templateConstValue( useTypesPackage: true, skipPointerWrapping, }); + case "array": + case "set": + case "map": + return templateConstContainerValue(fieldDef, usageLocation); case "bytes": default: throw new Error( @@ -746,6 +750,35 @@ function templateConstValue( registerTemplateFunc("templateConstValue", templateConstValue); +function templateConstContainerValue( + fieldDef: FieldDef, + usageLocation: string, +): string { + const containerType = sanitizeType(fieldDef.Type, false, usageLocation); + const renderItem = (value: any): string => + templateConstValue( + { + ...fieldDef, + Type: fieldDef.Type.ItemType, + Optional: false, + Nullable: false, + Const: { Value: value }, + } as FieldDef, + usageLocation, + true, + ); + + if (fieldDef.Type.Type.toString() === "map") { + const entries = Object.entries(fieldDef.Const.Value ?? {}).map( + ([key, value]) => `${JSON.stringify(key)}: ${renderItem(value)}`, + ); + return `${containerType}{${entries.join(", ")}}`; + } + + const items = ((fieldDef.Const.Value ?? []) as any[]).map(renderItem); + return `${containerType}{${items.join(", ")}}`; +} + // @ts-ignore function templateArrayValue(value: any): string { return value; diff --git a/templates/templates/go/auxiliary/{{getUtilsLocation}}/json.go.stmpl b/templates/templates/go/auxiliary/{{getUtilsLocation}}/json.go.stmpl index ffb6afffd..e8da8c543 100644 --- a/templates/templates/go/auxiliary/{{getUtilsLocation}}/json.go.stmpl +++ b/templates/templates/go/auxiliary/{{getUtilsLocation}}/json.go.stmpl @@ -215,7 +215,7 @@ func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool } } constValue := field.Tag.Get("const") - if val != constValue { + if val != constValue && !constJSONValuesEqual(r, constValue) { return fmt.Errorf("const field `%s` does not match expected value `%s` got `%s`", fieldName, constValue, val) } @@ -447,6 +447,66 @@ func implementsJSONMarshaler(v interface{}) bool { return reflect.PtrTo(vType).Implements(marshalerType) } +func constJSONValuesEqual(received json.RawMessage, constValue string) bool { + got, err := decodeJSONWithNumbers(received) + if err != nil { + return false + } + want, err := decodeJSONWithNumbers([]byte(constValue)) + if err != nil { + return false + } + return jsonValuesEqual(got, want) +} + +func decodeJSONWithNumbers(data []byte) (interface{}, error) { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + var value interface{} + if err := decoder.Decode(&value); err != nil { + return nil, err + } + return value, nil +} + +func jsonValuesEqual(a, b interface{}) bool { + switch av := a.(type) { + case json.Number: + bv, ok := b.(json.Number) + if !ok { + return false + } + ar, aok := new(big.Rat).SetString(av.String()) + br, bok := new(big.Rat).SetString(bv.String()) + return aok && bok && ar.Cmp(br) == 0 + case map[string]interface{}: + bv, ok := b.(map[string]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for key, value := range av { + other, ok := bv[key] + if !ok || !jsonValuesEqual(value, other) { + return false + } + } + return true + case []interface{}: + bv, ok := b.([]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for i := range av { + if !jsonValuesEqual(av[i], bv[i]) { + return false + } + } + return true + default: + return reflect.DeepEqual(a, b) + } +} + func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { if tagValue == "null" { return []byte("null") diff --git a/templates/templates/terraform/features.ts b/templates/templates/terraform/features.ts index ed42926a7..b249b429b 100644 --- a/templates/templates/terraform/features.ts +++ b/templates/templates/terraform/features.ts @@ -26,6 +26,8 @@ const supportedFeatures = { constsAndDefaults: "0.3.2", defaultArrays: "0.2.0", defaultObjects: "0.1.2", + constArrays: "0.1.0", + constObjects: "0.1.0", additionalProperties: "0.1.5", disallowCircularRefs: "0.0.0", oauth2ClientCredentials: "0.3.5", diff --git a/tests/specs/review-terraform.yaml b/tests/specs/review-terraform.yaml index 4c85fff0c..4651ffcb2 100644 --- a/tests/specs/review-terraform.yaml +++ b/tests/specs/review-terraform.yaml @@ -5697,6 +5697,54 @@ paths: responses: '200': description: OK + /v0/constcontainers: + post: + x-speakeasy-entity-operation: ConstContainer#create + description: | + Create a resource whose request schema declares const values on array + and map properties. Terraform must hide those properties from the + resource schema and send the const values in the request body. + operationId: create-const-container + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ConstContainerRequest' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ConstContainerResponse' + /v0/constcontainer/{id}: + parameters: + - name: id + in: path + required: true + schema: + type: string + get: + x-speakeasy-entity-operation: ConstContainer#read + description: Get a const container resource + operationId: get-const-container + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ConstContainerResponse' + '404': + description: Not Found + delete: + x-speakeasy-entity-operation: ConstContainer#delete + description: Delete a const container resource + operationId: delete-const-container + responses: + '200': + description: OK components: parameters: GlobalBoolean: @@ -5917,6 +5965,80 @@ components: type: string default: DEFAULT schemas: + ConstContainerRequest: + type: object + additionalProperties: false + x-speakeasy-entity: ConstContainer + properties: + name: + type: string + empty_list: + type: array + items: + type: string + const: [] + fixed_list: + type: array + items: + type: string + enum: + - read + - write + - admin + const: + - read + - write + empty_map: + type: object + additionalProperties: + type: string + const: {} + fixed_map: + type: object + additionalProperties: + type: integer + const: + max: 10 + required: + - name + - empty_list + - empty_map + ConstContainerResponse: + type: object + additionalProperties: false + x-speakeasy-entity: ConstContainer + properties: + id: + type: string + name: + type: string + empty_list: + type: array + items: + type: string + const: [] + fixed_list: + type: array + items: + type: string + enum: + - read + - write + - admin + const: + - read + - write + empty_map: + type: object + additionalProperties: + type: string + const: {} + fixed_map: + type: object + additionalProperties: + type: integer + const: + max: 10 DiscriminatedUnionCreateRequest: x-speakeasy-entity: DiscriminatedUnion type: object diff --git a/zSDKs/sdk-cli/.speakeasy/gen.lock b/zSDKs/sdk-cli/.speakeasy/gen.lock index f9b725c8f..db5cded35 100644 --- a/zSDKs/sdk-cli/.speakeasy/gen.lock +++ b/zSDKs/sdk-cli/.speakeasy/gen.lock @@ -598,7 +598,7 @@ trackedFiles: internal/sdk/sdkinternal/utils/headers.go: last_write_checksum: sha1:1b0bd68e1ea0bce8c5a42b5de9fd020fede7aecc internal/sdk/sdkinternal/utils/json.go: - last_write_checksum: sha1:cbddebcd3cd06c607a5c462eab1d490f62bcdc1a + last_write_checksum: sha1:7230262498cf1d11a4ca8e4b12e3e9354f1db635 internal/sdk/sdkinternal/utils/oauth2_credentials.go: last_write_checksum: sha1:f9218b6b3dbd11494cc353bf22eb37f0ca654a7d internal/sdk/sdkinternal/utils/oauth2_sessions.go: diff --git a/zSDKs/sdk-cli/internal/sdk/sdkinternal/utils/json.go b/zSDKs/sdk-cli/internal/sdk/sdkinternal/utils/json.go index bf44ff6e5..9c7d5d75f 100644 --- a/zSDKs/sdk-cli/internal/sdk/sdkinternal/utils/json.go +++ b/zSDKs/sdk-cli/internal/sdk/sdkinternal/utils/json.go @@ -216,7 +216,7 @@ func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool } } constValue := field.Tag.Get("const") - if val != constValue { + if val != constValue && !constJSONValuesEqual(r, constValue) { return fmt.Errorf("const field `%s` does not match expected value `%s` got `%s`", fieldName, constValue, val) } @@ -446,6 +446,66 @@ func implementsJSONMarshaler(v interface{}) bool { return reflect.PtrTo(vType).Implements(marshalerType) } +func constJSONValuesEqual(received json.RawMessage, constValue string) bool { + got, err := decodeJSONWithNumbers(received) + if err != nil { + return false + } + want, err := decodeJSONWithNumbers([]byte(constValue)) + if err != nil { + return false + } + return jsonValuesEqual(got, want) +} + +func decodeJSONWithNumbers(data []byte) (interface{}, error) { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + var value interface{} + if err := decoder.Decode(&value); err != nil { + return nil, err + } + return value, nil +} + +func jsonValuesEqual(a, b interface{}) bool { + switch av := a.(type) { + case json.Number: + bv, ok := b.(json.Number) + if !ok { + return false + } + ar, aok := new(big.Rat).SetString(av.String()) + br, bok := new(big.Rat).SetString(bv.String()) + return aok && bok && ar.Cmp(br) == 0 + case map[string]interface{}: + bv, ok := b.(map[string]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for key, value := range av { + other, ok := bv[key] + if !ok || !jsonValuesEqual(value, other) { + return false + } + } + return true + case []interface{}: + bv, ok := b.([]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for i := range av { + if !jsonValuesEqual(av[i], bv[i]) { + return false + } + } + return true + default: + return reflect.DeepEqual(a, b) + } +} + func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { if tagValue == "null" { return []byte("null") diff --git a/zSDKs/sdk-go/.speakeasy/gen.lock b/zSDKs/sdk-go/.speakeasy/gen.lock index 5032e8a95..40d67eac8 100644 --- a/zSDKs/sdk-go/.speakeasy/gen.lock +++ b/zSDKs/sdk-go/.speakeasy/gen.lock @@ -713,7 +713,7 @@ trackedFiles: internal/utils/headers.go: last_write_checksum: sha1:e529ca3d7ab96a091021bcd471ba2cbef50286b3 internal/utils/json.go: - last_write_checksum: sha1:785278d21e06d3c0b820630fc9d5ca38d364f9ee + last_write_checksum: sha1:031133cd77b7fb3478635075efe7f830be401140 internal/utils/pathparams.go: last_write_checksum: sha1:440aadb18d886ea0a79af0772eb65cdddc9eee60 internal/utils/queryparams.go: diff --git a/zSDKs/sdk-go/internal/utils/json.go b/zSDKs/sdk-go/internal/utils/json.go index 5e6fb780d..906ea3072 100644 --- a/zSDKs/sdk-go/internal/utils/json.go +++ b/zSDKs/sdk-go/internal/utils/json.go @@ -216,7 +216,7 @@ func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool } } constValue := field.Tag.Get("const") - if val != constValue { + if val != constValue && !constJSONValuesEqual(r, constValue) { return fmt.Errorf("const field `%s` does not match expected value `%s` got `%s`", fieldName, constValue, val) } @@ -446,6 +446,66 @@ func implementsJSONMarshaler(v interface{}) bool { return reflect.PtrTo(vType).Implements(marshalerType) } +func constJSONValuesEqual(received json.RawMessage, constValue string) bool { + got, err := decodeJSONWithNumbers(received) + if err != nil { + return false + } + want, err := decodeJSONWithNumbers([]byte(constValue)) + if err != nil { + return false + } + return jsonValuesEqual(got, want) +} + +func decodeJSONWithNumbers(data []byte) (interface{}, error) { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + var value interface{} + if err := decoder.Decode(&value); err != nil { + return nil, err + } + return value, nil +} + +func jsonValuesEqual(a, b interface{}) bool { + switch av := a.(type) { + case json.Number: + bv, ok := b.(json.Number) + if !ok { + return false + } + ar, aok := new(big.Rat).SetString(av.String()) + br, bok := new(big.Rat).SetString(bv.String()) + return aok && bok && ar.Cmp(br) == 0 + case map[string]interface{}: + bv, ok := b.(map[string]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for key, value := range av { + other, ok := bv[key] + if !ok || !jsonValuesEqual(value, other) { + return false + } + } + return true + case []interface{}: + bv, ok := b.([]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for i := range av { + if !jsonValuesEqual(av[i], bv[i]) { + return false + } + } + return true + default: + return reflect.DeepEqual(a, b) + } +} + func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { if tagValue == "null" { return []byte("null") diff --git a/zSDKs/terraform-provider-testing/.speakeasy/gen.lock b/zSDKs/terraform-provider-testing/.speakeasy/gen.lock index 8fffcb37d..67cd62b22 100644 --- a/zSDKs/terraform-provider-testing/.speakeasy/gen.lock +++ b/zSDKs/terraform-provider-testing/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: review-sdk-test-id management: - docChecksum: fccaa1943b7d99936e3cf386d6aef8ac + docChecksum: b5e0ebee357a2bc4151a20fb322e722c docVersion: 0.0.1 speakeasyVersion: internal generationVersion: internal @@ -11,6 +11,8 @@ features: terraform: additionalDependencies: 0.1.0 additionalProperties: 0.1.5 + constArrays: 0.1.0 + constObjects: 0.1.0 constsAndDefaults: 0.3.2 contentMediaTypeApplicationJSON: 0.1.1 core: 3.50.2 @@ -51,6 +53,8 @@ trackedFiles: last_write_checksum: sha1:d84cd84fe166ec2d063ebb13a1845e744425693c examples/data-sources/testing_basic/data-source.tf: last_write_checksum: sha1:02bc159abd38449bcddcff2ed9eb7daf0b5cf5b4 + examples/data-sources/testing_const_container/data-source.tf: + last_write_checksum: sha1:a45dc527c9b2b2404f3ce3797f8a0c3009de47dd examples/data-sources/testing_discriminated_union/data-source.tf: last_write_checksum: sha1:a21b63c90ce67e7b78e103fb57e8b55ba8ab4073 examples/data-sources/testing_framework_type/data-source.tf: @@ -275,6 +279,12 @@ trackedFiles: last_write_checksum: sha1:056e42f5b27cbc4a7e5b4a14c154ad4084026594 examples/resources/testing_basic/resource.tf: last_write_checksum: sha1:60780e18fdeae0f7305502bd819092015c9e36b5 + examples/resources/testing_const_container/import-by-string-id.tf: + last_write_checksum: sha1:094ecf7d894ef2ef5a9a4669ec05ef7c75bdceaa + examples/resources/testing_const_container/import.sh: + last_write_checksum: sha1:dc82e240078029b62b56bb695067d4c5e9cb4264 + examples/resources/testing_const_container/resource.tf: + last_write_checksum: sha1:73c7ef301934066294a6aa619b7a7e4268581b95 examples/resources/testing_discriminated_union/import-by-string-id.tf: last_write_checksum: sha1:61473f31485ac280bfa6943e7580d1bdd1e43413 examples/resources/testing_discriminated_union/import.sh: @@ -763,6 +773,14 @@ trackedFiles: last_write_checksum: sha1:550b1c9370c6f1e389f6145f4ab522df9c855e76 internal/provider/basic_resource_sdk.go: last_write_checksum: sha1:c7327b7ec6b530ccb4ba940c83953e7b58b092a4 + internal/provider/constcontainer_data_source.go: + last_write_checksum: sha1:3af6b6a8ec4bc9b019d8c83c297577266be2cb05 + internal/provider/constcontainer_data_source_sdk.go: + last_write_checksum: sha1:a3d2f26a22da0b68da7f73523f16dec043d43c59 + internal/provider/constcontainer_resource.go: + last_write_checksum: sha1:1c82779f3e4d07d9145f3c49abc8ecbf21b390af + internal/provider/constcontainer_resource_sdk.go: + last_write_checksum: sha1:e8e60887a5d623a9cdaef5a5d0f38884b3cc56a5 internal/provider/discriminatedunion_data_source.go: last_write_checksum: sha1:dd97963488cb1994321bf5e3f813917c80a7d7b5 internal/provider/discriminatedunion_data_source_sdk.go: @@ -1066,7 +1084,7 @@ trackedFiles: internal/provider/patch_resource_sdk.go: last_write_checksum: sha1:9b583e2ac47191350373ce5a86408c118bd606b1 internal/provider/provider.go: - last_write_checksum: sha1:a333255d3d9585c5700d3059b03e9b0dbf388774 + last_write_checksum: sha1:8b1723df0915047021b8fd1b267f93dc19808e67 internal/provider/reflect/diags.go: last_write_checksum: sha1:ace8bc53054bb1d8ee8689acf3e4323de75a6297 internal/provider/reflect/doc.go: @@ -2404,7 +2422,7 @@ trackedFiles: internal/sdk/internal/utils/jq.go: last_write_checksum: sha1:2e75ed7a7db1f91e97dac1ac4ca7801b2beb69b5 internal/sdk/internal/utils/json.go: - last_write_checksum: sha1:bc34328ed29902c9f3bbeb04b5706a684e9bccc1 + last_write_checksum: sha1:5d331461a0348861323c12b9489fefa388dd6a76 internal/sdk/internal/utils/oauth2_credentials.go: last_write_checksum: sha1:f9218b6b3dbd11494cc353bf22eb37f0ca654a7d internal/sdk/internal/utils/oauth2_sessions.go: @@ -2439,6 +2457,8 @@ trackedFiles: last_write_checksum: sha1:5b080b285280d5f8a012138b77c848c3118bddb0 internal/sdk/models/operations/createbasic.go: last_write_checksum: sha1:0fdbac8d615b203c4f6054f9f44176e1c4ce564a + internal/sdk/models/operations/createconstcontainer.go: + last_write_checksum: sha1:892a2934f9b5cc0d5e40335502d20c3750702b95 internal/sdk/models/operations/creatediscriminatedunion.go: last_write_checksum: sha1:68d916f8c709a15e2a564066dbc9317502aecf13 internal/sdk/models/operations/creatediscriminatedunionarray.go: @@ -2587,6 +2607,8 @@ trackedFiles: last_write_checksum: sha1:5b31482c4b581bc143b0403060004849179d8ffd internal/sdk/models/operations/deletebasic.go: last_write_checksum: sha1:2d991f9ce3b49547f88d3ed69a9a4e01ccc135a8 + internal/sdk/models/operations/deleteconstcontainer.go: + last_write_checksum: sha1:443d6380ef9a4f9405c655078371ab2c13e633fa internal/sdk/models/operations/deletecustomplanmodifiers.go: last_write_checksum: sha1:8bcb2a4f56d57265de023e41d21617f5b88a7bc6 internal/sdk/models/operations/deletediscriminatedunion.go: @@ -2727,6 +2749,8 @@ trackedFiles: last_write_checksum: sha1:3bcc0082028b5f01a4d13f84072629b010550da0 internal/sdk/models/operations/getbasic.go: last_write_checksum: sha1:73ada8ac1a2cec3987eb5b93f78f815934067b9d + internal/sdk/models/operations/getconstcontainer.go: + last_write_checksum: sha1:c4ac02b538bdf08901d302a4d4c07a6a18cb2536 internal/sdk/models/operations/getcustomplanmodifiers.go: last_write_checksum: sha1:c6f5938cb6330969450186336a3085558bcd6d1c internal/sdk/models/operations/getdiscriminatedunion.go: @@ -3073,6 +3097,10 @@ trackedFiles: last_write_checksum: sha1:34cb7c7fc9ab32b03e7c6a0875e7e6fec5805577 internal/sdk/models/shared/basicsearch.go: last_write_checksum: sha1:ebc8943a31148f7623c5f8ff940dd81c1b52f23d + internal/sdk/models/shared/constcontainerrequest.go: + last_write_checksum: sha1:3cdb41c7b838e1c15dd0483ce7f685fdc6e7d480 + internal/sdk/models/shared/constcontainerresponse.go: + last_write_checksum: sha1:d94a9a07729db01d47dd0ff890a363406d2169e5 internal/sdk/models/shared/discriminatedobject.go: last_write_checksum: sha1:9bb5641a1bcb27ec59c1f16d82d8a251ec733286 internal/sdk/models/shared/discriminatedunionarraycreaterequest.go: @@ -3578,7 +3606,7 @@ trackedFiles: internal/sdk/retry/config.go: last_write_checksum: sha1:102d1953fbd7e9f312c4442c71ccca2eaaeaa27d internal/sdk/sdk.go: - last_write_checksum: sha1:a2997f8af41cead3493768284c9cc8c950f8f57c + last_write_checksum: sha1:f390c541016ad47b4744f87a396fc36e8590d9f4 internal/sdk/types/bigint.go: last_write_checksum: sha1:49b004005d0461fb04b846eca062b070b0360b31 internal/sdk/types/date.go: @@ -6020,4 +6048,24 @@ examples: responses: "200": application/json: {} + create-const-container: + speakeasy-default-create-const-container: + requestBody: + application/json: {"name": "", "empty_list": [], "fixed_list": ["read", "write"], "empty_map": {}, "fixed_map": {"max": 10}} + responses: + "200": + application/json: {"empty_list": [], "fixed_list": ["read", "write"], "empty_map": {}, "fixed_map": {"max": 10}} + get-const-container: + speakeasy-default-get-const-container: + parameters: + path: + id: "" + responses: + "200": + application/json: {"empty_list": [], "fixed_list": ["read", "write"], "empty_map": {}, "fixed_map": {"max": 10}} + delete-const-container: + speakeasy-default-delete-const-container: + parameters: + path: + id: "" examplesVersion: 1.0.2 diff --git a/zSDKs/terraform-provider-testing/.speakeasy/logs/naming.log b/zSDKs/terraform-provider-testing/.speakeasy/logs/naming.log index 9f3e6a51d..73705617c 100644 --- a/zSDKs/terraform-provider-testing/.speakeasy/logs/naming.log +++ b/zSDKs/terraform-provider-testing/.speakeasy/logs/naming.log @@ -2238,6 +2238,15 @@ DEBUG discriminated: Renamed to "XUnknownValuesResponse_closed_enum_string" registrationID: "scope:shared refType:Schemas refName:XUnknownValuesResponse originalName:closed_enum_string" DEBUG +--- Renaming 2 types with name "fixed_list" --- +DEBUG discriminated: Renamed to "ConstContainerRequest_fixed_list" + labels: "original_name:fixed_list scope:shared data_type:enum refName:ConstContainerRequest" + registrationID: "scope:shared refType:Schemas refName:ConstContainerRequest originalName:fixed_list" +DEBUG discriminated: Renamed to "ConstContainerResponse_fixed_list" + labels: "original_name:fixed_list scope:shared data_type:enum refName:ConstContainerResponse" + registrationID: "scope:shared refType:Schemas refName:ConstContainerResponse originalName:fixed_list" +DEBUG + --- Renaming 3 types with name "tier" --- DEBUG discriminated: Renamed to "create_import_defaulted_id_tier" labels: "original_name:tier operation:create_import_defaulted_id data_type:enum parameter:pathParam" @@ -3813,6 +3822,15 @@ UpdateOasContentMediaTypeRequest (id: string, OASContentMediaTypeRequest: OASCon UpdateOasContentMediaTypeResponse (ContentType: string, StatusCode: int32, RawResponse: response ...) DeleteOasContentMediaTypeRequest (id: string) DeleteOasContentMediaTypeResponse (ContentType: string, StatusCode: int32, RawResponse: response) +ConstContainerRequest (name: string, empty_list: array, fixed_list: array ...) + ConstContainerRequestFixedList (enum: read, write, admin) +CreateConstContainerResponse (ContentType: string, StatusCode: int32, RawResponse: response ...) + ConstContainerResponse (id: string, name: string, empty_list: array ...) + ConstContainerResponseFixedList (enum: read, write, admin) +GetConstContainerRequest (id: string) +GetConstContainerResponse (ContentType: string, StatusCode: int32, RawResponse: response ...) +DeleteConstContainerRequest (id: string) +DeleteConstContainerResponse (ContentType: string, StatusCode: int32, RawResponse: response) Globals (globalBoolean: boolean, globalBooleanWithDefault: boolean, globalEnumFloat32: float32 ...) UpdateXGlobalsGlobals (globalBoolean: boolean, globalBooleanWithDefault: boolean, globalEnumFloat32: float32 ...) diff --git a/zSDKs/terraform-provider-testing/README.md b/zSDKs/terraform-provider-testing/README.md index f26d58fe8..62129d589 100644 --- a/zSDKs/terraform-provider-testing/README.md +++ b/zSDKs/terraform-provider-testing/README.md @@ -102,6 +102,7 @@ Available configuration: * [testing_api_create_and_update](docs/resources/api_create_and_update.md) * [testing_basic](docs/resources/basic.md) +* [testing_const_container](docs/resources/const_container.md) * [testing_discriminated_union](docs/resources/discriminated_union.md) * [testing_discriminated_union_array](docs/resources/discriminated_union_array.md) * [testing_framework_type](docs/resources/framework_type.md) @@ -179,6 +180,7 @@ Available configuration: * [testing_api_create_and_update](docs/data-sources/api_create_and_update.md) * [testing_basic](docs/data-sources/basic.md) +* [testing_const_container](docs/data-sources/const_container.md) * [testing_discriminated_union](docs/data-sources/discriminated_union.md) * [testing_framework_type](docs/data-sources/framework_type.md) * [testing_import_defaulted_id](docs/data-sources/import_defaulted_id.md) diff --git a/zSDKs/terraform-provider-testing/docs/data-sources/const_container.md b/zSDKs/terraform-provider-testing/docs/data-sources/const_container.md new file mode 100644 index 000000000..d32d01fab --- /dev/null +++ b/zSDKs/terraform-provider-testing/docs/data-sources/const_container.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "testing_const_container Data Source - terraform-provider-testing" +subcategory: "" +description: |- + ConstContainer DataSource +--- + +# testing_const_container (Data Source) + +ConstContainer DataSource + +## Example Usage + +```terraform +data "testing_const_container" "my_constcontainer" { + id = "...my_id..." +} +``` + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `name` (String) diff --git a/zSDKs/terraform-provider-testing/docs/resources/const_container.md b/zSDKs/terraform-provider-testing/docs/resources/const_container.md new file mode 100644 index 000000000..665204c33 --- /dev/null +++ b/zSDKs/terraform-provider-testing/docs/resources/const_container.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "testing_const_container Resource - terraform-provider-testing" +subcategory: "" +description: |- + ConstContainer Resource +--- + +# testing_const_container (Resource) + +ConstContainer Resource + +## Example Usage + +```terraform +resource "testing_const_container" "my_constcontainer" { + name = "...my_name..." +} +``` + + +## Schema + +### Required + +- `name` (String) Requires replacement if changed. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +In Terraform v1.5.0 and later, the [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used with the `id` attribute, for example: + +```terraform +import { + to = testing_const_container.my_testing_const_container + id = "..." +} +``` + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example: + +```shell +terraform import testing_const_container.my_testing_const_container "..." +``` diff --git a/zSDKs/terraform-provider-testing/examples/data-sources/testing_const_container/data-source.tf b/zSDKs/terraform-provider-testing/examples/data-sources/testing_const_container/data-source.tf new file mode 100644 index 000000000..96013b05a --- /dev/null +++ b/zSDKs/terraform-provider-testing/examples/data-sources/testing_const_container/data-source.tf @@ -0,0 +1,3 @@ +data "testing_const_container" "my_constcontainer" { + id = "...my_id..." +} \ No newline at end of file diff --git a/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/import-by-string-id.tf b/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/import-by-string-id.tf new file mode 100644 index 000000000..51488fd18 --- /dev/null +++ b/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/import-by-string-id.tf @@ -0,0 +1,4 @@ +import { + to = testing_const_container.my_testing_const_container + id = "..." +} diff --git a/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/import.sh b/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/import.sh new file mode 100644 index 000000000..1b394b9c7 --- /dev/null +++ b/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/import.sh @@ -0,0 +1 @@ +terraform import testing_const_container.my_testing_const_container "..." diff --git a/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/resource.tf b/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/resource.tf new file mode 100644 index 000000000..871e0d3f3 --- /dev/null +++ b/zSDKs/terraform-provider-testing/examples/resources/testing_const_container/resource.tf @@ -0,0 +1,3 @@ +resource "testing_const_container" "my_constcontainer" { + name = "...my_name..." +} \ No newline at end of file diff --git a/zSDKs/terraform-provider-testing/internal/provider/constcontainer_data_source.go b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_data_source.go new file mode 100644 index 000000000..4b2b78c30 --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_data_source.go @@ -0,0 +1,128 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-provider-testing/internal/sdk" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &ConstContainerDataSource{} +var _ datasource.DataSourceWithConfigure = &ConstContainerDataSource{} + +func NewConstContainerDataSource() datasource.DataSource { + return &ConstContainerDataSource{} +} + +// ConstContainerDataSource is the data source implementation. +type ConstContainerDataSource struct { + // Provider configured SDK client. + client *sdk.SDK +} + +// ConstContainerDataSourceModel describes the data model. +type ConstContainerDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` +} + +// Metadata returns the data source type name. +func (r *ConstContainerDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_const_container" +} + +// Schema defines the schema for the data source. +func (r *ConstContainerDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "ConstContainer DataSource", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Required: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r *ConstContainerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + providerData, ok := req.ProviderData.(*TestingProviderConfigureData) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected DataSource Configure Type", + fmt.Sprintf("Expected *TestingProviderConfigureData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = providerData.SDKClient +} + +func (r *ConstContainerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *ConstContainerDataSourceModel + var item types.Object + + resp.Diagnostics.Append(req.Config.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + request, requestDiags := data.ToOperationsGetConstContainerRequest(ctx) + resp.Diagnostics.Append(requestDiags...) + + if resp.Diagnostics.HasError() { + return + } + res, err := r.client.GetConstContainer(ctx, *request) + if err != nil { + resp.Diagnostics.AddError("failure to invoke API", redactSensitiveValues(ctx, err.Error())) + if res != nil && res.RawResponse != nil { + resp.Diagnostics.AddError("unexpected http request/response", debugResponse(res.RawResponse)) + } + return + } + if res == nil { + resp.Diagnostics.AddError("unexpected response from API", fmt.Sprintf("%v", res)) + return + } + if res.StatusCode != 200 { + resp.Diagnostics.AddError(fmt.Sprintf("unexpected response from API. Got an unexpected response code %v", res.StatusCode), debugResponse(res.RawResponse)) + return + } + if !(res.ConstContainerResponse != nil) { + resp.Diagnostics.AddError("unexpected response from API. Got an unexpected response body", debugResponse(res.RawResponse)) + return + } + resp.Diagnostics.Append(data.RefreshFromSharedConstContainerResponse(ctx, res.ConstContainerResponse)...) + + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/zSDKs/terraform-provider-testing/internal/provider/constcontainer_data_source_sdk.go b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_data_source_sdk.go new file mode 100644 index 000000000..0dfce4980 --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_data_source_sdk.go @@ -0,0 +1,35 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-testing/internal/sdk/models/operations" + "github.com/hashicorp/terraform-provider-testing/internal/sdk/models/shared" +) + +func (r *ConstContainerDataSourceModel) RefreshFromSharedConstContainerResponse(ctx context.Context, resp *shared.ConstContainerResponse) diag.Diagnostics { + var diags diag.Diagnostics + + if resp != nil { + r.ID = types.StringPointerValue(resp.ID) + r.Name = types.StringPointerValue(resp.Name) + } + + return diags +} + +func (r *ConstContainerDataSourceModel) ToOperationsGetConstContainerRequest(ctx context.Context) (*operations.GetConstContainerRequest, diag.Diagnostics) { + var diags diag.Diagnostics + + var id string + id = r.ID.ValueString() + + out := operations.GetConstContainerRequest{ + ID: id, + } + + return &out, diags +} diff --git a/zSDKs/terraform-provider-testing/internal/provider/constcontainer_resource.go b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_resource.go new file mode 100644 index 000000000..3cd47c03e --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_resource.go @@ -0,0 +1,268 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + speakeasy_stringplanmodifier "github.com/hashicorp/terraform-provider-testing/internal/planmodifiers/stringplanmodifier" + "github.com/hashicorp/terraform-provider-testing/internal/sdk" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &ConstContainerResource{} +var _ resource.ResourceWithImportState = &ConstContainerResource{} + +func NewConstContainerResource() resource.Resource { + return &ConstContainerResource{} +} + +// ConstContainerResource defines the resource implementation. +type ConstContainerResource struct { + // Provider configured SDK client. + client *sdk.SDK +} + +// ConstContainerResourceModel describes the resource data model. +type ConstContainerResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` +} + +func (r *ConstContainerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_const_container" +} + +func (r *ConstContainerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "ConstContainer Resource", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Description: `Requires replacement if changed.`, + }, + }, + } +} + +func (r *ConstContainerResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + providerData, ok := req.ProviderData.(*TestingProviderConfigureData) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *TestingProviderConfigureData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = providerData.SDKClient +} + +func (r *ConstContainerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ConstContainerResourceModel + var plan types.Object + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(plan.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + request, requestDiags := data.ToSharedConstContainerRequest(ctx) + resp.Diagnostics.Append(requestDiags...) + + if resp.Diagnostics.HasError() { + return + } + res, err := r.client.CreateConstContainer(ctx, *request) + if err != nil { + resp.Diagnostics.AddError("failure to invoke API", redactSensitiveValues(ctx, err.Error())) + if res != nil && res.RawResponse != nil { + resp.Diagnostics.AddError("unexpected http request/response", debugResponse(res.RawResponse)) + } + return + } + if res == nil { + resp.Diagnostics.AddError("unexpected response from API", fmt.Sprintf("%v", res)) + return + } + if res.StatusCode != 200 { + resp.Diagnostics.AddError(fmt.Sprintf("unexpected response from API. Got an unexpected response code %v", res.StatusCode), debugResponse(res.RawResponse)) + return + } + if !(res.ConstContainerResponse != nil) { + resp.Diagnostics.AddError("unexpected response from API. Got an unexpected response body", debugResponse(res.RawResponse)) + return + } + resp.Diagnostics.Append(data.RefreshFromSharedConstContainerResponse(ctx, res.ConstContainerResponse)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(refreshPlan(ctx, plan, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ConstContainerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *ConstContainerResourceModel + var item types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + request, requestDiags := data.ToOperationsGetConstContainerRequest(ctx) + resp.Diagnostics.Append(requestDiags...) + + if resp.Diagnostics.HasError() { + return + } + res, err := r.client.GetConstContainer(ctx, *request) + if err != nil { + resp.Diagnostics.AddError("failure to invoke API", redactSensitiveValues(ctx, err.Error())) + if res != nil && res.RawResponse != nil { + resp.Diagnostics.AddError("unexpected http request/response", debugResponse(res.RawResponse)) + } + return + } + if res == nil { + resp.Diagnostics.AddError("unexpected response from API", fmt.Sprintf("%v", res)) + return + } + if res.StatusCode == 404 { + resp.State.RemoveResource(ctx) + return + } + if res.StatusCode != 200 { + resp.Diagnostics.AddError(fmt.Sprintf("unexpected response from API. Got an unexpected response code %v", res.StatusCode), debugResponse(res.RawResponse)) + return + } + if !(res.ConstContainerResponse != nil) { + resp.Diagnostics.AddError("unexpected response from API. Got an unexpected response body", debugResponse(res.RawResponse)) + return + } + resp.Diagnostics.Append(data.RefreshFromSharedConstContainerResponse(ctx, res.ConstContainerResponse)...) + + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ConstContainerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *ConstContainerResourceModel + var plan types.Object + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + merge(ctx, req, resp, &data) + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; all attributes marked as RequiresReplace + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ConstContainerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *ConstContainerResourceModel + var item types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + request, requestDiags := data.ToOperationsDeleteConstContainerRequest(ctx) + resp.Diagnostics.Append(requestDiags...) + + if resp.Diagnostics.HasError() { + return + } + res, err := r.client.DeleteConstContainer(ctx, *request) + if err != nil { + resp.Diagnostics.AddError("failure to invoke API", redactSensitiveValues(ctx, err.Error())) + if res != nil && res.RawResponse != nil { + resp.Diagnostics.AddError("unexpected http request/response", debugResponse(res.RawResponse)) + } + return + } + if res == nil { + resp.Diagnostics.AddError("unexpected response from API", fmt.Sprintf("%v", res)) + return + } + switch res.StatusCode { + case 200, 404: + break + default: + resp.Diagnostics.AddError(fmt.Sprintf("unexpected response from API. Got an unexpected response code %v", res.StatusCode), debugResponse(res.RawResponse)) + return + } + +} + +func (r *ConstContainerResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) +} diff --git a/zSDKs/terraform-provider-testing/internal/provider/constcontainer_resource_sdk.go b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_resource_sdk.go new file mode 100644 index 000000000..f6f4a0ed4 --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/provider/constcontainer_resource_sdk.go @@ -0,0 +1,61 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package provider + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-testing/internal/sdk/models/operations" + "github.com/hashicorp/terraform-provider-testing/internal/sdk/models/shared" +) + +func (r *ConstContainerResourceModel) RefreshFromSharedConstContainerResponse(ctx context.Context, resp *shared.ConstContainerResponse) diag.Diagnostics { + var diags diag.Diagnostics + + if resp != nil { + r.ID = types.StringPointerValue(resp.ID) + r.Name = types.StringPointerValue(resp.Name) + } + + return diags +} + +func (r *ConstContainerResourceModel) ToOperationsDeleteConstContainerRequest(ctx context.Context) (*operations.DeleteConstContainerRequest, diag.Diagnostics) { + var diags diag.Diagnostics + + var id string + id = r.ID.ValueString() + + out := operations.DeleteConstContainerRequest{ + ID: id, + } + + return &out, diags +} + +func (r *ConstContainerResourceModel) ToOperationsGetConstContainerRequest(ctx context.Context) (*operations.GetConstContainerRequest, diag.Diagnostics) { + var diags diag.Diagnostics + + var id string + id = r.ID.ValueString() + + out := operations.GetConstContainerRequest{ + ID: id, + } + + return &out, diags +} + +func (r *ConstContainerResourceModel) ToSharedConstContainerRequest(ctx context.Context) (*shared.ConstContainerRequest, diag.Diagnostics) { + var diags diag.Diagnostics + + var name string + name = r.Name.ValueString() + + out := shared.ConstContainerRequest{ + Name: name, + } + + return &out, diags +} diff --git a/zSDKs/terraform-provider-testing/internal/provider/provider.go b/zSDKs/terraform-provider-testing/internal/provider/provider.go index c558c318a..06585ae95 100644 --- a/zSDKs/terraform-provider-testing/internal/provider/provider.go +++ b/zSDKs/terraform-provider-testing/internal/provider/provider.go @@ -808,6 +808,7 @@ func (p *TestingProvider) Resources(ctx context.Context) []func() resource.Resou return []func() resource.Resource{ NewAPICreateAndUpdateResource, NewBasicResource, + NewConstContainerResource, NewDiscriminatedUnionResource, NewDiscriminatedUnionArrayResource, NewFrameworkTypeResource, @@ -888,6 +889,7 @@ func (p *TestingProvider) DataSources(ctx context.Context) []func() datasource.D return []func() datasource.DataSource{ NewAPICreateAndUpdateDataSource, NewBasicDataSource, + NewConstContainerDataSource, NewDiscriminatedUnionDataSource, NewFrameworkTypeDataSource, NewImportDefaultedIDDataSource, diff --git a/zSDKs/terraform-provider-testing/internal/sdk/internal/utils/json.go b/zSDKs/terraform-provider-testing/internal/sdk/internal/utils/json.go index d49c56d68..83cf65606 100644 --- a/zSDKs/terraform-provider-testing/internal/sdk/internal/utils/json.go +++ b/zSDKs/terraform-provider-testing/internal/sdk/internal/utils/json.go @@ -214,7 +214,7 @@ func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool } } constValue := field.Tag.Get("const") - if val != constValue { + if val != constValue && !constJSONValuesEqual(r, constValue) { return fmt.Errorf("const field `%s` does not match expected value `%s` got `%s`", fieldName, constValue, val) } @@ -433,6 +433,66 @@ func implementsJSONMarshaler(v interface{}) bool { return reflect.PtrTo(vType).Implements(marshalerType) } +func constJSONValuesEqual(received json.RawMessage, constValue string) bool { + got, err := decodeJSONWithNumbers(received) + if err != nil { + return false + } + want, err := decodeJSONWithNumbers([]byte(constValue)) + if err != nil { + return false + } + return jsonValuesEqual(got, want) +} + +func decodeJSONWithNumbers(data []byte) (interface{}, error) { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + var value interface{} + if err := decoder.Decode(&value); err != nil { + return nil, err + } + return value, nil +} + +func jsonValuesEqual(a, b interface{}) bool { + switch av := a.(type) { + case json.Number: + bv, ok := b.(json.Number) + if !ok { + return false + } + ar, aok := new(big.Rat).SetString(av.String()) + br, bok := new(big.Rat).SetString(bv.String()) + return aok && bok && ar.Cmp(br) == 0 + case map[string]interface{}: + bv, ok := b.(map[string]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for key, value := range av { + other, ok := bv[key] + if !ok || !jsonValuesEqual(value, other) { + return false + } + } + return true + case []interface{}: + bv, ok := b.([]interface{}) + if !ok || len(av) != len(bv) { + return false + } + for i := range av { + if !jsonValuesEqual(av[i], bv[i]) { + return false + } + } + return true + default: + return reflect.DeepEqual(a, b) + } +} + func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { if tagValue == "null" { return []byte("null") diff --git a/zSDKs/terraform-provider-testing/internal/sdk/models/operations/createconstcontainer.go b/zSDKs/terraform-provider-testing/internal/sdk/models/operations/createconstcontainer.go new file mode 100644 index 000000000..7f223e99e --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/sdk/models/operations/createconstcontainer.go @@ -0,0 +1,47 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/hashicorp/terraform-provider-testing/internal/sdk/models/shared" + "net/http" +) + +type CreateConstContainerResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // OK + ConstContainerResponse *shared.ConstContainerResponse +} + +func (c *CreateConstContainerResponse) GetContentType() string { + if c == nil { + return "" + } + return c.ContentType +} + +func (c *CreateConstContainerResponse) GetStatusCode() int { + if c == nil { + return 0 + } + return c.StatusCode +} + +func (c *CreateConstContainerResponse) GetRawResponse() *http.Response { + if c == nil { + return nil + } + return c.RawResponse +} + +func (c *CreateConstContainerResponse) GetConstContainerResponse() *shared.ConstContainerResponse { + if c == nil { + return nil + } + return c.ConstContainerResponse +} diff --git a/zSDKs/terraform-provider-testing/internal/sdk/models/operations/deleteconstcontainer.go b/zSDKs/terraform-provider-testing/internal/sdk/models/operations/deleteconstcontainer.go new file mode 100644 index 000000000..d2937f78e --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/sdk/models/operations/deleteconstcontainer.go @@ -0,0 +1,48 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type DeleteConstContainerRequest struct { + ID string `pathParam:"style=simple,explode=false,name=id"` +} + +func (d *DeleteConstContainerRequest) GetID() string { + if d == nil { + return "" + } + return d.ID +} + +type DeleteConstContainerResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (d *DeleteConstContainerResponse) GetContentType() string { + if d == nil { + return "" + } + return d.ContentType +} + +func (d *DeleteConstContainerResponse) GetStatusCode() int { + if d == nil { + return 0 + } + return d.StatusCode +} + +func (d *DeleteConstContainerResponse) GetRawResponse() *http.Response { + if d == nil { + return nil + } + return d.RawResponse +} diff --git a/zSDKs/terraform-provider-testing/internal/sdk/models/operations/getconstcontainer.go b/zSDKs/terraform-provider-testing/internal/sdk/models/operations/getconstcontainer.go new file mode 100644 index 000000000..b9d5e2846 --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/sdk/models/operations/getconstcontainer.go @@ -0,0 +1,58 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package operations + +import ( + "github.com/hashicorp/terraform-provider-testing/internal/sdk/models/shared" + "net/http" +) + +type GetConstContainerRequest struct { + ID string `pathParam:"style=simple,explode=false,name=id"` +} + +func (g *GetConstContainerRequest) GetID() string { + if g == nil { + return "" + } + return g.ID +} + +type GetConstContainerResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // OK + ConstContainerResponse *shared.ConstContainerResponse +} + +func (g *GetConstContainerResponse) GetContentType() string { + if g == nil { + return "" + } + return g.ContentType +} + +func (g *GetConstContainerResponse) GetStatusCode() int { + if g == nil { + return 0 + } + return g.StatusCode +} + +func (g *GetConstContainerResponse) GetRawResponse() *http.Response { + if g == nil { + return nil + } + return g.RawResponse +} + +func (g *GetConstContainerResponse) GetConstContainerResponse() *shared.ConstContainerResponse { + if g == nil { + return nil + } + return g.ConstContainerResponse +} diff --git a/zSDKs/terraform-provider-testing/internal/sdk/models/shared/constcontainerrequest.go b/zSDKs/terraform-provider-testing/internal/sdk/models/shared/constcontainerrequest.go new file mode 100644 index 000000000..b8e5b45f3 --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/sdk/models/shared/constcontainerrequest.go @@ -0,0 +1,84 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package shared + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/terraform-provider-testing/internal/sdk/internal/utils" +) + +type ConstContainerRequestFixedList string + +const ( + ConstContainerRequestFixedListRead ConstContainerRequestFixedList = "read" + ConstContainerRequestFixedListWrite ConstContainerRequestFixedList = "write" + ConstContainerRequestFixedListAdmin ConstContainerRequestFixedList = "admin" +) + +func (e ConstContainerRequestFixedList) ToPointer() *ConstContainerRequestFixedList { + return &e +} +func (e *ConstContainerRequestFixedList) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "read": + fallthrough + case "write": + fallthrough + case "admin": + *e = ConstContainerRequestFixedList(v) + return nil + default: + return fmt.Errorf("invalid value for ConstContainerRequestFixedList: %v", v) + } +} + +type ConstContainerRequest struct { + Name string `json:"name"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + emptyList []string `const:"[]" json:"empty_list"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + fixedList []ConstContainerRequestFixedList `const:"[\"read\",\"write\"]" json:"fixed_list,omitempty"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + emptyMap map[string]string `const:"{}" json:"empty_map"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + fixedMap map[string]int64 `const:"{\"max\":10}" json:"fixed_map,omitempty"` +} + +func (c ConstContainerRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *ConstContainerRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *ConstContainerRequest) GetName() string { + if c == nil { + return "" + } + return c.Name +} + +func (c *ConstContainerRequest) GetEmptyList() []string { + return []string{} +} + +func (c *ConstContainerRequest) GetFixedList() []ConstContainerRequestFixedList { + return []ConstContainerRequestFixedList{ConstContainerRequestFixedListRead, ConstContainerRequestFixedListWrite} +} + +func (c *ConstContainerRequest) GetEmptyMap() map[string]string { + return map[string]string{} +} + +func (c *ConstContainerRequest) GetFixedMap() map[string]int64 { + return map[string]int64{"max": 10} +} diff --git a/zSDKs/terraform-provider-testing/internal/sdk/models/shared/constcontainerresponse.go b/zSDKs/terraform-provider-testing/internal/sdk/models/shared/constcontainerresponse.go new file mode 100644 index 000000000..67ae54213 --- /dev/null +++ b/zSDKs/terraform-provider-testing/internal/sdk/models/shared/constcontainerresponse.go @@ -0,0 +1,92 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package shared + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/terraform-provider-testing/internal/sdk/internal/utils" +) + +type ConstContainerResponseFixedList string + +const ( + ConstContainerResponseFixedListRead ConstContainerResponseFixedList = "read" + ConstContainerResponseFixedListWrite ConstContainerResponseFixedList = "write" + ConstContainerResponseFixedListAdmin ConstContainerResponseFixedList = "admin" +) + +func (e ConstContainerResponseFixedList) ToPointer() *ConstContainerResponseFixedList { + return &e +} +func (e *ConstContainerResponseFixedList) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "read": + fallthrough + case "write": + fallthrough + case "admin": + *e = ConstContainerResponseFixedList(v) + return nil + default: + return fmt.Errorf("invalid value for ConstContainerResponseFixedList: %v", v) + } +} + +type ConstContainerResponse struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + emptyList []string `const:"[]" json:"empty_list,omitempty"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + fixedList []ConstContainerResponseFixedList `const:"[\"read\",\"write\"]" json:"fixed_list,omitempty"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + emptyMap map[string]string `const:"{}" json:"empty_map,omitempty"` + //lint:ignore U1000 accessed via reflection for JSON marshaling + fixedMap map[string]int64 `const:"{\"max\":10}" json:"fixed_map,omitempty"` +} + +func (c ConstContainerResponse) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(c, "", false) +} + +func (c *ConstContainerResponse) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &c, "", false, nil); err != nil { + return err + } + return nil +} + +func (c *ConstContainerResponse) GetID() *string { + if c == nil { + return nil + } + return c.ID +} + +func (c *ConstContainerResponse) GetName() *string { + if c == nil { + return nil + } + return c.Name +} + +func (c *ConstContainerResponse) GetEmptyList() []string { + return []string{} +} + +func (c *ConstContainerResponse) GetFixedList() []ConstContainerResponseFixedList { + return []ConstContainerResponseFixedList{ConstContainerResponseFixedListRead, ConstContainerResponseFixedListWrite} +} + +func (c *ConstContainerResponse) GetEmptyMap() map[string]string { + return map[string]string{} +} + +func (c *ConstContainerResponse) GetFixedMap() map[string]int64 { + return map[string]int64{"max": 10} +} diff --git a/zSDKs/terraform-provider-testing/internal/sdk/sdk.go b/zSDKs/terraform-provider-testing/internal/sdk/sdk.go index 461079173..a31c2eb1d 100644 --- a/zSDKs/terraform-provider-testing/internal/sdk/sdk.go +++ b/zSDKs/terraform-provider-testing/internal/sdk/sdk.go @@ -42192,3 +42192,384 @@ func (s *SDK) DeleteOasContentMediaType(ctx context.Context, request operations. return res, nil } + +// CreateConstContainer - Create a resource whose request schema declares const values on array +// and map properties. Terraform must hide those properties from the +// resource schema and send the const values in the request body. +func (s *SDK) CreateConstContainer(ctx context.Context, request shared.ConstContainerRequest, opts ...operations.Option) (*operations.CreateConstContainerResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := url.JoinPath(baseURL, "/v0/constcontainers") + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "create-const-container", + OAuth2Scopes: []string{}, + SecuritySource: s.sdkConfiguration.Security, + } + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Request", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, err + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + defer cancel() + } + + req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + if reqContentType != "" { + req.Header.Set("Content-Type", reqContentType) + } + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.CreateConstContainerResponse{ + StatusCode: httpRes.StatusCode, + ContentType: httpRes.Header.Get("Content-Type"), + RawResponse: httpRes, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out shared.ConstContainerResponse + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.ConstContainerResponse = &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, errors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, errors.NewSDKError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} + +// GetConstContainer - Get a const container resource +func (s *SDK) GetConstContainer(ctx context.Context, request operations.GetConstContainerRequest, opts ...operations.Option) (*operations.GetConstContainerResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/v0/constcontainer/{id}", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "get-const-container", + OAuth2Scopes: []string{}, + SecuritySource: s.sdkConfiguration.Security, + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + defer cancel() + } + + req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.GetConstContainerResponse{ + StatusCode: httpRes.StatusCode, + ContentType: httpRes.Header.Get("Content-Type"), + RawResponse: httpRes, + } + + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + + var out shared.ConstContainerResponse + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.ConstContainerResponse = &out + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, errors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) + } + case httpRes.StatusCode == 404: + utils.DrainBody(httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, errors.NewSDKError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +} + +// DeleteConstContainer - Delete a const container resource +func (s *SDK) DeleteConstContainer(ctx context.Context, request operations.DeleteConstContainerRequest, opts ...operations.Option) (*operations.DeleteConstContainerResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionTimeout, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + + var baseURL string + if o.ServerURL == nil { + baseURL = utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + } else { + baseURL = *o.ServerURL + } + opURL, err := utils.GenerateURL(ctx, baseURL, "/v0/constcontainer/{id}", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + hookCtx := hooks.HookContext{ + SDK: s, + SDKConfiguration: s.sdkConfiguration, + BaseURL: baseURL, + Context: ctx, + OperationID: "delete-const-container", + OAuth2Scopes: []string{}, + SecuritySource: s.sdkConfiguration.Security, + } + + timeout := o.Timeout + if timeout == nil { + timeout = s.sdkConfiguration.Timeout + } + + if timeout != nil { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, *timeout) + defer cancel() + } + + req, err := http.NewRequestWithContext(ctx, "DELETE", opURL, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { + return nil, err + } + + for k, v := range o.SetHeaders { + req.Header.Set(k, v) + } + + req, err = s.hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) + if err != nil { + return nil, err + } + + httpRes, err := s.sdkConfiguration.Client.Do(req) + if err != nil || httpRes == nil { + if err != nil { + err = fmt.Errorf("error sending request: %w", err) + } else { + err = fmt.Errorf("error sending request: no response") + } + + _, err = s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) + return nil, err + } else if utils.MatchStatusCodes([]string{}, httpRes.StatusCode) { + _httpRes, err := s.hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) + if err != nil { + return nil, err + } else if _httpRes != nil { + httpRes = _httpRes + } + } else { + httpRes, err = s.hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) + if err != nil { + return nil, err + } + } + + res := &operations.DeleteConstContainerResponse{ + StatusCode: httpRes.StatusCode, + ContentType: httpRes.Header.Get("Content-Type"), + RawResponse: httpRes, + } + + switch { + case httpRes.StatusCode == 200: + utils.DrainBody(httpRes) + case httpRes.StatusCode == 404: + utils.DrainBody(httpRes) + default: + rawBody, err := utils.ConsumeRawBody(httpRes) + if err != nil { + return nil, err + } + return nil, errors.NewSDKError("unknown status code returned", httpRes.StatusCode, string(rawBody), httpRes) + } + + return res, nil + +}