Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[DOCS-3787] Remove FQL typer workarounds #13

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('fake@fauna.com').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
Loading