mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
143 lines
4.1 KiB
Go
143 lines
4.1 KiB
Go
// Copyright [2019] LinkedIn Corp. Licensed under the Apache License, Version
|
|
// 2.0 (the "License"); you may not use this file except in compliance with the
|
|
// License. You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
package goavro
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const nullNamespace = ""
|
|
|
|
// ErrInvalidName is the error returned when one or more parts of an Avro name
|
|
// is invalid.
|
|
type ErrInvalidName struct {
|
|
Message string
|
|
}
|
|
|
|
func (e ErrInvalidName) Error() string {
|
|
return "schema name ought to " + e.Message
|
|
}
|
|
|
|
// NOTE: This function designed to work with name components, after they have
|
|
// been split on the period rune.
|
|
func isRuneInvalidForFirstCharacter(r rune) bool {
|
|
return (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && r != '_'
|
|
}
|
|
|
|
func isRuneInvalidForOtherCharacters(r rune) bool {
|
|
return isRuneInvalidForFirstCharacter(r) && (r < '0' || r > '9')
|
|
}
|
|
|
|
func checkNameComponent(s string) error {
|
|
err := checkString(s)
|
|
if err != nil {
|
|
return &ErrInvalidName{err.Error()}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func checkString(s string) error {
|
|
if len(s) == 0 {
|
|
return errors.New("be non-empty string")
|
|
}
|
|
if strings.IndexFunc(s[:1], isRuneInvalidForFirstCharacter) != -1 {
|
|
return errors.New("start with [A-Za-z_]: " + s)
|
|
}
|
|
if strings.IndexFunc(s[1:], isRuneInvalidForOtherCharacters) != -1 {
|
|
return errors.New("have second and remaining characters contain only [A-Za-z0-9_]: " + s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// name describes an Avro name in terms of its full name and namespace.
|
|
type name struct {
|
|
fullName string // the instance's Avro name
|
|
namespace string // for use when building new name from existing one
|
|
}
|
|
|
|
// newName returns a new Name instance after first ensuring the arguments do not
|
|
// violate any of the Avro naming rules.
|
|
func newName(n, ns, ens string) (*name, error) {
|
|
var nn name
|
|
|
|
if index := strings.LastIndexByte(n, '.'); index > -1 {
|
|
// inputName does contain a dot, so ignore everything else and use it as the full name
|
|
nn.fullName = n
|
|
nn.namespace = n[:index]
|
|
} else {
|
|
// inputName does not contain a dot, therefore is not the full name
|
|
if ns != nullNamespace {
|
|
// if namespace provided in the schema in the same schema level, use it
|
|
nn.fullName = ns + "." + n
|
|
nn.namespace = ns
|
|
} else if ens != nullNamespace {
|
|
// otherwise if enclosing namespace provided, use it
|
|
nn.fullName = ens + "." + n
|
|
nn.namespace = ens
|
|
} else {
|
|
// otherwise no namespace, so use null namespace, the empty string
|
|
nn.fullName = n
|
|
}
|
|
}
|
|
|
|
// verify all components of the full name for adherence to Avro naming rules
|
|
for i, component := range strings.Split(nn.fullName, ".") {
|
|
if i == 0 && RelaxedNameValidation && component == "" {
|
|
continue
|
|
}
|
|
if err := checkNameComponent(component); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &nn, nil
|
|
}
|
|
|
|
var (
|
|
// RelaxedNameValidation causes name validation to allow the first component
|
|
// of an Avro namespace to be the empty string.
|
|
RelaxedNameValidation bool
|
|
)
|
|
|
|
func newNameFromSchemaMap(enclosingNamespace string, schemaMap map[string]interface{}) (*name, error) {
|
|
var nameString, namespaceString string
|
|
|
|
name, ok := schemaMap["name"]
|
|
if !ok {
|
|
return nil, errors.New("schema ought to have name key")
|
|
}
|
|
nameString, ok = name.(string)
|
|
if !ok || nameString == nullNamespace {
|
|
return nil, fmt.Errorf("schema name ought to be non-empty string; received: %T: %v", name, name)
|
|
}
|
|
if namespace, ok := schemaMap["namespace"]; ok {
|
|
namespaceString, ok = namespace.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("schema namespace, if provided, ought to be a string; received: %T: %v", namespace, namespace)
|
|
}
|
|
}
|
|
|
|
return newName(nameString, namespaceString, enclosingNamespace)
|
|
}
|
|
|
|
func (n *name) String() string {
|
|
return n.fullName
|
|
}
|
|
|
|
// short returns the name without the prefixed namespace.
|
|
func (n *name) short() string {
|
|
if index := strings.LastIndexByte(n.fullName, '.'); index > -1 {
|
|
return n.fullName[index+1:]
|
|
}
|
|
return n.fullName
|
|
}
|