github.com/CiscoM31/godata@v1.0.10/url_parser.go (about)

     1  package godata
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/url"
     7  	"strings"
     8  )
     9  
    10  // Parse a request from the HTTP server and format it into a GoDaataRequest type
    11  // to be passed to a provider to produce a result.
    12  func ParseRequest(ctx context.Context, path string, query url.Values) (*GoDataRequest, error) {
    13  	r := &GoDataRequest{
    14  		RequestKind: RequestKindUnknown,
    15  	}
    16  
    17  	if err := r.ParseUrlPath(path); err != nil {
    18  		return nil, err
    19  	}
    20  	if err := r.ParseUrlQuery(ctx, query); err != nil {
    21  		return nil, err
    22  	}
    23  	return r, nil
    24  }
    25  
    26  // Compare a request to a given service, and validate the semantics and update
    27  // the request with semantics included
    28  func (req *GoDataRequest) SemanticizeRequest(service *GoDataService) error {
    29  
    30  	// if request kind is a resource
    31  	for segment := req.FirstSegment; segment != nil; segment = segment.Next {
    32  		err := SemanticizePathSegment(segment, service)
    33  		if err != nil {
    34  			return err
    35  		}
    36  	}
    37  
    38  	switch req.LastSegment.SemanticReference.(type) {
    39  	case *GoDataEntitySet:
    40  		entitySet := req.LastSegment.SemanticReference.(*GoDataEntitySet)
    41  		entityType, err := service.LookupEntityType(entitySet.EntityType)
    42  		if err != nil {
    43  			return err
    44  		}
    45  		err = SemanticizeFilterQuery(req.Query.Filter, service, entityType)
    46  		if err != nil {
    47  			return err
    48  		}
    49  		err = SemanticizeExpandQuery(req.Query.Expand, service, entityType)
    50  		if err != nil {
    51  			return err
    52  		}
    53  		err = SemanticizeSelectQuery(req.Query.Select, service, entityType)
    54  		if err != nil {
    55  			return err
    56  		}
    57  		err = SemanticizeOrderByQuery(req.Query.OrderBy, service, entityType)
    58  		if err != nil {
    59  			return err
    60  		}
    61  		// TODO: disallow invalid query params
    62  	case *GoDataEntityType:
    63  		entityType := req.LastSegment.SemanticReference.(*GoDataEntityType)
    64  		if err := SemanticizeExpandQuery(req.Query.Expand, service, entityType); err != nil {
    65  			return err
    66  		}
    67  		if err := SemanticizeSelectQuery(req.Query.Select, service, entityType); err != nil {
    68  			return err
    69  		}
    70  	}
    71  
    72  	if req.LastSegment.SemanticType == SemanticTypeMetadata {
    73  		req.RequestKind = RequestKindMetadata
    74  	} else if req.LastSegment.SemanticType == SemanticTypeRef {
    75  		req.RequestKind = RequestKindRef
    76  	} else if req.LastSegment.SemanticType == SemanticTypeEntitySet {
    77  		if req.LastSegment.Identifier == nil {
    78  			req.RequestKind = RequestKindCollection
    79  		} else {
    80  			req.RequestKind = RequestKindEntity
    81  		}
    82  	} else if req.LastSegment.SemanticType == SemanticTypeCount {
    83  		req.RequestKind = RequestKindCount
    84  	} else if req.FirstSegment == nil && req.LastSegment == nil {
    85  		req.RequestKind = RequestKindService
    86  	}
    87  
    88  	return nil
    89  }
    90  
    91  func (req *GoDataRequest) ParseUrlPath(path string) error {
    92  	parts := strings.Split(path, "/")
    93  	req.FirstSegment = &GoDataSegment{
    94  		RawValue:   parts[0],
    95  		Name:       ParseName(parts[0]),
    96  		Identifier: ParseIdentifiers(parts[0]),
    97  	}
    98  	currSegment := req.FirstSegment
    99  	for _, v := range parts[1:] {
   100  		temp := &GoDataSegment{
   101  			RawValue:   v,
   102  			Name:       ParseName(v),
   103  			Identifier: ParseIdentifiers(v),
   104  			Prev:       currSegment,
   105  		}
   106  		currSegment.Next = temp
   107  		currSegment = temp
   108  	}
   109  	req.LastSegment = currSegment
   110  
   111  	return nil
   112  }
   113  
   114  func SemanticizePathSegment(segment *GoDataSegment, service *GoDataService) error {
   115  	var err error
   116  
   117  	if segment.RawValue == "$metadata" {
   118  		if segment.Next != nil || segment.Prev != nil {
   119  			return BadRequestError("A metadata segment must be alone.")
   120  		}
   121  
   122  		segment.SemanticType = SemanticTypeMetadata
   123  		segment.SemanticReference = service.Metadata
   124  		return nil
   125  	}
   126  
   127  	if segment.RawValue == "$ref" {
   128  		// this is a ref segment
   129  		if segment.Next != nil {
   130  			return BadRequestError("A $ref segment must be last.")
   131  		}
   132  		if segment.Prev == nil {
   133  			return BadRequestError("A $ref segment must be preceded by something.")
   134  		}
   135  
   136  		segment.SemanticType = SemanticTypeRef
   137  		segment.SemanticReference = segment.Prev
   138  		return nil
   139  	}
   140  
   141  	if segment.RawValue == "$count" {
   142  		// this is a ref segment
   143  		if segment.Next != nil {
   144  			return BadRequestError("A $count segment must be last.")
   145  		}
   146  		if segment.Prev == nil {
   147  			return BadRequestError("A $count segment must be preceded by something.")
   148  		}
   149  
   150  		segment.SemanticType = SemanticTypeCount
   151  		segment.SemanticReference = segment.Prev
   152  		return nil
   153  	}
   154  
   155  	if _, ok := service.EntitySetLookup[segment.Name]; ok {
   156  		// this is an entity set
   157  		segment.SemanticType = SemanticTypeEntitySet
   158  		segment.SemanticReference, err = service.LookupEntitySet(segment.Name)
   159  		if err != nil {
   160  			return err
   161  		}
   162  
   163  		if segment.Prev == nil {
   164  			// this is the first segment
   165  			if segment.Next == nil {
   166  				// this is the only segment
   167  				return nil
   168  			} else {
   169  				// there is at least one more segment
   170  				if segment.Identifier != nil {
   171  					return BadRequestError("An entity set must be the last segment.")
   172  				}
   173  				// if it has an identifier, it is allowed
   174  				return nil
   175  			}
   176  		} else if segment.Next == nil {
   177  			// this is the last segment in a sequence of more than one
   178  			return nil
   179  		} else {
   180  			// this is a middle segment
   181  			if segment.Identifier != nil {
   182  				return BadRequestError("An entity set must be the last segment.")
   183  			}
   184  			// if it has an identifier, it is allowed
   185  			return nil
   186  		}
   187  	}
   188  
   189  	if segment.Prev != nil && segment.Prev.SemanticType == SemanticTypeEntitySet {
   190  		// previous segment was an entity set
   191  		semanticRef := segment.Prev.SemanticReference.(*GoDataEntitySet)
   192  
   193  		entity, err := service.LookupEntityType(semanticRef.EntityType)
   194  
   195  		if err != nil {
   196  			return err
   197  		}
   198  
   199  		for _, p := range entity.Properties {
   200  			if p.Name == segment.Name {
   201  				segment.SemanticType = SemanticTypeProperty
   202  				segment.SemanticReference = p
   203  				return nil
   204  			}
   205  		}
   206  
   207  		return BadRequestError("A valid entity property must follow entity set.")
   208  	}
   209  
   210  	return BadRequestError("Invalid segment " + segment.RawValue)
   211  }
   212  
   213  var supportedOdataKeywords = map[string]bool{
   214  	"$filter":      true,
   215  	"$apply":       true,
   216  	"$expand":      true,
   217  	"$select":      true,
   218  	"$orderby":     true,
   219  	"$top":         true,
   220  	"$skip":        true,
   221  	"$count":       true,
   222  	"$inlinecount": true,
   223  	"$search":      true,
   224  	"$compute":     true,
   225  	"$format":      true,
   226  	"at":           true,
   227  	"tags":         true,
   228  }
   229  
   230  type OdataComplianceConfig int
   231  
   232  const (
   233  	ComplianceStrict OdataComplianceConfig = 0
   234  	// Ignore duplicate ODATA keywords in the URL query.
   235  	ComplianceIgnoreDuplicateKeywords OdataComplianceConfig = 1 << iota
   236  	// Ignore unknown ODATA keywords in the URL query.
   237  	ComplianceIgnoreUnknownKeywords
   238  	// Ignore extraneous comma as the last character in a list of function arguments.
   239  	ComplianceIgnoreInvalidComma
   240  	ComplianceIgnoreAll OdataComplianceConfig = ComplianceIgnoreDuplicateKeywords |
   241  		ComplianceIgnoreUnknownKeywords |
   242  		ComplianceIgnoreInvalidComma
   243  )
   244  
   245  type parserConfigKey int
   246  
   247  const (
   248  	odataCompliance parserConfigKey = iota
   249  )
   250  
   251  // If the lenient mode is set, the 'failOnConfig' bits are used to determine the ODATA compliance.
   252  // This is mostly for historical reasons because the original parser had compliance issues.
   253  // If the lenient mode is not set, the parser returns an error.
   254  func WithOdataComplianceConfig(ctx context.Context, cfg OdataComplianceConfig) context.Context {
   255  	return context.WithValue(ctx, odataCompliance, cfg)
   256  }
   257  
   258  // ParseUrlQuery parses the URL query, applying optional logic specified in the context.
   259  func (req *GoDataRequest) ParseUrlQuery(ctx context.Context, query url.Values) error {
   260  	cfg, hasComplianceConfig := ctx.Value(odataCompliance).(OdataComplianceConfig)
   261  	if !hasComplianceConfig {
   262  		// Strict ODATA compliance by default.
   263  		cfg = ComplianceStrict
   264  	}
   265  	// Validate each query parameter is a valid ODATA keyword.
   266  	for key, val := range query {
   267  		if _, ok := supportedOdataKeywords[key]; !ok && (cfg&ComplianceIgnoreUnknownKeywords == 0) {
   268  			return BadRequestError(fmt.Sprintf("Query parameter '%s' is not supported", key)).
   269  				SetCause(&UnsupportedQueryParameterError{key})
   270  		}
   271  		if (cfg&ComplianceIgnoreDuplicateKeywords == 0) && (len(val) > 1) {
   272  			return BadRequestError(fmt.Sprintf("Query parameter '%s' cannot be specified more than once", key)).
   273  				SetCause(&DuplicateQueryParameterError{key})
   274  		}
   275  	}
   276  	filter := query.Get("$filter")
   277  	at := query.Get("at")
   278  	apply := query.Get("$apply")
   279  	expand := query.Get("$expand")
   280  	sel := query.Get("$select")
   281  	orderby := query.Get("$orderby")
   282  	top := query.Get("$top")
   283  	skip := query.Get("$skip")
   284  	count := query.Get("$count")
   285  	inlinecount := query.Get("$inlinecount")
   286  	search := query.Get("$search")
   287  	compute := query.Get("$compute")
   288  	format := query.Get("$format")
   289  
   290  	result := &GoDataQuery{}
   291  
   292  	var err error = nil
   293  	if filter != "" {
   294  		result.Filter, err = ParseFilterString(ctx, filter)
   295  	}
   296  	if err != nil {
   297  		return err
   298  	}
   299  	if at != "" {
   300  		result.At, err = ParseFilterString(ctx, at)
   301  	}
   302  	if err != nil {
   303  		return err
   304  	}
   305  	if at != "" {
   306  		result.At, err = ParseFilterString(ctx, at)
   307  	}
   308  	if err != nil {
   309  		return err
   310  	}
   311  	if apply != "" {
   312  		result.Apply, err = ParseApplyString(ctx, apply)
   313  	}
   314  	if err != nil {
   315  		return err
   316  	}
   317  	if expand != "" {
   318  		result.Expand, err = ParseExpandString(ctx, expand)
   319  	}
   320  	if err != nil {
   321  		return err
   322  	}
   323  	if sel != "" {
   324  		result.Select, err = ParseSelectString(ctx, sel)
   325  	}
   326  	if err != nil {
   327  		return err
   328  	}
   329  	if orderby != "" {
   330  		result.OrderBy, err = ParseOrderByString(ctx, orderby)
   331  	}
   332  	if err != nil {
   333  		return err
   334  	}
   335  	if top != "" {
   336  		result.Top, err = ParseTopString(ctx, top)
   337  	}
   338  	if err != nil {
   339  		return err
   340  	}
   341  	if skip != "" {
   342  		result.Skip, err = ParseSkipString(ctx, skip)
   343  	}
   344  	if err != nil {
   345  		return err
   346  	}
   347  	if count != "" {
   348  		result.Count, err = ParseCountString(ctx, count)
   349  	}
   350  	if err != nil {
   351  		return err
   352  	}
   353  	if inlinecount != "" {
   354  		result.InlineCount, err = ParseInlineCountString(ctx, inlinecount)
   355  	}
   356  	if err != nil {
   357  		return err
   358  	}
   359  	if search != "" {
   360  		result.Search, err = ParseSearchString(ctx, search)
   361  	}
   362  	if err != nil {
   363  		return err
   364  	}
   365  	if compute != "" {
   366  		result.Compute, err = ParseComputeString(ctx, compute)
   367  	}
   368  	if err != nil {
   369  		return err
   370  	}
   371  	if format != "" {
   372  		err = NotImplementedError("Format is not supported")
   373  	}
   374  	if err != nil {
   375  		return err
   376  	}
   377  	req.Query = result
   378  	return err
   379  }
   380  
   381  func ParseIdentifiers(segment string) *GoDataIdentifier {
   382  	if !(strings.Contains(segment, "(") && strings.Contains(segment, ")")) {
   383  		return nil
   384  	}
   385  
   386  	rawIds := segment[strings.LastIndex(segment, "(")+1 : strings.LastIndex(segment, ")")]
   387  	parts := strings.Split(rawIds, ",")
   388  
   389  	result := make(GoDataIdentifier)
   390  
   391  	for _, v := range parts {
   392  		if strings.Contains(v, "=") {
   393  			split := strings.SplitN(v, "=", 2)
   394  			result[split[0]] = split[1]
   395  		} else {
   396  			result[v] = ""
   397  		}
   398  	}
   399  
   400  	return &result
   401  }
   402  
   403  func ParseName(segment string) string {
   404  	if strings.Contains(segment, "(") {
   405  		return segment[:strings.LastIndex(segment, "(")]
   406  	} else {
   407  		return segment
   408  	}
   409  }