Sign in

    smithy-openapi

    Convert assembled Smithy HTTP services to OpenAPI

    smithy
    openapi
    moonbit
    Download zip
    Author
    Version
    0.5.0
    License
    Apache-2.0
    Last updated
    2 days ago
    Downloads
    16

    #Smithy OpenAPI

    Convert an assembled Smithy HTTP service to OpenAPI 3.0.2 or 3.1.0. The converter reuses moonrockz/smithy-jsonschema for value schemas.

    The reference corpus and its exact upstream revision live in tests/tck/openapi. Conversion does not execute code, read files or make network requests. The CLI owns build configuration and artifact writes.

    #Library

    ///|
    test {
    let loader = @smithy.Loader::new()
    for input in @openapi.protocol_definitions() {
    ignore(loader.add(input))
    }
    ignore(
    loader.add_idl(
    "api.smithy",
    (
    #|$version: "2"
    #|namespace example
    #|use aws.protocols#restJson1
    #|@restJson1
    #|service Api { version: "1", operations: [Ping] }
    #|@http(method: "GET", uri: "/ping")
    #|operation Ping {}
    ),
    ),
    )
    let model = loader.assemble().model.unwrap()
    let config = @openapi.Config::new(
    @openapi.ServiceId::parse("example#Api").unwrap(),
    version=@openapi.V3_1_0,
    )
    let result = @openapi.convert_result(model, config)
    assert_true(
    result.document is { "openapi": "3.1.0", "paths": { "/ping": _, .. }, .. },
    )
    assert_true(result.warnings.is_empty())
    }

    convert(model, config) returns the document alone. convert_result also returns structured warnings with a classification and affected ShapeId. Invalid settings, unresolved services/protocols and unsupported constructs raise OpenApiError; message() provides a diagnostic suitable for a CLI.

    The bundled protocols are aws.protocols#restJson1 and smithy.protocols#rpcv2Json. Apply the protocol trait to the service. A single protocol is selected automatically; use protocol=ProtocolId::parse(...).unwrap() when the service declares several. Selecting an unapplied or unimplemented protocol fails.

    protocol_definitions() supplies ordinary trait-provider inputs. When your model dependencies already define these protocols, use those definitions instead. Alternatively, assemble the bundled inputs separately and call loader.add_defaults(provider_model): explicit input definitions take precedence regardless of insertion order. The CLI supplies these defaults for OpenAPI builds.

    #Build plugin

    Put this in smithy-build.json, then run smithy build:

    { "version": "1.0", "sources": ["model"], "plugins": { "openapi": { "service": "example#Api", "version": "3.1.0" } } }

    The artifact is build/smithy/source/openapi/Api.openapi.json. Projection plugins use the projection name in place of source. Named instances such as openapi::public use public as their output directory. Conversion warnings are printed to stderr; failures make the build exit unsuccessfully.

    SettingDefault and behavior
    serviceRequired absolute root shape ID
    version3.0.2; also 3.1.0
    protocolInfer the single applied protocol
    onHttpPrefixHeadersFAIL; WARN omits the binding and reports a warning
    ignoreUnsupportedTraitsfalse; true warns and omits unsupported traits
    forbidGreedyLabelsfalse; true rejects greedy URI labels
    removeGreedyParameterSuffixfalse; true removes + from parameter names
    onErrorStatusConflictUnset; oneOf combines errors sharing a status code
    jsonContentTypeapplication/json for REST document bodies
    defaultBlobFormatbyte; becomes contentEncoding in 3.1
    disableIntegerFormatfalse; suppresses int32/int64 formats
    useStringsForArbitraryPrecisionfalse; RPC v2 JSON always uses strings
    alphanumericOnlyRefsfalse; strips punctuation from component names
    tagsfalse; exports service/operation tags when enabled
    supportedTagsAll tags; restrict with an array of names
    externalDocsOrdered preferred documentation labels: Homepage, API Reference, User Guide, Developer Guide, Reference, Guide
    keepUnusedComponentsfalse; remove components unreachable from paths/security
    substitutions{}; replace matching string values throughout the document
    jsonAdd{}; insert JSON values at JSON Pointer paths after substitutions

    The JSON Schema settings for maps, unions, enums, defaults, numeric types and reference descriptions also apply. Library callers pass them through schema=.... OpenAPI fixes the definition pointer at #/components/schemas; 3.1 schema objects follow draft 2020-12 and 3.0 adaptations remove unsupported keywords. Reference descriptions require 3.1. REST JSON forces JSON member names and epoch-second default document timestamps, as the reference protocol does. Path/query timestamps default to date-time and header timestamps to HTTP-date.

    #Conversion boundaries

    HTTP traits determine paths, methods, parameters, headers, document bodies and payloads. Service/resource operations, contextual renames, examples, streaming payloads and HTTP Basic/Bearer/Digest/API-key authentication are supported. Resource identifiers/properties and custom traits are not exported as metadata. Endpoint and host-label traits fail unless ignoreUnsupportedTraits is enabled. Greedy labels retain the reference's OpenAPI representation by default.

    Amazon API Gateway extensions and AWS authentication/service trait packages are outside this module. Unsupported authentication schemes are omitted with a warning. This is a lossy conversion; retain the Smithy model for Smithy-specific semantics.

    The pinned Java plugin's serialized output is the compatibility contract, including its externalDocumentation key at service level (externalDocs on operations/schemas), omission of empty paths, and same-status error handling.

    Two reference behaviors affect models with several services or unusual names. Unsupported endpoint/host-label checks inspect the entire assembled model; prefix-header checks inspect every operation's bindings, including operations outside the selected service. Use projections to remove unrelated constructs before conversion when needed. With onErrorStatusConflict: "oneOf", Java's model transform replaces existing shapes whose IDs match its synthesized <Operation><status>Error or <Operation><status>ErrorContent names. Avoid those names for modeled shapes when enabling that strategy. The compatibility tests preserve these behaviors explicitly.

    #Reference verification

    Normal tests compare committed expectations without Java or network access. tests/tck/openapi/cases.json assigns each upstream golden or records its explicit scope exclusion. Local fixtures exercise core features that also appear in excluded AWS/custom-extension cases.

    To refresh from the pinned source snapshot, run mise run tck:vendor:openapi. mise run tck:reference:openapi compiles hash-verified upstream Java sources with a local JDK and runs the actual openapi build plugin. Add --check to verify generated expectations without rewriting them. The scripts take an optional upstream directory; their default is .dev/reference/snapshot. Java classes and intermediate outputs remain under .dev/.

    OpenApiError

    pub(all) suberror OpenApiError {
    InvalidConfiguration(String)
    MissingService(ServiceId)
    InvalidService(ServiceId)
    ProtocolResolution(ServiceId, String)
    UnsupportedProtocol(ProtocolId)
    UnsupportedTrait(
    ShapeId
    ,
    ShapeId
    )
    PrefixHeaders(
    ShapeId
    )
    GreedyLabel(
    ShapeId
    )
    SchemaConversion(String)
    } derive(
    Debug
    )

    OpenApiError::message

    fn OpenApiError::message(self : OpenApiError) -> String

    Config

    pub struct Config {
    service : ServiceId
    protocol : ProtocolId?
    version : OpenApiVersion
    prefix_headers : PrefixHeadersPolicy
    forbid_greedy_labels : Bool
    remove_greedy_parameter_suffix : Bool
    ignore_unsupported_traits : Bool
    tags : Bool
    supported_tags : Array[String]?
    keep_unused_components : Bool
    json_content_type : String
    default_blob_format : String
    external_docs : Array[String]
    disable_integer_format : Bool
    alphanumeric_only_refs : Bool
    use_strings_for_arbitrary_precision : Bool
    combine_error_responses : Bool
    schema :
    Config

    substitutions : Map[String, Json]
    json_add : Map[String, Json]
    }

    Settings for one service conversion. Service and protocol IDs, version and omission policy are validated before conversion begins.

    Config::from_json

    fn Config::from_json(settings : Json) -> Config raise OpenApiError

    Parse the settings of the openapi build plugin. This performs no I/O.

    Config::new

    fn Config::new(service : ServiceId, protocol? : ProtocolId?, version? : OpenApiVersion, prefix_headers? : PrefixHeadersPolicy, forbid_greedy_labels? : Bool, remove_greedy_parameter_suffix? : Bool, ignore_unsupported_traits? : Bool, tags? : Bool, supported_tags? : Array[String]?, keep_unused_components? : Bool, json_content_type? : String, default_blob_format? : String, external_docs? : Array[String], disable_integer_format? : Bool, alphanumeric_only_refs? : Bool, use_strings_for_arbitrary_precision? : Bool, combine_error_responses? : Bool, schema? :
    Config
    , substitutions? : Map[String, Json], json_add? : Map[String, Json]) -> Config

    Config::service

    fn Config::service(self : Config) -> ServiceId

    Config::version

    fn Config::version(self : Config) -> OpenApiVersion

    Conversion

    pub(all) struct Conversion {
    document : Json
    warnings : Array[ConversionWarning]
    } derive(
    Debug
    )

    ConversionWarning

    pub(all) struct ConversionWarning {
    code : WarningCode
    shape :
    ShapeId

    message : String
    } derive(Eq,
    Debug
    )

    OpenApiVersion

    pub(all) enum OpenApiVersion {
    V3_0_2
    V3_1_0
    } derive(Eq,
    Debug
    )

    OpenApiVersion::parse

    fn OpenApiVersion::parse(text : String) -> OpenApiVersion?

    OpenApiVersion::to_string

    fn OpenApiVersion::to_string(self : OpenApiVersion) -> String

    PrefixHeadersPolicy

    pub(all) enum PrefixHeadersPolicy {
    Fail
    Warn
    } derive(Eq,
    Debug
    )

    ProtocolId

    The trait ID of the selected serialization protocol.

    ProtocolId::parse

    fn ProtocolId::parse(text : String) -> ProtocolId?

    ProtocolId::shape_id

    ProtocolId::to_string

    fn ProtocolId::to_string(self : ProtocolId) -> String

    ServiceId

    A root shape ID naming the service selected for conversion.

    ServiceId::parse

    fn ServiceId::parse(text : String) -> ServiceId?

    ServiceId::shape_id

    ServiceId::to_string

    fn ServiceId::to_string(self : ServiceId) -> String

    WarningCode

    pub(all) enum WarningCode {
    OmittedPrefixHeaders
    OmittedUnsupportedTrait
    OmittedHttpMethod
    OmittedAuthScheme
    } derive(Eq,
    Debug
    )

    Stable conversion warning classifications. Warnings retain the affected semantic shape instead of parsing a rendered diagnostic later.

    convert

    fn convert(model :
    Model
    , config : Config) -> Json raise OpenApiError

    convert_result

    fn convert_result(model :
    Model
    , config : Config) -> Conversion raise OpenApiError

    Convert one assembled service and retain warnings about omitted constructs.

    protocol_definitions

    fn protocol_definitions() -> Array[
    ModelInput
    ]

    Trait definitions required by the bundled protocol converters. Callers that already load these definitions from model dependencies can use those instead. The inputs are ordinary provider models; this function performs no I/O.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io