Skip to content

HttpApiEndpoint handling prevents string insertion/computed values for path segment #4752

@dmeehan1968

Description

@dmeehan1968

What version of Effect is running?

3.14.5

What steps can reproduce the bug?

group.add(HttpApiEndpoint.get('listEntity')`/entities`

The current HttpApiEndpoint.<method>() API takes a template literal to define the path segment it will be on, and to allow for injection of parameters. Parameters are defined through HttpApiSchema, which appear to be Schema that accept strings. However, it does not accept primitive values (string, number etc), which prevents us from using computed values in path segments.

A lot of APIs include CRUD operations for entities, for example User, Company, etc. The path segments and supported operations can end up being identical save for the 'entity' type.

This means that we have to repeat identical group/endpoint/request/response formats that only vary for the 'entity' name and the schema. Because I can't inject a computed value for the entity into the path segment, I can't optimise my code by creating a helper than does all/most of the boiler plate.

What is the expected behavior?

group.add(HttpApiEndpoint.get(`list${name}`)`/${name}s`...

Obviously I'd need to do be more sophisticated on entity name pluralisation, but hopefully you get the point. I could then build parts of my API by using the helper, something like:

HttpApi.make('api')
  .add(crudForEntity('user', {
    entitySchema: UserSchema,
    createSchema: PartialUserSchema,
    updateSchema: MutableUserSchema,
  })
  .add(crudForEntity('company, {
    entitySchema: CompanySchema,
    createSchema: PartialCompanySchema,
    updateSchema: MutableCompanySchema,
  })

What do you see instead?

const name = 'user'
group.add(HttpApiEndpoint.get(`list${name}`)`/${name}s`...

name then produces the error

TS2345: Argument of type string is not assignable to parameter of type AnyString

Which is a bit confusing, but AnyString is a Schema of type Schema.String, not a primitive 'string'.

It would be great if the template literal handling allowed for types are string or perhaps also those that can be converted to string, so that the path segment support computed values.

Additional information

Longer example with two entities (Device and Gateway) where the operations are identical, other than changes to entity names in strings and the input/output schema.

const api = HttpApi.make('api')
  // DEVICES
  .add(HttpApiGroup.make('devices')
    .add(HttpApiEndpoint.get('listDevices')`/devices`
      .setUrlParams(ListDevicesQuerySchema)
      .addSuccess(DeviceListSchema)
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.post('createDevice')`/devices`
      .setPayload(CreateDeviceRequestSchema)
      .addSuccess(DeviceEntitySchema, { status: 201 })
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.Conflict)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.get('getDevice')`/devices/${deviceEuiParam}`
      .addSuccess(DeviceEntitySchema)
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.NotFound)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.patch('updateDevice')`/devices/${deviceEuiParam}`
      .setPayload(UpdateDeviceRequestSchema)
      .addSuccess(DeviceEntitySchema, { status: 202 })
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.NotFound)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.del('deleteDevice')`/devices/${deviceEuiParam}`
      .addSuccess(Schema.Null, { status: 204 })
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.NotFound)
      .addError(HttpApiError.InternalServerError)
    )
  )
  // GATEWAYS
  .add(HttpApiGroup.make('gateways')
    .add(HttpApiEndpoint.get('listGateways')`/gateways`
      .setUrlParams(ListGatewaysQuerySchema)
      .addSuccess(GatewayListSchema)
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.post('createGateway')`/gateways`
      .setPayload(CreateGatewayRequestSchema)
      .addSuccess(GatewayEntitySchema, { status: 201 })
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.Conflict)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.get('getGateway')`/gateways/${gatewayEuiParam}`
      .addSuccess(GatewayEntitySchema)
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.NotFound)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.patch('updateGateway')`/gateways/${gatewayEuiParam}`
      .setPayload(UpdateGatewayRequestSchema)
      .addSuccess(GatewayEntitySchema, { status: 202 })
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.NotFound)
      .addError(HttpApiError.InternalServerError)
    )
    .add(HttpApiEndpoint.del('deleteGateway')`/gateways/${gatewayEuiParam}`
      .addSuccess(Schema.Null, { status: 204 })
      .addError(HttpApiError.BadRequest)
      .addError(HttpApiError.Unauthorized)
      .addError(HttpApiError.NotFound)
      .addError(HttpApiError.InternalServerError)
    )
  )

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions