2020-03-14 14:24:17 +00:00
package config
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
2020-03-14 23:27:54 +00:00
2020-03-16 08:20:00 +00:00
"git.xdrm.io/go/aicra/datatype"
2020-03-14 14:24:17 +00:00
)
// Parse builds a server configuration from a json reader and checks for most format errors.
2020-03-14 23:27:54 +00:00
// you can provide additional DataTypes as variadic arguments
2020-03-16 08:20:00 +00:00
func Parse ( r io . Reader , dtypes ... datatype . T ) ( * Server , error ) {
2020-03-14 23:27:54 +00:00
server := & Server {
2020-03-16 08:20:00 +00:00
Types : make ( [ ] datatype . T , 0 ) ,
2020-03-16 08:01:51 +00:00
Services : make ( [ ] * Service , 0 ) ,
2020-03-14 23:27:54 +00:00
}
// add data types
for _ , dtype := range dtypes {
2020-03-16 08:01:51 +00:00
server . Types = append ( server . Types , dtype )
2020-03-14 23:27:54 +00:00
}
2020-03-14 14:24:17 +00:00
2020-03-14 23:27:54 +00:00
// parse JSON
2020-03-16 08:01:51 +00:00
if err := json . NewDecoder ( r ) . Decode ( & server . Services ) ; err != nil {
2020-03-14 14:24:17 +00:00
return nil , fmt . Errorf ( "%s: %w" , ErrRead , err )
}
2020-03-14 23:27:54 +00:00
// check services
if err := server . checkAndFormat ( ) ; err != nil {
2020-03-14 14:24:17 +00:00
return nil , fmt . Errorf ( "%s: %w" , ErrFormat , err )
}
2020-03-14 23:27:54 +00:00
// check collisions
if err := server . collide ( ) ; err != nil {
return nil , fmt . Errorf ( "%s: %w" , ErrFormat , err )
2020-03-14 14:24:17 +00:00
}
2020-03-14 23:27:54 +00:00
return server , nil
2020-03-14 14:24:17 +00:00
}
2020-03-16 09:56:26 +00:00
// Find a service matching an incoming HTTP request
func ( server Server ) Find ( r * http . Request ) * Service {
for _ , service := range server . Services {
if matches := service . Match ( r ) ; matches {
return service
}
}
return nil
}
2020-03-14 14:24:17 +00:00
// collide returns if there is collision between services
2020-03-14 23:27:54 +00:00
func ( server * Server ) collide ( ) error {
2020-03-16 08:01:51 +00:00
length := len ( server . Services )
2020-03-14 23:27:54 +00:00
// for each service combination
for a := 0 ; a < length ; a ++ {
for b := a + 1 ; b < length ; b ++ {
2020-03-16 08:01:51 +00:00
aService := server . Services [ a ]
bService := server . Services [ b ]
2020-03-14 23:27:54 +00:00
// ignore different method
if aService . Method != bService . Method {
continue
}
2020-03-16 08:26:10 +00:00
aParts := SplitURL ( aService . Pattern )
bParts := SplitURL ( bService . Pattern )
2020-03-14 23:27:54 +00:00
// not same size
if len ( aParts ) != len ( bParts ) {
continue
}
2020-03-21 14:48:59 +00:00
partErrors := make ( [ ] error , 0 )
2020-03-14 23:27:54 +00:00
// for each part
for pi , aPart := range aParts {
bPart := bParts [ pi ]
aIsCapture := len ( aPart ) > 1 && aPart [ 0 ] == '{'
bIsCapture := len ( bPart ) > 1 && bPart [ 0 ] == '{'
// both captures -> as we cannot check, consider a collision
if aIsCapture && bIsCapture {
2020-03-21 14:57:35 +00:00
partErrors = append ( partErrors , fmt . Errorf ( "(%s '%s') vs (%s '%s'): %w (path %s and %s)" , aService . Method , aService . Pattern , bService . Method , bService . Pattern , ErrPatternCollision , aPart , bPart ) )
2020-03-21 14:48:59 +00:00
continue
2020-03-14 23:27:54 +00:00
}
// no capture -> check equal
if ! aIsCapture && ! bIsCapture {
if aPart == bPart {
2020-03-21 14:57:35 +00:00
partErrors = append ( partErrors , fmt . Errorf ( "(%s '%s') vs (%s '%s'): %w (same path '%s')" , aService . Method , aService . Pattern , bService . Method , bService . Pattern , ErrPatternCollision , aPart ) )
2020-03-21 14:58:05 +00:00
continue
2020-03-14 23:27:54 +00:00
}
}
// A captures B -> check type (B is A ?)
if aIsCapture {
input , exists := aService . Input [ aPart ]
// fail if no type or no validator
if ! exists || input . Validator == nil {
2020-03-21 14:57:35 +00:00
partErrors = append ( partErrors , fmt . Errorf ( "(%s '%s') vs (%s '%s'): %w (invalid type for %s)" , aService . Method , aService . Pattern , bService . Method , bService . Pattern , ErrPatternCollision , aPart ) )
2020-03-21 14:48:59 +00:00
continue
2020-03-14 23:27:54 +00:00
}
// fail if not valid
2020-03-21 14:48:59 +00:00
if _ , valid := input . Validator ( bPart ) ; valid {
2020-03-21 14:57:35 +00:00
partErrors = append ( partErrors , fmt . Errorf ( "(%s '%s') vs (%s '%s'): %w (%s captures '%s')" , aService . Method , aService . Pattern , bService . Method , bService . Pattern , ErrPatternCollision , aPart , bPart ) )
2020-03-21 14:48:59 +00:00
continue
2020-03-14 23:27:54 +00:00
}
// B captures A -> check type (A is B ?)
2020-03-21 14:58:05 +00:00
} else if bIsCapture {
2020-03-14 23:27:54 +00:00
input , exists := bService . Input [ bPart ]
// fail if no type or no validator
if ! exists || input . Validator == nil {
2020-03-21 14:57:35 +00:00
partErrors = append ( partErrors , fmt . Errorf ( "(%s '%s') vs (%s '%s'): %w (invalid type for %s)" , aService . Method , aService . Pattern , bService . Method , bService . Pattern , ErrPatternCollision , bPart ) )
2020-03-21 14:48:59 +00:00
continue
2020-03-14 23:27:54 +00:00
}
// fail if not valid
2020-03-21 14:48:59 +00:00
if _ , valid := input . Validator ( aPart ) ; valid {
2020-03-21 14:57:35 +00:00
partErrors = append ( partErrors , fmt . Errorf ( "(%s '%s') vs (%s '%s'): %w (%s captures '%s')" , aService . Method , aService . Pattern , bService . Method , bService . Pattern , ErrPatternCollision , bPart , aPart ) )
2020-03-21 14:48:59 +00:00
continue
2020-03-14 23:27:54 +00:00
}
}
2020-03-21 14:48:59 +00:00
partErrors = append ( partErrors , nil )
}
// if at least 1 url part does not match -> ok
var firstError error
oneMismatch := false
for _ , err := range partErrors {
if err != nil && firstError == nil {
firstError = err
}
if err == nil {
oneMismatch = true
continue
}
}
if ! oneMismatch {
return firstError
2020-03-14 23:27:54 +00:00
}
}
}
return nil
2020-03-14 14:24:17 +00:00
}
// checkAndFormat checks for errors and missing fields and sets default values for optional fields.
2020-03-14 23:27:54 +00:00
func ( server Server ) checkAndFormat ( ) error {
2020-03-16 08:01:51 +00:00
for _ , service := range server . Services {
2020-03-14 14:24:17 +00:00
// check method
err := service . checkMethod ( )
if err != nil {
return fmt . Errorf ( "%s '%s' [method]: %w" , service . Method , service . Pattern , err )
}
// check pattern
service . Pattern = strings . Trim ( service . Pattern , " \t\r\n" )
err = service . checkPattern ( )
if err != nil {
return fmt . Errorf ( "%s '%s' [path]: %w" , service . Method , service . Pattern , err )
}
// check description
if len ( strings . Trim ( service . Description , " \t\r\n" ) ) < 1 {
return fmt . Errorf ( "%s '%s' [description]: %w" , service . Method , service . Pattern , ErrMissingDescription )
}
// check input parameters
2020-03-16 08:01:51 +00:00
err = service . checkAndFormatInput ( server . Types )
2020-03-14 14:24:17 +00:00
if err != nil {
return fmt . Errorf ( "%s '%s' [in]: %w" , service . Method , service . Pattern , err )
}
2020-03-16 09:56:26 +00:00
// fail if a brace capture remains undefined
for _ , capture := range service . Captures {
if capture . Ref == nil {
return fmt . Errorf ( "%s '%s' [in]: %s: %w" , service . Method , service . Pattern , capture . Name , ErrUndefinedBraceCapture )
}
}
2020-03-14 14:24:17 +00:00
}
return nil
}