github.com/mailru/activerecord@v1.12.2/internal/pkg/checker/checker.go (about)

     1  package checker
     2  
     3  import (
     4  	"log"
     5  	"strconv"
     6  
     7  	"github.com/mailru/activerecord/internal/pkg/arerror"
     8  	"github.com/mailru/activerecord/internal/pkg/ds"
     9  	"github.com/mailru/activerecord/pkg/octopus"
    10  )
    11  
    12  // Checker структура описывающая checker
    13  type Checker struct {
    14  	files map[string]*ds.RecordPackage
    15  }
    16  
    17  // Init конструктор checker-а
    18  func Init(files map[string]*ds.RecordPackage) *Checker {
    19  	checker := Checker{
    20  		files: files,
    21  	}
    22  
    23  	return &checker
    24  }
    25  
    26  // checkBackend проверка на указание бекенда
    27  // В данный момент поддерживается один и толлько один бекенд
    28  func checkBackend(cl *ds.RecordPackage) error {
    29  	if len(cl.Backends) == 0 {
    30  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckBackendEmpty}
    31  	}
    32  
    33  	if len(cl.Backends) > 1 {
    34  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckPkgBackendToMatch}
    35  	}
    36  
    37  	return nil
    38  }
    39  
    40  // checkLinkedObject проверка существования сущностей на которые ссылаются другие сущности
    41  func checkLinkedObject(cl *ds.RecordPackage, linkedObjects map[string]string) error {
    42  	for _, fobj := range cl.FieldsObjectMap {
    43  		if _, ok := linkedObjects[fobj.ObjectName]; !ok {
    44  			return &arerror.ErrCheckPackageLinkedDecl{Pkg: cl.Namespace.PackageName, Object: fobj.ObjectName, Err: arerror.ErrCheckObjectNotFound}
    45  		}
    46  	}
    47  
    48  	return nil
    49  }
    50  
    51  // checkNamespace проверка правильного описания неймспейса у сущности
    52  func checkNamespace(ns ds.NamespaceDeclaration) error {
    53  	if ns.PackageName == "" || ns.PublicName == "" {
    54  		return &arerror.ErrCheckPackageNamespaceDecl{Pkg: ns.PackageName, Name: ns.PublicName, Err: arerror.ErrCheckEmptyNamespace}
    55  	}
    56  
    57  	return nil
    58  }
    59  
    60  // checkFields функция проверки правильности описания полей структуры
    61  // - указан допустимый тип полей
    62  // - описаны все необходимые сериализаторы для полей с сериализацией
    63  // - поля с мутаторами не могут быть праймари клчом
    64  // - поля с мутаторами не могут быть сериализованными
    65  // - поля с мутаторами не могут являться ссылками на другие сущности
    66  // - сериализуемые поля не могут быть ссылками на другие сущности
    67  // - есть первичный ключ
    68  // - имена сущностей на которые ссылаемся на могут пересекаться с именами полей
    69  //
    70  //nolint:gocognit,gocyclo
    71  func checkFields(cl *ds.RecordPackage) error {
    72  	if len(cl.Fields) > 0 && len(cl.ProcOutFields) > 0 {
    73  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckFieldsManyDecl}
    74  	}
    75  
    76  	if !cl.ProcOutFields.Validate() {
    77  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckFieldsOrderDecl}
    78  	}
    79  
    80  	primaryFound := false
    81  
    82  	octopusAvailFormat := map[octopus.Format]bool{}
    83  	for _, form := range octopus.AllFormat {
    84  		octopusAvailFormat[form] = true
    85  	}
    86  
    87  	for _, fld := range cl.Fields {
    88  		if _, ex := octopusAvailFormat[fld.Format]; !ex {
    89  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
    90  		}
    91  
    92  		if len(fld.Serializer) > 0 {
    93  			if _, ex := cl.SerializerMap[fld.Serializer[0]]; len(cl.SerializerMap) == 0 || !ex {
    94  				return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
    95  			}
    96  
    97  		}
    98  
    99  		customMutCnt := 0
   100  		if len(fld.Mutators) > 0 {
   101  			fieldMutatorsChecker := ds.GetFieldMutatorsChecker()
   102  
   103  			for _, m := range fld.Mutators {
   104  				_, ex := fieldMutatorsChecker[m]
   105  
   106  				md, ok := cl.MutatorMap[m]
   107  				if ok {
   108  					customMutCnt++
   109  					if customMutCnt > 1 {
   110  						return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorInvalid}
   111  					}
   112  				}
   113  
   114  				if !ok && !ex {
   115  					return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorInvalid}
   116  				}
   117  
   118  				if len(md.PartialFields) > 0 && len(fld.Serializer) == 0 {
   119  					return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorTypeHasNotSerializer}
   120  				}
   121  			}
   122  
   123  			if fld.PrimaryKey {
   124  				return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictPK}
   125  			}
   126  
   127  			if fld.ObjectLink != "" {
   128  				return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictObject}
   129  			}
   130  		}
   131  
   132  		if len(fld.Serializer) > 0 && fld.ObjectLink != "" {
   133  			return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerConflictObject}
   134  		}
   135  
   136  		if fo, ex := cl.FieldsObjectMap[fld.Name]; ex {
   137  			return &arerror.ErrParseTypeFieldStructDecl{Name: fo.Name, Err: arerror.ErrRedefined}
   138  		}
   139  
   140  		if fld.PrimaryKey {
   141  			primaryFound = true
   142  		}
   143  	}
   144  
   145  	for _, fld := range cl.ProcOutFields.List() {
   146  		if _, ex := octopusAvailFormat[fld.Format]; !ex {
   147  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
   148  		}
   149  
   150  		if len(fld.Serializer) > 0 {
   151  			if _, ex := cl.SerializerMap[fld.Serializer[0]]; !ex {
   152  				return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
   153  			}
   154  		}
   155  
   156  		if fld.Type == 0 {
   157  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldTypeNotFound}
   158  		}
   159  	}
   160  
   161  	octopusProcAvailFormat := map[octopus.Format]bool{}
   162  	for _, form := range octopus.AllProcFormat {
   163  		octopusProcAvailFormat[form] = true
   164  	}
   165  
   166  	for _, fld := range cl.ProcInFields {
   167  		if _, ex := octopusProcAvailFormat[fld.Format]; !ex {
   168  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
   169  		}
   170  
   171  		if len(fld.Serializer) > 0 {
   172  			if _, ex := cl.SerializerMap[fld.Serializer[0]]; !ex {
   173  				return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
   174  			}
   175  		}
   176  
   177  		if fld.Type == 0 {
   178  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldTypeNotFound}
   179  		}
   180  	}
   181  
   182  	if len(cl.Fields) == 0 && len(cl.ProcOutFields) == 0 {
   183  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckFieldsEmpty}
   184  	}
   185  
   186  	if len(cl.Fields) > 0 && !primaryFound {
   187  		return &arerror.ErrCheckPackageIndexDecl{Pkg: cl.Namespace.PackageName, Index: "primary", Err: arerror.ErrIndexNotExist}
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // Check основная функция, которая запускает процесс проверки
   194  // Должна вызываться только после окончания процесса парсинга всех деклараций
   195  func Check(files map[string]*ds.RecordPackage, linkedObjects map[string]string) error {
   196  	for _, cl := range files {
   197  		if err := checkBackend(cl); err != nil {
   198  			return err
   199  		}
   200  
   201  		if err := checkNamespace(cl.Namespace); err != nil {
   202  			return err
   203  		}
   204  
   205  		if err := checkLinkedObject(cl, linkedObjects); err != nil {
   206  			return err
   207  		}
   208  
   209  		if err := checkFields(cl); err != nil {
   210  			return err
   211  		}
   212  
   213  		// Бекендозависимые проверки
   214  		for _, backend := range cl.Backends {
   215  			switch backend {
   216  			case "tarantool16":
   217  				fallthrough
   218  			case "tarantool2":
   219  				if err := checkTarantool(cl); err != nil {
   220  					return err
   221  				}
   222  			case "tarantool15":
   223  				fallthrough
   224  			case "octopus":
   225  				if err := checkOctopus(cl); err != nil {
   226  					return err
   227  				}
   228  			default:
   229  				return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Backend: backend, Err: arerror.ErrCheckBackendUnknown}
   230  			}
   231  		}
   232  	}
   233  
   234  	return nil
   235  }
   236  
   237  //nolint:gocognit,gocyclo
   238  func checkOctopus(cl *ds.RecordPackage) error {
   239  	if cl.Server.Host == "" && cl.Server.Conf == "" {
   240  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckServerEmpty}
   241  	}
   242  
   243  	if cl.Server.Host == "" && cl.Server.Port != "" {
   244  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckPortEmpty}
   245  	}
   246  
   247  	if cl.Server.Host != "" && cl.Server.Conf != "" {
   248  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckServerConflict}
   249  	}
   250  
   251  	for _, fl := range cl.Fields {
   252  		if (fl.Format == "string" || fl.Format == "[]byte") && fl.Size == 0 {
   253  			log.Printf("Warn: field `%s` declaration. Field with type string or []byte not contain size.", fl.Name)
   254  		}
   255  	}
   256  
   257  	for _, ind := range cl.Indexes {
   258  		if len(ind.Fields) == 0 {
   259  			return &arerror.ErrCheckPackageIndexDecl{Pkg: cl.Namespace.PackageName, Index: ind.Name, Err: arerror.ErrCheckFieldIndexEmpty}
   260  		}
   261  	}
   262  
   263  	if len(cl.Fields) > 0 {
   264  		_, err := strconv.ParseInt(cl.Namespace.ObjectName, 10, 64)
   265  		if err != nil {
   266  			return &arerror.ErrCheckPackageNamespaceDecl{Pkg: cl.Namespace.PackageName, Name: cl.Namespace.ObjectName, Err: arerror.ErrCheckFieldInvalidFormat}
   267  		}
   268  	}
   269  
   270  	octopusProcAvailFormat := map[octopus.Format]bool{}
   271  	for _, form := range []octopus.Format{octopus.String, octopus.StringArray, octopus.ByteArray} {
   272  		octopusProcAvailFormat[form] = true
   273  	}
   274  
   275  	for _, fld := range cl.ProcInFields {
   276  		if _, ex := octopusProcAvailFormat[fld.Format]; !ex {
   277  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldInvalidFormat}
   278  		}
   279  
   280  		if fld.Format != octopus.String && len(fld.Serializer) == 0 {
   281  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
   282  		}
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  //nolint:gocognit,gocyclo
   289  func checkTarantool(cl *ds.RecordPackage) error {
   290  	if cl.Server.Host == "" && cl.Server.Conf == "" {
   291  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckServerEmpty}
   292  	}
   293  
   294  	if cl.Server.Host == "" && cl.Server.Port != "" {
   295  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckPortEmpty}
   296  	}
   297  
   298  	if cl.Server.Host != "" && cl.Server.Conf != "" {
   299  		return &arerror.ErrCheckPackageDecl{Pkg: cl.Namespace.PackageName, Err: arerror.ErrCheckServerConflict}
   300  	}
   301  
   302  	for _, fl := range cl.Fields {
   303  		if (fl.Format == "string" || fl.Format == "[]byte") && fl.Size == 0 {
   304  			log.Printf("Warn: field `%s` declaration. Field with type string or []byte not contain size.", fl.Name)
   305  		}
   306  	}
   307  
   308  	for _, ind := range cl.Indexes {
   309  		if len(ind.Fields) == 0 {
   310  			return &arerror.ErrCheckPackageIndexDecl{Pkg: cl.Namespace.PackageName, Index: ind.Name, Err: arerror.ErrCheckFieldIndexEmpty}
   311  		}
   312  	}
   313  
   314  	for _, fld := range cl.ProcInFields {
   315  		if fld.Format != octopus.String && len(fld.Serializer) == 0 {
   316  			return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound}
   317  		}
   318  	}
   319  
   320  	return nil
   321  }