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 }