brown and pink pendant

Como utilizar tags customizadas

A utilização de tags ajuda muito na hora de escrever funções genéricas. Um exemplo disso é a função json.Marshal. Não importa como sua struct está estruturada, se ela tiver a tag json no atributo, a função consegue fazer o que precisa ser feito.

Se você não sabe do que eu estou falando, te convido à ler o post “O que são e como utilizar tags”. Lá dou todo o contexto para que você possa entender melhor o que vamos fazer aqui nesse post.

Além do package json, existe uma infinidade de outros packages que tiram vantagem da utilização das tags. Esses packages vão desde encoders até ORMs.

Embora existam packages que ajudem na validação de campos, para fins didáticos, vamos criar uma função que utiliza da tag required para saber se um campo é ou não obrigatório.

func validateFields(stc any) error {

}

Bem simples, nossa função irá receber uma interface qualquer e, caso um campo seja obrigatório e não esteja preenchido, retornar um erro.

Adicionando reflection

Para iniciar a implementação da função, vamos utilizar duas funções do package reflect. A primeira será a função reflect.TypeOf, e a segunda a reflect.ValueOf.

A primeira função irá nos retornar uma struct do tipo reflect.Type. Com ela, podemos acessar o tipo real da interface recebida, assim como seus campos.

Já a segunda nos retornará uma struct do tipo reflect.Value. Utilizaremos ela para acessar os valores da interface recebida.

func validateFields(stc any) error {
	obj := reflect.TypeOf(stc)
	value := reflect.ValueOf(stc)

}

Próximo passo é iterar por cada um dos campos. Em cada iteração precisamos obter informações sobre o campo e sobre o valor que ele contém. Para isso, vamos utilizar o método Field. Ambas as structs implementam esse método.

Um fato curioso sobre o método Field, é que ele precisa receber o índice do campo que queremos obter as informações.

Para saber a quantidade de campos que a interface contém, vamos utilizar o método NumField. Esse método é encontrado na struct reflect.Type.

func validateFields(stc any) error {
	obj := reflect.TypeOf(stc)
	value := reflect.ValueOf(stc)

	for i := 0; i < obj.NumField(); i++ {
		f := obj.Field(i)
		v := value.Field(i)
  }
}

Na sequência, vamos pegar o valor da tag required. Caso ela esteja como false, vamos dar um continue.

func validateFields(stc any) error {
	obj := reflect.TypeOf(stc)
	value := reflect.ValueOf(stc)

	for i := 0; i < obj.NumField(); i++ {
		f := obj.Field(i)
		v := value.Field(i)

		required := f.Tag.Get("required")
		if required == "false" {
			continue
		}
  }
}

Caso o valor de required seja true, vamos validar se o campo está preenchido.

Como cada tipo de dado tem sua forma “vazia”, precisamos criar um switch/case para validar o tipo, e só então verificar se o campo está vazio ou não.

func validateFields(stc any) error {
	obj := reflect.TypeOf(stc)
	value := reflect.ValueOf(stc)

	for i := 0; i < obj.NumField(); i++ {
		f := obj.Field(i)
		v := value.Field(i)

		required := f.Tag.Get("required")
		if required == "false" {
			continue
		}

		switch v.Kind() {
		case reflect.String:
			if v.String() == "" {
				return fmt.Errorf("Field %s is required", f.Name)
			}
		case reflect.Uint8:
			if v.Uint() == 0 {
				return fmt.Errorf("Field %s is required", f.Name)
			}
		}
	}

	return nil
}

Note que, para pegar as informações da tag, utilizamos o Field da struct reflect.Type. Já para obter informações sobre o valor do campo, utilizamos o Field da struct reflect.Value.

Testando a tag customizada

Para testar, vamos criar uma struct e escrever um pequeno código para utilizar a função que criamos.

type Person struct {
	FirstName string `required:"true"`
	LastName  string `required:"false"`
	Age       uint8  `required:"true"`
}

func main() {
	person := Person{
		FirstName: "Tiago",
	}

	err := validateFields(person)

	fmt.Println(err)
}

Como o campo Age não foi preenchido, ao executar esse código vamos obter a seguinte saída.

$ Field Age is required

Adicionando um valor ao campo Age e executando novamente, o resultado da saída será:

$ <nil>

Espero que essa abordagem possa te ajudar a reaproveitar melhor suas funções.

Até a próxima!


Se inscreva na nossa newsletter

* indicates required

Deixe uma resposta