diff --git a/.dagger/generate.go b/.dagger/generate.go index a2d3991d0..3938fcba4 100644 --- a/.dagger/generate.go +++ b/.dagger/generate.go @@ -96,6 +96,18 @@ func (m *Generate) WebSdk() *dagger.Directory { WithoutDirectory("node_modules") } +func (m *Generate) Server() *dagger.Directory { + openapi := m.Openapi() + + source := m.Source. + WithFile("api/openapi.yaml", openapi) + + return goModule(). + WithSource(source). + Exec([]string{"go", "generate", "-x", "./api"}). + Directory("/work/src/api") +} + func (m *Generate) Check(ctx context.Context) error { result := goModuleCross(""). WithSource(m.Source). diff --git a/api/spec/src/cloud/subjects.tsp b/api/spec/src/cloud/subjects.tsp index 74989bc02..2833bb478 100644 --- a/api/spec/src/cloud/subjects.tsp +++ b/api/spec/src/cloud/subjects.tsp @@ -16,14 +16,14 @@ interface Subjects { */ @get @operationId("listSubjects") - list(): Subject[] | OpenMeter.CommonErrors; + list(): OpenMeter.Subject[] | OpenMeter.CommonErrors; /** * Get subject by ID or key. */ @get @operationId("getSubject") - get(@path subjectIdOrKey: string): Subject | OpenMeter.NotFoundError | OpenMeter.CommonErrors; + get(@path subjectIdOrKey: string): OpenMeter.Subject | OpenMeter.NotFoundError | OpenMeter.CommonErrors; /** * Upserts a subject. Creates or updates subject. @@ -33,7 +33,7 @@ interface Subjects { */ @post @operationId("upsertSubject") - upsert(@body subject: Subject[]): Subject[] | OpenMeter.CommonErrors; + upsert(@body subject: OpenMeter.Subject[]): OpenMeter.Subject[] | OpenMeter.CommonErrors; /** * Delete subject by ID or key. @@ -42,53 +42,3 @@ interface Subjects { @operationId("deleteSubject") delete(@path subjectIdOrKey: string): void | OpenMeter.CommonErrors; } - -/** - * A subject is a unique identifier for a user or entity. - */ -@friendlyName("Subject") -@example(#{ - id: "01G65Z755AFWAKHE12NY0CQ9FH", - key: "customer-id", - displayName: "Customer Name", - metadata: #{ hubspotId: "123456" }, - currentPeriodStart: DateTime.fromISO("2023-01-01T00:00:00Z"), - currentPeriodEnd: DateTime.fromISO("2023-02-01T00:00:00Z"), - stripeCustomerId: "cus_JMOlctsKV8", -}) -model Subject { - // Validator doesn't obey required for readOnly properties - // See: https://github.com/stoplightio/spectral/issues/1274 - - /** - * A unique identifier for the subject. - */ - @visibility("read") - @example("01G65Z755AFWAKHE12NY0CQ9FH") - id: ULID; - - /** - * A unique, human-readable identifier for the subject. - * Must consist only alphanumeric and underscore characters. - */ - @example("customer-id") - key: Key; - - /** - * A human-readable display name for the subject. - */ - @example("Customer Name") - displayName?: string; - - @example(#{ hubspotId: "123456" }) - metadata?: Record; - - @example(DateTime.fromISO("2023-01-01T00:00:00Z")) - currentPeriodStart?: DateTime; - - @example(DateTime.fromISO("2023-02-01T00:00:00Z")) - currentPeriodEnd?: DateTime; - - @example("cus_JMOlctsKV8") - stripeCustomerId?: string; -} diff --git a/api/spec/src/entitlements/entitlements.tsp b/api/spec/src/entitlements/entitlements.tsp index 8a1e11153..03d7bfabd 100644 --- a/api/spec/src/entitlements/entitlements.tsp +++ b/api/spec/src/entitlements/entitlements.tsp @@ -76,6 +76,7 @@ interface Entitlements { * Type of the entitlement. */ @friendlyName("EntitlementType") +@extension("x-go-type", "string") enum EntitlementType { metered: "metered", boolean: "boolean", @@ -225,6 +226,13 @@ model EntitlementSharedFields { ...ResourceTimestamps; ...OmitProperties; + /** + * Readonly unique ULID identifier. + */ + @example("01ARZ3NDEKTSV4RRFFQ69G5FAV") + @visibility("read") + id: ULID; + /** * The type of the entitlement. */ diff --git a/api/spec/src/entitlements/feature.tsp b/api/spec/src/entitlements/feature.tsp index e30242e35..4e4fac061 100644 --- a/api/spec/src/entitlements/feature.tsp +++ b/api/spec/src/entitlements/feature.tsp @@ -32,12 +32,14 @@ interface Features { /** * Filter by meterSlug */ - @query meterSlug?: string, + @query + meterSlug?: string[], /** * Filter by meterGroupByFilters */ - @query includeArchived?: boolean = false, + @query + includeArchived?: boolean = false, ...OpenMeter.QueryPagination, ...OpenMeter.QueryLimitOffset, @@ -91,7 +93,7 @@ model FeatureCreateInputs { @summary("Optional metadata") @example(#{ key: "value" }) - metadata: Metadata; + metadata?: Metadata; // /** // * The meter that the feature is associated with and and based on which usage is calculated. @@ -125,4 +127,11 @@ model Feature { ...ResourceTimestamps; ...Archiveable; ...FeatureCreateInputs; + + /** + * Readonly unique ULID identifier. + */ + @example("01ARZ3NDEKTSV4RRFFQ69G5FAV") + @visibility("read") + id: ULID; } diff --git a/api/spec/src/main.tsp b/api/spec/src/main.tsp index 36299389f..e5f079590 100644 --- a/api/spec/src/main.tsp +++ b/api/spec/src/main.tsp @@ -13,6 +13,7 @@ import "./customer.tsp"; import "./events.tsp"; import "./meters.tsp"; import "./portal.tsp"; +import "./subjects.tsp"; import "./debug.tsp"; import "./notification"; import "./entitlements"; diff --git a/api/spec/src/meters.tsp b/api/spec/src/meters.tsp index f3e73c423..3175498bf 100644 --- a/api/spec/src/meters.tsp +++ b/api/spec/src/meters.tsp @@ -194,7 +194,7 @@ enum WindowSize { /** * A unique meter identifier. */ -alias MeterIdentifier = ULID | Key; +alias MeterIdentifier = ULIDOrKey; /** * Meter query parameters. diff --git a/api/spec/src/notification/event.tsp b/api/spec/src/notification/event.tsp index 3a1d14684..ca5e95712 100644 --- a/api/spec/src/notification/event.tsp +++ b/api/spec/src/notification/event.tsp @@ -108,7 +108,7 @@ model EventBalanceThresholdPayloadData { @visibility("read") @summary("Subject") - subject: Subject; + subject: OpenMeter.Subject; @visibility("read") @summary("Entitlement Value") @@ -119,12 +119,6 @@ model EventBalanceThresholdPayloadData { threshold: RuleBalanceThresholdValue; } -/** - * FIXME: move subject api to OpenMeter so Subject model is avaiable here. - */ -@friendlyName("DummySubject") -model Subject {} - /** * Type of the notification event. */ diff --git a/api/spec/src/subjects.tsp b/api/spec/src/subjects.tsp new file mode 100644 index 000000000..61f0acc91 --- /dev/null +++ b/api/spec/src/subjects.tsp @@ -0,0 +1,61 @@ +import "@typespec/http"; +import "@typespec/rest"; +import "@typespec/openapi3"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.OpenAPI; + +namespace OpenMeter; + +/** + * A subject is a unique identifier for a user or entity. + */ +@friendlyName("Subject") +@example(#{ + id: "01G65Z755AFWAKHE12NY0CQ9FH", + key: "customer-id", + displayName: "Customer Name", + metadata: #{ hubspotId: "123456" }, + currentPeriodStart: DateTime.fromISO("2023-01-01T00:00:00Z"), + currentPeriodEnd: DateTime.fromISO("2023-02-01T00:00:00Z"), + stripeCustomerId: "cus_JMOlctsKV8", +}) +model Subject { + // Validator doesn't obey required for readOnly properties + // See: https://github.com/stoplightio/spectral/issues/1274 + + /** + * A unique identifier for the subject. + */ + @visibility("read") + @example("01G65Z755AFWAKHE12NY0CQ9FH") + id: ULID; + + /** + * A unique, human-readable identifier for the subject. + * Must consist only alphanumeric and underscore characters. + */ + @example("customer-id") + key: Key; + + /** + * A human-readable display name for the subject. + */ + @example("Customer Name") + displayName?: string; + + // TODO: figure out if we want to support arbitrary values or string only + + @example(#{ hubspotId: "123456" }) + metadata?: Record | null; + + @example(DateTime.fromISO("2023-01-01T00:00:00Z")) + currentPeriodStart?: DateTime; + + @example(DateTime.fromISO("2023-02-01T00:00:00Z")) + currentPeriodEnd?: DateTime; + + @example("cus_JMOlctsKV8") + stripeCustomerId?: string; +} diff --git a/api/spec/src/types.tsp b/api/spec/src/types.tsp index b02731250..b457558a9 100644 --- a/api/spec/src/types.tsp +++ b/api/spec/src/types.tsp @@ -1,3 +1,7 @@ +import "@typespec/openapi3"; + +using TypeSpec.OpenAPI; + /** * ULID (Universally Unique Lexicographically Sortable Identifier). */ @@ -17,11 +21,24 @@ scalar ULID extends string; @maxLength(64) scalar Key extends string; +/** + * ULID (Universally Unique Lexicographically Sortable Identifier). + * A key is a unique string that is used to identify a resource. + * + * TODO: this is a temporary solution to support both ULID and Key in the same spec for codegen. + */ +@pattern("^[a-z0-9]+(?:_[a-z0-9]+)*$|^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$") +@minLength(1) +@maxLength(64) +scalar ULIDOrKey extends string; + // NOTE (andras): key format enforcement isn't supported by TypeSpec (patternProperties). See: https://github.com/microsoft/typespec/discussions/1626 +// TODO: decide if we want to use the generated Metadata type instead and update code to use it /** * Set of key-value pairs. * Metadata can be used to store additional information about a resource. */ +@extension("x-go-type", "map[string]string") @example(#{ externalId: "019142cc-a016-796a-8113-1a942fecd26d" }) model Metadata { ...Record;