Skip to content

Commit

Permalink
[DOCS-3787] Remove FQL typer workarounds (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrodewig authored Dec 4, 2024
1 parent f11b9c4 commit 9c9c07c
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 65 deletions.
42 changes: 20 additions & 22 deletions DotNetSampleApp/Controllers/Customers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace DotNetSampleApp.Controllers;

/// <summary>
/// Customers controller
/// Customers controller
/// </summary>
/// <param name="client">Fauna Client</param>
[ApiController,
Expand All @@ -30,10 +30,10 @@ public async Task<IActionResult> GetCustomer([FromRoute] string customerId)
//
// Use projection to only return the fields you need.
var query = Query.FQL($"""
let customer: Any = Customer.byId({customerId})!
let customer = Customer.byId({customerId})!
{QuerySnippets.CustomerResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Customer>(query);
Expand All @@ -52,16 +52,16 @@ public async Task<IActionResult> CreateCustomer(CustomerRequest customer)
{
// Create a new Customer document with the provided fields.
var query = Query.FQL($"""
let customer: Any = Customer.create({customer})
let customer = Customer.create({customer})
{QuerySnippets.CustomerResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Customer>(query);
return CreatedAtAction(nameof(GetCustomer), new { customerId = res.Data.Id }, res.Data);
}

/// <summary>
/// Update Customer Details
/// </summary>
Expand All @@ -85,10 +85,10 @@ public async Task<IActionResult> UpdateCustomer(
//
// Use projection to only return the fields you need.
var query = Query.FQL($"""
let customer: Any = Customer.byId({customerId})!.update({customer})
let customer = Customer.byId({customerId})!.update({customer})
{QuerySnippets.CustomerResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Customer>(query);
Expand Down Expand Up @@ -116,25 +116,23 @@ public async Task<IActionResult> GetOrdersByCustomer(
// Make sure to URL encode the `afterToken` value to preserve these
// characters in URLs.
var query = !string.IsNullOrEmpty(afterToken)

// Paginate with the after token if it's present.
? Query.FQL($"Set.paginate({afterToken})")

// Define an FQL query to retrieve a page of orders for a given customer.
// Get the Customer document by id, using the ! operator to assert that the document exists.
// If the document does not exist, Fauna will throw a document_not_found error. We then
// use the Order.byCustomer index to retrieve all orders for that customer and map over
// the results to return only the fields we care about.
: Query.FQL($$"""
let customer: Any = Customer.byId({{customerId}})!
let customer = Customer.byId({{customerId}})!
Order.byCustomer(customer).pageSize({{pageSize}}).map((order) => {
let order: Any = order
// Return the order.
{{QuerySnippets.OrderResponse()}}
})
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var result = await client.QueryAsync<Page<Order>>(query);
Expand All @@ -156,12 +154,12 @@ public async Task<IActionResult> CreateCart([FromRoute] string customerId)
// Call our getOrCreateCart UDF to get the customer's cart. The function
// definition can be found 'server/schema/functions.fsl'.
var query = Query.FQL($"""
let order: Any = getOrCreateCart({customerId})
let order = getOrCreateCart({customerId})
// Return the cart.
{QuerySnippets.OrderResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Order>(query);
Expand All @@ -184,12 +182,12 @@ public async Task<IActionResult> AddItemToCart([FromRoute] string customerId, Ad
// definition can be found 'server/schema/functions.fsl'.
var query = Query.FQL($"""
let req = {item}
let order: Any = createOrUpdateCartItem({customerId}, req.productName, req.quantity)
let order = createOrUpdateCartItem({customerId}, req.productName, req.quantity)
// Return the cart as an OrderResponse object.
{QuerySnippets.OrderResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Order>(query);
Expand All @@ -210,12 +208,12 @@ public async Task<IActionResult> GetCart([FromRoute] string customerId)
// Get the customer's cart by id, using the ! operator to assert that the document exists.
// If the document does not exist, Fauna will throw a document_not_found error.
var query = Query.FQL($"""
let order: Any = Customer.byId({customerId})!.cart
let order = Customer.byId({customerId})!.cart
// Return the cart as an OrderResponse object.
{QuerySnippets.OrderResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Order>(query);
Expand Down
12 changes: 6 additions & 6 deletions DotNetSampleApp/Controllers/Orders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ public class Orders(Client client) : ControllerBase
public async Task<IActionResult> GetOrder([FromRoute] string id)
{
var query = Query.FQL($"""
let order: Any = Order.byId({id})!
let order = Order.byId({id})!
{QuerySnippets.OrderResponse()}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var res = await client.QueryAsync<Order>(query);
Expand Down Expand Up @@ -58,19 +58,19 @@ OrderRequest order
// for validating that the order in a valid state to be processed and decrements the stock
// of each product in the order. This ensures that the product stock is updated in the same transaction
// as the order status.
var query = order.Status == "processing"
var query = order.Status == "processing"
? Query.FQL($"""
let req = {order}
let order: Any = checkout({id}, req.status, req.payment)
let order = checkout({id}, req.status, req.payment)
{QuerySnippets.OrderResponse()}
""")

// Define an FQL query to update the order. The query first retrieves the order by id
// using the Order.byId function. If the order does not exist, Fauna will throw a document_not_found
// error. We then use the validateOrderStatusTransition UDF to ensure that the order status transition
// is valid. If the transition is not valid, the UDF will throw an abort error.
: Query.FQL($$"""
let order: Any = Order.byId({{id}})!
let order = Order.byId({{id}})!
let req = {{order}}
// Validate the order status transition if a status is provided.
Expand Down
34 changes: 17 additions & 17 deletions DotNetSampleApp/Controllers/Products.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,24 @@ public async Task<IActionResult> PaginateProducts(
// fragment will either return all products sorted by category or all products in a specific
// category depending on whether the category query parameter is provided. This will later
// be embedded in a larger query.
var queryPrefix = string.IsNullOrEmpty(category)
? Query.FQL($"Product.sortedByCategory().pageSize({pageSize})")
var queryPrefix = string.IsNullOrEmpty(category)
? Query.FQL($"Product.sortedByCategory().pageSize({pageSize})")
: Query.FQL($"""
let category = Category.byName({category}).first()
if (category == null) abort("Category does not exist.")
Product.byCategory(category).pageSize({pageSize})
""");

// The `afterToken` parameter contains a Fauna `after` cursor.
// `after` cursors may contain special characters, such as `.` or `+`).
// Make sure to URL encode the `afterToken` value to preserve these
// characters in URLs.
var query = !string.IsNullOrEmpty(afterToken)
var query = !string.IsNullOrEmpty(afterToken)

// Paginate with the after token if it's present.
? Query.FQL($"Set.paginate({afterToken})")

// Define the main query. This query will return a page of products using the query fragment
// defined above.
: Query.FQL($$"""
Expand Down Expand Up @@ -90,17 +90,17 @@ public async Task<IActionResult> CreateProduct(ProductRequest product)
if (category == null) abort("Category does not exist.")
// Create the product with the given values.
let args = {
let args = {
name: {{product.Name}},
price: {{product.Price}},
stock: {{product.Stock}},
description: {{product.Description}},
category: category
category: category
}
let product: Any = Product.create(args)
let product = Product.create(args)
{{QuerySnippets.ProductResponse()}}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var result = await client.QueryAsync<Product>(query);
Expand All @@ -125,7 +125,7 @@ public async Task<IActionResult> UpdateProductById(
var query = Query.FQL($$"""
// Get the product by id, using the ! operator to assert that the product exists.
// If it does not exist Fauna will throw a document_not_found error.
let product: Any = Product.byId({{id}})!
let product = Product.byId({{id}})!
if (product == null) abort("Product does not exist.")
// Get the category by name. We can use .first() here because we know that the category
Expand All @@ -134,7 +134,7 @@ public async Task<IActionResult> UpdateProductById(
if (category == null) abort("Category does not exist.")
// Update category if a new one was provided
let newCategory: Any = Category.byName({{product.Category}})?.first()
let newCategory = Category.byName({{product.Category}})?.first()
let fields = {
name: {{product.Name}},
Expand All @@ -154,7 +154,7 @@ public async Task<IActionResult> UpdateProductById(
{{QuerySnippets.ProductResponse()}}
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var result = await client.QueryAsync<Product>(query);
Expand Down Expand Up @@ -185,10 +185,10 @@ public async Task<IActionResult> SearchProducts(
// Make sure to URL encode the `afterToken` value to preserve these
// characters in URLs.
var query = !string.IsNullOrEmpty(afterToken)

// Paginate with the after token if it's present.
? Query.FQL($"Set.paginate({afterToken})")

// This is an example of a covered query. A covered query is a query where all fields
// returned are indexed fields. In this case, we are querying the Product collection
// for products with a price between minPrice and maxPrice. We are also limiting the
Expand All @@ -204,7 +204,7 @@ public async Task<IActionResult> SearchProducts(
{{QuerySnippets.ProductResponse()}}
})
""");

// Connect to fauna using the client. The query method accepts an FQL query
// as a parameter and a generic type parameter representing the return type.
var result = await client.QueryAsync<Page<Product>>(query);
Expand Down
17 changes: 8 additions & 9 deletions DotNetSampleApp/Controllers/QuerySnippets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace DotNetSampleApp.Controllers;
public static class QuerySnippets
{
/// <summary>
/// A Query snippet for customer response projection.
/// A Query snippet for customer response projection.
/// </summary>
/// <returns></returns>
public static Query CustomerResponse()
Expand All @@ -25,11 +25,11 @@ public static Query CustomerResponse()
}

/// <summary>
/// A Query snippet for order response projection.
/// A Query snippet for order response projection.
/// </summary>
/// <returns></returns>
public static Query OrderResponse()
{
{
return Query.FQL($$"""
{
id: order.id,
Expand Down Expand Up @@ -63,27 +63,26 @@ public static Query OrderResponse()
}

/// <summary>
/// A Query snippet for product response projection.
/// A Query snippet for product response projection.
/// </summary>
/// <returns></returns>
public static Query ProductResponse()
{
return Query.FQL($$"""
// Use projection to return only the necessary fields.
let product: Any = product
let category: Any = product.category
product {
id: product.id,
name: product.name,
price: product.price,
description: product.description,
stock: product.stock,
category: {
id: category.id,
name: category.name,
description: category.description
id: product.category.id,
name: product.category.name,
description: product.category.description
},
}
""");
}
}
}
10 changes: 5 additions & 5 deletions DotNetSampleApp/Services/SeedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static void Init(Client client)
{name: 'The Godfather II', price: 1299, description: 'A movie by Francis Ford Coppola', stock: 10, category: 'movies'},
{name: 'The Godfather III', price: 1299, description: 'A movie by Francis Ford Coppola', stock: 10, category: 'movies'}
].map(p => {
let existing: Any = Product.byName(p.name).first()
let existing = Product.byName(p.name).first()
if (existing != null) {
existing!.update({ stock: p.stock })
} else {
Expand Down Expand Up @@ -85,16 +85,16 @@ public static void Init(Client client)
client.QueryAsync(Query.FQL($$"""
let customer = Customer.byEmail('[email protected]').first()!
let orders = ['cart', 'processing', 'shipped', 'delivered'].map(status => {
let order: Any = Order.byCustomer(customer).firstWhere(o => o.status == status)
let order = Order.byCustomer(customer).firstWhere(o => o.status == status)
if (order == null) {
let newOrder: Any = Order.create({
let newOrder = Order.create({
customer: customer,
status: status,
createdAt: Time.now(),
payment: {}
})
let product: Any = Product.byName('Drone').first()!
let orderItem: Any = OrderItem.create({ order: newOrder, product: product, quantity: 1 })
let product = Product.byName('Drone').first()!
let orderItem = OrderItem.create({ order: newOrder, product: product, quantity: 1 })
orderItem
newOrder
} else {
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ Customer documents and related API responses:
+ // Use a computed field to calculate the customer's cumulative purchase total.
+ // The field sums purchase `total` values from the customer's linked Order documents.
+ compute totalPurchaseAmt: Number = (customer => customer.orders.fold(0, (sum, order) => {
+ let order: Any = order
+ sum + order.total
+ }))
...
Expand Down
3 changes: 1 addition & 2 deletions schema/collections.fsl
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ collection Order {

compute items: Set<OrderItem> = (order => OrderItem.byOrder(order))
compute total: Number = (order => order.items.fold(0, (sum, orderItem) => {
let orderItem: Any = orderItem
if (orderItem.product != null) {
sum + orderItem.product.price * orderItem.quantity
sum + orderItem.product!.price * orderItem.quantity
} else {
sum
}
Expand Down
Loading

0 comments on commit 9c9c07c

Please sign in to comment.