diff --git a/LICENSE b/LICENSE index 641c707a8f4..717bfdb514b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +# MIT License -Copyright (c) 2022 Birch +Copyright (c) 2022 Birch Solutions, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 70a441baefa..7d1c5a30fb6 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,110 @@ # Fern -Fern is an open source framework that makes it easier to build APIs. +

-Fern allows you to +TODO Fern makes it easy to define APIs. -1. Define a source-of-truth for your API -2. Autogenerate idiomatic & typesafe clients and servers -3. Supports WebSocket and REST APIs -4. Easily manage backwards compatiblity +### Single source of truth -Fern is interoperable with Open API so you are never locked in. +Define your data model and your APIs in **one place** in your repo. -## Example Spec +### Type-safe servers and clients -Below we have written out a sample spec for a Food Delivery App. +Run `fern generate` to automatically generate **server stubs** and **type-safe clients**. + +> "Wow, this codegen is so idiomatic!" - Chuck Norris + +## What languages are supported? + +The Fern compiler reads API definitions written in the [human-readable YML format](/docs/fern_definitions.md) and produces a JSON [intermediate representation](/docs/intermediate_representation.md) (IR). + +| **Language** | **Server Stub** | **Client** | +| ------------ | ---------------- | ---------- | +| Java | ✅ | ✅ | +| TypeScript | 🚧 _in progress_ | ✅ | +| Python | 🚧 | 🚧 | + +_Interested in another language? [Get in touch](hey@buildwithfern.com)_ + +## Let's do an example + +Here's a simple API to get the current weather report: ```yaml -ids: - - MenuItemId - - OrderId: long +# api.yml types: - DeliveryMethod: - enum: - - PICKUP - - DELIVERY - OrderStatus: - union: - pickup: PickupOrderStatus - delivery: DeliveryOrderStatus - PickupOrderStatus: + WeatherReport: + properties: + tempInFahrenheit: double + humidity: + type: integer + docs: a number between 0 and 100 + conditions: WeatherConditions + WeatherConditions: enum: - - PREPARING - - READY_FOR_PICKUP - - PICKED_UP - DeliveryOrderStatus: - enum: - - PREPARING - - ON_THE_WAY - - DELIVERED + - SUNNY + - CLOUDY + - RAINY services: http: - OrderService: - base-path: /order + WeatherService: + base-path: /weather endpoints: - addItemToCart: - docs: Adds a menu item to a cart. - method: POST - path: /add - request: - properties: - menuItemId: MenuItemId - quantity: integer - placeOrder: - method: POST - path: /order/new - request: - properties: - deliveryMethod: DeliveryMethod - tip: optional - response: OrderId - errors: - union: - emptyCart: EmptyCartError - - websocket: - OrderStatusChannel: - messages: - subscribeToOrderStatus: - origin: client - body: OrderId - response: - properties: - orderStatus: OrderStatus - etaInMinutes: integer - behavior: ongoing - errors: - union: - notFound: OrderNotFoundError - -errors: - OrderNotFoundError: - http: - statusCode: 404 - EmptyCartError: - http: - statusCode: 400 + getWeather: + method: GET + path: /{zipCode} + parameters: + zipCode: string + response: WeatherReport +``` + +### The server + +Here's the Typescript/express server stubs that Fern generates: + +TODO + +### The client + +Let's say we published the client to npm... TODO make this better. Here's an example of someone consuming it: + +```ts +import { WeatherService } from "weather-api"; + +const weatherService = WeatherService.create({ + baseUrl: +}) + +const weatherReport = await Weather + ``` -The app has REST endpoints so that clients can add items to their cart and place orders. It also has a websocket channel where a client can subscribe to updates about an order's ETA. +## Contributing + +The team welcomes contributions! To make code changes to one of the Fern repos: + +- Fork the repo and make a branch +- Write your code +- Open a PR (optionally linking to a Github issue) + +## Getting started + +### Installation + +`$ npm install -g fern-api` + +### Initialize Fern in your repo + +`fern init` + +### Generate code + +`fern generate` + +`fern add` + +## License -This spec can be used to generate clients and servers. +This tooling is made available under the [MIT License](LICENSE). diff --git a/docs/concepts.md b/docs/concepts.md new file mode 100644 index 00000000000..d97b32048b8 --- /dev/null +++ b/docs/concepts.md @@ -0,0 +1,173 @@ +# Model + +

+ +The _Fern model_ describes the data model used to defining your APIs in Fern. + +## Core Concepts + +- [imports](#imports) +- [ids](#ids) +- [types](#types) +- [errors](#errors) +- [services](#services) + +### Imports + +_Reference Core Concepts from other Fern models._ + +```yml +import: + blog: blog.yml + user: user.yml +``` + +### Ids + +_Identifiers are named and default to string._ + +```yml +ids: + blog: BlogPostId + user: UserId + publication: PublicationId +``` + +### Types + +_Users may define the following kinds of types. These can be referenced by their name elsewhere in a Fern data model._ + +- [Primitives](#primitives) +- [Objects](#objects) +- [Aliases](#aliases) +- [Enums](#enums) +- [Containers](#containers) + +### Errors + +_Users may define a name and structure for errors so that clients can expect specific pieces of information on failure._ + +_Structured errors_ have the following properties: + +- _Name_ - a user chosen description e.g. `BlogNotFoundError` +- _Status code_ - an optional HTTP status code e.g. `404` + +```yml +errors: + BlogNotFoundError: + http: + statusCode: 404 + UserInvalidError: + http: + statusCode: 400 +``` + +### Services + +_HTTP endpoints that support `GET`, `PUT`, `POST`, `DELETE`_ + +```yml +services: + http: + PostsService: + base-path: /posts + endpoints: + getPost: + method: GET + path: /{postId} + parameters: + postId: string + request: CreatePostRequest + response: PostId + publishPost: + method: POST + path: /publish + request: PublishPostRequest + response: PublishPostResponse + deletePost: + method: DELETE + path: /{postId} + parameters: + postId: string +``` + +### Primitives + +_Types that are built-in to the Fern data model._ + +```yml +types: + Primitives: + properties: + a: any # a catch-all type + b: boolean + c: double + d: integer + e: long + f: string +``` + +### Objects + +_A collection of named properties, each of which has their own Fern type. Below is an example of a `Post` object._ + +```yml +types: + Post: + docs: A blog post + properties: + id: PostId + type: PostType + title: string + author: Author + content: string + Podcast: + docs: An audio version of a blog post + extends: Post + properties: + duration: integer + coverArt: string +``` + +### Aliases + +_A new name for an existing type to make a user's types more self-documenting._ + +```yml +types: + PostType: + properties: + length: PostLength +``` + +### Unions + +_A tagged union data structure that can take on several different, but fixed, types._ + +```yml +types: + Author: + union: + anonymous: {} + name: string +``` + +### Enums + +_A type consisting of named string variants._ + +```yml +types: + BlogStatus: + enum: + - DRAFT + - PUBLISHED + - ARCHIVED +``` + +### Containers + +- `list` - an ordered sequence of items of type `T`. +- `map` - values of type `V` each indexed by a unique key of type `K` (keys are unordered). +- `optional` - represents a value of type `T` which is either present or not present. +- `set` - a collection of distinct values of type `T`. diff --git a/docs/fern-definitions.md b/docs/fern-definitions.md new file mode 100644 index 00000000000..53719c7f31c --- /dev/null +++ b/docs/fern-definitions.md @@ -0,0 +1,40 @@ +# Fern definitions + +

+ +A Fern definition is made up of one or more source YAML files. Each file may define multiple types, services, errors, and ids. Types may also be imported from other files. Source files must end in `.yml`. + +Example file structure: + +```text +weather-api/src/main/fern/report.yml +weather-api/src/main/fern/forecast.yml +weather-api/src/main/fern/commons.yml +``` + +## fern-imports + +For example, one file called `commons.yml` might define a Fern type called `WeatherConditions`: + +```yaml +types: + properties: + WeatherConditions: + enum: + - SUNNY + - CLOUDY + - RAINY +``` + +A separate file in the same directory, `example.yml`, can then reference types defined in `common.yml`: + +```yaml +types: + conjure-imports: + common: common.yml + definitions: + default-package: com.palantir.product + objects: + SomeRequest: + alias: common.ProductId +``` diff --git a/packages/compiler/ir-generation/src/stage.ts b/packages/compiler/ir-generation/src/stage.ts index da3f970f6d1..d3ae99ea976 100644 --- a/packages/compiler/ir-generation/src/stage.ts +++ b/packages/compiler/ir-generation/src/stage.ts @@ -104,8 +104,8 @@ export const IntermediateRepresentationGenerationStage: CompilerStage< } } - if (services.webSocket != null) { - for (const [serviceId, serviceDefinition] of Object.entries(services.webSocket)) { + if (services.websocket != null) { + for (const [serviceId, serviceDefinition] of Object.entries(services.websocket)) { intermediateRepresentation.services.websocket.push( convertWebsocketService({ serviceId, diff --git a/packages/compiler/syntax-analysis/src/schemas/ServicesSchema.ts b/packages/compiler/syntax-analysis/src/schemas/ServicesSchema.ts index d253a8c81fb..0c268dc7145 100644 --- a/packages/compiler/syntax-analysis/src/schemas/ServicesSchema.ts +++ b/packages/compiler/syntax-analysis/src/schemas/ServicesSchema.ts @@ -4,7 +4,7 @@ import { WebSocketServiceSchema } from "./WebSocketServiceSchema"; export const ServicesSchema = z.strictObject({ http: z.optional(z.record(HttpServiceSchema)), - webSocket: z.optional(z.record(WebSocketServiceSchema)), + websocket: z.optional(z.record(WebSocketServiceSchema)), }); export type ServicesSchema = z.infer; diff --git a/packages/fern-typescript/client/src/__test__/generateClientFiles.test.ts b/packages/fern-typescript/client/src/__test__/generateClientFiles.test.ts index 5ef60cdf594..7fb38c86b10 100644 --- a/packages/fern-typescript/client/src/__test__/generateClientFiles.test.ts +++ b/packages/fern-typescript/client/src/__test__/generateClientFiles.test.ts @@ -17,6 +17,7 @@ describe("generateClientFiles", () => { helperManager: MOCK_HELPERS_MANAGERS, }); }, + outputToDisk: true, }); itFernETE("no errors", {