diff --git a/bin/hermit.hcl b/bin/hermit.hcl index e69de29b..624421a0 100644 --- a/bin/hermit.hcl +++ b/bin/hermit.hcl @@ -0,0 +1 @@ +sources = ["https://github.com/TBD54566975/hermit-ftl.git", "https://github.com/cashapp/hermit-packages.git"] diff --git a/frontend/mobile/lib/api/ad.dart b/frontend/mobile/lib/api/ad.dart index 2faa38bc..4721e4aa 100644 --- a/frontend/mobile/lib/api/ad.dart +++ b/frontend/mobile/lib/api/ad.dart @@ -8,11 +8,11 @@ import 'ftl_client.dart'; class AdRequest { List contextKeys; - AdRequest({ required this.contextKeys, }); + AdRequest({ required this.contextKeys, }); Map toMap() { return { - 'contextKeys': ((dynamic v) =>v.map((v) => v).cast().toList())(contextKeys), + 'contextKeys': ((dynamic v) => v.map((v) => v).cast().toList())(contextKeys), }; } @@ -31,12 +31,12 @@ class Ad { String redirectURL; String text; - Ad({ required this.redirectURL, required this.text, }); + Ad({ required this.redirectURL, required this.text, }); Map toMap() { return { - 'redirectURL': ((dynamic v) =>v)(redirectURL), - 'text': ((dynamic v) =>v)(text), + 'redirectURL': ((dynamic v) => v)(redirectURL), + 'text': ((dynamic v) => v)(text), }; } @@ -56,12 +56,12 @@ class AdResponse { String name; List ads; - AdResponse({ required this.name, required this.ads, }); + AdResponse({ required this.name, required this.ads, }); Map toMap() { return { - 'name': ((dynamic v) =>v)(name), - 'ads': ((dynamic v) =>v.map((v) => Ad.fromMap(v)).cast().toList())(ads), + 'name': ((dynamic v) => v)(name), + 'ads': ((dynamic v) => v.map((v) => Ad.fromMap(v)).cast().toList())(ads), }; } @@ -85,7 +85,7 @@ class AdClient { Future get(AdRequest request) async { - final response = await ftlClient.get('/ad', request: request.toMap()); + final response = await ftlClient.get('/ad', requestJson: request.toJson()); if (response.statusCode == 200) { return AdResponse.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/cart.dart b/frontend/mobile/lib/api/cart.dart index 8608fbb2..5890e88c 100644 --- a/frontend/mobile/lib/api/cart.dart +++ b/frontend/mobile/lib/api/cart.dart @@ -9,12 +9,12 @@ class Item { String productID; int quantity; - Item({ required this.productID, required this.quantity, }); + Item({ required this.productID, required this.quantity, }); Map toMap() { return { - 'productID': ((dynamic v) =>v)(productID), - 'quantity': ((dynamic v) =>v)(quantity), + 'productID': ((dynamic v) => v)(productID), + 'quantity': ((dynamic v) => v)(quantity), }; } @@ -34,12 +34,12 @@ class AddItemRequest { String userID; Item item; - AddItemRequest({ required this.userID, required this.item, }); + AddItemRequest({ required this.userID, required this.item, }); Map toMap() { return { - 'userID': ((dynamic v) =>v)(userID), - 'item': ((dynamic v) =>v.toMap())(item), + 'userID': ((dynamic v) => v)(userID), + 'item': ((dynamic v) => v.toMap())(item), }; } @@ -77,11 +77,11 @@ class AddItemResponse { class GetCartRequest { String userID; - GetCartRequest({ required this.userID, }); + GetCartRequest({ required this.userID, }); Map toMap() { return { - 'userID': ((dynamic v) =>v)(userID), + 'userID': ((dynamic v) => v)(userID), }; } @@ -100,12 +100,12 @@ class Cart { String userID; List items; - Cart({ required this.userID, required this.items, }); + Cart({ required this.userID, required this.items, }); Map toMap() { return { - 'userID': ((dynamic v) =>v)(userID), - 'items': ((dynamic v) =>v.map((v) => Item.fromMap(v)).cast().toList())(items), + 'userID': ((dynamic v) => v)(userID), + 'items': ((dynamic v) => v.map((v) => Item.fromMap(v)).cast().toList())(items), }; } @@ -124,11 +124,11 @@ class Cart { class EmptyCartRequest { String userID; - EmptyCartRequest({ required this.userID, }); + EmptyCartRequest({ required this.userID, }); Map toMap() { return { - 'userID': ((dynamic v) =>v)(userID), + 'userID': ((dynamic v) => v)(userID), }; } @@ -179,7 +179,7 @@ class CartClient { } Future getCart(GetCartRequest request) async { - final response = await ftlClient.get('/cart', request: request.toMap()); + final response = await ftlClient.get('/cart', requestJson: request.toJson()); if (response.statusCode == 200) { return Cart.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/checkout.dart b/frontend/mobile/lib/api/checkout.dart index fb0fee9f..99dc9a7b 100644 --- a/frontend/mobile/lib/api/checkout.dart +++ b/frontend/mobile/lib/api/checkout.dart @@ -3,94 +3,29 @@ library checkout; import 'dart:convert'; import 'ftl_client.dart'; -import 'productcatalog.dart' as productcatalog; -import 'currency.dart' as currency; import 'shipping.dart' as shipping; import 'payment.dart' as payment; import 'cart.dart' as cart; +import 'currency.dart' as currency; +import 'productcatalog.dart' as productcatalog; -class Address { - String streetAddress; - String city; - String state; - String country; - int zipCode; - - Address({ required this.streetAddress, required this.city, required this.state, required this.country, required this.zipCode, }); - - Map toMap() { - return { - 'streetAddress': ((dynamic v) =>v)(streetAddress), - 'city': ((dynamic v) =>v)(city), - 'state': ((dynamic v) =>v)(state), - 'country': ((dynamic v) =>v)(country), - 'zipCode': ((dynamic v) =>v)(zipCode), - }; - } - - factory Address.fromMap(Map map) { - return Address( - streetAddress: ((dynamic v) => v)(map['streetAddress']), - city: ((dynamic v) => v)(map['city']), - state: ((dynamic v) => v)(map['state']), - country: ((dynamic v) => v)(map['country']), - zipCode: ((dynamic v) => v)(map['zipCode']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Address.fromJson(String source) => Address.fromMap(json.decode(source)); -} - -class CreditCardInfo { - String number; - int cVV; - int expirationYear; - int expirationMonth; - - CreditCardInfo({ required this.number, required this.cVV, required this.expirationYear, required this.expirationMonth, }); - - Map toMap() { - return { - 'number': ((dynamic v) =>v)(number), - 'cVV': ((dynamic v) =>v)(cVV), - 'expirationYear': ((dynamic v) =>v)(expirationYear), - 'expirationMonth': ((dynamic v) =>v)(expirationMonth), - }; - } - - factory CreditCardInfo.fromMap(Map map) { - return CreditCardInfo( - number: ((dynamic v) => v)(map['number']), - cVV: ((dynamic v) => v)(map['cVV']), - expirationYear: ((dynamic v) => v)(map['expirationYear']), - expirationMonth: ((dynamic v) => v)(map['expirationMonth']), - ); - } - - String toJson() => json.encode(toMap()); - - factory CreditCardInfo.fromJson(String source) => CreditCardInfo.fromMap(json.decode(source)); -} - class PlaceOrderRequest { String userID; String userCurrency; - Address address; + shipping.Address address; String email; - CreditCardInfo creditCard; + payment.CreditCardInfo creditCard; - PlaceOrderRequest({ required this.userID, required this.userCurrency, required this.address, required this.email, required this.creditCard, }); + PlaceOrderRequest({ required this.userID, required this.userCurrency, required this.address, required this.email, required this.creditCard, }); Map toMap() { return { - 'userID': ((dynamic v) =>v)(userID), - 'userCurrency': ((dynamic v) =>v)(userCurrency), - 'address': ((dynamic v) =>v.toMap())(address), - 'email': ((dynamic v) =>v)(email), - 'creditCard': ((dynamic v) =>v.toMap())(creditCard), + 'userID': ((dynamic v) => v)(userID), + 'userCurrency': ((dynamic v) => v)(userCurrency), + 'address': ((dynamic v) => v.toMap())(address), + 'email': ((dynamic v) => v)(email), + 'creditCard': ((dynamic v) => v.toMap())(creditCard), }; } @@ -98,9 +33,9 @@ class PlaceOrderRequest { return PlaceOrderRequest( userID: ((dynamic v) => v)(map['userID']), userCurrency: ((dynamic v) => v)(map['userCurrency']), - address: ((dynamic v) => Address.fromMap(v))(map['address']), + address: ((dynamic v) => shipping.Address.fromMap(v))(map['address']), email: ((dynamic v) => v)(map['email']), - creditCard: ((dynamic v) => CreditCardInfo.fromMap(v))(map['creditCard']), + creditCard: ((dynamic v) => payment.CreditCardInfo.fromMap(v))(map['creditCard']), ); } @@ -109,76 +44,23 @@ class PlaceOrderRequest { factory PlaceOrderRequest.fromJson(String source) => PlaceOrderRequest.fromMap(json.decode(source)); } -class Money { - String currencyCode; - int units; - int nanos; - - Money({ required this.currencyCode, required this.units, required this.nanos, }); - - Map toMap() { - return { - 'currencyCode': ((dynamic v) =>v)(currencyCode), - 'units': ((dynamic v) =>v)(units), - 'nanos': ((dynamic v) =>v)(nanos), - }; - } - - factory Money.fromMap(Map map) { - return Money( - currencyCode: ((dynamic v) => v)(map['currencyCode']), - units: ((dynamic v) => v)(map['units']), - nanos: ((dynamic v) => v)(map['nanos']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Money.fromJson(String source) => Money.fromMap(json.decode(source)); -} - -class Item { - String productID; - int quantity; - - Item({ required this.productID, required this.quantity, }); - - Map toMap() { - return { - 'productID': ((dynamic v) =>v)(productID), - 'quantity': ((dynamic v) =>v)(quantity), - }; - } - - factory Item.fromMap(Map map) { - return Item( - productID: ((dynamic v) => v)(map['productID']), - quantity: ((dynamic v) => v)(map['quantity']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Item.fromJson(String source) => Item.fromMap(json.decode(source)); -} - class OrderItem { - Item item; - Money cost; + cart.Item item; + currency.Money cost; - OrderItem({ required this.item, required this.cost, }); + OrderItem({ required this.item, required this.cost, }); Map toMap() { return { - 'item': ((dynamic v) =>v.toMap())(item), - 'cost': ((dynamic v) =>v.toMap())(cost), + 'item': ((dynamic v) => v.toMap())(item), + 'cost': ((dynamic v) => v.toMap())(cost), }; } factory OrderItem.fromMap(Map map) { return OrderItem( - item: ((dynamic v) => Item.fromMap(v))(map['item']), - cost: ((dynamic v) => Money.fromMap(v))(map['cost']), + item: ((dynamic v) => cart.Item.fromMap(v))(map['item']), + cost: ((dynamic v) => currency.Money.fromMap(v))(map['cost']), ); } @@ -190,19 +72,19 @@ class OrderItem { class Order { String id; String shippingTrackingID; - Money shippingCost; - Address shippingAddress; + currency.Money shippingCost; + shipping.Address shippingAddress; List items; - Order({ required this.id, required this.shippingTrackingID, required this.shippingCost, required this.shippingAddress, required this.items, }); + Order({ required this.id, required this.shippingTrackingID, required this.shippingCost, required this.shippingAddress, required this.items, }); Map toMap() { return { - 'id': ((dynamic v) =>v)(id), - 'shippingTrackingID': ((dynamic v) =>v)(shippingTrackingID), - 'shippingCost': ((dynamic v) =>v.toMap())(shippingCost), - 'shippingAddress': ((dynamic v) =>v.toMap())(shippingAddress), - 'items': ((dynamic v) =>v.map((v) => OrderItem.fromMap(v)).cast().toList())(items), + 'id': ((dynamic v) => v)(id), + 'shippingTrackingID': ((dynamic v) => v)(shippingTrackingID), + 'shippingCost': ((dynamic v) => v.toMap())(shippingCost), + 'shippingAddress': ((dynamic v) => v.toMap())(shippingAddress), + 'items': ((dynamic v) => v.map((v) => OrderItem.fromMap(v)).cast().toList())(items), }; } @@ -210,8 +92,8 @@ class Order { return Order( id: ((dynamic v) => v)(map['id']), shippingTrackingID: ((dynamic v) => v)(map['shippingTrackingID']), - shippingCost: ((dynamic v) => Money.fromMap(v))(map['shippingCost']), - shippingAddress: ((dynamic v) => Address.fromMap(v))(map['shippingAddress']), + shippingCost: ((dynamic v) => currency.Money.fromMap(v))(map['shippingCost']), + shippingAddress: ((dynamic v) => shipping.Address.fromMap(v))(map['shippingAddress']), items: ((dynamic v) => v.map((v) => OrderItem.fromMap(v)).cast().toList())(map['items']), ); } @@ -229,7 +111,7 @@ class CheckoutClient { Future placeOrder(PlaceOrderRequest request) async { - final response = await ftlClient.post('/checkout', request: request.toMap()); + final response = await ftlClient.post('/checkout/${request.userID}', request: request.toMap()); if (response.statusCode == 200) { return Order.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/currency.dart b/frontend/mobile/lib/api/currency.dart index 5b0f6add..6175e7f6 100644 --- a/frontend/mobile/lib/api/currency.dart +++ b/frontend/mobile/lib/api/currency.dart @@ -27,11 +27,11 @@ class GetSupportedCurrenciesRequest { class GetSupportedCurrenciesResponse { List currencyCodes; - GetSupportedCurrenciesResponse({ required this.currencyCodes, }); + GetSupportedCurrenciesResponse({ required this.currencyCodes, }); Map toMap() { return { - 'currencyCodes': ((dynamic v) =>v.map((v) => v).cast().toList())(currencyCodes), + 'currencyCodes': ((dynamic v) => v.map((v) => v).cast().toList())(currencyCodes), }; } @@ -51,13 +51,13 @@ class Money { int units; int nanos; - Money({ required this.currencyCode, required this.units, required this.nanos, }); + Money({ required this.currencyCode, required this.units, required this.nanos, }); Map toMap() { return { - 'currencyCode': ((dynamic v) =>v)(currencyCode), - 'units': ((dynamic v) =>v)(units), - 'nanos': ((dynamic v) =>v)(nanos), + 'currencyCode': ((dynamic v) => v)(currencyCode), + 'units': ((dynamic v) => v)(units), + 'nanos': ((dynamic v) => v)(nanos), }; } @@ -78,12 +78,12 @@ class ConvertRequest { Money from; String toCode; - ConvertRequest({ required this.from, required this.toCode, }); + ConvertRequest({ required this.from, required this.toCode, }); Map toMap() { return { - 'from': ((dynamic v) =>v.toMap())(from), - 'toCode': ((dynamic v) =>v)(toCode), + 'from': ((dynamic v) => v.toMap())(from), + 'toCode': ((dynamic v) => v)(toCode), }; } @@ -107,7 +107,7 @@ class CurrencyClient { Future getSupportedCurrencies(GetSupportedCurrenciesRequest request) async { - final response = await ftlClient.get('/currency/supported', request: request.toMap()); + final response = await ftlClient.get('/currency/supported', requestJson: request.toJson()); if (response.statusCode == 200) { return GetSupportedCurrenciesResponse.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/ftl_client.dart b/frontend/mobile/lib/api/ftl_client.dart index f93b686b..3ce3a52f 100644 --- a/frontend/mobile/lib/api/ftl_client.dart +++ b/frontend/mobile/lib/api/ftl_client.dart @@ -21,14 +21,14 @@ class FTLHttpClient { Future get( String path, { - Map? request, + String? requestJson, Map? headers, }) { Uri uri; - if (request == null || request.isEmpty) { + if (requestJson == null || requestJson.isEmpty) { uri = Uri.http("localhost:8892", '/ingress$path'); } else { - uri = Uri.http("localhost:8892", '/ingress$path', request); + uri = Uri.http("localhost:8892", '/ingress$path', {'@json': requestJson}); } return httpClient.get(uri, headers: headers); } diff --git a/frontend/mobile/lib/api/payment.dart b/frontend/mobile/lib/api/payment.dart index c6a35bdf..d4d6388f 100644 --- a/frontend/mobile/lib/api/payment.dart +++ b/frontend/mobile/lib/api/payment.dart @@ -3,50 +3,23 @@ library payment; import 'dart:convert'; import 'ftl_client.dart'; +import 'currency.dart' as currency; -class Money { - String currencyCode; - int units; - int nanos; - - Money({ required this.currencyCode, required this.units, required this.nanos, }); - - Map toMap() { - return { - 'currencyCode': ((dynamic v) =>v)(currencyCode), - 'units': ((dynamic v) =>v)(units), - 'nanos': ((dynamic v) =>v)(nanos), - }; - } - - factory Money.fromMap(Map map) { - return Money( - currencyCode: ((dynamic v) => v)(map['currencyCode']), - units: ((dynamic v) => v)(map['units']), - nanos: ((dynamic v) => v)(map['nanos']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Money.fromJson(String source) => Money.fromMap(json.decode(source)); -} - class CreditCardInfo { String number; int cVV; int expirationYear; int expirationMonth; - CreditCardInfo({ required this.number, required this.cVV, required this.expirationYear, required this.expirationMonth, }); + CreditCardInfo({ required this.number, required this.cVV, required this.expirationYear, required this.expirationMonth, }); Map toMap() { return { - 'number': ((dynamic v) =>v)(number), - 'cVV': ((dynamic v) =>v)(cVV), - 'expirationYear': ((dynamic v) =>v)(expirationYear), - 'expirationMonth': ((dynamic v) =>v)(expirationMonth), + 'number': ((dynamic v) => v)(number), + 'cVV': ((dynamic v) => v)(cVV), + 'expirationYear': ((dynamic v) => v)(expirationYear), + 'expirationMonth': ((dynamic v) => v)(expirationMonth), }; } @@ -65,21 +38,21 @@ class CreditCardInfo { } class ChargeRequest { - Money amount; + currency.Money amount; CreditCardInfo creditCard; - ChargeRequest({ required this.amount, required this.creditCard, }); + ChargeRequest({ required this.amount, required this.creditCard, }); Map toMap() { return { - 'amount': ((dynamic v) =>v.toMap())(amount), - 'creditCard': ((dynamic v) =>v.toMap())(creditCard), + 'amount': ((dynamic v) => v.toMap())(amount), + 'creditCard': ((dynamic v) => v.toMap())(creditCard), }; } factory ChargeRequest.fromMap(Map map) { return ChargeRequest( - amount: ((dynamic v) => Money.fromMap(v))(map['amount']), + amount: ((dynamic v) => currency.Money.fromMap(v))(map['amount']), creditCard: ((dynamic v) => CreditCardInfo.fromMap(v))(map['creditCard']), ); } @@ -92,11 +65,11 @@ class ChargeRequest { class ChargeResponse { String transactionID; - ChargeResponse({ required this.transactionID, }); + ChargeResponse({ required this.transactionID, }); Map toMap() { return { - 'transactionID': ((dynamic v) =>v)(transactionID), + 'transactionID': ((dynamic v) => v)(transactionID), }; } diff --git a/frontend/mobile/lib/api/productcatalog.dart b/frontend/mobile/lib/api/productcatalog.dart index a9b56c5a..b799ab66 100644 --- a/frontend/mobile/lib/api/productcatalog.dart +++ b/frontend/mobile/lib/api/productcatalog.dart @@ -3,6 +3,7 @@ library productcatalog; import 'dart:convert'; import 'ftl_client.dart'; +import 'currency.dart' as currency; class ListRequest { @@ -24,52 +25,24 @@ class ListRequest { factory ListRequest.fromJson(String source) => ListRequest.fromMap(json.decode(source)); } -class Money { - String currencyCode; - int units; - int nanos; - - Money({ required this.currencyCode, required this.units, required this.nanos, }); - - Map toMap() { - return { - 'currencyCode': ((dynamic v) =>v)(currencyCode), - 'units': ((dynamic v) =>v)(units), - 'nanos': ((dynamic v) =>v)(nanos), - }; - } - - factory Money.fromMap(Map map) { - return Money( - currencyCode: ((dynamic v) => v)(map['currencyCode']), - units: ((dynamic v) => v)(map['units']), - nanos: ((dynamic v) => v)(map['nanos']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Money.fromJson(String source) => Money.fromMap(json.decode(source)); -} - class Product { String id; String name; String description; String picture; - Money priceUSD; + currency.Money priceUSD; List categories; - Product({ required this.id, required this.name, required this.description, required this.picture, required this.priceUSD, required this.categories, }); + Product({ required this.id, required this.name, required this.description, required this.picture, required this.priceUSD, required this.categories, }); Map toMap() { return { - 'id': ((dynamic v) =>v)(id), - 'name': ((dynamic v) =>v)(name), - 'description': ((dynamic v) =>v)(description), - 'picture': ((dynamic v) =>v)(picture), - 'priceUSD': ((dynamic v) =>v.toMap())(priceUSD), - 'categories': ((dynamic v) =>v.map((v) => v).cast().toList())(categories), + 'id': ((dynamic v) => v)(id), + 'name': ((dynamic v) => v)(name), + 'description': ((dynamic v) => v)(description), + 'picture': ((dynamic v) => v)(picture), + 'priceUSD': ((dynamic v) => v.toMap())(priceUSD), + 'categories': ((dynamic v) => v.map((v) => v).cast().toList())(categories), }; } @@ -79,7 +52,7 @@ class Product { name: ((dynamic v) => v)(map['name']), description: ((dynamic v) => v)(map['description']), picture: ((dynamic v) => v)(map['picture']), - priceUSD: ((dynamic v) => Money.fromMap(v))(map['priceUSD']), + priceUSD: ((dynamic v) => currency.Money.fromMap(v))(map['priceUSD']), categories: ((dynamic v) => v.map((v) => v).cast().toList())(map['categories']), ); } @@ -92,11 +65,11 @@ class Product { class ListResponse { List products; - ListResponse({ required this.products, }); + ListResponse({ required this.products, }); Map toMap() { return { - 'products': ((dynamic v) =>v.map((v) => Product.fromMap(v)).cast().toList())(products), + 'products': ((dynamic v) => v.map((v) => Product.fromMap(v)).cast().toList())(products), }; } @@ -114,11 +87,11 @@ class ListResponse { class GetRequest { String id; - GetRequest({ required this.id, }); + GetRequest({ required this.id, }); Map toMap() { return { - 'id': ((dynamic v) =>v)(id), + 'id': ((dynamic v) => v)(id), }; } @@ -136,11 +109,11 @@ class GetRequest { class SearchRequest { String query; - SearchRequest({ required this.query, }); + SearchRequest({ required this.query, }); Map toMap() { return { - 'query': ((dynamic v) =>v)(query), + 'query': ((dynamic v) => v)(query), }; } @@ -158,11 +131,11 @@ class SearchRequest { class SearchResponse { List results; - SearchResponse({ required this.results, }); + SearchResponse({ required this.results, }); Map toMap() { return { - 'results': ((dynamic v) =>v.map((v) => Product.fromMap(v)).cast().toList())(results), + 'results': ((dynamic v) => v.map((v) => Product.fromMap(v)).cast().toList())(results), }; } @@ -185,7 +158,7 @@ class ProductcatalogClient { Future list(ListRequest request) async { - final response = await ftlClient.get('/productcatalog', request: request.toMap()); + final response = await ftlClient.get('/productcatalog', requestJson: request.toJson()); if (response.statusCode == 200) { return ListResponse.fromJson(response.body); } else { @@ -194,7 +167,7 @@ class ProductcatalogClient { } Future get(GetRequest request) async { - final response = await ftlClient.get('/productcatalog/id', request: request.toMap()); + final response = await ftlClient.get('/productcatalog/${request.id}', requestJson: request.toJson()); if (response.statusCode == 200) { return Product.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/recommendation.dart b/frontend/mobile/lib/api/recommendation.dart index fed4e74f..7cec7332 100644 --- a/frontend/mobile/lib/api/recommendation.dart +++ b/frontend/mobile/lib/api/recommendation.dart @@ -10,12 +10,12 @@ class ListRequest { String userID; List userProductIDs; - ListRequest({ required this.userID, required this.userProductIDs, }); + ListRequest({ required this.userID, required this.userProductIDs, }); Map toMap() { return { - 'userID': ((dynamic v) =>v)(userID), - 'userProductIDs': ((dynamic v) =>v.map((v) => v).cast().toList())(userProductIDs), + 'userID': ((dynamic v) => v)(userID), + 'userProductIDs': ((dynamic v) => v.map((v) => v).cast().toList())(userProductIDs), }; } @@ -34,11 +34,11 @@ class ListRequest { class ListResponse { List productIDs; - ListResponse({ required this.productIDs, }); + ListResponse({ required this.productIDs, }); Map toMap() { return { - 'productIDs': ((dynamic v) =>v.map((v) => v).cast().toList())(productIDs), + 'productIDs': ((dynamic v) => v.map((v) => v).cast().toList())(productIDs), }; } @@ -61,7 +61,7 @@ class RecommendationClient { Future list(ListRequest request) async { - final response = await ftlClient.get('/recommendation', request: request.toMap()); + final response = await ftlClient.get('/recommendation', requestJson: request.toJson()); if (response.statusCode == 200) { return ListResponse.fromJson(response.body); } else { diff --git a/frontend/mobile/lib/api/shipping.dart b/frontend/mobile/lib/api/shipping.dart index d4d958e8..fd2a9381 100644 --- a/frontend/mobile/lib/api/shipping.dart +++ b/frontend/mobile/lib/api/shipping.dart @@ -3,6 +3,8 @@ library shipping; import 'dart:convert'; import 'ftl_client.dart'; +import 'cart.dart' as cart; +import 'currency.dart' as currency; class Address { @@ -12,15 +14,15 @@ class Address { String country; int zipCode; - Address({ required this.streetAddress, required this.city, required this.state, required this.country, required this.zipCode, }); + Address({ required this.streetAddress, required this.city, required this.state, required this.country, required this.zipCode, }); Map toMap() { return { - 'streetAddress': ((dynamic v) =>v)(streetAddress), - 'city': ((dynamic v) =>v)(city), - 'state': ((dynamic v) =>v)(state), - 'country': ((dynamic v) =>v)(country), - 'zipCode': ((dynamic v) =>v)(zipCode), + 'streetAddress': ((dynamic v) => v)(streetAddress), + 'city': ((dynamic v) => v)(city), + 'state': ((dynamic v) => v)(state), + 'country': ((dynamic v) => v)(country), + 'zipCode': ((dynamic v) => v)(zipCode), }; } @@ -39,48 +41,23 @@ class Address { factory Address.fromJson(String source) => Address.fromMap(json.decode(source)); } -class Item { - String productID; - int quantity; - - Item({ required this.productID, required this.quantity, }); - - Map toMap() { - return { - 'productID': ((dynamic v) =>v)(productID), - 'quantity': ((dynamic v) =>v)(quantity), - }; - } - - factory Item.fromMap(Map map) { - return Item( - productID: ((dynamic v) => v)(map['productID']), - quantity: ((dynamic v) => v)(map['quantity']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Item.fromJson(String source) => Item.fromMap(json.decode(source)); -} - class ShippingRequest { Address address; - List items; + List items; - ShippingRequest({ required this.address, required this.items, }); + ShippingRequest({ required this.address, required this.items, }); Map toMap() { return { - 'address': ((dynamic v) =>v.toMap())(address), - 'items': ((dynamic v) =>v.map((v) => Item.fromMap(v)).cast().toList())(items), + 'address': ((dynamic v) => v.toMap())(address), + 'items': ((dynamic v) => v.map((v) => cart.Item.fromMap(v)).cast().toList())(items), }; } factory ShippingRequest.fromMap(Map map) { return ShippingRequest( address: ((dynamic v) => Address.fromMap(v))(map['address']), - items: ((dynamic v) => v.map((v) => Item.fromMap(v)).cast().toList())(map['items']), + items: ((dynamic v) => v.map((v) => cart.Item.fromMap(v)).cast().toList())(map['items']), ); } @@ -89,42 +66,14 @@ class ShippingRequest { factory ShippingRequest.fromJson(String source) => ShippingRequest.fromMap(json.decode(source)); } -class Money { - String currencyCode; - int units; - int nanos; - - Money({ required this.currencyCode, required this.units, required this.nanos, }); - - Map toMap() { - return { - 'currencyCode': ((dynamic v) =>v)(currencyCode), - 'units': ((dynamic v) =>v)(units), - 'nanos': ((dynamic v) =>v)(nanos), - }; - } - - factory Money.fromMap(Map map) { - return Money( - currencyCode: ((dynamic v) => v)(map['currencyCode']), - units: ((dynamic v) => v)(map['units']), - nanos: ((dynamic v) => v)(map['nanos']), - ); - } - - String toJson() => json.encode(toMap()); - - factory Money.fromJson(String source) => Money.fromMap(json.decode(source)); -} - class ShipOrderResponse { String id; - ShipOrderResponse({ required this.id, }); + ShipOrderResponse({ required this.id, }); Map toMap() { return { - 'id': ((dynamic v) =>v)(id), + 'id': ((dynamic v) => v)(id), }; } @@ -146,10 +95,10 @@ class ShippingClient { ShippingClient({required this.ftlClient}); - Future getQuote(ShippingRequest request) async { + Future getQuote(ShippingRequest request) async { final response = await ftlClient.post('/shipping/quote', request: request.toMap()); if (response.statusCode == 200) { - return Money.fromJson(response.body); + return currency.Money.fromJson(response.body); } else { throw Exception('Failed to get getQuote response'); } diff --git a/frontend/mobile/lib/utils/money_utils.dart b/frontend/mobile/lib/utils/money_utils.dart index 850db079..db8f8cf5 100644 --- a/frontend/mobile/lib/utils/money_utils.dart +++ b/frontend/mobile/lib/utils/money_utils.dart @@ -1,4 +1,4 @@ -import 'package:online_boutique/api/productcatalog.dart'; +import 'package:online_boutique/api/currency.dart'; String fromMoney(Money money) { return "${money.currencyCode} ${money.units}.${money.nanos.toString().padLeft(9, '0').substring(0, 2)}"; diff --git a/frontend/mobile/templates/ftl_client.dart b/frontend/mobile/templates/ftl_client.dart index f93b686b..3ce3a52f 100644 --- a/frontend/mobile/templates/ftl_client.dart +++ b/frontend/mobile/templates/ftl_client.dart @@ -21,14 +21,14 @@ class FTLHttpClient { Future get( String path, { - Map? request, + String? requestJson, Map? headers, }) { Uri uri; - if (request == null || request.isEmpty) { + if (requestJson == null || requestJson.isEmpty) { uri = Uri.http("localhost:8892", '/ingress$path'); } else { - uri = Uri.http("localhost:8892", '/ingress$path', request); + uri = Uri.http("localhost:8892", '/ingress$path', {'@json': requestJson}); } return httpClient.get(uri, headers: headers); } diff --git a/frontend/mobile/templates/template.js b/frontend/mobile/templates/template.js new file mode 100644 index 00000000..f8bd7e84 --- /dev/null +++ b/frontend/mobile/templates/template.js @@ -0,0 +1,81 @@ +// Return the corresponding Dart type for an FTL type. +function dartType(t) { + const type = typeName(t); + switch (type) { + case "String": + return "String"; + + case "Int": + return "int"; + + case "Bool": + return "bool"; + + case "Float": + return "double"; + + case "Time": + return "DateTime"; + + case "Map": + return `Map<${dartType(t.key)}, ${dartType(t.value)}>`; + + case "Array": + return `List<${dartType(t.element)}>`; + + case "VerbRef": + case "DataRef": + if (context.name === t.module) { + return t.name; + } + return `${t.module}.${t.name}`; + + case "Optional": + return dartType(t.type) + "?"; + + default: + throw new Error(`Unspported FTL type: ${typeName(t)}`); + } + } + + function deserialize(t) { + switch (typeName(t)) { + case "Array": + return `v.map((v) => ${deserialize(t.element)}).cast<${dartType(t.element)}>().toList()`; + + case "Map": + return `v.map((k, v) => MapEntry(k, ${deserialize(t.value)})).cast<${dartType(t.key)}, ${dartType(t.value)}>()`; + + case "DataRef": + return `${dartType(t)}.fromMap(v)`; + + default: + return "v"; + } + } + + function serialize(t) { + switch (typeName(t)) { + case "Array": + return `v.map((v) => ${deserialize(t.element)}).cast<${dartType(t.element)}>().toList()`; + + case "Map": + return `v.map((k, v) => MapEntry(k, ${deserialize(t.value)})).cast<${dartType(t.key)}, ${dartType(t.value)}>()`; + + case "DataRef": + return "v.toMap()"; + + default: + return "v"; + } + } + + function url(verb) { + let path = verb.metadata[0].path; + const method = verb.metadata[0].method; + console.log(path); + + return path.replace(/{(.*?)}/g, (match, fieldName) => { + return '$' + `{request.${fieldName}}`; + }); + } diff --git a/frontend/mobile/templates/{{ .Name | lower }}.dart b/frontend/mobile/templates/{{ .Name | lower }}.dart index 5caf6d24..586499e6 100644 --- a/frontend/mobile/templates/{{ .Name | lower }}.dart +++ b/frontend/mobile/templates/{{ .Name | lower }}.dart @@ -3,60 +3,22 @@ library {{ .Name | lower }}; import 'dart:convert'; import 'ftl_client.dart'; -{{- range .Imports}} +{{- range .Imports }} import '{{. | lower }}.dart' as {{. | lower}}; {{- end}} -{{- define "darttype" -}} -{{- $type := (. | typename ) -}} -{{- if eq $type "Array" }}List<{{ template "darttype" .Element }}> -{{- else if eq $type "Map" }}Map<{{ template "darttype" .Key }}, {{ template "darttype" .Value }}> -{{- else if eq $type "DataRef" }}{{ . }} -{{- else if eq $type "Int" }}int -{{- else if eq $type "Bool" }}bool -{{- else if eq $type "Float" }}double -{{- else if eq $type "Time" }}DateTime -{{- else -}}{{ . | typename }}{{- end -}} -{{- end }} - -{{- define "deserialize" -}} -{{- $type := (. | typename ) -}} -{{- if eq $type "Array" -}} -v.map((v) => {{ template "deserialize" .Element }}).cast<{{ template "darttype" .Element }}>().toList() -{{- else if eq $type "Map" -}} -v.map((k, v) => MapEntry(k, {{ template "deserialize" .Value }})).cast<{{ template "darttype" .Key }}, {{ template "darttype" .Value }}>() -{{- else if eq $type "DataRef" -}} -{{ . }}.fromMap(v) -{{- else -}} -v -{{- end }} -{{- end }} - -{{- define "serialize" -}} -{{- $type := (. | typename ) -}} -{{- if eq $type "Array" -}} -v.map((v) => {{ template "deserialize" .Element }}).cast<{{ template "darttype" .Element }}>().toList() -{{- else if eq $type "Map" -}} -v.map((k, v) => MapEntry(k, {{ template "deserialize" .Value }})).cast<{{ template "darttype" .Key }}, {{ template "darttype" .Value }}>() -{{- else if eq $type "DataRef" -}} -v.toMap() -{{- else -}} -v -{{- end }} -{{- end }} - {{ range .Data }} class {{ .Name | camel }} { {{- range .Fields }} - {{ template "darttype" .Type }} {{ .Name }}; + {{ .Type | dartType }} {{ .Name }}; {{- end }} - {{ .Name | camel }}({{ if .Fields }}{ {{ range .Fields }}required this.{{ .Name }}, {{ end}} }{{ end }}); + {{ .Name | camel }}({{ if .Fields }}{ {{ range .Fields }}{{ if not (eq (.Type | typeName) "Optional")}} required{{end}} this.{{ .Name }}, {{ end}} }{{ end }}); Map toMap() { return { {{- range .Fields}} - '{{ .Name }}': ((dynamic v) =>{{ template "serialize" .Type }})({{ .Name }}), + '{{ .Name }}': ((dynamic v) => {{ .Type | serialize }})({{ .Name }}), {{- end }} }; } @@ -64,7 +26,7 @@ class {{ .Name | camel }} { factory {{ .Name | camel }}.fromMap(Map map) { return {{ .Name | camel }}( {{- range .Fields }} - {{ .Name }}: ((dynamic v) => {{ template "deserialize" .Type }})(map['{{ .Name }}']), + {{ .Name }}: ((dynamic v) => {{ .Type | deserialize }})(map['{{ .Name }}']), {{- end }} ); } @@ -82,11 +44,15 @@ class {{ .Name | camel }}Client { {{ range .Verbs }} {{- $verb := . -}} {{- range .Metadata }} -{{ if eq "MetadataIngress" (. | typename) }} - Future<{{ $verb.Response }}> {{ $verb.Name }}({{ $verb.Request }} request) async { - final response = await ftlClient.{{ .Method | lower }}('{{ .Path }}', request: request.toMap()); +{{ if eq "MetadataIngress" (. | typeName) }} + Future<{{ $verb.Response | dartType }}> {{ $verb.Name }}({{ $verb.Request | dartType }} request) async { + {{ if eq .Method "GET" -}} + final response = await ftlClient.{{ .Method | lower }}('{{ $verb | url }}', requestJson: request.toJson()); + {{ else -}} + final response = await ftlClient.{{ .Method | lower }}('{{ $verb | url }}', request: request.toMap()); + {{ end -}} if (response.statusCode == 200) { - return {{ $verb.Response }}.fromJson(response.body); + return {{ $verb.Response | dartType }}.fromJson(response.body); } else { throw Exception('Failed to get {{ $verb.Name }} response'); } diff --git a/frontend/web/src/api/ad.ts b/frontend/web/src/api/ad.ts index e0e3652f..0826a036 100644 --- a/frontend/web/src/api/ad.ts +++ b/frontend/web/src/api/ad.ts @@ -6,7 +6,9 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; +// + + export interface AdRequest { contextKeys: string[]; } @@ -17,6 +19,7 @@ export interface Ad { } export interface AdResponse { + name: string; ads: Ad[]; } @@ -28,8 +31,10 @@ export class AdClient { this.baseUrl = baseUrl; } + public async get(request: AdRequest): Promise { - const response = await fetch(`${this.baseUrl}/ad?${toQueryString(request)}`, { + const path = `/ad?@json=${encodeURIComponent(JSON.stringify(request))}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/cart.ts b/frontend/web/src/api/cart.ts index 8ea9727a..059f1935 100644 --- a/frontend/web/src/api/cart.ts +++ b/frontend/web/src/api/cart.ts @@ -6,7 +6,9 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; +// + + export interface Item { productID: string; quantity: number; @@ -44,8 +46,10 @@ export class CartClient { this.baseUrl = baseUrl; } + public async addItem(request: AddItemRequest): Promise { - const response = await fetch(`${this.baseUrl}/cart/add`, { + const path = `/cart/add`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -58,8 +62,10 @@ export class CartClient { return response.json(); } + public async getCart(request: GetCartRequest): Promise { - const response = await fetch(`${this.baseUrl}/cart?${toQueryString(request)}`, { + const path = `/cart?@json=${encodeURIComponent(JSON.stringify(request))}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -72,8 +78,10 @@ export class CartClient { return response.json(); } + public async emptyCart(request: EmptyCartRequest): Promise { - const response = await fetch(`${this.baseUrl}/cart/empty`, { + const path = `/cart/empty`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/checkout.ts b/frontend/web/src/api/checkout.ts index 74e98f13..1b6b36cb 100644 --- a/frontend/web/src/api/checkout.ts +++ b/frontend/web/src/api/checkout.ts @@ -6,51 +6,32 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; -export interface Address { - streetAddress: string; - city: string; - state: string; - country: string; - zipCode: number; -} +// +import * as shipping from "./shipping" +import * as payment from "./payment" +import * as cart from "./cart" +import * as currency from "./currency" +import * as productcatalog from "./productcatalog" -export interface CreditCardInfo { - number: string; - cVV: number; - expirationYear: number; - expirationMonth: number; -} export interface PlaceOrderRequest { userID: string; userCurrency: string; - address: Address; + address: shipping.Address; email: string; - creditCard: CreditCardInfo; -} - -export interface Money { - currencyCode: string; - units: number; - nanos: number; -} - -export interface Item { - productID: string; - quantity: number; + creditCard: payment.CreditCardInfo; } export interface OrderItem { - item: Item; - cost: Money; + item: cart.Item; + cost: currency.Money; } export interface Order { id: string; shippingTrackingID: string; - shippingCost: Money; - shippingAddress: Address; + shippingCost: currency.Money; + shippingAddress: shipping.Address; items: OrderItem[]; } @@ -62,8 +43,10 @@ export class CheckoutClient { this.baseUrl = baseUrl; } + public async placeOrder(request: PlaceOrderRequest): Promise { - const response = await fetch(`${this.baseUrl}/checkout`, { + const path = `/checkout/${request.userID}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/currency.ts b/frontend/web/src/api/currency.ts index edefbcdb..a09b860a 100644 --- a/frontend/web/src/api/currency.ts +++ b/frontend/web/src/api/currency.ts @@ -6,7 +6,9 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; +// + + export interface GetSupportedCurrenciesRequest { } @@ -33,8 +35,10 @@ export class CurrencyClient { this.baseUrl = baseUrl; } + public async getSupportedCurrencies(request: GetSupportedCurrenciesRequest): Promise { - const response = await fetch(`${this.baseUrl}/currency/supported?${toQueryString(request)}`, { + const path = `/currency/supported?@json=${encodeURIComponent(JSON.stringify(request))}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -47,8 +51,10 @@ export class CurrencyClient { return response.json(); } + public async convert(request: ConvertRequest): Promise { - const response = await fetch(`${this.baseUrl}/currency/convert`, { + const path = `/currency/convert`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/payment.ts b/frontend/web/src/api/payment.ts index 11c762be..6a2c6960 100644 --- a/frontend/web/src/api/payment.ts +++ b/frontend/web/src/api/payment.ts @@ -6,12 +6,9 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; -export interface Money { - currencyCode: string; - units: number; - nanos: number; -} +// +import * as currency from "./currency" + export interface CreditCardInfo { number: string; @@ -21,7 +18,7 @@ export interface CreditCardInfo { } export interface ChargeRequest { - amount: Money; + amount: currency.Money; creditCard: CreditCardInfo; } @@ -37,8 +34,10 @@ export class PaymentClient { this.baseUrl = baseUrl; } + public async charge(request: ChargeRequest): Promise { - const response = await fetch(`${this.baseUrl}/payment/charge`, { + const path = `/payment/charge`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/productcatalog.ts b/frontend/web/src/api/productcatalog.ts index 4940e040..1b27f454 100644 --- a/frontend/web/src/api/productcatalog.ts +++ b/frontend/web/src/api/productcatalog.ts @@ -6,14 +6,11 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; -export interface ListRequest { -} +// +import * as currency from "./currency" + -export interface Money { - currencyCode: string; - units: number; - nanos: number; +export interface ListRequest { } export interface Product { @@ -21,7 +18,7 @@ export interface Product { name: string; description: string; picture: string; - priceUSD: Money; + priceUSD: currency.Money; categories: string[]; } @@ -49,8 +46,10 @@ export class ProductcatalogClient { this.baseUrl = baseUrl; } + public async list(request: ListRequest): Promise { - const response = await fetch(`${this.baseUrl}/productcatalog?${toQueryString(request)}`, { + const path = `/productcatalog?@json=${encodeURIComponent(JSON.stringify(request))}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -63,8 +62,10 @@ export class ProductcatalogClient { return response.json(); } + public async get(request: GetRequest): Promise { - const response = await fetch(`${this.baseUrl}/productcatalog/id?${toQueryString(request)}`, { + const path = `/productcatalog/${request.id}?@json=${encodeURIComponent(JSON.stringify(request))}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/recommendation.ts b/frontend/web/src/api/recommendation.ts index f36c8a7c..7010afbe 100644 --- a/frontend/web/src/api/recommendation.ts +++ b/frontend/web/src/api/recommendation.ts @@ -6,7 +6,10 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; +// +import * as productcatalog from "./productcatalog" + + export interface ListRequest { userID: string; userProductIDs: string[]; @@ -24,8 +27,10 @@ export class RecommendationClient { this.baseUrl = baseUrl; } + public async list(request: ListRequest): Promise { - const response = await fetch(`${this.baseUrl}/recommendation?${toQueryString(request)}`, { + const path = `/recommendation?@json=${encodeURIComponent(JSON.stringify(request))}`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/src/api/shipping.ts b/frontend/web/src/api/shipping.ts index fde1a288..01ee55a2 100644 --- a/frontend/web/src/api/shipping.ts +++ b/frontend/web/src/api/shipping.ts @@ -6,7 +6,11 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; +// +import * as currency from "./currency" +import * as cart from "./cart" + + export interface Address { streetAddress: string; city: string; @@ -15,20 +19,9 @@ export interface Address { zipCode: number; } -export interface Item { - productID: string; - quantity: number; -} - export interface ShippingRequest { address: Address; - items: Item[]; -} - -export interface Money { - currencyCode: string; - units: number; - nanos: number; + items: cart.Item[]; } export interface ShipOrderResponse { @@ -43,8 +36,10 @@ export class ShippingClient { this.baseUrl = baseUrl; } - public async getQuote(request: ShippingRequest): Promise { - const response = await fetch(`${this.baseUrl}/shipping/quote`, { + + public async getQuote(request: ShippingRequest): Promise { + const path = `/shipping/quote`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -57,8 +52,10 @@ export class ShippingClient { return response.json(); } + public async shipOrder(request: ShippingRequest): Promise { - const response = await fetch(`${this.baseUrl}/shipping/ship`, { + const path = `/shipping/ship`; + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/frontend/web/templates/template.js b/frontend/web/templates/template.js new file mode 100644 index 00000000..92ac7302 --- /dev/null +++ b/frontend/web/templates/template.js @@ -0,0 +1,82 @@ +// Return the corresponding Typescript type for an FTL type. +function tsType(t) { + const type = typeName(t); + switch (type) { + case "String": + return "string"; + + case "Int": + return "number"; + + case "Bool": + return "bool"; + + case "Float": + return "double"; + + case "Time": + return "DateTime"; + + case "Map": + return `Map<${tsType(t.key)}, ${tsType(t.value)}>`; + + case "Array": + return `${tsType(t.element)}[]`; + + case "VerbRef": + case "DataRef": + if (context.name === t.module) { + return t.name; + } + return `${t.module}.${t.name}`; + + case "Optional": + return tsType(t.type) + "?"; + + default: + throw new Error(`Unspported FTL type: ${typeName(t)}`); + } + } + + function deserialize(t) { + switch (typeName(t)) { + case "Array": + return `v.map((v) => ${deserialize(t.element)}).cast<${tsType(t.element)}>().toList()`; + + case "Map": + return `v.map((k, v) => MapEntry(k, ${deserialize(t.value)})).cast<${tsType(t.key)}, ${tsType(t.value)}>()`; + + case "DataRef": + return `${tsType(t)}.fromMap(v)`; + + default: + return "v"; + } + } + + function serialize(t) { + switch (typeName(t)) { + case "Array": + return `v.map((v) => ${deserialize(t.element)}).cast<${tsType(t.element)}>().toList()`; + + case "Map": + return `v.map((k, v) => MapEntry(k, ${deserialize(t.value)})).cast<${tsType(t.key)}, ${tsType(t.value)}>()`; + + case "DataRef": + return "v.toMap()"; + + default: + return "v"; + } + } + + function url(verb) { + let path = verb.metadata[0].path; + const method = verb.metadata[0].method; + + path = path.replace(/{(.*?)}/g, (match, fieldName) => { + return '$' + `{request.${fieldName}}`; + }); + + return method !== 'GET' ? path : path + '?@json=${encodeURIComponent(JSON.stringify(request))}'; + } diff --git a/frontend/web/templates/{{ .Name | lower }}.ts b/frontend/web/templates/{{ .Name | lower }}.ts index 2be44e0f..71c70a24 100644 --- a/frontend/web/templates/{{ .Name | lower }}.ts +++ b/frontend/web/templates/{{ .Name | lower }}.ts @@ -6,21 +6,16 @@ // /_/ /_/ /____/ // // @ts-ignore ignore unused error in generated code -import { toQueryString } from "./utils"; -{{- define "tstype" -}} -{{- $type := (. | typename ) -}} -{{- if eq $type "Array" }}{{ template "tstype" .Element }}[] -{{- else if eq $type "Map" }}Map<{{ template "tstype" .Key }}, {{ template "tstype" .Value }}> -{{- else if eq $type "DataRef" }}{{ . }} -{{- else if eq $type "Int" }}number -{{- else if eq $type "String" }}string -{{- else -}}{{ . | typename }}{{- end -}} -{{- end -}} +// + +{{- range .Imports }} +import * as {{. | lower }} from "./{{. | lower}}" +{{- end}} {{ range .Data }} export interface {{ .Name | camel }} { {{- range .Fields }} - {{ .Name }}: {{ template "tstype" .Type }}; + {{ .Name }}: {{ .Type | tsType }}; {{- end }} } {{ end}} @@ -31,22 +26,22 @@ export class {{ .Name | camel }}Client { constructor(baseUrl: string) { this.baseUrl = baseUrl; } - {{- range .Verbs }} {{- $verb := . -}} {{- range .Metadata }} -{{ if eq "MetadataIngress" (. | typename) }} - public async {{ $verb.Name | lowerCamel }}(request: {{ $verb.Request }}): Promise<{{ $verb.Response }}> { +{{ if eq "MetadataIngress" (. | typeName) }} + + public async {{ $verb.Name | lowerCamel }}(request: {{ $verb.Request | tsType }}): Promise<{{ $verb.Response | tsType }}> { + const path = `{{ $verb | url }}`; {{ if eq .Method "GET" -}} - - const response = await fetch(`${this.baseUrl}{{ .Path }}?${toQueryString(request)}`, { + const response = await fetch(`${this.baseUrl}${path}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); {{ else if eq .Method "POST" -}} - const response = await fetch(`${this.baseUrl}{{ .Path }}`, { + const response = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json',