odata
| Kind | kit |
|---|---|
| Capabilities | net |
| 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: building query URLs for the public Northwind OData v4 test service |
kit.toml | Package manifest with metadata, capabilities, and tasks |
src/client.kit | HTTP client wrapper for OData requests (GET, POST, PUT, PATCH, DELETE) |
src/main.kit | kit-odata public module exports |
src/query.kit | Fluent query builder ($select, $filter, $orderby, $top, $skip, $expand) |
src/response.kit | OData response parsing for collections, entities, counts, and pagination links |
src/types.kit | OData error types and sort direction ADTs |
tests/query.test.kit | Tests for query building and response parsing |
Dependencies
No Kit package dependencies.
The package uses Kit standard library modules for HTTP and JSON parsing. Live service requests require network capability, for example kit run --allow=network your-script.kit.
Installation
kit add gitlab.com/kit-lang/packages/kit-odata.gitUsage
import Kit.OData as OData
import Kit.OData.{Desc}
main = fn(-env: Env) =>
cfg = OData.config "https://services.odata.org/V4/Northwind/Northwind.svc"
# Build a query with the fluent API
query = OData.new-query "Products"
|> OData.select ["ProductName", "UnitPrice"]
|> OData.filter "UnitPrice gt 20"
|> OData.order-by "UnitPrice" Desc
|> OData.top 10
println "URL: ${OData.build-url query}"
# Execute the query and parse the collection response
match OData.get http-auth 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}"
# Fetch a single entity by key
single = OData.new-query "Products" |> OData.by-key "1"
match OData.get-entity http-auth cfg single
| Ok json ->
println "Product: ${OData.get-field json "ProductName" ?? "?"}"
| Err e ->
println "Error: ${show e}"
mainAPI Reference
Query Builder
| Function | Description |
|---|---|
OData.new-query entity | Create a query for an entity set |
OData.select fields | Add $select fields |
OData.filter expr | Add a $filter expression; multiple filters are joined with and |
OData.order-by field direction | Add an $orderby clause with Asc or Desc |
OData.top n | Set $top |
OData.skip n | Set $skip |
OData.expand relation | Add $expand for related entities |
OData.with-count | Enable $count=true |
OData.search term | Set $search for full-text search |
OData.by-key key | Build a single entity path such as Products(1) |
OData.build-query-string query | Build only the query string |
OData.build-url query | Build the relative OData URL |
Client Functions
| Function | Description |
|---|---|
OData.config base-url | Create a client config with a base service URL |
OData.config-with-auth base-url auth | Create a config with an Authorization header |
OData.config-with-headers base-url headers | Create a config with custom headers |
OData.get http-auth cfg query | Execute a query and parse a collection response |
OData.get-entity http-auth cfg query | Execute a query and parse a single entity response |
OData.get-raw http-auth cfg path | Raw GET request against a relative OData path |
OData.create http-auth cfg entity-set body | POST a new entity |
OData.update http-auth cfg entity-path body | PUT an entity |
OData.patch http-auth cfg entity-path body | PATCH an entity |
OData.delete http-auth cfg entity-path | DELETE an entity |
Response Helpers
| Function | Description |
|---|---|
OData.parse-collection body | Parse an OData JSON collection with value, @odata.count, and @odata.nextLink |
OData.parse-entity body | Parse a single JSON entity response |
OData.get-field json field | Extract a string field |
OData.get-int-field json field | Extract an integer field |
OData.get-float-field json field | Extract a float field |
OData.get-bool-field json field | Extract a boolean field |
Error Handling
match OData.get http-auth cfg query
| Ok resp ->
println "Fetched ${to-string (List.length resp.values)} entities"
| 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 the deterministic query URL example with the interpreter:
kit run examples/northwind.kitCompile the example to a native binary:
kit build examples/northwind.kit && ./northwindThe checked-in example imports src/main.kit directly so it works before local installation. Consumer code should import Kit.OData.
Interactive 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/ - Type-check examples in
examples/ - Run tests in
tests/with coverage
Running Parity Checks
Run the interpreter/compiler parity check for examples:
kit parity --failures-onlyGenerating 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.
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.
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.
cfg = config-with-auth "https://api.example.com/odata" "Bearer token123"get
Execute a query and return a parsed collection response.
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.
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.
match get-raw cfg "Products?$top=5"
| Ok body -> println body
| Err e -> println (show e)create
Create a new entity via POST.
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).
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.
body = "{\"UnitPrice\": 14.99}"
match patch cfg "Products(1)" body
| Ok _ -> println "Patched!"
| Err e -> println (show e)delete
Delete an entity via DELETE.
match delete cfg "Products(1)"
| Ok _ -> println "Deleted!"
| Err e -> println (show e)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.
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).
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.
get-int-field
Extract an int field from a JSON value.
get-float-field
Extract a float field from a JSON value.
get-bool-field
Extract a bool field from a JSON value.
new-query
Create a new OData query for an entity set.
q = new-query "Products"select
Add $select fields to the query.
q |> select ["ProductName", "UnitPrice"]filter
Add a $filter expression to the query.
Multiple filters are combined with 'and'.
q |> filter "UnitPrice gt 20"
q |> filter "contains(ProductName,'Chai')"order-by
Add an $orderby clause to the query.
q |> order-by "UnitPrice" Desctop
Set the $top parameter (max number of results).
q |> top 10skip
Set the $skip parameter (number of results to skip).
q |> skip 20expand
Add an $expand clause to include related entities.
q |> expand "Category"
q |> expand "OrderDetails($select=Quantity,UnitPrice)"with-count
Enable $count to include total result count in the response.
q |> with-countsearch
Set a $search term for full-text search.
q |> search "chai tea"by-key
Set a key for single entity lookup by ID.
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.
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.
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"config
Create an OData client configuration with a base service URL.
config-with-headers
Create an OData client configuration with custom headers.
config-with-auth
Create an OData client configuration with bearer token auth.
new-query
Create a new OData query for an entity set.
select
Add $select fields to the query.
filter
Add a $filter expression to the query.
order-by
Add an $orderby clause to the query.
top
Set the $top parameter (max results).
skip
Set the $skip parameter (results to skip).
expand
Add an $expand clause for related entities.
with-count
Enable $count in the response.
search
Set a $search term for full-text search.
by-key
Set a key for single entity lookup by ID.
build-query-string
Build the query string from a query record.
build-url
Build the full relative URL from a query record.
get
Execute a query and return a parsed collection response.
get-entity
Execute a query for a single entity and return parsed JSON.
get-raw
Execute a raw GET against an OData path.
create
Create a new entity via POST.
update
Update an entity via PUT.
patch
Partially update an entity via PATCH.
delete
Delete an entity via DELETE.
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}request-error
Create an HTTP request failure error.
protocol-error
Create a non-2xx OData protocol error.
SortDirection
Sort direction for $orderby clauses.
Variants
AscDesc