diff --git a/stdlib/Makefile b/stdlib/Makefile index ffe1b8fbcd8..bc653692c98 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -44,6 +44,7 @@ docMd: \ $(DOCDIR)/README.md \ $(DOCDIR)/prelude.md \ $(DOCDIR)/option.md \ + $(DOCDIR)/result.md \ $(DOCDIR)/hash.md \ $(DOCDIR)/list.md \ $(DOCDIR)/assocList.md \ @@ -66,6 +67,7 @@ docHtml: \ $(DOCDIR)/README.html \ $(DOCDIR)/prelude.html \ $(DOCDIR)/option.html \ + $(DOCDIR)/result.html \ $(DOCDIR)/hash.html \ $(DOCDIR)/list.html \ $(DOCDIR)/assocList.html \ @@ -133,7 +135,7 @@ $(OUTDIR)/SetDbTest.out: prelude.as hash.as list.as assocList.as trie.as set.as $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ PRODUCE_EXCHANGE_SRC=\ - prelude.as option.as hash.as list.as assocList.as trie.as docTable.as \ + prelude.as option.as hash.as list.as assocList.as trie.as docTable.as result.as \ examples/produce-exchange/serverTypes.as \ examples/produce-exchange/serverModelTypes.as \ examples/produce-exchange/serverModel.as \ @@ -141,7 +143,7 @@ PRODUCE_EXCHANGE_SRC=\ $(OUTDIR)/ProduceExchange.out: $(PRODUCE_EXCHANGE_SRC) \ examples/produce-exchange/test/simpleSetupAndQuery.as | $(OUTDIR) - $(ASC) -r $(filter-out $(OUTDIR), $^) > $@ + $(ASC) -dt -r $(filter-out $(OUTDIR), $^) > $@ $(OUTDIR)/ProduceExchange.wasm: $(PRODUCE_EXCHANGE_SRC) | $(OUTDIR) $(ASC) -c --dfinity -o $@ $(filter-out $(OUTDIR), $^) diff --git a/stdlib/examples/produce-exchange/serverActor.as b/stdlib/examples/produce-exchange/serverActor.as index ee3a519046e..a347f8bc5fa 100644 --- a/stdlib/examples/produce-exchange/serverActor.as +++ b/stdlib/examples/produce-exchange/serverActor.as @@ -64,16 +64,19 @@ actor server = { isProducer: Bool, isRetailer: Bool, isTransporter: Bool - ) : async ?UserId { - getModel().addUser( - public_key, - user_name, - description, - region, - isDeveloper, - isProducer, - isRetailer, - isTransporter + ) : async Result { + optionResult( + getModel().addUser( + public_key, + user_name, + description, + region, + isDeveloper, + isProducer, + isRetailer, + isTransporter + ), + {#idErr} ) }; @@ -91,9 +94,11 @@ actor server = { --------------------------- Get the information associated with a user, based on its id. */ - getUserInfo(id:UserId) : async ?UserInfo { - getModel() - .userTable.getInfo(id) + getUserInfo(id:UserId) : async Result { + optionResult( + getModel().userTable.getInfo(id), + {#idErr} + ) }; /** @@ -115,26 +120,28 @@ actor server = { capacity_ : Weight, isFridge_ : Bool, isFreezer_ : Bool, - ) : async ?TruckTypeId { - getModel() - .truckTypeTable.addInfoGetId( + ) : async Result { + optionUnwrapResult( + getModel() + .truckTypeTable.addInfoGetId( func (id_:TruckTypeId) : TruckTypeInfo = - // xxx: AS should have more concise syntax for this pattern, below: - // two problems I see, that are separate: - // 1: repeating the label/variable name, which is the same in each case, twice. - // 2: explicit type annotations, because of "type error, cannot infer type of forward variable ..." - // but two other sources exist for each type: the type of `insert` is known, and hence, this record has a known type, - // and, the type of each of these `variables` is known, as well. - - shared { - id=id_ :TruckTypeId; - short_name=short_name_:Text; - description=description_:Text; - capacity=capacity_:Weight; - isFridge=isFridge_:Bool; - isFreezer=isFreezer_:Bool; - }) + // xxx: AS should have more concise syntax for this pattern, below: + // two problems I see, that are separate: + // 1: repeating the label/variable name, which is the same in each case, twice. + // 2: explicit type annotations, because of "type error, cannot infer type of forward variable ..." + // but two other sources exist for each type: the type of `insert` is known, and hence, this record has a known type, + // and, the type of each of these `variables` is known, as well. + + shared { + id=id_ :TruckTypeId; + short_name=short_name_:Text; + description=description_:Text; + capacity=capacity_:Weight; + isFridge=isFridge_:Bool; + isFreezer=isFreezer_:Bool; + }) + ) }; /** @@ -144,8 +151,11 @@ actor server = { registrarRemTruckType( id: TruckTypeId - ) : async ?() { - getModel().truckTypeTable.remGetUnit(id) + ) : async Result<(),ServerErr> { + optionResult<(),IdErr>( + getModel().truckTypeTable.remGetUnit(id), + {#idErr} + ) }; /** @@ -155,8 +165,11 @@ actor server = { getTruckTypeInfo( id: TruckTypeId - ) : async ?TruckTypeInfo { - getModel().truckTypeTable.getInfo(id) + ) : async Result { + optionResult( + getModel().truckTypeTable.getInfo(id), + {#idErr} + ) }; /** @@ -186,14 +199,16 @@ actor server = { registrarAddRegion( short_name_: Text, description_: Text, - ) : async ?RegionId { - getModel().regionTable.addInfoGetId( - func (id_:RegionId) : RegionInfo = - shared { - id = id_:RegionId; - short_name=short_name_:Text; - description=description_:Text - }) + ) : async Result { + optionUnwrapResult( + getModel().regionTable.addInfoGetId( + func (id_:RegionId) : RegionInfo = + shared { + id = id_:RegionId; + short_name=short_name_:Text; + description=description_:Text + }) + ) }; /** @@ -205,8 +220,11 @@ actor server = { registrarRemRegion( id: RegionId - ) : async ?() { - getModel().regionTable.remGetUnit(id) + ) : async Result<(),IdErr> { + optionResult<(),IdErr>( + getModel().regionTable.remGetUnit(id), + {#idErr}, + ) }; /** @@ -219,8 +237,11 @@ actor server = { getRegionInfo( id: RegionId - ) : async ?RegionInfo { - getModel().regionTable.getInfo(id) + ) : async Result { + optionResult( + getModel().regionTable.getInfo(id), + {#idErr} + ) }; @@ -255,15 +276,17 @@ actor server = { short_name_: Text, description_: Text, grade_: Grade, - ) : async ?ProduceId { - getModel().produceTable.addInfoGetId( - func (id_:ProduceId) : ProduceInfo = - shared { - id = id_:ProduceId; - short_name=short_name_:Text; - description=description_:Text; - grade=grade_:Grade - }) + ) : async Result { + optionUnwrapResult( + getModel().produceTable.addInfoGetId( + func (id_:ProduceId) : ProduceInfo = + shared { + id = id_:ProduceId; + short_name=short_name_:Text; + description=description_:Text; + grade=grade_:Grade + }) + ) }; /** @@ -275,8 +298,11 @@ actor server = { registrarRemProduce( id: ProduceId - ) : async ?() { - getModel().produceTable.remGetUnit(id) + ) : async Result<(),IdErr> { + optionResult<(),IdErr>( + getModel().produceTable.remGetUnit(id), + {#idErr}, + ) }; @@ -287,8 +313,11 @@ actor server = { getProduceInfo( id: ProduceId - ) : async ?ProduceInfo { - getModel().produceTable.getInfo(id) + ) : async Result { + optionResult( + getModel().produceTable.getInfo(id), + {#idErr} + ) }; /** @@ -318,18 +347,21 @@ actor server = { short_name_: Text, description_: Text, region_: RegionId, - ) : async ?ProducerId { - getModel().producerTable.addInfoGetId( - func(id_:ProducerId):ProducerInfo { - shared { - id=id_:ProducerId; - short_name=short_name_:Text; - description=description_:Text; - region=region_:RegionId; - inventory=[]; - reserved=[]; - } - }) + ) : async Result { + optionResult( + getModel().producerTable.addInfoGetId( + func(id_:ProducerId):ProducerInfo { + shared { + id=id_:ProducerId; + short_name=short_name_:Text; + description=description_:Text; + region=region_:RegionId; + inventory=[]; + reserved=[]; + } + }), + {#idErr} + ) }; /** @@ -341,8 +373,11 @@ actor server = { registrarRemProducer( id: ProducerId - ) : async ?() { - getModel().producerTable.remGetUnit(id) + ) : async Result<(),IdErr> { + optionResult<(),IdErr>( + getModel().producerTable.remGetUnit(id), + {#idErr} + ) }; @@ -353,8 +388,11 @@ actor server = { getProducerInfo( id: ProducerId - ) : async ?ProducerInfo { - getModel().producerTable.getInfo(id) + ) : async Result { + optionResult( + getModel().producerTable.getInfo(id), + {#idErr} + ) }; /** @@ -385,16 +423,19 @@ actor server = { short_name_: Text, description_: Text, region_: RegionId, - ) : async ?RetailerId { - getModel().retailerTable.addInfoGetId( - func(id_:RetailerId):RetailerInfo { - shared { - id=id_:RetailerId; - short_name=short_name_:Text; - description=description_:Text; - region=region_:RegionId - } - }) + ) : async Result { + optionResult( + getModel().retailerTable.addInfoGetId( + func(id_:RetailerId):RetailerInfo { + shared { + id=id_:RetailerId; + short_name=short_name_:Text; + description=description_:Text; + region=region_:RegionId + } + }), + {#idErr} + ) }; /** @@ -406,8 +447,11 @@ actor server = { registrarRemRetailer( id: RetailerId - ) : async ?() { - getModel().retailerTable.remGetUnit(id) + ) : async Result<(),IdErr> { + optionResult<(),IdErr>( + getModel().retailerTable.remGetUnit(id), + {#idErr} + ) }; /** @@ -417,8 +461,11 @@ actor server = { getRetailerInfo( id: RetailerId - ) : async ?RetailerInfo { - getModel().retailerTable.getInfo(id) + ) : async Result { + optionResult( + getModel().retailerTable.getInfo(id), + {#idErr} + ) }; /** @@ -445,18 +492,19 @@ actor server = { registrarAddTransporter( short_name_: Text, description_: Text, - ) : async ?TransporterId { - getModel().transporterTable.addInfoGetId( - func(id_:TransporterId):TransporterInfo { - shared { - id=id_:TransporterId; - short_name=short_name_:Text; - description=description_:Text; - routes=[]; - reserved=[]; - } - }) - + ) : async Result { + optionUnwrapResult( + getModel().transporterTable.addInfoGetId( + func(id_:TransporterId):TransporterInfo { + shared { + id=id_:TransporterId; + short_name=short_name_:Text; + description=description_:Text; + routes=[]; + reserved=[]; + } + }) + ) }; /** @@ -467,8 +515,11 @@ actor server = { registrarRemTransporter( id: TransporterId - ) : async ?() { - getModel().transporterTable.remGetUnit(id) + ) : async Result<(),IdErr> { + optionResult<(),IdErr>( + getModel().transporterTable.remGetUnit(id), + {#idErr} + ) }; /** @@ -478,8 +529,11 @@ actor server = { getTransporterInfo( id: TransporterId - ) : async ?TransporterInfo { - getModel().transporterTable.getInfo(id) + ) : async Result { + optionResult( + getModel().transporterTable.getInfo(id), + {#idErr} + ) }; @@ -514,7 +568,7 @@ actor server = { begin:Date, end: Date, comments: Text, - ) : async ?InventoryId { + ) : async Result { getModel(). producerAddInventory( public_key, null, id, prod, quant, weight, ppu, begin, end, comments) @@ -536,7 +590,7 @@ actor server = { begin:Date, end: Date, comments: Text, - ) : async ?() { + ) : async Result<(),ServerErr> { getModel(). producerUpdateInventory( public_key, iid, id, prod, quant, weight, ppu, begin, end, comments) @@ -546,7 +600,7 @@ actor server = { `producerRemInventory` --------------------------- */ - producerRemInventory(public_key: PublicKey, id:InventoryId) : async ?() { + producerRemInventory(public_key: PublicKey, id:InventoryId) : async Result<(),ServerErr> { getModel() .producerRemInventory(public_key, id) }; @@ -555,18 +609,24 @@ actor server = { `producerAllInventoryInfo` --------------------------- */ - producerAllInventoryInfo(public_key: PublicKey, id:UserId) : async ?[InventoryInfo] { - getModel() - .producerAllInventoryInfo(public_key, id) + producerAllInventoryInfo(public_key: PublicKey, id:UserId) : async Result<[InventoryInfo],IdErr> { + optionResult<[InventoryInfo],IdErr>( + getModel() + .producerAllInventoryInfo(public_key, id), + {#idErr} + ) }; /** `producerReservations` --------------------------- */ - producerReservations(public_key: PublicKey, id:UserId) : async ?[ReservedInventoryInfo] { - getModel() - .producerReservations(public_key, id) + producerReservations(public_key: PublicKey, id:UserId) : async Result<[ReservedInventoryInfo],IdErr> { + optionResult<[ReservedInventoryInfo],IdErr>( + getModel() + .producerReservations(public_key, id), + {#idErr} + ) }; @@ -582,9 +642,12 @@ actor server = { --------------------------- The last sales price for produce within a given geographic area; null region id means "all areas." */ - produceMarketInfo(public_key: PublicKey, id:ProduceId, reg:?RegionId) : async ?[ProduceMarketInfo] { - getModel() - .produceMarketInfo(public_key, id, reg) + produceMarketInfo(public_key: PublicKey, id:ProduceId, reg:?RegionId) : async Result<[ProduceMarketInfo],IdErr> { + optionResult<[ProduceMarketInfo],IdErr>( + getModel() + .produceMarketInfo(public_key, id, reg), + {#idErr} + ) }; @@ -603,9 +666,12 @@ actor server = { --------------------------- Get the information associated with inventory, based on its id. */ - getInventoryInfo(id:InventoryId) : async ?InventoryInfo { - getModel() - .inventoryTable.getInfo(id) + getInventoryInfo(id:InventoryId) : async Result { + optionResult( + getModel() + .inventoryTable.getInfo(id), + {#idErr} + ) }; @@ -627,7 +693,7 @@ actor server = { end: Date, cost: Price, ttid: TruckTypeId - ) : async ?RouteId { + ) : async Result { getModel().transporterAddRoute(public_key, null, id, rstart, rend, start, end, cost, ttid) }; @@ -645,7 +711,7 @@ actor server = { end: Date, cost: Price, ttid: TruckTypeId - ) : async ?() { + ) : async Result<(),ServerErr> { getModel().transporterUpdateRoute(public_key, route, id, rstart, rend, start, end, cost, ttid) }; @@ -653,27 +719,33 @@ actor server = { `transporterRemRoute` --------------------------- */ - transporterRemRoute(public_key: PublicKey, id:RouteId) : async ?() { - getModel() - .transporterRemRoute(public_key, id) + transporterRemRoute(public_key: PublicKey, id:RouteId) : async Result<(),ServerErr> { + getModel() + .transporterRemRoute(public_key, id) }; /** `transporterAllRouteInfo` --------------------------- */ - transporterAllRouteInfo(public_key: PublicKey, id:UserId) : async ?[RouteInfo] { - getModel() - .transporterAllRouteInfo(public_key, id) + transporterAllRouteInfo(public_key: PublicKey, id:UserId) : async Result<[RouteInfo],IdErr> { + optionResult<[RouteInfo],IdErr>( + getModel() + .transporterAllRouteInfo(public_key, id), + {#idErr} + ) }; /** `transporterAllReservationInfo` --------------------------- */ - transporterAllReservationInfo(public_key: PublicKey, id:UserId) : async ?[ReservedRouteInfo] { - getModel() - .transporterAllReservationInfo(public_key, id) + transporterAllReservationInfo(public_key: PublicKey, id:UserId) : async Result<[ReservedRouteInfo],IdErr> { + optionResult<[ReservedRouteInfo],IdErr>( + getModel() + .transporterAllReservationInfo(public_key, id), + {#idErr} + ) }; /** @@ -696,8 +768,11 @@ actor server = { getRouteInfo( id: RouteId - ) : async ?RouteInfo { - getModel().routeTable.getInfo(id) + ) : async Result { + optionResult( + getModel().routeTable.getInfo(id), + {#idErr} + ) }; /** @@ -707,12 +782,16 @@ actor server = { `retailerQueryAll` --------------------------- - TODO-Cursors (see above). - */ - retailerQueryAll(public_key: PublicKey, id:UserId) : async ?QueryAllResults { - getModel(). - retailerQueryAll(public_key, id) + retailerQueryAll(public_key: PublicKey, id:UserId, + queryProduce:?ProduceId, + queryDate:?Date + ) : async Result { + optionResult( + getModel(). + retailerQueryAll(public_key, id, queryProduce, queryDate), + {#idErr} + ) }; /** @@ -729,10 +808,13 @@ actor server = { id:UserId, begin:Date, end:Date - ) : async ?[InventoryInfo] + ) : async Result<[InventoryInfo],IdErr> { - getModel(). - retailerQueryDates(public_key, id, begin, end) + optionResult<[InventoryInfo],IdErr>( + getModel(). + retailerQueryDates(public_key, id, begin, end), + {#idErr} + ) }; /** @@ -743,7 +825,7 @@ actor server = { public_key: PublicKey, id:UserId, inventory:InventoryId, - route:RouteId) : async ?(ReservedInventoryId, ReservedRouteId) + route:RouteId) : async Result<(ReservedInventoryId, ReservedRouteId),ServerErr> { getModel(). retailerReserve(public_key, id, inventory, route) @@ -753,15 +835,17 @@ actor server = { `retailerReservations` --------------------------- - TODO-Cursors (see above). - - */ + */ retailerReservations(public_key: PublicKey, id:UserId) : - async ?[(ReservedInventoryInfo, - ReservedRouteInfo)] + async Result<[(ReservedInventoryInfo, + ReservedRouteInfo)],ServerErr> { - getModel(). - retailerAllReservationInfo(public_key, id) + optionResult<[(ReservedInventoryInfo, + ReservedRouteInfo)],ServerErr>( + getModel(). + retailerAllReservationInfo(public_key, id), + #idErr + ) }; diff --git a/stdlib/examples/produce-exchange/serverModel.as b/stdlib/examples/produce-exchange/serverModel.as index d368524d1e2..d507bec9aae 100644 --- a/stdlib/examples/produce-exchange/serverModel.as +++ b/stdlib/examples/produce-exchange/serverModel.as @@ -766,8 +766,6 @@ than the MVP goals, however. case (?doc) { doc }; }; - assert(isValidUser(public_key, doc.short_name)); - ?Map.toArray( doc.inventory, func (_:InventoryId,doc:InventoryDoc):[InventoryInfo] = @@ -791,7 +789,7 @@ than the MVP goals, however. start_date_: Date, end_date_ : Date, comments_ : Text, - ) : ?InventoryId + ) : Result { /** The model adds inventory and maintains secondary indicies as follows: */ @@ -801,10 +799,12 @@ than the MVP goals, however. let (producer_, produce_) = { switch (oproducer, oproduce) { case (?producer, ?produce) (producer, produce); - case _ { return null }; + case _ { return #err(#idErr) }; }}; - assert(isValidUser(public_key, producer_.short_name)); + if (not isValidUser(public_key, producer_.short_name)) { + return (#err(#publicKeyErr)) + }; /**- Create the inventory item document: */ let (_, item) = { @@ -857,7 +857,7 @@ than the MVP goals, however. updatedInventory, ); - ?item.id + return #ok(item.id) }; /** @@ -876,7 +876,7 @@ than the MVP goals, however. start_date_: Date, end_date_ : Date, comments_ : Text, - ) : ?() + ) : Result<(),ServerErr> { /**- Validate these ids; fail here if anything is invalid: */ let oproducer: ?ProducerDoc = producerFromUserId(id_); @@ -891,19 +891,21 @@ than the MVP goals, however. if ( inventory.producer == producer.id ) { (inventory, producer, produce) } else { - return null + return (#err(#idErr)) } }; - case _ { return null }; + case _ { return (#err(#idErr)) }; }}; - assert(isValidUser(public_key, producer_.short_name)); + if (not isValidUser(public_key, producer_.short_name)) { + return (#err(#publicKeyErr)) + }; /**- remove the inventory item; given the validation above, this cannot fail. */ - assertSome<()>( producerRemInventory(public_key, iid_) ); + assertOk( producerRemInventory(public_key, iid_) ); /**- add the (updated) inventory item; given the validation above, this cannot fail. */ - assertSome( + assertOk( producerAddInventory( public_key, ?iid_, id_, produce_id, @@ -911,7 +913,7 @@ than the MVP goals, however. ); /**- Success! */ - ?() + #ok }; /** @@ -921,12 +923,12 @@ than the MVP goals, however. Remove the given inventory item from the exchange. */ - producerRemInventory(public_key: PublicKey, id:InventoryId) : ?() { + producerRemInventory(public_key: PublicKey, id:InventoryId) : Result<(),ServerErr> { /**- validate the `id` */ /// xxx macro for this pattern? let doc = switch (inventoryTable.getDoc(id)) { - case null { return null }; + case null { return #err(#idErr) }; case (?doc) { doc }; }; @@ -934,7 +936,9 @@ than the MVP goals, however. let producer = unwrap(producerTable.getDoc(doc.producer)); /// xxx: access control: Check that the current user is the owner of this inventory - assert(isValidUser(public_key, producer.short_name)); + if (not isValidUser(public_key, producer.short_name)) { + return (#err(#publicKeyErr)) + }; /**- remove document from `inventoryTable` */ assertSome( @@ -973,7 +977,7 @@ than the MVP goals, however. t }; - ?() + #ok }; /** @@ -987,8 +991,6 @@ than the MVP goals, however. case (?doc) { doc }; }; - assert(isValidUser(public_key, doc.short_name)); - ?Map.toArray( @@ -1022,7 +1024,7 @@ than the MVP goals, however. end_date_: Date, cost_: Price, trucktype_id: TruckTypeId - ) : ?RouteId { + ) : Result { /** The model adds inventory and maintains secondary indicies as follows: */ /**- Validate these ids; fail fast if not defined: */ @@ -1033,11 +1035,13 @@ than the MVP goals, however. let (transporter, start_region_, end_region_, truck_type_) = { switch (otransporter, orstart, orend, otrucktype) { case (?x1, ?x2, ?x3, ?x4) (x1, x2, x3, x4); - case _ { return null }; + case _ { return #err(#idErr) }; }}; let transporterId = transporter.id; - assert(isValidUser(public_key, transporter.short_name)); + if (not isValidUser(public_key, transporter.short_name)) { + return (#err(#publicKeyErr)) + }; /**- Create the route item document: */ let route : RouteDoc = { @@ -1088,7 +1092,7 @@ than the MVP goals, however. route ); - ?route.id + #ok(route.id) }; /** @@ -1106,7 +1110,7 @@ than the MVP goals, however. end_date_ : Date, cost_ : Price, trucktype_id : TruckTypeId - ) : ?() { + ) : Result<(),ServerErr> { /** The model updates routes and maintains secondary indicies as follows: */ /**- Validate these ids; fail fast if not defined: */ @@ -1124,19 +1128,22 @@ than the MVP goals, however. if ( route.transporter == transporter.id ) { (route, transporter, x2, x3, x4); } else { - return null + return #err(#idErr) } }; - case _ { return null }; + case _ { return #err(#idErr) }; }}; - assert(isValidUser(public_key, transporter.short_name)); + /**- validate the user */ + if (not isValidUser(public_key, transporter.short_name)) { + return #err(#publicKeyErr) + } /**- remove the route; given the validation above, this cannot fail. */ - assertSome<()>( transporterRemRoute(public_key, rid_) ); + assertOk( transporterRemRoute(public_key, rid_) ); /**- add the (updated) route; given the validation above, this cannot fail. */ - assertSome( + assertOk( transporterAddRoute( public_key, ?rid_, id_, @@ -1150,7 +1157,7 @@ than the MVP goals, however. ); /**- Success! */ - ?() + #ok }; /** @@ -1158,23 +1165,23 @@ than the MVP goals, however. --------------------------- Remove the given route from the exchange. */ - transporterRemRoute(public_key: PublicKey, id:RouteId) : ?() { + transporterRemRoute(public_key: PublicKey, id:RouteId) : Result<(),ServerErr> { let doc = switch (routeTable.getDoc(id)) { - case null { return null }; + case null { return #err(#idErr) }; case (?doc) { doc }; }; let transporter = unwrap(transporterTable.getDoc(doc.transporter)); - assert(isValidUser(public_key, transporter.short_name)); + if (not isValidUser(public_key, transporter.short_name)) { + return #err(#publicKeyErr) + } assertSome( routeTable.rem( id ) ); - /// xxx: access control: Check that the current user is the owner of this route - let (updatedRoutes, _) = Trie.remove( transporter.routes, keyOf(id), idIsEq); @@ -1202,7 +1209,7 @@ than the MVP goals, however. t }; - ?() + #ok }; /** @@ -1215,8 +1222,6 @@ than the MVP goals, however. case (?doc) { doc }; }; - assert(isValidUser(public_key, doc.short_name)); - ?Map.toArray( @@ -1240,8 +1245,6 @@ than the MVP goals, however. case (?doc) { doc }; }; - assert(isValidUser(public_key, doc.short_name)); - ?Map.toArray( @@ -1309,7 +1312,33 @@ than the MVP goals, however. */ - isFeasibleReservation(retailer:RetailerDoc, item:InventoryDoc, route:RouteDoc) : Bool { + isFeasibleReservation( + retailer:RetailerDoc, + item:InventoryDoc, + route:RouteDoc, + queryProduce:?ProduceId, + queryDate:?Date) + : Bool + { + + switch queryProduce { + case null { }; + case (?qp) { + if (item.produce.id != qp) { + debugOff "nope: wrong produce kind\n"; + return false + }; + }; + }; + switch queryDate { + case null { }; + case (?qd) { + if (route.end_date > qd ) { + debugOff "nope: route arrives too late\n"; + return false + } + } + }; /** - window start: check that the route begins after the inventory window begins */ if (item.start_date > route.start_date) { debugOff "nope: item start after route start\n"; @@ -1348,7 +1377,13 @@ than the MVP goals, however. - [`Trie.prod`]($DOCURL/trie.md#prod): For the catesian product of routes and inventory. - [`Trie.mergeDisjoint2D`]($DOCURL/trie.md#mergeDisjoint2D): To flatten 2D mappings into 1D mappings. */ - retailerQueryAll(public_key: PublicKey, id:UserId) : ?QueryAllResults { + retailerQueryAll( + public_key: PublicKey, + id:UserId, + queryProduce:?ProduceId, + queryDate:?Date + ) : ?QueryAllResults + { retailerQueryCount += 1; /** - Find the retailer's document: */ @@ -1357,8 +1392,6 @@ than the MVP goals, however. case (null) { return null }; case (?x) { x }}; - assert(isValidUser(public_key, retailer.short_name)); - debug "- user_name: "; debug (retailer.short_name); debug ", public_key: "; @@ -1416,7 +1449,7 @@ than the MVP goals, however. { retailerQueryCost += 1; /** - Consider the constraints of the retailer-route-item combination: */ - if (isFeasibleReservation(retailer, item, route)) { + if (isFeasibleReservation(retailer, item, route, queryProduce, queryDate)) { ?( keyOfIdPair(route_id, item_id), (route, item) ) @@ -1466,8 +1499,6 @@ than the MVP goals, however. case (?doc) { doc }; }; - assert(isValidUser(public_key, doc.short_name)); - ?Map.toArray( @@ -1521,7 +1552,7 @@ than the MVP goals, however. public_key: PublicKey, id:UserId, inventory:InventoryId, - route:RouteId) : ?(ReservedRouteId, ReservedInventoryId) + route:RouteId) : Result<(ReservedRouteId, ReservedInventoryId), ServerErr> { nyi() }; diff --git a/stdlib/examples/produce-exchange/serverTypes.as b/stdlib/examples/produce-exchange/serverTypes.as index 740f9119e7c..013bba03763 100644 --- a/stdlib/examples/produce-exchange/serverTypes.as +++ b/stdlib/examples/produce-exchange/serverTypes.as @@ -78,6 +78,21 @@ type TransporterId = Nat; type RouteId = Nat; type ReservedRouteId = Nat; +/** + Errors + ----------- +*/ + +type IdErr = { + #idErr; +}; + +type ServerErr = { + #idErr; + #publicKeyErr; +}; + + /** Public info associated with Ids ===================================== diff --git a/stdlib/examples/produce-exchange/test/simpleSetupAndQuery.as b/stdlib/examples/produce-exchange/test/simpleSetupAndQuery.as index e92b6d2aa2d..c8be27550bf 100644 --- a/stdlib/examples/produce-exchange/test/simpleSetupAndQuery.as +++ b/stdlib/examples/produce-exchange/test/simpleSetupAndQuery.as @@ -52,11 +52,11 @@ actor class Test() = this { printEntityCount("Produce", (await s.getCounts()).produce_count); // register all users - let uida = await s.registrarAddUser(pka, "usera", "", unwraprega, true, true, true, true); - let uidb = await s.registrarAddUser(pkb, "userb", "", unwrapregb, true, true, true, true); - let uidc = await s.registrarAddUser(pkc, "userc", "", unwrapregc, true, true, true, true); - let uidd = await s.registrarAddUser(pkd, "userd", "", unwrapregd, true, true, true, true); - let uide = await s.registrarAddUser(pke, "usere", "", unwraprege, true, true, true, true); + let uida = await s.registrarAddUser(pka, "usera", "", assertUnwrapAnyrega, true, true, true, true); + let uidb = await s.registrarAddUser(pkb, "userb", "", assertUnwrapAnyregb, true, true, true, true); + let uidc = await s.registrarAddUser(pkc, "userc", "", assertUnwrapAnyregc, true, true, true, true); + let uidd = await s.registrarAddUser(pkd, "userd", "", assertUnwrapAnyregd, true, true, true, true); + let uide = await s.registrarAddUser(pke, "usere", "", assertUnwrapAnyrege, true, true, true, true); printEntityCount("Producer", (await s.getCounts()).producer_count); printEntityCount("Transporter", (await s.getCounts()).transporter_count); @@ -65,48 +65,48 @@ actor class Test() = this { // populate with inventory let praia = await s.producerAddInventory( pka, - unwrap(uida), - unwrap(pea), 100, 100, 10, 0, 110, "" + assertUnwrapAny(uida), + assertUnwrapAny(pea), 100, 100, 10, 0, 110, "" ); let praib = await s.producerAddInventory( pka, - unwrap(uida), - unwrap(peb), 200, 200, 10, 1, 111, "" + assertUnwrapAny(uida), + assertUnwrapAny(peb), 200, 200, 10, 1, 111, "" ); let praic = await s.producerAddInventory( pka, - unwrap(uida), - unwrap(pec), 300, 300, 10, 2, 112, "" + assertUnwrapAny(uida), + assertUnwrapAny(pec), 300, 300, 10, 2, 112, "" ); let prbia = await s.producerAddInventory( pkb, - unwrap(uidb), - unwrap(peb), 200, 200, 10, 4, 117, "" + assertUnwrapAny(uidb), + assertUnwrapAny(peb), 200, 200, 10, 4, 117, "" ); let prbib = await s.producerAddInventory( pkb, - unwrap(uidb), - unwrap(peb), 1500, 1600, 9, 2, 115, "" + assertUnwrapAny(uidb), + assertUnwrapAny(peb), 1500, 1600, 9, 2, 115, "" ); let prbic = await s.producerAddInventory( pkb, - unwrap(uidb), - unwrap(pec), 300, 300, 10, 2, 112, "" + assertUnwrapAny(uidb), + assertUnwrapAny(pec), 300, 300, 10, 2, 112, "" ); let prcia = await s.producerAddInventory( pkb, - unwrap(uidb), - unwrap(peb), 200, 200, 9, 4, 711, "" + assertUnwrapAny(uidb), + assertUnwrapAny(peb), 200, 200, 9, 4, 711, "" ); let prdib = await s.producerAddInventory( pkb, - unwrap(uidb), - unwrap(peb), 1500, 1500, 7, 2, 115, "" + assertUnwrapAny(uidb), + assertUnwrapAny(peb), 1500, 1500, 7, 2, 115, "" ); let prdic = await s.producerAddInventory( pkb, - unwrap(uidb), - unwrap(pec), 300, 300, 6, 2, 112, "" + assertUnwrapAny(uidb), + assertUnwrapAny(pec), 300, 300, 6, 2, 112, "" ); printEntityCount("Inventory@time1", (await s.getCounts()).inventory_count); @@ -115,17 +115,15 @@ actor class Test() = this { /**- remove some of the inventory items added above */ - let x = await s.producerRemInventory(pkb, unwrap(prdib)); - assertSome<()>(x); + assertOk(await s.producerRemInventory(pkb, assertUnwrapAny(prdib))); // a double-remove should return null - assertNull<()>(await s.producerRemInventory(pkb, unwrap(prdib))); + //assertErr(await s.producerRemInventory(pkb, assertUnwrapAny(prdib))); - let y = await s.producerRemInventory(pka, unwrap(praib)); - assertSome<()>(y); + assertOk(await s.producerRemInventory(pka, assertUnwrapAny(praib))); // a double-remove should return null - assertNull<()>(await s.producerRemInventory(pka, unwrap(praib))); + //assertErr(await s.producerRemInventory(pka, assertUnwrapAny(praib))); printEntityCount("Inventory@time2", (await s.getCounts()).inventory_count); @@ -133,29 +131,29 @@ actor class Test() = this { /**- update some of the (remaining) inventory items added above */ - let praic2 = await s.producerUpdateInventory( - pka, - unwrap(praic), - unwrap(uida), - unwrap(pec), 666, 300, 10, 2, 112, "" - ); - assertSome<()>(praic2); - - let prbia2 = await s.producerUpdateInventory( - pkb, - unwrap(prbia), - unwrap(uidb), - unwrap(peb), 200, 666, 10, 4, 117, "" - ); - assertSome<()>(prbia2); - - let prbib2 = await s.producerUpdateInventory( - pkb, - unwrap(prbib), - unwrap(uidb), - unwrap(peb), 666, 1600, 9, 2, 115, "" - ); - assertSome<()>(prbib2); + assertOk( + await s.producerUpdateInventory( + pka, + assertUnwrapAny(praic), + assertUnwrapAny(uida), + assertUnwrapAny(pec), 666, 300, 10, 2, 112, "" + )); + + assertOk( + await s.producerUpdateInventory( + pkb, + assertUnwrapAny(prbia), + assertUnwrapAny(uidb), + assertUnwrapAny(peb), 200, 666, 10, 4, 117, "" + )); + + assertOk( + await s.producerUpdateInventory( + pkb, + assertUnwrapAny(prbib), + assertUnwrapAny(uidb), + assertUnwrapAny(peb), 666, 1600, 9, 2, 115, "" + )); printEntityCount("Inventory@time3", (await s.getCounts()).inventory_count); @@ -165,103 +163,103 @@ actor class Test() = this { let rta_a_c_tta = await s.transporterAddRoute( pka, - unwrap(uida), - unwrap(rega), - unwrap(regc), + assertUnwrapAny(uida), + assertUnwrapAny(rega), + assertUnwrapAny(regc), 0, 20, 100, - unwrap(tta) + assertUnwrapAny(tta) ); let rta_b_c_ttb = await s.transporterAddRoute( pka, - unwrap(uida), - unwrap(regb), - unwrap(regc), + assertUnwrapAny(uida), + assertUnwrapAny(regb), + assertUnwrapAny(regc), 0, 20, 100, - unwrap(ttb) + assertUnwrapAny(ttb) ); let rta_a_c_ttc = await s.transporterAddRoute( pka, - unwrap(uida), - unwrap(rega), - unwrap(rege), + assertUnwrapAny(uida), + assertUnwrapAny(rega), + assertUnwrapAny(rege), 0, 20, 100, - unwrap(ttc) + assertUnwrapAny(ttc) ); let rtb_a_c_tta = await s.transporterAddRoute( pkb, - unwrap(uidb), - unwrap(regc), - unwrap(rege), + assertUnwrapAny(uidb), + assertUnwrapAny(regc), + assertUnwrapAny(rege), 0, 20, 40, - unwrap(tta) + assertUnwrapAny(tta) ); let rtb_b_c_ttb = await s.transporterAddRoute( pkb, - unwrap(uidb), - unwrap(regb), - unwrap(regc), + assertUnwrapAny(uidb), + assertUnwrapAny(regb), + assertUnwrapAny(regc), 0, 40, 70, - unwrap(ttb) + assertUnwrapAny(ttb) ); let rtb_a_c_ttc = await s.transporterAddRoute( pkb, - unwrap(uidb), - unwrap(rega), - unwrap(regc), + assertUnwrapAny(uidb), + assertUnwrapAny(rega), + assertUnwrapAny(regc), 20, 40, 97, - unwrap(ttc) + assertUnwrapAny(ttc) ); let rtc_b_c_tta = await s.transporterAddRoute( pkc, - unwrap(uidc), - unwrap(regb), - unwrap(regb), + assertUnwrapAny(uidc), + assertUnwrapAny(regb), + assertUnwrapAny(regb), 20, 40, 40, - unwrap(tta) + assertUnwrapAny(tta) ); let rtc_c_e_tta = await s.transporterAddRoute( pkc, - unwrap(uidc), - unwrap(regc), - unwrap(regb), + assertUnwrapAny(uidc), + assertUnwrapAny(regc), + assertUnwrapAny(regb), 20, 40, 70, - unwrap(tta) + assertUnwrapAny(tta) ); let rtc_a_c_ttc = await s.transporterAddRoute( pkc, - unwrap(uidc), - unwrap(rega), - unwrap(regc), + assertUnwrapAny(uidc), + assertUnwrapAny(rega), + assertUnwrapAny(regc), 20, 40, 97, - unwrap(ttc) + assertUnwrapAny(ttc) ); let rtd_b_c_ttb = await s.transporterAddRoute( pkd, - unwrap(uidd), - unwrap(regb), - unwrap(regd), + assertUnwrapAny(uidd), + assertUnwrapAny(regb), + assertUnwrapAny(regd), 20, 40, 50, - unwrap(ttb) + assertUnwrapAny(ttb) ); let rtd_c_e_tta = await s.transporterAddRoute( pkd, - unwrap(uidd), - unwrap(regc), - unwrap(regd), + assertUnwrapAny(uidd), + assertUnwrapAny(regc), + assertUnwrapAny(regd), 20, 40, 70, - unwrap(tta) + assertUnwrapAny(tta) ); let rte_a_c_ttc = await s.transporterAddRoute( pke, - unwrap(uide), - unwrap(rega), - unwrap(regd), + assertUnwrapAny(uide), + assertUnwrapAny(rega), + assertUnwrapAny(regd), 20, 40, 97, - unwrap(ttc) + assertUnwrapAny(ttc) ); printEntityCount("Route@time1", (await s.getCounts()).route_count); @@ -270,20 +268,18 @@ actor class Test() = this { /**- remove some of the routes added above */ - { let x = await s.transporterRemRoute(pkc, unwrap(rtc_b_c_tta)); - assertSome<()>(x); }; + assertOk(await s.transporterRemRoute(pkc, assertUnwrapAny(rtc_b_c_tta))); // a double-remove should return null - assertNull<()>(await s.transporterRemRoute(pkc, unwrap(rtc_b_c_tta))); + //assertErr(await s.transporterRemRoute(pkc, assertUnwrapAny(rtc_b_c_tta))); printEntityCount("Route@time2", (await s.getCounts()).route_count); - { let x = await s.transporterRemRoute(pkc, unwrap(rtc_c_e_tta)); - assertSome<()>(x); }; + assertOk(await s.transporterRemRoute(pkc, assertUnwrapAny(rtc_c_e_tta))); // a double-remove should return null - assertNull<()>(await s.transporterRemRoute(pkc, unwrap(rtc_c_e_tta))); - + //assertErr(await s.transporterRemRoute(pkc, assertUnwrapAny(rtc_c_e_tta))); + printEntityCount("Route@time3", (await s.getCounts()).route_count); ////////////////////////////////////////////////////////////////// @@ -296,11 +292,11 @@ actor class Test() = this { print "\nRetailer queries\n====================================\n"; // do some queries - await retailerQueryAll(pka, uida); - await retailerQueryAll(pkb, uidb); - await retailerQueryAll(pkc, uidc); - await retailerQueryAll(pkd, uidd); - await retailerQueryAll(pke, uide); + await retailerQueryAll(pka, ? assertUnwrapAny(uida)); + await retailerQueryAll(pkb, ? assertUnwrapAny(uidb)); + await retailerQueryAll(pkc, ? assertUnwrapAny(uidc)); + await retailerQueryAll(pkd, ? assertUnwrapAny(uidd)); + await retailerQueryAll(pke, ? assertUnwrapAny(uide)); print "\nQuery counts\n----------------\n"; let counts = await s.getCounts(); @@ -314,7 +310,7 @@ actor class Test() = this { // User c should not be able to remove user a's route if false { print "\nAuthentication test, expect assertion failure:\n"; - ignore(await s.transporterRemRoute(pkc, unwrap(rta_a_c_tta))) + ignore(await s.transporterRemRoute(pkc, assertUnwrapAny(rta_a_c_tta))) }; }) }; @@ -330,8 +326,8 @@ func retailerQueryAll(pk:Text, r:?UserId) : async () { print "------------------------------------\n"; print "\n## Query begin:\n"; - let res = unwrap( - await server.retailerQueryAll(pk, retailerId) + let res = assertUnwrapAny( + await server.retailerQueryAll(pk, retailerId, null, null) ); print "\n## Query end."; diff --git a/stdlib/prelude.as b/stdlib/prelude.as index 5c85d42638a..260cd5b6d25 100644 --- a/stdlib/prelude.as +++ b/stdlib/prelude.as @@ -22,10 +22,10 @@ trap in all execution contexts. */ func nyi() : None = - { assert false ; nyi(); }; + { assert false ; loop { } }; func xxx() : None = - { assert false ; xxx(); }; + { assert false ; loop { } }; /*** @@ -38,4 +38,4 @@ func xxx() : None = trap in all execution contexts. */ -func unreachable() : None = { assert false ; unreachable() }; +func unreachable() : None = { assert false ; loop { } }; diff --git a/stdlib/result.as b/stdlib/result.as new file mode 100644 index 00000000000..aad5e8fb8b7 --- /dev/null +++ b/stdlib/result.as @@ -0,0 +1,101 @@ +/** + + Result + ========= + + The result of a computation that may contain errors, exceptions, etc. + + ActorScript does not have exceptions, so we use a datatype to encode these outcomes. + + Rust does something analogous, for the same reason. We use the Rust nomenclature for the datatype and its constructors. + + */ + +type Result = { + #ok:Ok; + #err:Err; +}; + + +/** + `unwrapOptionResult` + --------------- + to do: rename me. cc @paulyoung. +*/ +func optionUnwrapResult(o:?Ok):Result { + switch(o) { + case (?o) (#ok o); + case _ unreachable(); + } +}; + +/** + `assertUnwrap` + --------------- + assert that we can unwrap the result; should only be used in tests, not in canister implementations. This will trap. +*/ +func assertUnwrap(r:Result):Ok { + switch(r) { + case (#err e) unreachable(); + case (#ok r) r; + } +}; + +/** + `assertUnwrapAny` + --------------- + */ +func assertUnwrapAny(r:Result):Ok { + switch(r) { + case (#err e) unreachable(); + case (#ok r) r; + } +}; + +/** + `assertOk` + --------------- +*/ +func assertOk(r:Result) { + switch(r) { + case (#err _) assert false; + case (#ok _) (); + } +}; + +/** + `assertErr` + --------------- +*/ +func assertErr(r:Result) { + switch(r) { + case (#err _) (); + case (#ok _) assert false; + } +}; + +/** + `bind` + ------- + bind operation in result monad. +*/ +func bind( + x:Result, + y:R1 -> Result) : Result { + switch x { + case (#err e) (#err e); + case (#ok r) (y r); + } +}; + +/** + `option` + ------- + create a result from an option, including an error value to handle the `null` case. +*/ +func optionResult(x:?R, err:E):Result { + switch x { + case (? x) {#ok x}; + case null {#err err}; + } +};