Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@typespec/rest": "~0.43.0",
"@typespec/openapi3": "~0.43.0",
"@typespec/openapi": "~0.43.0",
"@typespec/protobuf": "~0.43.0",
"@typespec/html-program-viewer": "~0.43.0",
"@emotion/react": "^11.10.4",
"@vitejs/plugin-react": "~3.1.0",
Expand Down
101 changes: 101 additions & 0 deletions packages/playground/samples/kiosk.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import "@typespec/protobuf";

using TypeSpec.Protobuf;

@package({
name: "kiosk",
})
namespace KioskExample;

@TypeSpec.Protobuf.service
interface Display {
/**
* Create a new kiosk. This enrolls the kiosk for sign display.
*/
createKiosk(...Kiosk): Kiosk;

/**
* List active kiosks.
*/
listKiosks(...WellKnown.Empty): {
@field(1) kiosks: Kiosk[];
};

/**
* Get a kiosk.
*/
getKiosk(@field(1) id: int32): Kiosk;

/**
* Delete a kiosk.
*/
deleteKiosk(@field(1) id: int32): void;

/**
* Create a new sign.
*/
createSign(...Sign): Sign;

/**
* List active signs.
*/
listSigns(...WellKnown.Empty): {
@field(1) signs: Sign[];
};

/**
* Get a sign.
*/
getSign(@field(1) id: int32): Sign;

/**
* Delete a sign.
*/
deleteSign(@field(1) id: int32): void;

/**
* Set a sign for display on one or more kiosks
*/
setSignIdForKioskIds(@field(1) kiosk_ids: int32[], @field(2) sign_id: int32): void;

/**
* Get the sign that should be displayed on a kiosk.
*/
getSignIdForKioskId(@field(1) kiosk_id: int32): GetSignIdResponse;
/**
* Get signs that should be displayed on a kiosk. Streams.
*/
@stream(StreamMode.Out)
getSignIdsforKioskId(@field(1) kiosk_id: int32): GetSignIdResponse;
}

model Kiosk {
// Output only.
@field(1) id?: int32;
// Required.
@field(2) name: string;
@field(3) size: ScreenSize;
@field(4) location: WellKnown.LatLng;
// Output only.
@field(5) create_time?: WellKnown.Timestamp;
}

model Sign {
// Output only.
@field(1) id?: int32;
// Required.
@field(2) name: string;
@field(3) text: string;
@field(4) image: bytes;
// Output only.
@field(5) create_time?: WellKnown.Timestamp;
}

model ScreenSize {
@field(1) width: int32;
@field(2) height: int32;
}

model GetSignIdResponse {
@field(1) sign_id: int32;
}
16 changes: 14 additions & 2 deletions packages/playground/src/build-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,23 @@ function playgroundManifestPlugin(config: PlaygroundConfig): Plugin {
load(id: string) {
if (id === `playground-manifest.js`) {
const sampleImport = Object.values(samples)
.map((path, index) => `import s${index} from "${viteConfig.root}/${path}?raw"`)
.map(
(sampleValue, index) =>
`import s${index} from "${viteConfig.root}/${sampleValue.fileName}?raw"`
)
.join("\n");
const sampleObj = [
"{",
...Object.keys(samples).map((label, index) => `${JSON.stringify(label)}: s${index}, `),
...Object.entries(samples).map(
([label, config], index) =>
`${JSON.stringify(label)}: {
fileName: ${JSON.stringify(config.fileName)},
preferredEmitter: ${
config.preferredEmitter ? JSON.stringify(config.preferredEmitter) : "undefined"
},
content: s${index}
}, `
),
"}",
].join("\n");

Expand Down
24 changes: 21 additions & 3 deletions packages/playground/src/components/editor-command-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Link, Toolbar, ToolbarButton, Tooltip } from "@fluentui/react-components";
import { Bug16Regular, Save16Regular } from "@fluentui/react-icons";
import { FunctionComponent } from "react";
import { FunctionComponent, useCallback } from "react";
import { useSetRecoilState } from "recoil";
import { SampleConfig } from "../index.js";
import { PlaygroundManifest } from "../manifest.js";
import { selectedEmitterState } from "../state.js";
import { EmitterDropdown } from "./emitter-dropdown.js";
import { SamplesDropdown } from "./samples-dropdown.js";

export interface EditorCommandBarProps {
documentationUrl?: string;
saveCode: () => Promise<void> | void;
updateTypeSpec: (value: string) => Promise<void> | void;
updateTypeSpec: (value: string) => void;
newIssue: () => Promise<void> | void;
}
export const EditorCommandBar: FunctionComponent<EditorCommandBarProps> = ({
Expand All @@ -23,6 +27,20 @@ export const EditorCommandBar: FunctionComponent<EditorCommandBarProps> = ({
</Link>
</label>
) : undefined;

const setEmitter = useSetRecoilState(selectedEmitterState);

const onSelectSample = useCallback(
(config: SampleConfig) => {
if (!config.content) throw new Error("Unreachable: sample has no 'content' property");

updateTypeSpec(config.content);

setEmitter(config.preferredEmitter ?? PlaygroundManifest.defaultEmitter);
},
[setEmitter, updateTypeSpec]
);

return (
<div css={{ borderBottom: "1px solid #f5f5f5" }}>
<Toolbar>
Expand All @@ -34,7 +52,7 @@ export const EditorCommandBar: FunctionComponent<EditorCommandBarProps> = ({
onClick={saveCode as any}
/>
</Tooltip>
<SamplesDropdown onSelectSample={updateTypeSpec as any} />
<SamplesDropdown onSelectSample={onSelectSample} />
<EmitterDropdown />
{documentation}
<div css={{ flex: "1" }}></div>
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/src/components/emitter-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const EmitterDropdown: FunctionComponent<EmitterDropdownProps> = () => {
return (
<Select className="sample-dropdown" onChange={handleSelected} value={selectedEmitter}>
<option value="" disabled>
Select sample...
Select emitter...
</option>
{options}
</Select>
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/src/components/openapi-output.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface OpenAPIOutputProps {
export const OpenAPIOutput: FunctionComponent<OpenAPIOutputProps> = (props) => {
const [selected, setSelected] = useState<"raw" | "swagger-ui">("raw");
const options = [
{ label: "OpenAPI", value: "raw" },
{ label: "Output Files", value: "raw" },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hhm, thats another issue didn' think of, we don't really want that dropdown to be showing for non openapi output

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you get a kind of ugly warning about missing the OpenAPI version number if you set it to Swagger UI when the protobuf emitter is selected...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should have yet another config to load a plugin(swagger view) for certain emitters. become out of scope of your pr though

{ label: "Swagger UI", value: "swagger-ui" },
];

Expand Down
10 changes: 8 additions & 2 deletions packages/playground/src/components/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const PlaygroundInternal: FunctionComponent<PlaygroundProps> = ({
const doCompile = useCallback(async () => {
const content = typespecModel.getValue();
const typespecCompiler = await importTypeSpecCompiler();

const state = await compile(host, content, selectedEmitter, emittersOptions);
setCompilationStatus(state);
if ("program" in state) {
Expand Down Expand Up @@ -81,9 +82,14 @@ const PlaygroundInternal: FunctionComponent<PlaygroundProps> = ({
}, [updateTypeSpec]);

useEffect(() => {
const disposable = typespecModel.onDidChangeContent(debounce(() => doCompile(), 200));
return () => disposable.dispose();
const debouncer = debounce(() => doCompile(), 200);
const disposable = typespecModel.onDidChangeContent(debouncer);
return () => {
debouncer.clear();
disposable.dispose();
};
}, [typespecModel, doCompile]);

useEffect(() => {
void doCompile();
}, [doCompile]);
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/src/components/samples-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Select } from "@fluentui/react-components";
import { FunctionComponent, useCallback, useEffect, useState } from "react";
import { SampleConfig } from "../index.js";
import { PlaygroundManifest } from "../manifest.js";
export interface SamplesDropdownProps {
onSelectSample: (content: string) => void;
onSelectSample: (content: SampleConfig) => void;
}
export const SamplesDropdown: FunctionComponent<SamplesDropdownProps> = ({ onSelectSample }) => {
const [selected, setSelected] = useState<string>("");
Expand All @@ -24,6 +25,7 @@ export const SamplesDropdown: FunctionComponent<SamplesDropdownProps> = ({ onSel
const handleSelected = useCallback(
(evt: any) => {
setSelected(evt.target.value);

onSelectSample(PlaygroundManifest.samples[evt.target.value]);
},
[onSelectSample]
Expand Down
8 changes: 7 additions & 1 deletion packages/playground/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
export interface PlaygroundConfig {
defaultEmitter: string;
libraries: string[];
samples: Record<string, string>;
samples: Record<string, SampleConfig>;
enableSwaggerUI: boolean;
links: {
newIssue: string;
documentation: string;
};
}

export interface SampleConfig {
fileName: string;
preferredEmitter?: string;
content?: string;
}

export { createBrowserHost } from "./browser-host.js";
export { Playground, PlaygroundProps } from "./components/playground.js";
19 changes: 15 additions & 4 deletions packages/playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,23 @@ const config = definePlaygroundViteConfig({
"@typespec/openapi",
"@typespec/versioning",
"@typespec/openapi3",
"@typespec/protobuf",
],
samples: {
"API versioning": "samples/versioning.tsp",
"Discriminated unions": "samples/unions.tsp",
"HTTP service": "samples/http.tsp",
"REST framework": "samples/rest.tsp",
"API versioning": {
fileName: "samples/versioning.tsp",
preferredEmitter: "@typespec/openapi3",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be better to have this as an array of emitters that are compatible with this sample?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR I'm hoping to do the very minimum thing to light up typespec/protobuf in the existing playground so that we can share it. If we want to rework the playground a little bit to have an emitter -> sample flow then we should do that separately IMO and just let this be a dead simple autoselect for now.

@timotheeguerin timotheeguerin Apr 26, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure lets merge that and update after, can you just make a PR updating typespec-azure with the new option too

},
"Discriminated unions": {
fileName: "samples/unions.tsp",
preferredEmitter: "@typespec/openapi3",
},
"HTTP service": { fileName: "samples/http.tsp", preferredEmitter: "@typespec/openapi3" },
"REST framework": { fileName: "samples/rest.tsp", preferredEmitter: "@typespec/openapi3" },
"Protobuf Kiosk": {
fileName: "samples/kiosk.tsp",
preferredEmitter: "@typespec/protobuf",
},
},
enableSwaggerUI: true,
links: {
Expand Down
9 changes: 8 additions & 1 deletion packages/protobuf/lib/proto.tsp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "../dist/src/proto.js";
import "../dist/src/index.js";

namespace TypeSpec.Protobuf;

Expand Down Expand Up @@ -57,6 +57,13 @@ namespace WellKnown {
* This model references `google.protobuf.Any` from `google/protobuf/any.proto`.
*/
model Any is Extern<"google/protobuf/any.proto", "google.protobuf.Any">;

/**
* A latitude and longitude.
*
* This model references `google.type.LatLng` from `google/type/latlng.proto`.
*/
model LatLng is Extern<"google/type/latlng.proto", "google.type.LatLng">;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/protobuf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"protobuf",
"grpc"
],
"main": "dist/src/lib.js",
"main": "dist/src/index.js",
"type": "module",
"tspMain": "lib/proto.tsp",
"scripts": {
Expand Down
7 changes: 7 additions & 0 deletions packages/protobuf/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TypeSpecProtobufLibrary } from "./lib.js";

export const namespace = "TypeSpec.Protobuf";

export * from "./proto.js";

export const $lib = TypeSpecProtobufLibrary;
2 changes: 0 additions & 2 deletions packages/protobuf/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ export const TypeSpecProtobufLibrary = createTypeSpecLibrary({

export const { reportDiagnostic } = TypeSpecProtobufLibrary;

export { $onEmit } from "./proto.js";

export type TypeSpecProtobufLibrary = typeof TypeSpecProtobufLibrary;

const keys = [
Expand Down
5 changes: 2 additions & 3 deletions packages/protobuf/src/proto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ const IMPLEMENTATION_RESERVED_RANGE = [19000, 19999] as const;
/**
* Defined in the [ProtoBuf Language Spec](https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#identifiers).
*
* ident = letter { letter | decimalDigit | "_" }
* fullIdent = ident { "." ident }
* ident = letter \{ letter | decimalDigit | "_" \}
* fullIdent = ident \{ "." ident \}
*/
export const PROTO_FULL_IDENT = /([a-zA-Z][a-zA-Z0-9_]*)+/;

Expand Down Expand Up @@ -215,4 +215,3 @@ export async function $onValidate(program: Program) {
}

export const namespace = "TypeSpec.Protobuf";
export { TypeSpecProtobufLibrary as $lib };
5 changes: 2 additions & 3 deletions packages/protobuf/src/transform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
Type,
Union,
} from "@typespec/compiler";
import { EOL } from "os";
import {
map,
matchType,
Expand Down Expand Up @@ -350,7 +349,7 @@ function tspToProto(program: Program): ProtoFile[] {

if (!emptyType) {
throw new Error(
`Could not resolve the empty type: ${diagnostics.map(formatDiagnostic).join(EOL)}`
`Could not resolve the empty type: ${diagnostics.map(formatDiagnostic).join("\n")}`
);
}

Expand Down Expand Up @@ -569,7 +568,7 @@ function tspToProto(program: Program): ProtoFile[] {

for (const [[type, diagnostics]] of entries) {
if (!type) {
const diagnosticString = diagnostics.map(formatDiagnostic).join(EOL);
const diagnosticString = diagnostics.map(formatDiagnostic).join("\n");
throw new Error(
`Failed to construct TypeSpec -> Protobuf scalar map. Unexpected failure to resolve TypeSpec scalar: ${diagnosticString}`
);
Expand Down