Terraform list object in Golang - go

I am trying to represent Terraform list of objects in Go i.e.
variable "map_roles" {
description = "Additional IAM roles to add to the aws-auth configmap."
type = list(object({
rolearn = string
username = string
groups = list(string)
}))
Are rolearn and others basic types or composite ones e.g. a map? And so is map_roles just a struct of strings and slice of string (list), or is it a struct of maps?

Terratest converts variable values given in terraform.Options to -var command line arguments using its internal function toHclString.
From reading through the implementation of that function and the other functions it calls, it seems like it will convert a Go []interface{} value into Terraform tuple syntax and a Go map[string]interface{} into Terraform object syntax, so a valid value for the type constraint shown might look like this:
[]interface{}{
map[string]interface{}{
"rolearn": "foo",
"username": "bar",
"groups": []interface{"baz"},
},
map[string]interface{}{
"rolearn": "boop",
"username": "beep",
"groups": []interface{"blurp"},
},
}
Based on my read of the code (note: I didn't actually test it 😖) I would expect that to generate a -var argument value like this:
-var map_roles='[{"rolearn" = "foo", "username" = "bar", "groups" = ["baz"]},{"rolearn" = "boop", "username" = "beep", "groups" = ["blurp"]}]'

Related

GO is a complex nested structure

I wanted to clarify how to set values for
type ElkBulkInsert struct {
Index []struct {
_Index string `json:"_index"`
_Id string `json:"_id"`
} `json:"index"`
}
to make json.Marshall
there were no problems for the usual structure
package main
import (
"encoding/json"
"fmt"
)
type ElkBulkInsert struct {
Index []struct {
_Index string `json:"_index"`
_Id string `json:"_id"`
} `json:"index"`
}
type ElkBulIsertUrl struct {
Url string `json:"url"`
}
func main() {
sf := ElkBulIsertUrl{
Url: "http://mail.ru",
}
dd, _ := json.Marshal(sf)
fmt.Println(string(dd))
}
It's really unclear what you're asking here. Are you asking why the JSON output doesn't match what you expect? Are you unsure how to initialise/set values on the Index field of type []struct{...}?
Because it's quite unclear, I'll attempt to explain why your JSON output may appear to have missing fields (or why not all fields are getting populated), how you can initialise your fields, and how you may be able to improve the types you have.
General answer
If you want to marshal/unmarshal into a struct/type you made, there's a simple rule to keep in mind:
json.Marshal and json.Unmarshal can only access exported fields. An exported field have Capitalised identifiers. Your Index fieldin the ElkBulkInsert is a slice of an anonymous struct, which has no exported fields (_Index and _Id start with an underscore).
Because you're using the json:"_index" tags anyway, the field name itself doesn't have to even resemble the fields of the JSON itself. It's obviously preferable they do in most cases, but it's not required. As an aside: you have a field called Url. It's generally considered better form to follow the standards WRT initialisms, and rename that field to URL:
Words in names that are initialisms or acronyms (e.g. "URL" or "NATO") have a consistent case. For example, "URL" should appear as "URL" or "url" (as in "urlPony", or "URLPony"), never as "Url". Here's an example: ServeHTTP not ServeHttp.
This rule also applies to "ID" when it is short for "identifier," so write "appID" instead of "appId".
Code generated by the protocol buffer compiler is exempt from this rule. Human-written code is held to a higher standard than machine-written code.
With that being said, simply changing the types to this will work:
type ElkBulkInsert struct {
Index []struct {
Index string `json:"_index"`
ID string `json:"_id"`
} `json:"index"`
}
type ElkBulIsertUrl struct {
URL string `json:"url"`
}
Of course, this implies the data for ElkBulkInsert looks something like:
{
"index": [
{
"_index": "foo",
"_id": "bar"
},
{
"_index": "fizz",
"_id": "buzz"
}
]
}
When you want to set values for a structure like this, I generally find it easier to shy away from using anonymous struct fields like the one you have in your Index slice, and use something like:
type ElkInsertIndex struct {
ID string `json:"_id"`
Index string `json:"_index"`
}
type ElkBulkInsert struct {
Index []ElkInsertIndex `json:"index"`
}
This makes it a lot easier to populate the slice:
bulk := ElkBulkInsert{
Index: make([]ElkInsertIndex, 0, 10), // as an example
}
for i := 0; i < 10; i++ {
bulk.Index = append(bulk.Index, ElkInsertIndex{
ID: fmt.Sprintf("%d", i),
Index: fmt.Sprintf("Idx#%d", i), // wherever these values come from
})
}
Even easier (for instance when writing fixtures or unit tests) is to create a pre-populated literal:
data := ElkBulkInsert{
Index: []ElkInsertIndex{
{
ID: "foo",
Index: "bar",
},
{
ID: "fizz",
Index: "buzz",
},
},
}
With your current type, using the anonymous struct type, you can still do the same thing, but it looks messier, and requires more maintenance: you have to repeat the type:
data := ElkBulkInsert{
Index: []struct{
ID string `json:"_id"`
Index string `json:"_index"`
} {
ID: "foo",
Index: "bar",
},
{ // you can omit the field names if you know the order, and initialise all of them
"fizz",
"buzz",
},
}
Omitting field names when initialising in possible in both cases, but I'd advise against it. As fields get added/renamed/moved around over time, maintaining this mess becomes a nightmare. That's also why I'd strongly suggest you use move away from the anonymous struct here. They can be useful in places, but when you're representing a known data-format that comes from, or is sent to an external party (as JSON tends to do), I find it better to have all the relevant types named, labelled, documented somewhere. At the very least, you can add comments to each type, detailing what values it represents, and you can generate a nice, informative godoc from it all.

UUID field within Go Pact consumer test

I'm currently looking at adding Pact testing into my Go code, and i'm getting stuck on how to deal with field types of UUID.
I have the following struct, which I use to deserialise a response from an API to
import (
"github.com/google/uuid"
)
type Foo struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
Now, when I try and write my consumer test, it looks something like this
pact.
AddInteraction().
Given("A result exists").
UponReceiving("A request to get all results").
WithRequest(dsl.Request{
Method: "get",
Path: dsl.String("/v1"),
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json")},
Body: dsl.Match(&Foo{}),
})
The problem now, is the mocked response comes through as below, where it tries to put an array of bytes in the "id" field, I'm assuming since behind the scenes that is what the google/uuid library stores it as.
{
"id": [
1
],
"name": "string",
"description": "string"
}
Has anyone encountered this? And what would be the best way forward - the only solution I can see is changing my model to be a string, and then manually convert to a UUID within my code.
You currently can't use the Match function this way, as it recurses non primitive structures, albeit it should be possible to override this behaviour with struct tags. Could you please raise a feature request?
The simplest approach is to not use the Match method, and just manually express the contract details in the usual way.
The internal representation of uuid.UUID is type UUID [16]byte but json representation of UUID is string
var u uuid.UUID
u, _ = uuid.Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
foo1 := Foo{u, "n", "d"}
res, _ := json.Marshal(foo1)
fmt.Println(string(res))
{
"id": "f47ac10b-58cc-0372-8567-0e02b2c3d479",
"name": "n",
"description": "d"
}
and then load marshaled []byte
var foo Foo
json.Unmarshal(res, &foo)

How to make a 2-level depth type definition in a struct?

Currently I have the following definition for my structs:
type WholeJson struct {
Features []Temp
}
type Temp struct {
Properties Human
}
type Human struct {
Name string
Age uint
}
Which is working when unmarshaling a JSON string into a variable of type WholeJson, which would have the following structure:
{
"features":[
{
"properties": {
"name": "John Doe",
"age": 50
}
}
]
}
Go Playground sample here: https://play.golang.org/p/3WTLxR0EZWP
But I don't know how to write it in a simpler way. It is obvious that not both WholeJson and Temp are necessary as far as using the information they will hold. The only reason I have Temp is because I simply don't know how to avoid defining it (and still have the program work).
Presumably the Features property of WholeJson would have an array to some interface{}, but I can't nail the syntax. And I'm assuming the code for reading the unmarshalled data (from the playground sample) will stay the same.
How would I "squash" those those two structs into one (the Human struct i'm assuming is okay if it stays on its own), and still have useful data in the end, where I could loop through the features key to go through all the data?
It's OK to define a type for each level of the hierarchy and preferred when constructing values from Go code. The types do not need to be exported.
Use anonymous types to eliminate the defined types:
var sourceData struct {
Features []struct {
Properties Human
}
}
var jsonString string = `{
"features":[
{
"properties": {
"name": "John Doe",
"age": 50
}
}
]
}`
json.Unmarshal([]byte(jsonString), &sourceData)
fmt.Println(sourceData.Features[0].Properties.Name)

Accessing values through bracket notation with a variable in bracket after looping through struct for field names

I have 2 JSON files one containing users, and another containing email templates
I'm looping through the code in the email templates, and when there is a key as a value, like this:
"keyToFind": "Username"
Then I want to get the value for Username in the other JSON file:
"Username": "ssmith"
KeyToFind can be a few different things, like Password or Group and I want to avoid writing a specific if statement
I'm trying to do this in my loop but it appears that I can't use a variable in bracket notation
for _, emailElements := range emailTemplates.EmailSpecification {
for _, fieldName := range structs.Names(&User{}) {
if emailElements.KeyToFind == fieldName {
EmailBody.WriteString(user[fieldName])
}
}
What the above is trying to do is loop through the elements in the email template, and then the fields in the Users struct; where an emailElement in the template JSON file of type KeyToFind is gotten, and this is the same as a field name in the struct; look up the user value for the KeyToFind
I could do this in Python without a problem
How can I rewrite line 4 to work in Go? --> user[FieldName]
The error I get is:
user[fieldName] (type User does not support indexing)
But if I write line 4 again to this:
user.Username
It will work fine, but that's obviously only for usernames, they could be Password or Group for the value in KeyToFind
Here are the JSON files:
Email template:
"emailName": "customer",
"emailSpecification": [
{
"emailSubject": "Hi"
},
{
"text": "Username: "
},
{
"keyToFind": "Username"
}
]
I want to get the value of KeyToFind and search the properties in the User file and return the value from that property
User file:
[
{
"UserType": "customer",
"Username": "ssmith",
"Password": "sophie"
}
]
I got it by converting the User struct to a map, once it's a map you can use the bracket notation with dot notation inside
In my context, I then had to convert to a string to pass into WriteString function that buffer needs
Here is the final version:
for _, emailElements := range emailTemplates.EmailSpecification {
for _, fieldName := range structs.Names(&User{}) {
if emailElements.KeyToFind == fieldName {
EmailBody.WriteString(structs.Map(user)[emailElements.KeyToFind].(string))
}
}
}
It's using the package:
"github.com/fatih/structs"

Flutter internationalization - Dynamic strings

I'm translating my app to spanish using the intl package.
locales.dart
class AppLocale {
...
String get folder => Intl.message("Folder", name: 'folder');
...
}
messages_es.dart
class MessageLookup extends MessageLookupByLibrary {
get localeName => 'es';
final messages = _notInlinedMessages(_notInlinedMessages);
static _notInlinedMessages(_) => <String, Function> {
"folder": MessageLookupByLibrary.simpleMessage("Carpeta"),
};
}
I call it using the following code:
AppLocale.of(context).folder
It is working fine.
However, I need to create "dynamic" strings. For example:
"Hi, {$name}"
Then I would call this string, passing this "name" as parameter, or something like this. It would be translate as "Hola, {$name}" in spanish.
It is possible using this intl package?
If you follow the official internationalization docs and specify all your phrases in .arb files, you can do parameters like this:
{
"greeting": "Hi, {name}!",
"#greeting": {
"description": "Greet the user by their name.",
"placeholders": {
"name": {
"type": "String",
"example": "Jane"
}
}
}
}
When you compile your code, a function like the following will be generated for you, complete with a nice docbloc to power your IDE tooltips:
/// Greet the user by their name.
///
/// In en, this message translates to:
/// **'Hi, {name}!'**
String greeting(String name);
So you can just use it like this:
Text(AppLocalizations.of(context)!.greeting("Koos"))
The README of the intl package explains that example
https://github.com/dart-lang/intl
The purpose of wrapping the message in a function is to allow it to
have parameters which can be used in the result. The message string is
allowed to use a restricted form of Dart string interpolation, where
only the function's parameters can be used, and only in simple
expressions. Local variables cannot be used, and neither can
expressions with curly braces. Only the message string can have
interpolation. The name, desc, args, and examples must be literals and
not contain interpolations. Only the args parameter can refer to
variables, and it should list exactly the function parameters. If you
are passing numbers or dates and you want them formatted, you must do
the formatting outside the function and pass the formatted string into
the message.
greetingMessage(name) => Intl.message(
"Hello $name!",
name: "greetingMessage",
args: [name],
desc: "Greet the user as they first open the application",
examples: const {'name': "Emily"});
print(greetingMessage('Dan'));
Below this section there are more complex examples explained that also deal with plurals and genders.
In order to use placeholders in your translations you need to:
Add that placeholder as a getter argument
Mention that placeholder with $ prefix in the translation (ie $name)
Add the placeholder in args list when calling Intl.message
So a full example looks like this:
greetingMessage(name) => Intl.message(
"Hello $name!",
name: 'greetingMessage',
args: [name]
);
Follow this link. Once you have finished all steps, do the below changes in your .arb file:
{
"title": "App Title",
"helloWorld": "{name1} and {name2} must be different",
"#helloWorld": {
"description": "The conventional newborn programmer greeting",
"placeholders": {
"name1": {
"type": "String"
},
"name2": {
"type": "String"
}
}
},
"appInfo": "Information about your app",
}

Resources