В Go нет классов и привычной реализации ООП. Вместо классов в языке используются структуры — наборы полей, имеющих название и тип данных. Объявление структуры имеет следующий вид:
type Person struct {
// [название поля] [тип данных]
Name string
Age int
}
func main() {
p := Person{Name: "John", Age: 25}
p.Name // "John"
p.Age // 25
}
Структуру можно инициализировать, не передавая значения. В этом случае каждое поле примет свое «нулевое» значение:
func main() {
p := Person{}
p.Name // ""
p.Age // 0
}
Регистр первой буквы в названии структуры и полей означает публичность точно так же, как в переменных и функциях. Если первая буква заглавная, то структуру можно инициализировать во внешних пакетах. В противном случае она доступна только в рамках текущего пакета:
type Person struct { // структура публична
Name string // поле публично
wallet wallet // поле приватно: можно обращаться только внутри текущего пакета
}
type wallet struct { // структура приватна: можно инициализировать только внутри текущего пакета
id string
moneyAmount float64
}
У любого поля структуры можно указать теги. Они используются для метаинформации о поле для сериализации, валидации, маппинга данных из БД и тд. Тег указывается после типа данных через бектики:
type User struct {
ID int64 `json:"id" validate:"required"`
Email string `json:"email" validate:"required,email"`
FirstName string `json:"first_name" validate:"required"`
}
Тег json
используется для названий полей при сериализации/десериализации структуры в json и обратно:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
}
func main() {
u := User{}
u.ID = 22
u.Email = "test@test.com"
u.FirstName = "John"
bs, _ := json.Marshal(u)
fmt.Println(string(bs)) // {"id":22,"email":"test@test.com","first_name":"John"}
}
Тег validate
используется Go-валидатором. В следующем примере присутствует вызов функции у структуры v.Struct(u)
. Функции структур — методы — мы разберем подробно в соответствующем уроке, а пока просто посмотрите, как происходит вызов:
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type User struct {
ID int64 `validate:"required"`
Email string `validate:"required,email"`
FirstName string `validate:"required"`
}
func main() {
// создали пустую структуру, чтобы проверить валидацию
u := User{}
// создаем валидатор
v := validator.New()
// метод Struct валидирует переданную структуру и возвращает ошибку `error`, если какое-то поле некорректно
fmt.Println(v.Struct(u))
}
Вывод программы:
Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag
Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag
Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag
На сервер приходит HTTP-запрос. Тело запроса парсится и мапится в модель. Сразу работать с моделью нельзя, потому что данные могут быть неверными.
Реализуйте функцию Validate(req UserCreateRequest) string
, которая валидирует запрос и возвращает строку с ошибкой "invalid request", если модель невалидна. Если запрос корректный, то функция возвращает пустую строку. Правила валидации и структура модели описаны ниже. Не используйте готовые библиотеки и опишите правила самостоятельно.
type UserCreateRequest struct {
FirstName string // не может быть пустым; не может содержать пробелы
Age int // не может быть 0 или отрицательным; не может быть больше 150
}
Наличие пробелов можно проверить с помощью функции strings.Contains(firstName, " ")
.
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1package solution
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7)
8
9func TestValidate(t *testing.T) {
10 a := assert.New(t)
11 a.Equal("invalid request", Validate(UserCreateRequest{
12 FirstName: "",
13 Age: 0,
14 }))
15 a.Equal("invalid request", Validate(UserCreateRequest{
16 FirstName: "John",
17 Age: -5,
18 }))
19 a.Equal("invalid request", Validate(UserCreateRequest{
20 FirstName: "Andy",
21 Age: 0,
22 }))
23 a.Equal("invalid request", Validate(UserCreateRequest{
24 FirstName: "Karl",
25 Age: 151,
26 }))
27 a.Equal("invalid request", Validate(UserCreateRequest{
28 FirstName: "",
29 Age: 5,
30 }))
31 a.Equal("invalid request", Validate(UserCreateRequest{
32 FirstName: " Henry",
33 Age: 15,
34 }))
35 a.Equal("invalid request", Validate(UserCreateRequest{
36 FirstName: "John Smith",
37 Age: 15,
38 }))
39 a.Equal("", Validate(UserCreateRequest{
40 FirstName: "John",
41 Age: 150,
42 }))
43 a.Equal("", Validate(UserCreateRequest{
44 FirstName: "Susan",
45 Age: 30,
46 }))
47}
48
Решение учителя откроется через: