🍌
google.scripts.run における引数の制約
例
code.gs
function myFunction(param) {
console.log(param);
return {
ok: true,
value: {
fruit: "banana",
count: 10,
},
date: new Date(),
}
}
index.html
<script>
function onSuccess(result) {
console.log(result);
// > null
}
const param = {
ok: true,
value: {
fruit: "banana",
count: 10,
},
// func: () => console.log("ok"),
// ^^^^^^^^^^^^^^^^^^^^^^^
// Uncaught TypeError: Failed due to illegal value in property: func
}
google.script.run
.withSuccessHandler(onSuccess)
.myFunction(param)
</script>
なぜこのようなことが起こるか
クライアント <-> サーバーの通信では、関数の他、Date オブジェクトなどを渡せない。
「クライアント -> サーバー」では、Uncaught TypeError: Failed due to illegal value in property: {property name}
が発生する。
「サーバー -> クライアント」では、成功判定の上で渡される値が null
になる。
暫定的な解決策
以下では clasp
を用いてローカルに開発環境を移し、TypeScript x React で開発をし、ライブラリ gas-client を用いる前提とする(勝手に飛躍してすみません)。
result.ts
result.ts
type Result<T, E extends Error = Error> = Success<T> | Failure<E>;
class Success<T> {
readonly value: T;
constructor(value: T) {
this.value = value;
}
isOk(): this is Success<T> {
return true;
}
isErr(): this is Failure<never> {
return false;
}
}
class Failure<E extends Error> {
readonly error: E;
constructor(error: E) {
this.error = error;
}
isOk(): this is Success<unknown> {
return false;
}
isErr(): this is Failure<E> {
return true;
}
}
function Ok<T>(value: T) {
return new Success(value);
}
function Err<E extends Error>(err: E) {
return new Failure(err);
}
サーバー側の適当な関数
type MyType = {
ok: boolean;
value: {
fruit: string;
count: number;
nested: {
hoge: boolean;
fuga: (number | number[])[];
};
};
};
function myFunction(param: MyType, param2: string): Result<MyType> {
console.log(param, param2);
return Ok({
ok: true,
value: {
fruit: "banana",
count: 10,
nested: {
hoge: true,
fuga: [0.1, 2, [3, 4]],
},
},
});
}
gas-client に api として認識させる部分
type ApiData =
| string
| number
| boolean
| undefined
| null
| { [key: number]: ApiData }
| { [key: string]: ApiData }
| ApiData[]
| HTMLFormElement;
type ApiResult<T extends ApiData> =
| {
ok: true;
data: T;
}
| {
ok: false;
name: string;
message: string;
};
function apiHandler<T extends ApiData>(
proc: () => Result<T>
): ApiResult<T> {
const result = proc();
if (result.isOk()) {
return {
ok: true,
data: result.value,
};
}
return {
ok: false,
name: result.error.name,
message: result.error.message,
};
}
export function apiMyFunction(...p: Parameters<typeof myFunction>) {
return apiHandler(() => myFunction(...p));
}
クライアント側
import { GASClient } from "gas-client";
import * as server from "../server/main";
const { serverFunctions } = new GASClient<typeof server>();
function App() {
const param = {
ok: true,
value: {
fruit: "banana",
count: 10,
nested: {
hoge: true,
fuga: [0.1, 2, [3, 4]],
},
},
};
serverFunctions.apiMyFunction(param, "param2").then((v) => {
console.log(v);
});
return <></>;
}
export default App;
解決したこと
- サーバーからの返り値を制限できた
解決してないこと
- クライアントから渡す引数の型を制限できていない
- GAS の制限により
export const
が出来ないため - 任意の引数の型を受け取る関数が作成できないため
- GAS の制限により
良い方法あったら教えてください。
Discussion