oxidecomputer / progenitor

An OpenAPI client generator
426 stars 55 forks source link

Can't build Stripe's OpenAPI spec with "not yet implemented". panic from proc macro from `typify-impl` #850

Open cowlicks opened 1 week ago

cowlicks commented 1 week ago

The OpenAPI spec is here.

The failure is at "typify-288d5a84bbbe6a46/25e11d8/typify-impl/src/util.rs:246:25:"

When building with the macro, the error comes from a proc macro panic which offers very little context:

    Checking redacted v0.1.0 (redacted)
error: proc macro panicked
  --> redacted/src/main.rs:40:1
   |
40 | generate_api!("../../../stripe-openapi/openapi/spec3.json");
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: message: not yet implemented

error: could not compile redacted` (bin "redacted") due to 1 previous error

I got more info running it from a build.rs:

 RUST_BACKTRACE=1 cargo c
warning: unused variable: `src`
 --> redacted/build.rs:6:9
  |
6 |     let src = "../../../stripe-openapi/openapi/fixtures3.json";
  |         ^^^ help: if this is intentional, prefix it with an underscore: `_src`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: `redacted` (build script) generated 1 warning
   Compiling redacted v0.1.0 (redacted
error: failed to run custom build command for `redacted v0.1.0 (redacted)`
note: To improve backtraces for build dependencies, set the CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable to enable debug information generation.

Caused by:
  process didn't exit successfully: `redacted/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-changed=../migrations
  cargo:rerun-if-changed=../../../stripe-openapi/openapi/spec3.json

  --- stderr
  thread 'main' panicked at /Users/nbajskansbbbelopni/.cargo/git/checkouts/typify-288d5a84bbbe6a46/25e11d8/typify-impl/src/util.rs:246:25:
  not yet implemented
  stack backtrace:
     0: rust_begin_unwind
               at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/std/src/panicking.rs:647:5
     1: core::panicking::panic_fmt
               at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/panicking.rs:72:14
     2: core::panicking::panic
               at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/panicking.rs:144:5
     3: typify_impl::util::schemas_mutually_exclusive
     4: typify_impl::util::all_mutually_exclusive::{{closure}}
     5: core::iter::traits::iterator::Iterator::all::check::{{closure}}
     6: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut
     7: core::iter::adapters::map::map_try_fold::{{closure}}
     8: core::iter::traits::iterator::Iterator::try_fold
     9: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::try_fold
    10: <core::iter::adapters::flatten::FlattenCompat<I,U> as core::iter::traits::iterator::Iterator>::try_fold::flatten::{{closure}}
    11: core::iter::adapters::flatten::FlattenCompat<I,U>::iter_try_fold::flatten::{{closure}}
    12: core::iter::adapters::map::map_try_fold::{{closure}}
    13: core::iter::traits::iterator::Iterator::try_fold
    14: <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::try_fold
    15: <core::iter::adapters::fuse::Fuse<I> as core::iter::adapters::fuse::FuseImpl<I>>::try_fold
    16: core::iter::adapters::flatten::FlattenCompat<I,U>::iter_try_fold
    17: <core::iter::adapters::flatten::FlatMap<I,U,F> as core::iter::traits::iterator::Iterator>::try_fold
    18: core::iter::traits::iterator::Iterator::all
    19: typify_impl::util::all_mutually_exclusive
    20: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_any_of
    21: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema_object
    22: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema
    23: typify_impl::TypeSpace::id_for_schema
    24: typify_impl::structs::<impl typify_impl::TypeSpace>::struct_property
    25: typify_impl::structs::<impl typify_impl::TypeSpace>::struct_members::{{closure}}
    26: core::iter::adapters::filter_map::filter_map_try_fold::{{closure}}
    27: core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut
    28: core::iter::traits::iterator::Iterator::try_fold
    29: <core::iter::adapters::chain::Chain<A,B> as core::iter::traits::iterator::Iterator>::try_fold
    30: <core::iter::adapters::filter_map::FilterMap<I,F> as core::iter::traits::iterator::Iterator>::try_fold
    31: <core::iter::adapters::GenericShunt<I,R> as core::iter::traits::iterator::Iterator>::try_fold
    32: <core::iter::adapters::GenericShunt<I,R> as core::iter::traits::iterator::Iterator>::next
    33: alloc::vec::Vec<T,A>::extend_desugared
    34: <alloc::vec::Vec<T,A> as alloc::vec::spec_extend::SpecExtend<T,I>>::spec_extend
    35: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter
    36: <alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter
    37: <alloc::vec::Vec<T> as core::iter::traits::collect::FromIterator<T>>::from_iter
    38: <core::result::Result<V,E> as core::iter::traits::collect::FromIterator<core::result::Result<A,E>>>::from_iter::{{closure}}
    39: core::iter::adapters::try_process
    40: <core::result::Result<V,E> as core::iter::traits::collect::FromIterator<core::result::Result<A,E>>>::from_iter
    41: core::iter::traits::iterator::Iterator::collect
    42: typify_impl::structs::<impl typify_impl::TypeSpace>::struct_members
    43: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_object
    44: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema_object
    45: typify_impl::convert::<impl typify_impl::TypeSpace>::convert_schema
    46: typify_impl::TypeSpace::convert_ref_type
    47: typify_impl::TypeSpace::add_ref_types_impl
    48: typify_impl::TypeSpace::add_ref_types
    49: progenitor_impl::Generator::generate_tokens
    50: build_script_build::main
    51: core::ops::function::FnOnce::call_once
  note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
ahl commented 1 week ago

I think this proximate failure may be addressed by the fix for https://github.com/oxidecomputer/typify/issues/616. Once I try that I get:

gen fail: UnexpectedFormat("unsupported style of query parameter DeepObject")
Error: generation experienced errors

This seems to be caused by this:

    "/v1/account": {
      "get": {
        "description": "<p>Retrieves the details of an account.</p>",
        "operationId": "GetAccount",
        "parameters": [
          {
            "description": "Specifies which fields in the response should be expanded.",
            "explode": true,
            "in": "query",
            "name": "expand",
            "required": false,
            "schema": {
              "items": {
                "maxLength": 5000,
                "type": "string"
              },
              "type": "array"
            },
            "style": "deepObject"
          }
        ],

It's a bit confusing: style: deepObject is only defined for non-nested objects; it's behavior for arrays is undefined.

I see other uses of style: deepObject that would take some work to support...

cowlicks commented 1 week ago

FWIW I moved on to using async-stripe, however they are using the OpenAPI spec to generate their client. That code is here: https://github.com/arlyon/async-stripe/tree/master/openapi

ahl commented 1 week ago

That seems like a good option!

Some notes for myself if we get back here: async-stripe appears to be partially hand-written and tailored particularly to the Stripe API. For example, it contains special handling for this "expand" query parameter:

https://github.com/arlyon/async-stripe/blob/0a00d31894191ee0c6b4bda31e0d52d59e8e93b7/openapi/src/codegen.rs#L364-L369

It uses serde_qs, but that seems to produce output different than what the Stripe docs describe. For example:

Stripe docs:

curl https://api.stripe.com/v1/charges/ch_3LmzzQ2eZvKYlo2C0XjzUzJV \
  -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
  -d "expand[]"=customer \
  -d "expand[]"="invoice.subscription" \
  -G

serde_qs:

#[derive(Deserialize, Serialize, Debug, Clone)]
struct Foo {
    expand: Vec<String>,
}

fn main() {
    let foo = Foo {
        expand: vec!["customer".to_string(), "invoice.subscription".to_string()],
    };

    let out = serde_qs::to_string(&foo).unwrap();
    assert_eq!(out, "expand[0]=customer&expand[1]=invoice.subscription");
 }

I'd bet that the Stripe API is using Javascript's qs or something similar so it probably accepts that as well (even though serde_qs doesn't accept the form with indices present).