Canister API that evolves over time

Say I have a API between frontend and canister (e.g. motoko):

type Request = {
 #getInfo;
 #getMessages;
}

type Response {
 #info = Text;
 #messages = [Text];
}

actor {
  public query func makeRequest(request: ?Request) : async ?Response {
    switch(request) {
      case (null) { 
        //got unsupported variant
        null;
      };
      case (?requestUnwrapped) {
         //respond with corresponding response variant...
         ?#info "hello from info";
      }
 }
}

I would like to evolve this API over time - add new request-response variant pairs.

E.g. add “#getLastMessages” case to Request or add “#status” to Response

Those changes can be made in different ways:

1. new UI can talk to old canister API

UI candid (passing Request #getLastMessages)

type Request = {
 #getInfo;
 #getMessages;
 #getLastMessages; //new!
}
type Response {
 #info = Text;
 #messages = [Text];
 #status = Text; //new!
}

Canister

type Request = {
 #getInfo;
 #getMessages;
}
type Response {
 #info = Text;
 #messages = [Text];
}

All is good - canister gets null as a fallback for unsupported opt type.

2. old UI can talk to new canister API

UI candid

type Request = {
 #getInfo;
 #getMessages;
}
type Response {
 #info = Text;
 #messages = [Text];
}

Canister (responding with Response #status)

type Request = {
 #getInfo;
 #getMessages;
 #getLastMessages; //new!
}
type Response {
 #info = Text;
 #messages = [Text];
 #status = Text; //new!
}

Provided approach give an error in second case “old UI- new canister”: adding new variant case in canister and returning it to UI (javascript in the browser) gives an error: Error: Cannot find field hash _xxxxxx_.

If there is a way to get a fallback to null for non matching types on both sides - in canister and in javascript?

As I understand - I get NULL case in motoko because of “Candid - opt is a special”.

@nomeata @claudio Please share your thoughts on building API that evolves and handles unsupported variant cases in parameter and return type on both sides.

3 Likes

Yes, I think it should work as you describe; maybe the JS library isn’t implementing the spec correctly in that case?

Thanks for a quick reply.
Can you tag tech guys who clarify that question from JS side?

@chenyan woud be your man here