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"
--> ])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"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')"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"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"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"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"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
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'"Not Equals
import Url
import Url.Builder
filter (ne "deleted" null)
|> toQueryParameter >> List.singleton >> Url.Builder.toQuery >> Url.percentDecode
--> Just "?$filter=deleted ne null"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"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"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"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"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"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'"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')"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')"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')"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')"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')"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)"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:23
GuidValue eq 01234567-89ab-cdef-0123-456789abcdefNote the single quotes
string "Say Hello,then go"
|> test.stringFromValue
--> "'Say Hello,then go'"import Time
date (Time.millisToPosix 1631124861000)
|> test.stringFromValue
--> "2021-09-08"import Time
dateTime (Time.millisToPosix 1631124861000)
|> test.stringFromValue
--> "2021-09-08T18:14:21Z"guid "01234567-89ab-cdef-0123-456789abcdef"
|> test.stringFromValue
--> "01234567-89ab-cdef-0123-456789abcdef"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'"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'"Build Url.Builder.QueryParameter from QueryOption,
which you can further combine with other QueryParameters 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 internals are needed for elm-verify-examples
System query options are query string parameters that control the amount and order of the data returned for the resource identified by the URL.