// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package provider import ( "context" "fmt" "github.com/hashicorp-demoapp/hashicups-client-go" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" ) // Ensure the implementation satisfies the expected interfaces. var ( _ datasource.DataSource = &coffeesDataSource{} _ datasource.DataSourceWithConfigure = &coffeesDataSource{} ) // NewCoffeesDataSource is a helper function to simplify the provider implementation. func NewCoffeesDataSource() datasource.DataSource { return &coffeesDataSource{} } // coffeesDataSource is the data source implementation. type coffeesDataSource struct { client *hashicups.Client } // coffeesDataSourceModel maps the data source schema data. type coffeesDataSourceModel struct { Coffees []coffeesModel `tfsdk:"coffees"` } // coffeesModel maps coffees schema data. type coffeesModel struct { ID types.Int64 `tfsdk:"id"` Name types.String `tfsdk:"name"` Teaser types.String `tfsdk:"teaser"` Description types.String `tfsdk:"description"` Price types.Float64 `tfsdk:"price"` Image types.String `tfsdk:"image"` Ingredients []coffeesIngredientsModel `tfsdk:"ingredients"` } // coffeesIngredientsModel maps coffee ingredients data. type coffeesIngredientsModel struct { ID types.Int64 `tfsdk:"id"` } // Metadata returns the data source type name. func (d *coffeesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_coffees" } // Schema defines the schema for the data source. func (d *coffeesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "coffees": schema.ListNestedAttribute{ Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.Int64Attribute{ Computed: true, }, "name": schema.StringAttribute{ Computed: true, }, "teaser": schema.StringAttribute{ Computed: true, }, "description": schema.StringAttribute{ Computed: true, }, "price": schema.Float64Attribute{ Computed: true, }, "image": schema.StringAttribute{ Computed: true, }, "ingredients": schema.ListNestedAttribute{ Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "id": schema.Int64Attribute{ Computed: true, }, }, }, }, }, }, }, }, } } // Configure adds the provider configured client to the data source. func (d *coffeesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { // Add a nil check when handling ProviderData because Terraform // sets that data after it calls the ConfigureProvider RPC. if req.ProviderData == nil { return } client, ok := req.ProviderData.(*hashicups.Client) if !ok { resp.Diagnostics.AddError( "Unexpected Data Source Configure Type", fmt.Sprintf("Expected *hashicups.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), ) return } d.client = client } // Read is used by the data source to refresh the Terraform state based on the schema data. func (d *coffeesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var state coffeesDataSourceModel var diags diag.Diagnostics // 0. Checks whether the API Client is configured. if d.client == nil { resp.Diagnostics.AddError( "cannot read coffees", "client is not configured in coffeesDataSource.Read", ) return } // 1. Reads coffees list. The method invokes the API client's GetCoffees method. coffees, err := d.client.GetCoffees() if err != nil { resp.Diagnostics.AddError( "Unable to Read HashiCups Coffees", err.Error(), ) return } // 2. Maps response body to schema attributes. // After the method reads the coffees, it maps the []hashicups.Coffee response // to coffeesModel so the data source can set the Terraform state. for _, coffee := range coffees { coffeeState := coffeesModel{ ID: types.Int64Value(int64(coffee.ID)), Name: types.StringValue(coffee.Name), Teaser: types.StringValue(coffee.Teaser), Description: types.StringValue(coffee.Description), Price: types.Float64Value(coffee.Price), Image: types.StringValue(coffee.Image + ""), Ingredients: make([]coffeesIngredientsModel, 0, len(coffee.Ingredient)), // Not in tutorial, why ? } for _, ingredient := range coffee.Ingredient { coffeeState.Ingredients = append(coffeeState.Ingredients, coffeesIngredientsModel{ ID: types.Int64Value(int64(ingredient.ID)), }) } state.Coffees = append(state.Coffees, coffeeState) } // 3. Set Terraform's state with the coffees model. diags = resp.State.Set(ctx, state) resp.Diagnostics.Append(diags...) // Useless code in tutorial, why ? if resp.Diagnostics.HasError() { return } }