Skip to content

Commit

Permalink
Add RecordApi.list examples to docs and streamline the list APIs for …
Browse files Browse the repository at this point in the history
…rust and dotnet.
  • Loading branch information
ignatz committed Jan 22, 2025
1 parent 288d13f commit 2683c2f
Show file tree
Hide file tree
Showing 18 changed files with 230 additions and 76 deletions.
6 changes: 6 additions & 0 deletions client/testfixture/config.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ record_apis: [
name: "simple_subset_view"
table_name: "simple_subset_view"
acl_authenticated: [CREATE, READ, UPDATE, DELETE]
},
{
name: "movies"
table_name: "movies"
acl_world: [READ]
acl_authenticated: [CREATE, READ, UPDATE, DELETE]
}
]
schemas: [
Expand Down
16 changes: 16 additions & 0 deletions client/testfixture/migrations/U1728810800__create_table_movies.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- A table schema to hold the IMDB test dataset from:
-- https://www.kaggle.com/datasets/inductiveanks/top-1000-imdb-movies-dataset/data
--
-- The only TrailBase API requirements are: "STRICT" typing and a INTEGER (or
-- UUIDv7) PRIMARY KEY column.
CREATE TABLE IF NOT EXISTS movies (
rank INTEGER PRIMARY KEY,
name TEXT NOT NULL,
year ANY NOT NULL,
watch_time INTEGER NOT NULL,
rating REAL NOT NULL,
metascore ANY,
gross ANY,
votes TEXT NOT NULL,
description TEXT NOT NULL
) STRICT;
4 changes: 2 additions & 2 deletions client/trailbase-dart/lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ class Pagination {
final int? limit;

const Pagination({
required this.cursor,
required this.limit,
this.cursor,
this.limit,
});
}

Expand Down
39 changes: 28 additions & 11 deletions client/trailbase-dotnet/src/RecordApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ public override string ToString() {

/// <summary>Pagination state representation.</summary>
public class Pagination {
/// <summary>Offset cursor.</summary>
public string? cursor { get; }
/// <summary>Limit of elements per page.</summary>
public int? limit { get; }
/// <summary>Offset cursor.</summary>
public string? cursor { get; }

/// <summary>Pagination constructor.</summary>
public Pagination(string? cursor, int? limit) {
public Pagination(int? limit = null, string? cursor = null) {
this.cursor = cursor;
this.limit = limit;
}
Expand Down Expand Up @@ -190,6 +190,7 @@ public ErrorEvent(string errorMsg) {

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(ResponseRecordId))]
[JsonSerializable(typeof(ListResponse<JsonObject>))]
internal partial class SerializeResponseRecordIdContext : JsonSerializerContext {
}

Expand Down Expand Up @@ -286,9 +287,9 @@ private async Task<RecordId> CreateImpl(HttpContent recordJson) {
[RequiresDynamicCode(DynamicCodeMessage)]
[RequiresUnreferencedCode(UnreferencedCodeMessage)]
public async Task<ListResponse<T>> List<T>(
Pagination? pagination,
List<string>? order,
List<string>? filters
Pagination? pagination = null,
List<string>? order = null,
List<string>? filters = null
) {
string json = await (await ListImpl(pagination, order, filters)).ReadAsStringAsync();
return JsonSerializer.Deserialize<ListResponse<T>>(json) ?? new ListResponse<T>(null, []);
Expand All @@ -297,20 +298,36 @@ public async Task<ListResponse<T>> List<T>(
/// <summary>
/// List records.
/// </summary>
/// <param name="jsonTypeInfo">Serialization type info for AOT mode.</param>
/// <param name="pagination">Pagination state.</param>
/// <param name="order">Sort results by the given columns in ascending/descending order, e.g. "-col_name".</param>
/// <param name="filters">Results filters, e.g. "col0[gte]=100".</param>
/// <param name="jsonTypeInfo">Serialization type info for AOT mode.</param>
public async Task<ListResponse<T>> List<T>(
Pagination? pagination,
List<string>? order,
List<string>? filters,
JsonTypeInfo<ListResponse<T>> jsonTypeInfo
JsonTypeInfo<ListResponse<T>> jsonTypeInfo,
Pagination? pagination = null,
List<string>? order = null,
List<string>? filters = null
) {
string json = await (await ListImpl(pagination, order, filters)).ReadAsStringAsync();
return JsonSerializer.Deserialize<ListResponse<T>>(json, jsonTypeInfo) ?? new ListResponse<T>(null, []);
}

/// <summary>
/// List records.
/// </summary>
/// <param name="pagination">Pagination state.</param>
/// <param name="order">Sort results by the given columns in ascending/descending order, e.g. "-col_name".</param>
/// <param name="filters">Results filters, e.g. "col0[gte]=100".</param>
public async Task<ListResponse<JsonObject>> List(
Pagination? pagination = null,
List<string>? order = null,
List<string>? filters = null
) {
string json = await (await ListImpl(pagination, order, filters)).ReadAsStringAsync();
return JsonSerializer.Deserialize<ListResponse<JsonObject>>(
json, SerializeResponseRecordIdContext.Default.ListResponseJsonObject) ?? new ListResponse<JsonObject>(null, []);
}

private async Task<HttpContent> ListImpl(
Pagination? pagination,
List<string>? order,
Expand Down
26 changes: 10 additions & 16 deletions client/trailbase-dotnet/test/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,31 +236,27 @@ public async Task RecordsTest() {

{
ListResponse<SimpleStrict> response = await api.List(
null,
null,
[$"text_not_null={messages[0]}"],
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict,
filters: [$"text_not_null={messages[0]}"]
)!;
Assert.Single(response.records);
Assert.Equal(messages[0], response.records[0].text_not_null);
}

{
var responseAsc = await api.List(
null,
["+text_not_null"],
[$"text_not_null[like]=% =?&{suffix}"],
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict,
order: ["+text_not_null"],
filters: [$"text_not_null[like]=% =?&{suffix}"]
)!;
var recordsAsc = responseAsc.records;
Assert.Equal(messages.Count, recordsAsc.Count);
Assert.Equal(messages, recordsAsc.ConvertAll((e) => e.text_not_null));

var responseDesc = await api.List(
null,
["-text_not_null"],
[$"text_not_null[like]=%{suffix}"],
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict,
order: ["-text_not_null"],
filters: [$"text_not_null[like]=%{suffix}"]
)!;
var recordsDesc = responseDesc.records;
Assert.Equal(messages.Count, recordsDesc.Count);
Expand Down Expand Up @@ -298,10 +294,8 @@ await api.Update(
await api.Delete(id);

var response = await api.List(
null,
null,
[$"text_not_null[like]=%{suffix}"],
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict
SerializeSimpleStrictContext.Default.ListResponseSimpleStrict,
filters: [$"text_not_null[like]=%{suffix}"]
)!;

Assert.Single(response.records);
Expand Down
40 changes: 18 additions & 22 deletions client/trailbase-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub struct Tokens {
pub csrf_token: Option<String>,
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub struct Pagination {
pub cursor: Option<String>,
pub limit: Option<usize>,
Expand Down Expand Up @@ -164,36 +164,32 @@ impl RecordApi {

pub async fn list<T: DeserializeOwned>(
&self,
pagination: Option<Pagination>,
order: Option<&[&str]>,
filters: Option<&[&str]>,
pagination: Pagination,
order: &[&str],
filters: &[&str],
) -> Result<ListResponse<T>, Error> {
let mut params: Vec<(Cow<'static, str>, Cow<'static, str>)> = vec![];
if let Some(pagination) = pagination {
if let Some(cursor) = pagination.cursor {
params.push((Cow::Borrowed("cursor"), Cow::Owned(cursor)));
}
if let Some(cursor) = pagination.cursor {
params.push((Cow::Borrowed("cursor"), Cow::Owned(cursor)));
}

if let Some(limit) = pagination.limit {
params.push((Cow::Borrowed("limit"), Cow::Owned(limit.to_string())));
}
if let Some(limit) = pagination.limit {
params.push((Cow::Borrowed("limit"), Cow::Owned(limit.to_string())));
}

if let Some(order) = order {
if !order.is_empty() {
params.push((Cow::Borrowed("order"), Cow::Owned(order.join(","))));
}

if let Some(filters) = filters {
for filter in filters {
let Some((name_op, value)) = filter.split_once("=") else {
panic!("Filter '{filter}' does not match: 'name[op]=value'");
};
for filter in filters {
let Some((name_op, value)) = filter.split_once("=") else {
panic!("Filter '{filter}' does not match: 'name[op]=value'");
};

params.push((
Cow::Owned(name_op.to_string()),
Cow::Owned(value.to_string()),
));
}
params.push((
Cow::Owned(name_op.to_string()),
Cow::Owned(value.to_string()),
));
}

let response = self
Expand Down
16 changes: 8 additions & 8 deletions client/trailbase-rs/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,20 @@ async fn records_test() {
let filter = format!("text_not_null={}", messages[0]);
let filters = vec![filter.as_str()];
let response = api
.list::<serde_json::Value>(None, None, Some(filters.as_slice()))
.list::<serde_json::Value>(Pagination::default(), &[], filters.as_slice())
.await
.unwrap();

assert_eq!(response.records.len(), 1);

let second_response = api
.list::<serde_json::Value>(
Some(Pagination {
Pagination {
cursor: response.cursor,
limit: None,
}),
None,
Some(filters.as_slice()),
..Default::default()
},
&[],
filters.as_slice(),
)
.await
.unwrap();
Expand All @@ -148,7 +148,7 @@ async fn records_test() {
// List all the messages
let filter = format!("text_not_null[like]=% =?&{now}");
let records_ascending: Vec<SimpleStrict> = api
.list(None, Some(&["+text_not_null"]), Some(&[&filter]))
.list(Pagination::default(), &["+text_not_null"], &[&filter])
.await
.unwrap()
.records;
Expand All @@ -160,7 +160,7 @@ async fn records_test() {
assert_eq!(messages, messages_ascending);

let records_descending: Vec<SimpleStrict> = api
.list(None, Some(&["-text_not_null"]), Some(&[&filter]))
.list(Pagination::default(), &["-text_not_null"], &[&filter])
.await
.unwrap()
.records;
Expand Down
5 changes: 5 additions & 0 deletions docs/examples/record_api_curl/list.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
curl --globoff \
--header "Content-Type: application/json" \
--header "Authorization: Bearer ${AUTH_TOKEN}" \
--request GET \
'http://localhost:4000/api/records/v1/movies?limit=3&order=rank&watch_time[lt]=120&description[like]=%love%'
5 changes: 3 additions & 2 deletions docs/examples/record_api_dart/lib/record_api.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export 'src/create.dart';
export 'src/read.dart';
export 'src/update.dart';
export 'src/delete.dart';
export 'src/list.dart';
export 'src/read.dart';
export 'src/subscribe.dart';
export 'src/update.dart';
8 changes: 8 additions & 0 deletions docs/examples/record_api_dart/lib/src/list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:trailbase/trailbase.dart';

Future<ListResponse> list(Client client) async =>
await client.records('movies').list(
pagination: Pagination(limit: 3),
order: ['rank'],
filters: ['watch_time[lt]=120', 'description[like]=%love%'],
);
8 changes: 8 additions & 0 deletions docs/examples/record_api_dart/test/record_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,12 @@ void main() {
}).toList();
expect(tableEventList.length, equals(3));
});

test('Test code listing example', () async {
final client = await connect();
final results = await list(client);

expect(results.records.length, 3);
expect(results.records[0]['name'], 'Casablanca');
});
}
9 changes: 9 additions & 0 deletions docs/examples/record_api_dotnet/ExamplesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,13 @@ public async Task BasicTest() {
}
}
}

[Fact]
public async Task ListTest() {
var client = await Connect();
var response = await Examples.List(client);

Assert.Equal(3, response.records.Count);
Assert.Equal("Casablanca", response.records[0]["name"]!.ToString());
}
}
10 changes: 10 additions & 0 deletions docs/examples/record_api_dotnet/List.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using TrailBase;
using System.Text.Json.Nodes;

public partial class Examples {
public static async Task<ListResponse<JsonObject>> List(Client client) =>
await client.Records("movies").List(
pagination: new Pagination(limit: 3),
order: ["rank"],
filters: ["watch_time[lt]=120", "description[like]=%love%"]);
}
16 changes: 16 additions & 0 deletions docs/examples/record_api_rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod create;
pub mod delete;
pub mod list;
pub mod read;
pub mod subscribe;
pub mod update;
Expand All @@ -11,6 +12,7 @@ mod test {

use crate::create::*;
use crate::delete::*;
use crate::list::*;
use crate::read::*;
use crate::subscribe::*;
use crate::update::*;
Expand Down Expand Up @@ -58,4 +60,18 @@ mod test {
let table_events = table_stream.take(3).collect::<Vec<_>>().await;
assert_eq!(table_events.len(), 3);
}

#[ignore]
#[tokio::test]
async fn list_test() {
let client = connect().await;

let response = list(&client).await.unwrap();

assert_eq!(response.records.len(), 3);
assert_eq!(
response.records[0].get("name").unwrap(),
&serde_json::Value::String("Casablanca".into())
);
}
}
17 changes: 17 additions & 0 deletions docs/examples/record_api_rs/src/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use trailbase_client::{Client, ListResponse, Pagination};

pub async fn list(client: &Client) -> anyhow::Result<ListResponse<serde_json::Value>> {
Ok(
client
.records("movies")
.list(
Pagination {
limit: Some(3),
..Default::default()
},
&["rank"],
&["watch_time[lt]=120", "description[like]=%love%"],
)
.await?,
)
}
Loading

0 comments on commit 2683c2f

Please sign in to comment.