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
9 changes: 8 additions & 1 deletion packages/aws-cdk-lib/aws-appsync/lib/api-base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { IResource, Resource } from '../../core';
import { IApiRef, ApiReference } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* Interface for an API
*/
export interface IApi extends IResource {
export interface IApi extends IResource, IApiRef {

/**
* The unique identifier for the AWS AppSync Api generated by the service.
Expand Down Expand Up @@ -33,4 +34,10 @@ export abstract class ApiBase extends Resource implements IApi {
* The ARN of the AWS AppSync Api.
*/
public abstract readonly apiArn: string;

public get apiRef(): ApiReference {
return {
apiArn: this.apiArn,
};
}
}
14 changes: 13 additions & 1 deletion packages/aws-cdk-lib/aws-appsync/lib/appsync-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FunctionRuntime } from './runtime';
import { Resource, IResource, Lazy, Fn, ValidationError } from '../../core';
import { addConstructMetadata } from '../../core/lib/metadata-resource';
import { propertyInjectable } from '../../core/lib/prop-injectable';
import { IFunctionConfigurationRef, FunctionConfigurationReference } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* the base properties for AppSync Functions
Expand Down Expand Up @@ -91,7 +92,7 @@ export interface AppsyncFunctionAttributes {
/**
* Interface for AppSync Functions
*/
export interface IAppsyncFunction extends IResource {
export interface IAppsyncFunction extends IResource, IFunctionConfigurationRef {
/**
* the name of this AppSync Function
*
Expand Down Expand Up @@ -130,6 +131,11 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction {
constructor(s: Construct, i: string) {
super(s, i);
}
public get functionConfigurationRef(): FunctionConfigurationReference {
return {
functionArn: this.functionArn,
};
}
}
return new Import(scope, id);
}
Expand Down Expand Up @@ -201,4 +207,10 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction {
this.function.addDependency(this.dataSource.ds);
props.api.addSchemaDependency(this.function);
}

public get functionConfigurationRef(): FunctionConfigurationReference {
return {
functionArn: this.functionArn,
};
}
}
14 changes: 13 additions & 1 deletion packages/aws-cdk-lib/aws-appsync/lib/channel-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import { IGrantable } from '../../aws-iam';
import { IResource, Resource, Token, ValidationError } from '../../core';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
import { propertyInjectable } from '../../core/lib/prop-injectable';
import { IChannelNamespaceRef, ChannelNamespaceReference } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* An AppSync channel namespace
*/
export interface IChannelNamespace extends IResource {
export interface IChannelNamespace extends IResource, IChannelNamespaceRef {
/**
* The ARN of the AppSync channel namespace
*
Expand Down Expand Up @@ -187,6 +188,11 @@ export class ChannelNamespace extends Resource implements IChannelNamespace {
public static fromChannelNamespaceArn(scope: Construct, id: string, channelNamespaceArn: string): IChannelNamespace {
class Import extends Resource implements IChannelNamespace {
public readonly channelNamespaceArn = channelNamespaceArn;
public get channelNamespaceRef(): ChannelNamespaceReference {
return {
channelNamespaceArn: this.channelNamespaceArn,
};
}
}
return new Import(scope, id);
}
Expand Down Expand Up @@ -359,4 +365,10 @@ export class ChannelNamespace extends Resource implements IChannelNamespace {
throw new ValidationError('LambdaInvokeType is only supported for Direct handler behavior type', this);
}
}

public get channelNamespaceRef(): ChannelNamespaceReference {
return {
channelNamespaceArn: this.channelNamespaceArn,
};
}
}
25 changes: 21 additions & 4 deletions packages/aws-cdk-lib/aws-appsync/lib/data-source-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { IDomain } from '../../aws-opensearchservice';
import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds';
import { ISecret } from '../../aws-secretsmanager';
import { IResolvable, Token, Lazy, Stack } from '../../core';
import { extractApiIdFromApiRef, toIApi } from './private/ref-utils';
import { IApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* Valid data source types for AppSync
Expand Down Expand Up @@ -83,7 +85,7 @@ export interface AppSyncBaseDataSourceProps {
/**
* The API to attach this data source to
*/
readonly api: IApi;
readonly api: IApiRef;
/**
* The name of the data source. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*}.
* Any invalid characters will be automatically removed.
Expand Down Expand Up @@ -171,7 +173,7 @@ export abstract class AppSyncBaseDataSource extends Construct {
*/
public readonly resource: CfnDataSource;

protected api: IApi;
private _api: IApiRef;
protected serviceRole?: IRole;

constructor(scope: Construct, id: string, props: AppSyncBackedDataSourceProps, extended: AppSyncExtendedDataSourceProps) {
Expand All @@ -182,15 +184,30 @@ export abstract class AppSyncBaseDataSource extends Construct {
// Replace unsupported characters from DataSource name. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*}
const name = (props.name ?? id);
const supportedName = Token.isUnresolved(name) ? name : name.replace(/[\W]+/g, '');
const apiId = extractApiIdFromApiRef(props.api);
this.resource = new CfnDataSource(this, 'Resource', {
apiId: props.api.apiId,
apiId: apiId,
name: supportedName,
description: props.description,
serviceRoleArn: this.serviceRole?.roleArn,
...extended,
});
this.name = supportedName;
this.api = props.api;
this._api = props.api;
}

/**
* The API this data source is attached to
*/
protected get api(): IApi {
return toIApi(this._api);
}

/**
* Set the API this data source is attached to
*/
protected set api(api: IApi) {
this._api = api;
}
}

Expand Down
25 changes: 21 additions & 4 deletions packages/aws-cdk-lib/aws-appsync/lib/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Construct } from 'constructs';
import { BaseAppsyncFunctionProps, AppsyncFunction } from './appsync-function';
import { CfnDataSource } from './appsync.generated';
import { IGraphqlApi } from './graphqlapi-base';
import { extractApiIdFromGraphQLApiRef, toIGraphqlApi } from './private/ref-utils';
import { BaseResolverProps, Resolver } from './resolver';
import { ITable } from '../../aws-dynamodb';
import { IDomain as IElasticsearchDomain } from '../../aws-elasticsearch';
Expand All @@ -13,6 +14,7 @@ import { IServerlessCluster, IDatabaseCluster } from '../../aws-rds';
import { ISecret } from '../../aws-secretsmanager';
import { IResolvable, Lazy, Stack, Token } from '../../core';
import { propertyInjectable } from '../../core/lib/prop-injectable';
import { IGraphQLApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* Base properties for an AppSync datasource
Expand All @@ -21,7 +23,7 @@ export interface BaseDataSourceProps {
/**
* The API to attach this data source to
*/
readonly api: IGraphqlApi;
readonly api: IGraphQLApiRef;
/**
* The name of the data source
*
Expand Down Expand Up @@ -116,7 +118,7 @@ export abstract class BaseDataSource extends Construct {
*/
public readonly ds: CfnDataSource;

protected api: IGraphqlApi;
private _api: IGraphQLApiRef;
protected serviceRole?: IRole;

constructor(scope: Construct, id: string, props: BackedDataSourceProps, extended: ExtendedDataSourceProps) {
Expand All @@ -128,15 +130,30 @@ export abstract class BaseDataSource extends Construct {
// Replace unsupported characters from DataSource name. The only allowed pattern is: {[_A-Za-z][_0-9A-Za-z]*}
const name = (props.name ?? id);
const supportedName = Token.isUnresolved(name) ? name : name.replace(/[\W]+/g, '');
const apiId = extractApiIdFromGraphQLApiRef(props.api);
this.ds = new CfnDataSource(this, 'Resource', {
apiId: props.api.apiId,
apiId: apiId,
name: supportedName,
description: props.description,
serviceRoleArn: this.serviceRole?.roleArn,
...extended,
});
this.name = supportedName;
this.api = props.api;
this._api = props.api;
}

/**
* The API this data source is attached to
*/
protected get api(): IGraphqlApi {
return toIGraphqlApi(this._api);
}

/**
* Set the API this data source is attached to
*/
protected set api(api: IGraphqlApi) {
this._api = api;
}

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice';
import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds';
import { ISecret } from '../../aws-secretsmanager';
import { ArnFormat, CfnResource, IResource, Resource, Stack, UnscopedValidationError } from '../../core';
import { IGraphQLApiRef, GraphQLApiReference } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* Optional configuration for data sources
Expand Down Expand Up @@ -156,7 +157,7 @@ export enum AuthorizationType {
/**
* Interface for GraphQL
*/
export interface IGraphqlApi extends IResource {
export interface IGraphqlApi extends IResource, IGraphQLApiRef {

/**
* an unique AWS AppSync GraphQL API identifier
Expand Down Expand Up @@ -595,4 +596,10 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi {
public grantSubscription(grantee: IGrantable, ...fields: string[]): Grant {
return this.grant(grantee, IamResource.ofType('Subscription', ...fields), 'appsync:GraphQL');
}

public get graphQlApiRef(): GraphQLApiReference {
return {
graphQlApiArn: this.arn,
};
}
}
77 changes: 77 additions & 0 deletions packages/aws-cdk-lib/aws-appsync/lib/private/ref-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Fn, UnscopedValidationError } from '../../../core';
import { IGraphQLApiRef, IApiRef, IFunctionConfigurationRef } from '../../../interfaces/generated/aws-appsync-interfaces.generated';
import { IApi } from '../api-base';
import { IAppsyncFunction } from '../appsync-function';
import { IGraphqlApi } from '../graphqlapi-base';

/**
* Converts an IGraphQLApiRef to IGraphqlApi, validating that it implements the full interface
*/
export function toIGraphqlApi(api: IGraphQLApiRef): IGraphqlApi {
if (!isGraphQlApi(api)) {
throw new UnscopedValidationError(`'api' instance should implement IGraphqlApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`);
}
return api;
}

function isGraphQlApi(apiRef: IGraphQLApiRef): apiRef is IGraphqlApi {
const api = apiRef as any;
if (typeof api.apiId !== 'string' || typeof api.arn !== 'string' || typeof api.addNoneDataSource !== 'function') {
return false;
}
return true;
}

function isIApi(apiRef: IApiRef): apiRef is IApi {
const api = apiRef as any;
if (typeof api.apiId !== 'string' || typeof api.apiArn !== 'string' || typeof api.addDynamoDbDataSource !== 'function') {
return false;
}
return true;
}

/**
* Converts an IApiRef to IApi, validating that it implements the full interface
*/
export function toIApi(api: IApiRef): IApi {
if (!isIApi(api)) {
throw new UnscopedValidationError(`'api' instance should implement IApi, but doesn't: ${api.constructor?.name ?? 'unknown'}`);
}
return api;
}

export function extractApiIdFromApiRef(apiRef: IApiRef): string {
// Check if this is actually an IApi (which has apiId directly)
if (isIApi(apiRef)) {
return apiRef.apiId;
}

// Otherwise, extract from the ARN
// ARN format: arn:aws:appsync:region:account:apis/<apiId>
return Fn.select(1, Fn.split('/', apiRef.apiRef.apiArn));
}

export function extractApiIdFromGraphQLApiRef(apiRef: IGraphQLApiRef): string {
// Check if this is actually an IGraphqlApi (which has apiId directly)
if (isGraphQlApi(apiRef)) {
return apiRef.apiId;
}
// Otherwise, extract from the ARN
// ARN format: arn:aws:appsync:region:account:apis/<apiId>
return Fn.select(1, Fn.split('/', apiRef.graphQlApiRef.graphQlApiArn));
}

function isIFunctionConfiguration(funcRef: IFunctionConfigurationRef): funcRef is IAppsyncFunction {
const fr = funcRef as unknown as IAppsyncFunction;
return !!fr.functionId;
}

export function extractFunctionIdFromFunctionRef(funcRef: IFunctionConfigurationRef): string {
// Check if this is actually an IAppsyncFunction (which has functionId directly)
if (isIFunctionConfiguration(funcRef)) {
return funcRef.functionId;
}
// Otherwise, extract from the ARN
// ARN format: arn:aws:appsync:region:account:apis/<apiId>/functions/<functionId>
return Fn.select(3, Fn.split('/', funcRef.functionConfigurationRef.functionArn));
}
7 changes: 4 additions & 3 deletions packages/aws-cdk-lib/aws-appsync/lib/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Construct } from 'constructs';
import { IAppsyncFunction } from './appsync-function';
import { CfnResolver } from './appsync.generated';
import { CachingConfig } from './caching-config';
import { BASE_CACHING_KEYS } from './caching-key';
Expand All @@ -9,6 +8,8 @@ import { IGraphqlApi } from './graphqlapi-base';
import { MappingTemplate } from './mapping-template';
import { FunctionRuntime } from './runtime';
import { Token, ValidationError } from '../../core';
import { extractFunctionIdFromFunctionRef } from './private/ref-utils';
import { IFunctionConfigurationRef } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* Basic properties for an AppSync resolver
Expand All @@ -28,7 +29,7 @@ export interface BaseResolverProps {
* @default - no pipeline resolver configuration
* An empty array | undefined sets resolver to be of kind, unit
*/
readonly pipelineConfig?: IAppsyncFunction[];
readonly pipelineConfig?: IFunctionConfigurationRef[];
/**
* The request mapping template for this resolver
*
Expand Down Expand Up @@ -105,7 +106,7 @@ export class Resolver extends Construct {
super(scope, id);

const pipelineConfig = props.pipelineConfig && props.pipelineConfig.length ?
{ functions: props.pipelineConfig.map((func) => func.functionId) }
{ functions: props.pipelineConfig.map((func) => extractFunctionIdFromFunctionRef(func)) }
: undefined;

// If runtime is specified, code must also be
Expand Down
9 changes: 5 additions & 4 deletions packages/aws-cdk-lib/aws-appsync/lib/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync } from 'fs';
import { IGraphqlApi } from './graphqlapi-base';
import { extractApiIdFromGraphQLApiRef } from './private/ref-utils';
import { IGraphQLApiRef } from '../../interfaces/generated/aws-appsync-interfaces.generated';

/**
* Configuration for bound graphql schema
Expand Down Expand Up @@ -39,7 +40,7 @@ export interface ISchema {
* @param api the api to bind the schema to
* @param options configuration for bind behavior
*/
bind(api: IGraphqlApi, options?: SchemaBindOptions): ISchemaConfig;
bind(api: IGraphQLApiRef, options?: SchemaBindOptions): ISchemaConfig;
}

/**
Expand Down Expand Up @@ -86,9 +87,9 @@ export class SchemaFile implements ISchema {
*
* @param api The binding GraphQL Api
*/
public bind(api: IGraphqlApi, _options?: SchemaBindOptions): ISchemaConfig {
public bind(api: IGraphQLApiRef, _options?: SchemaBindOptions): ISchemaConfig {
return {
apiId: api.apiId,
apiId: extractApiIdFromGraphQLApiRef(api),
definition: this.definition,
};
}
Expand Down
Loading
Loading