odata
| Kind | kit |
|---|---|
| Categories | web network |
| Keywords | odata rest api query microsoft sap |
OData v4 client for Kit - query builder, filtering, and pagination
Files
| File | Description |
|---|---|
.editorconfig | Editor formatting configuration |
.gitignore | Git ignore rules for build artifacts and dependencies |
.tool-versions | asdf tool versions (Zig, Kit) |
LICENSE | MIT license file |
README.md | This file |
dev/northwind.kit | REPL preload: Northwind OData v4 helpers and pre-built queries |
examples/northwind.kit | Example: querying the public Northwind OData v4 test service |
kit.toml | Package manifest with metadata and dependencies |
src/client.kit | HTTP client wrapper for OData requests (GET, POST, PUT, PATCH, DELETE) |
src/main.kit | kit-odata: OData v4 client for Kit |
src/query.kit | Fluent query builder ($select, $filter, $orderby, $top, $skip, $expand) |
src/response.kit | OData response parsing (collections, entities, pagination) |
src/types.kit | OData error types and sort direction ADTs |
tests/query.test.kit | Tests for query builder and response parsing |
Dependencies
No Kit package dependencies.
Installation
kit add gitlab.com/kit-lang/packages/kit-odata.gitUsage
import Kit.OData as OData
cfg = OData.config "https://services.odata.org/V4/Northwind/Northwind.svc"
main = fn(-env: Env) =>
# Build a query with fluent API
query = OData.new-query "Products"
|> OData.select ["ProductName", "UnitPrice"]
|> OData.filter "UnitPrice gt 20"
|> OData.order-by "UnitPrice" Desc
|> OData.top 10
# Execute against the service
match OData.get cfg query
| Ok resp ->
List.map (fn(v) =>
name = OData.get-field v "ProductName" ?? "(unknown)"
price = OData.get-float-field v "UnitPrice" ?? 0.0
println "${name} - $${to-string price}"
) resp.values
no-op
| Err e ->
println "Error: ${show e}"
# Single entity by key
match OData.get-entity cfg (OData.new-query "Products" |> OData.by-key "1")
| Ok json ->
println "Product: ${OData.get-field json "ProductName" ?? "?"}"
| Err e ->
println "Error: ${show e}"
mainQuery Builder
| Function | Description |
|---|---|
OData.new-query entity | Create a query for an entity set |
OData.select fields | Add $select fields |
OData.filter expr | Add $filter expression (multiple are joined with and) |
OData.order-by field direction | Add $orderby clause (Asc or Desc) |
OData.top n | Set $top (max results) |
OData.skip n | Set $skip (pagination offset) |
OData.expand relation | Add $expand for related entities |
OData.with-count | Enable $count in response |
OData.search term | Set $search for full-text search |
OData.by-key key | Look up a single entity by key |
Client Functions
| Function | Description |
|---|---|
OData.config base-url | Create client config with base service URL |
OData.config-with-auth base-url auth | Create config with Authorization header |
OData.config-with-headers base-url headers | Create config with custom headers |
OData.get cfg query | Execute query, return parsed collection |
OData.get-entity cfg query | Execute query, return single entity JSON |
OData.get-raw cfg path | Raw GET request against a path |
OData.create cfg entity-set body | POST a new entity |
OData.update cfg entity-path body | PUT (full replace) an entity |
OData.patch cfg entity-path body | PATCH (partial update) an entity |
OData.delete cfg entity-path | DELETE an entity |
Error Handling
match OData.get cfg query
| Ok resp -> use resp.values
| Err (ODataRequestError {message}) -> println "HTTP failed: ${message}"
| Err (ODataParseError {message}) -> println "Bad JSON: ${message}"
| Err (ODataProtocolError {status, message}) -> println "Status ${to-string status}: ${message}"
| Err (ODataNotFound {entity, key}) -> println "Not found: ${entity}(${key})"Development
Running Examples
Run examples with the interpreter:
kit run --allow=network examples/northwind.kitCompile examples to a native binary:
kit build --allow=network examples/northwind.kit -o northwind && ./northwindInteractive REPL
Launch an interactive REPL pre-loaded with helpers for the public Northwind OData v4 service:
kit repl --allow=network --preload dev/northwind.kitThis gives you pre-built queries (products-query, categories-query, etc.) and helper functions for exploring the service interactively:
products 10 # List 10 products (name, price, stock)
top-priced 5 # Top 5 most expensive products
categories # List all categories
customers-in "Germany" # Customers in a country
search-products "UnitPrice gt 100" # Filter products by OData expression
products-with-categories 5 # Products with expanded category info
page "Orders" 2 10 # Page 2, 10 results per page
get-one "Products" "1" # Fetch a single entity by keyYou can also build and run custom queries:
q = OData.new-query "Products" |> OData.filter "UnitPrice gt 50" |> OData.top 5
run qRunning Tests
Run the test suite:
kit testRun the test suite with coverage:
kit test --coverageRunning kit dev
Run the standard development workflow (format, check, test):
kit devThis will:
- Format and check source files in
src/ - Run tests in
tests/with coverage
Generating Documentation
Generate API documentation from doc comments:
kit docNote: Kit sources with doc comments (##) will generate HTML documents in docs/*.html
Cleaning Build Artifacts
Remove generated files, caches, and build artifacts:
kit task cleanNote: Defined in kit.toml.
Local Installation
To install this package locally for development:
kit installThis installs the package to ~/.kit/packages/@kit/odata/, making it available for import as Kit.OData in other projects.
License
This package is released under the MIT License - see LICENSE for details.
Exported Functions & Types
config
Create an OData client configuration with a base service URL.
String -> Record
config-with-headers
Create an OData client configuration with custom headers.
String -> List {name: String, value: String} -> Record
config-with-auth
Create an OData client configuration with bearer token auth.
String -> String -> Record
new-query
Create a new OData query for an entity set.
String -> Record
select
Add $select fields to the query.
Record -> List String -> Record
filter
Add a $filter expression to the query.
Record -> String -> Record
order-by
Add an $orderby clause to the query.
Record -> String -> SortDirection -> Record
top
Set the $top parameter (max results).
Record -> Int -> Record
skip
Set the $skip parameter (results to skip).
Record -> Int -> Record
expand
Add an $expand clause for related entities.
Record -> String -> Record
with-count
Enable $count in the response.
Record -> Record
search
Set a $search term for full-text search.
Record -> String -> Record
by-key
Set a key for single entity lookup by ID.
Record -> String -> Record
build-query-string
Build the query string from a query record.
Record -> String
build-url
Build the full relative URL from a query record.
Record -> String
get
Execute a query and return a parsed collection response.
Record -> Record -> Result Record ODataError
get-entity
Execute a query for a single entity and return parsed JSON.
Record -> Record -> Result JSONValue ODataError
get-raw
Execute a raw GET against an OData path.
Record -> String -> Result String ODataError
create
Create a new entity via POST.
Record -> String -> String -> Result JSONValue ODataError
update
Update an entity via PUT.
Record -> String -> String -> Result String ODataError
patch
Partially update an entity via PATCH.
Record -> String -> String -> Result String ODataError
delete
Delete an entity via DELETE.
Record -> String -> Result String ODataError
parse-collection
Parse a collection response body.
String -> Result Record ODataError
parse-entity
Parse a single entity response body.
String -> Result JSONValue ODataError
get-field
Extract a string field from a JSON value.
JSONValue -> String -> Option String
get-int-field
Extract an int field from a JSON value.
JSONValue -> String -> Option Int
get-float-field
Extract a float field from a JSON value.
JSONValue -> String -> Option Float
get-bool-field
Extract a bool field from a JSON value.
JSONValue -> String -> Option Bool
parse-collection
Parse an OData collection response body (JSON with "value" array).
OData v4 collection responses have the shape: { "value": [...], "@odata.count": N, "@odata.nextLink": "..." }
Returns a record with values, count, and next-link fields.
String -> Result Record ODataError
match parse-collection body
| Ok resp -> List.map show resp.values
| Err e -> println (show e)parse-entity
Parse an OData single entity response body.
Single entity responses are just a JSON object (no "value" wrapper).
String -> Result JSONValue ODataError
match parse-entity body
| Ok json -> JSON.get-string json "ProductName"
| Err e -> println (show e)get-field
Extract a string field from a JSON value.
JSONValue -> String -> Option String
get-int-field
Extract an int field from a JSON value.
JSONValue -> String -> Option Int
get-float-field
Extract a float field from a JSON value.
JSONValue -> String -> Option Float
get-bool-field
Extract a bool field from a JSON value.
JSONValue -> String -> Option Bool
new-query
Create a new OData query for an entity set.
String -> Record
q = new-query "Products"select
Add $select fields to the query.
Record -> List String -> Record
q |> select ["ProductName", "UnitPrice"]filter
Add a $filter expression to the query.
Multiple filters are combined with 'and'.
Record -> String -> Record
q |> filter "UnitPrice gt 20"
q |> filter "contains(ProductName,'Chai')"order-by
Add an $orderby clause to the query.
Record -> String -> SortDirection -> Record
q |> order-by "UnitPrice" Desctop
Set the $top parameter (max number of results).
Record -> Int -> Record
q |> top 10skip
Set the $skip parameter (number of results to skip).
Record -> Int -> Record
q |> skip 20expand
Add an $expand clause to include related entities.
Record -> String -> Record
q |> expand "Category"
q |> expand "OrderDetails($select=Quantity,UnitPrice)"with-count
Enable $count to include total result count in the response.
Record -> Record
q |> with-countsearch
Set a $search term for full-text search.
Record -> String -> Record
q |> search "chai tea"by-key
Set a key for single entity lookup by ID.
Record -> String -> Record
q |> by-key "42"
q |> by-key "'ALFKI'"build-query-string
Build the query string portion of the OData URL.
Returns the query parameters as a string (without the leading '?'). If there are no query parameters, returns an empty string.
Record -> String
q = new-query "Products" |> select ["Name"] |> top 10
build-query-string q # "$select=Name&$top=10"build-url
Build the full relative URL path for the query.
Record -> String
q = new-query "Products" |> top 5
build-url q # "Products?$top=5"
q = new-query "Products" |> by-key "42" |> select ["Name"]
build-url q # "Products(42)?$select=Name"ODataError
Error type for OData client operations. Covers HTTP failures, parse errors, and OData-specific protocol errors.
Variants
ODataRequestError {message}ODataParseError {message}ODataNotFound {entity, key}ODataProtocolError {status, message}SortDirection
Sort direction for $orderby clauses.
Variants
AscDescODataError
Error type for OData client operations.
ODataRequestError | ODataParseError | ODataNotFound | ODataProtocolError
Variants
ODataErrorODataRequestError
HTTP request failed.
{message: String}
Variants
ODataRequestErrorODataParseError
Response JSON could not be parsed.
{message: String}
Variants
ODataParseErrorODataNotFound
Entity not found by key.
{entity: String, key: String}
Variants
ODataNotFoundODataProtocolError
Server returned a non-2xx status code.
{status: Int, message: String}
Variants
ODataProtocolErrorSortDirection
Sort direction for $orderby clauses.
Asc | Desc
Variants
SortDirectionAsc
Ascending sort order.
Variants
AscDesc
Descending sort order.
Variants
Descconfig
Create an OData client configuration.
String -> Record
cfg = config "https://services.odata.org/V4/Northwind/Northwind.svc"
cfg = config-with-auth "https://api.example.com/odata" "Bearer token123"config-with-headers
Create an OData client configuration with custom headers.
String -> List {name: String, value: String} -> Record
cfg = config-with-headers "https://api.example.com/odata" [
{name: "Authorization", value: "Bearer token123"},
{name: "X-Custom", value: "value"}
]config-with-auth
Create an OData client configuration with bearer token auth.
String -> String -> Record
cfg = config-with-auth "https://api.example.com/odata" "Bearer token123"get
Execute a query and return a parsed collection response.
Record -> Record -> Result Record ODataError
q = Query.new-query "Products" |> Query.top 5
match get cfg q
| Ok resp -> List.map show resp.values
| Err e -> println (show e)get-entity
Execute a query for a single entity and return parsed JSON.
Record -> Record -> Result JSONValue ODataError
q = Query.new-query "Products" |> Query.by-key "1"
match get-entity cfg q
| Ok json -> JSON.get-string json "ProductName"
| Err e -> println (show e)get-raw
Execute a raw GET request against an OData URL path.
Useful for following @odata.nextLink pagination URLs or accessing service metadata.
Record -> String -> Result String ODataError
match get-raw cfg "Products?$top=5"
| Ok body -> println body
| Err e -> println (show e)create
Create a new entity via POST.
Record -> String -> String -> Result JSONValue ODataError
body = "{\"ProductName\": \"New Product\", \"UnitPrice\": 9.99}"
match create cfg "Products" body
| Ok json -> println "Created!"
| Err e -> println (show e)update
Update an entity via PUT (full replacement).
Record -> String -> String -> Result String ODataError
body = "{\"ProductName\": \"Updated\", \"UnitPrice\": 12.99}"
match update cfg "Products(1)" body
| Ok _ -> println "Updated!"
| Err e -> println (show e)patch
Partially update an entity via PATCH.
Record -> String -> String -> Result String ODataError
body = "{\"UnitPrice\": 14.99}"
match patch cfg "Products(1)" body
| Ok _ -> println "Patched!"
| Err e -> println (show e)delete
Delete an entity via DELETE.
Record -> String -> Result String ODataError
match delete cfg "Products(1)"
| Ok _ -> println "Deleted!"
| Err e -> println (show e)