How to use a struct field into another struct without referring to it as a key - go

I want to insert a struct field into another struct without having to use the struct name.
I know that I can do this:
type Person struct {
Name string
}
type User struct {
Person
Email, Password string
}
But It results in this struct:
user := User{Person: Person{Name: ""}, Email: "", Password: ""}
How can I do something like this:
type Person struct {
Name string
}
type User struct {
Name Person.Name // Here
Email, Password string
}
To use it like this
user := User{Name: "", Email: "", Password: ""}
Is it possible?

Simply put, with the current language implementation you can't.
When initialising a literal you need to be explicit (or, put another way: literal! [sic]). Since a User contains a Person, a literal User must contain a literal Person, as you illustrate:
u := User{
Person: Person{
Name: "Bob",
},
Email: "bob#bobspot.com",
Password: "you're kidding right?",
}
However, once you have a variable of type User, you can then leverage the anonymous field to set (or get) the Name of the anonymous Person with the User:
u := User{}
u.Name = "Bob"
u.Email = "bob#bobspot.com",
u.Password = "you're kidding right?",
Why Does Go Make Me Do All This Work?
Let us imagine that it was possible to initialise the inner Person in the way you are looking for:
u := User{ Name: "Bob" }
Now let us further imagine that we later modify the User struct and add its own Name field:
type User struct {
Person
Name string
Email string
Password string
}
And now you can obviously initialise the new Name field:
u := User{ Name: "Bob" }
Notice that this is identical to the previous code that initialised User.Person.Name but now it is initialising User.Name. Not good.
More Gotchas
There are further traps lurking with code like this.
First, the addition of a Name field in User already similarly "breaks" unqualified references to Name on User variables:
u.Name = "Bob" // used to set User.Person.Name, now sets User.Name
Also, with only an anonymous Person field, the User.Person.Name field is marshalled to JSON by default as a "Name" field:
{
"Name": "",
"Email": "",
"Password": ""
}
If a Name field is added, then this is the field that is marshalled as "Name" and the User.Person.Name field is not marshalled at all.
You might think you can add a json tag for the User.Person.Name, e.g.
type User struct {
Person `json:"PersonName"`
Name string
Email string
Password string
}
But now the Person is marshalled as an object with a Name field:
{
"PersonName": {
"Name": ""
},
"Name": "",
"Email": "",
"Password": ""
}
This also happens if you try to change the marshalled field name of the anonymous Person even if User does not have a Name field.
In short: using anonymous structs within structs as a way of "adding fields" is potentially problematic and fragile and should probably be avoided.

Related

Gorm create and return value many2many

I want to create a data and then return the value, but the value is related to another table.
User response struct
type UserRespone struct {
ID int `json:"id,omitempty"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Address string `json:"address"`
Roles []roles.Roles `json:"roles" gorm:"many2many:users_roles;foreignKey:ID;joinForeignKey:UserID;references:ID;joinReferences:RolesID"`
}
Roles struct
type Roles struct {
ID int `json:"id" gorm:"<-:false;primaryKey"`
Name string
}
And this is a function to create a data, and I want to return the value [ ]roles.Roles
func (r *UserRepositoryImpl) AuthSignUp(ctx context.Context, req user.AuthSignUp) (user.UserRespone, error) {
createdUser := req.ToUser()
hashPassword, _ := bcrypt.GenerateFromPassword([]byte(createdUser.Password), bcrypt.DefaultCost)
createdUser.Password = string(hashPassword[:])
result := r.Db.
WithContext(ctx).
Create(&createdUser)
for _, rolesID := range req.RolesID {
userRole := new(user.UsersRoles)
userRole.UserID = createdUser.ID
userRole.RolesID = rolesID
result = r.Db.WithContext(ctx).Create(&userRole)
}
return createdUser.ToResponse(), result.Error
}
I want to return the value like this:
user.UserResponse{
ID: 4,
Email: "putri4#gmail.com",
FirstName: "putri",
LastName: "cantik",
Address: "bonang",
Roles: []roles.Roles{
{
ID: 1,
Name: "Admin",
},
{
ID: 2,
Name: "Consumer",
},
},
}
But when I create a data set, I only get values like :
user.UserRespones{
ID: 4,
Email: "putri4#gmail.com",
FirstName: "putri",
LastName: "cantik",
Address: "bonang",
Roles: []roles.Roles(nil),
}
As far as I can tell, you already have the user data and the role ids. I figure you just want to get the role names as well:
err := r.db.Find(&createdUser.Roles, req.RolesID)
// err handling
That said, your types and names are a bit unclear. UserRespone (note the typo) should probably named like DBUser - it isn't important whether it is used as response or something else, it represents the database entry for an user, right?
Additionally, I can only make assumptions about the type and fields of createdUser and UsersRoles, so I might have missed something.
I figure createdUser is of type UserRespone - but then I would expect that Roles is already complete (and you don't have to query anything at all). If not, then you should introduce a new type for that, e.g. RequestUser (as opposed to DBUser), which only contains Role ids but not Role names. Squeezing the data from the request, the db entry and the response data into the same type is confusing (and a tad too tightly coupled for my taste).

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 filter users by role in Gorm?

I found some issues understanding the documentation. I have REST API with Go and I tried to create an endpoint where I need to extract users only by their role. I tried different solutions, but can't achieve what I need. This is what I made and I'm not sure how to continue so I can take the users only by exact role. If someone can help me it will be appreciated, because I can't find more advanced stuff in the documentation.
The JSON response from this endpoint is:
[
{
"ID": 1,
"CreatedAt": "2020-12-09T14:40:55.171011+02:00",
"UpdatedAt": "2020-12-09T14:40:55.175537+02:00",
"DeletedAt": null,
"email": "h#go.com",
"password": "$2a$14$KN2wAOnfecAriBW0xeAJke.okEUlcpDHVeuk",
"bearer": "eyJhbGciOiJIUzI1NiIs2lhdCI6MTYwNjMwNTEzNn0.J2wBp8ecA9TebP6L73qZ1OZmo02DwQy9vTySt0fil4c",
"first_name": "H",
"last_name": "Pro",
"phone": "353456",
"salesforce_id": "sfsdddfsdf",
"webflow_id": "wfwfwfaawfw",
"Roles": null
},
{
"ID": 2,
"CreatedAt": "2020-12-09T14:40:55.171011+02:00",
"UpdatedAt": "2020-12-09T14:40:55.175537+02:00",
"DeletedAt": null,
"email": "s#go.com",
"password": "$2wAOnfecAriBW0xeAJke.okEUlcpDHVeuk",
"bearer": "eyJhbGiIs2lhdCI6MTYwNjMwNTEzNn0.J2wBp8ecA9TebP6L73qZ1OZmo02DwQy9vTy0fil4c",
"first_name": "S",
"last_name": "Test",
"phone": "3556",
"salesforce_id": "sfsdf",
"webflow_id": "wfwfwfw",
"Roles": null
}
]
User struct:
type User struct {
gorm.Model
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"bearer"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
SalesforceID string `json:"salesforce_id"`
WebflowID string `json:"webflow_id"`
Roles []*Roles `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;many2many:user_roles;"`
}
Role struct:
type Roles struct {
gorm.Model
Name string `json:"name"`
Users []*User `gorm:"many2many:user_roles"`
}
The sql query I made and works for my case in Postgre:
SELECT * FROM users
JOIN user_roles ON users.id = user_roles.user_id
JOIN roles ON user_roles.roles_id = roles.id
WHERE user_roles.roles_id = 1
the User struct:
type User struct {
gorm.Model
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"bearer"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
SalesforceID string `json:"salesforce_id"`
WebflowID string `json:"webflow_id"`
Roles []Roles `json:"roles" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;many2many:user_roles;"`
}
the struct of Roles
type Roles struct {
gorm.Model
Name string `json:"name"`
Users []*User `gorm:"many2many:user_roles"`
}
the endpoint:
func (h *Handler) GetAllEmployees(c *gin.Context) {
var users []models.User
//var roles []models.Roles //the comment here is because I get error message roles is defined but never used
var roleid int = 1
//you need to extract roleID from your query string or request body
tx := h.db.DB.
Preload("Roles").
Joins("INNER JOIN user_roles ON users.id = user_roles.user.id").
Joins("INNER JOIN roles ON user_roles.roles_id = roles.id").
Where("user_roles.roles_id = ?", roleid).
Find(&users)
if tx.Error != nil {
//handle error
}
c.JSON(200, users)
}
EDIT:
Based on all the answers below, following things need to happen here:
You need to rename Roles struct into Role
You need to load Roles for each object
Filter users by a specific role ID
First, you need to rename the Roles struct to Role, and change references to it:
Role struct
type Role struct {
gorm.Model
Name string `json:"name"`
Users []User `gorm:"many2many:user_roles"`
}
User struct
type User struct {
gorm.Model
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"bearer"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
SalesforceID string `json:"salesforce_id"`
WebflowID string `json:"webflow_id"`
Roles []Role `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;many2many:user_roles;"`
}
Next, modify your code a bit. There is one join that is not necessary here:
func (h *Handler) GetAllEmployeesByRoleID(c *gin.Context) {
var users []models.User
var roleID int = 1
//you need to extract roleID from your query string or request body
tx := h.db.DB.
Preload("Roles").
Joins("INNER JOIN user_roles ur ON ur.user_id = users.id").
Where("ur.role_id = ?", roleID).
Find(&users)
if tx.Error != nil {
//handle error
c.JSON(500, tx.Error)
return
}
c.JSON(200, users)
}
So, you add Preload("Roles") to load all roles that a user can have, as I assumed that, even if you filter by roleID, you want to see all roles that a user can have.
You can use Joins and Where to construct a similar query you already used. A JOIN with the roles table isn't necessary since you have the needed info in the user_roles table.

GraphQL mutation - confusion designing gql tag for Apollo Client

I need help figuring out the GraphQL tag for use with Apollo Client. The Docs don't go far beyond the most basic use case for mutations.
My goal is to have the only required input be an email. If the other variables are present, I would like those to be accepted and create a proposal with all that information.
I have the mutation (in both only email and full variables scenarios) working successfully on the GraphQl Playground (if it helps, you can find it here and test it out, look at the schema, etc.,): https://prisma2-graphql-yoga-shield.now.sh/playground)
mutation {
createOneProposal(
data: {
email: "fake#gmail.com"
name: "Sean"
types: {
create: {
model: PURCHASE
name: "e-commerce"
cost: 600
services: {
create: [
{ service: "Responsive web design" }
{ service: "Another service!" }
{ service: "And yet another service!" }
]
}
}
}
}
) {
created_at
proposal_id
types {
cost
model
name
type_id
services {
service
service_id
}
}
}
}
Producing as a result:
{
"data": {
"createOneProposal": {
"created_at": "2020-02-27T21:28:53.256Z",
"proposal_id": 35,
"types": [
{
"cost": 600,
"model": "PURCHASE",
"name": "e-commerce",
"type_id": 6,
"services": [
{
"service": "Responsive web design",
"service_id": 10
},
{
"service": "Another service!",
"service_id": 11
},
{
"service": "And yet another service!",
"service_id": 12
}
]
}
]
}
}
}
My initial design for the gql tag:
export const NEW_PROPOSAL = gql`
mutation createOneProposal(
$email: String!
$name: String
$cost: Int
$model: Model
$service: Service
) {
createOneProposal(
email: $email
name: $name
cost: $cost
model: $model
service: $service
) {
created_at
proposal_id
types {
services {
service_id
}
}
}
}
`;
But, I get a lot of errors with this.
{"errors":[
{"Variable "$service" cannot be non-input type `"Service`".","locations":[{"line":1,"column":97}]},
{"Unknown argument "email" on field "createOneProposal`" of type "Mutation`".","locations":[{"line":2,"column":21}]},
{"Unknown argument "name" on field "createOneProposal`" of type "Mutation`".","locations":[{"line":2,"column":36}]},
{"Unknown argument"cost" on field "createOneProposal\" of type "Mutation`".","locations":[{"line":2,"column":49}]},
{"Unknown argument "model" on field "createOneProposal`" of type "Mutation`".","locations":[{"line":2,"column":62}]},
{"Unknown argument "service" on field "createOneProposal`" of type "Mutation`".","locations":[{"line":2,"column":77}]},
{"Field "createOneProposal" argument "data" of type "ProposalCreateInput!`" is required, but it was not provided.","locations":[{"line":2,"column":3}]}]}
So, how can I go about this... I figured out the query version (much easier...), but I just can't figure this out!
My schema, if it helps:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("MYSQL_URL_PRISMA2")
}
model Post {
content String #default("")
created_at DateTime #default(now())
post_id Int #default(autoincrement()) #id
published Boolean #default(false)
published_at DateTime?
title String #default("")
author User
}
model Profile {
bio String?
profile_id Int #default(autoincrement()) #id
user_id User
}
model Proposal {
email String #unique
name String?
proposal_id Int #default(autoincrement()) #id
created_at DateTime #default(now())
types Type[]
}
model Type {
cost Int?
name String?
model Model? #default(SUBSCRIPTION)
services Service[]
type_id Int #default(autoincrement()) #id
proposal_id Proposal
}
model Service {
service_id Int #default(autoincrement()) #id
service String?
type_id Type
}
model User {
email String #default("") #unique
name String #default("")
password String #default("")
role Role #default(USER)
user_id Int #default(autoincrement()) #id
posts Post[]
profiles Profile[]
}
enum Role {
USER ADMIN
}
enum Model {
SUBSCRIPTION PURCHASE CUSTOM
}
GraphQL types are categorized as either input types or output types. Input types are used for inputs like variable definitions or argument definitions. Output types are used for typing fields, which are what compose the actual response. Certain types, like scalars and enums, can be used as either an input or an output. However, with objects, there are output object types (sometimes referred to just object types or objects) and input object types.
Service is an output type, so it can't be used where an input type is expected (in this case, a variable definition). Examine the schema generated by Prisma to determine the appropriate type to use.
Thanks to some very needed direction from #xadm, I figured out the structure of the tag! For anyone who comes across this in the future:
mutation createOneProposal($input: ProposalCreateInput!){
createOneProposal(data:$input){
created_at
name
email
proposal_id
type{
cost
description
model
name
type_id
services{
service
cost
service_id
}
}
}
}

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"

Resources