Build Open Data Protocol (OData v4) queries in Elm.
This package supports a subset of OData 4.01 query option, common expression, and primitive literal.
http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html
http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html
Url.Builder.QueryParameter
(see example)plainStringOperator
(see example)customValue
(see example)import Time
import Url
import Url.Builder
-- Custom value function
dateAsString : Time.Posix -> Value
dateAsString posix =
customValue (\s -> "'" ++ s ++ "'") (date posix)
[ filter
( or
[ eq "City" (string "Tallinn")
, eq "City" (string "Singapore")
-- Custom operator
, plainStringOperator "concat(concat(City,', '), Country) eq 'Berlin, Germany'"
-- Custom value
, ge "start/dateTime" (dateAsString (Time.millisToPosix 1631124861000))
]
)
, top 20
, skip 40
, orderBy [ ("Created", Just desc), ("City", Just asc) ]
, select [ "Id", "City", "Created", "Body" ]
]
|> List.map toQueryParameter
-- Custom query option
|> List.append [ Url.Builder.string "$search" "blue OR green" ]
|> Url.Builder.toQuery >> Url.percentDecode
--> Just (String.join ""
--> [ "?$search=blue OR green&"
--> , "$filter=City eq 'Tallinn' or "
--> , "City eq 'Singapore' or "
--> , "concat(concat(City,', '), Country) eq 'Berlin, Germany' or "
--> , "start/dateTime ge '2021-09-08'&"
--> , "$top=20&"
--> , "$skip=40&"
--> , "$orderBy=Created desc,City asc&"
--> , "$select=Id,City,Created,Body"
--> ])
System query options are query string parameters that control the amount and order of the data returned for the resource identified by the URL.
select : List String -> QueryOption
The $select
system query option requests that the service return only the properties explicitly requested by the client.
import Url
import Url.Builder
select [ "id", "subject", "body" ]
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$select=id,subject,body"
filter : CommonExpression -> QueryOption
The $filter
system query option restricts the set of items returned.
import Url
import Url.Builder
filter (endsWith "mail" "@hotmail.com")
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=endswith(mail,'@hotmail.com')"
orderBy : List ( String, Maybe Order ) -> QueryOption
The $orderby
System Query option specifies the order in which items are returned from the service.
import Url
import Url.Builder
orderBy [ ("created", Just desc), ("displayName", Just asc) ]
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$orderBy=created desc,displayName asc"
top : Basics.Int -> QueryOption
The $top
query parameter requests the number of items in the queried collection to be included in the result.
A client can request a particular page of items by combining $top
and $skip
.
import Url
import Url.Builder
top 10
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$top=10"
skip : Basics.Int -> QueryOption
The $skip
query parameter requests the number of items in the queried collection that are to be skipped and not included in the result.
A client can request a particular page of items by combining $top
and $skip
.
import Url
import Url.Builder
skip 10
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$skip=10"
The expression can include the suffix
asc
for ascending or
desc
for descending.
Defaults to asc
.
asc : Order
Ascending order
import Url
import Url.Builder
orderBy [ ("displayName", Just asc) ]
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$orderBy=displayName asc"
orderBy [ ("displayName", Nothing) ]
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$orderBy=displayName"
desc : Order
Descending order
import Url
import Url.Builder
orderBy [ ("created", Just desc) ]
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$orderBy=created desc"
orderBy [ ("created", Nothing) ]
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$orderBy=created"
The following operators, functions, and literals can be used in $filter
and $orderby
eq : String -> Value -> CommonExpression
Equals
The null
value is equal to itself, and only to itself.
import Url
import Url.Builder
filter (eq "name" (string "Luna"))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=name eq 'Luna'"
ne : String -> Value -> CommonExpression
Not Equals
import Url
import Url.Builder
filter (ne "deleted" null)
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=deleted ne null"
gt : String -> Value -> CommonExpression
Greater Than
import Url
import Url.Builder
filter (gt "age" (int 14))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=age gt 14"
ge : String -> Value -> CommonExpression
Greater Than or Equal
import Url
import Url.Builder
filter (ge "age" (int 14))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=age ge 14"
lt : String -> Value -> CommonExpression
Less Than
import Url
import Url.Builder
filter (lt "age" (int 14))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=age lt 14"
le : String -> Value -> CommonExpression
Less Than or Equal
import Url
import Url.Builder
filter (le "age" (int 14))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=age le 14"
and : List CommonExpression -> CommonExpression
The and
operator returns true
if both the left and right operands evaluate to true
, otherwise it returns false
.
The null
value is treated as unknown, so if one operand evaluates to null
and the other operand to false
, the and operator returns false
.
All other combinations with null
return null
.
import Url
import Url.Builder
filter (and [ eq "name" (string "Luna"), le "age" (int 14)])
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=name eq 'Luna' and age le 14"
or : List CommonExpression -> CommonExpression
The or
operator returns false
if both the left and right operands both evaluate to false
, otherwise it returns true
.
The null
value is treated as unknown, so if one operand evaluates to null
and the other operand to true
, the or
operator returns true
.
All other combinations with null
return null
.
import Url
import Url.Builder
filter (or [ eq "department" (string "Sales"), eq "department" (string "Marketing")])
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=department eq 'Sales' or department eq 'Marketing'"
not_ : CommonExpression -> CommonExpression
The not
operator returns true
if the operand returns false
, otherwise it returns false
.
The null
value is treated as unknown, so not null
returns null
.
import Url
import Url.Builder
filter (not_ (contains "email" "@org.com"))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=not contains(email,'@org.com')"
in_ : String -> List Value -> CommonExpression
The in
operator returns true if the left operand is a member of the right operand.
The right operand is a comma-separated list of primitive values.
import Url
import Url.Builder
filter (in_ "department" [string "Retail", string "Sales"])
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=department in ('Retail', 'Sales')"
startsWith : String -> String -> CommonExpression
The startsWith
function returns true
if the first string starts with the second string, otherwise it returns false
import Url
import Url.Builder
filter (startsWith "CompanyName" "Alfr")
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=startswith(CompanyName,'Alfr')"
endsWith : String -> String -> CommonExpression
The endsWith
function returns true
if the first string ends with the second string, otherwise it returns false
import Url
import Url.Builder
filter (endsWith "CompanyName" "Futterkiste")
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=endswith(CompanyName,'Futterkiste')"
contains : String -> String -> CommonExpression
The contains
function returns true
if the second string is a substring of the first string, otherwise it returns false
import Url
import Url.Builder
filter (contains "CompanyName" "Alfreds")
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=contains(CompanyName,'Alfreds')"
any : String -> CommonExpression -> CommonExpression
The any
operator applies a Boolean expression to each member of a collection and returns true
if the expression is true
for any member of the collection, otherwise it returns false
.
The any
operator without an argument returns true
if the collection is not empty.
import Url
import Url.Builder
filter (any "Items" (gt "Quantity" (int 100)))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=Items/any(a:a/Quantity gt 100)"
all : String -> CommonExpression -> CommonExpression
The all
operator applies a Boolean expression to each member of a collection and returns true
if the expression is true
for all members of the collection, otherwise it returns false
.
import Url
import Url.Builder
filter (all "Items" (gt "Quantity" (int 100)))
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=Items/all(a:a/Quantity gt 100)"
Value
type represents primitive literals
NullValue eq null
TrueValue eq true
FalseValue eq false
IntegerValue lt -128
FloatValue eq 34.95
StringValue eq 'Say Hello,then go'
DateValue eq 2012-12-03
DateTimeOffsetValue eq 2012-12-03T07:16:23Z
GuidValue eq 01234567-89ab-cdef-0123-456789abcdef
null : Value
null
|> test.stringFromValue
--> "null"
true : Value
true
|> test.stringFromValue
--> "true"
false : Value
false
|> test.stringFromValue
--> "false"
int : Basics.Int -> Value
int -128
|> test.stringFromValue
--> "-128"
float : Basics.Float -> Value
float 0.31415
|> test.stringFromValue
--> "0.31415"
string : String -> Value
Note the single quotes
string "Say Hello,then go"
|> test.stringFromValue
--> "'Say Hello,then go'"
date : Time.Posix -> Value
import Time
date (Time.millisToPosix 1631124861000)
|> test.stringFromValue
--> "2021-09-08"
dateTime : Time.Posix -> Value
import Time
dateTime (Time.millisToPosix 1631124861000)
|> test.stringFromValue
--> "2021-09-08T18:14:21Z"
guid : String -> Value
guid "01234567-89ab-cdef-0123-456789abcdef"
|> test.stringFromValue
--> "01234567-89ab-cdef-0123-456789abcdef"
plainStringOperator : String -> CommonExpression
plainStringOperator
is useful
when your target API offers OData operator that is out of the standard scope
when your target API OData formatting differs from standard formatting
for OData operators that are not implemented by this package
In the following example, concat
is not implemented in this package.
import Url
import Url.Builder
filter
( or
[ eq "Country" (string "France")
, plainStringOperator "concat(concat(City,', '), Country) eq 'Berlin, Germany'"
]
)
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=Country eq 'France' or concat(concat(City,', '), Country) eq 'Berlin, Germany'"
customValue : (String -> String) -> Value -> Value
In situations like this https://github.com/microsoftgraph/microsoft-graph-docs/issues/14547, custom value may come in handy. In the example below, date needs to be treated as string (with single quotes):
import Time
dateAsString : Time.Posix -> Value
dateAsString posix =
customValue (\s -> "'" ++ s ++ "'") (date posix)
dateAsString (Time.millisToPosix 1631124861000)
|> test.stringFromValue
--> "'2021-09-08'"
toQueryParameter : QueryOption -> Url.Builder.QueryParameter
Build Url.Builder.QueryParameter
from QueryOption
,
which you can further combine with other QueryParameter
s and convert to string using
Url.Builder.toQuery
import Url
import Url.Builder
[ toQueryParameter (top 10) ]
|> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$top=10"
test : { stringFromValue : Value -> String }
Test internals are needed for elm-verify-examples